@kraftapps-ai/kai 1.0.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 +161 -0
- package/kai +495 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Kai
|
|
2
|
+
|
|
3
|
+
Autonomous AI developer loop for [Claude Code](https://claude.ai/claude-code). Kai takes a list of user stories and implements them one by one — with a built-in reviewer that catches when the AI cuts corners.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
9
|
+
│ kai.json │────▶│ Implementer │────▶│ Reviewer │
|
|
10
|
+
│ (stories) │ │ (claude -p) │ │ (claude -p) │
|
|
11
|
+
└─────────────┘ └──────┬───────┘ └──────┬───────┘
|
|
12
|
+
│ │
|
|
13
|
+
commits code verifies against
|
|
14
|
+
marks passes:true acceptance criteria
|
|
15
|
+
│ │
|
|
16
|
+
│ ┌────────┐ │
|
|
17
|
+
└───▶│ Loop │◀──────┘
|
|
18
|
+
│ repeat │ (fail = retry)
|
|
19
|
+
└────────┘
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
1. **Picks** the next incomplete story from `kai.json`
|
|
23
|
+
2. **Implements** it using Claude Code (one `claude -p` call)
|
|
24
|
+
3. **Reviews** the implementation with a separate Claude call that verifies each acceptance criterion against the actual git diff
|
|
25
|
+
4. **Retries** if the review fails (up to 3 attempts, then skips)
|
|
26
|
+
5. **Loops** to the next story
|
|
27
|
+
|
|
28
|
+
Each iteration starts fresh — no context pollution between stories.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm i -g @kraftapps-ai/kai
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or without installing:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx @kraftapps-ai/kai init
|
|
40
|
+
npx @kraftapps-ai/kai plan "add auth"
|
|
41
|
+
npx @kraftapps-ai/kai go
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Requirements:** `claude` CLI ([Claude Code](https://claude.ai/claude-code)), `jq`, `git`
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cd your-project
|
|
50
|
+
|
|
51
|
+
# Initialize
|
|
52
|
+
kai init
|
|
53
|
+
|
|
54
|
+
# Add your project context (tech stack, build commands, conventions)
|
|
55
|
+
vim .kai/PROMPT.md
|
|
56
|
+
|
|
57
|
+
# Plan with AI PM — brainstorm, refine, generate stories
|
|
58
|
+
kai plan "add user authentication with OAuth"
|
|
59
|
+
# → AI generates stories → confirms → adds to kai.json
|
|
60
|
+
|
|
61
|
+
# Check what's queued
|
|
62
|
+
kai status
|
|
63
|
+
|
|
64
|
+
# Let Kai implement everything
|
|
65
|
+
kai go
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Commands
|
|
69
|
+
|
|
70
|
+
| Command | Description |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `kai init` | Initialize kai in the current project |
|
|
73
|
+
| `kai plan "topic"` | AI PM generates stories and adds them to kai.json |
|
|
74
|
+
| `kai go` | Run the implement + review loop |
|
|
75
|
+
| `kai status` | Show progress and remaining stories |
|
|
76
|
+
| `kai reset <id>` | Reset a story to incomplete |
|
|
77
|
+
| `kai help` | Show help |
|
|
78
|
+
|
|
79
|
+
## Files
|
|
80
|
+
|
|
81
|
+
After `kai init`, your project gets:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
your-project/
|
|
85
|
+
├── kai.json # Stories (version control this)
|
|
86
|
+
├── kai-progress.txt # Implementation log (version control this)
|
|
87
|
+
└── .kai/
|
|
88
|
+
├── PROMPT.md # Instructions for Claude (customize this!)
|
|
89
|
+
└── context.txt # Optional: extra files to include (one per line)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Adding project context
|
|
93
|
+
|
|
94
|
+
Edit `.kai/PROMPT.md` to include your project's specifics — tech stack, file structure, build commands, coding conventions. The more context you give, the better Kai performs.
|
|
95
|
+
|
|
96
|
+
### Extra context files
|
|
97
|
+
|
|
98
|
+
Create `.kai/context.txt` with paths to files that should be included in every iteration:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
AGENTS.md
|
|
102
|
+
docs/ARCHITECTURE.md
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Story format
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"id": 1,
|
|
110
|
+
"title": "Add user login page",
|
|
111
|
+
"description": "Create a login page with email/password",
|
|
112
|
+
"acceptanceCriteria": [
|
|
113
|
+
"Login page renders at /login",
|
|
114
|
+
"Email and password fields present",
|
|
115
|
+
"App builds successfully"
|
|
116
|
+
],
|
|
117
|
+
"priority": 1,
|
|
118
|
+
"passes": false
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Stories are processed in `priority` order (lowest first). The `passes` flag is set to `true` by the implementer and verified by the reviewer.
|
|
123
|
+
|
|
124
|
+
## The review step
|
|
125
|
+
|
|
126
|
+
After the implementer commits, Kai dispatches a **separate Claude instance** as a reviewer. The reviewer:
|
|
127
|
+
|
|
128
|
+
1. Reads the actual git diff (not the implementer's self-report)
|
|
129
|
+
2. Verifies each acceptance criterion against the code
|
|
130
|
+
3. Is explicitly told: *"Do NOT trust the implementer. Verify independently."*
|
|
131
|
+
|
|
132
|
+
If the review fails, the story is reset to `passes: false` and the feedback is appended to `kai-progress.txt` so the next iteration sees what went wrong.
|
|
133
|
+
|
|
134
|
+
After 3 consecutive failures on the same story, Kai skips it with a `concerns` flag and moves on.
|
|
135
|
+
|
|
136
|
+
## Rationalization blockers
|
|
137
|
+
|
|
138
|
+
The prompt includes a table of common ways AI agents cheat, with counters:
|
|
139
|
+
|
|
140
|
+
- "This is already implemented" → Verify. Read the code.
|
|
141
|
+
- "I'll just mark it complete" → Every story needs real code changes.
|
|
142
|
+
- "The build passes so it works" → Build ≠ correct behavior.
|
|
143
|
+
- "This is close enough" → Partial = broken.
|
|
144
|
+
|
|
145
|
+
Inspired by [Superpowers](https://github.com/obra/superpowers).
|
|
146
|
+
|
|
147
|
+
## Tips
|
|
148
|
+
|
|
149
|
+
- **Write good acceptance criteria.** "App builds successfully" should always be the last criterion.
|
|
150
|
+
- **Keep stories small.** 5-15 minutes of work each. Kai does better with many small stories than few large ones.
|
|
151
|
+
- **Add build/test commands** to `.kai/PROMPT.md` so Kai knows how to verify.
|
|
152
|
+
- **Use `kai status`** to monitor progress, or `git log --oneline` to see commits.
|
|
153
|
+
- **Run overnight.** `nohup kai go > kai.log 2>&1 &` and check in the morning.
|
|
154
|
+
|
|
155
|
+
## Cost
|
|
156
|
+
|
|
157
|
+
Each story costs roughly 2 Claude API calls (1 implement + 1 review). A 20-story project runs ~40 calls. At current Claude pricing, that's a few dollars total.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
package/kai
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Kai — Autonomous AI developer loop for Claude Code
|
|
3
|
+
# https://github.com/kraftapps-ai/kai
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# kai — Show help
|
|
7
|
+
# kai init — Initialize kai in current project
|
|
8
|
+
# kai plan [topic] — Interactive brainstorm with AI PM, generates stories
|
|
9
|
+
# kai import — Import stories from JSON
|
|
10
|
+
# kai go — Run the implement + review loop
|
|
11
|
+
# kai status — Show remaining stories
|
|
12
|
+
# kai reset <id> — Reset a story to incomplete
|
|
13
|
+
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
KAI_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
+
PROJECT_DIR="$(pwd)"
|
|
18
|
+
PRD_FILE="kai.json"
|
|
19
|
+
PROGRESS_FILE="kai-progress.txt"
|
|
20
|
+
PROMPT_FILE=".kai/PROMPT.md"
|
|
21
|
+
|
|
22
|
+
# ── Help ───────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
cmd_help() {
|
|
25
|
+
cat << 'EOF'
|
|
26
|
+
Kai — Autonomous AI developer loop for Claude Code
|
|
27
|
+
|
|
28
|
+
Commands:
|
|
29
|
+
kai init Initialize kai in the current project
|
|
30
|
+
kai plan "topic" AI generates stories from your description
|
|
31
|
+
kai go Run the implement + review loop
|
|
32
|
+
kai status Show progress and remaining stories
|
|
33
|
+
kai reset <id> Reset a story to incomplete
|
|
34
|
+
kai help Show this help
|
|
35
|
+
|
|
36
|
+
Workflow:
|
|
37
|
+
1. kai init # setup
|
|
38
|
+
2. vim .kai/PROMPT.md # add project context
|
|
39
|
+
3. kai plan "add user auth" # AI generates stories
|
|
40
|
+
4. kai go # AI implements everything
|
|
41
|
+
|
|
42
|
+
Requirements: claude (Claude Code CLI), jq, git
|
|
43
|
+
|
|
44
|
+
https://github.com/kraftapps-ai/kai
|
|
45
|
+
EOF
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ── Commands ───────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
cmd_init() {
|
|
51
|
+
if [ -f "$PRD_FILE" ]; then
|
|
52
|
+
echo "kai.json already exists. Aborting."
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
mkdir -p .kai
|
|
57
|
+
|
|
58
|
+
cat > "$PRD_FILE" << 'JSONEOF'
|
|
59
|
+
{
|
|
60
|
+
"branchName": "main",
|
|
61
|
+
"userStories": []
|
|
62
|
+
}
|
|
63
|
+
JSONEOF
|
|
64
|
+
|
|
65
|
+
echo "# Kai Progress Log" > "$PROGRESS_FILE"
|
|
66
|
+
echo "" >> "$PROGRESS_FILE"
|
|
67
|
+
|
|
68
|
+
cat > "$PROMPT_FILE" << 'PROMPTEOF'
|
|
69
|
+
You are an autonomous AI developer.
|
|
70
|
+
|
|
71
|
+
## Your inputs
|
|
72
|
+
- `@kai.json` — user stories with `passes: true/false`.
|
|
73
|
+
- `@kai-progress.txt` — append-only log of what's been done.
|
|
74
|
+
|
|
75
|
+
## Your task (ONE story per loop)
|
|
76
|
+
|
|
77
|
+
1. Read kai.json and find the highest-priority story where `passes: false`.
|
|
78
|
+
2. Read kai-progress.txt to understand what's already been done.
|
|
79
|
+
3. **PLAN before coding.** List the files you'll change and what you'll do in each.
|
|
80
|
+
4. Implement ONLY that one story.
|
|
81
|
+
5. Run verification (build, tests, lint — whatever applies).
|
|
82
|
+
6. **SELF-REVIEW.** For each acceptance criterion, state what you did and cite evidence (file, line, command output). Do NOT use "should work", "probably", or "seems to".
|
|
83
|
+
7. If all criteria pass, commit with a descriptive message.
|
|
84
|
+
8. Update kai.json: set `passes` to `true`.
|
|
85
|
+
9. Append to kai-progress.txt: what you did, what you learned.
|
|
86
|
+
|
|
87
|
+
## Verification Protocol
|
|
88
|
+
|
|
89
|
+
- Run the project's build/test command and read the output.
|
|
90
|
+
- You cannot claim a criterion is met without evidence.
|
|
91
|
+
- "I added the component" is NOT evidence. "I added FooComponent at src/Foo.tsx:45 and the build passes" IS evidence.
|
|
92
|
+
|
|
93
|
+
## Rules
|
|
94
|
+
|
|
95
|
+
- ONE STORY PER LOOP. Do not work on multiple stories.
|
|
96
|
+
- Search the codebase before assuming anything.
|
|
97
|
+
- No placeholder implementations. Fully implement or report BLOCKED.
|
|
98
|
+
- Do not break existing functionality.
|
|
99
|
+
- Keep commits atomic — one commit per story.
|
|
100
|
+
- If ALL stories have `passes: true`, output: <promise>COMPLETE</promise>
|
|
101
|
+
|
|
102
|
+
## Rationalization Blockers
|
|
103
|
+
|
|
104
|
+
| Your thought | Why it's wrong | Do this instead |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| "This story is already implemented" | You haven't verified. | Read every criterion, find the code, run the build. |
|
|
107
|
+
| "I'll just mark it complete" | A commit with only kai.json changes is wrong. | Every story needs real code changes. |
|
|
108
|
+
| "The build passes so it works" | Build ≠ correct behavior. | Verify each criterion independently. |
|
|
109
|
+
| "I'll fix other issues I noticed" | Scope creep introduces bugs. | Only implement the current story. |
|
|
110
|
+
| "This is close enough" | Partial = broken for the user. | Implement ALL criteria or report BLOCKED. |
|
|
111
|
+
| "I'll skip the review, it's simple" | Simple code has bugs too. | Always self-review against criteria. |
|
|
112
|
+
| "I need to refactor first" | Refactoring without a story = scope creep. | Only refactor if the story requires it. |
|
|
113
|
+
|
|
114
|
+
## Status Reporting
|
|
115
|
+
|
|
116
|
+
- **DONE** — All criteria met, build passes, committed.
|
|
117
|
+
- **DONE_WITH_CONCERNS** — Completed but something feels off. Document in `concerns` field.
|
|
118
|
+
- **BLOCKED** — Cannot complete. Document why in kai-progress.txt.
|
|
119
|
+
- **NEEDS_CONTEXT** — Missing information. Document in kai-progress.txt.
|
|
120
|
+
PROMPTEOF
|
|
121
|
+
|
|
122
|
+
echo "Kai initialized."
|
|
123
|
+
echo ""
|
|
124
|
+
echo " Created: kai.json, kai-progress.txt, .kai/PROMPT.md"
|
|
125
|
+
echo ""
|
|
126
|
+
echo "Next:"
|
|
127
|
+
echo " 1. Edit .kai/PROMPT.md with your project context"
|
|
128
|
+
echo " 2. kai plan \"what you want to build\""
|
|
129
|
+
echo " 3. kai go"
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
cmd_plan() {
|
|
133
|
+
topic="${1:-}"
|
|
134
|
+
|
|
135
|
+
if [ ! -f "$PRD_FILE" ]; then
|
|
136
|
+
echo "kai.json not found. Run 'kai init' first."
|
|
137
|
+
exit 1
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# Build context about existing stories
|
|
141
|
+
existing=""
|
|
142
|
+
story_count=$(jq '.userStories | length' "$PRD_FILE")
|
|
143
|
+
if [ "$story_count" -gt 0 ]; then
|
|
144
|
+
existing="
|
|
145
|
+
|
|
146
|
+
Existing stories in kai.json (don't duplicate these):
|
|
147
|
+
$(jq -r '.userStories[] | "- [\(if .passes then "done" else "todo" end)] \(.title)"' "$PRD_FILE")"
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# Build context about the project
|
|
151
|
+
project_context=""
|
|
152
|
+
if [ -f "$PROMPT_FILE" ]; then
|
|
153
|
+
project_context="
|
|
154
|
+
|
|
155
|
+
Project context from .kai/PROMPT.md:
|
|
156
|
+
$(cat "$PROMPT_FILE")"
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
# PM system prompt
|
|
160
|
+
pm_prompt="You are a senior product manager and technical architect. You're planning work with a developer.
|
|
161
|
+
|
|
162
|
+
Your job:
|
|
163
|
+
1. Understand what they want to build or improve
|
|
164
|
+
2. Ask clarifying questions (target user, constraints, scope)
|
|
165
|
+
3. Break the work into small, implementable stories (5-15 min each)
|
|
166
|
+
4. Each story needs: title, description, and specific acceptance criteria
|
|
167
|
+
5. Stories should be ordered by dependency (build foundations first)
|
|
168
|
+
|
|
169
|
+
Rules for good stories:
|
|
170
|
+
- Each story should be independently committable
|
|
171
|
+
- Last acceptance criterion should always be \"App builds successfully\" or equivalent
|
|
172
|
+
- Acceptance criteria must be verifiable (not vague like \"looks good\")
|
|
173
|
+
- Keep stories small — if it takes more than 15 min, split it
|
|
174
|
+
- Include edge cases and error handling as separate stories
|
|
175
|
+
- Don't forget: tests, error states, loading states, empty states
|
|
176
|
+
|
|
177
|
+
When the user is happy with the plan, output the stories in this EXACT format — one JSON block with all stories:
|
|
178
|
+
|
|
179
|
+
\`\`\`kai-stories
|
|
180
|
+
[
|
|
181
|
+
{
|
|
182
|
+
\"title\": \"Story title here\",
|
|
183
|
+
\"description\": \"What to implement and why\",
|
|
184
|
+
\"acceptanceCriteria\": [\"Criterion 1\", \"Criterion 2\", \"App builds successfully\"]
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
\`\`\`
|
|
188
|
+
|
|
189
|
+
The user will say \"looks good\" or \"ship it\" when they want you to output the final stories.
|
|
190
|
+
${existing}${project_context}"
|
|
191
|
+
|
|
192
|
+
# Generate a session ID so we can find the transcript after
|
|
193
|
+
SESSION_ID=$(python3 -c "import uuid; print(uuid.uuid4())" 2>/dev/null || uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null)
|
|
194
|
+
|
|
195
|
+
if [ -n "$topic" ]; then
|
|
196
|
+
echo "Planning: $topic"
|
|
197
|
+
echo "Say 'ship it' when you're happy with the stories."
|
|
198
|
+
echo ""
|
|
199
|
+
claude --session-id "$SESSION_ID" --system-prompt "$pm_prompt" "Let's plan: $topic"
|
|
200
|
+
else
|
|
201
|
+
echo "Describe what you want to build. Say 'ship it' when ready."
|
|
202
|
+
echo ""
|
|
203
|
+
claude --session-id "$SESSION_ID" --system-prompt "$pm_prompt"
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# After session ends, find the transcript and extract stories
|
|
207
|
+
echo ""
|
|
208
|
+
echo "Scanning session for stories..."
|
|
209
|
+
|
|
210
|
+
# Find the project transcript directory
|
|
211
|
+
project_dir=$(echo "$PROJECT_DIR" | sed 's|/|-|g; s|^-||')
|
|
212
|
+
transcript=""
|
|
213
|
+
for dir in ~/.claude/projects/*/; do
|
|
214
|
+
if [ -f "${dir}${SESSION_ID}.jsonl" ]; then
|
|
215
|
+
transcript="${dir}${SESSION_ID}.jsonl"
|
|
216
|
+
break
|
|
217
|
+
fi
|
|
218
|
+
done
|
|
219
|
+
|
|
220
|
+
if [ -z "$transcript" ] || [ ! -f "$transcript" ]; then
|
|
221
|
+
echo "Could not find session transcript."
|
|
222
|
+
echo "If stories were generated, you can manually add them to kai.json."
|
|
223
|
+
return
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
# Extract kai-stories JSON from the transcript
|
|
227
|
+
stories_json=$(python3 -c "
|
|
228
|
+
import json, re, sys
|
|
229
|
+
|
|
230
|
+
stories = None
|
|
231
|
+
with open('$transcript') as f:
|
|
232
|
+
for line in f:
|
|
233
|
+
try:
|
|
234
|
+
d = json.loads(line)
|
|
235
|
+
if d.get('type') == 'assistant':
|
|
236
|
+
content = d.get('message', {}).get('content', [])
|
|
237
|
+
if isinstance(content, list):
|
|
238
|
+
for c in content:
|
|
239
|
+
if c.get('type') == 'text':
|
|
240
|
+
text = c['text']
|
|
241
|
+
# Find kai-stories block
|
|
242
|
+
match = re.search(r'\`\`\`kai-stories\s*\n(.*?)\n\`\`\`', text, re.DOTALL)
|
|
243
|
+
if match:
|
|
244
|
+
stories = match.group(1)
|
|
245
|
+
except: pass
|
|
246
|
+
|
|
247
|
+
if stories:
|
|
248
|
+
# Validate it's valid JSON
|
|
249
|
+
parsed = json.loads(stories)
|
|
250
|
+
print(json.dumps(parsed))
|
|
251
|
+
" 2>/dev/null)
|
|
252
|
+
|
|
253
|
+
if [ -n "$stories_json" ] && echo "$stories_json" | jq empty 2>/dev/null; then
|
|
254
|
+
count=$(echo "$stories_json" | jq 'length')
|
|
255
|
+
echo ""
|
|
256
|
+
echo "Found ${count} stories:"
|
|
257
|
+
echo "$stories_json" | jq -r '.[] | " - \(.title)"'
|
|
258
|
+
echo ""
|
|
259
|
+
echo "Add to kai.json? (y/n)"
|
|
260
|
+
read -r confirm
|
|
261
|
+
if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ] || [ "$confirm" = "" ]; then
|
|
262
|
+
_import_stories "$stories_json"
|
|
263
|
+
else
|
|
264
|
+
echo "Skipped."
|
|
265
|
+
fi
|
|
266
|
+
else
|
|
267
|
+
echo "No stories found in session."
|
|
268
|
+
echo "If the PM generated stories, you can copy the JSON and add manually to kai.json."
|
|
269
|
+
fi
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
_import_stories() {
|
|
273
|
+
local stories_json="$1"
|
|
274
|
+
|
|
275
|
+
if ! echo "$stories_json" | jq empty 2>/dev/null; then
|
|
276
|
+
echo "Invalid JSON."
|
|
277
|
+
return 1
|
|
278
|
+
fi
|
|
279
|
+
|
|
280
|
+
max_id=$(jq '[.userStories[].id] | max // 0' "$PRD_FILE")
|
|
281
|
+
count=$(echo "$stories_json" | jq 'length')
|
|
282
|
+
|
|
283
|
+
echo "$stories_json" | jq -c '.[]' | while IFS= read -r story; do
|
|
284
|
+
max_id=$((max_id + 1))
|
|
285
|
+
title=$(echo "$story" | jq -r '.title')
|
|
286
|
+
desc=$(echo "$story" | jq -r '.description')
|
|
287
|
+
criteria=$(echo "$story" | jq '.acceptanceCriteria')
|
|
288
|
+
|
|
289
|
+
jq --arg title "$title" \
|
|
290
|
+
--arg desc "$desc" \
|
|
291
|
+
--argjson id "$max_id" \
|
|
292
|
+
--argjson criteria "$criteria" \
|
|
293
|
+
'.userStories += [{
|
|
294
|
+
id: $id,
|
|
295
|
+
title: $title,
|
|
296
|
+
description: $desc,
|
|
297
|
+
acceptanceCriteria: $criteria,
|
|
298
|
+
priority: $id,
|
|
299
|
+
passes: false
|
|
300
|
+
}]' "$PRD_FILE" > "${PRD_FILE}.tmp" && mv "${PRD_FILE}.tmp" "$PRD_FILE"
|
|
301
|
+
|
|
302
|
+
echo " #${max_id}: ${title}"
|
|
303
|
+
done
|
|
304
|
+
|
|
305
|
+
echo ""
|
|
306
|
+
echo "Added ${count} stories."
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
cmd_status() {
|
|
310
|
+
if [ ! -f "$PRD_FILE" ]; then
|
|
311
|
+
echo "kai.json not found. Run 'kai init' first."
|
|
312
|
+
exit 1
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
total=$(jq '.userStories | length' "$PRD_FILE")
|
|
316
|
+
done_count=$(jq '[.userStories[] | select(.passes == true)] | length' "$PRD_FILE")
|
|
317
|
+
remaining=$((total - done_count))
|
|
318
|
+
|
|
319
|
+
echo "Kai: ${done_count}/${total} complete (${remaining} remaining)"
|
|
320
|
+
echo ""
|
|
321
|
+
|
|
322
|
+
if [ "$remaining" -gt 0 ]; then
|
|
323
|
+
echo "Remaining:"
|
|
324
|
+
jq -r '.userStories[] | select(.passes == false) | " \(.id): \(.title)"' "$PRD_FILE"
|
|
325
|
+
fi
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
cmd_reset() {
|
|
329
|
+
id="$1"
|
|
330
|
+
if [ -z "$id" ]; then
|
|
331
|
+
echo "Usage: kai reset <story-id>"
|
|
332
|
+
exit 1
|
|
333
|
+
fi
|
|
334
|
+
|
|
335
|
+
jq --argjson id "$id" '(.userStories[] | select(.id == $id)).passes = false' "$PRD_FILE" > "${PRD_FILE}.tmp" && mv "${PRD_FILE}.tmp" "$PRD_FILE"
|
|
336
|
+
echo "Reset story #${id}."
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
cmd_go() {
|
|
340
|
+
if [ ! -f "$PRD_FILE" ]; then
|
|
341
|
+
echo "kai.json not found. Run 'kai init' first."
|
|
342
|
+
exit 1
|
|
343
|
+
fi
|
|
344
|
+
|
|
345
|
+
if [ ! -f "$PROMPT_FILE" ]; then
|
|
346
|
+
echo ".kai/PROMPT.md not found. Run 'kai init' first."
|
|
347
|
+
exit 1
|
|
348
|
+
fi
|
|
349
|
+
|
|
350
|
+
# Collect context files
|
|
351
|
+
context_files="@${PROMPT_FILE} @${PRD_FILE} @${PROGRESS_FILE}"
|
|
352
|
+
if [ -f ".kai/context.txt" ]; then
|
|
353
|
+
while IFS= read -r file; do
|
|
354
|
+
[ -n "$file" ] && [ -f "$file" ] && context_files="$context_files @${file}"
|
|
355
|
+
done < .kai/context.txt
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
echo "Starting Kai..."
|
|
359
|
+
echo " Stories remaining: $(jq '[.userStories[] | select(.passes == false)] | length' "$PRD_FILE")"
|
|
360
|
+
echo " Ctrl+C to stop"
|
|
361
|
+
echo ""
|
|
362
|
+
|
|
363
|
+
iteration=0
|
|
364
|
+
review_failures=0
|
|
365
|
+
|
|
366
|
+
while :; do
|
|
367
|
+
iteration=$((iteration + 1))
|
|
368
|
+
REMAINING=$(jq '[.userStories[] | select(.passes == false)] | length' "$PRD_FILE")
|
|
369
|
+
|
|
370
|
+
if [ "$REMAINING" -eq 0 ]; then
|
|
371
|
+
echo "All stories complete!"
|
|
372
|
+
break
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
NEXT_ID=$(jq -r '[.userStories[] | select(.passes == false)] | sort_by(.priority) | .[0].id' "$PRD_FILE")
|
|
376
|
+
NEXT=$(jq -r '[.userStories[] | select(.passes == false)] | sort_by(.priority) | .[0].title' "$PRD_FILE")
|
|
377
|
+
|
|
378
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
379
|
+
echo "#${iteration} — $(date)"
|
|
380
|
+
echo " Remaining: $REMAINING | Next: $NEXT"
|
|
381
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
382
|
+
|
|
383
|
+
start_time=$(date +%s)
|
|
384
|
+
|
|
385
|
+
# ── Implement ──────────────────────────────────
|
|
386
|
+
set +e
|
|
387
|
+
result=$(claude -p \
|
|
388
|
+
--dangerously-skip-permissions \
|
|
389
|
+
$context_files)
|
|
390
|
+
exit_code=$?
|
|
391
|
+
set -e
|
|
392
|
+
|
|
393
|
+
end_time=$(date +%s)
|
|
394
|
+
elapsed=$(( end_time - start_time ))
|
|
395
|
+
minutes=$(( elapsed / 60 ))
|
|
396
|
+
seconds=$(( elapsed % 60 ))
|
|
397
|
+
|
|
398
|
+
echo "$result" | tail -20
|
|
399
|
+
echo ""
|
|
400
|
+
|
|
401
|
+
if [ $exit_code -ne 0 ]; then
|
|
402
|
+
echo "Iteration #${iteration} failed (code ${exit_code}). Restarting..."
|
|
403
|
+
continue
|
|
404
|
+
fi
|
|
405
|
+
|
|
406
|
+
if [[ "$result" == *"<promise>COMPLETE</promise>"* ]]; then
|
|
407
|
+
echo "All stories complete after $iteration iterations!"
|
|
408
|
+
exit 0
|
|
409
|
+
fi
|
|
410
|
+
|
|
411
|
+
# ── Review ─────────────────────────────────────
|
|
412
|
+
STORY_PASSES=$(jq -r ".userStories[] | select(.id == $NEXT_ID) | .passes" "$PRD_FILE")
|
|
413
|
+
|
|
414
|
+
if [ "$STORY_PASSES" = "true" ]; then
|
|
415
|
+
last_commit=$(git log --oneline -1 2>/dev/null || echo "no git")
|
|
416
|
+
last_diff_stat=$(git diff --stat HEAD~1 HEAD 2>/dev/null | tail -1)
|
|
417
|
+
|
|
418
|
+
# Sanity: real code changes?
|
|
419
|
+
if [ -z "$last_diff_stat" ] || [[ "$last_diff_stat" == *"0 insertions"*"0 deletions"* ]]; then
|
|
420
|
+
echo "REVIEW FAIL: No code changes in commit!"
|
|
421
|
+
jq --argjson id "$NEXT_ID" '(.userStories[] | select(.id == $id)).passes = false' "$PRD_FILE" > "${PRD_FILE}.tmp" && mv "${PRD_FILE}.tmp" "$PRD_FILE"
|
|
422
|
+
review_failures=$((review_failures + 1))
|
|
423
|
+
if [ $review_failures -ge 3 ]; then
|
|
424
|
+
echo "3 failures on story #${NEXT_ID}. Skipping."
|
|
425
|
+
jq --argjson id "$NEXT_ID" '(.userStories[] | select(.id == $id)).passes = true | (.userStories[] | select(.id == $id)).concerns = "Skipped after 3 failures"' "$PRD_FILE" > "${PRD_FILE}.tmp" && mv "${PRD_FILE}.tmp" "$PRD_FILE"
|
|
426
|
+
review_failures=0
|
|
427
|
+
fi
|
|
428
|
+
continue
|
|
429
|
+
fi
|
|
430
|
+
|
|
431
|
+
# Dispatch reviewer
|
|
432
|
+
echo "Reviewing story #${NEXT_ID}..."
|
|
433
|
+
ACCEPTANCE=$(jq -r ".userStories[] | select(.id == $NEXT_ID) | .acceptanceCriteria | join(\"\n- \")" "$PRD_FILE")
|
|
434
|
+
|
|
435
|
+
set +e
|
|
436
|
+
review_result=$(claude -p \
|
|
437
|
+
--dangerously-skip-permissions \
|
|
438
|
+
"You are a code reviewer. The implementer claims story #${NEXT_ID} ('${NEXT}') is complete.
|
|
439
|
+
|
|
440
|
+
Last commit: ${last_commit}
|
|
441
|
+
Changes: ${last_diff_stat}
|
|
442
|
+
|
|
443
|
+
Acceptance criteria:
|
|
444
|
+
- ${ACCEPTANCE}
|
|
445
|
+
|
|
446
|
+
Instructions:
|
|
447
|
+
1. Read the changed files (git diff HEAD~1 HEAD).
|
|
448
|
+
2. For EACH criterion, verify it is met by the actual code. Be skeptical.
|
|
449
|
+
3. Run the build/test command if applicable.
|
|
450
|
+
4. If ANY criterion is not met: REVIEW_FAIL: [which criterion and why]
|
|
451
|
+
5. If all criteria met: REVIEW_PASS
|
|
452
|
+
|
|
453
|
+
Do NOT trust the implementer's self-report. Verify independently.")
|
|
454
|
+
review_exit=$?
|
|
455
|
+
set -e
|
|
456
|
+
|
|
457
|
+
echo "$review_result" | tail -10
|
|
458
|
+
|
|
459
|
+
if [[ "$review_result" == *"REVIEW_FAIL"* ]]; then
|
|
460
|
+
echo "Review failed!"
|
|
461
|
+
jq --argjson id "$NEXT_ID" '(.userStories[] | select(.id == $id)).passes = false' "$PRD_FILE" > "${PRD_FILE}.tmp" && mv "${PRD_FILE}.tmp" "$PRD_FILE"
|
|
462
|
+
echo "" >> "$PROGRESS_FILE"
|
|
463
|
+
echo "## REVIEW FEEDBACK for Story $NEXT_ID" >> "$PROGRESS_FILE"
|
|
464
|
+
echo "$review_result" | grep -i "REVIEW_FAIL" >> "$PROGRESS_FILE" 2>/dev/null
|
|
465
|
+
review_failures=$((review_failures + 1))
|
|
466
|
+
if [ $review_failures -ge 3 ]; then
|
|
467
|
+
echo "3 review failures on story #${NEXT_ID}. Skipping."
|
|
468
|
+
jq --argjson id "$NEXT_ID" '(.userStories[] | select(.id == $id)).passes = true | (.userStories[] | select(.id == $id)).concerns = "Passed after 3 review attempts"' "$PRD_FILE" > "${PRD_FILE}.tmp" && mv "${PRD_FILE}.tmp" "$PRD_FILE"
|
|
469
|
+
review_failures=0
|
|
470
|
+
fi
|
|
471
|
+
else
|
|
472
|
+
echo "Review passed."
|
|
473
|
+
review_failures=0
|
|
474
|
+
fi
|
|
475
|
+
fi
|
|
476
|
+
|
|
477
|
+
echo "Iteration #${iteration} done in ${minutes}m ${seconds}s"
|
|
478
|
+
last_commit=$(git log --oneline -1 2>/dev/null || echo "")
|
|
479
|
+
[ -n "$last_commit" ] && echo " Last commit: ${last_commit}"
|
|
480
|
+
echo ""
|
|
481
|
+
done
|
|
482
|
+
|
|
483
|
+
echo "Kai finished after ${iteration} iterations."
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
# ── Main ───────────────────────────────────────────────
|
|
487
|
+
|
|
488
|
+
case "${1:-help}" in
|
|
489
|
+
init) cmd_init ;;
|
|
490
|
+
plan) shift; cmd_plan "$@" ;;
|
|
491
|
+
go) cmd_go ;;
|
|
492
|
+
status) cmd_status ;;
|
|
493
|
+
reset) shift; cmd_reset "$@" ;;
|
|
494
|
+
help|*) cmd_help ;;
|
|
495
|
+
esac
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kraftapps-ai/kai",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Autonomous AI developer loop for Claude Code",
|
|
5
|
+
"bin": {
|
|
6
|
+
"kai": "kai"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"claude",
|
|
10
|
+
"claude-code",
|
|
11
|
+
"ai",
|
|
12
|
+
"developer",
|
|
13
|
+
"autonomous",
|
|
14
|
+
"coding-agent",
|
|
15
|
+
"cli"
|
|
16
|
+
],
|
|
17
|
+
"author": "kraftapps-ai",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/kraftapps-ai/kai.git"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/kraftapps-ai/kai",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
27
|
+
}
|