docs: Commons integration — README, docs site, roadmap, launch draft#10
Conversation
skdd add <source> [selector]: install a drop (or one skill) from a Commons repo — GitHub shorthand, git URL (#ref), or local path. Strict validation before install, collision check with --rename escape hatch, registry provenance owner/repo@shortsha (drop-id), full sha recorded in .skdd-lock.json, mirrors refreshed via the existing safe (never forced) link path. --dry-run/--json/--non-interactive/-g. skdd push <skill|pack>: ship a skill upstream as a PR via gh. Strips machine-local state (usage-count -> "0", last-used dropped), preserves forged-* provenance. Upstream-known skills branch as evolve/<name> with a diff summary; new skills branch as skill/<name> into incoming/ (or an existing drop via --drop, updating drops.json). Local-path targets are a --dry-run test seam. Default target from ~/.skdd/config.toml commons key (smol-toml, already a dependency). skdd drops [--from]: list a Commons' drops (table/json). No new runtime dependencies. 24 new tests incl. a regression test that add never force-replaces a populated mirror dir.
…e-the-session reminder Two gates in plugins/skdd-claude (v0.3.0), both OFF by default: - finish-loop-gate.mjs (Stop): when the session changed non-test product source and the final report claims success without observed evidence, bounce the stop once with instructions to verify or say it's unverified. Blocks at most once per session (state in $TMPDIR) — the codex gate's anti-loop pattern. - freeze-reminder.mjs (SessionEnd + PreCompact): non-blocking systemMessage when a substantive session ends with the colony registry untouched since session start. Deterministic heuristics; silent when unsure. - session-start.mjs seeds the session-start timestamp; lib/state.mjs holds state + toggle reads (.claude/skdd.local.md via /skdd-hooks). A skill is a procedure the model follows when it decides to; a hook is a gate for when it forgets. Node >=20 built-ins only; 11 e2e tests spawn the real scripts.
…e, roadmap tranche, launch draft - README: 'SkDD Commons — skills that evolve in public' section with the add one-liner and current-drop link - docs/commons.md (+ site mirror & sidebar group): drops, add/push flow, security posture, hooks pointer - ROADMAP: Commons tranche (shipped, launch pending) + Commons v2 deferred list (skdd update three-way merge flagged highest-value) - docs/plans/2026-07-commons-announcement.md: launch copy drafted for review, not posted - plan §9: phases 1-4 logged, manual launch checklist queued
The site's prebuild sync copies canonical docs into src/content/docs and removes strays — a hand-copied mirror gets deleted on the next build. commons.md now flows through the manifest like every other doc, and the generated mirror is gitignored.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f3a333163
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }, | ||
| { | ||
| label: "Commons", | ||
| items: [{ label: "SkDD Commons & drops", link: "/commons/" }], |
There was a problem hiding this comment.
Add the Commons page to the docs sync mapping
Because site/scripts/sync-docs.mjs cleans generated docs and only copies explicit mappings, and docs/commons.md was not added there, prebuild will leave no site/src/content/docs/commons.md. This new sidebar item therefore sends users to /commons/ with no Starlight page after every docs build; add the Commons doc to the sync mapping before exposing the nav entry.
Useful? React with 👍 / 👎.
…yload Codex adversarial review findings: - [high] drops.json ids/names were used as filesystem path segments unchecked, so a malicious Commons could list a skill like '../escape-skill' and write outside the colony. Every manifest parse site now enforces the lowercase-kebab-case grammar (no slashes, dots, or absolute paths) and add asserts source/destination containment as defense in depth. - [medium] push copied the entire local skill directory into the PR clone, so dotfiles, logs, .env files, or symlinked content could leak to a public Commons PR. Only an allowlisted payload travels now (SKILL.md + regular files under scripts/, references/, assets/); dry-run enumerates exactly what travels and what stays home. +5 adversarial tests (hostile manifests, payload exclusion incl. symlinks and .env); 899 total green.
# Conflicts: # docs/plans/2026-07-skdd-commons.md
# Conflicts: # docs/plans/2026-07-skdd-commons.md
readFileSync follows symlinks, so a symlinked SKILL.md (or a symlinked skill directory) could exfiltrate arbitrary file contents into a Commons PR even with the payload allowlist. push now lstats both and refuses; collectPublishablePayload throws defensively. +2 tests.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 50a3a70b23
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| { src: "docs/global-colony.md", dest: "global-colony.md" }, | ||
| { src: "docs/mcp-sync.md", dest: "mcp-sync.md" }, | ||
| // Commons — community drops + the add/push evolution loop. | ||
| { src: "docs/commons.md", dest: "commons.md" }, |
There was a problem hiding this comment.
Untrack the generated Commons page
With this new mapping, site/scripts/sync-docs.mjs rewrites site/src/content/docs/commons.md on every pnpm build/prebuild, but that generated page is already tracked in this branch and differs from the new canonical docs/commons.md content; adding it to .gitignore does not untrack it. In the inspected repo, running the sync script immediately leaves site/src/content/docs/commons.md dirty, so docs builds will keep producing uncommitted changes unless the generated copy is removed from the index or regenerated and committed consistently.
Useful? React with 👍 / 👎.
…s loop Pack discovery called parseSkill (readFileSync follows symlinks) before the items-loop symlink guard, so a pack push dereferenced a symlinked skill while scanning. Hoisted the check into a skillDirIsSymlinked helper applied at discovery: direct pushes refuse, pack scans skip+warn, and the items-loop check remains as defense in depth. +1 pack-scan test.
- add/push honor .colony.json canonicalSkillsDir (pass to runLink; resolve before scanning) — .colony.json users could not add/push before - reject symlinks anywhere in a fetched Commons skill tree (add), matching the push-side guard - registry cells escape pipes/newlines and the parser unescapes, so an untrusted Commons description can't inject fake rows - push: metadata stripping scoped to frontmatter only (body examples with usage-count/last-used lines are preserved) - push: validate each local skill --strict before opening a PR CI would reject; distinguish an empty diff from a real git commit failure and hint at missing git identity; validate pack ids as git-ref-safe branch slugs; clear the upstream dir before an evolve copy so deleted files don't linger - parseSource splits #ref before local-path detection (../commons#feature) and local #ref checks out from a clean clone (never mutates the user repo) - local dirty repos record a -dirty provenance marker + lock flag - malformed ~/.skdd/config.toml surfaces instead of silently defaulting - extract lib/colony.ts (canonicalDirName) shared by add/push +11 tests (hostile symlink/name-mismatch in add, frontmatter-only strip, registry injection round-trip, parseSource #ref, provenance dirty). 914 green.
- finish-loop measures product-source changes against the SessionStart baseline (a file already dirty before the session no longer triggers), recognizes suffix-style test names (foo_test.go, test_foo.py), fails OPEN when the anti-loop flag can't be persisted, and resolves the toggle by searching upward so a repo-root toggle works from a subdir - freeze counts untracked files and commits-since-start as substantive (not just git diff HEAD), recognizes .skills-registry.json, and never invents a start time (silent when SessionStart didn't seed state) - SessionStart snapshots HEAD + changed paths and resets per-run state, so a resumed session doesn't inherit a stale finishLoopBlocked/baseline - saveState reports success; lib/state.mjs gains repoSnapshot/commitsSince +7 hook tests. Node >=20 built-ins only; 932 total green.
The baseline-by-path check ignored any path present at SessionStart, so a further edit to an already-dirty product file went unnoticed. SessionStart now hashes each dirty file; sessionChangedPaths content-compares, so both newly-dirty files AND edits to pre-dirty files count, while a pre-dirty file left untouched is still ignored. finish-loop and freeze both use it. +1 regression test; 933 green.
git status emits cwd-relative paths, so when SessionStart and the Stop gate ran from different directories (subdirectory sessions) the baseline hash keys didn't align and an edit to a pre-dirty file was missed or misattributed. repoSnapshot now resolves git rev-parse --show-toplevel and runs status + hashes files from the repo root, making paths and hashes independent of each hook's invocation cwd. +1 regression test (SessionStart from root, edit + Stop from a subdir → blocks). 934 green.
- local #ref add clones with --branch (branch/tag refs resolve; sha falls back to detach) instead of failing on origin-only branches - --dry-run (including -g) no longer creates ~/.skdd - reject symlinked pack ancestors via realpath containment (a Commons making packs/ or the drop dir a symlink can't serve outside bytes) - validate pack ids with a full git-ref rule (reject foo.lock, a..b, …) - push payload skips non-regular files (FIFO/socket/device would hang cp) - strip an emptied metadata: block so the pushed SKILL.md has no null key - push validates the stripped payload (what the Commons CI sees) - rewrite drops.json only when a new skill is actually added Deferred: renamed-then-pushed skills classify by local name (niche). +8 tests; 919 green.
- finish-loop counts committed product changes since session start (the commit-before-final-report workflow left a clean worktree and slipped through), and fails open when no SessionStart baseline exists - tighten the 'I ran' evidence marker to require a real run object, so an explicitly-unverified 'I ran out of time … should work' still bounces - freeze treats a large single-file change as substantive (line count), finds a repo-root registry from a subdirectory, and takes the max mtime across .md/.json (touching either active format counts as frozen) - bound the toggle search at the git/project root so a parent-workspace .claude/skdd.local.md can't opt a nested repo in +8 tests; 943 green.
# Conflicts: # docs/plans/2026-07-skdd-commons.md
Phase 4 of docs/plans/2026-07-skdd-commons.md. Stacked on #9 (which stacks on #8) — merge in order 8 → 9 → 10 and each retargets automatically.
skdd addone-liner, current drop).docs/commons.md+ Starlight mirror with a new 'Commons' sidebar group — site builds clean (28 pages).skdd update(three-way evolution merge) flagged as the highest-value follow-up.docs/plans/2026-07-commons-announcement.md— for review, not auto-posted.