Skip to content

Latest commit

 

History

History
89 lines (58 loc) · 22.2 KB

File metadata and controls

89 lines (58 loc) · 22.2 KB

Knowledge

Patterns, rules, and lessons learned while building ytstack. This file is read by every future session. Keep it short. Keep it actionable.

Conventions

  • UX contracts are mandatory. Every skill must pass docs/ux/* checks. bin/ytstack-check enforces them locally (run before commit); there is no UX-contract CI gate, only secret-scan.yml. Non-procedural skills opt out of the structural sections via kind: directive or kind: reference (see DECISIONS 2026-05-31 + skill-structure.md).
  • No 1:1 copying from upstream. Vendored superpowers/gstack content is read-only. If we need to change behavior, wrap it in a ytstack-namespaced skill, don't modify the vendor.
  • Artifact-first thinking. If a decision or learning would help future sessions, write it to DECISIONS.md or KNOWLEDGE.md, not just conversation.
  • Third-party methodology attribution is "inspired by", never verbatim. Concepts adapted, prose rewritten. Do not name the external source in public artifacts.

Lessons learned

  • Hook matcher startup|clear|compact is the key. SessionStart hook only fires on these three events. /reload-plugins does NOT fire it, which means mid-session plugin enable produces dead-looking skills. Always test with /clear after install.
  • The using-<plugin> skill is a hook-injected prompt, not a regular skill. superpowers demonstrates this: the SessionStart hook reads skills/using-superpowers/SKILL.md and injects the full content via additionalContext. That's why auto-triggering works -- it's forceful prompt priming, not passive skill availability.
  • Agent Teams replicate GSD's fresh-context-per-task natively. Each teammate is a separate Claude Code session with its own 200k window. No need to build this ourselves.
  • CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 required to enable Agent Teams. Version v2.1.32+ required.

Gotchas -- and things we got wrong before

  • Competitive claims require skill-inventory checks. Initial README drafts (2026-04-24) overclaimed that superpowers "offers nothing for planning" and gstack "offers nothing for execution." Both wrong. superpowers has brainstorming, writing-plans, executing-plans. gstack has investigate (equivalent to superpowers' systematic-debugging), qa, review, ship, and context-save/restore skills. The actual ytstack value-prop is cherry-picking the non-overlapping best from each, plus adding GSD-style artifact hierarchy that neither has. Lesson: before any "X tool offers nothing for Y" statement, grep the actual skill dirs.

  • Project-state injection alone is not enough -- you need a directive. The initial M002 SessionStart hook injected only project state (milestone / slice / task + next action). That told the agent where it was, but not what to do with ytstack. Result: ytstack skills sat dormant unless the user typed /ytstack:<name> manually. The superpowers pattern is clearer: their SessionStart hook injects the entire content of using-superpowers/SKILL.md -- a forceful directive with anti-rationalization Red Flags and "1% chance → invoke" compliance pressure, and skill selection driven by each skill's own description: field (semantic match, no phrase table). Without this primer, the plugin is a slash-command menu the user has to drive. With it, the agent reaches for skills proactively based on natural-language intent. See DECISIONS 2026-04-24 "Add using-ytstack" + 2026-04-24 "Skill selection is semantic" for ytstack's version (we initially added a trigger-map alongside the directive; rolled back same day as antipattern). Lesson: a SessionStart hook that only injects state produces a passive plugin; it needs to also prime the agent's behavior. But do NOT reach for a phrase-map -- sharpen descriptions instead.

  • Don't cargo-cult templates. Cross-reference dependent files before designing a new artifact. While writing the three agents/ files (post-M009), we used superpowers/agents/code-reviewer.md as template and carried over its frontmatter verbatim -- which didn't include a skills: field. Result: three ytstack agents that are supposed to invoke ytstack skills, with no skills allowlist. The miss was caught on review ("die agents haben keine skills?????"), not by process. Root cause: we reached for a template instead of re-reading skills/spawn-milestone-team/SKILL.md (which references these agents and specifies what skills they'll use) and designing from purpose. Lesson: when creating a new file type (skill, agent, hook), FIRST read every other file that references or depends on this new file, THEN design from purpose. Templates are reference, not contract. Template-driven development produces files that look right and miss the point. See docs/ux/agent-structure.md (added to prevent this exact miss in future) and the 2026-04-24 DECISIONS entry on using-ytstack.

  • Bulk find-replace can corrupt meta-references. While fixing 288 em-dash () warnings by bulk-replacing --, the same replacement hit docs/ux/writing-style.md's banned-punctuation rule itself (which was literally describing the banned character), leaving the rule reading "Em dashes (--)" -- self-contradictory. Same with KNOWLEDGE.md's lesson and REVIEW-NOTES' entry about the same issue. Lesson: before any bulk text replacement, grep for files where the text-being-replaced is the subject of discussion (meta-references), not just content. Exclude or post-edit those files. Simple heuristic: if a file contains phrases like "banned", "converts X to Y", "rule about Y", the Y-references in that file are probably meta.

  • Plugin skills (skills/) vs project-meta skills (.claude/skills/) are distinct scopes; classify before writing. Surfaced 2026-04-24: a freshly-authored check-consistency skill (which audits ytstack's own docs-vs-code drift) was placed under skills/ by default -- which would have shipped an internal-audit tool to every ytstack end user as if it were part of the plugin's value prop. Rule: anything that exists to help build / audit / maintain ytstack itself belongs under .claude/skills/. Anything that ships to ytstack users belongs under skills/. Before creating a new skill, ask: is this for end users of the plugin, or for ytstack-dev? Place accordingly. Don't "drop it under skills/ for now and move later" -- default placement shapes the mental model.

  • DRAFT synthesis papers must stay clearly subordinate to their source. Surfaced 2026-04-24: a concept paper was generated in-session and immediately treated as authoritative, including landing a DECISIONS.md entry that "settled" an architectural question based on the unapproved paper. Rule: derivative docs (concept, summaries, inventories) are always subordinate to their source (README / DECISIONS). When synthesis and source disagree, source wins until the human explicitly signs off on the synthesis. Never use unapproved synthesis to drive DECISIONS entries or README edits.

  • Append-only DECISIONS means approvals, not proposals. A DECISIONS entry records a locked choice the user has accepted. Writing one based on "the concept paper implies X" (when the paper itself is unapproved) corrupts the register. Always check: has the user explicitly agreed to this? If not, don't file. Proposals go in REVIEW-NOTES, not DECISIONS.

  • Context loss is structural, not incidental. "Remember this" or "keep it in your context" doesn't work across compaction. The fix is persistent: condensed memory file + source-of-truth pointers in CLAUDE.md + explicit read these three files before proposing changes ritual. Surfaced 2026-04-24 when I repeatedly forgot the curation scope and invented architecture rules ("strict gstack-vs-superpowers split") that contradicted README. Now captured as auto-memory project_ytstack_readme_facts.md with re-read instructions.

  • Don't ask obvious clarifying questions. If the user has just told you what to do and the next step is self-evident, execute; don't re-ask for permission. "Should I X?" after they said "do X" is noise, erodes trust, and signals you didn't parse the instruction. Surfaced 2026-04-24 repeatedly.

  • .cursor-plugin/plugin.json is a different schema than .claude-plugin/plugin.json. The superpowers repo contains BOTH. The .cursor-plugin/ variant declares "skills": "./skills/", "agents": "./agents/", "commands": "./commands/", "hooks": "./hooks/hooks-cursor.json" -- that is the Cursor format. The .claude-plugin/ variant is minimal (no path pointers). Reading the Cursor file when researching Claude Code plugin structure produces 4+ iterations of wrong "fixes" that the CC install validator rejects. Rule: when researching plugin manifest fields, read .claude-plugin/plugin.json from a known-working CC plugin (e.g. claude-md-management), never .cursor-plugin/plugin.json. Surfaced 2026-04-24 during the install debacle.

  • Claude Code plugin components are implicitly auto-discovered. Adding explicit path pointers in plugin.json causes duplicate-load errors or validation rejects. Per CC plugins-reference docs: "Skills and commands are automatically discovered when the plugin is installed" from skills/ in plugin root. Hooks auto-load from hooks/hooks.json. Agents auto-load from agents/. Adding "skills": "./skills/" or "hooks": "./hooks/hooks.json" to plugin.json triggers "Duplicate hooks file detected" or "agents: Invalid input" on install. Rule: plugin.json stays minimal -- name, version, description, author, homepage, repository, license, keywords. No component path pointers. Let auto-discovery handle it.

  • /reload-plugins output counts are undocumented and misleading; use /skills, /plugin, /agents for authoritative audits. The /reload-plugins line "Reloaded: N plugins · M skills · X agents · Y hooks · ..." has no documented semantics beyond "reports counts for each reloaded component". Counts do not match simple total-across-plugins or delta-since-last-reload interpretations. A plugin shipping 16 skills may report "1 skill" on reload. Rule: to verify what's actually registered, open /skills (lists all registered skills with scope + token cost) or /plugin (Installed tab shows per-plugin components). Do not debug install issues from /reload-plugins counts. Surfaced 2026-04-24 after 9 commits chasing a nonexistent bug.

  • Excalidraw rendering needs a version-pinned esm.sh import. https://esm.sh/@excalidraw/excalidraw?bundle (no version) hits a transitive 404 on @braintree/sanitize-url and the renderer hangs. Pin to @excalidraw/excalidraw@0.18.0?bundle. Pattern: cp render_template.html render_template.html.bak && sed -i '' 's|...|...@0.18.0?bundle|' render_template.html && uv run python render_excalidraw.py --output ... && mv render_template.html.bak render_template.html. Always restore the template after the render to avoid leaking a per-project pin into the global skill. Surfaced 2026-04-24/25 across all three workflow infographics.

  • Excalidraw dark mode is a render flag, not a color swap. First attempt at a dark variant of the greenfield infographic copied the .excalidraw source and replaced fill/stroke colors -- looked muddy and unbalanced because Excalidraw's dark theme also flips contrast and shadows. The correct path: keep ONE .excalidraw source of truth, render twice -- once with appState.exportWithDarkMode = false (light), once with = true (dark). Patch the same render_template.html like the version pin. User caught the color-swap as fake on first review. Source-of-truth-stays-singular even for visual variants.

  • GitHub Flavored Markdown <details open> is the lever for default-expanded sections. <details><summary>...</summary>...</details> collapses by default. Add open to the opening tag (<details open>) to render expanded but still collapsible. Use for "primary" diagrams the reader should see immediately, while keeping secondary ones collapsed. Pair the summary text with the actual state ("click to expand" when closed-by-default, "click to collapse" when open). Used 2026-04-25 in README Workflows section.

  • Plugin-marketplace owner is a required object. .claude-plugin/marketplace.json REQUIRES a top-level "owner": { "name": "...", "url"|"email": "..." } object. Missing owner causes install to fail with owner: Invalid input: expected object, received undefined. Similarly plugins[].author is expected (seen in superpowers, not strictly required but recommended). Self-marketplace "source": use "./" to match superpowers, not "./." (both may work but ./ is the convention). Surfaced 2026-04-24 during first install attempt.

Gotchas

  • Each bash code block in a SKILL.md runs in a separate shell. Variables don't persist between blocks. Pass state via prose ("remember as _X") or disk files, never via shell variables across blocks.

  • Dot-prefixed directories under ~/.claude/skills/ are hidden from skill discovery. We exploit this for .deactivated/ snapshots. Claude Code only scans immediate non-dot children.

  • gstack SKILL.md files are auto-generated from .tmpl via bun run gen:skill-docs. When reading vendored gstack content for reference, compare with the template, not just the generated MD.

  • Only the em-dash rule is mechanically enforced; the old banned-words list was retracted. Per DECISIONS 2026-04-24 "drop banned-vocabulary list (scope creep)", bin/ytstack-check flags only em-dashes (the U+2014 character; use --). macOS autocorrect silently converts -- to that character, so disable smart-punctuation or run bin/ytstack-check pre-commit. The rest of docs/ux/writing-style.md (short sentences, concrete nouns, no delve/robust/comprehensive) is quality guidance reviewers apply, not a grep gate.

  • Headless claude -p blocks on Write permissions by default. In M001 T05 smoke-test, first headless run stalled because -p mode cannot display or approve the Write-tool permission dialog. Fix: pass --permission-mode acceptEdits for file-creating skills, or --dangerously-skip-permissions for fully isolated sandbox/CI runs. Document this prominently for any automated-test scenario.

  • Headless mode may not register --plugin-dir skills for Skill tool invocation. In the same smoke-test, the headless Claude reported that the Skill tool invocation for ytstack:init-project errored as "not registered in the harness", but the agent fell back to following the SKILL.md procedure manually -- producing correct output. This means: (1) our skills must work as "prompted instructions" without requiring the Skill tool, and (2) real interactive testing is still needed to verify the Skill tool registration works in non-headless mode. Worth an upstream bug report once reproduced in interactive mode.

  • RemotePluginSync (Claude Desktop + SSH) refuses ANY symlink in a plugin directory and aborts the whole sync. Reported failure: RemotePluginSync: refusing symlink in plugin directory: vendor/gstack/bin/gstack-brain-reader. Sources of symlinks in this repo: vendored upstream symlinks (e.g. vendor/gstack/connect-chrome, vendor/gstack/bin/gstack-brain-reader, vendor/superpowers/AGENTS.md -> CLAUDE.md) AND, since 2026-05-31, our own repo-root CLAUDE.md -> AGENTS.md shim. Native install on the remote host is unaffected; only Desktop+SSH-sync trips it. UNVERIFIED for the new root symlink -- verification path: install via a Desktop SSH session against a remote host and watch for the refusing symlink error. Mitigation sketch (from an old, superseded draft in the claude/thirsty-bhabha-a03490 worktree): dereference symlinks in sync-upstream.sh after each subtree pull (find vendor -type l | ... rm + cp -R), idempotent. Not yet planned as a milestone.

  • A literal triple-backtick inside a ```! executable block crashes the preamble for every user. (M012 / issue #20) The harness extracts the executable block by scanning for the next fence; a literal triple-backtick in a sed/awk pattern (e.g. sed -n '/^```/,...') terminates the extraction early, so the captured bash is truncated and fails with unmatched ' / parse error. Environment-independent. Fix: generate the fence at runtime with _FENCE=$(printf '\140\140\140') and match fences by PREFIX (index($0,f)==1, handles language-tagged opening fences like ```bash); never write a literal triple-backtick inside the block. This crashed verification-before-completion AND, on spawn, the ytstack:implementer / ytstack:verifier subagent types -- they run a skill preamble at startup, so a broken preamble takes the agent down before it does any work.

  • Hooks receive session_id on stdin JSON, not from an env var. (M012 / issue #21-RC2) There is no CLAUDE_SESSION_ID env var; reading it always yields the fallback, so session-end recorded session_id:"unknown" on every entry. Fix: capture stdin once near the top (INPUT=$(cat)) and parse printf '%s' "$INPUT" | jq -r '.session_id // "unknown"'. Available hook env vars are CLAUDE_PROJECT_DIR, CLAUDE_PLUGIN_ROOT, CLAUDE_PLUGIN_DATA, CLAUDE_ENV_FILE, CLAUDE_EFFORT, CLAUDE_CODE_REMOTE -- none carry the session id.

  • Subagents load skills/hooks from the installed plugin CACHE, not your working tree. A fix committed to the repo is INERT in the running session until a version bump + release + /plugin update. Corollary: a bug in a published skill (e.g. #20) keeps reproducing in-session and crashes ytstack subagent types even after you have committed the fix. Dev workaround when a ytstack skill is broken: dispatch general-purpose agents with self-contained prompts (they do not auto-run the broken ytstack preamble) instead of the ytstack:* agent types.

  • slice-milestone detects unplanned slices only by the literal string "(to be planned)". (M012) If plan-milestone pre-fills the slice goals in the roadmap, the detector finds zero unplanned slices and short-circuits even though no M###-S##-PLAN.md files exist yet. More robust signal: presence of the per-slice PLAN files, not the placeholder text.

  • Uncommitted milestone planning in a stale worktree causes number collisions. (M012) A prior session left an uncommitted "M012 = vendor symlink dereferencing" plan in the claude/thirsty-bhabha-a03490 worktree; because it never reached main, a later session reused M012 for the swarm fix-pack. Lesson: reserve milestone numbers in the committed ROADMAP.md table, never only in an uncommitted worktree. The vendor-symlink work is reserved as M014.

  • A PostToolUse hook that writes a TRACKED file after every commit can never settle the tree. (M015 / issue #22) The describing-line is written after the commit it describes, so it is always uncommitted; committing it re-fires the hook; an always-dirty .ytstack draft is invisible to a worktree branched from main, which silently breaks spawn-milestone-team dispatch. Two compounding bugs made it worse: a glob matcher *git*commit* that fired on echoes / git log | grep commit / doc strings, and a git rev-parse HEAD append with no new-commit check and no dedup (N identical SHAs, N timestamps). Fix pattern: the hook is stub-once (create the draft once, then [ -f "$SUMMARY_FILE" ] && exit 0 -- never re-write), and the durable commit-to-task list is owned by summarize-task, which greps git log --grep "^M###-S##-T##" (ytstack's commit convention) into a committed ## Commits section. Matcher tightened to require git commit after a command separator ((^|[;&|()])[[:space:]]*git[[:space:]]+commit) -- a raw lexical matcher cannot perfectly distinguish a real invocation from an echo of the phrase, but stub-once makes a rare false-positive harmless (one premature stub, overwritten at close). Regression guard: tests/post-tool-use-bash.test.sh.

  • Vendor-drift audit: only the six cat-ed wrapped SKILL.md matter; check path-existence first (silent break), then content. (2026-05-31) The wrappers inline upstream via ```!cat vendor/<repo>/.../SKILL.md, so the breaking surface is exactly those six files. Method: shallow-clone both upstreams, (1) confirm each cat-ed path still exists upstream -- a rename makes the cat silently empty and the wrapper produces nothing; (2) diff the six SKILL.md. Result of the 2026-05-31 audit: superpowers 6efe32c -> 6fd4507 left all three wrapped SKILL.md byte-identical (safe, pulled + on origin/main as ca6675e); gstack aeea57f -> 3bef43b is a v1 restructure (~1000 changed lines/file) but the methodology anchors (forcing questions, CEO modes, eng anchors) all survive -- no hard break. The .tmpl diffs are irrelevant; the wrapper cats the rendered SKILL.md, not the template.

  • gstack-v1 adds a gbrain: frontmatter context_queries block -- NOT covered by the "Vendored-preamble drift accepted" decision. (2026-05-31) That 2026-04-25 decision accepts gstack's shell preamble failing via || true. gstack-v1's gbrain: is YAML frontmatter (queries against ~/.gstack/builder-profile.jsonl, eureka.jsonl, etc.), plus a new GSTACK_PLAN_MODE detection. Frontmatter is not || true-guarded shell, so before pulling gstack, re-open DECISIONS 2026-04-25 "Vendored-preamble drift accepted" and decide how the wrapper should treat gbrain. Also note the existing preamble already side-effects beyond silent failure: mkdir -p ~/.gstack/sessions, analytics-jsonl appends, and telemetry/feature-discovery AskUserQuestion instructions the agent may follow inside a ytstack session.

Terminology

  • Milestone (M###): a shippable chunk of the roadmap. 1-7 slices per milestone.
  • Slice (S##): 1-7 tasks inside a milestone, executable sequentially.
  • Task (T##): one atomic unit that fits in one context window. If it doesn't, split into two tasks.
  • Artifact: a persistent file in .ytstack/ that survives across sessions.
  • Spec: a design doc written before code, committed under docs/ytstack/specs/.
  • Handoff: state written when a session ends, read when the next session starts.
  • Sentinel file: marker in ~/.ytstack/.<name>-prompted that indicates an "ask-once" question has been answered.
  • Tier: question scope -- core (session-wide, persisted), task (single turn, not persisted), background (skippable, default-pickable).

Related projects