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 +31 -3
- package/commands/loop.sh +55 -22
- package/commands/plan-review.sh +13 -19
- package/commands/pr.sh +1 -0
- package/commands/spec.sh +13 -3
- package/commands/status.sh +1 -1
- package/lib/codex.sh +10 -1
- package/lib/state.sh +19 -0
- package/package.json +1 -1
package/bt.sh
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
-
|
|
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)
|
|
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
|
|
80
|
-
if ! bt_run_gates
|
|
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
|
-
|
|
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
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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"
|
package/commands/plan-review.sh
CHANGED
|
@@ -29,26 +29,20 @@ EOF
|
|
|
29
29
|
|
|
30
30
|
local out="$dir/PLAN_REVIEW.md"
|
|
31
31
|
|
|
32
|
-
if bt_codex_available; then
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
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
|
-
|
|
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")
|
package/commands/status.sh
CHANGED
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
|
-
|
|
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"
|