mirror of
https://github.com/obra/superpowers.git
synced 2026-06-18 16:49:04 +08:00
Compare commits
1 Commits
fix/1780-s
...
codex/pri-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3567c68388 |
@@ -9,7 +9,7 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "6.0.2",
|
||||
"version": "6.0.0",
|
||||
"source": "./",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "6.0.2",
|
||||
"version": "6.0.0",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
"email": "jesse@fsck.com"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "6.0.2",
|
||||
"version": "6.0.0",
|
||||
"description": "An agentic skills framework & software development methodology that works: planning, TDD, debugging, and collaboration workflows.",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "superpowers",
|
||||
"displayName": "Superpowers",
|
||||
"description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "6.0.2",
|
||||
"version": "6.0.0",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
"email": "jesse@fsck.com"
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -7,7 +7,8 @@ node_modules/
|
||||
inspo
|
||||
triage/
|
||||
|
||||
# Eval harness lives in its own repository, cloned into evals/ for local
|
||||
# development (see CLAUDE.md / README.md). It is not part of the published
|
||||
# plugin, so the whole directory is ignored here.
|
||||
evals/
|
||||
# Eval harness — drill ships its own gitignore at evals/.gitignore;
|
||||
# these are belt-and-suspenders entries for tools that don't recurse.
|
||||
evals/results/
|
||||
evals/.venv/
|
||||
evals/.env
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "evals"]
|
||||
path = evals
|
||||
url = git@github.com:prime-radiant-inc/superpowers-evals.git
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "6.0.2",
|
||||
"version": "6.0.0",
|
||||
"description": "An agentic skills framework and software development methodology.",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
|
||||
@@ -101,7 +101,7 @@ Skills are not prose — they are code that shapes agent behavior. If you modify
|
||||
|
||||
## Eval harness
|
||||
|
||||
Skill-behavior evals live in [superpowers-evals](https://github.com/prime-radiant-inc/superpowers-evals/), cloned into `evals/` — see `evals/README.md` for setup. Drill (the harness) drives real tmux sessions of Claude Code / Codex / Gemini CLI and judges skill compliance with an LLM verifier. Plugin-infrastructure tests still live at `tests/`.
|
||||
Skill-behavior evals live in the `evals/` submodule — after cloning, run `git submodule update --init evals`, then see `evals/README.md`. Drill (the harness) drives real tmux sessions of Claude Code / Codex / Gemini CLI and judges skill compliance with an LLM verifier. Plugin-infrastructure tests still live at `tests/`.
|
||||
|
||||
## Understand the Project Before Contributing
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ The general contribution process for Superpowers is below. Keep in mind that we
|
||||
4. Follow the `writing-skills` skill for creating and testing new and modified skills
|
||||
5. Submit a PR, being sure to fill in the pull request template.
|
||||
|
||||
Skill-behavior tests use the drill eval harness from [superpowers-evals](https://github.com/prime-radiant-inc/superpowers-evals/), cloned into `evals/` — see `evals/README.md` for setup. Plugin-infrastructure tests live at `tests/` and run via the relevant `run-*.sh` or `npm test`.
|
||||
Skill-behavior tests use the eval harness submodule at `evals/`. After cloning this repo, run `git submodule update --init evals`, then see `evals/README.md` for setup. Plugin-infrastructure tests live at `tests/` and run via the relevant `run-*.sh` or `npm test`.
|
||||
|
||||
See `skills/writing-skills/SKILL.md` for the complete guide.
|
||||
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
# Superpowers Release Notes
|
||||
|
||||
## v6.0.2 (2026-06-16)
|
||||
|
||||
### Install Fixes
|
||||
|
||||
- **We no longer ship the `evals` submodule.** It broke plugin installs for some users, so the eval harness now lives in its own repo, separate from the published plugin. (#1778, #1774)
|
||||
|
||||
## v6.0.1 (2026-06-16)
|
||||
|
||||
### Codex Fixes
|
||||
|
||||
- **Version display in the brainstorm companion** — packaged Codex plugins ship without a root `package.json`, so the visual companion reported its version as "unknown". `readSuperpowersVersion()` now falls back to `.codex-plugin/plugin.json` when `package.json` is absent.
|
||||
- **Cleaner Codex plugin sync** — the sync-to-codex script now excludes `.gitmodules` and `.pre-commit-config.yaml`, keeping repo metadata out of the packaged Codex plugin.
|
||||
|
||||
## v6.0.0 (2026-06-16)
|
||||
|
||||
Superpowers 6.0 is a big release. The headline is a rewrite of how `subagent-driven-development` reviews each task — cheaper, stricter, and harder to game.
|
||||
|
||||
1
evals
Submodule
1
evals
Submodule
Submodule evals added at 70a245c36c
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "6.0.2",
|
||||
"version": "6.0.0",
|
||||
"contextFileName": "GEMINI.md"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "6.0.2",
|
||||
"version": "6.0.0",
|
||||
"description": "Superpowers skills and runtime bootstrap for coding agents",
|
||||
"type": "module",
|
||||
"main": ".opencode/plugins/superpowers.js",
|
||||
|
||||
@@ -52,11 +52,9 @@ EXCLUDES=(
|
||||
"/.gitattributes"
|
||||
"/.github/"
|
||||
"/.gitignore"
|
||||
"/.gitmodules"
|
||||
"/.kimi-plugin/"
|
||||
"/.opencode/"
|
||||
"/.pi/"
|
||||
"/.pre-commit-config.yaml"
|
||||
"/.version-bump.json"
|
||||
"/.worktrees/"
|
||||
".DS_Store"
|
||||
|
||||
@@ -206,22 +206,14 @@ const helperInjection = '<script>\n' + helperScript + '\n</script>';
|
||||
// ========== Helper Functions ==========
|
||||
|
||||
function readSuperpowersVersion() {
|
||||
const root = path.join(__dirname, '../../..');
|
||||
const manifests = [
|
||||
path.join(root, 'package.json'),
|
||||
path.join(root, '.codex-plugin/plugin.json')
|
||||
];
|
||||
|
||||
for (const manifest of manifests) {
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(manifest, 'utf-8'));
|
||||
if (data.version) return String(data.version);
|
||||
} catch (e) {
|
||||
// Packaged Codex plugins omit package.json; try the next manifest.
|
||||
}
|
||||
try {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, '../../..', 'package.json'), 'utf-8')
|
||||
);
|
||||
return String(packageJson.version || 'unknown');
|
||||
} catch (e) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
function isTruthyEnv(value) {
|
||||
|
||||
@@ -251,7 +251,7 @@ sequences — the single most expensive failure observed. Track progress in
|
||||
a ledger file, not only in todos.
|
||||
|
||||
- At skill start, check for a ledger:
|
||||
`cat "$(git rev-parse --show-toplevel)/.superpowers/sdd/progress.md"`. Tasks listed there
|
||||
`cat "$(git rev-parse --git-path sdd)/progress.md"`. Tasks listed there
|
||||
as complete are DONE — do not re-dispatch them; resume at the first task
|
||||
not marked complete.
|
||||
- When a task's review comes back clean, append one line to the ledger in
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
# tasks intact.
|
||||
#
|
||||
# Usage: review-package BASE HEAD [OUTFILE]
|
||||
# Default OUTFILE: <repo-root>/.superpowers/sdd/review-<base7>..<head7>.diff
|
||||
# (named per range, so a re-review after fixes gets a distinct fresh file).
|
||||
# Default OUTFILE: <git-dir>/sdd/review-<base7>..<head7>.diff — unique per
|
||||
# repo instance and per range, so concurrent sessions cannot collide and a
|
||||
# re-review after fixes always gets a distinctly named fresh file.
|
||||
set -euo pipefail
|
||||
|
||||
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
||||
@@ -23,7 +24,9 @@ git rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&
|
||||
if [ $# -eq 3 ]; then
|
||||
out=$3
|
||||
else
|
||||
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
|
||||
dir=$(git rev-parse --git-path sdd)
|
||||
mkdir -p "$dir"
|
||||
dir=$(cd "$dir" && pwd)
|
||||
out="$dir/review-$(git rev-parse --short "$base")..$(git rev-parse --short "$head").diff"
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Resolve and ensure the working-tree directory SDD uses for its short-lived
|
||||
# artifacts: task briefs, implementer reports, review packages, and the
|
||||
# progress ledger. Print the directory's absolute path.
|
||||
#
|
||||
# The workspace lives in the working tree (not under .git/) because Claude Code
|
||||
# treats .git/ as a protected path and denies agent writes there — which blocks
|
||||
# an implementer subagent from writing its report file. A self-ignoring
|
||||
# .gitignore keeps the workspace out of `git status` and out of accidental
|
||||
# commits without modifying any tracked file.
|
||||
#
|
||||
# Single source of truth for the workspace location, so task-brief and
|
||||
# review-package cannot drift to different directories.
|
||||
#
|
||||
# Usage: sdd-workspace
|
||||
set -euo pipefail
|
||||
|
||||
root=$(git rev-parse --show-toplevel)
|
||||
dir="$root/.superpowers/sdd"
|
||||
mkdir -p "$dir"
|
||||
printf '*\n' > "$dir/.gitignore"
|
||||
cd "$dir" && pwd
|
||||
@@ -4,8 +4,7 @@
|
||||
# through the controller's context.
|
||||
#
|
||||
# Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]
|
||||
# Default OUTFILE: <repo-root>/.superpowers/sdd/task-<N>-brief.md
|
||||
# (per worktree; concurrent runs in the same working tree share it).
|
||||
# Default OUTFILE: <git-dir>/sdd/task-<N>.<unique>/task-<N>-brief.md.
|
||||
set -euo pipefail
|
||||
|
||||
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
||||
@@ -20,8 +19,11 @@ n=$2
|
||||
if [ $# -eq 3 ]; then
|
||||
out=$3
|
||||
else
|
||||
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
|
||||
out="$dir/task-${n}-brief.md"
|
||||
dir=$(git rev-parse --git-path sdd)
|
||||
mkdir -p "$dir"
|
||||
dir=$(cd "$dir" && pwd)
|
||||
brief_dir=$(mktemp -d "$dir/task-${n}.XXXXXX")
|
||||
out="$brief_dir/task-${n}-brief.md"
|
||||
fi
|
||||
|
||||
awk -v n="$n" '
|
||||
|
||||
@@ -26,9 +26,9 @@ function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function startServer({ port, dir, env = {}, serverPath = SERVER_PATH }) {
|
||||
function startServer({ port, dir, env = {} }) {
|
||||
cleanup(dir);
|
||||
return spawn('node', [serverPath], {
|
||||
return spawn('node', [SERVER_PATH], {
|
||||
env: {
|
||||
...process.env,
|
||||
BRAINSTORM_PORT: String(port),
|
||||
@@ -74,21 +74,6 @@ function writeFragment(dir) {
|
||||
fs.writeFileSync(path.join(contentDir, 'screen.html'), '<h2>Pick a layout</h2>');
|
||||
}
|
||||
|
||||
function createPackagedServerFixture(version) {
|
||||
const root = fs.mkdtempSync(path.join('/tmp', 'superpowers-packaged-server-'));
|
||||
const scriptDir = path.join(root, 'skills/brainstorming/scripts');
|
||||
fs.cpSync(path.join(REPO_ROOT, 'skills/brainstorming/scripts'), scriptDir, { recursive: true });
|
||||
fs.mkdirSync(path.join(root, '.codex-plugin'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(root, '.codex-plugin/plugin.json'),
|
||||
JSON.stringify({ name: 'superpowers', version }, null, 2)
|
||||
);
|
||||
return {
|
||||
root,
|
||||
serverPath: path.join(scriptDir, 'server.cjs')
|
||||
};
|
||||
}
|
||||
|
||||
async function withServer(options, fn) {
|
||||
const server = startServer(options);
|
||||
try {
|
||||
@@ -119,13 +104,13 @@ async function test(name, fn) {
|
||||
}
|
||||
}
|
||||
|
||||
function assertBrandedWithLogo(html, version = PACKAGE_VERSION) {
|
||||
function assertBrandedWithLogo(html) {
|
||||
assert(
|
||||
html.includes(`Superpowers v${version}`),
|
||||
html.includes(`Superpowers v${PACKAGE_VERSION}`),
|
||||
'branding text should include dynamic package version'
|
||||
);
|
||||
assert(
|
||||
!html.includes(`Superpowers v${version} by`),
|
||||
!html.includes(`Superpowers v${PACKAGE_VERSION} by`),
|
||||
'branding text should not include "by" when the logo is visible'
|
||||
);
|
||||
assert(
|
||||
@@ -154,15 +139,15 @@ function assertBrandedWithLogo(html, version = PACKAGE_VERSION) {
|
||||
);
|
||||
}
|
||||
|
||||
function assertBrandedFallbackText(html, version = PACKAGE_VERSION) {
|
||||
function assertBrandedFallbackText(html) {
|
||||
assert(
|
||||
html.includes(`Prime Radiant Superpowers v${version}`),
|
||||
html.includes(`Prime Radiant Superpowers v${PACKAGE_VERSION}`),
|
||||
'disabled telemetry should keep plain text Prime Radiant/Superpowers branding'
|
||||
);
|
||||
}
|
||||
|
||||
function assertTelemetryImage(html, version = PACKAGE_VERSION) {
|
||||
const expectedUrl = `${ASSET_URL}?v=${encodeURIComponent(version)}`;
|
||||
function assertTelemetryImage(html) {
|
||||
const expectedUrl = `${ASSET_URL}?v=${encodeURIComponent(PACKAGE_VERSION)}`;
|
||||
assert(html.includes(`src="${expectedUrl}"`), 'remote image should use the dedicated main-domain asset with only v=');
|
||||
assert(!html.includes('event='), 'remote image URL must not include event=');
|
||||
assert(!html.includes('surface='), 'remote image URL must not include surface=');
|
||||
@@ -270,26 +255,6 @@ async function main() {
|
||||
});
|
||||
});
|
||||
|
||||
await test('packaged Codex plugin reads version from .codex-plugin manifest', async () => {
|
||||
const port = 3457;
|
||||
const dir = '/tmp/brainstorm-branding-packaged-codex';
|
||||
const packagedVersion = '7.8.9';
|
||||
const fixture = createPackagedServerFixture(packagedVersion);
|
||||
|
||||
try {
|
||||
await withServer({ port, dir, serverPath: fixture.serverPath }, async () => {
|
||||
writeFragment(dir);
|
||||
await sleep(300);
|
||||
const html = await fetchHtml(port);
|
||||
assertBrandedWithLogo(html, packagedVersion);
|
||||
assertTelemetryImage(html, packagedVersion);
|
||||
assert(!html.includes('Superpowers vunknown'), 'packaged plugin should not fall back to unknown version');
|
||||
});
|
||||
} finally {
|
||||
cleanup(fixture.root);
|
||||
}
|
||||
});
|
||||
|
||||
await test('SUPERPOWERS_DISABLE_TELEMETRY=true omits remote image but keeps local branding', async () => {
|
||||
const port = 3453;
|
||||
const dir = '/tmp/brainstorm-branding-disabled';
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tests for the SDD workspace: scripts/sdd-workspace resolves a self-ignoring
|
||||
# working-tree directory for SDD artifacts, and the SDD scripts write into it.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SDD_SCRIPTS="$REPO_ROOT/skills/subagent-driven-development/scripts"
|
||||
|
||||
FAILURES=0
|
||||
TEST_ROOT=""
|
||||
|
||||
pass() { echo " [PASS] $1"; }
|
||||
fail() {
|
||||
echo " [FAIL] $1"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "$TEST_ROOT" && -d "$TEST_ROOT" ]]; then
|
||||
rm -rf "$TEST_ROOT"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
echo "=== Test: sdd-workspace ==="
|
||||
|
||||
TEST_ROOT="$(mktemp -d)"
|
||||
trap cleanup EXIT
|
||||
|
||||
# Resolve repo to its physical path so string comparisons match the
|
||||
# helper's output (git rev-parse --show-toplevel resolves symlinks; on
|
||||
# macOS mktemp lives under /var -> /private/var).
|
||||
git init -q -b main "$TEST_ROOT/repo"
|
||||
local repo
|
||||
repo="$(cd "$TEST_ROOT/repo" && git rev-parse --show-toplevel)"
|
||||
|
||||
local dir
|
||||
dir="$(cd "$repo" && "$SDD_SCRIPTS/sdd-workspace")"
|
||||
|
||||
if [[ "$dir" == "$repo/.superpowers/sdd" ]]; then
|
||||
pass "prints <repo-root>/.superpowers/sdd"
|
||||
else
|
||||
fail "prints <repo-root>/.superpowers/sdd"
|
||||
echo " got: $dir"
|
||||
fi
|
||||
|
||||
if [[ -f "$repo/.superpowers/sdd/.gitignore" && "$(cat "$repo/.superpowers/sdd/.gitignore")" == "*" ]]; then
|
||||
pass "self-ignoring .gitignore created with '*'"
|
||||
else
|
||||
fail "self-ignoring .gitignore created with '*'"
|
||||
fi
|
||||
|
||||
printf 'x\n' > "$repo/.superpowers/sdd/artifact.md"
|
||||
local status
|
||||
status="$(cd "$repo" && git status --porcelain)"
|
||||
if [[ -z "$status" ]]; then
|
||||
pass "workspace invisible to git status"
|
||||
else
|
||||
fail "workspace invisible to git status"
|
||||
echo " status: $status"
|
||||
fi
|
||||
|
||||
( cd "$repo" && git add -A )
|
||||
local staged
|
||||
staged="$(cd "$repo" && git diff --cached --name-only)"
|
||||
if [[ -z "$staged" ]]; then
|
||||
pass "git add -A does not stage the workspace"
|
||||
else
|
||||
fail "git add -A does not stage the workspace"
|
||||
echo " staged: $staged"
|
||||
fi
|
||||
|
||||
cat > "$repo/plan.md" <<'PLAN'
|
||||
# Plan
|
||||
|
||||
## Task 1: First thing
|
||||
|
||||
Do the first thing.
|
||||
PLAN
|
||||
|
||||
local brief_out brief_path
|
||||
brief_out="$(cd "$repo" && "$SDD_SCRIPTS/task-brief" plan.md 1)"
|
||||
brief_path="$(printf '%s\n' "$brief_out" | sed -n 's/^wrote \(.*\): [0-9][0-9]* lines$/\1/p')"
|
||||
case "$brief_path" in
|
||||
"$repo/.superpowers/sdd/"*) pass "task-brief writes its brief under the workspace" ;;
|
||||
*)
|
||||
fail "task-brief writes its brief under the workspace"
|
||||
echo " got: $brief_path"
|
||||
;;
|
||||
esac
|
||||
|
||||
local git_id=(-c user.email=t@example.com -c user.name=t -c commit.gpgsign=false)
|
||||
( cd "$repo" \
|
||||
&& git add plan.md \
|
||||
&& git "${git_id[@]}" commit -qm c1 \
|
||||
&& printf 'y\n' > f && git add f \
|
||||
&& git "${git_id[@]}" commit -qm c2 )
|
||||
local rp_out rp_path
|
||||
rp_out="$(cd "$repo" && "$SDD_SCRIPTS/review-package" HEAD~1 HEAD)"
|
||||
rp_path="$(printf '%s\n' "$rp_out" | sed -n 's/^wrote \(.*\): [0-9].*$/\1/p')"
|
||||
case "$rp_path" in
|
||||
"$repo/.superpowers/sdd/"*) pass "review-package writes its diff under the workspace" ;;
|
||||
*)
|
||||
fail "review-package writes its diff under the workspace"
|
||||
echo " got: $rp_path"
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Worktree isolation: a linked worktree resolves its own workspace ---
|
||||
local wt="$TEST_ROOT/wt"
|
||||
( cd "$repo" && git worktree add -q "$wt" -b wt-feature )
|
||||
local wt_root wt_dir
|
||||
wt_root="$(cd "$wt" && git rev-parse --show-toplevel)"
|
||||
wt_dir="$(cd "$wt" && "$SDD_SCRIPTS/sdd-workspace")"
|
||||
if [[ "$wt_dir" == "$wt_root/.superpowers/sdd" && "$wt_dir" != "$dir" ]]; then
|
||||
pass "linked worktree resolves its own distinct workspace"
|
||||
else
|
||||
fail "linked worktree resolves its own distinct workspace"
|
||||
echo " main: $dir"
|
||||
echo " wt: $wt_dir"
|
||||
fi
|
||||
|
||||
printf 'y\n' > "$wt/.superpowers/sdd/artifact.md"
|
||||
local wt_status
|
||||
wt_status="$(cd "$wt" && git status --porcelain)"
|
||||
if [[ -z "$wt_status" ]]; then
|
||||
pass "worktree workspace invisible to git status"
|
||||
else
|
||||
fail "worktree workspace invisible to git status"
|
||||
echo " status: $wt_status"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [[ "$FAILURES" -ne 0 ]]; then
|
||||
echo "FAILED: $FAILURES assertion(s)."
|
||||
exit 1
|
||||
fi
|
||||
echo "PASS"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
117
tests/claude-code/test-task-brief.sh
Executable file
117
tests/claude-code/test-task-brief.sh
Executable file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
TASK_BRIEF="$REPO_ROOT/skills/subagent-driven-development/scripts/task-brief"
|
||||
|
||||
FAILURES=0
|
||||
TEST_ROOT=""
|
||||
|
||||
pass() {
|
||||
echo " [PASS] $1"
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo " [FAIL] $1"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "$TEST_ROOT" && -d "$TEST_ROOT" ]]; then
|
||||
rm -rf "$TEST_ROOT"
|
||||
fi
|
||||
}
|
||||
|
||||
extract_written_path() {
|
||||
local output="$1"
|
||||
printf '%s\n' "$output" | sed -n 's/^wrote \(.*\): [0-9][0-9]* lines$/\1/p'
|
||||
}
|
||||
|
||||
assert_not_equals() {
|
||||
local actual="$1"
|
||||
local expected="$2"
|
||||
local description="$3"
|
||||
|
||||
if [[ "$actual" != "$expected" ]]; then
|
||||
pass "$description"
|
||||
else
|
||||
fail "$description"
|
||||
echo " both were: $actual"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_file_contains() {
|
||||
local path="$1"
|
||||
local needle="$2"
|
||||
local description="$3"
|
||||
|
||||
if grep -Fq -- "$needle" "$path"; then
|
||||
pass "$description"
|
||||
else
|
||||
fail "$description"
|
||||
echo " expected $path to contain: $needle"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
echo "=== Test: task-brief output paths ==="
|
||||
|
||||
TEST_ROOT="$(mktemp -d)"
|
||||
trap cleanup EXIT
|
||||
|
||||
local repo="$TEST_ROOT/repo"
|
||||
local plan="$repo/plan.md"
|
||||
local output_one
|
||||
local output_two
|
||||
local path_one
|
||||
local path_two
|
||||
|
||||
git init -q -b main "$repo"
|
||||
|
||||
cat > "$plan" <<'EOF'
|
||||
# Implementation Plan
|
||||
|
||||
## Task 1: First thing
|
||||
|
||||
Do the first thing.
|
||||
|
||||
## Task 2: Second thing
|
||||
|
||||
Do the second thing.
|
||||
EOF
|
||||
|
||||
output_one="$(cd "$repo" && "$TASK_BRIEF" "$plan" 1)"
|
||||
output_two="$(cd "$repo" && "$TASK_BRIEF" "$plan" 1)"
|
||||
path_one="$(extract_written_path "$output_one")"
|
||||
path_two="$(extract_written_path "$output_two")"
|
||||
|
||||
assert_not_equals "$path_one" "$path_two" "Default task brief paths are unique per invocation"
|
||||
assert_file_contains "$path_one" "## Task 1: First thing" "First default brief contains the requested task"
|
||||
assert_file_contains "$path_two" "## Task 1: First thing" "Second default brief contains the requested task"
|
||||
|
||||
if [[ "$path_one" == "$repo/.git/sdd/"* ]]; then
|
||||
pass "First default brief stays under the repo git metadata directory"
|
||||
else
|
||||
fail "First default brief stays under the repo git metadata directory"
|
||||
echo " actual: $path_one"
|
||||
fi
|
||||
|
||||
if [[ "$path_two" == "$repo/.git/sdd/"* ]]; then
|
||||
pass "Second default brief stays under the repo git metadata directory"
|
||||
else
|
||||
fail "Second default brief stays under the repo git metadata directory"
|
||||
echo " actual: $path_two"
|
||||
fi
|
||||
|
||||
if [[ $FAILURES -ne 0 ]]; then
|
||||
echo ""
|
||||
echo "FAILED: $FAILURES assertion(s) failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "PASS"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -200,23 +200,6 @@ EOF
|
||||
.private-journal/
|
||||
EOF
|
||||
|
||||
cat > "$repo/.gitmodules" <<'EOF'
|
||||
[submodule "evals"]
|
||||
path = evals
|
||||
url = git@example.com:example/evals.git
|
||||
EOF
|
||||
|
||||
cat > "$repo/.pre-commit-config.yaml" <<'EOF'
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: evals-check
|
||||
name: evals check
|
||||
entry: echo evals
|
||||
language: system
|
||||
files: ^evals/
|
||||
EOF
|
||||
|
||||
if [[ "$with_pure_ignored" == "1" ]]; then
|
||||
cat >> "$repo/.gitignore" <<'EOF'
|
||||
ignored-cache/
|
||||
@@ -294,8 +277,6 @@ EOF
|
||||
.codex-plugin/plugin.json \
|
||||
.kimi-plugin/plugin.json \
|
||||
.gitignore \
|
||||
.gitmodules \
|
||||
.pre-commit-config.yaml \
|
||||
assets/app-icon.png \
|
||||
assets/superpowers-small.svg \
|
||||
evals/drill/README.md \
|
||||
@@ -662,8 +643,6 @@ main() {
|
||||
assert_not_contains "$preview_section" ".private-journal/leak.txt" "Preview excludes ignored untracked file"
|
||||
assert_not_contains "$preview_section" "ignored-cache/" "Preview excludes pure ignored directories"
|
||||
assert_not_contains "$preview_section" "evals/" "Preview excludes eval harness"
|
||||
assert_not_contains "$preview_section" ".gitmodules" "Preview excludes repo submodule metadata"
|
||||
assert_not_contains "$preview_section" ".pre-commit-config.yaml" "Preview excludes repo pre-commit config"
|
||||
assert_not_contains "$preview_output" "Overlay file (.codex-plugin/plugin.json) will be regenerated" "Preview omits overlay regeneration note"
|
||||
assert_not_contains "$preview_output" "Assets (superpowers-small.svg, app-icon.png) will be seeded from" "Preview omits assets seeding note"
|
||||
assert_contains "$preview_section" "skills/example/SKILL.md" "Preview reflects dirty tracked destination file"
|
||||
|
||||
Reference in New Issue
Block a user