Incognito-Wiki/docs/superpowers/plans/2026-08-02-starlight-wiki-migration.md

42 KiB
Raw Blame History

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

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

// 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:

{
  "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:

// src/config/sidebar.mjs
export const sidebar = [{ label: 'Home', link: '/' }];
// 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
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

// 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:

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:

"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
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:

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:

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
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

// 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:

:::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.

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
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.

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.

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.

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, 20162017 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.

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

// 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 = '<main><h1>Guide</h1><p>Text</p><a href="/next">Next</a></main>';
  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 <main> element, falling back to <article> and then <body>. 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:

> 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.

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"

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

// 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'), '<a href="/missing/">Missing</a>');
  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'), '<a href="/about/">About</a>');
  await writeFile(join(root, 'about/index.html'), '<h1>About</h1>');
  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 <route>/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:

{
  "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
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
# netlify.toml
[build]
  command = "npm run build"
  publish = "dist"

[build.environment]
  NODE_VERSION = "22.22.3"

Set .env.example to:

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:

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
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.