biotonomy 0.1.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.
@@ -0,0 +1,19 @@
1
+ # Implement (Biotonomy v0.1.0)
2
+
3
+ You are the implementation agent.
4
+
5
+ Rules:
6
+ - Prefer small, surgical commits.
7
+ - Add/adjust tests first.
8
+ - Run the project's quality gates.
9
+ - Update `specs/<feature>/SPEC.md` story status as you make progress.
10
+
11
+ Inputs:
12
+ - `specs/<feature>/SPEC.md`
13
+ - `specs/<feature>/RESEARCH.md` (if present)
14
+ - `specs/<feature>/progress.txt`
15
+
16
+ Outputs:
17
+ - Modified source + tests
18
+ - Updated `SPEC.md`
19
+
@@ -0,0 +1,12 @@
1
+ # Research (Biotonomy v0.1.0)
2
+
3
+ You are the research agent. Learn the codebase quickly and record actionable guidance.
4
+
5
+ Write to:
6
+ - `specs/<feature>/RESEARCH.md`
7
+
8
+ Capture:
9
+ - How to run tests, lint, typecheck
10
+ - Key architectural patterns and conventions
11
+ - Risky areas and common pitfalls
12
+
@@ -0,0 +1,15 @@
1
+ # Review (Biotonomy v0.1.0)
2
+
3
+ You are the reviewer agent. You must not implement features; only identify issues.
4
+
5
+ Checklist:
6
+ - Correctness and edge cases
7
+ - Tests: coverage and brittleness
8
+ - Security and safety
9
+ - Style and maintainability
10
+
11
+ Output:
12
+ - Write `specs/<feature>/REVIEW.md` with:
13
+ - `Verdict: APPROVE` or `Verdict: NEEDS_CHANGES`
14
+ - A numbered list of findings with file paths and suggested fixes
15
+
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # End-to-end "real loop" demo for Issue #3.
5
+ # Runs the actual entrypoint (bt.sh) against a deterministic workspace and
6
+ # writes scrubbed (timestamp/path-normalized) outputs under specs/issue-3-real-loop/.
7
+
8
+ cd "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
9
+
10
+ ROOT="$PWD"
11
+ BT="$ROOT/bt.sh"
12
+
13
+ BASE="$ROOT/specs/issue-3-real-loop"
14
+ WORK="$BASE/workspace"
15
+ BIN="$WORK/bin"
16
+
17
+ iters="${BT_DEMO_ITERS:-3}"
18
+ if [[ $# -gt 0 ]]; then
19
+ case "${1:-}" in
20
+ -h|--help)
21
+ cat <<EOF
22
+ Usage:
23
+ npm run demo -- [iterations]
24
+
25
+ Writes deterministic demo outputs under:
26
+ specs/issue-3-real-loop/
27
+
28
+ You can also set:
29
+ BT_DEMO_ITERS=5
30
+ EOF
31
+ exit 0
32
+ ;;
33
+ *[!0-9]*)
34
+ echo "demo: iterations must be a number (got: $1)" >&2
35
+ exit 2
36
+ ;;
37
+ *)
38
+ iters="$1"
39
+ ;;
40
+ esac
41
+ fi
42
+
43
+ demo_sed_inplace() {
44
+ local expr="$1"
45
+ local file="$2"
46
+ if sed --version >/dev/null 2>&1; then
47
+ sed -i -E "$expr" "$file"
48
+ else
49
+ sed -i '' -E "$expr" "$file"
50
+ fi
51
+ }
52
+
53
+ run_bt() {
54
+ local -a args=("$@")
55
+ # bt logs to stderr; capture both, and normalize ANSI coloring.
56
+ (
57
+ export BT_NO_COLOR=1
58
+ export LC_ALL=C
59
+ export LANG=C
60
+ export PATH="$BIN:$PATH"
61
+ cd "$WORK"
62
+ bash "$BT" "${args[@]}"
63
+ ) 2>&1
64
+ }
65
+
66
+ write_stub_gh() {
67
+ cat >"$BIN/gh" <<'EOF'
68
+ #!/usr/bin/env bash
69
+ set -euo pipefail
70
+ if [[ "${1:-}" == "issue" && "${2:-}" == "view" && "${3:-}" == "3" ]]; then
71
+ cat <<'JSON'
72
+ {"title":"Core loop (Codex + gh integration)","url":"https://github.com/archive-dot-com/biotonomy/issues/3","body":"Issue #3 demo body.\n\nThis is deterministic stub data."}
73
+ JSON
74
+ exit 0
75
+ fi
76
+ echo "stub gh: unexpected args: $*" >&2
77
+ exit 2
78
+ EOF
79
+ chmod 755 "$BIN/gh"
80
+ }
81
+
82
+ write_stub_codex() {
83
+ cat >"$BIN/codex" <<'EOF'
84
+ #!/usr/bin/env bash
85
+ set -euo pipefail
86
+ if [[ "${1:-}" != "exec" ]]; then
87
+ echo "stub codex: expected 'exec' (got: ${1:-})" >&2
88
+ exit 2
89
+ fi
90
+ shift
91
+
92
+ out=""
93
+ while [[ $# -gt 0 ]]; do
94
+ case "$1" in
95
+ -o) shift; out="${1:-}"; shift ;;
96
+ *) shift ;;
97
+ esac
98
+ done
99
+
100
+ # Read-only path writes an output file; full-auto path just exits 0.
101
+ if [[ -n "$out" ]]; then
102
+ mkdir -p "$(dirname "$out")"
103
+ # Keep output stable across runs (no timestamps).
104
+ cat >"$out" <<'MD'
105
+ # Codex Stub Output
106
+
107
+ Verdict: OK
108
+
109
+ This is deterministic stub output for the Issue #3 real-loop demo.
110
+ MD
111
+ fi
112
+ exit 0
113
+ EOF
114
+ chmod 755 "$BIN/codex"
115
+ }
116
+
117
+ scrub_workspace_nondeterminism() {
118
+ # Normalize timestamps in files that intentionally include them.
119
+ local f
120
+ if [[ -f "$WORK/.bt/state/gates.json" ]]; then
121
+ demo_sed_inplace 's/"ts":[[:space:]]*"[^"]+"/"ts": "1970-01-01T00:00:00Z"/' "$WORK/.bt/state/gates.json"
122
+ fi
123
+ if [[ -d "$WORK/specs/issue-3" ]]; then
124
+ if [[ -f "$WORK/specs/issue-3/gates.json" ]]; then
125
+ demo_sed_inplace 's/"ts":[[:space:]]*"[^"]+"/"ts": "1970-01-01T00:00:00Z"/' "$WORK/specs/issue-3/gates.json"
126
+ fi
127
+ if [[ -f "$WORK/specs/issue-3/progress.txt" ]]; then
128
+ demo_sed_inplace 's/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/1970-01-01 00:00:00/' "$WORK/specs/issue-3/progress.txt"
129
+ fi
130
+ if [[ -d "$WORK/specs/issue-3/history" ]]; then
131
+ for f in "$WORK/specs/issue-3/history/"*.md; do
132
+ [[ -f "$f" ]] || continue
133
+ demo_sed_inplace 's/^- when: [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/- when: 1970-01-01 00:00:00/' "$f"
134
+ done
135
+ fi
136
+ fi
137
+ }
138
+
139
+ mkdir -p "$BASE"
140
+ rm -rf "$WORK"
141
+ mkdir -p "$WORK" "$BIN"
142
+
143
+ # Minimal project scaffolding for deterministic repo resolution and gate behavior.
144
+ cat >"$WORK/.bt.env" <<'EOF'
145
+ BT_SPECS_DIR=specs
146
+ BT_STATE_DIR=.bt
147
+
148
+ # Make gates deterministic and fast.
149
+ BT_GATE_LINT=true
150
+ BT_GATE_TYPECHECK=true
151
+ BT_GATE_TEST=true
152
+
153
+ # Use the stubbed codex in workspace bin/.
154
+ BT_CODEX_BIN=codex
155
+ EOF
156
+
157
+ write_stub_gh
158
+ write_stub_codex
159
+
160
+ (
161
+ cd "$WORK"
162
+ git init -q
163
+ git remote add origin https://github.com/archive-dot-com/biotonomy.git
164
+ ) >/dev/null 2>&1 || true
165
+
166
+ raw="$BASE/transcript.raw.txt"
167
+ out="$BASE/transcript.txt"
168
+ snap="$BASE/snapshot.txt"
169
+
170
+ : >"$raw"
171
+
172
+ {
173
+ echo "biotonomy issue-3 real-loop demo"
174
+ echo "iterations=$iters"
175
+ echo
176
+
177
+ run_bt bootstrap
178
+ run_bt spec 3
179
+
180
+ i=1
181
+ while [[ "$i" -le "$iters" ]]; do
182
+ echo "== iteration $i =="
183
+ run_bt research issue-3
184
+ run_bt implement issue-3 || true
185
+ run_bt review issue-3
186
+ run_bt gates issue-3 || true
187
+ run_bt status
188
+ echo
189
+ i=$((i + 1))
190
+ done
191
+ } >>"$raw"
192
+
193
+ scrub_workspace_nondeterminism
194
+
195
+ # Create a scrubbed transcript: normalize bt timestamps and any absolute paths.
196
+ cp "$raw" "$out"
197
+ demo_sed_inplace 's/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/1970-01-01 00:00:00/' "$out"
198
+ demo_sed_inplace "s#${WORK}#<WORK>#g" "$out"
199
+ demo_sed_inplace "s#${ROOT}#<REPO>#g" "$out"
200
+
201
+ # Snapshot key outputs (tree + a couple of file excerpts).
202
+ {
203
+ echo "workspace: <WORK>"
204
+ echo
205
+ echo "files:"
206
+ (cd "$WORK" && find . -type f -not -path "./.git/*" | LC_ALL=C sort)
207
+ echo
208
+ echo "SPEC.md (head):"
209
+ sed -n '1,40p' "$WORK/specs/issue-3/SPEC.md"
210
+ echo
211
+ echo "RESEARCH.md (head):"
212
+ sed -n '1,40p' "$WORK/specs/issue-3/RESEARCH.md"
213
+ echo
214
+ echo "REVIEW.md (head):"
215
+ sed -n '1,40p' "$WORK/specs/issue-3/REVIEW.md"
216
+ } >"$snap"
217
+
218
+ demo_sed_inplace "s#${WORK}#<WORK>#g" "$snap"
219
+
220
+ echo "demo: wrote $out" >&2
221
+ echo "demo: wrote $snap" >&2
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Safe, opt-in PR helper using gh.
5
+ # Defaults to dry-run (prints commands). Pass --run to execute.
6
+
7
+ cd "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
8
+
9
+ usage() {
10
+ cat <<'EOF'
11
+ Usage:
12
+ npm run pr:open -- <feature> [--run] [--dry-run] [--base <branch>] [--remote <name>] [--draft]
13
+
14
+ Notes:
15
+ - Default mode is --dry-run (no git/gh side effects).
16
+ - Determines branch from specs/<feature>/SPEC.md frontmatter (branch: ...), else uses feat/<feature>.
17
+ EOF
18
+ }
19
+
20
+ run_mode="dry-run"
21
+ base=""
22
+ remote="origin"
23
+ draft=0
24
+ feature=""
25
+
26
+ while [[ $# -gt 0 ]]; do
27
+ case "${1:-}" in
28
+ -h|--help) usage; exit 0 ;;
29
+ --run) run_mode="run"; shift ;;
30
+ --dry-run) run_mode="dry-run"; shift ;;
31
+ --base) base="${2:-}"; shift 2 ;;
32
+ --remote) remote="${2:-}"; shift 2 ;;
33
+ --draft) draft=1; shift ;;
34
+ -*)
35
+ echo "pr: unknown flag: $1" >&2
36
+ usage >&2
37
+ exit 2
38
+ ;;
39
+ *)
40
+ feature="$1"
41
+ shift
42
+ ;;
43
+ esac
44
+ done
45
+
46
+ [[ -n "$feature" ]] || { usage >&2; exit 2; }
47
+
48
+ run_cmd() {
49
+ printf '+ '
50
+ printf '%q ' "$@"
51
+ printf '\n'
52
+ if [[ "$run_mode" == "run" ]]; then
53
+ "$@"
54
+ fi
55
+ }
56
+
57
+ if ! command -v git >/dev/null 2>&1; then
58
+ echo "pr: git not found" >&2
59
+ exit 127
60
+ fi
61
+
62
+ if [[ "$run_mode" == "run" ]] && ! command -v gh >/dev/null 2>&1; then
63
+ echo "pr: gh not found (install GitHub CLI or re-run with --dry-run)" >&2
64
+ exit 127
65
+ fi
66
+
67
+ specs_dir="${BT_SPECS_DIR:-specs}"
68
+ spec="$specs_dir/$feature/SPEC.md"
69
+
70
+ branch="feat/$feature"
71
+ repo=""
72
+ issue=""
73
+
74
+ if [[ -f "$spec" ]]; then
75
+ # Read simple YAML-ish frontmatter keys (one per line).
76
+ b="$(awk -F': *' '$1=="branch"{print $2; exit}' "$spec" | tr -d '\r')"
77
+ r="$(awk -F': *' '$1=="repo"{print $2; exit}' "$spec" | tr -d '\r')"
78
+ i="$(awk -F': *' '$1=="issue"{print $2; exit}' "$spec" | tr -d '\r')"
79
+ [[ -n "${b:-}" ]] && branch="$b"
80
+ [[ -n "${r:-}" ]] && repo="$r"
81
+ [[ -n "${i:-}" ]] && issue="$i"
82
+ fi
83
+
84
+ if [[ -z "$base" ]]; then
85
+ # Try to detect the default remote HEAD (origin/main vs origin/master, etc.).
86
+ ref="$(git symbolic-ref -q "refs/remotes/$remote/HEAD" 2>/dev/null || true)"
87
+ if [[ -n "$ref" ]]; then
88
+ base="${ref##*/}"
89
+ else
90
+ base="main"
91
+ fi
92
+ fi
93
+
94
+ title="feat: $feature"
95
+ body="Feature: $feature"
96
+ if [[ -n "$repo" && -n "$issue" ]]; then
97
+ body="$body
98
+ Issue: https://github.com/$repo/issues/$issue"
99
+ fi
100
+ if [[ -f "$spec" ]]; then
101
+ body="$body
102
+ Spec: $spec"
103
+ fi
104
+
105
+ if git show-ref --verify --quiet "refs/heads/$branch"; then
106
+ run_cmd git checkout "$branch"
107
+ else
108
+ run_cmd git checkout -b "$branch"
109
+ fi
110
+
111
+ run_cmd git push -u "$remote" "$branch"
112
+
113
+ pr_args=(pr create --head "$branch" --base "$base" --title "$title" --body "$body")
114
+ if [[ "$draft" == "1" ]]; then
115
+ pr_args+=(--draft)
116
+ fi
117
+ run_cmd gh "${pr_args[@]}"
118
+
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ cd "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+
6
+ # Deterministic output ordering regardless of runner locale.
7
+ export LC_ALL=C
8
+ export LANG=C
9
+
10
+ if ! command -v shellcheck >/dev/null 2>&1; then
11
+ if [[ "${BT_LINT_STRICT:-0}" == "1" ]]; then
12
+ echo "lint: shellcheck not found (BT_LINT_STRICT=1); failing" >&2
13
+ exit 1
14
+ fi
15
+ echo "lint: shellcheck not found; skipping (install shellcheck to enable)" >&2
16
+ exit 0
17
+ fi
18
+
19
+ files=()
20
+
21
+ # Prefer tracked files when running from a git worktree. This avoids glob ordering
22
+ # differences and silently skipping new folders.
23
+ if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
24
+ while IFS= read -r f; do
25
+ files+=("$f")
26
+ done < <(git ls-files -- '*.sh' | sort)
27
+ else
28
+ # npm-installed tarballs don't include `.git/`, so fall back to globs.
29
+ shopt -s nullglob
30
+ files+=(bt.sh commands/*.sh lib/*.sh hooks/*.sh scripts/*.sh)
31
+ shopt -u nullglob
32
+ # Normalize ordering.
33
+ mapfile -t files < <(printf '%s\n' "${files[@]}" | sort)
34
+ fi
35
+
36
+ if [[ ${#files[@]} -eq 0 ]]; then
37
+ echo "lint: no shell files found; skipping" >&2
38
+ exit 0
39
+ fi
40
+
41
+ shellcheck -x "${files[@]}"
@@ -0,0 +1,54 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import assert from "node:assert/strict";
3
+
4
+ function sh(cmd, args) {
5
+ const res = spawnSync(cmd, args, { encoding: "utf8" });
6
+ if ((res.status ?? 1) !== 0) {
7
+ throw new Error(
8
+ `command failed: ${cmd} ${args.join(" ")}\n${res.stdout || ""}\n${res.stderr || ""}`
9
+ );
10
+ }
11
+ return { stdout: res.stdout || "", stderr: res.stderr || "" };
12
+ }
13
+
14
+ // Prefer JSON output when available (npm >=9 supports --json for pack).
15
+ let files = null;
16
+ try {
17
+ const { stdout } = sh("npm", ["pack", "--dry-run", "--json"]);
18
+ const j = JSON.parse(stdout);
19
+ // npm returns an array; each entry has "files": [{path,size,mode}, ...]
20
+ files = (j?.[0]?.files || []).map((f) => f.path).filter(Boolean);
21
+ } catch {
22
+ // Fallback: parse the human output (less stable, but better than nothing).
23
+ const { stdout } = sh("npm", ["pack", "--dry-run"]);
24
+ files = stdout
25
+ .split("\n")
26
+ .map((l) => l.trim())
27
+ .filter((l) => l.startsWith("npm notice "))
28
+ .map((l) => l.replace(/^npm notice\s+/, ""))
29
+ .filter((l) => l.startsWith("package.json") || l.startsWith("bt.sh") || l.includes("/"));
30
+ }
31
+
32
+ assert.ok(Array.isArray(files) && files.length > 0, "npm pack produced no file list");
33
+
34
+ const mustHave = [
35
+ "package.json",
36
+ "README.md",
37
+ "LICENSE",
38
+ "bt.sh",
39
+ "commands/bootstrap.sh",
40
+ "lib/env.sh",
41
+ "prompts/implement.md",
42
+ "scripts/gh-pr.sh",
43
+ ];
44
+ for (const p of mustHave) {
45
+ assert.ok(files.includes(p), `pack missing required file: ${p}`);
46
+ }
47
+
48
+ const mustNotHave = [".github/workflows/ci.yml", ".gitignore", "specs/"];
49
+ for (const p of mustNotHave) {
50
+ const has = files.some((f) => f === p || f.startsWith(p));
51
+ assert.ok(!has, `pack unexpectedly includes: ${p}`);
52
+ }
53
+
54
+ process.stderr.write(`ok - pack includes ${files.length} files\n`);