@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.
Files changed (3) hide show
  1. package/README.md +161 -0
  2. package/kai +495 -0
  3. 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
+ }