Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .agents/prompts/project-grill.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ Read `AGENTS.md`, `README.md`, `docs/repository-setup.md`, any existing
`docs/PROJECT_REQUIREMENTS.md`, accepted ADRs, and the actual repository before
asking questions.

If `docs/PROJECT_DESCRIPTION.md` exists, read it first. It is the project
description supplied by the human. Take every decision it already makes as
given, ask only about what it leaves unresolved or contradictory, and do not
modify it.

## Goal

Discover only unresolved decisions that materially affect the product,
Expand Down
3 changes: 3 additions & 0 deletions .agents/prompts/project-planner.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Read `AGENTS.md`, the approved `docs/PROJECT_REQUIREMENTS.md`,
`docs/repository-setup.md`, the current architecture, accepted ADRs, and the
actual repository before changing planning artifacts.

`docs/PROJECT_DESCRIPTION.md`, when present, is background supplied by the
human. The approved requirements take precedence over it. Do not modify it.

## Preconditions

Do not proceed unless `docs/PROJECT_REQUIREMENTS.md` contains:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ A lightweight, model-agnostic repository template for agentic software engineeri

```bash
./scripts/start-planning.sh
# or, with an existing project description:
./scripts/start-planning.sh --description path/to/description.md
```

The script creates `planning/project-bootstrap` in a sibling worktree. The
Expand Down
18 changes: 17 additions & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,24 @@ idea and completing `docs/repository-setup.md`:
The full interface is:

```text
./scripts/start-planning.sh [name] [--agent <agent>] [--model <model>]
./scripts/start-planning.sh [name] [--description <file>] [--agent <agent>] [--model <model>]
```

If you already have a project description, pass it with `--description`:

```bash
./scripts/start-planning.sh --description ~/notes/project-idea.md
```

The file may be anywhere, including outside the repository. An uncommitted
description inside the repository is the one change the clean-checkout check
allows.
The script copies it to `docs/PROJECT_DESCRIPTION.md` in the planning worktree
before the first session. Project Grill reads it first and asks only about
what it leaves unresolved. Neither planning phase may modify it, and it is
committed with the planning branch as the recorded input. A missing, empty, or
non-regular file fails before a branch or worktree is created.

Project Grill uses role `project-grill` and the planning session uses role
`project-planner`; `--agent` and `--model` override both. The optional name
defaults to `project-bootstrap`. A custom planning cycle such as:
Expand Down Expand Up @@ -137,6 +152,7 @@ manually:
```bash
cd ../<repository>-planning-project-bootstrap
./scripts/verify.sh
git add docs/PROJECT_DESCRIPTION.md # only when --description was used
git add docs/PROJECT_REQUIREMENTS.md docs/architecture.md docs/roadmap.md docs/decisions
git commit -m "Plan project bootstrap"
git push -u origin planning/project-bootstrap
Expand Down
75 changes: 71 additions & 4 deletions scripts/start-planning.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/lib/agent.sh"

usage() {
cat <<EOF
Usage: $0 [name] [--agent <agent>] [--model <model>]
Usage: $0 [name] [--description <file>] [--agent <agent>] [--model <model>]

The agent and model of each phase come from .agents/agents.conf (roles
project-grill and project-planner). --agent and --model override both phases.

--description copies an existing project description into the planning
worktree as docs/PROJECT_DESCRIPTION.md. Project Grill reads it first.

Examples:
$0
$0 architecture-refresh
$0 --description ~/notes/project-idea.md
$0 --agent claude --model fable
EOF
}
Expand All @@ -24,12 +28,41 @@ fail() {
}

agent_parse_args "$@"
if [[ "${#AGENT_POSITIONAL[@]}" -gt 1 ]]; then

description_source=""
names=()
set -- ${AGENT_POSITIONAL[@]+"${AGENT_POSITIONAL[@]}"}
while [[ $# -gt 0 ]]; do
case "$1" in
--description)
[[ $# -ge 2 && -n "$2" ]] || fail "--description requires a file."
description_source="$2"
shift 2
;;
--*)
fail "unknown option: $1"
;;
*)
names+=("$1")
shift
;;
esac
done

if [[ "${#names[@]}" -gt 1 ]]; then
usage
exit 1
fi

name="${AGENT_POSITIONAL[0]:-project-bootstrap}"
name="${names[0]:-project-bootstrap}"

if [[ -n "$description_source" ]]; then
[[ -f "$description_source" ]] ||
fail "project description is not a regular file: $description_source"
[[ -r "$description_source" && -s "$description_source" ]] ||
fail "project description is empty or unreadable: $description_source"
description_source="$(cd "$(dirname "$description_source")" && pwd -P)/$(basename "$description_source")"
fi

[[ "$name" =~ ^[a-z0-9][a-z0-9-]*$ ]] ||
fail "planning name must match [a-z0-9][a-z0-9-]*: $name"
Expand All @@ -54,7 +87,13 @@ planner_model="$AGENT_MODEL"
[[ -f "$grill_prompt" ]] || fail "missing Project Grill prompt: $grill_prompt"
[[ -f "$planner_prompt" ]] || fail "missing project-planner prompt: $planner_prompt"

if [[ -n "$(git -C "$repo_root" status --porcelain)" ]]; then
# An uncommitted description inside the repository is the only change that
# does not count as a dirty checkout.
clean_paths=(.)
if [[ -n "$description_source" && "$description_source" == "$repo_root"/* ]]; then
clean_paths+=(":(exclude,literal)${description_source#"$repo_root"/}")
fi
if [[ -n "$(git -C "$repo_root" status --porcelain -- "${clean_paths[@]}")" ]]; then
fail "current worktree is not clean. Commit or stash changes first."
fi

Expand All @@ -71,6 +110,9 @@ echo " Branch: $branch"
echo " Worktree: $worktree"
echo " Grill: $grill_agent ($grill_model)"
echo " Planner: $planner_agent ($planner_model)"
if [[ -n "$description_source" ]]; then
echo " Description: $description_source"
fi
echo

git -C "$repo_root" fetch origin main ||
Expand Down Expand Up @@ -228,6 +270,14 @@ $worktree
Do not commit, push, open or merge a pull request, create GitHub Issues,
implement application features, or deploy.
EOF

if [[ -n "$description_source" ]]; then
cat <<EOF

The user supplied a project description as $description_relative.
Read it before anything else. Do not modify it.
EOF
fi
}

run_agent() {
Expand Down Expand Up @@ -376,6 +426,20 @@ approve_requirements() {
approval_tmp=""
}

# Copy the description before the scope baselines are taken, so it is part of
# the protected state of both phases instead of an out-of-scope change.
description_relative="docs/PROJECT_DESCRIPTION.md"
if [[ -n "$description_source" ]]; then
description_target="$worktree/$description_relative"
[[ ! -L "$description_target" && (! -e "$description_target" || -f "$description_target") ]] ||
post_creation_fail "$description_relative exists in the worktree and is not a regular file."
mkdir -p "$worktree/docs" && cp "$description_source" "$description_target" ||
post_creation_fail "could not copy the project description into the planning worktree."
chmod 644 "$description_target" ||
post_creation_fail "could not set permissions on $description_relative."
echo "Copied project description to: $description_relative"
fi

grill_baseline="$state_dir/grill-baseline.tsv"
planner_baseline="$state_dir/planner-baseline.tsv"
snapshot_forbidden_paths "grill" "$grill_baseline"
Expand Down Expand Up @@ -468,6 +532,9 @@ echo
echo "Review the planning artifacts, then run:"
echo " cd \"$worktree\""
echo " ./scripts/verify.sh"
if [[ -n "$description_source" ]]; then
echo " git add $description_relative"
fi
echo " git add docs/PROJECT_REQUIREMENTS.md docs/architecture.md docs/roadmap.md docs/decisions"
echo " git commit -m \"Plan project bootstrap\""
echo " git push -u origin \"$branch\""
Expand Down
95 changes: 95 additions & 0 deletions tests/start-planning-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ REQUIREMENTS
printf 'Out-of-scope Grill change.\n' >README.md
elif [[ "${MOCK_GRILL_MODE:-success}" == "ignored" ]]; then
printf 'IGNORED_SECRET=test\n' >.env
elif [[ "${MOCK_GRILL_MODE:-success}" == "edit-description" ]]; then
printf 'Changed by Project Grill.\n' >>docs/PROJECT_DESCRIPTION.md
elif [[ "${MOCK_GRILL_MODE:-success}" == "weird-paths" ]]; then
printf 'Tab path.\n' >$'docs/PROJECT_REQUIREMENTS.md\textra'
printf 'Newline path.\n' >$'unexpected\nfile'
Expand Down Expand Up @@ -794,4 +796,97 @@ fi
[[ ! -e "$tmp/dirty-repo-planning-project-bootstrap" ]] ||
fail "dirty repository created a worktree"

# A project description from outside the repository is copied into the
# planning worktree before the phases start and is announced to Project Grill.
description="$tmp/project idea.txt"
printf 'A local-first recipe organizer.\n' >"$description"
description_repo="$(setup_repo description)"
description_log="$tmp/description.log"
(
cd "$description_repo"
printf 'y\n' |
PATH="$tmp/bin:/usr/bin:/bin" \
MOCK_AGENT_LOG="$description_log" \
./scripts/start-planning.sh --description "$description" --agent claude --model fable \
>"$tmp/description.out" 2>&1
) || {
cat "$tmp/description.out" >&2
fail "planning with a project description failed"
}
description_worktree="$tmp/description-repo-planning-project-bootstrap"
cmp -s "$description" "$description_worktree/docs/PROJECT_DESCRIPTION.md" ||
fail "project description was not copied into the planning worktree"
grep -Fq "docs/PROJECT_DESCRIPTION.md" "$description_log" ||
fail "project description was not announced to the agent"
[[ -z "$(git -C "$description_repo" status --porcelain)" ]] ||
fail "project description changed the primary checkout"

# An uncommitted description inside the repository is accepted, but it does
# not excuse other uncommitted changes.
inside_repo="$(setup_repo description-inside)"
printf 'An untracked description in the checkout.\n' >"$inside_repo/idea.md"
printf '\nDirty.\n' >>"$inside_repo/AGENTS.md"
if (
cd "$inside_repo"
PATH="$tmp/bin:/usr/bin:/bin" \
MOCK_AGENT_LOG="$tmp/description-inside.log" \
./scripts/start-planning.sh --description idea.md --agent codex --model astra >/dev/null 2>&1
); then
fail "unrelated uncommitted changes were accepted together with a description"
fi
[[ ! -e "$tmp/description-inside-repo-planning-project-bootstrap" ]] ||
fail "a dirty checkout created a worktree"
git -C "$inside_repo" checkout -q -- AGENTS.md
(
cd "$inside_repo"
printf 'y\n' |
PATH="$tmp/bin:/usr/bin:/bin" \
MOCK_AGENT_LOG="$tmp/description-inside.log" \
./scripts/start-planning.sh --description idea.md --agent codex --model astra \
>"$tmp/description-inside.out" 2>&1
) || {
cat "$tmp/description-inside.out" >&2
fail "an uncommitted description inside the repository was rejected"
}
cmp -s "$inside_repo/idea.md" \
"$tmp/description-inside-repo-planning-project-bootstrap/docs/PROJECT_DESCRIPTION.md" ||
fail "in-repository description was not copied into the planning worktree"

# A phase that modifies the supplied description exceeds its scope.
description_edit_repo="$(setup_repo description-edit)"
if (
cd "$description_edit_repo"
printf 'y\n' |
PATH="$tmp/bin:/usr/bin:/bin" \
MOCK_AGENT_LOG="$tmp/description-edit.log" \
MOCK_GRILL_MODE=edit-description \
./scripts/start-planning.sh --description "$description" --agent codex --model astra \
>/dev/null 2>&1
); then
fail "a modified project description returned success"
fi
[[ "$(grep -c '^PHASE=' "$tmp/description-edit.log")" -eq 1 ]] ||
fail "planner ran after the project description was modified"

# An unusable description fails before any branch or worktree is created.
: >"$tmp/empty-description.txt"
description_invalid_repo="$(setup_repo description-invalid)"
for invalid_description in "$tmp/no-such-description.txt" "$tmp/empty-description.txt" "$tmp/bin" ""; do
if (
cd "$description_invalid_repo"
PATH="$tmp/bin:/usr/bin:/bin" \
MOCK_AGENT_LOG="$tmp/description-invalid.log" \
./scripts/start-planning.sh --agent codex --model astra --description "$invalid_description" \
>/dev/null 2>&1
); then
fail "unusable project description returned success: '$invalid_description'"
fi
done
[[ ! -e "$tmp/description-invalid-repo-planning-project-bootstrap" ]] ||
fail "unusable project description created a worktree"
if git -C "$description_invalid_repo" show-ref --verify --quiet refs/heads/planning/project-bootstrap; then
fail "unusable project description created a branch"
fi
[[ ! -e "$tmp/description-invalid.log" ]] || fail "an agent started despite an unusable project description"

echo "start-planning tests passed"
Loading