From 18a589cb4ad8f42e5190318fd1b912abf017ea0d Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 19 Oct 2026 11:47:20 +0200 Subject: docs: plan the new project tool, agent choice in the spec Co-Authored-By: Claude Opus 5.5 --- docs/superpowers/plans/2026-10-19-project-tool.md | 252 +++++++++++++++++++++ .../specs/2026-10-19-project-tool-design.md | 37 +-- 2 files changed, 274 insertions(+), 15 deletions(-) create mode 100644 docs/superpowers/plans/2026-10-19-project-tool.md (limited to 'docs/superpowers') diff --git a/docs/superpowers/plans/2026-10-19-project-tool.md b/docs/superpowers/plans/2026-10-19-project-tool.md new file mode 100644 index 0000000..5aad3c3 --- /dev/null +++ b/docs/superpowers/plans/2026-10-19-project-tool.md @@ -0,0 +1,252 @@ +# New Project Tool Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Let the assistant start a new project: a folder in `~/Programming/GIT` with AGENTS.md, CLAUDE.md, README.md, LICENSE, a signed first commit, and a kitty tab running Claude Code or opencode in it. + +**Architecture:** One tool file (`project.toml`), one script (`project.sh`) and the license texts it copies (`examples/tools/licenses/`), in the existing tool format. No change to `assistant.py`. kitty gets remote control in the `kitty-conf` repo. Spec: `docs/superpowers/specs/2026-10-19-project-tool-design.md`. + +**Tech Stack:** bash, git (signed commits, global hooks in `~/.config/git/hooks`), kitty remote control, hyprctl. + +--- + +### Task 1: License texts + +**Files:** +- Create: `examples/tools/licenses/gpl-2.0.txt`, `gpl-3.0.txt`, `mit.txt` + +- [ ] **Step 1: Fetch them** from the official sources, never from memory. + +```bash +d=examples/tools/licenses; mkdir -p $d +curl -fsSL --max-time 20 https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt -o $d/gpl-2.0.txt +curl -fsSL --max-time 20 https://www.gnu.org/licenses/gpl-3.0.txt -o $d/gpl-3.0.txt +curl -fsSL --max-time 20 https://raw.githubusercontent.com/spdx/license-list-data/main/text/MIT.txt -o $d/mit.txt +``` + +If gnu.org times out, copy `gpl-2.0.txt` from `LICENSE` in this repo +(`cmp` it against `../wallhaven-dl/LICENSE`). + +- [ ] **Step 2: Check** `grep -c ' ' examples/tools/licenses/mit.txt` + prints `1` (project.sh fills those two placeholders), and + `head -3` of both GPL files shows the license title. + +### Task 2: project.sh with its check + +**Files:** +- Create: `examples/tools/test-project.sh` +- Create: `examples/tools/project.sh` + +- [ ] **Step 1: Write the check** (GPLv2 header as in `test-mode.sh`). It + creates projects in a throwaway `PROJECTS_DIR` with `KITTY=true`, so no + tab opens. Commits are really signed: with a cold PIN cache the dialog + appears once. + +```bash +set -u +here=$(dirname "$0") +PROJECTS_DIR=$(mktemp -d) +KITTY=true +export PROJECTS_DIR KITTY +trap 'rm -rf "$PROJECTS_DIR"' EXIT +fail=0 +check() { [ "$2" = "$3" ] || { echo "FAIL $1: got '$2', want '$3'"; fail=1; }; } +p=$PROJECTS_DIR/demo +year=$(date +%Y) + +out=$("$here/project.sh" demo "" "A demo project." "") +case "$out" in + "created $p (GPLv2 only), committed "*", opened claude in a kitty "*) ;; + *) echo "FAIL output: $out"; fail=1 ;; +esac +check "files" "$(cd "$p" && ls)" "$(printf '%s\n' AGENTS.md CLAUDE.md LICENSE README.md)" +check "signed" "$(git -C "$p" log --format=%G?)" "G" +check "branch" "$(git -C "$p" branch --show-current)" "master" +check "clean" "$(git -C "$p" status --porcelain)" "" +cmp -s "$p/LICENSE" "$here/licenses/gpl-2.0.txt"; check "license text" "$?" "0" +check "claude" "$(tail -1 "$p/CLAUDE.md")" "@AGENTS.md" +grep -qF '`demo`: A demo project.' "$p/AGENTS.md"; check "agents" "$?" "0" +grep -qF 'GPLv2 only. See [LICENSE]' "$p/README.md"; check "readme license" "$?" "0" +grep -qF '## Development Approach' "$p/README.md"; check "readme ai" "$?" "0" + +out=$("$here/project.sh" m mit "MIT one." opencode) +case "$out" in *", opened opencode in a kitty "*) ;; *) echo "FAIL opencode: $out"; fail=1 ;; esac +check "mit" "$(grep -c "Copyright (c) $year Danilo M. " "$PROJECTS_DIR/m/LICENSE")" "1" + +for bad in Foo ../x "" .x; do + "$here/project.sh" "$bad" "" "x." >/dev/null 2>&1; check "name '$bad'" "$?" "1" +done +"$here/project.sh" demo "" "x." >/dev/null 2>&1; check "exists" "$?" "1" +"$here/project.sh" n bsd "x." >/dev/null 2>&1; check "license" "$?" "1" +"$here/project.sh" n "" "" >/dev/null 2>&1; check "no description" "$?" "1" +"$here/project.sh" n "" "x." vim >/dev/null 2>&1; check "agent" "$?" "1" +check "nothing else made" "$(ls "$PROJECTS_DIR" | tr '\n' ' ')" "demo m " +[ $fail = 0 ] && echo "ok" +exit $fail +``` + +- [ ] **Step 2: Run it, expect failure** (`project.sh` missing). + +Run: `bash examples/tools/test-project.sh` + +- [ ] **Step 3: Write `examples/tools/project.sh`** (GPLv2 header as in + `mode.sh`, usage comment `project.sh [claude|opencode]`, + check: `test-project.sh`). + +```bash +set -uo pipefail + +name=${1:-} +license=${2:-} +desc=${3:-} +agent=${4:-claude} +root=${PROJECTS_DIR:-$HOME/Programming/GIT} +kitty=${KITTY:-kitty} +here=$(dirname "$(readlink -f "$0")") +year=$(date +%Y) +author="Danilo M. " + +[[ $name =~ ^[a-z0-9][a-z0-9._-]*$ ]] || + { echo "name must be lowercase letters, digits, - _ . (not first), not: '$name'" >&2; exit 1; } +case "${license:-gpl2}" in + gpl2) lic="GPLv2 only"; text=gpl-2.0.txt ;; + gpl2+) lic="GPLv2 or later"; text=gpl-2.0.txt ;; + gpl3) lic="GPLv3 only"; text=gpl-3.0.txt ;; + mit) lic="MIT"; text=mit.txt ;; + *) echo "license must be gpl2, gpl2+, gpl3 or mit, not: $license" >&2; exit 1 ;; +esac +[ -n "$desc" ] || { echo "description is empty" >&2; exit 1; } +case "$agent" in + claude|opencode) ;; + *) echo "agent must be claude or opencode, not: $agent" >&2; exit 1 ;; +esac +dir=$root/$name +[ -e "$dir" ] && { echo "$dir already exists" >&2; exit 1; } +mkdir -p "$dir" && cd "$dir" || exit 1 + +sed "s//$year/; s//$author/" "$here/licenses/$text" > LICENSE + +cat > CLAUDE.md <<'EOF' +# CLAUDE.md + +This file is intentionally thin. AGENTS.md is the single source of truth for +project guidance, shared across every agent tool. Edit AGENTS.md instead. + +@AGENTS.md +EOF + +cat > AGENTS.md < README.md <&1); then + c="committed $(git rev-parse --short HEAD)" +else + c="not committed: $(grep -m1 -E 'error|fatal' <<< "$err" || tail -1 <<< "$err")" +fi + +# A tab in the running kitty if one listens (kitty-conf: listen_on +# kitty-{kitty_pid}), else a new window. +# ponytail: focuses some kitty window, not necessarily the one with the new +# tab when several are open; match by kitty's window id if that bites. +prompt="New project: $desc Fill in AGENTS.md and README.md from this, then wait." +[ "$agent" = claude ] && run=(claude "$prompt") || run=(opencode --prompt "$prompt") +how=window +for s in $(ls -t "${XDG_RUNTIME_DIR:-/tmp}"/kitty-* 2>/dev/null); do + if "$kitty" @ --to "unix:$s" launch --type=tab --cwd="$dir" "${run[@]}" >/dev/null 2>&1; then + how=tab + hyprctl dispatch focuswindow class:kitty >/dev/null 2>&1 + break + fi +done +[ $how = tab ] || setsid -f "$kitty" -d "$dir" "${run[@]}" >/dev/null 2>&1 +echo "created $dir ($lic), $c, opened $agent in a kitty $how" +``` + +- [ ] **Step 4: Run the check, expect `ok`.** + +Run: `chmod +x examples/tools/project.sh examples/tools/test-project.sh && examples/tools/test-project.sh` + +### Task 3: project.toml and commit + +**Files:** +- Create: `examples/tools/project.toml` + +- [ ] **Step 1: Write it**, exactly as in the spec's "Tool" section. +- [ ] **Step 2: Self-check still passes:** `python3 assistant.py --test`. +- [ ] **Step 3: Ask, then commit** `examples/tools/licenses/`, `project.sh`, + `test-project.sh`, `project.toml`. + +### Task 4: kitty remote control (kitty-conf repo) + +**Files:** +- Modify: `~/Programming/GIT/kitty-conf/kitty.conf` (after `shell_integration`) + +- [ ] **Step 1: Ask the user**, then add: + +``` +allow_remote_control socket-only +listen_on unix:${XDG_RUNTIME_DIR}/kitty-{kitty_pid} +``` + +- [ ] **Step 2:** User restarts kitty. Check `ls $XDG_RUNTIME_DIR/kitty-*` + shows one socket and `kitty @ --to unix: ls >/dev/null` exits 0. +- [ ] **Step 3: Ask, then commit** in `kitty-conf` (not `my-dotfiles`). + +### Task 5: Install and live check + +- [ ] **Step 1:** `cp -r examples/tools/project.sh examples/tools/project.toml examples/tools/licenses ~/.config/desktop-assistant/tools/` + then `./assistant.sh restart`. +- [ ] **Step 2:** Live, over the socket: 5 phrasings it/en ("start a new + project called photo-sorter, it sorts photos by date", "crea un nuovo + progetto ..."). No tool call before the confirming yes, 5/5. Answer + "no" on all but one. +- [ ] **Step 3:** One "yes": folder in `~/Programming/GIT`, `%G?` is `G`, + kitty tab focused, Claude Code started with the prompt. Then one with + "...with opencode": opencode starts with the prompt. Delete that + folder afterwards only if the user says so. +- [ ] **Step 4:** With kitty closed, one creation opens a new window. +- [ ] **Step 5:** Record results under Findings in `AGENTS.md` (and the + tool in the Status paragraph), mark the spec implemented, ask, commit. diff --git a/docs/superpowers/specs/2026-10-19-project-tool-design.md b/docs/superpowers/specs/2026-10-19-project-tool-design.md index e1b69e9..0fed3c5 100644 --- a/docs/superpowers/specs/2026-10-19-project-tool-design.md +++ b/docs/superpowers/specs/2026-10-19-project-tool-design.md @@ -7,8 +7,8 @@ Date: 2026-10-19. Status: approved, not implemented. Ask the assistant to start a new project and get a folder in `~/Programming/GIT` laid out like the user's other repos (AGENTS.md, CLAUDE.md pointing at it, README.md, LICENSE), a GPG-signed first commit, -and a kitty tab in that folder running Claude Code, already told what the -project is. +and a kitty tab in that folder running Claude Code or opencode (the +user's choice), already told what the project is. This tool writes more than one bit, so unlike `mode` it has a review step: the model repeats name, description and license and calls the tool only @@ -20,8 +20,8 @@ after the user says yes. A misheard name would otherwise become a folder. like the others. No change to `assistant.py`. ```toml -description = "Start a new programming project: a folder in ~/Programming/GIT with AGENTS.md, CLAUDE.md, README.md, LICENSE and a signed first commit, then a kitty tab running Claude Code in it. Before calling, tell the user the name, description and license you will use, and call only after they say yes." -command = ["project.sh", "{name}", "{license}", "{description}"] +description = "Start a new programming project: a folder in ~/Programming/GIT with AGENTS.md, CLAUDE.md, README.md, LICENSE and a signed first commit, then a kitty tab running a coding agent (Claude Code or opencode) in it. Before calling, tell the user the name, description, license and agent you will use, and call only after they say yes." +command = ["project.sh", "{name}", "{license}", "{description}", "{agent}"] timeout = 120 [params.name] @@ -35,6 +35,10 @@ description = "gpl2 (GPLv2 only, default), gpl2+ (GPLv2 or later), gpl3 or mit. [params.description] type = "string" description = "What the project is, one or two sentences, in the user's words" + +[params.agent] +type = "string" +description = "claude (Claude Code, default) or opencode. Empty means claude." ``` The 120 s timeout leaves room for the Nitrokey PIN dialog: gpg-agent uses @@ -45,7 +49,8 @@ The 120 s timeout leaves room for the Nitrokey PIN dialog: gpg-agent uses Bash, GPLv2 header like the other tool scripts. 1. **Validate.** Name matches `^[a-z0-9][a-z0-9._-]*$`; license in - `gpl2 gpl2+ gpl3 mit` (empty = gpl2); description not empty. Refuse + `gpl2 gpl2+ gpl3 mit` (empty = gpl2); description not empty; agent + `claude` or `opencode` (empty = claude). Refuse if `$PROJECTS_DIR/` exists (`PROJECTS_DIR` defaults to `~/Programming/GIT`, overridable for the check). Each refusal is one line on stderr naming what is accepted, exit 1. @@ -71,17 +76,19 @@ Bash, GPLv2 header like the other tool scripts. disabled. If the commit fails (PIN cancelled, hook rejection), the folder stays and the output says so with git's first error line; the tab still opens. -4. **Open Claude Code.** Prompt: `New project: . Fill in +4. **Open the agent.** `claude ` or `opencode --prompt + `; both read AGENTS.md (CLAUDE.md points Claude Code at it). + Prompt: `New project: . Fill in AGENTS.md and README.md from this, then wait.` - - Socket: newest `$XDG_RUNTIME_DIR/kitty-*` that answers - `kitty @ --to unix: ls`. Then `kitty @ --to unix: launch - --type=tab --cwd= claude ` and + - Sockets `$XDG_RUNTIME_DIR/kitty-*`, newest first: the first where + `kitty @ --to unix: launch --type=tab --cwd= ` succeeds wins (a stale socket just fails), then `hyprctl dispatch focuswindow class:kitty`. - - No socket answers: `setsid -f kitty -d claude `, a new + - No socket answers: `setsid -f kitty -d `, a new window, detached. - `KITTY` env var overrides the `kitty` binary, for the check. 5. **Output**, one line: `created (), committed | not committed: , opened in a kitty tab | window`. + sha> | not committed: , opened in a kitty tab | window`. ## kitty-conf (separate repo) @@ -101,19 +108,19 @@ inside kitty cannot drive it. Takes effect after kitty restarts once. `PROJECTS_DIR`, `KITTY=true` so no tab opens. Asserts the four files and a signed commit (`git log --format=%G?` is `G`), then the refusals: bad name (`Foo`, `../x`, empty), existing folder, unknown license, empty -description. The commit is really signed: with a cold PIN cache the +description, unknown agent. The commit is really signed: with a cold PIN cache the dialog appears once. Live, against the running model: - 5 phrasings, Italian and English. The model must state name, description and license and wait: no tool call before the yes, 5/5. -- One creation end to end: folder, signed commit, kitty tab focused, - Claude Code started with the prompt. +- One creation end to end per agent: folder, signed commit, kitty tab + focused, Claude Code or opencode started with the prompt. - kitty not running: a new window opens instead. ## Not included Remotes (gitctl, gh, tea), language scaffolding, source-file headers -(there are no sources yet). The Claude Code session in the new tab +(there are no sources yet). The agent session in the new tab handles those. -- cgit v1.2.3