Global Development Rules
Section titled “Global Development Rules”Mission: Build professional, robust, and efficient software solutions using world-class standards.
Note: These are production standards. For learning side projects, prioritize speed and experimentation over perfection.
AI Agents: The operative subset of these rules (package managers, Python standards, web stack) is also in
~/.claude/CLAUDE.md → ## Dev Standards, which AI agents read at session start. Keep both in sync when updating core tooling decisions.
1. Core Philosophy
Section titled “1. Core Philosophy”1.1 Clarity Over Cleverness
Section titled “1.1 Clarity Over Cleverness”- PRIORITY 1: Clear, semantic, understandable code that enhances maintainability
- Simple and clear is ALWAYS superior to clever and complex
- Exception: When execution speed is explicitly paramount, provide clear explanatory comments
- Code should be self-documenting; comments explain WHY, not WHAT
- Never use hacks or rigid fixed solutions; use robust solutions that flexibly handle all edge cases
1.2 Goal Upgrades Framework
Section titled “1.2 Goal Upgrades Framework”When providing solutions, ALWAYS:
- Ask for more details if needed to be very clear on the objective
- Add “or better” to any objective to be achieved
- Explore innovative, unconventional solutions combining effective insights in novel ways
Definitions of “Better” for Tech/Code:
- Simpler, more understandable, more maintainable
- Lower security risks, lower future risk (tech discontinuation)
- Less vendor lock-in, better FOSS alignment
- More efficient resource usage
- Better error handling and debugging capability
1.3 AI-First Development
Section titled “1.3 AI-First Development”- Primary AI Tools: Currently using Claude Code, Cursor 2.0, Gemini 3.0
- Supporting AI Tools: Crew AI, n8n when appropriate
- Leverage AI for: Code generation, documentation, testing, refactoring, project orchestration
- Critical: ALWAYS review and test AI-generated code. Never blindly commit.
1.4 FOSS Preference
Section titled “1.4 FOSS Preference”- Prefer FOSS solutions when functionality and deliverability don’t suffer
- Avoid monthly subscription lock-ins unless significantly superior
- Balance: Don’t be penny-wise and pound-foolish with time vs. cost
1.5 Performance Philosophy
Section titled “1.5 Performance Philosophy”- Priority Order: Functional and robust first. Then optimize for speed.
- Speed is a feature, but not at the expense of correctness or maintainability
- Ship minimal JavaScript
- Optimize assets
- Use SSG (Static Site Generation) by default
- Profile before optimizing; document performance trade-offs
2. Technology Stack
Section titled “2. Technology Stack”2.1 Web Development
Section titled “2.1 Web Development”Core Architecture
Section titled “Core Architecture”- Site Framework: Astro (latest) for all websites - static content, blogs, marketing sites
- Web Applications / PWAs: SvelteKit (
adapter-static+ Cloudflare Pages) for complex, state-heavy applications - UI Components: Use framework-appropriate component libraries
- Currently using Shadcn-Svelte for Svelte/SvelteKit projects — add components via CLI (
npx shadcn-svelte@latest add <component>)
- Currently using Shadcn-Svelte for Svelte/SvelteKit projects — add components via CLI (
- Styling: Tailwind CSS
- v4 is the target standard; SvelteKit apps currently use v3 for stability (migrate when shadcn-svelte v4 integration matures)
- Use utility classes for layout and spacing
- Use
@applyor CSS variables for theming and reusable components
- i18n: Paraglide (
@inlang/paraglide-sveltekit) for SvelteKit apps requiring multiple languages — compiler-based, type-safe, zero runtime overhead - Design System: CSS variables + Utopia fluid responsive design
- Content: Markdown (.md) or MDX (.mdx) for blog/documentation content
- CMS: Currently using Decap CMS for blog content management
Decision Framework: When to Use What
Section titled “Decision Framework: When to Use What”- Static content site/blog: Astro + minimal JavaScript
- Lightweight interactivity (menus, modals, simple state): Alpine.js or vanilla JS
- Complex web application: Svelte or React-based framework
- Default Rule: Use the simplest appropriate tool. Avoid over-engineering.
Package Management
Section titled “Package Management”- Node.js: Currently using pnpm (fast, disk-efficient)
- Python: Currently using UV (replaces pip/poetry)
Language Standards
Section titled “Language Standards”- TypeScript: Strict mode required for production code. No
anyunless absolutely necessary. - Python: Type hints required for all non-trivial scripts. Follow PEP 8.
2.2 Backend & Database
Section titled “2.2 Backend & Database”Database
Section titled “Database”- Primary: Currently using MariaDB for direct EspoCRM and Sendy integration
- Modern Projects: Currently using PostgreSQL with Supabase
- Local Development: SQLite with proper migration path to production
- ORM: Currently using Prisma for type-safe database access
API Development
Section titled “API Development”- REST APIs: Follow RESTful conventions
- Authentication: Currently using Supabase Auth or EspoCRM Portal
- Error Handling: Consistent error response format
- Rate Limiting: Implement for all public endpoints
- Documentation: OpenAPI/Swagger for all endpoints
2.3 Deployment & Hosting
Section titled “2.3 Deployment & Hosting”Frontend Hosting
Section titled “Frontend Hosting”- Primary: Currently using Cloudflare Pages for unbeatable performance
- Alternative: Vercel for Jamstack hosting
- CDN: Leverage Cloudflare’s global network
- Build: Automatic builds from Git commits
Backend Hosting
Section titled “Backend Hosting”- Managed VPS: Currently using Cloudways on DigitalOcean Toronto
- PHP Applications: EspoCRM, Sendy on LAMP stack
- Node.js: For Astro SSR and APIs
- Database: MariaDB on same VPS for performance
DevOps Pipeline
Section titled “DevOps Pipeline”- Version Control: GitHub with GitHub Actions for CI/CD
- Secrets Management: Platform built-ins (Cloudflare Pages, n8n Cloud) or currently using Doppler
- Monitoring: Currently using Uptime Kuma for service monitoring
- Error Tracking: Currently using Sentry or similar for production error logging
3. Development Standards
Section titled “3. Development Standards”3.1 Project Structure
Section titled “3.1 Project Structure”project/├── src/│ ├── pages/ # Astro pages or app routes│ ├── components/ # Reusable components│ ├── layouts/ # Layout templates│ ├── content/ # Markdown content (if applicable)│ ├── lib/ # Utilities and helpers│ ├── styles/ # Global CSS + design tokens│ ├── assets/ # Images, fonts, static files│ └── config/ # Configuration files├── scripts/ # Build scripts, utilities├── public/ # Static assets├── dist/ # Build output (gitignored)├── tests/ # Test files├── docs/ # Documentation│ └── Lessons.md # Learnings from development process└── README.md # Setup and deployment guide3.2 Naming Conventions
Section titled “3.2 Naming Conventions”- Files/Directories:
kebab-case(e.g.,my-component.astro,user-profile/) - Components:
PascalCase(e.g.,Button.astro,HeroSection.svelte) - Variables/Functions:
camelCase(e.g.,getUserData,isValid) - Constants:
UPPER_SNAKE_CASE(e.g.,MAX_RETRIES,API_BASE_URL) - Use descriptive, searchable names - avoid abbreviations and mental mapping
- Functions/methods: Use verbs (calculateTotal, getUserData)
- Variables: Use nouns (userEmail, totalAmount)
3.3 Code Quality Standards
Section titled “3.3 Code Quality Standards”Architecture & Design
Section titled “Architecture & Design”- Follow SOLID principles
- Prefer composition over inheritance
- Use dependency injection for testability
- Design for failure - assume things will break
Error Handling
Section titled “Error Handling”- Graceful Failure: Applications should never crash silently
- Fail fast and fail clearly
- Use specific exception types
- Logging:
- Dev: Console logging
- Prod: Structured logging with correlation IDs in markdown format
- Log errors with context (what, when, why)
- Notification: Critical errors must notify developer (Email/SMS)
- Provide actionable error messages
Documentation
Section titled “Documentation”- README.md for every project with setup instructions
- Inline comments for complex business logic only (WHY, not WHAT)
- API documentation for all public interfaces
- Keep documentation close to code (avoid separate wikis)
- Architecture Decisions: Document in Obsidian knowledge base as ADRs
- Maintain single source of truth for architectural decisions
3.4 Documentation Standards
Section titled “3.4 Documentation Standards”Project Documentation
Section titled “Project Documentation”- README.md: Setup, deployment, and contribution guidelines for every project
- docs/Lessons.md: Capture learnings from development process
- Document when AI or human struggles to produce results
- Record solutions that worked well
- Note patterns to avoid
- Purpose: Make future development more effective and efficient
Code Documentation
Section titled “Code Documentation”- Inline Comments: Only for complex business logic (explain WHY, not WHAT)
- API Documentation: Generate from OpenAPI specifications for all endpoints
- Component Stories: Use Storybook or similar for component documentation when appropriate
Architecture Documentation
Section titled “Architecture Documentation”- ADRs (Architecture Decision Records): Document in Obsidian knowledge base
- Tech Stack Decisions: Link to relevant business processes and rationale
- Single Source of Truth: This Global-Dev-Rules.md for all development standards
3.5 Security Standards
Section titled “3.5 Security Standards”Security First
Section titled “Security First”- Never commit secrets or credentials
- Validate all inputs (client-side + server-side verification)
- Use parameterized queries only (prevent SQL injection)
- Follow principle of least privilege
- Content Security Policy: Strict CSP headers
- HTTPS: Enforce SSL/TLS everywhere
- XSS Prevention: Escape all user content
- CORS: Strict CORS policy configuration
Dependency Security
Section titled “Dependency Security”- Regular CVE scanning: Use tools like
uv audit,pip-audit,safety check, orsnyk - Keep dependencies updated to patched versions
- Automate security scanning in CI/CD pipeline
- Balance security patches with stability (not bleeding-edge, but not stale)
3.6 Performance Standards
Section titled “3.6 Performance Standards”Core Web Vitals
Section titled “Core Web Vitals”- LCP (Largest Contentful Paint): Optimize images, lazy loading, critical resource hints
- FID (First Input Delay): Minimize JavaScript, defer non-critical scripts
- CLS (Cumulative Layout Shift): Define image dimensions, avoid layout shifts
- Target: All metrics in “Good” range (green)
Asset Optimization
Section titled “Asset Optimization”- Images: Use modern formats (WebP, AVIF), responsive images with proper dimensions
- CSS: Critical CSS inline, defer non-critical styles
- JavaScript: Code splitting, tree shaking, minimal bundles
- Fonts: Font display swap, preload critical fonts
Caching Strategy
Section titled “Caching Strategy”- Static Assets: Long cache headers (1 year)
- HTML: Short cache with ETags
- API Responses: Appropriate cache based on content type
- CDN: Leverage Cloudflare’s caching capabilities
3.7 Accessibility Standards
Section titled “3.7 Accessibility Standards”- ARIA labels for interactive elements
- Semantic HTML
- Keyboard navigation support
- Color contrast compliance (WCAG AA minimum)
- Screen reader testing for critical flows
3.8 Browser Gotchas
Section titled “3.8 Browser Gotchas”Mobile touch-target inflation on <button>
Section titled “Mobile touch-target inflation on <button>”Mobile browsers enforce a minimum ~44-45px touch target on <button> elements, overriding CSS width/height regardless of units (rem, em, px). A small toggle button styled as a pill (e.g. 34×19px) will render as a square on mobile.
Fix: Use <span role="switch" tabindex="0"> (or <div role="...">) for custom-styled interactive controls. Inline/block elements don’t receive touch-target inflation. Add keyboard handlers (keydown for Space/Enter) for full accessibility if needed beyond basic dev tools.
Cloudflare _headers not applied by local dev servers
Section titled “Cloudflare _headers not applied by local dev servers”public/_headers (Cloudflare Pages response header rules) is served as a plain static file by Python http.server and most local dev servers — it is not interpreted as HTTP headers. CSP-dependent bugs (blocked iframes, ORB-blocked images, mixed content) are invisible locally and only appear on the deployed Cloudflare Pages site.
Fix: Always test CSP-related behavior with a production or preview deployment, not local. For the monorepo template site: use web-deploy template online (not local) when debugging anything blocked by response headers.
Caching / headers / PWA freshness (hard-won, focus-pages bug recurred 3×)
Section titled “Caching / headers / PWA freshness (hard-won, focus-pages bug recurred 3×)”- Identify the actual web host BEFORE applying any header/cache fix — the mechanism is server-specific.
.htaccessis Apache-only (IIS ignores it and even 404s.ht*); IIS/Plesk-Windows needsweb.config(<clientCache cacheControlMode="DisableCache"/>); Cloudflare Pages needs_headers. Applying the wrong file is a silent no-op. Check first:curl -sI <url>→ read theserver:header. (talbotstevens.com = IIS today, migrating to Apache — so its focus pages ship BOTHweb.config+.htaccess.) - Never trust HTTP cache behavior for Android PWA freshness. A
no-cacheheader only governs future responses — it cannot evict an already-poisoned heuristic cache entry, andlocation.reload()honors that entry instead of hitting the network (iOS/Safari revalidates; Android Chrome does not). The durable, host-independent fix is client-side: stamp a unique<meta name="focus-build">per build, and on load/resumefetch(url+'?_='+Date.now(), {cache:'no-store'})— if the live build id differs,location.replace('?v='+id)(guaranteed cache-miss → fresh). Degrade gracefully offline. - Verify the mechanism actually landed live — uploaded ≠ applied. After any header/cache change, confirm on the wire:
curl -sI <url> | grep -i cache-control. A deployed file proves nothing; only the live response header does.
3.9 WSL + AI Utility Gotchas
Section titled “3.9 WSL + AI Utility Gotchas”- Dedicated Gemini API key per utility — Gemini’s free tier quota is shared across all tools using the same key. Each AI utility must have its own
GEMINI_API_KEYin its local.envto avoid cross-tool quota conflicts (e.g.pdf2md/.env,source_summarizer/.env). - Write
.envfiles from WSL, not Windows — Copy-pasting.envcontent from Windows silently embeds Unicode/emoji characters in comments, garbling the file when Python’s dotenv reads it. Always write.envdirectly in WSL. Use ASCII-only comments in.env.example. - WSL cron + Windows binary paths — When a WSL cron job calls Windows binaries, use
/mnt/c/...paths for discovery (os.path.exists,glob.glob). WSL interop handles execution transparently, but Linux Python cannot resolveC:\...backslash paths. Triggered whenever a refactor switches fromuv.exe(Windows Python) touv run(Linux Python) without updating path constants. uv tool installbreaks__file__-relative paths — When a Python CLI is installed viauv tool install,__file__resolves to the tool’ssite-packages, not the source tree. UsePath(os.environ.get("TOOL_HOME", "/known/absolute/path"))for config/data file paths in tools that have a fixed install location.load_dotenv()with no path uses CWD — unreliable in cron — CWD is unpredictable in cron jobs. Always useload_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)), '.env'))so the.envloads from the module’s own directory, regardless of where the process was launched.- Schedule monitoring jobs AFTER the jobs they monitor — A monitor that runs at 6 AM before the 10 AM jobs it watches will always false-alarm. Set the monitor’s cron time to after the last monitored job in the day (e.g. if last job is 1 PM, run monitor at 2 PM).
set VAR=value &&in cmd.exe includes trailing space in the value —set PYTHONUTF8=1 &&setsPYTHONUTF8to"1 "(with trailing space). Python’sPYTHONUTF8only accepts exactly"0"or"1"— the space causes a fatal crash before any Python runs. Use the quoted formset "VAR=value" &&(strips trailing space) or just omitsetentirely if not needed.Path("D:")in Python is CWD-relative, not drive root —Path("D:") / "foo"resolves toD:<cwd>\foo, notD:\foo. When addressing a Windows drive root, usePath("D:/")orPath("D:\\")explicitly.- External HTTP data sources must fail gracefully, not crash the pipeline — Secondary/validation sources (price feeds, external APIs) will go down or add bot-verification walls. Guard all external HTTP calls with
except requests.exceptions.RequestException: return []so one unavailable source doesn’t abort the whole update. stooq.com added a JS bot-challenge ~2026-06-14 and is no longer usable viarequests. - PWA manifest
background_colormust contrast with favicon SVG foreground — If the favicon SVG is monochromatic (e.g. #004425 leaf on transparent), using the same color asbackground_colormakes it invisible on the Android splash screen and homescreen icon. Use white (#ffffff) or a neutral background when the favicon has no built-in background fill. - WSL→Windows
.exeinterop breaks silently when/etc/binfmt.d/WSLInterop.confexists but is empty — WSL distros booting withsystemd=true([boot] systemd=truein/etc/wsl.conf, or now default on newer WSL) register the WSLInterop binfmt handler via this conf file instead of automatically via legacy init. If the file exists but has no content,systemctl status systemd-binfmtreports success while registering nothing — so any WSL process trying to exec a Windows.exe(powershell.exe,cmd.exe, etc. viasubprocess) silently fails with no clear error pointing at the cause. Checkcat /proc/sys/fs/binfmt_misc/WSLInterop(should printenabled) and the conf file’s actual content, not just its existence, before assuming interop is broken for some other reason. Fix:echo ':WSLInterop:M::MZ::/init:PF' | sudo tee /etc/binfmt.d/WSLInterop.conf && sudo systemctl restart systemd-binfmt. Discovered: web-deploy auto-open-browser regression, 2026-07-08. - A WSL Claude session cannot live-verify Windows-native Selenium/browser automation — no local Chrome/Selenium is available in WSL, and WSL→Windows interop may itself be down (as above).
curl/WebFetchcan’t render JS or hold an authenticated session, so a post-login SPA page (or any JS-rendered content) is invisible from WSL. Don’t guess at a DOM/selector fix blind. Instead: broaden error handling, add failure-diagnostic capture (screenshot + page source + URL, written to a debug folder on the next real failure) to the script itself, and ask the user to trigger one real run on Windows — then read the captured artifacts to write the actual fix. Discovered: my_backup Dynalist-capture timeout, 2026-07-08/09. - Cron’s minimal PATH resolves system node, not nvm’s — cron jobs don’t source an interactive shell’s rc files, so a script’s
export PATH=...line that only addspnpm/.local/binstill leaves cron falling back to/usr/bin/node(whatever apt installed) instead of nvm’s version, even when the interactive shell resolves the right one via.bashrc. Symptom is silent/delayed — a tool with a hard version floor (e.g. wrangler needs Node ≥22) starts failing in cron only, working fine when run manually. Fix: explicitly prepend the nvm node bin dir ($HOME/.nvm/versions/node/vX.Y.Z/bin) to the script’s ownPATHexport — don’t rely on nvm’s shell init being sourced. Verify withenv -i HOME="$HOME" PATH="/usr/bin:/bin" ./script.shto reproduce cron’s minimal PATH locally instead of trusting an interactive-shell test run. Discovered: mBR rate-scannerdaily-run.sh— Cloudflare Pages deploy silently failed daily from 2026-07-13 (Wrangler requires at least Node.js v22.0.0. You are using v20.19.6) until traced to this, 2026-07-15. - A comment claiming “script X updates this later” is a claim, not a fact — verify it, don’t trust it —
runner.pywrote ops-log status fields as"pending"with a comment sayingdaily-run.shwould update them after export/deploy; the referenced script never actually did, so the fields silently read “pending” forever, even on fully successful runs, masking whether deploys were actually succeeding. Any comment describing cross-file behavior (“X handles this”, “Y updates this later”) is an assumption baked in at write time that can rot the moment either file changes — grep the referenced file for the claimed behavior before relying on it, especially when auditing “is this working” claims. Discovered: same investigation as above, 2026-07-15. - A cron job’s
>> file.logredirect adds no timestamp of its own — a script that justprint()s its progress produces a log where every line looks identical run to run; only the file’s mtime tells you how stale it is, and that’s useless once the file keeps growing. Any script whose output is redirected by cron (not logged via a framework that timestamps automatically) should prefix its own lines with[YYYY-MM-DD HH:MM:SS]before printing. Discovered:my_backup’ssend_status_report.py—logs/send_status_report.loghad no way to tell which run produced which line, 2026-07-18. - A Windows Scheduled Task with
RunLevel: Highestdoesn’t resolve bare executable names viaPATH— a task whose action isuv(not a fully-qualified path) works fine when run manually in an interactive PowerShell (which resolvesPATHnormally), but fails silently every time as a scheduled, elevated action (LastTaskResult: 2147942402/0x80070002/ERROR_FILE_NOT_FOUND) —NumberOfMissedRunsclimbs whileGet-ScheduledTaskInfoshows it “ran” each time. Elevated Task Scheduler process launches don’t resolveExecutethrough the registry-persisted UserPATHthe way an interactive shell does. Fix: always use the fully-qualified path (C:\Users\<user>\.local\bin\uv.exe) in a scheduled task’s action; if the task doesn’t actually need admin rights, dropRunLeveltoLimitedentirely rather than patching the path — this also removes the separate risk of an unattended run silently hanging on a UAC prompt nobody’s there to click. Also: the task’s real registered name can differ from the utility’s name in its comment/description (e.g. adiskcheckutil registered as taskDiskSpaceCheck) —schtasks /query /tn "<expected-name>"and PowerShellGet-ScheduledTask -TaskNamecan both fail to find it by the name you’d guess; grep the full task list’s content (schtasks /query /fo LIST /v) instead of assuming the query-by-name failure means the task doesn’t exist. Verify a fix like this by actually triggering the task (Start-ScheduledTask, then checkLastTaskResult), not just confirming the edit was accepted — a “command succeeded” result only proves the edit landed, not that the scheduled/unattended path works. Discovered:diskcheck/DiskSpaceCheckweekly disk-space monitor silently failing since ~Feb 2026 despite showingEnabled, System-Maintenance ongoing project, 2026-07-21. - A
uvproject’s default.venvon a cross-mounted drive is a silent WSL/Windows collision trap — if a WSL cron and a Windows Scheduled Task both invokeuv runin the same repo dir on a shared drive (e.g.D:) with neither passingUV_PROJECT_ENVIRONMENT, they share one.venv. A bareuv run <anything>typed manually from the other OS (e.g. testing a fix from WSL bash) silently rebuilds that.venvfor its own platform — a Windowsuv.execannot sync a Linux-built venv (symlinks likelib64 -> libfail withAccess is deniedon Windows), so the production entrypoint on the other OS starts failing with no code change and no obvious cause. Fix is symmetric isolation, not remembering to set an env var: give both OS-side entrypoints their ownUV_PROJECT_ENVIRONMENTon that OS’s own local disk (e.g. WSL →/home/ta/.venvs/<project>, Windows →C:\Users\<user>\.venvs\<project>) so neither ever touches the shared-drive default path — then a stray manualuv runjust poisons an unused folder instead of breaking production. Discovered:my_backupWindows daily-backup task silently failed 07-19 through 07-26 (8 days) after a manual WSL verification run rebuilt the sharedD:default.venvas Linux;Core/IT/Tasks/my_backup-no-backup.md, 2026-07-26. schtasks /query /xmloutput piped through WSL is not real UTF-16, even though it declaresencoding="UTF-16"— redirectingschtasks.exe /query /tn ... /xml > filefrom a WSL shell writes single-byte (ASCII/UTF-8-ish) bytes under a UTF-16 XML declaration;schtasks /create /xmlon that same file then fails withERROR: unable to switch the encoding. Editing the file’s text (e.g. changing the<Actions>block) doesn’t fix this — the declaration and the bytes must actually match. Fix: re-encode properly before importing —open(path, encoding='utf-8').read()thenopen(path, 'w', encoding='utf-16').write(data)(Python’sutf-16codec writes a real BOM + 2-byte-per-char stream) — and verify withxxd(fffe 3c00 3f00 ...= real UTF-16LE-BOM) before handing the file toschtasks /create. Also: acp/cleanup step done after this fix can silently re-clobber the corrected file with a stale copy from earlier in the same session — re-verify the final file’s bytes right before the user runs the command, don’t assume an earlier fix survived later shell operations. Discovered: samemy_backup-no-backuptask, 2026-07-26.- A Windows Scheduled Task switched to Password logon (“run whether user is logged on or not”) can run with nobody logged in — but WSL doesn’t auto-start in that unattended session. Under the old Interactive logon type,
\\wsl$\<distro>\...paths only ever got hit while the user was logged in, and WSL was usually already running incidentally (an open terminal). Once the task can run fully unattended, any step touching\\wsl$(a backup source, awsl.exeheartbeat call) silently fails —resolveSymlink: stat: CreateFile \\wsl$\...: The specified network name is no longer available, followed by “WSL not running”. Fix: addwsl.exe -d <DistroName> -e trueat the top of the wrapper script/task action, before anything touches\\wsl$— this boots the distro explicitly regardless of logon state. Verify with a realschtasks /run+ exit-code check after the change, not just that the edit was accepted. Discovered:my_backup-backup-stale, 2026-07-30 — surfaced as a second-order bug from fixing the original “task skipped when nobody logged in” issue. - Triggering a Windows Scheduled Task from WSL fails with a misleading “system cannot find the file specified,” even when the task and its target both exist.
schtasks.exe /Run /TN "<name>",schtasks.exe /Query /TN "<name>", and PowerShell’sStart-ScheduledTask -TaskName "<name>"(invoked viapowershell.exefrom a WSL shell) all fail with this exact error for a task confirmed present andReadyviaGet-ScheduledTaskseconds earlier — the batch file the task points to also verifiably exists.Get-ScheduledTask(a read-only listing) works fine from WSL; anything that talks to the Task Scheduler engine to actually start/query a specific task (/Run,Start-ScheduledTask,/Querywithout/FO LIST) does not — a session/token limitation of the WSL→Windows process-interop boundary, not a real missing file. Fix: don’t try to trigger the task through the scheduler from WSL — run the task’s actual target executable/batch file directly instead (cmd.exe /c "<path-to-the-.bat-the-task-points-at>"), which exercises the identical production command without going through the scheduler engine. Discovered:my_backup-ecco-main-recovery, 2026-08-13, trying to force an ad hoc run of the “Daily Backup” task to verify a same-session config fix. schtasks /createneeds a genuinely elevated token —/queryworking proves nothing about/create. A non-elevated shell (WSL bash calling/mnt/c/Windows/System32/schtasks.exe, or a plain unelevated Windows terminal) can read every task via/querybut fails/createwithERROR: Access is denied., even for an account in the Administrators group —whoami /groupson that token showsBUILTIN\Administrators ... Group used for deny only(UAC-filtered split token). This is true regardless of who runs it or from where; the only fix is a genuinely elevated shell (“Run as administrator”). Don’t waste rounds retrying the same command from a different non-elevated context. Discovered:my_backup-job-monitors-stale, 2026-09-17.- PowerShell does not parse
\"as an escaped quote — that’s cmd.exe syntax. A command built for cmd.exe that embeds a literal"via backslash-escaping (/tr "wscript.exe \"C:\path\file.vbs\"") mis-parses in PowerShell: the parser mis-splits the argument and swallows the next flag into it (symptom:Invalid syntax. Mandatory option 'X' is missing.for a flag that’s clearly present in the typed command). Fix: wrap the value in single quotes instead — PowerShell passes a single-quoted string through literally, inner"included (/tr 'wscript.exe "C:\path\file.vbs"'). Verify a corrected command’s syntax cheaply by running it unelevated first:Invalid syntaxmeans still-broken quoting,Access is denied(for anything needing elevation) means the syntax is now correct. Discovered: same task, 2026-09-17. - A Windows Task Scheduler “Run whether user is logged on or not” change can silently fail to save if the post-OK password prompt is cancelled or left blank — the Properties dialog shows the correct radio button selected even when the change never actually committed; there’s no error, just a dialog that looks right and a task that stays on the old logon mode. A screenshot of the dialog with the right option selected is not evidence the change landed. Verify with
schtasks /query /tn "<name>" /v→Logon Mode:(Interactive/Background= fixed,Interactive only= still broken) after every such change, redoing it and actually entering the password at the prompt if it didn’t stick. Discovered: same task, 2026-09-17 — a round-2 fix believed done (with a supporting screenshot) turned out unsaved when checked against live system state in round 5. - A WSL symlink’s
ls -lasize is the target PATH STRING’s length, not the pointed-to file’s size — and Windows/\\wsl$doesn’t follow WSL symlinks at all.ls -laon a symlink (e.g. 108 bytes for a ~100-char target path) looks like file corruption if misread as content size;readlink -f+catshow the real target and its actual content. Separately, apps reading over\\wsl$\<distro>\...from Windows (Explorer, browsers, most non-WSL-aware tools) do not resolve WSL symlinks — a symlink that works perfectly from WSL itself can appear broken/empty/tiny from the Windows side, with no error. Check both angles (stat vs. content, WSL-side vs. Windows-side) before concluding a symlinked file lost its content. If a file needs to be opened from Windows, use a real copy instead of a symlink. Discovered:web-deploy-artifactstask, 2026-09-26 — a “broken, 108 bytes” report was neither: the file’s real content (549 lines, populated dashboard) was intact, only the Windows-side symlink view was the problem.
3.10 Dev Project Artifact Placement (screenshots, PDFs, review bundles)
Section titled “3.10 Dev Project Artifact Placement (screenshots, PDFs, review bundles)”The KB holds context and pointers, never the artifacts themselves. A task/log markdown file in the vault should never contain a binary reference-image, generated PDF, or review bundle directly under Tasks//Logs/ — that folder is scanned as a task/log list, and non-task binaries dropped there get misread as tasks (confirmed: LevPro screenshots left in SDC/IT/Tasks/<task>-review/ showed up in Tasks tables). The artifact’s real home is the dev project’s own repo, sorted by what kind of artifact it is:
| Kind | Example | Home | Committed? |
|---|---|---|---|
| Context/reference (used to build against; low churn, small, has standing reference value) | Screenshots of a legacy app being ported, a vendor’s spec PDF | <package>/docs/reference/ (mirrors sd-math/docs/accuracy-audit.md’s existing pattern) | Yes — it’s the SSOT for “what were we matching” |
| Testing artifact — ephemeral (proves one session’s verification pass; no standing value once superseded) | A screenshot confirming a form round-trips, a one-off Playwright capture | The agent’s scratchpad (/tmp/claude-*/.../scratchpad) or the repo’s already-gitignored test-results/playwright-report | No — discard after the session that produced it is done |
| Testing artifact — audit-worthy (the record a later reviewer or task-close depends on: golden-fixture cross-checks, a generated PDF diffed against a spec, a full before/after bundle) | sd-math’s M5 audit artifacts, a full-parity review bundle for a closed task | <package>/docs/audit/<task-slug>/ | Yes — same footing as accuracy-audit.md |
Rule for the KB task/log file itself: name the artifact and give the exact repo path + regeneration command (or note “one-off, not reproducible” if that’s true) — never embed or copy the binary into the vault. This is the same shape as the vault’s existing SSOT rule (one home, zero duplicates) applied specifically to dev artifacts, and the same “KB = context + why, repo = the thing itself” split the CONSTITUTION already draws for code.
(Discovered: sdc-sdapp-full-levpro-port, round 7 moved a LevPro-port review bundle — screenshots + a generated PDF — into SDC/IT/Tasks/<task>-review/, which Talbot correctly rejected round 8: it polluted the Tasks table and was the wrong home regardless. Round 9 wrote this rule and relocated the bundle into the repo per the table above.)
3.11 Clickable links in VS Code’s Markdown preview
Section titled “3.11 Clickable links in VS Code’s Markdown preview”VS Code’s built-in Markdown preview uses markdown-it, and its link-destination validator rejects the file: scheme outright — [label](file:///mnt/d/...) renders as literal unparsed bracket/paren text, not a clickable link (confirmed: md.validateLink('file:///...') returns false). This applies from a WSL-Remote window even when the path exists on the remote filesystem.
- Link to a file in the repo: plain relative markdown link,
[label](relative/path/to/file.ext)— opens in the editor. - Link to a folder: does not open — the preview’s link handler tries to open it as a file and silently fails. Always link the individual files inside the folder, never the folder path itself.
- Link to a file outside the repo (e.g. a KB-vault note on a Windows drive): use VS Code’s own
vscode://file/protocol handler instead offile://—D:\FSS\KB\SDC\IT\Tasks\foo.md→[label](vscode://file/D:/FSS/KB/SDC/IT/Tasks/foo.md)(forward slashes after the drive letter,%20for spaces). This validates undermarkdown-itand opens the file directly on its native drive, sidestepping WSL-remote path-resolution entirely. It opens as plain markdown, not in Obsidian — that’s the ceiling for a one-click open from VS Code.
(Discovered: create-review-guide-skill, 2026-08-31 — the Review-LevPro.md guide’s static-proof and KB-note links looked plausible but were dead in practice; both failure modes reproduced locally with markdown-it before shipping the fix, rather than guessing again from a screenshot.)
3.12 Porting existing software — source the full archive, not screenshots-on-request
Section titled “3.12 Porting existing software — source the full archive, not screenshots-on-request”When porting a legacy app, find and read its full source/spec archive before asking the user for screenshots one gap at a time. A complete archive (form source, string tables, sample output, layout specs) gives every workstream a deterministic on-disk acceptance target — enabling safe autonomous/parallel execution — whereas screenshot-by-screenshot elicitation is slow, interactive, and finds gaps only when someone happens to ask.
(Discovered: sdc-sdapp-full-levpro-port, rounds 1–11 sourced spec from screenshots the user produced on request, one gap at a time — frustrating and slow. Round 12 found the legacy app’s full VB6 source archive (7,972 lines of .frm form source, both language files, a sample PDF printout, layout .doc specs) already sitting on disk. Every remaining workstream then had a deterministic acceptance artifact, which is what made round 13’s 3-way parallel-subagent fan-out safe.)
Concurrent-subagent write protocol for a shared file (e.g. i18n strings): each agent owns a distinct key prefix, must read-modify-write the shared file (never overwrite), and dumps its own key set to a handoff file (docs/handoff/<agent>-<resource>.json) for the orchestrator to reconcile against the live file afterward. This makes a lost concurrent write detectable and repairable instead of silently invisible.
(Discovered: same task, round 13–14 — 4 agents wrote to the same i18n files concurrently; the handoff-file protocol verified 228/228 EN/FR keys survived with zero lost keys and zero value drift across all four writers.)
3.13 A generated file should be read-only, not just labelled
Section titled “3.13 A generated file should be read-only, not just labelled”A “DO NOT HAND EDIT” banner asks. A read-only file attribute refuses. Any generator that owns a file end-to-end should chmod 444 it after writing, and chmod 644 it again immediately before the next rewrite.
This works across the WSL/Windows boundary, which is the part worth knowing. Verified 2026-09-21 on D: (drvfs/9p):
chmod 444 <file on /mnt/d/...>cmd.exe /c attrib <file> # → A R D:\...powershell Add-Content <file> # → UnauthorizedAccessExceptionSo a chmod from WSL sets the Windows read-only attribute, and Obsidian refuses to save. The edit fails loudly instead of being silently overwritten on the next generator run.
Only for wholly-generated files. A file with a generated section and hand-written prose around it — Core/Misc/OrgChart.md is the live example, where only the ## Tree block is generated — must stay writable. Locking it would block the human half.
Keep the rule in each generator, not in a global hook. A vault-wide enforcer would need a registry of “which files are generated”, which is a second source of truth about a fact the generator already knows. Reference implementation: ~/utils/strategy-lint/ (make_writable / make_readonly), registered at Core/IT/Utils/Custom/strategy-lint.md.
The protection does not survive a copy. Deno.copyFile (and cp) preserve the source’s mode bits, so a build/sync pipeline that copies a read-only generated file into a local working copy (e.g. sync-content.ts copying vault content into a site’s src/content/docs/) produces a read-only copy too — and any later pipeline step that needs to rewrite that copy (a transform, a link-converter, a sidebar generator) breaks on it. The fix belongs at the copy site: chmod the destination writable immediately after copying. The protection is for the source, not for a disposable downstream copy.
(Discovered: web-deploy-bug, 2026-09-26 — sync-content.ts copied strategy-lint’s read-only index.md files into 3 projects’ local content dirs; convert-wikilinks.ts then crashed trying to rewrite them.)
(Raised by Talbot 2026-09-22: “Is there a more robust way to ensure that files that should not be human edited CAN’T be edited?” — answer: yes, and it is three lines.)
4. Version Control & Collaboration
Section titled “4. Version Control & Collaboration”Git Workflow
Section titled “Git Workflow”- Commit Messages: Use conventional commits format
feat:,fix:,docs:,refactor:,test:,chore:
- Branching: Feature branches, no direct commits to main
- Code Reviews: Required for all changes
- Atomic Commits: One logical change per commit
AI-Assisted Workflow
Section titled “AI-Assisted Workflow”- Plan: Define objective and requirements clearly, incorporating insights from
docs/Lessons.md - Generate: Use AI tools for boilerplate, refactoring, or implementations
- Verify: ALWAYS review and test AI-generated code
- Update docs/Lessons.md: Document useful learnings from dev process, especially when AI or human struggles to produce results, so future development efforts are more effective and efficient
- Iterate: Refine based on testing and world-class standards
5. Testing & Quality Assurance
Section titled “5. Testing & Quality Assurance”AI agents: for how you must test your own work before reporting completion (autonomous testing, Levels 1–4, web/UI verification, anti-patterns), see the SSOT AI-Testing-Standards. The rules below are the codebase’s human + AI dev-testing philosophy.
Testing Philosophy
Section titled “Testing Philosophy”- Test behavior, not implementation
- Unit tests for business logic
- Integration tests for system boundaries
- E2E tests for critical user paths
- Aim for 80% coverage, focus on critical paths
Current Testing Tools
Section titled “Current Testing Tools”- Unit Tests: Vitest for component and utility testing
- E2E Tests: Playwright for user flow testing
- Performance: Lighthouse CI for performance regression
- Accessibility: Axe-core for automated a11y testing
Code Quality Tools
Section titled “Code Quality Tools”- Linting: ESLint + Prettier for code consistency
- Type Safety: TypeScript strict mode, Python type hints
- Bundle Analysis: Analyze and optimize bundle size
- Performance Budgets: Set and enforce performance budgets
6. Integration Patterns
Section titled “6. Integration Patterns”Third-Party Services (Currently Using)
Section titled “Third-Party Services (Currently Using)”- Email: Amazon SES via Sendy for bulk emails
- Analytics: Plausible Analytics for privacy-first tracking
- Forms: Web3Forms or Formspree for form handling
- Payments: Stripe for payment processing
- Search: Meilisearch for site search functionality
Automation Integration
Section titled “Automation Integration”- Workflow Automation: Currently using n8n for data processing
- Webhooks: Implement for real-time integrations
- Cron Jobs: Server-side scheduling for maintenance tasks
7. Project Categories
Section titled “7. Project Categories”7.1 System Utilities
Section titled “7.1 System Utilities”- Scripts and tools to automate tasks (Python/Bash)
- Type hints for Python
- Clear error handling and logging
7.2 Side Projects (Learning)
Section titled “7.2 Side Projects (Learning)”- Speed > Perfection
- Experiment freely with new technologies
- Document learnings, not every detail
7.3 Production Apps & Sites
Section titled “7.3 Production Apps & Sites”- Quality, Testing, and Stability are paramount
- Follow ALL standards in this document
- Comprehensive testing required
- Monitoring from day one
- Proper backup strategy (automated, tested, documented)
8. Critical Reminders
Section titled “8. Critical Reminders”Before Starting ANY Task
Section titled “Before Starting ANY Task”IF ANY TASK IS NOT VERY CLEAR OR ADDITIONAL INFORMATION IS NEEDED OR EVEN WOULD BE HELPFUL, ALWAYS ASK. IT IS MUCH MORE EFFECTIVE TO BE 100% CLEAR ON WHAT IS EXPECTED FROM THE START.
During Development
Section titled “During Development”- Follow naming conventions from this document and related naming systems
- Never use hacks - find robust solutions
- Review AI-generated code carefully
- Write tests for critical paths
- Document complex decisions
Before Deployment
Section titled “Before Deployment”- Run security scans
- Check performance metrics
- Verify error monitoring is active
- Test backup and restore procedures
- Review secrets management
Document Version: 1.0.0
Last Updated: 2024
Single Source of Truth: This document supersedes all previous development guidelines