# Incognito Starlight Wiki Migration Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build a branded, public Starlight knowledge base from the 29 Incognito pages in the DokuWiki export, while keeping newer live-wiki material unpublished for later comparison. **Architecture:** Use a static Astro 7 site with Starlight, a hand-maintained sidebar, and manually rewritten Markdown/MDX content under `src/content/docs/`. Node-based validators enforce the migration manifest, reject leftover DokuWiki syntax, and verify built internal links; `to-be-studied/` remains outside the published content collection. **Tech Stack:** Node.js 22.22.3 or newer, npm 10 or newer, Astro 7.1.6, `@astrojs/starlight` 0.41.6, `@astrojs/check` 0.9.10, TypeScript, Node's built-in test runner, LinkeDOM, and Turndown. ## Global Constraints - Publish only `/Volumes/S/Incognito/DokuWiki Export/pages/start.txt` and `/Volumes/S/Incognito/DokuWiki Export/pages/study/**`; never publish `/pages/wiki/**`. - Perform a fully manual page-by-page DokuWiki-to-Markdown migration; do not build a DokuWiki converter. - Keep the public website read-only and unauthenticated; editing happens through Git. - Preserve empty course and year pages as discoverable placeholders. - Correct spelling and obvious grammar without changing factual meaning. - Mark uncertain or time-sensitive information as originating in the previous wiki and possibly outdated. - Replace missing images and unresolved links with explicit notices, and document them in the migration report. - Use the current MSV Incognito logo, favicon, and brand colors from `https://msvincognito.nl/`. - Keep all newer live-wiki captures under `to-be-studied/`; nothing there may enter `src/content/docs/` or `public/`. - Local `npm` commands are authoritative; Forgejo CI must not be required. - Keep Netlify and GitHub Pages support optional and removable. - Do not commit credentials, deployment tokens, or private repository access data. - Use relative published-content links and Starlight sidebar slugs so the same content works at `/` and at a configured `BASE` subpath. ## File Structure ```text Incognito-Wiki/ ├── .github/workflows/deploy.yml # Optional GitHub mirror deployment ├── .gitignore ├── .nvmrc ├── README.md ├── astro.config.mjs # Astro/Starlight, SITE/BASE, branding, sidebar ├── docs/ │ ├── brand-sources.md │ ├── migration-manifest.json # Exact 29-source migration contract │ ├── migration-report.md # Human-readable migration decisions │ └── superpowers/ ├── netlify.toml ├── package-lock.json ├── package.json ├── public/favicon.ico ├── scripts/ │ ├── audit-content.mjs │ ├── capture-live-wiki.mjs │ ├── check-internal-links.mjs │ ├── check-migration.mjs │ └── lib/ │ ├── internal-links.mjs │ ├── live-wiki.mjs │ └── migration.mjs ├── src/ │ ├── assets/ │ │ ├── logo-dark.png │ │ └── logo.svg │ ├── config/sidebar.mjs │ ├── content.config.ts │ ├── content/docs/ # 28 destinations for 29 source pages │ └── styles/incognito.css ├── tests/ │ ├── content-audit.test.mjs │ ├── internal-links.test.mjs │ ├── live-wiki.test.mjs │ ├── migration-manifest.test.mjs │ └── project-structure.test.mjs ├── to-be-studied/ │ ├── README.md │ ├── comparison.md │ ├── manifest.json │ └── live-wiki/2026-08-02/ └── tsconfig.json ``` ## Primary References - Approved design: `docs/superpowers/specs/2026-08-02-starlight-wiki-migration-design.md` - Starlight setup: `https://starlight.astro.build/getting-started/` - Starlight configuration: `https://starlight.astro.build/reference/configuration/` - Starlight asides: `https://starlight.astro.build/components/asides/` - Astro GitHub Pages deployment: `https://docs.astro.build/en/guides/deploy/github/` - Astro Netlify deployment: `https://docs.astro.build/en/guides/deploy/netlify/` --- ### Task 1: Scaffold the Astro and Starlight foundation **Files:** - Create: `.gitignore` - Create: `.nvmrc` - Create: `package.json` - Create: `package-lock.json` via npm - Create: `astro.config.mjs` - Create: `src/config/sidebar.mjs` - Create: `src/content.config.ts` - Create: `src/content/docs/index.mdx` - Create: `tsconfig.json` - Create: `tests/project-structure.test.mjs` **Interfaces:** - Produces: `npm run dev`, `npm run build`, `npm run check`, and `npm test`. - Produces: `SITE` and `BASE` environment inputs, defaulting to `http://localhost:4321` and `/`. - Produces: `sidebar` exported by `src/config/sidebar.mjs` for later content tasks. - [ ] **Step 1: Write the failing project-structure test** ```js // tests/project-structure.test.mjs import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import test from 'node:test'; test('package scripts expose the local workflow', async () => { const pkg = JSON.parse(await readFile('package.json', 'utf8')); assert.equal(pkg.scripts.dev, 'astro dev'); assert.equal(pkg.scripts.build, 'astro build'); assert.equal(pkg.scripts.check, 'astro check && node --test'); assert.equal(pkg.scripts.test, 'node --test'); }); test('Astro configuration is environment-portable', async () => { const config = await readFile('astro.config.mjs', 'utf8'); assert.match(config, /process\.env\.SITE/); assert.match(config, /process\.env\.BASE/); assert.match(config, /starlight\(/); }); ``` - [ ] **Step 2: Run the test and confirm the empty repository fails** Run: `node --test tests/project-structure.test.mjs` Expected: FAIL with `ENOENT` for `package.json`. - [ ] **Step 3: Create the minimal package and configuration files** Use this package contract, then run `npm install` to generate the lockfile: ```json { "name": "incognito-wiki", "version": "0.1.0", "private": true, "type": "module", "engines": { "node": ">=22.12.0" }, "scripts": { "dev": "astro dev", "build": "astro build", "preview": "astro preview", "check": "astro check && node --test", "test": "node --test" }, "dependencies": { "@astrojs/starlight": "0.41.6", "astro": "7.1.6", "sharp": "0.35.3" }, "devDependencies": { "@astrojs/check": "0.9.10", "typescript": "6.0.2" } } ``` Set `.nvmrc` to `22.22.3`. Ignore `node_modules/`, `dist/`, `.astro/`, `.DS_Store`, and `.env*` except `.env.example`. Configure Starlight with the title `Incognito Wiki`, the description `Academic knowledge and student resources maintained by MSV Incognito.`, `lastUpdated: true`, Pagefind enabled, and an initial Home-only sidebar: ```js // src/config/sidebar.mjs export const sidebar = [{ label: 'Home', link: '/' }]; ``` ```js // astro.config.mjs import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; import { sidebar } from './src/config/sidebar.mjs'; const site = process.env.SITE || 'http://localhost:4321'; const base = process.env.BASE || '/'; export default defineConfig({ site, base, integrations: [ starlight({ title: 'Incognito Wiki', description: 'Academic knowledge and student resources maintained by MSV Incognito.', lastUpdated: true, sidebar, }), ], }); ``` Use Starlight's `docsLoader()` and `docsSchema()` in `src/content.config.ts`. Create a temporary `src/content/docs/index.mdx` with valid frontmatter and the sentence `Migration in progress.`; it will be replaced in Task 3. - [ ] **Step 4: Run the foundation checks** Run: `npm test && npm run check && npm run build` Expected: all tests pass, Astro check reports no errors, and `dist/index.html` exists. - [ ] **Step 5: Commit the foundation** ```bash git add .gitignore .nvmrc package.json package-lock.json astro.config.mjs tsconfig.json src tests/project-structure.test.mjs git commit -m "build: scaffold Incognito Starlight site" ``` --- ### Task 2: Define and test the 29-page migration contract **Files:** - Create: `docs/migration-manifest.json` - Create: `scripts/lib/migration.mjs` - Create: `scripts/check-migration.mjs` - Create: `tests/migration-manifest.test.mjs` - Modify: `package.json` **Interfaces:** - Produces: `loadManifest(path)`, `validateManifest(manifest)`, and `checkDestinations(manifest, root)`. - Produces: `npm run check:migration`. - Consumes: future destination files under `src/content/docs/`; missing destinations are allowed only when `--allow-missing` is passed during staged migration. - [ ] **Step 1: Write manifest validation tests** ```js // tests/migration-manifest.test.mjs import assert from 'node:assert/strict'; import test from 'node:test'; import { loadManifest, validateManifest } from '../scripts/lib/migration.mjs'; test('manifest accounts for exactly 29 unique source pages', async () => { const manifest = await loadManifest('docs/migration-manifest.json'); const result = validateManifest(manifest); assert.equal(result.entries.length, 29); assert.equal(new Set(result.entries.map(({ source }) => source)).size, 29); }); test('generic DokuWiki pages are excluded', async () => { const manifest = await loadManifest('docs/migration-manifest.json'); assert.equal(manifest.entries.some(({ source }) => source.startsWith('pages/wiki/')), false); }); ``` - [ ] **Step 2: Run the manifest tests and confirm they fail** Run: `node --test tests/migration-manifest.test.mjs` Expected: FAIL because the manifest and module do not exist. - [ ] **Step 3: Create the exact source-to-destination manifest** Create JSON with `version: 1`, `sourceRoot: "/Volumes/S/Incognito/DokuWiki Export"`, and these entries: ```text pages/start.txt -> src/content/docs/index.mdx pages/study.txt -> src/content/docs/index.mdx (merge) pages/study/msv_incognito.txt -> src/content/docs/about-incognito.md pages/study/bachelor.txt -> src/content/docs/bachelor/index.md pages/study/bachelor/year_1.txt -> src/content/docs/bachelor/year-1/index.md pages/study/bachelor/year_1/project_1-1.txt -> src/content/docs/bachelor/year-1/project-1-1.md pages/study/bachelor/year_1/project_1-2.txt -> src/content/docs/bachelor/year-1/project-1-2.md pages/study/bachelor/year_2.txt -> src/content/docs/bachelor/year-2/index.md pages/study/bachelor/year_2/honours_programme.txt -> src/content/docs/bachelor/year-2/honours-programme.md pages/study/bachelor/year_2/project_2-1.txt -> src/content/docs/bachelor/year-2/project-2-1.md pages/study/bachelor/year_2/project_2-2.txt -> src/content/docs/bachelor/year-2/project-2-2.md pages/study/bachelor/year_3.txt -> src/content/docs/bachelor/year-3/index.md pages/study/bachelor/year_3/bachelors_thesis.txt -> src/content/docs/bachelor/year-3/bachelors-thesis.md pages/study/bachelor/year_3/honours_programme.txt -> src/content/docs/bachelor/year-3/honours-programme.md pages/study/bachelor/year_3/project_3-1.txt -> src/content/docs/bachelor/year-3/project-3-1.md pages/study/bachelor/year_3/study_abroad.txt -> src/content/docs/bachelor/year-3/study-abroad.md pages/study/master_ai.txt -> src/content/docs/master-ai/index.md pages/study/master_ai/year_1.txt -> src/content/docs/master-ai/year-1/index.md pages/study/master_ai/year_1/project_mai_1.txt -> src/content/docs/master-ai/year-1/research-project-1.md pages/study/master_ai/year_1/project_mai_2.txt -> src/content/docs/master-ai/year-1/research-project-2.md pages/study/master_ai/year_2.txt -> src/content/docs/master-ai/year-2/index.md pages/study/master_dsdm.txt -> src/content/docs/master-dsdm/index.md pages/study/master_dsdm/year_1.txt -> src/content/docs/master-dsdm/year-1/index.md pages/study/master_dsdm/year_2.txt -> src/content/docs/master-dsdm/year-2/index.md pages/study/useful_information.txt -> src/content/docs/useful-information/index.md pages/study/useful_information/pages/dke_locations.txt -> src/content/docs/useful-information/dke-locations.md pages/study/useful_information/pages/handy_locations.txt -> src/content/docs/useful-information/handy-locations.md pages/study/useful_information/pages/it_services.txt -> src/content/docs/useful-information/it-services.md pages/study/useful_information/pages/laptop_buy_advice.txt -> src/content/docs/useful-information/laptop-buying-advice.md ``` Each entry must contain `source`, `destination`, and `mode` (`page` or `merge`). Only the two home sources share a destination, and only `pages/study.txt` uses `mode: "merge"`. - [ ] **Step 4: Implement validation and staged destination checking** `validateManifest()` must reject a non-array `entries`, a count other than 29, duplicate sources, `pages/wiki/` sources, paths outside `pages/start.txt` or `pages/study`, destinations outside `src/content/docs/`, and duplicate destinations unless the later entry has `mode: "merge"`. `scripts/check-migration.mjs` must parse `--allow-missing`; without it, report each missing destination and exit non-zero. Add: ```json "check:migration": "node scripts/check-migration.mjs" ``` - [ ] **Step 5: Run contract tests in staged mode** Run: `npm test && node scripts/check-migration.mjs --allow-missing` Expected: all tests pass and the command reports 29 accounted sources plus the not-yet-created destinations without failing. - [ ] **Step 6: Commit the migration contract** ```bash git add docs/migration-manifest.json scripts package.json tests/migration-manifest.test.mjs git commit -m "test: define exported wiki migration contract" ``` --- ### Task 3: Apply Incognito branding and migrate the home and association pages **Files:** - Create: `public/favicon.ico` - Create: `src/assets/logo.svg` - Create: `src/assets/logo-dark.png` - Create: `src/styles/incognito.css` - Create: `docs/brand-sources.md` - Modify: `astro.config.mjs` - Modify: `src/config/sidebar.mjs` - Replace: `src/content/docs/index.mdx` - Create: `src/content/docs/about-incognito.md` - Modify: `tests/project-structure.test.mjs` **Interfaces:** - Consumes: official assets at `https://msvincognito.nl/assets/logo.svg`, `/assets/logo-dark.png`, and `/favicon.ico`. - Produces: reusable Starlight status styles and branded light/dark color tokens. - Produces routes: `/` and `/about-incognito/` from three source pages. - [ ] **Step 1: Add failing branding and route assertions** Test that all three asset files exist and are non-empty, `astro.config.mjs` contains `customCss`, `logo`, `favicon`, and the association URL, and both content destinations exist. Run: `node --test tests/project-structure.test.mjs` Expected: FAIL for missing branding assets and association page. - [ ] **Step 2: Download and document official assets** Download the three official assets to the exact paths above. In `docs/brand-sources.md`, record each source URL, destination, capture date `2026-08-02`, and these colors recovered from the association stylesheet: ```text Ink/navy: #071526 Dark navy: #10253d Primary blue: #155eef Bright blue: #2f7cff Light blue: #eaf2ff Soft blue: #eef4ff Pink accent: #ef3b8f Orange accent: #f57a32 Violet accent: #5746d8 ``` - [ ] **Step 3: Add brand tokens and Starlight configuration** Use `#155eef`/`#2f7cff` for the Starlight accent, navy for high-contrast text, and the pink/orange/violet colors only as restrained secondary accents. Override Starlight variables in both themes, keep WCAG-readable contrast, and add styles for `.status-empty` and `.status-missing` without changing Starlight's core layout. Configure separate light/dark logo sources, `/favicon.ico`, `customCss: ['./src/styles/incognito.css']`, an external social/header link to `https://msvincognito.nl/`, and this edit base: ```js editLink: { baseUrl: 'https://git.msvincognito.nl/Incognito-Tech/Incognito-Wiki/_edit/main/', } ``` - [ ] **Step 4: Manually migrate the two home sources and association source** Rewrite `pages/start.txt` and `pages/study.txt` into one splash-style `index.mdx`. Preserve the welcome, useful official links, Maastricht University overview, fields of study, and the historical programme descriptions. Remove claims about member-only viewing, registration, browser editing, minutes, manuals, and unexported pages. Replace the old two-column DokuWiki table with Starlight `CardGrid`/`LinkCard` navigation to Bachelor, Master AI, Master DSDM, Useful Information, and About Incognito. Convert the YouTube iframe to a normal `https://www.youtube.com/watch?v=__N81DcxsEw` link unless an accessible responsive embed is implemented and tested. Rewrite `pages/study/msv_incognito.txt` as `about-incognito.md`, retaining the association description and official website link. Correct `Artifical` to `Artificial` and other obvious spelling errors. - [ ] **Step 5: Expand the manual sidebar** Add Home and About Incognito links plus empty groups for Bachelor, Master AI, Master DSDM, and Useful Information. Later tasks fill group items with exact slugs. - [ ] **Step 6: Verify the branded foundation** Run: `npm run check && npm run build` Expected: PASS; the build contains `/index.html` and `/about-incognito/index.html`. - [ ] **Step 7: Commit branding and core pages** ```bash git add astro.config.mjs public src docs/brand-sources.md tests/project-structure.test.mjs git commit -m "feat: add Incognito branding and wiki landing pages" ``` --- ### Task 4: Migrate the Bachelor overview and Year 1 **Files:** - Create: `src/content/docs/bachelor/index.md` - Create: `src/content/docs/bachelor/year-1/index.md` - Create: `src/content/docs/bachelor/year-1/project-1-1.md` - Create: `src/content/docs/bachelor/year-1/project-1-2.md` - Modify: `src/config/sidebar.mjs` - Create: `tests/content-audit.test.mjs` **Interfaces:** - Produces routes: `/bachelor/`, `/bachelor/year-1/`, and both Year 1 project pages. - Establishes shared page conventions: required `title`/`description`, historical caution aside, missing-asset danger aside, and empty-page note aside. - [ ] **Step 1: Write failing content convention tests** ```js // tests/content-audit.test.mjs import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import test from 'node:test'; const forbidden = [/\[\[/, /\{\{/, /~~NOCACHE~~/, /NEWPAGE>/, /indexmenu>/]; for (const file of [ 'src/content/docs/bachelor/index.md', 'src/content/docs/bachelor/year-1/index.md', 'src/content/docs/bachelor/year-1/project-1-1.md', 'src/content/docs/bachelor/year-1/project-1-2.md', ]) { test(`${file} contains clean migrated Markdown`, async () => { const content = await readFile(file, 'utf8'); assert.match(content, /^---\n[\s\S]*title:/); assert.match(content, /This information originated in the previous wiki and may be outdated\./); for (const pattern of forbidden) assert.doesNotMatch(content, pattern); }); } ``` - [ ] **Step 2: Run tests and confirm the four pages are missing** Run: `node --test tests/content-audit.test.mjs` Expected: FAIL with `ENOENT`. - [ ] **Step 3: Manually migrate the Bachelor overview** Retain the programme description and official Maastricht University education link. Remove DokuWiki page-creation instructions and `indexmenu`. Use the corrected title `Data Science and Artificial Intelligence`. Add the standard historical caution because programme names and course structures are time-sensitive. - [ ] **Step 4: Manually migrate Year 1 and its projects** For Year 1, retain schedule context but replace `study:dke-schedule.png` with: ```md :::danger[Missing source asset] The previous wiki referenced `study:dke-schedule.png`, but that file was not included in the export. ::: ``` Remove `NEWPAGE` and `indexmenu`. Preserve the complete Project 1-1 and Project 1-2 course descriptions, prerequisites, and recommended reading. Fix grammar such as subject-verb agreement and punctuation without changing prerequisite meaning. Add the historical caution to all four pages. - [ ] **Step 5: Add exact Bachelor and Year 1 sidebar items** Use slugs `bachelor`, `bachelor/year-1`, `bachelor/year-1/project-1-1`, and `bachelor/year-1/project-1-2` in that order. - [ ] **Step 6: Verify and commit Year 1** Run: `npm test && npm run check && npm run build` Expected: PASS. ```bash git add src/content/docs/bachelor src/config/sidebar.mjs tests/content-audit.test.mjs git commit -m "content: migrate Bachelor overview and year one" ``` --- ### Task 5: Migrate Bachelor Year 2 **Files:** - Create: `src/content/docs/bachelor/year-2/index.md` - Create: `src/content/docs/bachelor/year-2/honours-programme.md` - Create: `src/content/docs/bachelor/year-2/project-2-1.md` - Create: `src/content/docs/bachelor/year-2/project-2-2.md` - Modify: `src/config/sidebar.mjs` - Modify: `tests/content-audit.test.mjs` **Interfaces:** - Produces the complete `/bachelor/year-2/` route group. - [ ] **Step 1: Add all four Year 2 paths to the content audit test** Also assert that `honours-programme.md` contains `:::note[Awaiting content]` and `This page is awaiting content.`. Run: `npm test` Expected: FAIL for the missing Year 2 destinations. - [ ] **Step 2: Manually migrate the Year 2 index** Retain the schedule introduction, add the same missing `study:dke-schedule.png` danger aside, remove `NEWPAGE`/`indexmenu`, and delete the malformed DokuWiki media-upload link. Replace the upload instruction with a short repository-maintenance sentence only if it helps contributors; do not expose a private edit URL in page body. - [ ] **Step 3: Preserve the empty honours page** Use the title `Honours Programme`, the standard historical caution, and the exact awaiting-content note. Do not invent course details. - [ ] **Step 4: Manually migrate Projects 2-1 and 2-2** Preserve full descriptions, prerequisites, and recommended reading. Correct grammar while retaining the stated dependency chain: Project 1-1 leads to 2-1, Project 1-2 leads to 2-2, and 2-1 is stated as a prerequisite for 3-1. - [ ] **Step 5: Add Year 2 sidebar items and verify** Add the index, both projects, and Honours Programme with the placeholder last. Run: `npm test && npm run check && npm run build` Expected: PASS. - [ ] **Step 6: Commit Year 2** ```bash git add src/content/docs/bachelor/year-2 src/config/sidebar.mjs tests/content-audit.test.mjs git commit -m "content: migrate Bachelor year two" ``` --- ### Task 6: Migrate Bachelor Year 3 **Files:** - Create: `src/content/docs/bachelor/year-3/index.md` - Create: `src/content/docs/bachelor/year-3/bachelors-thesis.md` - Create: `src/content/docs/bachelor/year-3/honours-programme.md` - Create: `src/content/docs/bachelor/year-3/project-3-1.md` - Create: `src/content/docs/bachelor/year-3/study-abroad.md` - Modify: `src/config/sidebar.mjs` - Modify: `tests/content-audit.test.mjs` **Interfaces:** - Produces the complete `/bachelor/year-3/` route group. - [ ] **Step 1: Add the five Year 3 paths to content audit tests** Assert the honours page has the awaiting-content note and `study-abroad.md` contains a caution mentioning `2022`. Run: `npm test` Expected: FAIL for missing Year 3 content. - [ ] **Step 2: Manually migrate the Year 3 index, thesis, and project** Retain the year schedule explanation, Bachelor's Thesis description, Project 3-1 description, prerequisites, and recommended reading. Remove DokuWiki controls. Correct grammar while preserving factual meaning. Add the standard historical caution to each page. - [ ] **Step 3: Preserve the Year 3 honours placeholder** Use title `DKE Honours Programme — KE@Work (3-1)` and the exact awaiting-content note. Do not infer details. - [ ] **Step 4: Manually migrate Study Abroad with a specific warning** Preserve the motivation-letter guidance and historical advice. Clearly state that the named cities, GPA threshold, fees, and selection process were documented in 2022 and must be verified against current Maastricht University exchange information. Link to an official current UM study-abroad landing page only after verifying the target. - [ ] **Step 5: Add sidebar items, verify, and commit** Run: `npm test && npm run check && npm run build` Expected: PASS. ```bash git add src/content/docs/bachelor/year-3 src/config/sidebar.mjs tests/content-audit.test.mjs git commit -m "content: migrate Bachelor year three" ``` --- ### Task 7: Migrate the Master AI section **Files:** - Create: `src/content/docs/master-ai/index.md` - Create: `src/content/docs/master-ai/year-1/index.md` - Create: `src/content/docs/master-ai/year-1/research-project-1.md` - Create: `src/content/docs/master-ai/year-1/research-project-2.md` - Create: `src/content/docs/master-ai/year-2/index.md` - Modify: `src/config/sidebar.mjs` - Modify: `tests/content-audit.test.mjs` **Interfaces:** - Produces the complete `/master-ai/` route group. - [ ] **Step 1: Add all five Master AI destinations to content audit tests** Run: `npm test` Expected: FAIL for missing Master AI files. - [ ] **Step 2: Manually migrate the programme overview and both year pages** Retain programme, core-course, elective, credit, and thesis descriptions. Remove page-creation and cache directives. Add the standard historical caution because the course lists and programme name may have changed. Retain the official UM education link. - [ ] **Step 3: Manually migrate Research Project MAI 1 and 2** Preserve the semester structure, group size, deliverables, prerequisites, and recommended reading. Correct `No Prerequisites` to `No prerequisites.` and normalize punctuation without changing assessment claims. - [ ] **Step 4: Add the Master AI sidebar hierarchy** Use index, Year 1 with both project pages, then Year 2. Use explicit sidebar entries rather than autogeneration. - [ ] **Step 5: Verify and commit Master AI** Run: `npm test && npm run check && npm run build` Expected: PASS. ```bash git add src/content/docs/master-ai src/config/sidebar.mjs tests/content-audit.test.mjs git commit -m "content: migrate Master AI section" ``` --- ### Task 8: Migrate the Master DSDM section **Files:** - Create: `src/content/docs/master-dsdm/index.md` - Create: `src/content/docs/master-dsdm/year-1/index.md` - Create: `src/content/docs/master-dsdm/year-2/index.md` - Modify: `src/config/sidebar.mjs` - Modify: `tests/content-audit.test.mjs` **Interfaces:** - Produces the complete `/master-dsdm/` route group. - [ ] **Step 1: Add all three DSDM destinations to content audit tests** Run: `npm test` Expected: FAIL for missing files. - [ ] **Step 2: Manually migrate the DSDM overview and years** Retain the programme description, course blocks, electives, project structure, credits, and thesis information. Remove DokuWiki-only controls and contribution instructions. Add the historical caution to all pages and keep the verified official UM education link. - [ ] **Step 3: Add explicit DSDM sidebar items** Use index, Year 1, then Year 2. - [ ] **Step 4: Verify and commit DSDM** Run: `npm test && npm run check && npm run build` Expected: PASS. ```bash git add src/content/docs/master-dsdm src/config/sidebar.mjs tests/content-audit.test.mjs git commit -m "content: migrate Master DSDM section" ``` --- ### Task 9: Migrate Useful Information **Files:** - Create: `src/content/docs/useful-information/index.md` - Create: `src/content/docs/useful-information/dke-locations.md` - Create: `src/content/docs/useful-information/handy-locations.md` - Create: `src/content/docs/useful-information/it-services.md` - Create: `src/content/docs/useful-information/laptop-buying-advice.md` - Modify: `src/config/sidebar.mjs` - Modify: `tests/content-audit.test.mjs` **Interfaces:** - Produces the complete `/useful-information/` route group. - [ ] **Step 1: Add all five Useful Information destinations to content audit tests** Assert `it-services.md` and `laptop-buying-advice.md` contain the standard historical caution, and `handy-locations.md` contains at least one awaiting-content note. Run: `npm test` Expected: FAIL for missing files. - [ ] **Step 2: Migrate the overview and locations pages** Remove DokuWiki creation/index controls. Preserve the Paul-Henri Spaaklaan address and label BOU8-10, Tapijn Building Z, and SSK39 explicitly as historical locations. Verify current official and Google Maps links before retaining them. Keep the unfinished Food and Other sections in Handy Locations as awaiting-content notices. - [ ] **Step 3: Migrate IT Services conservatively** Preserve the old instructions for reference but place a prominent page-level historical warning before them. Mark EleUM, `unimaas.nl`, old MyUM navigation, 2016–2017 timetable language, VPN, printing, service-desk hours, and contact details as items readers must verify. Replace external URLs only when the current official UM successor is unambiguous. Correct `Eletctronic`, `he Education Office`, `you personal computer`, `LaTex`, and similar spelling/grammar errors. - [ ] **Step 4: Migrate Laptop Buying Advice conservatively** Preserve the complete budget tiers and recommendations as historical guidance. Add a specific warning that prices, Windows versions, hardware capacities, and processor availability are dated. Correct plurals, `en` to `and`, `Macbooks` to `MacBooks`, `CPU's` to `CPUs`, `recomment` to `recommend`, and hyphenation without updating the numeric recommendations. - [ ] **Step 5: Add sidebar items, verify, and commit** Run: `npm test && npm run check && npm run build` Expected: PASS. ```bash git add src/content/docs/useful-information src/config/sidebar.mjs tests/content-audit.test.mjs git commit -m "content: migrate useful student information" ``` --- ### Task 10: Capture the newer live wiki under `to-be-studied/` **Files:** - Create: `scripts/lib/live-wiki.mjs` - Create: `scripts/capture-live-wiki.mjs` - Create: `tests/live-wiki.test.mjs` - Create: `to-be-studied/README.md` - Create: `to-be-studied/manifest.json` via capture - Create: `to-be-studied/live-wiki/2026-08-02/**` via capture - Create: `to-be-studied/comparison.md` - Modify: `package.json` **Interfaces:** - Produces: `normalizeWikiUrl(url)`, `routeToCapturePath(url)`, `extractPage(html, url)`, and `crawlWiki(options)`. - Produces: `npm run capture:live-wiki`. - Consumes only public `https://wiki.msvincognito.nl/` pages; it must not authenticate or submit forms. - [ ] **Step 1: Write crawler unit tests with local HTML fixtures** ```js // tests/live-wiki.test.mjs import assert from 'node:assert/strict'; import test from 'node:test'; import { extractPage, normalizeWikiUrl, routeToCapturePath } from '../scripts/lib/live-wiki.mjs'; test('normalizes only same-origin content routes', () => { assert.equal(normalizeWikiUrl('/useful-guides/description'), 'https://wiki.msvincognito.nl/useful-guides/description'); assert.equal(normalizeWikiUrl('https://example.com/page'), null); }); test('maps routes to readable Markdown files', () => { assert.equal(routeToCapturePath('https://wiki.msvincognito.nl/useful-guides/description'), 'useful-guides/description.md'); }); test('extracts the main article and internal links', () => { const html = '

Guide

Text

Next
'; const page = extractPage(html, 'https://wiki.msvincognito.nl/guide'); assert.equal(page.title, 'Guide'); assert.match(page.markdown, /# Guide/); assert.deepEqual(page.links, ['https://wiki.msvincognito.nl/next']); }); ``` - [ ] **Step 2: Run the crawler tests and confirm they fail** Run: `node --test tests/live-wiki.test.mjs` Expected: FAIL because the crawler library does not exist. - [ ] **Step 3: Implement a bounded, polite public crawler** Install `linkedom@0.18.13` and `turndown@7.2.4` as exact dev dependencies. Parse the first `
` element, falling back to `
` and then ``. Remove scripts, styles, navigation, forms, buttons, and repeated footer content before Turndown conversion. Normalize same-origin links, remove query strings and fragments, skip asset extensions, sort the crawl queue, wait 150 ms between requests, and stop with an error rather than silently truncating if more than 500 content pages are discovered. Every Markdown capture must start with: ```md > Source: https://wiki.msvincognito.nl/example > Captured: 2026-08-02 ``` Write `to-be-studied/manifest.json` with `capturedAt`, `baseUrl`, and sorted objects containing `url`, `title`, `path`, and SHA-256 `contentHash`. Failed URLs must be listed in a `failures` array and cause a non-zero exit after successful pages are saved. - [ ] **Step 4: Document the non-publication boundary** `to-be-studied/README.md` must say this directory is a research snapshot, is not a source for the published migration, may contain outdated material, and must never be moved into `src/content/docs/` without separate review. - [ ] **Step 5: Capture the public live wiki** Run: `npm run capture:live-wiki` Expected: readable Markdown captures and a manifest with no failed URLs. If the site blocks crawling or exposes no crawlable navigation, record the exact failure in the manifest and use its public sitemap or the live wiki's source repository if the Incognito maintainers provide it; do not fabricate missing content. - [ ] **Step 6: Write the comparison inventory** Create `comparison.md` with three concrete tables generated from the captured manifest and the 29-entry export manifest: - New live-wiki pages with no exported equivalent - Exported pages with no obvious live-wiki equivalent - Overlapping topics, including source path, live URL, and a short substantive-difference summary At minimum, explicitly classify the live Useful Guides pages, programme/year/course hierarchy, and all exported project pages. - [ ] **Step 7: Verify the research boundary and commit** Run: `npm test && test ! -e src/content/docs/to-be-studied && test ! -e public/to-be-studied` Expected: PASS. ```bash git add package.json package-lock.json scripts/lib/live-wiki.mjs scripts/capture-live-wiki.mjs tests/live-wiki.test.mjs to-be-studied git commit -m "docs: archive newer wiki content for comparison" ``` --- ### Task 11: Add full migration, syntax, and built-link validation **Files:** - Create: `scripts/audit-content.mjs` - Create: `scripts/lib/internal-links.mjs` - Create: `scripts/check-internal-links.mjs` - Create: `tests/internal-links.test.mjs` - Create: `docs/migration-report.md` - Modify: `package.json` - Modify: `tests/content-audit.test.mjs` **Interfaces:** - Produces: `auditContent({ docsRoot, manifest })` and `checkInternalLinks({ distRoot, base })`. - Produces: `npm run audit:content`, `npm run check:links`, and `npm run verify`. - [ ] **Step 1: Write failing internal-link fixture tests** ```js // tests/internal-links.test.mjs import assert from 'node:assert/strict'; import { mkdtemp, mkdir, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import test from 'node:test'; import { checkInternalLinks } from '../scripts/lib/internal-links.mjs'; test('reports a missing built internal route', async () => { const root = await mkdtemp(join(tmpdir(), 'incognito-links-')); await writeFile(join(root, 'index.html'), 'Missing'); const result = await checkInternalLinks({ distRoot: root, base: '/' }); assert.deepEqual(result.broken.map(({ href }) => href), ['/missing/']); }); test('accepts an existing directory index route', async () => { const root = await mkdtemp(join(tmpdir(), 'incognito-links-')); await mkdir(join(root, 'about'), { recursive: true }); await writeFile(join(root, 'index.html'), 'About'); await writeFile(join(root, 'about/index.html'), '

About

'); const result = await checkInternalLinks({ distRoot: root, base: '/' }); assert.equal(result.broken.length, 0); }); ``` - [ ] **Step 2: Run link tests and confirm they fail** Run: `node --test tests/internal-links.test.mjs` Expected: FAIL because the internal-link module is missing. - [ ] **Step 3: Implement built-link checking** Scan every `dist/**/*.html`, extract local `href` values, ignore fragments, `mailto:`, `tel:`, protocol URLs, and Astro-generated asset URLs, remove the configured base prefix, and accept either an exact file or `/index.html`. Report source HTML file, original `href`, and resolved missing path; exit non-zero if any are broken. - [ ] **Step 4: Implement the final content audit** `audit-content.mjs` must: - Run strict manifest destination checking with no `--allow-missing`. - Confirm all 29 sources are accounted for and all 28 unique destinations exist. - Reject DokuWiki tokens `[[`, `{{`, `NEWPAGE>`, `indexmenu>`, and `~~NOCACHE~~` in published content. - Confirm every Markdown/MDX page has `title` and `description` frontmatter. - Confirm empty pages contain the exact awaiting-content notice. - Confirm the two missing schedule references are documented. - Confirm no source file from `pages/wiki/` appears in the manifest or migration report. - Confirm `to-be-studied` is outside `src/content/docs` and `public`. - [ ] **Step 5: Write the human-readable migration report** Create a 29-row source/destination table. For every source, record migration mode, outdated notice, repaired or removed links, missing assets, and significant spelling/grammar fixes. Include dedicated summaries for: - Missing `study:dke-schedule.png` on Bachelor Years 1 and 2 - Removed nonexistent Minutes and Manuals targets from the old home page - Removed DokuWiki editing/upload controls - The malformed Year 2 media-upload link - Historical Study Abroad claims from 2022 - Historical IT service names, URLs, and instructions - Historical laptop pricing and specifications - [ ] **Step 6: Wire the complete verification command** Add scripts: ```json { "audit:content": "node scripts/audit-content.mjs", "check:links": "node scripts/check-internal-links.mjs dist", "verify": "npm run check && npm run audit:content && npm run build && npm run check:links" } ``` - [ ] **Step 7: Run the full non-network verification** Run: `npm run verify` Expected: all tests and Astro checks pass, 29 sources/28 destinations are reported, build succeeds, and zero broken internal links are found. - [ ] **Step 8: Commit validation and report** ```bash git add package.json scripts tests docs/migration-report.md git commit -m "test: verify migrated wiki content and links" ``` --- ### Task 12: Add optional deployment configuration and maintainer documentation **Files:** - Create: `.env.example` - Create: `.github/workflows/deploy.yml` - Create: `netlify.toml` - Create: `README.md` - Modify: `tests/project-structure.test.mjs` **Interfaces:** - Consumes: `SITE` and `BASE` environment values. - Produces: Netlify static build (`npm run build`, `dist`) and optional GitHub Pages deployment from the mirror. - [ ] **Step 1: Add failing deployment-contract assertions** Assert that `netlify.toml` contains `command = "npm run build"` and `publish = "dist"`; the workflow contains `actions/checkout@v7`, `withastro/action@v6`, `actions/deploy-pages@v5`, and only triggers on `main` or manual dispatch; README states Forgejo has no CI dependency. Run: `node --test tests/project-structure.test.mjs` Expected: FAIL for missing files. - [ ] **Step 2: Add Netlify and environment configuration** ```toml # netlify.toml [build] command = "npm run build" publish = "dist" [build.environment] NODE_VERSION = "22.22.3" ``` Set `.env.example` to: ```dotenv SITE=https://wiki.example.org BASE=/ ``` Explain that these are public build settings, not secrets. - [ ] **Step 3: Add the optional official Astro GitHub Pages workflow** Use `push.branches: [main]`, `workflow_dispatch`, `contents: read`, `pages: write`, and `id-token: write`. The build job uses `actions/checkout@v7` and `withastro/action@v6` with `node-version: 22.22.3` plus repository variables `SITE_URL` and `BASE_PATH`. The deploy job uses `actions/deploy-pages@v5`. State in a file comment that Forgejo ignores this GitHub-specific workflow and the repository does not rely on it. - [ ] **Step 4: Write maintainer documentation** README must cover: - Node/npm prerequisites and `npm install` - `npm run dev`, `npm run check`, and `npm run verify` - Published content location and manual sidebar updates - Exact outdated, empty, and missing-asset notice conventions - Forgejo as the authoritative private repository - The optional GitHub mirror workflow - Netlify build command/output - `SITE` and `BASE` examples for custom-domain and repository-subpath hosting - The non-publication rule for `to-be-studied/` - How to review `docs/migration-report.md` - [ ] **Step 5: Run host-neutral and subpath builds** Run: ```bash npm run verify SITE=https://example.github.io BASE=/Incognito-Wiki npm run build BASE=/Incognito-Wiki node scripts/check-internal-links.mjs dist SITE=https://wiki.example.org BASE=/ npm run build node scripts/check-internal-links.mjs dist ``` Expected: both deployment shapes build with zero broken internal links. - [ ] **Step 6: Perform browser verification** Start `npm run dev` and inspect at minimum: - Home, Bachelor Year 1, an empty honours page, IT Services, and Laptop Buying Advice - Desktop width 1440 px and mobile width 390 px - Light and dark themes - Sidebar keyboard navigation, search, table of contents, external association link, and edit-page link - Missing-asset, historical-warning, and awaiting-content callouts Record any defects, fix them, and rerun `npm run verify`. Do not claim visual completion without this inspection. - [ ] **Step 7: Commit deployment and maintainer handoff** ```bash git add .env.example .github netlify.toml README.md tests/project-structure.test.mjs git commit -m "docs: add deployment and maintainer workflow" ``` --- ## Final Acceptance Run - [ ] Run `git status --short` and confirm only intentional files are present. - [ ] Run `npm ci` from the committed lockfile. - [ ] Run `npm run verify` and confirm a clean pass. - [ ] Confirm the migration report has 29 source rows and the manifest has 28 unique destinations. - [ ] Confirm `find dist -iname '*to-be-studied*'` returns no paths. - [ ] Confirm the Git history contains the task-level commits above. - [ ] Hand the user the local preview/build commands and identify that production hostname selection remains a deployment-time choice.