wile 0.4.15 → 0.4.17

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 CHANGED
@@ -25,6 +25,7 @@ This creates:
25
25
 
26
26
  Set `WILE_REPO_SOURCE=local` in `.wile/secrets/.env` to run against the current directory without GitHub.
27
27
  When `WILE_REPO_SOURCE=local`, GitHub credentials are optional.
28
+ Set `WILE_MAX_ITERATIONS` in `.wile/secrets/.env` to change the default loop limit (default: 25).
28
29
 
29
30
  ## Run Wile
30
31
 
@@ -7,7 +7,7 @@
7
7
  set -e
8
8
 
9
9
  echo "══════════════════════════════════════════════════════"
10
- echo " 🗡️ WILE - Container Startup"
10
+ echo " 🌵 WILE - Container Startup"
11
11
  echo "══════════════════════════════════════════════════════"
12
12
 
13
13
  if [ "${WILE_TEST:-}" = "true" ]; then
@@ -104,8 +104,9 @@ if [ "${WILE_MOCK_CLAUDE:-}" = "true" ]; then
104
104
  mkdir -p "$MOCK_BIN"
105
105
  cat > "$MOCK_BIN/claude" << 'MOCK'
106
106
  #!/bin/sh
107
- echo "ANSWER: 2"
108
- echo "<promise>COMPLETE</promise>"
107
+ cat <<'JSON'
108
+ {"type":"assistant","message":{"content":[{"type":"text","text":"ANSWER: 2\n<promise>COMPLETE</promise>\n"}]}}
109
+ JSON
109
110
  MOCK
110
111
  chmod +x "$MOCK_BIN/claude"
111
112
  export PATH="$MOCK_BIN:$PATH"
@@ -161,7 +162,7 @@ echo ""
161
162
 
162
163
  # Configure git
163
164
  echo "Configuring git..."
164
- git config --global user.name "Wile Bot"
165
+ git config --global user.name "wile"
165
166
  git config --global user.email "wile@bot.local"
166
167
  git config --global credential.helper store
167
168
 
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ const readline = require("node:readline");
3
+
4
+ const rl = readline.createInterface({
5
+ input: process.stdin,
6
+ crlfDelay: Infinity
7
+ });
8
+
9
+ const writeLine = (value) => {
10
+ process.stdout.write(`${value}\n`);
11
+ };
12
+
13
+ const extractText = (content) => {
14
+ if (!Array.isArray(content)) return;
15
+ for (const chunk of content) {
16
+ if (!chunk || typeof chunk !== "object") continue;
17
+ if (chunk.type === "text" && typeof chunk.text === "string") {
18
+ process.stdout.write(chunk.text);
19
+ continue;
20
+ }
21
+ if (chunk.type === "thinking" && typeof chunk.thinking === "string") {
22
+ writeLine(`[thinking] ${chunk.thinking}`);
23
+ }
24
+ }
25
+ };
26
+
27
+ const extractToolUse = (content) => {
28
+ if (!Array.isArray(content)) return;
29
+ for (const chunk of content) {
30
+ if (!chunk || typeof chunk !== "object") continue;
31
+ if (chunk.type === "tool_use") {
32
+ const toolName = chunk.name ?? "tool";
33
+ const description = chunk.input?.description ?? "";
34
+ const command = chunk.input?.command ?? "";
35
+ if (description && command) {
36
+ writeLine(`[tool] ${toolName}: ${description} (${command})`);
37
+ } else if (command) {
38
+ writeLine(`[tool] ${toolName}: ${command}`);
39
+ } else if (description) {
40
+ writeLine(`[tool] ${toolName}: ${description}`);
41
+ } else {
42
+ writeLine(`[tool] ${toolName}`);
43
+ }
44
+ }
45
+ }
46
+ };
47
+
48
+ rl.on("line", (line) => {
49
+ if (!line.trim()) return;
50
+ let payload;
51
+ try {
52
+ payload = JSON.parse(line);
53
+ } catch {
54
+ return;
55
+ }
56
+
57
+ if (payload.type === "system" && payload.subtype === "init") {
58
+ const model = payload.model ?? "unknown";
59
+ writeLine(`[system] model: ${model}`);
60
+ return;
61
+ }
62
+
63
+ if (payload.type === "assistant" && payload.message) {
64
+ extractToolUse(payload.message.content);
65
+ extractText(payload.message.content);
66
+ return;
67
+ }
68
+
69
+ if (payload.type === "user" && payload.message?.content) {
70
+ for (const chunk of payload.message.content) {
71
+ if (chunk?.type === "tool_result") {
72
+ writeLine(`[tool-result] ${chunk.tool_use_id ?? "unknown"} ${chunk.is_error ? "error" : "ok"}`);
73
+ }
74
+ }
75
+ return;
76
+ }
77
+
78
+ if (payload.type === "result" && payload.result) {
79
+ writeLine(payload.result);
80
+ }
81
+ });
@@ -100,6 +100,9 @@ After completing steps 1-8, check if ALL stories in `.wile/prd.json` have `passe
100
100
  6. **No interactive prompts** - Use `echo -e "\n\n\n" |` if a command might prompt
101
101
  7. **NEVER commit node_modules, dist, or build artifacts** - .gitignore should already be set up at the start of the run
102
102
  8. **Use acceptance criteria as verification steps** - Run commands to confirm outputs or write tests that fail if the feature is removed
103
+ 9. **Integration tests must validate real system behavior, not just the harness**
104
+ 10. **If you discover reusable, module-specific guidance, add it to the nearest AGENTS.md**
105
+ Note: Never update .wile/AGENTS.md.
103
106
 
104
107
  ## Common Patterns
105
108
 
@@ -17,6 +17,7 @@ run_case() {
17
17
  mkdir -p "$SCRIPT_DIR" "$BIN_DIR"
18
18
 
19
19
  cp /Users/thiagoduarte/Projects/personal/wile/packages/agent/scripts/wile.sh "$SCRIPT_DIR/wile.sh"
20
+ cp /Users/thiagoduarte/Projects/personal/wile/packages/agent/scripts/claude-stream.js "$SCRIPT_DIR/claude-stream.js"
20
21
  chmod +x "$SCRIPT_DIR/wile.sh"
21
22
 
22
23
  echo "BASE PROMPT" > "$SCRIPT_DIR/prompt.md"
@@ -43,10 +44,10 @@ EOF
43
44
 
44
45
  CAPTURE="$TMP_DIR/capture.txt"
45
46
 
46
- cat > "$BIN_DIR/claude" <<'EOF'
47
+ cat > "$BIN_DIR/claude" <<'EOF'
47
48
  #!/bin/sh
48
49
  cat > "$CLAUDE_CAPTURE"
49
- echo "<promise>COMPLETE</promise>"
50
+ printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"text","text":"<promise>COMPLETE</promise>\\n"}]}}'
50
51
  EOF
51
52
  chmod +x "$BIN_DIR/claude"
52
53
 
@@ -0,0 +1,44 @@
1
+ #!/bin/sh
2
+ set -euo pipefail
3
+
4
+ TMP_DIR=$(mktemp -d /tmp/wile-iteration-limit-XXXXXX)
5
+ cleanup() {
6
+ rm -rf "$TMP_DIR"
7
+ }
8
+ trap cleanup EXIT INT TERM
9
+
10
+ SCRIPT_DIR="$TMP_DIR/agent"
11
+ BIN_DIR="$TMP_DIR/bin"
12
+ mkdir -p "$SCRIPT_DIR" "$BIN_DIR"
13
+
14
+ cp /Users/thiagoduarte/Projects/personal/wile/packages/agent/scripts/wile.sh "$SCRIPT_DIR/wile.sh"
15
+ cp /Users/thiagoduarte/Projects/personal/wile/packages/agent/scripts/claude-stream.js "$SCRIPT_DIR/claude-stream.js"
16
+ chmod +x "$SCRIPT_DIR/wile.sh"
17
+
18
+ echo "BASE PROMPT" > "$SCRIPT_DIR/prompt.md"
19
+
20
+ OUTPUT_FILE="$TMP_DIR/output.txt"
21
+
22
+ cat > "$BIN_DIR/claude" <<'EOF'
23
+ #!/bin/sh
24
+ printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"text","text":"working...\n"}]}}'
25
+ EOF
26
+ chmod +x "$BIN_DIR/claude"
27
+
28
+ set +e
29
+ PATH="$BIN_DIR:$PATH" \
30
+ CC_CLAUDE_MODEL="sonnet" \
31
+ "$SCRIPT_DIR/wile.sh" 3 > "$OUTPUT_FILE" 2>&1
32
+ EXIT_CODE=$?
33
+ set -e
34
+
35
+ if [ "$EXIT_CODE" -eq 0 ]; then
36
+ echo "error: expected non-zero exit when max iterations reached" >&2
37
+ exit 1
38
+ fi
39
+
40
+ grep -q "MAX ITERATIONS REACHED (3)" "$OUTPUT_FILE"
41
+ grep -q "Iteration 1 of 3" "$OUTPUT_FILE"
42
+ grep -q "Iteration 3 of 3" "$OUTPUT_FILE"
43
+
44
+ echo "test-iteration-limit: ok"
@@ -14,7 +14,7 @@ SETUP_PROMPT_FILE="$SCRIPT_DIR/prompt-setup.md"
14
14
  ADDITIONAL_PROMPT_FILE="${WILE_ADDITIONAL_INSTRUCTIONS:-}"
15
15
 
16
16
  echo "══════════════════════════════════════════════════════"
17
- echo " 🗡️ WILE - Autonomous Coding Agent"
17
+ echo " 🌵 WILE - Autonomous Coding Agent"
18
18
  echo "══════════════════════════════════════════════════════"
19
19
  echo " Max iterations: $MAX_ITERATIONS"
20
20
  echo " Model: $CLAUDE_MODEL"
@@ -49,7 +49,12 @@ echo "════════════════════════
49
49
  echo ""
50
50
 
51
51
  if [ -f "$SETUP_PROMPT_FILE" ]; then
52
- OUTPUT=$(cat "$SETUP_PROMPT_FILE" | claude --model "$CLAUDE_MODEL" --verbose --dangerously-skip-permissions 2>&1 | tee /dev/stderr) || true
52
+ OUTPUT=$(
53
+ cat "$SETUP_PROMPT_FILE" \
54
+ | claude --model "$CLAUDE_MODEL" --print --output-format stream-json --verbose --dangerously-skip-permissions \
55
+ | node "$SCRIPT_DIR/claude-stream.js" \
56
+ | tee /dev/stderr
57
+ ) || true
53
58
 
54
59
  # Check if setup failed critically
55
60
  if echo "$OUTPUT" | grep -q "<promise>SETUP_FAILED</promise>"; then
@@ -80,7 +85,12 @@ for i in $(seq 1 $MAX_ITERATIONS); do
80
85
  # Pipe prompt to Claude Code
81
86
  # --dangerously-skip-permissions allows autonomous operation
82
87
  # Capture output while also displaying it (tee to stderr)
83
- OUTPUT=$(cat "$PROMPT_FILE" | claude --model "$CLAUDE_MODEL" --verbose --dangerously-skip-permissions 2>&1 | tee /dev/stderr) || true
88
+ OUTPUT=$(
89
+ cat "$PROMPT_FILE" \
90
+ | claude --model "$CLAUDE_MODEL" --print --output-format stream-json --verbose --dangerously-skip-permissions \
91
+ | node "$SCRIPT_DIR/claude-stream.js" \
92
+ | tee /dev/stderr
93
+ ) || true
84
94
 
85
95
  # Check for completion signal
86
96
  if echo "$OUTPUT" | grep -q "<promise>COMPLETE</promise>"; then
package/dist/cli.js CHANGED
@@ -7658,9 +7658,10 @@ var runConfig = async () => {
7658
7658
  message: "Default Claude model",
7659
7659
  choices: [
7660
7660
  { title: "sonnet", value: "sonnet" },
7661
- { title: "opus", value: "opus" }
7661
+ { title: "opus", value: "opus" },
7662
+ { title: "haiku", value: "haiku" }
7662
7663
  ],
7663
- initial: existingEnv.CC_CLAUDE_MODEL === "opus" ? 1 : 0
7664
+ initial: existingEnv.CC_CLAUDE_MODEL === "opus" ? 1 : existingEnv.CC_CLAUDE_MODEL === "haiku" ? 2 : 0
7664
7665
  });
7665
7666
  const repoSourceResponse = await prompt({
7666
7667
  type: "select",
@@ -7696,6 +7697,14 @@ var runConfig = async () => {
7696
7697
  message: "Default branch name",
7697
7698
  initial: existingEnv.BRANCH_NAME ?? "main"
7698
7699
  });
7700
+ const iterationsResponse = await prompt({
7701
+ type: "number",
7702
+ name: "maxIterations",
7703
+ message: "Default max iterations",
7704
+ initial: existingEnv.WILE_MAX_ITERATIONS ? Number(existingEnv.WILE_MAX_ITERATIONS) : 25
7705
+ });
7706
+ const fallbackIterations = existingEnv.WILE_MAX_ITERATIONS ? Number(existingEnv.WILE_MAX_ITERATIONS) : 25;
7707
+ const maxIterations = Number.isFinite(iterationsResponse.maxIterations) && iterationsResponse.maxIterations > 0 ? iterationsResponse.maxIterations : fallbackIterations;
7699
7708
  const authFallback = authMethod === "oauth" ? existingEnv.CC_CLAUDE_CODE_OAUTH_TOKEN : existingEnv.CC_ANTHROPIC_API_KEY;
7700
7709
  const authValue = coalesceValue(authValueResponse.authValue, authFallback);
7701
7710
  const githubToken = repoSource === "github" ? coalesceValue(githubTokenResponse.githubToken, existingEnv.GITHUB_TOKEN) : existingEnv.GITHUB_TOKEN;
@@ -7707,7 +7716,8 @@ var runConfig = async () => {
7707
7716
  `GITHUB_TOKEN=${githubToken ?? ""}`,
7708
7717
  `GITHUB_REPO_URL=${repoUrl ?? ""}`,
7709
7718
  `BRANCH_NAME=${branchName ?? "main"}`,
7710
- `CC_CLAUDE_MODEL=${defaultModelResponse.model}`
7719
+ `CC_CLAUDE_MODEL=${defaultModelResponse.model}`,
7720
+ `WILE_MAX_ITERATIONS=${maxIterations}`
7711
7721
  ];
7712
7722
  if (authMethod === "oauth") {
7713
7723
  envLines.push(`CC_CLAUDE_CODE_OAUTH_TOKEN=${authValue ?? ""}`);
@@ -7745,6 +7755,7 @@ Use bullet points for additional instructions, e.g.
7745
7755
  "Guidelines:",
7746
7756
  "- Use outcome-focused acceptance criteria (observable results).",
7747
7757
  '- Criteria should be hard to satisfy with "empty" tests.',
7758
+ "- For integration tests, write acceptance criteria that validate real system behavior (not just the harness).",
7748
7759
  "- If verification is a command, state the expected result of that command.",
7749
7760
  "- Use one behavior per bullet.",
7750
7761
  "- Keep IDs stable and unique (e.g., US-123).",
@@ -7753,9 +7764,6 @@ Use bullet points for additional instructions, e.g.
7753
7764
  "- Mark `passes: false` for work not done yet.",
7754
7765
  "- Place the STOP HERE note only on the last story that requires human approval.",
7755
7766
  "- Prefer concrete files/commands only when they reflect the real outcome.",
7756
- "- Integration tests must validate real system behavior, not just the harness.",
7757
- "- If you discover reusable, module-specific guidance, add it to the nearest AGENTS.md.",
7758
- " Note: Never update .wile/AGENTS.md.",
7759
7767
  "",
7760
7768
  "Environment notes:",
7761
7769
  "- Playwright (Chromium) is available in the agent container for UI checks.",
@@ -7834,6 +7842,7 @@ var readWileConfig = (options = {}) => {
7834
7842
  branchName: env.BRANCH_NAME ?? "",
7835
7843
  repoSource,
7836
7844
  ccClaudeModel: env.CC_CLAUDE_MODEL,
7845
+ maxIterations: env.WILE_MAX_ITERATIONS,
7837
7846
  ccClaudeCodeOauthToken: env.CC_CLAUDE_CODE_OAUTH_TOKEN,
7838
7847
  ccAnthropicApiKey: env.CC_ANTHROPIC_API_KEY,
7839
7848
  envProject
@@ -8020,8 +8029,9 @@ var runWile = async (options) => {
8020
8029
  return;
8021
8030
  }
8022
8031
  const agentDir = resolveAgentDir();
8032
+ const resolvedIterations = options.maxIterations || config.maxIterations || "25";
8023
8033
  buildAgentImage(agentDir);
8024
- const dockerArgs = buildDockerArgs(options, config, paths, cwd);
8034
+ const dockerArgs = buildDockerArgs({ ...options, maxIterations: resolvedIterations }, config, paths, cwd);
8025
8035
  const logsDir = join3(paths.wileDir, "logs");
8026
8036
  mkdirSync(logsDir, { recursive: true });
8027
8037
  const logPath = join3(logsDir, `run-${getTimestamp()}.log`);
@@ -8051,7 +8061,7 @@ program2.name("wile").description("Autonomous AI coding agent that ships feature
8051
8061
  program2.command("config").description("Configure the current project for Wile").action(async () => {
8052
8062
  await runConfig();
8053
8063
  });
8054
- program2.command("run").description("Run Wile on a repository").option("--repo <repo>", "Repository URL or local path").option("--max-iterations <count>", "Maximum iterations", "25").option("--test", "Run in test mode").option("--debug", "Print debug info before running").action(async (options) => {
8064
+ program2.command("run").description("Run Wile on a repository").option("--repo <repo>", "Repository URL or local path").option("--max-iterations <count>", "Maximum iterations").option("--test", "Run in test mode").option("--debug", "Print debug info before running").action(async (options) => {
8055
8065
  await runWile(options);
8056
8066
  });
8057
8067
  program2.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wile",
3
- "version": "0.4.15",
3
+ "version": "0.4.17",
4
4
  "description": "Autonomous AI coding agent that ships features while you sleep",
5
5
  "type": "module",
6
6
  "bin": {