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.
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/bt +5 -0
- package/bt.sh +134 -0
- package/commands/bootstrap.sh +47 -0
- package/commands/compound.sh +37 -0
- package/commands/design.sh +33 -0
- package/commands/fix.sh +70 -0
- package/commands/gates.sh +40 -0
- package/commands/implement.sh +70 -0
- package/commands/pr.sh +265 -0
- package/commands/research.sh +80 -0
- package/commands/reset.sh +37 -0
- package/commands/review.sh +84 -0
- package/commands/spec.sh +196 -0
- package/commands/status.sh +114 -0
- package/hooks/telegram.sh +31 -0
- package/lib/codex.sh +39 -0
- package/lib/env.sh +96 -0
- package/lib/gates.sh +109 -0
- package/lib/log.sh +48 -0
- package/lib/notify.sh +13 -0
- package/lib/path.sh +36 -0
- package/lib/repo.sh +70 -0
- package/lib/state.sh +60 -0
- package/package.json +39 -0
- package/prompts/fix.md +17 -0
- package/prompts/implement.md +19 -0
- package/prompts/research.md +12 -0
- package/prompts/review.md +15 -0
- package/scripts/demo-issue-3-real-loop.sh +221 -0
- package/scripts/gh-pr.sh +118 -0
- package/scripts/lint-shell.sh +41 -0
- package/scripts/verify-pack.mjs +54 -0
|
@@ -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
|
package/scripts/gh-pr.sh
ADDED
|
@@ -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`);
|