biotonomy 0.2.2 → 0.2.3

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/commands/loop.sh CHANGED
@@ -59,15 +59,6 @@ bt_cmd_loop() {
59
59
  bt_env_load || true
60
60
  bt_ensure_dirs
61
61
 
62
- bt_info "starting loop for: $feature (max iterations: $max_iter)"
63
-
64
- bt_info "running preflight gates..."
65
- if ! bt_run_gates; then
66
- bt_err "preflight gates failed; aborting before implement/review"
67
- return 1
68
- fi
69
- bt_info "preflight gates: PASS"
70
-
71
62
  local feat_dir
72
63
  feat_dir="$(bt_feature_dir "$feature")"
73
64
  local plan_review="$feat_dir/PLAN_REVIEW.md"
@@ -77,6 +68,27 @@ bt_cmd_loop() {
77
68
  bt_die "loop hard-fails without approved PLAN_REVIEW verdict before implement/review"
78
69
  fi
79
70
 
71
+ bt_info "starting loop for: $feature (max iterations: $max_iter)"
72
+
73
+ local -a gate_args=()
74
+ if [[ "${BT_LOOP_REQUIRE_GATES:-0}" == "1" ]]; then
75
+ gate_args=(--require-any)
76
+ fi
77
+
78
+ bt_info "running preflight gates..."
79
+ if (( ${#gate_args[@]} > 0 )); then
80
+ if ! bt_run_gates "${gate_args[@]}"; then
81
+ bt_err "preflight gates failed (or none configured); aborting before implement/review"
82
+ return 1
83
+ fi
84
+ else
85
+ if ! bt_run_gates; then
86
+ bt_err "preflight gates failed (or none configured); aborting before implement/review"
87
+ return 1
88
+ fi
89
+ fi
90
+ bt_info "preflight gates: PASS"
91
+
80
92
  # Source required commands so we can call them directly
81
93
  # shellcheck source=/dev/null
82
94
  source "$BT_ROOT/commands/implement.sh"
@@ -165,11 +177,20 @@ PY
165
177
  # bt_cmd_implement/fix return non-zero if gates fail, but we capture the status.
166
178
  # We call bt_run_gates here just to be sure of the final state post-review.
167
179
  local gates_ok=1
168
- if ! bt_run_gates; then
169
- gates_ok=0
170
- bt_info "gates: FAIL"
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
171
187
  else
172
- bt_info "gates: PASS"
188
+ if ! bt_run_gates; then
189
+ gates_ok=0
190
+ bt_info "gates: FAIL"
191
+ else
192
+ bt_info "gates: PASS"
193
+ fi
173
194
  fi
174
195
 
175
196
  local fix_status="SKIP"
@@ -31,7 +31,12 @@ EOF
31
31
 
32
32
  if bt_codex_available; then
33
33
  bt_info "running codex (full-auto) using prompts/plan-review.md"
34
- if BT_FEATURE="$feature" BT_CODEX_LOG_FILE="/tmp/codex.log" bt_codex_exec_full_auto "$BT_ROOT/prompts/plan-review.md"; then
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
35
40
  :
36
41
  else
37
42
  bt_die "codex failed (plan-review)"
package/commands/pr.sh CHANGED
@@ -189,20 +189,19 @@ bt_cmd_pr() {
189
189
  fi
190
190
 
191
191
  # 2. Fail-loud preflight for unstaged expected files (before tests/commit flow).
192
- if [[ "$commit" == "1" ]]; then
193
- local unstaged=""
194
- local check_paths=(tests lib commands scripts specs prompts) # Added specs and prompts
195
- if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
196
- unstaged="$(git ls-files --others --modified --exclude-standard -- "${check_paths[@]}" 2>/dev/null || true)"
197
- else
198
- # Outside a git repo, treat present implementation files as unstaged by definition.
199
- unstaged="$(find "${check_paths[@]}" -type f 2>/dev/null | LC_ALL=C sort || true)"
200
- fi
201
- if [[ -n "$unstaged" ]]; then
202
- bt_err "Found unstaged files that might be required for this feature:"
203
- printf '%s\n' "$unstaged" >&2
204
- bt_die "Abort: ship requires all feature files to be staged. Use git add and try again."
205
- fi
192
+ # P0 #18: fail-loud even with --no-commit
193
+ local unstaged=""
194
+ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
195
+ # Protect all repo files, not only a fixed allowlist of directories.
196
+ unstaged="$(git ls-files --others --modified --exclude-standard 2>/dev/null || true)"
197
+ else
198
+ # Outside a git repo, treat present files (except .git internals) as unstaged by definition.
199
+ unstaged="$(find . -path './.git' -prune -o -type f -print 2>/dev/null | sed 's#^\./##' | LC_ALL=C sort || true)"
200
+ fi
201
+ if [[ -n "$unstaged" ]]; then
202
+ bt_err "Found unstaged files that might be required for this feature:"
203
+ printf '%s\n' "$unstaged" >&2
204
+ bt_die "Abort: ship requires all feature files to be staged. Use git add and try again."
206
205
  fi
207
206
 
208
207
  if [[ "$run_mode" == "dry-run" ]]; then
@@ -255,14 +254,7 @@ bt_cmd_pr() {
255
254
  # 4. Commit changes if requested
256
255
  if [[ "$commit" == "1" ]]; then
257
256
  bt_info "committing changes..."
258
- local unstaged
259
- local check_paths=(tests lib commands scripts specs prompts)
260
- unstaged="$(git status --porcelain -- "${check_paths[@]}" 2>/dev/null || true)"
261
- if [[ -n "$unstaged" ]]; then
262
- bt_err "Found unstaged files that might be required for this feature:"
263
- printf '%s\n' "$unstaged" >&2
264
- bt_die "Abort: ship requires all feature files to be staged. Use git add and try again."
265
- fi
257
+ # (Unstaged check removed here as it is now redundant with global T0 check)
266
258
 
267
259
  if ! git diff --cached --quiet; then
268
260
  git commit -m "feat($feature): ship implementation"
package/commands/spec.sh CHANGED
@@ -38,6 +38,84 @@ NODE
38
38
  )"
39
39
  }
40
40
 
41
+ bt__stories_from_issue_json() {
42
+ bt__require_cmd node
43
+ node -e "$(
44
+ cat <<'NODE'
45
+ const fs = require("fs");
46
+ const j = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
47
+
48
+ function clean(s) {
49
+ return String(s || "").replace(/\s+/g, " ").trim();
50
+ }
51
+
52
+ const title = clean(j.title);
53
+ const body = String(j.body || "").replace(/\r/g, "");
54
+ const lines = body.split("\n");
55
+
56
+ let inAcceptance = false;
57
+ const acceptanceBullets = [];
58
+ const allBullets = [];
59
+ for (const rawLine of lines) {
60
+ const line = rawLine || "";
61
+ if (/^\s*#{1,6}\s*acceptance\b/i.test(line) || /^\s*acceptance criteria\s*:?\s*$/i.test(line)) {
62
+ inAcceptance = true;
63
+ continue;
64
+ }
65
+ if (/^\s*#{1,6}\s+/.test(line) && inAcceptance) {
66
+ inAcceptance = false;
67
+ }
68
+
69
+ const m = line.match(/^\s*[-*]\s+(?:\[[ xX]\]\s*)?(.+?)\s*$/);
70
+ if (!m) {
71
+ continue;
72
+ }
73
+ const bullet = clean(m[1]);
74
+ if (!bullet) {
75
+ continue;
76
+ }
77
+ allBullets.push(bullet);
78
+ if (inAcceptance) {
79
+ acceptanceBullets.push(bullet);
80
+ }
81
+ }
82
+
83
+ const selectedBullets = acceptanceBullets.length > 0 ? acceptanceBullets : allBullets;
84
+ const storyTitles = [];
85
+ if (title) {
86
+ storyTitles.push(title);
87
+ }
88
+ for (const bullet of selectedBullets) {
89
+ if (storyTitles.length >= 5) {
90
+ break;
91
+ }
92
+ storyTitles.push(bullet);
93
+ }
94
+
95
+ if (storyTitles.length === 0) {
96
+ const fallback = clean(body).slice(0, 120);
97
+ if (fallback) {
98
+ storyTitles.push(fallback);
99
+ } else {
100
+ storyTitles.push("Capture issue requirements");
101
+ }
102
+ }
103
+
104
+ let out = "";
105
+ for (let i = 0; i < storyTitles.length; i += 1) {
106
+ const id = i + 1;
107
+ const titleText = storyTitles[i];
108
+ out += "## [ID:S" + id + "] " + titleText + "\n";
109
+ out += "- **status:** draft\n";
110
+ out += "- **priority:** " + (id <= 3 ? 1 : 2) + "\n";
111
+ out += "- **acceptance:** " + titleText + "\n";
112
+ out += "- **tests:**\n\n";
113
+ }
114
+ process.stdout.write(out);
115
+ NODE
116
+ )"
117
+ }
118
+
41
119
  bt_cmd_spec() {
42
120
  local force=0
43
121
  local arg=""
@@ -132,8 +210,9 @@ EOF
132
210
  [[ -n "$title" ]] || title="(untitled)"
133
211
  [[ -n "$url" ]] || url="https://github.com/$repo/issues/$issue"
134
212
 
135
- local summary
213
+ local summary stories
136
214
  summary="$(bt__summarize_body "$body")"
215
+ stories="$(printf '%s' "$json" | bt__stories_from_issue_json)"
137
216
 
138
217
  cat >"$spec" <<EOF
139
218
  ---
@@ -154,35 +233,7 @@ $summary
154
233
 
155
234
  # Stories
156
235
 
157
- ## [ID:S1] Confirm repo resolution and env fallback
158
- - **status:** draft
159
- - **priority:** 1
160
- - **acceptance:** bt can determine repo slug from git remote origin; otherwise requires BT_REPO
161
- - **tests:**
162
-
163
- ## [ID:S2] Fetch issue details via gh
164
- - **status:** draft
165
- - **priority:** 1
166
- - **acceptance:** bt spec <issue#> uses gh to retrieve title/body/url and handles errors clearly
167
- - **tests:**
168
-
169
- ## [ID:S3] Generate a SPEC.md with frontmatter + problem summary
170
- - **status:** draft
171
- - **priority:** 1
172
- - **acceptance:** SPEC includes required frontmatter, a Problem section, and a Stories section (3-7 stories)
173
- - **tests:**
174
-
175
- ## [ID:S4] Record exact gh commands used in SPEC footer
176
- - **status:** draft
177
- - **priority:** 2
178
- - **acceptance:** SPEC footer includes the exact gh command(s) executed
179
- - **tests:**
180
-
181
- ## [ID:S5] Add tests stubbing gh via PATH
182
- - **status:** draft
183
- - **priority:** 1
184
- - **acceptance:** tests run offline and validate SPEC content generation
185
- - **tests:**
236
+ ${stories}
186
237
 
187
238
  ---
188
239
 
package/lib/gates.sh CHANGED
@@ -77,6 +77,12 @@ bt_get_gate_config() {
77
77
  # Runs gates and returns a JSON string fragment with results.
78
78
  # Writes logs to stderr. Returns 0 if all gates passed, 1 otherwise.
79
79
  bt_run_gates() {
80
+ local require_any=0
81
+ if [[ "${1:-}" == "--require-any" ]]; then
82
+ require_any=1
83
+ shift
84
+ fi
85
+
80
86
  local config
81
87
  config="$(bt_get_gate_config)"
82
88
 
@@ -115,6 +121,9 @@ bt_run_gates() {
115
121
  if [[ "$any" == "0" ]]; then
116
122
  bt_warn "no gates ran"
117
123
  printf '{"ts": "%s", "results": {}}\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
124
+ if [[ "$require_any" == "1" ]]; then
125
+ return 1
126
+ fi
118
127
  return 0
119
128
  fi
120
129
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "biotonomy",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Codex-native autonomous development loop CLI",
5
5
  "license": "MIT",
6
6
  "repository": {