diff --git a/commands/gsd/note.md b/commands/gsd/note.md new file mode 100644 index 00000000..da1affe5 --- /dev/null +++ b/commands/gsd/note.md @@ -0,0 +1,34 @@ +--- +name: gsd:note +description: Zero-friction idea capture. Append, list, or promote notes to todos. +argument-hint: " | list | promote [--global]" +allowed-tools: + - Read + - Write + - Glob + - Grep +--- + +Zero-friction idea capture — one Write call, one confirmation line. + +Three subcommands: +- **append** (default): Save a timestamped note file. No questions, no formatting. +- **list**: Show all notes from project and global scopes. +- **promote**: Convert a note into a structured todo. + +Runs inline — no Task, no AskUserQuestion, no Bash. + + + +@~/.claude/get-shit-done/workflows/note.md +@~/.claude/get-shit-done/references/ui-brand.md + + + +$ARGUMENTS + + + +Execute the note workflow from @~/.claude/get-shit-done/workflows/note.md end-to-end. +Capture the note, list notes, or promote to todo — depending on arguments. + diff --git a/get-shit-done/workflows/help.md b/get-shit-done/workflows/help.md index acbf7b1b..058d4a81 100644 --- a/get-shit-done/workflows/help.md +++ b/get-shit-done/workflows/help.md @@ -255,6 +255,21 @@ Systematic debugging with persistent state across context resets. Usage: `/gsd:debug "login button doesn't work"` Usage: `/gsd:debug` (resume active session) +### Quick Notes + +**`/gsd:note `** +Zero-friction idea capture — one command, instant save, no questions. + +- Saves timestamped note to `.planning/notes/` (or `~/.claude/notes/` globally) +- Three subcommands: append (default), list, promote +- Promote converts a note into a structured todo +- Works without a project (falls back to global scope) + +Usage: `/gsd:note refactor the hook system` +Usage: `/gsd:note list` +Usage: `/gsd:note promote 3` +Usage: `/gsd:note --global cross-project idea` + ### Todo Management **`/gsd:add-todo [description]`** diff --git a/get-shit-done/workflows/note.md b/get-shit-done/workflows/note.md new file mode 100644 index 00000000..2daf3be4 --- /dev/null +++ b/get-shit-done/workflows/note.md @@ -0,0 +1,156 @@ + +Zero-friction idea capture. One Write call, one confirmation line. No questions, no prompts. +Runs inline — no Task, no AskUserQuestion, no Bash. + + + +Read all files referenced by the invoking prompt's execution_context before starting. + + + + + +**Note storage format.** + +Notes are stored as individual markdown files: + +- **Project scope**: `.planning/notes/{YYYY-MM-DD}-{slug}.md` — used when `.planning/` exists in cwd +- **Global scope**: `~/.claude/notes/{YYYY-MM-DD}-{slug}.md` — fallback when no `.planning/`, or when `--global` flag is present + +Each note file: + +```markdown +--- +date: "YYYY-MM-DD HH:mm" +promoted: false +--- + +{note text verbatim} +``` + +**`--global` flag**: Strip `--global` from anywhere in `$ARGUMENTS` before parsing. When present, force global scope regardless of whether `.planning/` exists. + +**Important**: Do NOT create `.planning/` if it doesn't exist. Fall back to global scope silently. + + + +**Parse subcommand from $ARGUMENTS (after stripping --global).** + +| Condition | Subcommand | +|-----------|------------| +| Arguments are exactly `list` (case-insensitive) | **list** | +| Arguments are exactly `promote ` where N is a number | **promote** | +| Arguments are empty (no text at all) | **list** | +| Anything else | **append** (the text IS the note) | + +**Critical**: `list` is only a subcommand when it's the ENTIRE argument. `/gsd:note list of groceries` saves a note with text "list of groceries". Same for `promote` — only a subcommand when followed by exactly one number. + + + +**Subcommand: append — create a timestamped note file.** + +1. Determine scope (project or global) per storage format above +2. Ensure the notes directory exists (`.planning/notes/` or `~/.claude/notes/`) +3. Generate slug: first ~4 meaningful words of the note text, lowercase, hyphen-separated (strip articles/prepositions from the start) +4. Generate filename: `{YYYY-MM-DD}-{slug}.md` + - If a file with that name already exists, append `-2`, `-3`, etc. +5. Write the file with frontmatter and note text (see storage format) +6. Confirm with exactly one line: `Noted ({scope}): {note text}` + - Where `{scope}` is "project" or "global" + +**Constraints:** +- **Never modify the note text** — capture verbatim, including typos +- **Never ask questions** — just write and confirm +- **Timestamp format**: Use local time, `YYYY-MM-DD HH:mm` (24-hour, no seconds) + + + +**Subcommand: list — show notes from both scopes.** + +1. Glob `.planning/notes/*.md` (if directory exists) — project notes +2. Glob `~/.claude/notes/*.md` (if directory exists) — global notes +3. For each file, read frontmatter to get `date` and `promoted` status +4. Exclude files where `promoted: true` from active counts (but still show them, dimmed) +5. Sort by date, number all active entries sequentially starting at 1 +6. If total active entries > 20, show only the last 10 with a note about how many were omitted + +**Display format:** + +``` +Notes: + +Project (.planning/notes/): + 1. [2026-02-08 14:32] refactor the hook system to support async validators + 2. [promoted] [2026-02-08 14:40] add rate limiting to the API endpoints + 3. [2026-02-08 15:10] consider adding a --dry-run flag to build + +Global (~/.claude/notes/): + 4. [2026-02-08 10:00] cross-project idea about shared config + +{count} active note(s). Use `/gsd:note promote ` to convert to a todo. +``` + +If a scope has no directory or no entries, show: `(no notes)` + + + +**Subcommand: promote — convert a note into a todo.** + +1. Run the **list** logic to build the numbered index (both scopes) +2. Find entry N from the numbered list +3. If N is invalid or refers to an already-promoted note, tell the user and stop +4. **Requires `.planning/` directory** — if it doesn't exist, warn: "Todos require a GSD project. Run `/gsd:new-project` to initialize one." +5. Ensure `.planning/todos/pending/` directory exists +6. Generate todo ID: `{NNN}-{slug}` where NNN is the next sequential number (scan both `.planning/todos/pending/` and `.planning/todos/done/` for the highest existing number, increment by 1, zero-pad to 3 digits) and slug is the first ~4 meaningful words of the note text +7. Extract the note text from the source file (body after frontmatter) +8. Create `.planning/todos/pending/{id}.md`: + +```yaml +--- +title: "{note text}" +status: pending +priority: P2 +source: "promoted from /gsd:note" +created: {YYYY-MM-DD} +theme: general +--- + +## Goal + +{note text} + +## Context + +Promoted from quick note captured on {original date}. + +## Acceptance Criteria + +- [ ] {primary criterion derived from note text} +``` + +9. Mark the source note file as promoted: update its frontmatter to `promoted: true` +10. Confirm: `Promoted note {N} to todo {id}: {note text}` + + + + + +1. **"list" as note text**: `/gsd:note list of things` saves note "list of things" (subcommand only when `list` is the entire arg) +2. **No `.planning/`**: Falls back to global `~/.claude/notes/` — works in any directory +3. **Promote without project**: Warns that todos require `.planning/`, suggests `/gsd:new-project` +4. **Large files**: `list` shows last 10 when >20 active entries +5. **Duplicate slugs**: Append `-2`, `-3` etc. to filename if slug already used on same date +6. **`--global` position**: Stripped from anywhere — `--global my idea` and `my idea --global` both save "my idea" globally +7. **Promote already-promoted**: Tell user "Note {N} is already promoted" and stop +8. **Empty note text after stripping flags**: Treat as `list` subcommand + + + +- [ ] Append: Note file written with correct frontmatter and verbatim text +- [ ] Append: No questions asked — instant capture +- [ ] List: Both scopes shown with sequential numbering +- [ ] List: Promoted notes shown but dimmed +- [ ] Promote: Todo created with correct format +- [ ] Promote: Source note marked as promoted +- [ ] Global fallback: Works when no `.planning/` exists + diff --git a/tests/copilot-install.test.cjs b/tests/copilot-install.test.cjs index 2c3d9cae..9081740b 100644 --- a/tests/copilot-install.test.cjs +++ b/tests/copilot-install.test.cjs @@ -625,7 +625,7 @@ describe('copyCommandsAsCopilotSkills', () => { // Count gsd-* directories — should be 31 const dirs = fs.readdirSync(tempDir, { withFileTypes: true }) .filter(e => e.isDirectory() && e.name.startsWith('gsd-')); - assert.strictEqual(dirs.length, 37, `expected 37 skill folders, got ${dirs.length}`); + assert.strictEqual(dirs.length, 38, `expected 38 skill folders, got ${dirs.length}`); } finally { fs.rmSync(tempDir, { recursive: true }); } @@ -1119,7 +1119,7 @@ const { execFileSync } = require('child_process'); const crypto = require('crypto'); const INSTALL_PATH = path.join(__dirname, '..', 'bin', 'install.js'); -const EXPECTED_SKILLS = 37; +const EXPECTED_SKILLS = 38; const EXPECTED_AGENTS = 15; function runCopilotInstall(cwd) {