diff --git a/.agents/prompts/project-grill.md b/.agents/prompts/project-grill.md index 5644324..2bf160f 100644 --- a/.agents/prompts/project-grill.md +++ b/.agents/prompts/project-grill.md @@ -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, diff --git a/.agents/prompts/project-planner.md b/.agents/prompts/project-planner.md index 1cf3741..e94e4bd 100644 --- a/.agents/prompts/project-planner.md +++ b/.agents/prompts/project-planner.md @@ -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: diff --git a/README.md b/README.md index 531a4b8..d95191b 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/development.md b/docs/development.md index 8125d6b..af20ec7 100644 --- a/docs/development.md +++ b/docs/development.md @@ -97,9 +97,24 @@ idea and completing `docs/repository-setup.md`: The full interface is: ```text -./scripts/start-planning.sh [name] [--agent ] [--model ] +./scripts/start-planning.sh [name] [--description ] [--agent ] [--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: @@ -137,6 +152,7 @@ manually: ```bash cd ../-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 diff --git a/scripts/start-planning.sh b/scripts/start-planning.sh index cb7b372..1a16788 100755 --- a/scripts/start-planning.sh +++ b/scripts/start-planning.sh @@ -6,14 +6,18 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/lib/agent.sh" usage() { cat <] [--model ] +Usage: $0 [name] [--description ] [--agent ] [--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 } @@ -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" @@ -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 @@ -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 || @@ -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 <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' @@ -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"