aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers')
-rw-r--r--docs/superpowers/plans/2026-10-19-project-tool.md252
-rw-r--r--docs/superpowers/specs/2026-10-19-project-tool-design.md37
2 files changed, 274 insertions, 15 deletions
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 '<year> <copyright holders>' 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. <danix@danix.xyz>" "$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 <name> <license> <description> [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. <danix@danix.xyz>"
+
+[[ $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>/$year/; s/<copyright holders>/$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 <<EOF
+# AGENTS.md
+
+Guidance for AI coding agents working in this repository.
+
+## What this is
+
+\`$name\`: $desc
+
+## Conventions
+
+- Placeholders only in code, docs, tests and commits.
+
+## Testing
+
+No test suite yet.
+
+## Repo facts
+
+- License: $lic (\`LICENSE\`).
+- Work directly on master.
+EOF
+
+cat > README.md <<EOF
+# $name
+
+$desc
+
+## License
+
+Copyright (C) $year $author
+
+$lic. See [LICENSE](LICENSE) for the full text.
+
+## Development Approach
+
+This project is developed using AI-assisted tools. Code is generated with the help of AI based on human-provided specifications, design decisions, and iterative feedback.
+
+All contributions are reviewed, tested, and curated by the maintainer before being included in the codebase. AI is used as a productivity and exploration tool, while human oversight remains central to all decisions.
+
+The goal is to combine the flexibility of AI-assisted development with standard open-source practices such as transparency, review, and accountability.
+EOF
+
+# Signing is never turned off: a cold cache brings up pinentry-qt, hence
+# the tool's 120 s timeout. A failed commit keeps the folder and says why.
+git init -q -b master && git add -A || exit 1
+if err=$(git commit -q -S -m "Initial commit" 2>&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:<it> 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/<name>` 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: <description>. Fill in
+4. **Open the agent.** `claude <prompt>` or `opencode --prompt
+ <prompt>`; both read AGENTS.md (CLAUDE.md points Claude Code at it).
+ Prompt: `New project: <description>. Fill in
AGENTS.md and README.md from this, then wait.`
- - Socket: newest `$XDG_RUNTIME_DIR/kitty-*` that answers
- `kitty @ --to unix:<sock> ls`. Then `kitty @ --to unix:<sock> launch
- --type=tab --cwd=<dir> claude <prompt>` and
+ - Sockets `$XDG_RUNTIME_DIR/kitty-*`, newest first: the first where
+ `kitty @ --to unix:<sock> launch --type=tab --cwd=<dir> <agent
+ command>` succeeds wins (a stale socket just fails), then
`hyprctl dispatch focuswindow class:kitty`.
- - No socket answers: `setsid -f kitty -d <dir> claude <prompt>`, a new
+ - No socket answers: `setsid -f kitty -d <dir> <agent command>`, a new
window, detached.
- `KITTY` env var overrides the `kitty` binary, for the check.
5. **Output**, one line: `created <path> (<license>), committed <short
- sha> | not committed: <reason>, opened in a kitty tab | window`.
+ sha> | not committed: <reason>, opened <agent> 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.