@samahlstrom/forge-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -0
- package/bin/forge.js +2 -0
- package/dist/addons/index.d.ts +25 -0
- package/dist/addons/index.js +139 -0
- package/dist/addons/index.js.map +1 -0
- package/dist/commands/add.d.ts +1 -0
- package/dist/commands/add.js +61 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +177 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/ingest.d.ts +24 -0
- package/dist/commands/ingest.js +316 -0
- package/dist/commands/ingest.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.js +557 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/remove.d.ts +1 -0
- package/dist/commands/remove.js +42 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +48 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/upgrade.d.ts +5 -0
- package/dist/commands/upgrade.js +190 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/detect/features.d.ts +10 -0
- package/dist/detect/features.js +33 -0
- package/dist/detect/features.js.map +1 -0
- package/dist/detect/go.d.ts +3 -0
- package/dist/detect/go.js +38 -0
- package/dist/detect/go.js.map +1 -0
- package/dist/detect/index.d.ts +25 -0
- package/dist/detect/index.js +32 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/detect/node.d.ts +3 -0
- package/dist/detect/node.js +99 -0
- package/dist/detect/node.js.map +1 -0
- package/dist/detect/python.d.ts +3 -0
- package/dist/detect/python.js +86 -0
- package/dist/detect/python.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/render/engine.d.ts +8 -0
- package/dist/render/engine.js +71 -0
- package/dist/render/engine.js.map +1 -0
- package/dist/render/merge.d.ts +5 -0
- package/dist/render/merge.js +33 -0
- package/dist/render/merge.js.map +1 -0
- package/dist/utils/fs.d.ts +8 -0
- package/dist/utils/fs.js +42 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/git.d.ts +3 -0
- package/dist/utils/git.js +31 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/hash.d.ts +8 -0
- package/dist/utils/hash.js +22 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/yaml.d.ts +3 -0
- package/dist/utils/yaml.js +12 -0
- package/dist/utils/yaml.js.map +1 -0
- package/package.json +53 -0
- package/templates/addons/beads-dolt-backend/files/dolt-setup.sh +267 -0
- package/templates/addons/beads-dolt-backend/manifest.yaml +13 -0
- package/templates/addons/browser-testing/files/browser-smoke.sh +196 -0
- package/templates/addons/browser-testing/files/visual-qa.md +103 -0
- package/templates/addons/browser-testing/manifest.yaml +20 -0
- package/templates/addons/compliance-hipaa/files/hipaa-checks.sh +184 -0
- package/templates/addons/compliance-hipaa/files/hipaa-context.md +91 -0
- package/templates/addons/compliance-hipaa/manifest.yaml +15 -0
- package/templates/addons/compliance-soc2/files/soc2-checks.sh +232 -0
- package/templates/addons/compliance-soc2/files/soc2-context.md +147 -0
- package/templates/addons/compliance-soc2/manifest.yaml +15 -0
- package/templates/core/CLAUDE.md.hbs +70 -0
- package/templates/core/agents/architect.md.hbs +68 -0
- package/templates/core/agents/backend.md.hbs +27 -0
- package/templates/core/agents/frontend.md.hbs +25 -0
- package/templates/core/agents/quality.md.hbs +40 -0
- package/templates/core/agents/security.md.hbs +53 -0
- package/templates/core/context/project.md.hbs +60 -0
- package/templates/core/forge.yaml.hbs +69 -0
- package/templates/core/hooks/post-edit.sh.hbs +8 -0
- package/templates/core/hooks/pre-edit.sh.hbs +41 -0
- package/templates/core/hooks/session-start.sh.hbs +34 -0
- package/templates/core/pipeline/classify.sh.hbs +159 -0
- package/templates/core/pipeline/decompose.md.hbs +100 -0
- package/templates/core/pipeline/deliver.sh.hbs +171 -0
- package/templates/core/pipeline/execute.md.hbs +138 -0
- package/templates/core/pipeline/intake.sh.hbs +152 -0
- package/templates/core/pipeline/orchestrator.sh.hbs +361 -0
- package/templates/core/pipeline/verify.sh.hbs +160 -0
- package/templates/core/settings.json.hbs +55 -0
- package/templates/core/skill-creator.md.hbs +151 -0
- package/templates/core/skill-deliver.md.hbs +46 -0
- package/templates/core/skill-ingest.md.hbs +245 -0
- package/templates/presets/go/stack.md.hbs +133 -0
- package/templates/presets/python-fastapi/stack.md.hbs +101 -0
- package/templates/presets/react-next-ts/stack.md.hbs +77 -0
- package/templates/presets/sveltekit-ts/stack.md.hbs +116 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Architect Agent: Task Decomposition
|
|
2
|
+
|
|
3
|
+
You are the **architect agent** for Forge. Your job is to break a work item into parallel-safe subtasks organized into execution waves.
|
|
4
|
+
|
|
5
|
+
## Input
|
|
6
|
+
|
|
7
|
+
**Task ID:** `{{bead_id}}`
|
|
8
|
+
**Title:** {{title}}
|
|
9
|
+
**Description:**
|
|
10
|
+
{{description}}
|
|
11
|
+
|
|
12
|
+
**Risk Tier:** {{tier}}
|
|
13
|
+
**Mode:** {{mode}}
|
|
14
|
+
|
|
15
|
+
### Project Context
|
|
16
|
+
Read these files for architectural understanding:
|
|
17
|
+
{{#each context_files}}
|
|
18
|
+
- `{{this}}`
|
|
19
|
+
{{/each}}
|
|
20
|
+
|
|
21
|
+
## Your Task
|
|
22
|
+
|
|
23
|
+
Analyze the work description and produce a decomposition plan. You must output **valid JSON** matching the schema below.
|
|
24
|
+
|
|
25
|
+
## Constraints
|
|
26
|
+
|
|
27
|
+
1. **Max subtasks:** 8 (prefer fewer; most work needs 2-4)
|
|
28
|
+
2. **Max waves:** 4 (prefer fewer)
|
|
29
|
+
3. **No circular dependencies:** If A depends on B, B cannot depend on A
|
|
30
|
+
4. **No file conflicts within a wave:** Two subtasks in the same wave CANNOT modify the same file
|
|
31
|
+
5. **Each subtask must be independently verifiable:** It should compile/typecheck on its own after the wave completes
|
|
32
|
+
6. **Respect the risk tier:**
|
|
33
|
+
- T1: Minimal decomposition needed; 1-2 subtasks is fine
|
|
34
|
+
- T2: Break along service/module boundaries
|
|
35
|
+
- T3: Isolate security-critical changes into their own subtask with explicit verification
|
|
36
|
+
7. **Agent assignment:** Each subtask gets an agent type:
|
|
37
|
+
- `code` — writes implementation code
|
|
38
|
+
- `test` — writes tests
|
|
39
|
+
- `docs` — writes documentation
|
|
40
|
+
- `config` — modifies configuration files
|
|
41
|
+
|
|
42
|
+
## Analysis Steps
|
|
43
|
+
|
|
44
|
+
Before producing output, think through:
|
|
45
|
+
|
|
46
|
+
1. **What files will be touched?** List every file that needs to change.
|
|
47
|
+
2. **What are the dependency relationships?** Which changes must happen before others?
|
|
48
|
+
3. **Where are the file conflicts?** Group non-conflicting changes into waves.
|
|
49
|
+
4. **What is the verification for each subtask?** How do you know it worked?
|
|
50
|
+
|
|
51
|
+
## Output Schema
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"analysis": {
|
|
56
|
+
"files_affected": ["src/lib/foo.ts", "src/routes/bar/+page.svelte"],
|
|
57
|
+
"dependency_graph": "A -> B means B depends on A",
|
|
58
|
+
"risk_notes": "Any special concerns for this tier"
|
|
59
|
+
},
|
|
60
|
+
"subtasks": [
|
|
61
|
+
{
|
|
62
|
+
"id": "sub-1",
|
|
63
|
+
"title": "Short description of the subtask",
|
|
64
|
+
"agent": "code",
|
|
65
|
+
"files": ["src/lib/foo.ts", "src/lib/foo.test.ts"],
|
|
66
|
+
"dependsOn": [],
|
|
67
|
+
"verification": "npm run check passes; foo.test.ts passes",
|
|
68
|
+
"instructions": "Detailed instructions for what the agent should do. Be specific about function signatures, data shapes, and integration points."
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"waves": [
|
|
72
|
+
{
|
|
73
|
+
"id": "wave-1",
|
|
74
|
+
"subtasks": ["sub-1", "sub-2"],
|
|
75
|
+
"gate": "typecheck"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "wave-2",
|
|
79
|
+
"subtasks": ["sub-3"],
|
|
80
|
+
"gate": "typecheck + test"
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"verification_plan": {
|
|
84
|
+
"after_all_waves": "Full test suite, lint, typecheck",
|
|
85
|
+
"manual_checks": ["Describe any checks that need human verification"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Rules for Good Decomposition
|
|
91
|
+
|
|
92
|
+
- **Prefer wide waves over deep chains.** 3 subtasks in wave-1 is better than 3 sequential waves of 1 subtask each.
|
|
93
|
+
- **Tests go in the same wave as the code they test** (same subtask if the file set is small, separate subtask if large).
|
|
94
|
+
- **Type definitions and interfaces go in wave-1.** Downstream code depends on them.
|
|
95
|
+
- **Database migrations go in their own subtask** in wave-1, before any code that uses the new schema.
|
|
96
|
+
- **Each subtask's `instructions` field should be detailed enough** that an agent with no prior context can execute it. Include: what to create/modify, expected function signatures, data flow, and how it integrates with existing code.
|
|
97
|
+
|
|
98
|
+
## Output
|
|
99
|
+
|
|
100
|
+
Respond with **only** the JSON object. No markdown fences, no explanation, no commentary. Just the JSON.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# forge pipeline — deliver: branch, commit, push, PR
|
|
3
|
+
# Generated by: forge init
|
|
4
|
+
# Uses bd (steveyegge/beads) for task tracking
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
FORGE_DIR=".forge"
|
|
8
|
+
PROMPTS_DIR="${FORGE_DIR}/pipeline"
|
|
9
|
+
|
|
10
|
+
TASK_ID="${1:-}"
|
|
11
|
+
PR_BODY_FILE="${2:-}"
|
|
12
|
+
|
|
13
|
+
if [[ -z "$TASK_ID" ]]; then
|
|
14
|
+
echo '{"error":"Usage: deliver.sh <task-id> [pr-body-file]"}' >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# --- Read task state from bd ---
|
|
19
|
+
|
|
20
|
+
TASK_JSON=$(bd show "$TASK_ID" --json 2>/dev/null)
|
|
21
|
+
TITLE=$(echo "$TASK_JSON" | jq -r '.title // "untitled"')
|
|
22
|
+
DESCRIPTION=$(echo "$TASK_JSON" | jq -r '.description // ""')
|
|
23
|
+
|
|
24
|
+
# Extract tier and mode from labels
|
|
25
|
+
TIER=$(echo "$TASK_JSON" | jq -r '.labels[]? // empty' | grep "^tier:" | head -1 | sed 's/^tier://')
|
|
26
|
+
TIER="${TIER:-T1}"
|
|
27
|
+
MODE=$(echo "$TASK_JSON" | jq -r '.labels[]? // empty' | grep "^mode:" | head -1 | sed 's/^mode://')
|
|
28
|
+
MODE="${MODE:-normal}"
|
|
29
|
+
|
|
30
|
+
# Detect modified files from git
|
|
31
|
+
MODIFIED_FILES=$(git diff --name-only HEAD 2>/dev/null || true)
|
|
32
|
+
if [[ -z "$MODIFIED_FILES" ]]; then
|
|
33
|
+
MODIFIED_FILES=$(git diff --cached --name-only 2>/dev/null || true)
|
|
34
|
+
fi
|
|
35
|
+
if [[ -z "$MODIFIED_FILES" ]]; then
|
|
36
|
+
MODIFIED_FILES=$(git status --porcelain | grep -v '^\?' | awk '{print $2}')
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
if [[ -z "$MODIFIED_FILES" ]]; then
|
|
40
|
+
echo '{"error":"No modified files found to deliver"}' >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# --- Generate slug from title ---
|
|
45
|
+
|
|
46
|
+
slugify() {
|
|
47
|
+
echo "$1" | tr '[:upper:]' '[:lower:]' | \
|
|
48
|
+
sed 's/[^a-z0-9]/-/g' | \
|
|
49
|
+
sed 's/--*/-/g' | \
|
|
50
|
+
sed 's/^-//' | \
|
|
51
|
+
sed 's/-$//' | \
|
|
52
|
+
cut -c1-40
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
SLUG=$(slugify "$TITLE")
|
|
56
|
+
BRANCH="feature/${TASK_ID}-${SLUG}"
|
|
57
|
+
|
|
58
|
+
# --- Phase 1: Branch + commit + push ---
|
|
59
|
+
|
|
60
|
+
if [[ -z "$PR_BODY_FILE" ]]; then
|
|
61
|
+
# Create branch
|
|
62
|
+
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
|
|
63
|
+
if [[ "$CURRENT_BRANCH" != "$BRANCH" ]]; then
|
|
64
|
+
git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH" 2>/dev/null || {
|
|
65
|
+
echo "{\"error\":\"Failed to create or switch to branch ${BRANCH}\"}" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
}
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# Stage files explicitly
|
|
71
|
+
while IFS= read -r file; do
|
|
72
|
+
[[ -z "$file" ]] && continue
|
|
73
|
+
if [[ -f "$file" ]]; then
|
|
74
|
+
git add "$file"
|
|
75
|
+
fi
|
|
76
|
+
done <<< "$MODIFIED_FILES"
|
|
77
|
+
|
|
78
|
+
# Build commit message
|
|
79
|
+
local_type="feat"
|
|
80
|
+
if echo "$TITLE" | grep -qiE '(fix|bug|patch|repair|correct)'; then
|
|
81
|
+
local_type="fix"
|
|
82
|
+
elif echo "$TITLE" | grep -qiE '(refactor|cleanup|reorganize)'; then
|
|
83
|
+
local_type="refactor"
|
|
84
|
+
elif echo "$TITLE" | grep -qiE '(doc|readme|comment)'; then
|
|
85
|
+
local_type="docs"
|
|
86
|
+
elif echo "$TITLE" | grep -qiE '(test|spec)'; then
|
|
87
|
+
local_type="test"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
COMMIT_MSG="${local_type}(${SLUG}): ${TITLE}
|
|
91
|
+
|
|
92
|
+
Task: ${TASK_ID}
|
|
93
|
+
Risk: ${TIER}"
|
|
94
|
+
|
|
95
|
+
git commit -m "$COMMIT_MSG" || {
|
|
96
|
+
echo "{\"error\":\"Commit failed\"}" >&2
|
|
97
|
+
exit 1
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Push branch
|
|
101
|
+
git push -u origin "$BRANCH" 2>/dev/null || {
|
|
102
|
+
echo "{\"error\":\"Push failed. You may need to set up remote.\"}" >&2
|
|
103
|
+
exit 1
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# PAUSE: ask LLM to write PR body
|
|
107
|
+
local task_dir="${FORGE_DIR}/pipeline/runs/${TASK_ID}"
|
|
108
|
+
mkdir -p "$task_dir"
|
|
109
|
+
PR_BODY_OUTPUT="${task_dir}/pr-body.md"
|
|
110
|
+
|
|
111
|
+
jq -n \
|
|
112
|
+
--arg status "PAUSE" \
|
|
113
|
+
--arg task "write-pr-body" \
|
|
114
|
+
--arg prompt_file "${PROMPTS_DIR}/pr-body-prompt.md" \
|
|
115
|
+
--arg output_file "$PR_BODY_OUTPUT" \
|
|
116
|
+
--arg branch "$BRANCH" \
|
|
117
|
+
--arg resume "bash ${FORGE_DIR}/pipeline/deliver.sh ${TASK_ID} ${PR_BODY_OUTPUT}" \
|
|
118
|
+
--argjson context '["{{stackFile}}","{{projectFile}}"]' \
|
|
119
|
+
'{status:$status, task:$task, prompt_file:$prompt_file, output_file:$output_file, branch:$branch, context:$context, resume:$resume}'
|
|
120
|
+
exit 0
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# --- Phase 2: Create PR with body ---
|
|
124
|
+
|
|
125
|
+
if [[ ! -f "$PR_BODY_FILE" ]]; then
|
|
126
|
+
echo "{\"error\":\"PR body file not found: ${PR_BODY_FILE}\"}" >&2
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
PR_BODY=$(cat "$PR_BODY_FILE")
|
|
131
|
+
|
|
132
|
+
PR_TITLE="${TITLE}"
|
|
133
|
+
if [[ ${#PR_TITLE} -gt 70 ]]; then
|
|
134
|
+
PR_TITLE=$(echo "$PR_TITLE" | cut -c1-67)"..."
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# Add risk badge
|
|
138
|
+
RISK_BADGE=""
|
|
139
|
+
case "$TIER" in
|
|
140
|
+
T1) RISK_BADGE="<!-- risk:T1 --> **Risk: T1** (Low)" ;;
|
|
141
|
+
T2) RISK_BADGE="<!-- risk:T2 --> **Risk: T2** (Moderate)" ;;
|
|
142
|
+
T3) RISK_BADGE="<!-- risk:T3 --> **Risk: T3** (Critical)" ;;
|
|
143
|
+
esac
|
|
144
|
+
|
|
145
|
+
FULL_BODY="${RISK_BADGE}
|
|
146
|
+
|
|
147
|
+
${PR_BODY}
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
Task: \`${TASK_ID}\`"
|
|
151
|
+
|
|
152
|
+
PR_URL=$(gh pr create \
|
|
153
|
+
--title "$PR_TITLE" \
|
|
154
|
+
--body "$FULL_BODY" \
|
|
155
|
+
--head "$BRANCH" 2>/dev/null) || {
|
|
156
|
+
echo "{\"error\":\"gh pr create failed. Is gh authenticated?\"}" >&2
|
|
157
|
+
exit 1
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Add labels based on tier
|
|
161
|
+
case "$TIER" in
|
|
162
|
+
T3) gh pr edit "$PR_URL" --add-label "critical,security-review" 2>/dev/null || true ;;
|
|
163
|
+
T2) gh pr edit "$PR_URL" --add-label "needs-review" 2>/dev/null || true ;;
|
|
164
|
+
esac
|
|
165
|
+
|
|
166
|
+
jq -n \
|
|
167
|
+
--arg pr_url "$PR_URL" \
|
|
168
|
+
--arg branch "$BRANCH" \
|
|
169
|
+
--arg task_id "$TASK_ID" \
|
|
170
|
+
--arg tier "$TIER" \
|
|
171
|
+
'{pr_url:$pr_url, branch:$branch, task_id:$task_id, tier:$tier}'
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Execution Dispatcher: Wave-by-Wave Agent Orchestration
|
|
2
|
+
|
|
3
|
+
You are the **execution dispatcher** for Forge. Your job is to execute a decomposition plan by launching agents wave-by-wave, verifying between waves, and tracking progress via `bd`.
|
|
4
|
+
|
|
5
|
+
## Input
|
|
6
|
+
|
|
7
|
+
**Task ID:** `{{bead_id}}`
|
|
8
|
+
**Title:** {{title}}
|
|
9
|
+
**Risk Tier:** {{tier}}
|
|
10
|
+
|
|
11
|
+
### Decomposition Plan
|
|
12
|
+
The decomposition is at: `{{decomposition_file}}`
|
|
13
|
+
|
|
14
|
+
Read it now. It contains `subtasks[]` and `waves[]`.
|
|
15
|
+
|
|
16
|
+
### Project Context
|
|
17
|
+
{{#each context_files}}
|
|
18
|
+
- `{{this}}`
|
|
19
|
+
{{/each}}
|
|
20
|
+
|
|
21
|
+
## Execution Protocol
|
|
22
|
+
|
|
23
|
+
For each wave in order:
|
|
24
|
+
|
|
25
|
+
### 1. Pre-Wave Setup
|
|
26
|
+
|
|
27
|
+
- Read the wave definition to get the list of subtask IDs
|
|
28
|
+
- For each subtask in this wave, read its `files[]`, `instructions`, and `verification`
|
|
29
|
+
|
|
30
|
+
### 2. Execute Subtasks in Parallel
|
|
31
|
+
|
|
32
|
+
For each subtask in the wave, launch a subagent with this context:
|
|
33
|
+
|
|
34
|
+
**Subagent prompt template:**
|
|
35
|
+
```
|
|
36
|
+
You are a {{subtask.agent}} agent. Your task:
|
|
37
|
+
|
|
38
|
+
Title: {{subtask.title}}
|
|
39
|
+
|
|
40
|
+
Instructions:
|
|
41
|
+
{{subtask.instructions}}
|
|
42
|
+
|
|
43
|
+
Files to modify:
|
|
44
|
+
{{#each subtask.files}}
|
|
45
|
+
- {{this}}
|
|
46
|
+
{{/each}}
|
|
47
|
+
|
|
48
|
+
Verification: {{subtask.verification}}
|
|
49
|
+
|
|
50
|
+
Rules:
|
|
51
|
+
- Only modify the files listed above
|
|
52
|
+
- Follow the project conventions in the context files
|
|
53
|
+
- After making changes, verify: {{subtask.verification}}
|
|
54
|
+
- If you encounter an error you cannot resolve, output:
|
|
55
|
+
{"status": "blocked", "subtask": "{{subtask.id}}", "error": "<description>"}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Each subagent receives:
|
|
59
|
+
1. The subagent prompt above
|
|
60
|
+
2. The project context files (stack.md, project.md)
|
|
61
|
+
3. The agent.md file matching its agent type (if it exists)
|
|
62
|
+
|
|
63
|
+
### 3. Post-Wave Verification Gate
|
|
64
|
+
|
|
65
|
+
After ALL subtasks in a wave complete:
|
|
66
|
+
|
|
67
|
+
1. **Run typecheck:** `{{commands.typecheck}}`
|
|
68
|
+
2. **If the wave definition has `gate: "typecheck + test"`**, also run: `{{commands.test}}`
|
|
69
|
+
|
|
70
|
+
### 4. Handle Failures
|
|
71
|
+
|
|
72
|
+
If the verification gate fails after a wave:
|
|
73
|
+
|
|
74
|
+
1. **Identify the breaking subtask:**
|
|
75
|
+
- Check which files were modified in this wave
|
|
76
|
+
- Run typecheck/test with verbose output
|
|
77
|
+
- Match errors to specific files and subtasks
|
|
78
|
+
|
|
79
|
+
2. **Retry the breaking subtask:**
|
|
80
|
+
- Provide the subagent with the error output as additional context
|
|
81
|
+
- Include the failing test output or typecheck errors
|
|
82
|
+
- Limit to 2 retries per subtask
|
|
83
|
+
|
|
84
|
+
3. **If retry fails after 2 attempts:**
|
|
85
|
+
- Revert the breaking subtask's file changes: `git checkout -- <files>`
|
|
86
|
+
- Continue to the next wave (the breaking subtask becomes a "deferred" item)
|
|
87
|
+
- Track deferred subtasks for the PR summary
|
|
88
|
+
|
|
89
|
+
## Progress Reporting
|
|
90
|
+
|
|
91
|
+
After each wave, output a progress update:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"wave": "wave-1",
|
|
96
|
+
"status": "complete",
|
|
97
|
+
"subtasks_completed": ["sub-1", "sub-2"],
|
|
98
|
+
"subtasks_failed": [],
|
|
99
|
+
"subtasks_deferred": [],
|
|
100
|
+
"verification": "passed",
|
|
101
|
+
"files_modified": ["src/lib/foo.ts", "src/lib/bar.ts"]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Completion
|
|
106
|
+
|
|
107
|
+
After all waves are done:
|
|
108
|
+
|
|
109
|
+
1. Run the full verification suite:
|
|
110
|
+
- `{{commands.typecheck}}`
|
|
111
|
+
- `{{commands.lint}}`
|
|
112
|
+
- `{{commands.test}}`
|
|
113
|
+
|
|
114
|
+
2. Compile the execution summary:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"status": "complete",
|
|
119
|
+
"waves_executed": 2,
|
|
120
|
+
"subtasks_completed": ["sub-1", "sub-2", "sub-3"],
|
|
121
|
+
"subtasks_deferred": [],
|
|
122
|
+
"files_modified": ["list", "of", "all", "files"],
|
|
123
|
+
"verification": {
|
|
124
|
+
"typecheck": "passed",
|
|
125
|
+
"lint": "passed",
|
|
126
|
+
"test": "passed"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
3. Write the summary to: `.forge/pipeline/runs/{{bead_id}}/execution.json`
|
|
132
|
+
|
|
133
|
+
## Rules
|
|
134
|
+
|
|
135
|
+
- **Never skip a wave.** Execute them in order.
|
|
136
|
+
- **Never modify files not listed in a subtask's `files[]` array.**
|
|
137
|
+
- **If a subtask has `dependsOn` entries not in its wave, those were already completed in prior waves.** You can assume their output exists.
|
|
138
|
+
- **Keep each subagent focused.** It gets only its subtask, not the full decomposition.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# forge pipeline — intake: parse input and score quality
|
|
3
|
+
# Generated by: forge init
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
# --- Quality Scoring ---
|
|
7
|
+
# Scores a work description from 0.0 to 1.0 based on:
|
|
8
|
+
# What (25%) — Does it describe what to build/fix?
|
|
9
|
+
# Why (15%) — Does it explain why this is needed?
|
|
10
|
+
# Where (15%) — Does it mention files, components, or areas?
|
|
11
|
+
# Scope (15%) — Is the scope bounded (not "refactor everything")?
|
|
12
|
+
# Layers (20%)— Does it mention affected layers (UI, API, DB, etc.)?
|
|
13
|
+
# Criteria (10%) — Does it include acceptance criteria or done conditions?
|
|
14
|
+
|
|
15
|
+
score_quality() {
|
|
16
|
+
local desc="$1"
|
|
17
|
+
local desc_lower
|
|
18
|
+
desc_lower=$(echo "$desc" | tr '[:upper:]' '[:lower:]')
|
|
19
|
+
local total=0
|
|
20
|
+
|
|
21
|
+
# What (25 pts): presence of action verbs and object nouns
|
|
22
|
+
local what_score=0
|
|
23
|
+
if echo "$desc_lower" | grep -qE '(add|create|build|implement|fix|update|remove|delete|refactor|migrate|change|replace|move|rename|extract|split|merge)'; then
|
|
24
|
+
what_score=$((what_score + 15))
|
|
25
|
+
fi
|
|
26
|
+
if echo "$desc_lower" | grep -qE '(component|page|endpoint|api|function|service|hook|store|model|schema|table|route|handler|middleware|view|controller|module)'; then
|
|
27
|
+
what_score=$((what_score + 10))
|
|
28
|
+
fi
|
|
29
|
+
total=$((total + what_score))
|
|
30
|
+
|
|
31
|
+
# Why (15 pts): rationale keywords
|
|
32
|
+
local why_score=0
|
|
33
|
+
if echo "$desc_lower" | grep -qE '(because|so that|in order to|to enable|to fix|to prevent|to improve|to support|to allow|currently|problem|issue|bug|broken|incorrect|missing|needed)'; then
|
|
34
|
+
why_score=15
|
|
35
|
+
fi
|
|
36
|
+
total=$((total + why_score))
|
|
37
|
+
|
|
38
|
+
# Where (15 pts): file paths, component names, or area references
|
|
39
|
+
local where_score=0
|
|
40
|
+
if echo "$desc_lower" | grep -qE '(\.[a-z]{1,4}$|/[a-z]|src/|lib/|components/|pages/|routes/|api/|server/|client/|services/)'; then
|
|
41
|
+
where_score=$((where_score + 10))
|
|
42
|
+
fi
|
|
43
|
+
if echo "$desc_lower" | grep -qE '(in the|on the|at the|within|inside|under|header|footer|sidebar|navbar|modal|form|table|list|card|dashboard|settings|profile|login|signup)'; then
|
|
44
|
+
where_score=$((where_score + 5))
|
|
45
|
+
fi
|
|
46
|
+
total=$((total + where_score))
|
|
47
|
+
|
|
48
|
+
# Scope (15 pts): bounded descriptions score higher
|
|
49
|
+
local scope_score=10 # default: assume moderate scope
|
|
50
|
+
if echo "$desc_lower" | grep -qE '(all|every|entire|whole|everything|refactor the entire|rewrite)'; then
|
|
51
|
+
scope_score=0 # unbounded = bad
|
|
52
|
+
fi
|
|
53
|
+
if echo "$desc_lower" | grep -qE '(only|just|single|specific|one|the [a-z]+ (component|page|endpoint|function))'; then
|
|
54
|
+
scope_score=15 # well-bounded = good
|
|
55
|
+
fi
|
|
56
|
+
total=$((total + scope_score))
|
|
57
|
+
|
|
58
|
+
# Layers (20 pts): mentions of architectural layers
|
|
59
|
+
local layers_score=0
|
|
60
|
+
local layer_count=0
|
|
61
|
+
for layer in "ui" "frontend" "client" "backend" "server" "api" "database" "db" "schema" "migration" "state" "store" "style" "css" "test" "auth" "middleware"; do
|
|
62
|
+
if echo "$desc_lower" | grep -qw "$layer"; then
|
|
63
|
+
layer_count=$((layer_count + 1))
|
|
64
|
+
fi
|
|
65
|
+
done
|
|
66
|
+
if [[ $layer_count -ge 3 ]]; then
|
|
67
|
+
layers_score=20
|
|
68
|
+
elif [[ $layer_count -ge 2 ]]; then
|
|
69
|
+
layers_score=15
|
|
70
|
+
elif [[ $layer_count -ge 1 ]]; then
|
|
71
|
+
layers_score=10
|
|
72
|
+
fi
|
|
73
|
+
total=$((total + layers_score))
|
|
74
|
+
|
|
75
|
+
# Criteria (10 pts): acceptance criteria or done conditions
|
|
76
|
+
local criteria_score=0
|
|
77
|
+
if echo "$desc_lower" | grep -qE '(should|must|expect|when .* then|given .* when|acceptance|criteria|done when|verify that|ensure that|assert)'; then
|
|
78
|
+
criteria_score=10
|
|
79
|
+
fi
|
|
80
|
+
total=$((total + criteria_score))
|
|
81
|
+
|
|
82
|
+
# Normalize to 0.0-1.0
|
|
83
|
+
echo "scale=2; $total / 100" | bc | sed 's/^\./0./'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# --- Input Parsing ---
|
|
87
|
+
|
|
88
|
+
MODE="normal"
|
|
89
|
+
SOURCE="text"
|
|
90
|
+
ISSUE_NUMBER=""
|
|
91
|
+
DESCRIPTION=""
|
|
92
|
+
TITLE=""
|
|
93
|
+
|
|
94
|
+
while [[ $# -gt 0 ]]; do
|
|
95
|
+
case "$1" in
|
|
96
|
+
--quick)
|
|
97
|
+
MODE="quick"; shift ;;
|
|
98
|
+
--hotfix)
|
|
99
|
+
MODE="hotfix"; shift ;;
|
|
100
|
+
--issue)
|
|
101
|
+
ISSUE_NUMBER="$2"; SOURCE="issue"; shift 2 ;;
|
|
102
|
+
*)
|
|
103
|
+
if [[ -z "$DESCRIPTION" ]]; then
|
|
104
|
+
DESCRIPTION="$1"
|
|
105
|
+
else
|
|
106
|
+
DESCRIPTION="${DESCRIPTION} $1"
|
|
107
|
+
fi
|
|
108
|
+
shift ;;
|
|
109
|
+
esac
|
|
110
|
+
done
|
|
111
|
+
|
|
112
|
+
# Fetch from GitHub issue if --issue
|
|
113
|
+
if [[ "$SOURCE" == "issue" && -n "$ISSUE_NUMBER" ]]; then
|
|
114
|
+
if ! command -v gh &>/dev/null; then
|
|
115
|
+
echo '{"error":"gh CLI not installed, cannot fetch issue"}' >&2
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
issue_json=$(gh issue view "$ISSUE_NUMBER" --json title,body 2>/dev/null) || {
|
|
120
|
+
echo '{"error":"Failed to fetch issue '"$ISSUE_NUMBER"'"}' >&2
|
|
121
|
+
exit 1
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
TITLE=$(echo "$issue_json" | jq -r '.title // ""')
|
|
125
|
+
issue_body=$(echo "$issue_json" | jq -r '.body // ""')
|
|
126
|
+
DESCRIPTION="${TITLE}. ${issue_body}"
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
if [[ -z "$DESCRIPTION" ]]; then
|
|
130
|
+
echo '{"error":"No work description provided. Usage: forge work \"<description>\" | --issue N"}' >&2
|
|
131
|
+
exit 1
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
# Generate title from description if not set
|
|
135
|
+
if [[ -z "$TITLE" ]]; then
|
|
136
|
+
# Take first sentence or first 80 chars
|
|
137
|
+
TITLE=$(echo "$DESCRIPTION" | head -1 | cut -c1-80)
|
|
138
|
+
# Remove trailing period if present
|
|
139
|
+
TITLE="${TITLE%.}"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# Score quality
|
|
143
|
+
QUALITY_SCORE=$(score_quality "$DESCRIPTION")
|
|
144
|
+
|
|
145
|
+
# Output JSON result
|
|
146
|
+
jq -n \
|
|
147
|
+
--arg title "$TITLE" \
|
|
148
|
+
--arg description "$DESCRIPTION" \
|
|
149
|
+
--arg source "$SOURCE" \
|
|
150
|
+
--arg mode "$MODE" \
|
|
151
|
+
--arg quality_score "$QUALITY_SCORE" \
|
|
152
|
+
'{title:$title, description:$description, source:$source, mode:$mode, quality_score:($quality_score | tonumber)}'
|