biotonomy 0.2.3 → 0.2.6

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/bt.sh CHANGED
@@ -1,7 +1,14 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
- BT_VERSION="0.1.0"
4
+ bt_package_version() {
5
+ local pkg="$BT_ROOT/package.json"
6
+ if [[ -f "$pkg" ]]; then
7
+ awk -F '"' '/"version"[[:space:]]*:/ { print $4; exit }' "$pkg"
8
+ return 0
9
+ fi
10
+ return 1
11
+ }
5
12
 
6
13
  bt_script_dir() {
7
14
  local src="${BASH_SOURCE[0]}"
@@ -15,6 +22,8 @@ bt_script_dir() {
15
22
  }
16
23
 
17
24
  BT_ROOT="$(bt_script_dir)"
25
+ BT_VERSION="$(bt_package_version 2>/dev/null || true)"
26
+ BT_VERSION="${BT_VERSION:-0.1.0}"
18
27
 
19
28
  # shellcheck source=/dev/null
20
29
  source "$BT_ROOT/lib/log.sh"
@@ -41,7 +50,7 @@ Usage:
41
50
  bt [--target <path>] <command> [args]
42
51
 
43
52
  Commands:
44
- bootstrap spec research implement review fix loop compound design status gates reset pr ship
53
+ bootstrap spec research plan-review implement review fix loop compound design status gates reset pr ship
45
54
 
46
55
  Global options:
47
56
  -h, --help Show help
@@ -93,7 +102,26 @@ bt_dispatch() {
93
102
  shift || true
94
103
 
95
104
  case "$cmd" in
96
- -h|--help|help) bt_usage; return 0 ;;
105
+ -h|--help|help)
106
+ if [[ $# -gt 0 ]]; then
107
+ # bt help <cmd>
108
+ cmd="$1"
109
+ shift
110
+ local cmd_file="$BT_ROOT/commands/$cmd.sh"
111
+ if [[ "$cmd" == "ship" ]]; then cmd_file="$BT_ROOT/commands/pr.sh"; fi
112
+ if [[ -f "$cmd_file" ]]; then
113
+ # shellcheck source=/dev/null
114
+ source "$cmd_file"
115
+ local safe_cmd="${cmd//-/_}"
116
+ local fn="bt_${safe_cmd}_usage"
117
+ if [[ "$cmd" == "ship" ]]; then fn="bt_pr_usage"; fi
118
+ if declare -F "$fn" >/dev/null 2>&1; then
119
+ "$fn"
120
+ return 0
121
+ fi
122
+ fi
123
+ fi
124
+ bt_usage; return 0 ;;
97
125
  esac
98
126
 
99
127
  bt_env_load || true
package/commands/loop.sh CHANGED
@@ -56,6 +56,8 @@ bt_cmd_loop() {
56
56
  return 2
57
57
  fi
58
58
 
59
+ feature="$(bt_require_feature "$feature")"
60
+
59
61
  bt_env_load || true
60
62
  bt_ensure_dirs
61
63
 
@@ -70,14 +72,9 @@ bt_cmd_loop() {
70
72
 
71
73
  bt_info "starting loop for: $feature (max iterations: $max_iter)"
72
74
 
73
- local -a gate_args=()
74
- if [[ "${BT_LOOP_REQUIRE_GATES:-0}" == "1" ]]; then
75
- gate_args=(--require-any)
76
- fi
77
-
78
75
  bt_info "running preflight gates..."
79
- if (( ${#gate_args[@]} > 0 )); then
80
- if ! bt_run_gates "${gate_args[@]}"; then
76
+ if [[ "${BT_LOOP_REQUIRE_GATES:-0}" == "1" ]]; then
77
+ if ! bt_run_gates --require-any; then
81
78
  bt_err "preflight gates failed (or none configured); aborting before implement/review"
82
79
  return 1
83
80
  fi
@@ -104,10 +101,53 @@ bt_cmd_loop() {
104
101
  review_file="$feat_dir/REVIEW.md"
105
102
  local history_dir="$feat_dir/history"
106
103
  local progress_file="$feat_dir/loop-progress.json"
104
+ local resume_iter=0
105
+ local should_resume=0
107
106
 
108
- # Initialize progress
109
107
  mkdir -p "$history_dir"
110
- cat > "$progress_file" <<EOF
108
+ if [[ -f "$progress_file" ]]; then
109
+ local resume_meta
110
+ resume_meta="$(python3 - <<PY
111
+ import json
112
+ p = "$progress_file"
113
+ max_iter = $max_iter
114
+ try:
115
+ with open(p, "r", encoding="utf-8") as f:
116
+ d = json.load(f)
117
+ except Exception:
118
+ print("0 0")
119
+ raise SystemExit(0)
120
+ result = str(d.get("result", ""))
121
+ completed = d.get("completedIterations", 0)
122
+ try:
123
+ completed = int(completed)
124
+ except Exception:
125
+ completed = 0
126
+ if result in {"in-progress", "implement-failed"} and completed < max_iter:
127
+ d["feature"] = "$feature"
128
+ d["maxIterations"] = max_iter
129
+ d["completedIterations"] = completed
130
+ d["result"] = "in-progress"
131
+ if not isinstance(d.get("iterations"), list):
132
+ d["iterations"] = []
133
+ with open(p, "w", encoding="utf-8") as f:
134
+ json.dump(d, f, indent=2)
135
+ print(f"1 {completed}")
136
+ else:
137
+ print("0 0")
138
+ PY
139
+ )"
140
+ if [[ "$resume_meta" =~ ^1[[:space:]]+([0-9]+)$ ]]; then
141
+ should_resume=1
142
+ resume_iter="${BASH_REMATCH[1]}"
143
+ fi
144
+ fi
145
+
146
+ if [[ "$should_resume" == "1" ]]; then
147
+ iter="$resume_iter"
148
+ bt_info "resuming loop from iteration $((iter + 1)) / $max_iter"
149
+ else
150
+ cat > "$progress_file" <<EOF
111
151
  {
112
152
  "feature": "$feature",
113
153
  "maxIterations": $max_iter,
@@ -116,10 +156,12 @@ bt_cmd_loop() {
116
156
  "iterations": []
117
157
  }
118
158
  EOF
159
+ fi
119
160
 
120
161
  # Ensure subcommands return non-zero instead of exiting the entire loop process.
121
162
  export BT_DIE_MODE="return"
122
163
 
164
+
123
165
  while [[ "$iter" -lt "$max_iter" ]]; do
124
166
  iter=$((iter + 1))
125
167
  bt_info "--- Iteration $iter / $max_iter ---"
@@ -177,20 +219,11 @@ PY
177
219
  # bt_cmd_implement/fix return non-zero if gates fail, but we capture the status.
178
220
  # We call bt_run_gates here just to be sure of the final state post-review.
179
221
  local gates_ok=1
180
- if (( ${#gate_args[@]} > 0 )); then
181
- if ! bt_run_gates "${gate_args[@]}"; then
182
- gates_ok=0
183
- bt_info "gates: FAIL"
184
- else
185
- bt_info "gates: PASS"
186
- fi
222
+ if ! bt_run_gates --require-any; then
223
+ gates_ok=0
224
+ bt_info "gates: FAIL"
187
225
  else
188
- if ! bt_run_gates; then
189
- gates_ok=0
190
- bt_info "gates: FAIL"
191
- else
192
- bt_info "gates: PASS"
193
- fi
226
+ bt_info "gates: PASS"
194
227
  fi
195
228
 
196
229
  local fix_status="SKIP"
@@ -29,26 +29,20 @@ EOF
29
29
 
30
30
  local out="$dir/PLAN_REVIEW.md"
31
31
 
32
- if bt_codex_available; then
33
- bt_info "running codex (full-auto) using prompts/plan-review.md"
34
- local artifacts_dir codex_logf
35
- artifacts_dir="$dir/.artifacts"
36
- mkdir -p "$artifacts_dir"
37
- codex_logf="$artifacts_dir/codex-plan-review.log"
38
- : >"$codex_logf"
39
- if BT_FEATURE="$feature" BT_CODEX_LOG_FILE="$codex_logf" bt_codex_exec_full_auto "$BT_ROOT/prompts/plan-review.md"; then
40
- :
41
- else
42
- bt_die "codex failed (plan-review)"
43
- fi
44
- else
45
- # v0.1.0 stub
46
- cat <<'EOF' > "$out"
47
- # Plan Review: Stub Approved
32
+ if ! bt_codex_available; then
33
+ bt_die "codex required for plan-review (set BT_CODEX_BIN to a valid codex executable or install codex)"
34
+ fi
48
35
 
49
- Verdict: APPROVED_PLAN
50
- EOF
51
- bt_info "wrote $out"
36
+ bt_info "running codex (full-auto) using prompts/plan-review.md"
37
+ local artifacts_dir codex_logf
38
+ artifacts_dir="$dir/.artifacts"
39
+ mkdir -p "$artifacts_dir"
40
+ codex_logf="$artifacts_dir/codex-plan-review.log"
41
+ : >"$codex_logf"
42
+ if BT_FEATURE="$feature" BT_CODEX_LOG_FILE="$codex_logf" bt_codex_exec_full_auto "$BT_ROOT/prompts/plan-review.md" "$out"; then
43
+ :
44
+ else
45
+ bt_die "codex failed (plan-review)"
52
46
  fi
53
47
 
54
48
  if [[ ! -f "$out" ]]; then
package/commands/pr.sh CHANGED
@@ -161,6 +161,7 @@ bt_cmd_pr() {
161
161
  bt_err "feature name is required"
162
162
  return 2
163
163
  fi
164
+ feature="$(bt_require_feature "$feature")"
164
165
 
165
166
  # Ensure BT_PROJECT_ROOT reflects BT_TARGET_DIR / BT_ENV_FILE, and operate within it.
166
167
  bt_env_load || true
package/commands/spec.sh CHANGED
@@ -159,13 +159,19 @@ EOF
159
159
  bt_env_load || true
160
160
  bt_ensure_dirs
161
161
 
162
- local feature issue
162
+ local feature issue url_slug
163
163
  issue=""
164
+ url_slug=""
164
165
  if [[ "$arg" =~ ^[0-9]+$ ]]; then
165
166
  issue="$arg"
166
167
  feature="issue-$arg"
168
+ elif [[ "$arg" =~ ^https?://github.com/([^/]+/[^/]+)/issues/([0-9]+) ]]; then
169
+ # support URL format
170
+ issue="${BASH_REMATCH[2]}"
171
+ url_slug="${BASH_REMATCH[1]}"
172
+ feature="issue-$issue"
167
173
  else
168
- feature="$arg"
174
+ feature="$(bt_require_feature "$arg")"
169
175
  fi
170
176
 
171
177
  local dir
@@ -186,7 +192,11 @@ EOF
186
192
  bt__require_cmd gh
187
193
 
188
194
  local repo
189
- repo="$(bt_repo_resolve "$BT_PROJECT_ROOT")"
195
+ if [[ -n "$url_slug" ]]; then
196
+ repo="$url_slug"
197
+ else
198
+ repo="$(bt_repo_resolve "$BT_PROJECT_ROOT")"
199
+ fi
190
200
 
191
201
  local -a gh_cmd
192
202
  gh_cmd=(gh issue view "$issue" -R "$repo" --json "title,body,url")
@@ -75,7 +75,7 @@ EOF
75
75
 
76
76
  bt_env_load || true
77
77
 
78
- echo "bt v0.1.0"
78
+ echo "bt v${BT_VERSION:-unknown}"
79
79
  echo "project_root: $BT_PROJECT_ROOT"
80
80
  echo "env_file: ${BT_ENV_FILE:-<none>}"
81
81
  echo "specs_dir: $BT_SPECS_DIR"
package/lib/codex.sh CHANGED
@@ -11,6 +11,7 @@ bt_codex_available() {
11
11
 
12
12
  bt_codex_exec_full_auto() {
13
13
  local prompt_file="$1"
14
+ local out_file="${2:-}"
14
15
  if ! bt_codex_available; then
15
16
  bt_warn "codex not found; skipping (set BT_CODEX_BIN or install codex)"
16
17
  return 0
@@ -19,7 +20,15 @@ bt_codex_exec_full_auto() {
19
20
  bin="$(bt_codex_bin)"
20
21
  local log_file
21
22
  log_file="${BT_CODEX_LOG_FILE:-/dev/null}"
22
- "$bin" exec --full-auto -C "$BT_PROJECT_ROOT" "$(cat "$prompt_file")" 2>&1 | tee -a "$log_file"
23
+
24
+ local -a args
25
+ args=(exec --full-auto -C "$BT_PROJECT_ROOT")
26
+ if [[ -n "$out_file" ]]; then
27
+ args+=(-o "$out_file")
28
+ fi
29
+ args+=("$(cat "$prompt_file")")
30
+
31
+ "$bin" "${args[@]}" 2>&1 | tee -a "$log_file"
23
32
  return "${PIPESTATUS[0]}"
24
33
  }
25
34
 
package/lib/state.sh CHANGED
@@ -14,10 +14,29 @@ bt_ensure_dirs() {
14
14
  mkdir -p "$(bt_specs_path)" "$BT_PROJECT_ROOT/$BT_STATE_DIR"
15
15
  }
16
16
 
17
+ bt_sanitize_feature() {
18
+ local s="${1:-}"
19
+ # Replace characters not in [A-Za-z0-9._-] with underscore
20
+ s="${s//[^A-Za-z0-9._-]/_}"
21
+ # Ensure it doesn't start with a dot or dash and isn't empty
22
+ if [[ "$s" =~ ^[._-] ]]; then
23
+ s="f$s"
24
+ fi
25
+ if [[ -z "$s" ]]; then
26
+ s="feature"
27
+ fi
28
+ printf '%s\n' "$s"
29
+ }
30
+
17
31
  bt_require_feature() {
18
32
  local feature="${1:-${BT_FEATURE:-}}"
19
33
  [[ -n "$feature" ]] || bt_die "feature required (pass as first arg or set BT_FEATURE)"
20
34
 
35
+ # Apply sanitization if it doesn't already pass validation
36
+ if [[ "$feature" == *"/"* || "$feature" == *".."* || ! "$feature" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
37
+ feature="$(bt_sanitize_feature "$feature")"
38
+ fi
39
+
21
40
  # Prevent path traversal and keep on-disk state predictable.
22
41
  [[ "$feature" != *"/"* ]] || bt_die "invalid feature (must not contain '/'): $feature"
23
42
  [[ "$feature" != *".."* ]] || bt_die "invalid feature (must not contain '..'): $feature"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "biotonomy",
3
- "version": "0.2.3",
3
+ "version": "0.2.6",
4
4
  "description": "Codex-native autonomous development loop CLI",
5
5
  "license": "MIT",
6
6
  "repository": {