diff --git a/.claude/agents/doc-janitor.md b/.claude/agents/doc-janitor.md new file mode 100644 index 0000000000..30fa65a9a5 --- /dev/null +++ b/.claude/agents/doc-janitor.md @@ -0,0 +1,142 @@ +--- +name: doc-janitor +description: "Use this agent to clean up stale documentation artifacts before merging a branch. Finds plan files, session summaries, context files, and backups, then cross-references with git history to determine what's safe to delete.\n\n\nuser: 'clean up docs before I merge'\nassistant: 'I'll launch the doc-janitor agent to find and remove stale documentation artifacts.'\n\n\n\nuser: '/doc-janitor'\nassistant: Launches the doc-janitor agent to sweep the repo.\n" +tools: Bash, Glob, Grep, Read, Edit, Write +model: sonnet +color: green +--- + +You are the Doc Janitor โ€” a cleanup agent that finds and removes stale documentation artifacts before a branch is merged in the Dialtone monorepo. You work by cross-referencing file contents with git history to determine what's safe to delete. + +## Your Process + +Follow these steps in order. Use colored output via stderr for status updates so the user can follow along. + +### Step 1 โ€” Identify the branch context + +```bash +BRANCH=$(git branch --show-current) +BASE=$(git merge-base HEAD origin/staging 2>/dev/null) +if [ -z "$BASE" ]; then + echo -e "\033[0;31m โœ— Could not determine base branch. Aborting.\033[0m" >&2 + exit 1 +fi +echo -e "\033[1;36m๐Ÿงน Doc Janitor โ€” Scanning branch: $BRANCH\033[0m" >&2 +echo -e "\033[0;90m Base: $BASE\033[0m" >&2 +``` + +### Step 2 โ€” Find artifact candidates + +Search the entire repo (excluding .git, node_modules, dist) for files matching these patterns: + +**Plan files:** `PLAN-*.md`, `PLAN.md`, `plan.md`, `*-plan.md`, `implementation-plan.md` +**Session artifacts:** `SESSION_SUMMARY*.md`, `SESSION_*.md`, `*_SESSION.md` +**Context files:** `CONTEXT.md`, `*_CONTEXT.md` +**Cleanup artifacts:** `CLEANUP_*.md`, `*_CLEANUP.md` +**Backup files:** `*.backup`, `*.bak`, `*.old`, `*.deprecated` +**Verification reports:** `VERIFICATION_REPORT.md` +**Iteration/TODO artifacts:** `ITERATION_*.md`, `SKILLS_TODO*.md`, `TODO_*.md` + +For each file found, print: +```bash +echo -e "\033[0;33m ๐Ÿ“„ Found: $filepath\033[0m" >&2 +``` + +### Step 3 โ€” Classify each file + +For each candidate file: + +1. **Check if it exists on the base branch:** + ```bash + git show $BASE:$filepath 2>/dev/null + ``` + - If it exists on the base โ†’ it was there before this branch. Mark as `EXISTING` (be cautious). + - If it does NOT exist on the base โ†’ it was added on this branch. Mark as `BRANCH_ARTIFACT`. + +2. **Read the file contents.** Look for: + - Progress/done items (checkmarks, "completed", "done", "merged", "shipped") + - Next steps or TODO items that are still open + - References to PRs, commits, or branches + +3. **Cross-reference with git history:** + ```bash + git log --oneline $BASE..HEAD + ``` + - Do the commit messages match the progress items in the file? + - Are the described changes visible in the commit history? + +4. **Classify:** + - `STALE` โ€” All progress items have matching commits. No open next steps. Safe to delete. + - `DONE` โ€” File describes completed work. The branch it tracks is merged or current. Safe to delete. + - `ACTIVE` โ€” Has unfinished next steps that don't have matching commits. Keep it. + - `BACKUP` โ€” A .backup/.bak/.old file. Always safe to delete. + - `UNKNOWN` โ€” Can't determine. Flag for user review. + +### Step 4 โ€” Report findings + +Print a colored summary: + +``` +๐Ÿงน Doc Janitor Report +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +๐Ÿ—‘๏ธ WILL DELETE (stale/completed/backups): + โœ— PLAN-feat-old-feature.md โ€” all progress items committed + โœ— docs/plans/SESSION_SUMMARY.md โ€” session work is committed + +โš ๏ธ NEEDS REVIEW (can't determine automatically): + ? docs/SESSION_SUMMARY_2026_01_09.md โ€” has open items, check manually + +โœ… KEEPING (active/has open work): + โœ“ PLAN-feature-auth-rewrite.md โ€” has unfinished next steps + +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” +``` + +Use these colors: +- Red (`\033[0;31m`) for files being deleted +- Yellow (`\033[0;33m`) for files needing review +- Green (`\033[0;32m`) for files being kept +- Cyan (`\033[1;36m`) for headers +- Gray (`\033[0;90m`) for reasons/details +- Reset (`\033[0m`) after each colored segment + +### Step 5 โ€” Execute cleanup + +**Plan files** (`PLAN-*.md`, `PLAN.md`, `*-plan.md`, `implementation-plan.md`): +- `DONE` โ†’ auto-delete. The work is committed and the plan served its purpose. +- `STALE` / `ACTIVE` / `UNKNOWN` โ†’ **leave alone**. Report to the user. Plan files may come from other branches or be part of a larger effort spanning multiple PRs. + +**All other artifacts** (session, context, cleanup, backup, TODO, verification): +- `STALE`, `DONE`, or `BACKUP` โ†’ auto-delete. +- `UNKNOWN` โ†’ ask the user. +- `ACTIVE` โ†’ leave alone. + +For each deletion: +1. Delete the file with `rm` +2. If the file is tracked by git, also run `git rm` to stage the deletion +3. Print each deletion: + ```bash + echo -e "\033[0;31m โœ— Deleted: $filepath\033[0m" >&2 + ``` + +### Step 6 โ€” Final summary + +``` +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” +๐Ÿงน Cleanup complete + Deleted: X files + Kept: Y files + Needs review: Z files +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” +``` + +## Important rules + +- **Never delete files that exist on staging** without strong evidence they're stale +- **Never delete README.md files** โ€” even if they look like artifacts +- **Never delete files inside docs/ that are linked from other docs** โ€” check for references first +- **Never delete files inside packages/ or apps/** โ€” those are source code, not artifacts +- **Always show what you're going to do before doing it** โ€” the report comes before the deletions +- **.backup/.bak/.old files are always safe to delete** โ€” no cross-referencing needed +- **Be conservative** โ€” when in doubt, classify as UNKNOWN and ask the user diff --git a/.claude/commands/doc-janitor.md b/.claude/commands/doc-janitor.md new file mode 100644 index 0000000000..a6d6da8ed1 --- /dev/null +++ b/.claude/commands/doc-janitor.md @@ -0,0 +1,5 @@ +--- +description: Clean up stale plan files, session summaries, backups, and other documentation artifacts before merging +--- + +Launch the **doc-janitor** agent to sweep this branch for stale documentation artifacts. diff --git a/.claude/commands/doc-sync-enforcer.md b/.claude/commands/doc-sync-enforcer.md new file mode 100644 index 0000000000..7874642d9b --- /dev/null +++ b/.claude/commands/doc-sync-enforcer.md @@ -0,0 +1,5 @@ +--- +description: Update dialtone-docs content files to reflect source code changes made this session +--- + +Run the **doc-sync-enforcer** skill to update documentation in `packages/dialtone-docs/src/content/`. diff --git a/.claude/commands/pr-fill.md b/.claude/commands/pr-fill.md index 6f339aec75..e19c26edb5 100644 --- a/.claude/commands/pr-fill.md +++ b/.claude/commands/pr-fill.md @@ -60,13 +60,16 @@ When this command is used, Claude should: 8. **Detect cross-package impact:** When changes span multiple packages, add a "Cross-Package Impact" section noting affected packages and downstream effects per the dependency graph (`tokens --> CSS --> Vue --> docs / MCP / language-server`). -9. **Flag documentation artifacts:** - Check all 6 documentation artifacts are updated (see CLAUDE.md Documentation Pipeline). Add a "Documentation Artifacts" section listing which are relevant to the changes. Mark artifacts already updated with a checkmark and those needing attention with a flag. +9. **Suggest doc-janitor cleanup:** + Before generating the PR body, check for stale work artifacts (PLAN-*.md, SESSION_*.md, *.backup, etc.) in the working tree. If any are found, suggest running `/doc-janitor` to clean them up before opening the PR. Do not block โ€” just inform the user and continue. -10. **Strip Co-Authored-By lines:** +10. **Flag documentation artifacts:** + Check all 6 documentation artifacts are updated (see CLAUDE.md Documentation Pipeline). Add a "Documentation Artifacts" section listing which are relevant to the changes. Mark artifacts already updated with a checkmark and those needing attention with a flag. + +11. **Strip Co-Authored-By lines:** Before writing the final PR body, remove any `Co-Authored-By:` lines from the generated content. These must never appear in PR descriptions. -11. **Update the PR:** +12. **Update the PR:** - Use `gh pr edit --body ""` to update - Confirm update: `gh pr view --json title,body,url` - Display success message with PR URL diff --git a/.claude/hooks/post-tool-use-tracker.sh b/.claude/hooks/post-tool-use-tracker.sh new file mode 100755 index 0000000000..b834e6ad1f --- /dev/null +++ b/.claude/hooks/post-tool-use-tracker.sh @@ -0,0 +1,182 @@ +#!/bin/bash +set -e + +# Post-tool-use hook that tracks edited files and their repos +# This runs after Edit, MultiEdit, or Write tools complete successfully + + +# Read tool information from stdin +tool_info=$(cat) + + +# Extract relevant data +tool_name=$(echo "$tool_info" | jq -r '.tool_name // empty') +file_path=$(echo "$tool_info" | jq -r '.tool_input.file_path // empty') +session_id=$(echo "$tool_info" | jq -r '.session_id // empty') + + +# Skip if not an edit tool or no file path +if [[ ! "$tool_name" =~ ^(Edit|MultiEdit|Write)$ ]] || [[ -z "$file_path" ]]; then + exit 0 # Exit 0 for skip conditions +fi + + +# Create cache directory in project +cache_dir="$CLAUDE_PROJECT_DIR/.claude/tsc-cache/${session_id:-default}" +mkdir -p "$cache_dir" + +# Function to detect repo from file path +detect_repo() { + local file="$1" + local project_root="$CLAUDE_PROJECT_DIR" + + # Remove project root from path + local relative_path="${file#$project_root/}" + + # Extract first directory component + local repo=$(echo "$relative_path" | cut -d'/' -f1) + + # Dialtone monorepo structure + case "$repo" in + # Monorepo packages (packages/dialtone-vue, packages/dialtone-css, etc.) + packages) + local package=$(echo "$relative_path" | cut -d'/' -f2) + if [[ -n "$package" ]]; then + echo "packages/$package" + else + echo "$repo" + fi + ;; + # Apps (apps/dialtone-documentation) + apps) + local app=$(echo "$relative_path" | cut -d'/' -f2) + if [[ -n "$app" ]]; then + echo "apps/$app" + else + echo "$repo" + fi + ;; + # Common shared files + common) + echo "common" + ;; + # Scripts + scripts) + echo "scripts" + ;; + # CI/CD + .github) + echo ".github" + ;; + # Claude config + .claude) + echo ".claude" + ;; + *) + # Check if it's a source file in root + if [[ ! "$relative_path" =~ / ]]; then + echo "root" + else + echo "unknown" + fi + ;; + esac +} + +# Function to get build command for repo +get_build_command() { + local repo="$1" + local project_root="$CLAUDE_PROJECT_DIR" + + # Dialtone uses NX โ€” build commands go through pnpm nx + case "$repo" in + packages/dialtone-tokens) + echo "pnpm nx run dialtone-tokens:build" + ;; + packages/dialtone-css) + echo "pnpm nx run dialtone-css:build" + ;; + packages/dialtone-vue) + echo "pnpm nx run dialtone-vue:build" + ;; + packages/dialtone-icons) + echo "pnpm nx run dialtone-icons:build" + ;; + packages/dialtone-mcp-server) + echo "pnpm nx run dialtone-mcp-server:build" + ;; + packages/language-server) + echo "pnpm nx run language-server:build" + ;; + apps/dialtone-documentation) + echo "pnpm nx run dialtone-documentation:build" + ;; + *) + echo "" + ;; + esac +} + +# Function to get test command for repo +get_tsc_command() { + local repo="$1" + + # Dialtone uses Vitest/Mocha/node --test depending on package + case "$repo" in + packages/dialtone-vue) + echo "pnpm nx run dialtone-vue:test" + ;; + packages/eslint-plugin-dialtone) + echo "pnpm nx run eslint-plugin-dialtone:test" + ;; + packages/stylelint-plugin-dialtone) + echo "pnpm nx run stylelint-plugin-dialtone:test" + ;; + packages/postcss-responsive-variations) + echo "pnpm nx run postcss-responsive-variations:test" + ;; + packages/dialtone-docs) + echo "pnpm nx run dialtone-docs:test" + ;; + *) + echo "" + ;; + esac +} + +# Detect repo +repo=$(detect_repo "$file_path") + +# Skip if unknown repo +if [[ "$repo" == "unknown" ]] || [[ -z "$repo" ]]; then + exit 0 # Exit 0 for skip conditions +fi + +# Log edited file +printf '%s\t%s\t%s\n' "$(date +%s)" "$file_path" "$repo" >> "$cache_dir/edited-files.log" + +# Update affected repos list +if ! grep -q "^$repo$" "$cache_dir/affected-repos.txt" 2>/dev/null; then + echo "$repo" >> "$cache_dir/affected-repos.txt" +fi + +# Store build commands +build_cmd=$(get_build_command "$repo") +tsc_cmd=$(get_tsc_command "$repo") + +if [[ -n "$build_cmd" ]]; then + echo "$repo:build:$build_cmd" >> "$cache_dir/commands.txt.tmp" +fi + +if [[ -n "$tsc_cmd" ]]; then + echo "$repo:tsc:$tsc_cmd" >> "$cache_dir/commands.txt.tmp" +fi + +# Remove duplicates from commands +if [[ -f "$cache_dir/commands.txt.tmp" ]]; then + sort -u "$cache_dir/commands.txt.tmp" > "$cache_dir/commands.txt" + rm -f "$cache_dir/commands.txt.tmp" +fi + +# Exit cleanly +exit 0 diff --git a/.claude/hooks/pre-push-pr-guard.sh b/.claude/hooks/pre-push-pr-guard.sh new file mode 100755 index 0000000000..99dbb86650 --- /dev/null +++ b/.claude/hooks/pre-push-pr-guard.sh @@ -0,0 +1,195 @@ +#!/bin/bash +set -e + +# PreToolUse hook โ€” checks documentation before push and PR creation +# Uses the edit tracker to see if code files were touched without doc updates +# Maps package source paths to dialtone-docs content files +# Push: suggests updating docs if missing +# PR creation: same + asks about /doc-janitor + +tool_info=$(cat) + +tool_name=$(echo "$tool_info" | jq -r '.tool_name // empty' 2>/dev/null) +command=$(echo "$tool_info" | jq -r '.tool_input.command // empty' 2>/dev/null) +session_id=$(echo "$tool_info" | jq -r '.session_id // empty' 2>/dev/null) + +# Only check Bash tool +if [[ "$tool_name" != "Bash" ]] || [[ -z "$command" ]]; then + exit 0 +fi + +# Detect push and PR creation +is_push=false +is_pr=false + +# Split on && and || to check each command in the chain independently +# This prevents "git stash push && git push" from being classified as stash-only +IFS=$'\n' +for cmd_part in $(echo "$command" | sed 's/&&/\n/g; s/||/\n/g; s/|/\n/g'); do + # Skip stash push commands + if echo "$cmd_part" | grep -qE 'git\s+stash\s+push'; then + continue + fi + if echo "$cmd_part" | grep -qE 'git\s+.*push(\s|$)'; then + is_push=true + fi +done +unset IFS + +if echo "$command" | grep -qE '(^|\s|&&|\|)gh\s+.*pr\s+create(\s|$)'; then + is_pr=true +fi + +if [[ "$is_push" == "false" ]] && [[ "$is_pr" == "false" ]]; then + exit 0 +fi + +# Check if checks were already done this session +project_dir="${CLAUDE_PROJECT_DIR:-$(pwd)}" +marker_dir="$project_dir/.claude/tsc-cache/${session_id:-default}" + +if [[ "$is_pr" == "true" ]]; then + marker_file="$marker_dir/pr-create-done" +else + marker_file="$marker_dir/push-done" +fi + +# If marker exists, check if new edits happened after it was created +if [[ -f "$marker_file" ]]; then + log_file_check="$marker_dir/edited-files.log" + if [[ -f "$log_file_check" ]] && [[ "$log_file_check" -nt "$marker_file" ]]; then + # New edits since last check โ€” remove marker and re-run + rm -f "$marker_file" + else + exit 0 + fi +fi + +# Read edit tracker +log_file="$marker_dir/edited-files.log" + +if [[ ! -f "$log_file" ]]; then + # No edits tracked โ€” allow + exit 0 +fi + +edited_files=$(cut -d$'\t' -f2 < "$log_file") + +# Dialtone docs content path +docs_content="packages/dialtone-docs/src/content" + +# Map: source package paths to their corresponding dialtone-docs content files +# Check if code files were edited in these areas +has_token_edits=$(echo "$edited_files" | grep -c 'packages/dialtone-tokens/' || true) +has_css_edits=$(echo "$edited_files" | grep -c 'packages/dialtone-css/' || true) +has_vue_edits=$(echo "$edited_files" | grep -c 'packages/dialtone-vue/' || true) +has_icon_edits=$(echo "$edited_files" | grep -c 'packages/dialtone-icons/' || true) +has_ci_edits=$(echo "$edited_files" | grep -c '\.github/workflows/' || true) +has_mcp_edits=$(echo "$edited_files" | grep -c 'packages/dialtone-mcp-server/' || true) +has_eslint_edits=$(echo "$edited_files" | grep -c 'packages/eslint-plugin-dialtone/' || true) +has_stylelint_edits=$(echo "$edited_files" | grep -c 'packages/stylelint-plugin-dialtone/' || true) + +# Check if any corresponding doc files were also edited +has_doc_edits=$(echo "$edited_files" | grep -c "$docs_content/" || true) + +# Build list of packages touched without doc updates +missing_docs="" + +if (( has_token_edits > 0 )); then + # Check for token-related docs + token_docs=$(echo "$edited_files" | grep -cE "(development-design-tokens|architecture-design-token-pipeline)" || true) + if (( token_docs == 0 )); then + missing_docs="${missing_docs} - dialtone-tokens โ†’ development-design-tokens.md, architecture-design-token-pipeline.md\n" + fi +fi + +if (( has_css_edits > 0 )); then + css_docs=$(echo "$edited_files" | grep -c "development-css-utilities" || true) + if (( css_docs == 0 )); then + missing_docs="${missing_docs} - dialtone-css โ†’ development-css-utilities.md\n" + fi +fi + +if (( has_vue_edits > 0 )); then + vue_docs=$(echo "$edited_files" | grep -cE "(development-component-workflow|reference-component-api-patterns|reference-accessibility-checklist|development-testing)" || true) + if (( vue_docs == 0 )); then + missing_docs="${missing_docs} - dialtone-vue โ†’ development-component-workflow.md, reference-component-api-patterns.md\n" + fi +fi + +if (( has_icon_edits > 0 )); then + icon_docs=$(echo "$edited_files" | grep -c "development-icons" || true) + if (( icon_docs == 0 )); then + missing_docs="${missing_docs} - dialtone-icons โ†’ development-icons.md\n" + fi +fi + +if (( has_ci_edits > 0 )); then + ci_docs=$(echo "$edited_files" | grep -c "workflow-ci-pipeline" || true) + if (( ci_docs == 0 )); then + missing_docs="${missing_docs} - .github/workflows โ†’ workflow-ci-pipeline.md\n" + fi +fi + +docs_missing=false +if [[ -n "$missing_docs" ]]; then + docs_missing=true +fi + +# If no issues and not a PR, allow through +if [[ "$docs_missing" == "false" ]] && [[ "$is_pr" == "false" ]]; then + mkdir -p "$marker_dir" + touch "$marker_file" + exit 0 +fi + +mkdir -p "$marker_dir" + +CYAN=$'\033[1;36m'; RESET=$'\033[0m' + +if [[ "$is_pr" == "true" ]]; then + echo -e "${CYAN}๐Ÿ“‹ PR creation check triggered${RESET}" >&2 + + if [[ "$docs_missing" == "true" ]]; then + # Write missing docs mapping to cache so the skill can read it + echo -e "$missing_docs" > "$marker_dir/missing-docs.txt" + + jq -n \ + --arg missing "$missing_docs" \ + --arg log_file "$log_file" \ + --arg marker_dir "$marker_dir" \ + --arg marker_file "$marker_file" \ + '{ + "hookSpecificOutput": { "permissionDecision": "deny" }, + "systemMessage": ("Code files were edited this session but the corresponding dialtone-docs content was not updated.\n\nMissing doc updates:\n" + $missing + "\nRun the doc-sync-enforcer skill now. For each mapping above:\n1. Read the source files that were changed (listed in " + $log_file + ")\n2. Read the corresponding doc file in packages/dialtone-docs/src/content/\n3. Update the doc file to reflect the source code changes โ€” update relevant sections, examples, and the last_updated frontmatter field\n4. If the change is trivial (bug fix, formatting only), note that and skip the doc update\n\nAfter the enforcer finishes, ask the user: \"Want me to run /doc-janitor to clean up stale artifacts before the PR?\"\nIf yes, launch the doc-janitor agent.\nIf no, skip.\n\nThen run:\n mkdir -p " + $marker_dir + " && touch " + $marker_file + "\n\nThen retry the PR creation.") + }' + else + # Docs are up to date โ€” allow PR creation (pr-fill already suggests /doc-janitor) + touch "$marker_file" + exit 0 + fi +else + # Push: only block if docs are missing + if [[ "$docs_missing" == "true" ]]; then + echo -e "${CYAN}๐Ÿ“‹ Pre-push doc check triggered${RESET}" >&2 + + # Write missing docs mapping to cache so the skill can read it + echo -e "$missing_docs" > "$marker_dir/missing-docs.txt" + + jq -n \ + --arg missing "$missing_docs" \ + --arg log_file "$log_file" \ + --arg marker_dir "$marker_dir" \ + --arg marker_file "$marker_file" \ + '{ + "hookSpecificOutput": { "permissionDecision": "deny" }, + "systemMessage": ("Code files were edited this session but the corresponding dialtone-docs content was not updated.\n\nMissing doc updates:\n" + $missing + "\nRun the doc-sync-enforcer skill now. For each mapping above:\n1. Read the source files that were changed (listed in " + $log_file + ")\n2. Read the corresponding doc file in packages/dialtone-docs/src/content/\n3. Update the doc file to reflect the source code changes โ€” update relevant sections, examples, and the last_updated frontmatter field\n4. If the change is trivial (bug fix, formatting only), note that and skip the doc update\n\nAfter the enforcer finishes, run:\n mkdir -p " + $marker_dir + " && touch " + $marker_file + "\n\nThen retry the push.") + }' + else + # Docs were updated, allow push + touch "$marker_file" + exit 0 + fi +fi + +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json index 5cbefed8e0..ae021d9eed 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,4 +1,10 @@ { + "permissions": { + "allow": [ + "Skill(doc-sync-enforcer)", + "Agent(doc-janitor)" + ] + }, "hooks": { "PreToolUse": [ { @@ -7,6 +13,10 @@ { "type": "command", "command": ".claude/hooks/commit-format.sh" + }, + { + "type": "command", + "command": ".claude/hooks/pre-push-pr-guard.sh" } ] }, @@ -28,6 +38,10 @@ "type": "command", "command": ".claude/hooks/shaping-ripple.sh", "timeout": 1000 + }, + { + "type": "command", + "command": ".claude/hooks/post-tool-use-tracker.sh" } ] } diff --git a/.claude/skills/doc-sync-enforcer.md b/.claude/skills/doc-sync-enforcer.md new file mode 100644 index 0000000000..84b437cc36 --- /dev/null +++ b/.claude/skills/doc-sync-enforcer.md @@ -0,0 +1,115 @@ +--- +name: doc-sync-enforcer +description: Updates dialtone-docs content files when source code changes. Triggered by the pre-push-pr-guard hook or manually via /doc-sync-enforcer. Reads the edit tracker cache to know what changed, maps source packages to doc files, and updates them. +--- + +# Documentation Sync Enforcer + +Updates `packages/dialtone-docs/src/content/` files to reflect source code changes across the Dialtone monorepo. + +## When This Runs + +- **Automatically** โ€” triggered by `pre-push-pr-guard.sh` when code files were edited but corresponding docs were not +- **Manually** โ€” via `/doc-sync-enforcer` command + +## Source โ†’ Doc Mapping + +| Source path | Doc file(s) in `packages/dialtone-docs/src/content/` | +|---|---| +| `packages/dialtone-tokens/` | `development/development-design-tokens.md`, `architecture/architecture-design-token-pipeline.md` | +| `packages/dialtone-css/` | `development/development-css-utilities.md` | +| `packages/dialtone-vue/` | `development/development-component-workflow.md`, `reference/reference-component-api-patterns.md`, `reference/reference-accessibility-checklist.md`, `development/development-testing.md` | +| `packages/dialtone-icons/` | `development/development-icons.md` | +| `.github/workflows/` | `workflows/workflow-ci-pipeline.md` | +| Release config changes | `workflows/workflow-release-process.md` | +| Branch strategy changes | `workflows/workflow-branch-strategy.md` | +| Commit convention changes | `workflows/workflow-conventional-commits.md` | + +## Workflow + +### Step 1 โ€” Read the edit tracker + +Read the session cache to find what was changed: + +``` +.claude/tsc-cache//edited-files.log โ€” every file edited this session +.claude/tsc-cache//missing-docs.txt โ€” mappings flagged by the hook +.claude/tsc-cache//affected-repos.txt โ€” which packages were touched +``` + +The `edited-files.log` format is `timestamp:filepath:repo` โ€” extract the filepaths. + +### Step 2 โ€” For each mapping, read source and doc + +For each entry in `missing-docs.txt`: + +1. **Read the changed source files** from `edited-files.log` that belong to the flagged package +2. **Read the existing doc file** from `packages/dialtone-docs/src/content/` +3. **Understand what changed** โ€” new files added, APIs modified, configs changed, patterns introduced + +### Step 3 โ€” Determine if update is needed + +Not every code change needs a doc update: + +- **Bug fix / formatting** โ€” skip, note it +- **New feature / API / config** โ€” update the relevant section +- **Removed feature** โ€” remove or update the section +- **New file / pattern** โ€” add to the doc if significant + +### Step 4 โ€” Update the doc file + +When updating a doc file: + +1. **Preserve the existing structure** โ€” the doc has `##` sections that are independently searchable +2. **Update only the sections affected** by the source code changes +3. **Update the `last_updated` frontmatter field** to today's date +4. **Never invent information** โ€” only document what exists in the actual code +5. **Keep the same writing style** โ€” concise, technical, grep-friendly + +### Frontmatter format + +Every doc file has YAML frontmatter that must be preserved: + +```yaml +--- +type: guide|reference|workflow|architecture|standard +category: development|architecture|workflows|reference|standards +keywords: + - keyword1 + - keyword2 + - keyword3 +ai_summary: "One-line summary, max 150 characters" +last_updated: "2026-03-13" +related_packages: + - "@dialpad/package-name" +--- +``` + +Required fields: `type`, `category`, `keywords` (3+), `ai_summary` (โ‰ค150 chars), `last_updated`. + +### Step 5 โ€” Report what was done + +After updating, print a summary: + +``` +๐Ÿ“š Doc Sync Complete +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +โœ… Updated: + development-design-tokens.md โ€” added new spacing token section + architecture-design-token-pipeline.md โ€” updated build step 3 + +โญ๏ธ Skipped (trivial changes): + development-css-utilities.md โ€” bug fix only, no doc impact + +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” +``` + +## Rules + +- **Never invent functionality** โ€” only document what exists in the actual code +- **Read the source files first** โ€” don't guess what changed, read the actual diff or file +- **Preserve human-authored content** โ€” when updating, never remove existing sections unless the feature was removed +- **Keep docs under 350 lines** โ€” if longer, the doc is trying to cover too much +- **Use actual package names and technical terms** โ€” content must be grep-friendly (no placeholders) +- **File naming convention** โ€” kebab-case with category prefix: `development-component-workflow.md` diff --git a/.github/workflows/cleanup-plan-docs.yml b/.github/workflows/cleanup-plan-docs.yml new file mode 100644 index 0000000000..8a414ff520 --- /dev/null +++ b/.github/workflows/cleanup-plan-docs.yml @@ -0,0 +1,39 @@ +name: Cleanup Plan Docs + +on: + pull_request: + types: [closed] + branches: [staging] + +jobs: + cleanup: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + permissions: + contents: write + concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + steps: + - name: Checkout staging + uses: actions/checkout@v4 + with: + ref: staging + token: ${{ github.token }} + + - name: Configure Git User + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Remove planning docs + run: | + git rm -f PLAN-*.md --ignore-unmatch + git rm -rf docs/plans/ --ignore-unmatch + + - name: Commit and push if changed + run: | + if ! git diff --cached --quiet --exit-code; then + git commit -m "chore: remove planning docs post-merge [skip ci]" + git push origin staging + fi diff --git a/.gitignore b/.gitignore index b7d7aa8091..e51b0dfa03 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,26 @@ packages/postcss-responsive-variations/coverage *.vsix packages/language-server/sample/*.vue packages/language-server/sample/*.css + +# Claude session cache +.claude/tsc-cache/ + +# Work artifacts โ€” session files generated by Claude during development +# These are useful locally but should never be committed or pushed. +# Patterns match at any directory depth. +PLAN-*.md +PLAN.md +*-plan.md +SESSION_*.md +*_SESSION.md +CLEANUP_*.md +*_CLEANUP.md +ITERATION_*.md +VERIFICATION_*.md +VALIDATION_*.md +TODO_*.md +SKILLS_TODO*.md +*.backup +*.bak +*.old +*.deprecated diff --git a/CLAUDE.md b/CLAUDE.md index 96e12e8d97..48fa659df4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -111,6 +111,36 @@ When creating or updating a component, ALL must stay in sync: - Workflow: feature branch โ†’ PR to `staging` โ†’ semantic-release โ†’ `production` fast-forward - Config: `release-ci.config.cjs` per package +## Doc Sync Tooling + +Hooks and tools that keep `packages/dialtone-docs/src/content/` in sync with source code changes. Requires the `dialtone-docs` package (PR #1051) to be merged. + +### How it works + +1. **Edit tracker** (`.claude/hooks/post-tool-use-tracker.sh`) โ€” PostToolUse hook that silently logs every file edit to `.claude/tsc-cache//edited-files.log` with affected packages and NX commands. +2. **Pre-push guard** (`.claude/hooks/pre-push-pr-guard.sh`) โ€” PreToolUse hook on Bash that fires on `git push` / `gh pr create`. Maps source packages to doc content files. Denies push if docs are missing and triggers the enforcer. +3. **Doc sync enforcer** โ€” Skill (`/doc-sync-enforcer`) that reads the edit log, maps changes to `dialtone-docs` content files, and updates them. +4. **Doc janitor** โ€” Agent (`/doc-janitor`) that sweeps stale artifacts (plan files, session files, backups) before merging. + +### Package โ†’ doc mapping + +| Source package | Doc content file | +| --- | --- | +| `packages/dialtone-tokens/` | `development-design-tokens.md`, `architecture-design-token-pipeline.md` | +| `packages/dialtone-css/` | `development-css-utilities.md` | +| `packages/dialtone-vue/` | `development-component-workflow.md`, `reference-component-api-patterns.md` | +| `packages/dialtone-icons/` | `development-icons.md` | +| `.github/workflows/` | `workflow-ci-pipeline.md` | + +### Session cache + +All tracking data lives in `.claude/tsc-cache//` (gitignored): +- `edited-files.log` โ€” tab-separated: `timestamp\tfilepath\trepo` +- `affected-repos.txt` โ€” one package per line +- `commands.txt` โ€” NX build/test commands for affected packages +- `missing-docs.txt` โ€” written by the guard when docs are missing +- `push-done` / `pr-create-done` โ€” marker files to avoid re-checking + ## Key Files Reference | File | Purpose |