scaffolder | features | documentation | ARCHITECTURE
scaffolder | features | documentation | ARCHITECTURE
Section titled “scaffolder | features | documentation | ARCHITECTURE”project: scaffolderfeature: documentationdoc-type: ARCHITECTUREstatus: acceptedversion: 0.1updated: 2026-03-22depends-on: scaffolder | features | documentation | SPECSystem overview
Section titled “System overview”The documentation system is three things wired together:
- A config layer that eliminates hardcoded paths across the entire scaffolder system
- A Starlight site in a private GitHub repo that renders project docs and ai-sessions as a browsable website
- A Cloudflare deployment that serves the site privately at
projects.purlshq.com
The folder structure after this feature is complete:
D:\purlshq-dev\├── workspace.json ← global config├── scaffolder-engine\ ← engine repo (purlshq/scaffolder)│ ├── src/│ ├── templates/│ └── projects/│ └── scaffolder/ ← project files MINUS docs/ai-sessions│ ├── .claude/│ ├── CLAUDE.md│ ├── src/│ └── ...├── projects-purlshq-com\ ← doc site repo (purlshq/projects-purlshq-com)│ ├── astro.config.mjs│ ├── package.json│ ├── tsconfig.json│ ├── src/│ │ └── content/│ │ └── docs/│ │ └── scaffolder/│ │ ├── project.json ← project config│ │ ├── docs/ ← all scaffolder docs│ │ │ ├── SPEC.md│ │ │ ├── ARCHITECTURE.md│ │ │ ├── features/│ │ │ │ └── documentation/│ │ │ │ ├── DISCOVERY.md│ │ │ │ ├── SPEC.md│ │ │ │ ├── ARCHITECTURE.md│ │ │ │ ├── TASKS.md│ │ │ │ ├── TESTING.md│ │ │ │ └── decisions/│ │ │ └── ...│ │ └── ai-sessions/ ← all scaffolder sessions│ │ ├── CURRENT-CHAT.md│ │ ├── CURRENT-CODE.md│ │ └── logs/│ └── public/│ └── favicon.svg└── backups\Config layer
Section titled “Config layer”Workspace config
Section titled “Workspace config”File: D:\purlshq-dev\workspace.json
Read by: Joker (filesystem MCP), Harley (filesystem), sc CLI (future)
{ "workspace_root": "D:\\purlshq-dev", "engine_root": "D:\\purlshq-dev\\scaffolder-engine", "docs_site_root": "D:\\purlshq-dev\\projects-purlshq-com", "backups_root": "D:\\purlshq-dev\\backups", "github_org": "purlshq", "domain": "projects.purlshq.com", "projects": ["scaffolder"]}Keys:
workspace_root— the parent directory containing all reposengine_root— the scaffolder engine repo rootdocs_site_root— the doc site repo rootbackups_root— the backup target directorygithub_org— the GitHub organization/usernamedomain— the doc site domainprojects— array of active project names (used to enumerate project configs)
Project config
Section titled “Project config”File: {docs_site_root}/src/content/docs/{project}/project.json
Read by: Joker (filesystem MCP), Harley (filesystem), sc CLI (future)
{ "project": "scaffolder", "github_repo": "purlshq/scaffolder", "scale_tier": "project-management", "engine_project_path": "projects\\scaffolder", "linear_project": null, "version": "0.11.0"}Keys:
project— project name (matches folder name and workspace.projects entry)github_repo— full GitHub repo path (org/repo)scale_tier— basic / project-management / teamengine_project_path— relative path fromengine_rootto the project’s engine folder (for projects that have engine-side files)linear_project— Linear project ID (null until Linear feature)version— current project version
Path resolution
Section titled “Path resolution”To find a file, combine config values. Examples:
| What | Resolution |
|---|---|
| CURRENT-CHAT.md | {docs_site_root}/src/content/docs/{project}/ai-sessions/CURRENT-CHAT.md |
| CURRENT-CODE.md | {docs_site_root}/src/content/docs/{project}/ai-sessions/CURRENT-CODE.md |
| Session logs | {docs_site_root}/src/content/docs/{project}/ai-sessions/logs/ |
| Feature SPEC | {docs_site_root}/src/content/docs/{project}/docs/features/{feature}/SPEC.md |
| Project CLAUDE.md | {engine_root}/{engine_project_path}/CLAUDE.md |
| Engine source | {engine_root}/src/ |
| Workspace config | {workspace_root}/workspace.json |
| Project config | {docs_site_root}/src/content/docs/{project}/project.json |
Joker reads workspace.json first at every session start, then reads
the relevant project.json, then reads session files. All paths are
constructed from config values — never hardcoded.
Reference files after config migration
Section titled “Reference files after config migration”CHAT-REFERENCE.md, PROJECT-INSTRUCTIONS.md, and CLAUDE.md all stop containing literal paths. Instead they describe paths using config keys:
Session files: {docs_site_root}/src/content/docs/{project}/ai-sessions/Docs: {docs_site_root}/src/content/docs/{project}/docs/Engine project: {engine_root}/{engine_project_path}/Joker and Harley resolve these by reading the actual config files. The reference files tell them which config keys to use, not what the paths are.
Relationship to ~/.scaffolder-config.json
Section titled “Relationship to ~/.scaffolder-config.json”The CLI config stays where it is. It handles identity and preferences (operator name, AI names, default scale tier, etc.). Path resolution is the workspace config’s job. The two configs are complementary:
| Config | Purpose | Location | Read by |
|---|---|---|---|
workspace.json | Where things are | D:\purlshq-dev\ | Joker, Harley, sc (future) |
project.json | Project-specific settings | Doc site repo per project | Joker, Harley, sc (future) |
.scaffolder-config.json | Who am I, what are my defaults | ~/.scaffolder-config.json | sc CLI only |
Starlight site
Section titled “Starlight site”Framework
Section titled “Framework”Starlight is an Astro-based documentation framework. It generates a
static site from markdown files. The folder structure under
src/content/docs/ becomes the sidebar navigation automatically.
Astro config
Section titled “Astro config”astro.config.mjs at the doc site repo root:
import { defineConfig } from 'astro/config';import starlight from '@astrojs/starlight';
export default defineConfig({ integrations: [ starlight({ title: 'purlshq projects', sidebar: [ // Auto-generated from folder structure // No manual entries needed ], }), ],});Starlight auto-generates sidebar entries from the folder structure when no manual sidebar config is provided. This is the key requirement: adding a new doc file or project folder automatically updates navigation.
Content structure
Section titled “Content structure”All content lives under src/content/docs/. Each project gets a
top-level folder. Within each project, docs/ and ai-sessions/
mirror the structure they had in the engine’s project folder.
Starlight renders the folder hierarchy as nested sidebar navigation:
scaffolder/├── docs/│ ├── SPEC.md → sidebar: scaffolder > docs > SPEC│ ├── features/│ │ └── documentation/│ │ ├── SPEC.md → sidebar: scaffolder > docs > features > documentation > SPEC│ │ └── decisions/│ │ └── ... → sidebar: scaffolder > docs > features > documentation > decisions > ...│ └── ...└── ai-sessions/ ├── CURRENT-CHAT.md → sidebar: scaffolder > ai-sessions > CURRENT-CHAT └── logs/ └── ... → sidebar: scaffolder > ai-sessions > logs > ...Frontmatter
Section titled “Frontmatter”Every markdown file gets YAML frontmatter with at minimum a title
field. The title uses our pipe-separated naming convention:
---title: "scaffolder | features | documentation | SPEC"---This is added above the existing # heading and code-block header.
The existing header format is unchanged. Starlight uses the frontmatter
title for sidebar labels, browser tabs, and breadcrumbs.
Content config
Section titled “Content config”src/content.config.ts (or src/content/config.ts depending on
Starlight version):
import { defineCollection } from 'astro:content';import { docsLoader } from '@astrojs/starlight/loaders';import { docsSchema } from '@astrojs/starlight/schema';
export const collections = { docs: defineCollection({ loader: docsLoader(), schema: docsSchema(), }),};Default schema is sufficient. No custom frontmatter fields needed — our page header lives in the document body, not in frontmatter.
Non-markdown files
Section titled “Non-markdown files”project.json files live alongside markdown content in the docs
folder. Starlight ignores non-markdown files — they won’t appear in
navigation or cause build errors. They’re just config files that
happen to live in the content tree for easy co-location.
Cloudflare deployment
Section titled “Cloudflare deployment”Cloudflare Pages
Section titled “Cloudflare Pages”The doc site repo connects to Cloudflare Pages:
| Setting | Value |
|---|---|
| Repository | purlshq/projects-purlshq-com |
| Production branch | main |
| Build command | npm run build |
| Build output directory | dist |
| Framework preset | Astro |
| Node.js version | 18+ (Cloudflare Pages default) |
Every push to main triggers an automatic build and deploy.
On Cloudflare DNS (purlshq.com is already on Cloudflare):
| Type | Name | Target |
|---|---|---|
| CNAME | projects | projects-purlshq-com.pages.dev |
The exact Pages URL will be provided by Cloudflare when the project is created. The CNAME target may vary.
Cloudflare Access
Section titled “Cloudflare Access”Access policy on projects.purlshq.com:
| Setting | Value |
|---|---|
| Application type | Self-hosted |
| Application domain | projects.purlshq.com |
| Policy name | Operator only |
| Policy action | Allow |
| Include rule | Emails — operator email address |
| Authentication method | One-time PIN (email OTP) |
This is on Cloudflare’s free tier. No cost. The operator receives an email with a one-time code to access the site.
Data flow
Section titled “Data flow”Joker writing a doc
Section titled “Joker writing a doc”- Joker reads
workspace.json→ getsdocs_site_root - Joker reads
project.json→ gets project name - Joker writes the file to
{docs_site_root}/src/content/docs/{project}/docs/... - Operator (or Harley) commits and pushes to
main - Cloudflare Pages builds and deploys automatically
- Doc appears on
projects.purlshq.com
Joker starting a session
Section titled “Joker starting a session”- Joker reads
workspace.json→ getsdocs_site_root - Joker reads
project.json→ gets project name, version, settings - Joker reads
CURRENT-CHAT.mdfrom{docs_site_root}/src/content/docs/{project}/ai-sessions/ - Joker reads
CURRENT-CODE.mdfrom same location - Session begins with full context
Joker ending a session
Section titled “Joker ending a session”- Joker writes
CURRENT-CHAT.mdto{docs_site_root}/src/content/docs/{project}/ai-sessions/ - Joker writes immutable log to
{docs_site_root}/src/content/docs/{project}/ai-sessions/logs/ - Operator commits and pushes (or Harley handles via hook)
Harley starting a session
Section titled “Harley starting a session”- Harley reads
workspace.json→ gets paths - Harley reads
project.json→ gets project settings - Harley reads
CURRENT-CODE.mdfrom doc site repo - Harley reads relevant docs (TASKS.md, SPEC.md, etc.) from doc site repo
- Harley works in the engine project folder for code changes
- Harley writes to the doc site repo for session state
Adding a new project
Section titled “Adding a new project”- Add project name to
workspace.jsonprojectsarray - Create
{docs_site_root}/src/content/docs/{project}/project.json - Create
{docs_site_root}/src/content/docs/{project}/docs/folder - Create
{docs_site_root}/src/content/docs/{project}/ai-sessions/folder - Project appears in site navigation on next deploy
Migration sequence
Section titled “Migration sequence”The build order matters. Dependencies flow downward:
1. Create D:\purlshq-dev\ and workspace.json ↓2. Move scaffolder-engine into D:\purlshq-dev\ ↓3. Initialize Starlight site at D:\purlshq-dev\projects-purlshq-com\ ↓4. Create GitHub repo purlshq/projects-purlshq-com ↓5. Move docs/ and ai-sessions/ from engine to doc site repo ↓6. Add Starlight frontmatter to all markdown files ↓7. Create project.json for scaffolder ↓8. Rewrite reference files to use config keys ↓9. Connect Cloudflare Pages + configure DNS ↓10. Set up Cloudflare Access ↓11. Verify build, deploy, and accessSteps 1-2 are the folder restructure. Steps 3-7 are the doc site setup. Step 8 is the config migration. Steps 9-11 are the deployment.
ARCHITECTURE — accepted.