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
64 changes: 16 additions & 48 deletions devloop
Original file line number Diff line number Diff line change
Expand Up @@ -5307,9 +5307,8 @@ spec_command() {
skill-path) printf '%s\n' "$skill"; return ;;
esac

local context today prompt spec_dir target_path target_dir
local context prompt spec_dir target_path target_dir
context="$(resolve_spec_context ${context_items[@]+"${context_items[@]}"})"
today="$(date +%F)"
spec_dir="$(devloop_spec_dir)"
if [ -n "$output" ]; then
target_path="$(absolute_path "$output")" || return 1
Expand All @@ -5323,18 +5322,15 @@ spec_command() {
target_dir="$(absolute_dir_path "$spec_dir")" || return 1
fi

prompt="$(spec_prompt "$context" "$target_path" "$force" "$skill" "$today" "$target_dir")"
prompt="$(spec_prompt "$context" "$target_path" "$force" "$skill" "$target_dir")"
printf 'agent: %s\n' "$(agent_label "$agent")"
printf 'mode: interactive spec session\n'
printf 'write: %s\n' "$target_dir"
launch_spec_agent "$agent" "$prompt" "$target_dir"
}

resolve_spec_context() {
if [ "$#" -eq 0 ]; then
printf '%s\n' "No source material was provided. Use the cold-start interview path in the bundled skill to discover intent before writing the spec."
return
fi
if [ "$#" -eq 0 ]; then return; fi
local first=true
local item file
for item in "$@"; do
Expand All @@ -5345,7 +5341,7 @@ resolve_spec_context() {
printf 'Source file: %s\n\n' "$file"
cat "$file"
else
printf 'Context:\n%s\n' "$item"
printf '%s\n' "$item"
fi
done
}
Expand All @@ -5355,51 +5351,23 @@ spec_prompt() {
local output="$2"
local force="$3"
local skill="$4"
local today="$5"
local target_dir="$6"
local target_line overwrite_line
local target_dir="$5"
local target_line
if [ -n "$output" ]; then
target_line="Requested spec path: $output"
target_line="Output path: $output"
else
target_line="Requested spec path: choose $target_dir/$today-<slug>.md"
target_line="Output directory: $target_dir"
fi

printf "Use the installed \`devloop-spec\` skill.\n"
printf 'If unavailable, read the bundled skill at: %s\n\n' "$skill"
printf '%s\n' "$target_line"
if [ "$force" = true ]; then
overwrite_line="Overwrite policy: overwriting the requested path is allowed."
else
overwrite_line="Overwrite policy: do not overwrite an existing spec; choose a non-conflicting path instead."
printf '\n%s\n' "Overwrite allowed: true"
fi
if [ -n "$context" ]; then
printf '\nContext:\n%s\n' "$context"
fi
cat <<EOF
Skill: use the installed devloop-spec skill.

You are running inside an interactive agent session launched by devloop.
You own the interview and the spec text. Devloop owns the implementation,
review, PR, and report loop after the spec file exists.

Current date: $today
Spec directory: $target_dir
$target_line
$overwrite_line

If the source context is missing, too thin, ambiguous, spans multiple possible
implementation slices, or has open questions that could change the work, follow
the skill's interview path before drafting. Do not turn a conversation,
artifact bundle, or notes into a spec until scope and acceptance criteria are
clear. Ask one question at a time until the spec can be written without invented
requirements.

When ready, write exactly one devloop-ready markdown spec to the requested path.
After writing the spec, tell the user the path and offer to continue with:

devloop --create-pr <spec path>

Do not implement the change directly in this session unless the user explicitly
asks you to leave the devloop flow.

Bundled skill path, for fallback only: $skill

Context:
$context
EOF
}

launch_spec_agent() {
Expand Down
28 changes: 21 additions & 7 deletions scripts/devloop_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2784,13 +2784,19 @@ repo_specs_real="$(cd "$repo_specs" && pwd -P)"
contains "$(cat /tmp/devloop-spec-test.out)" "interactive spec session" "spec command"
contains "$(cat /tmp/devloop-spec-test.out)" "write: $repo_specs_real" "spec command target"
if [ -f "$repo_specs/$(date +%F)-shell-migration-spec.md" ]; then fail "spec command wrote the spec instead of launching the agent"; fi
contains "$(cat /tmp/devloop-spec-agent-prompt.txt)" "Skill: use the installed devloop-spec skill." "spec prompt skill"
contains "$(cat /tmp/devloop-spec-agent-prompt.txt)" "Keep devloop as Bash." "spec prompt"
contains "$(cat /tmp/devloop-spec-agent-prompt.txt)" "Requested spec path: choose $repo_specs_real/$(date +%F)-<slug>.md" "spec prompt configured output"
contains "$(cat /tmp/devloop-spec-agent-prompt.txt)" "has open questions that could change the work" "spec prompt interview gate"
contains "$(cat /tmp/devloop-spec-agent-prompt.txt)" "Do not turn a conversation" "spec prompt interview gate"
contains "$(cat /tmp/devloop-spec-agent-prompt.txt)" "Devloop owns the implementation" "spec prompt ownership"
contains "$(cat /tmp/devloop-spec-agent-prompt.txt)" "devloop --create-pr <spec path>" "spec prompt handoff"
spec_prompt_text="$(cat /tmp/devloop-spec-agent-prompt.txt)"
expected_spec_prompt="$(printf "Use the installed \`devloop-spec\` skill.\nIf unavailable, read the bundled skill at: %s/skills/devloop-spec/SKILL.md\n\nOutput directory: %s\n\nContext:\nKeep devloop as Bash." "$REPO_ROOT" "$repo_specs_real")"
equals "$spec_prompt_text" "$expected_spec_prompt" "spec prompt"
contains "$spec_prompt_text" "$REPO_ROOT/skills/devloop-spec/SKILL.md" "spec prompt fallback skill"

(
cd "$repo"
HOME="$spec_home" main spec --agent "$agent" >/tmp/devloop-spec-test.out
)
spec_prompt_text="$(cat /tmp/devloop-spec-agent-prompt.txt)"
expected_spec_prompt="$(printf "Use the installed \`devloop-spec\` skill.\nIf unavailable, read the bundled skill at: %s/skills/devloop-spec/SKILL.md\n\nOutput directory: %s" "$REPO_ROOT" "$repo_specs_real")"
equals "$spec_prompt_text" "$expected_spec_prompt" "cold-start spec prompt"
not_contains "$spec_prompt_text" "Context:" "cold-start spec prompt"
contains "$(absolute_path "$work/absolute-path/nested.md")" "/absolute-path/nested.md" "absolute path"
existing_spec="$repo/existing.md"
existing_spec_real="$(cd "$repo" && pwd -P)/existing.md"
Expand All @@ -2799,6 +2805,14 @@ rm -f /tmp/devloop-spec-agent-prompt.txt
if (cd "$repo" && HOME="$spec_home" main spec --agent "$agent" --output "$existing_spec" "Replace me") >/tmp/devloop-spec-existing.out 2>/tmp/devloop-spec-existing.err; then fail "spec command allowed existing output without force"; fi
contains "$(cat /tmp/devloop-spec-existing.err)" "spec already exists: $existing_spec_real" "spec existing output"
if [ -f /tmp/devloop-spec-agent-prompt.txt ]; then fail "spec command launched agent for existing output"; fi

(
cd "$repo"
HOME="$spec_home" main spec --agent "$agent" --output "$existing_spec" --force "Replace me" >/tmp/devloop-spec-test.out
)
spec_prompt_text="$(cat /tmp/devloop-spec-agent-prompt.txt)"
expected_spec_prompt="$(printf "Use the installed \`devloop-spec\` skill.\nIf unavailable, read the bundled skill at: %s/skills/devloop-spec/SKILL.md\n\nOutput path: %s\n\nOverwrite allowed: true\n\nContext:\nReplace me" "$REPO_ROOT" "$existing_spec_real")"
equals "$spec_prompt_text" "$expected_spec_prompt" "forced spec prompt"
ok "spec launch"

cat > "$fake_bin/codex" <<'AGENT'
Expand Down
Loading