@oneie/claude 0.6.0 → 0.7.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/agents/w1-recon.md +9 -4
- package/agents/w2-decide.md +51 -28
- package/agents/w3-edit.md +70 -78
- package/agents/w4-verify.md +164 -69
- package/commands/browser.md +98 -35
- package/commands/cc-connect.md +6 -0
- package/commands/chat.md +13 -1
- package/commands/close.md +45 -19
- package/commands/create.md +20 -10
- package/commands/db-sync.md +5 -1
- package/commands/deploy.md +174 -87
- package/commands/do-autonomous.md +31 -20
- package/commands/do-improve.md +4 -4
- package/commands/do-show.md +4 -3
- package/commands/do.md +53 -13
- package/commands/go.md +75 -0
- package/commands/improve.md +4 -2
- package/commands/kill.md +6 -8
- package/commands/notify.md +15 -2
- package/commands/oo-push.md +24 -9
- package/commands/rag.md +6 -0
- package/commands/release.md +46 -25
- package/commands/restart.md +5 -4
- package/commands/see.md +45 -35
- package/commands/skill-create.md +38 -5
- package/commands/sync.md +21 -11
- package/hooks/scripts/auto-continue.sh +70 -0
- package/hooks/scripts/branch-pin.sh +85 -0
- package/hooks/scripts/git-add-guard.sh +16 -5
- package/hooks/scripts/post-edit-check.sh +16 -0
- package/hooks/scripts/session-start.sh +13 -8
- package/hooks/scripts/sync-priority-todo.sh +5 -0
- package/package.json +1 -1
- package/rules/api.md +9 -0
- package/rules/design.md +13 -3
- package/rules/documentation.md +30 -71
- package/rules/engine.md +8 -1
- package/rules/ui.md +5 -1
- package/scripts/__pycache__/do-rank.cpython-313.pyc +0 -0
- package/scripts/browser-check.mjs +44 -103
- package/scripts/cc-events-proof.sh +13 -12
- package/scripts/chrome.mjs +381 -0
- package/scripts/do-analyze.sh +109 -4
- package/scripts/do-auto.sh +350 -43
- package/scripts/do-consumer-sweep.sh +72 -0
- package/scripts/do-derives-check.sh +88 -0
- package/scripts/do-fleet.sh +50 -1
- package/scripts/do-killswitch-audit.py +33 -14
- package/scripts/do-promise-lint.sh +150 -4
- package/scripts/do-promise-settle.sh +131 -9
- package/scripts/do-prove.sh +197 -17
- package/scripts/do-rank.py +606 -20
- package/scripts/do-rubric.py +21 -2
- package/scripts/do-signal.sh +157 -17
- package/scripts/do-smoke.sh +9 -0
- package/scripts/do-substrate-check.sh +49 -0
- package/scripts/do-tasks-wire-check.sh +81 -0
- package/scripts/do-tier.sh +90 -5
- package/scripts/do-ui-gate.sh +332 -0
- package/scripts/do-untracked-gate.sh +231 -0
- package/scripts/do-walk.sh +344 -0
- package/scripts/factory-check.sh +747 -0
- package/scripts/factory-repo.sh +1361 -0
- package/scripts/factory-tasks-check.sh +85 -0
- package/scripts/outcome-pull.ts +279 -0
- package/scripts/promise-manifest.mjs +167 -0
- package/scripts/reconcile-allow.txt +5 -0
- package/scripts/rubric-weights.json +64 -0
- package/scripts/spine-canary.sh +20 -0
- package/scripts/typedb-cluster-status.sh +128 -0
- package/scripts/typedb-env.sh +182 -0
- package/scripts/typedb-flap-recorder.sh +85 -0
- package/scripts/typedb-probes/containment-probe.py +88 -0
- package/scripts/typedb-probes/panic-probe-version.py +92 -0
- package/scripts/typedb-probes/panic-probe.py +77 -0
- package/scripts/typedb-scratch.sh +73 -0
- package/scripts/urls-lint.sh +58 -0
- package/scripts/w4-rubric.ts +96 -12
- package/scripts/wf-check.mjs +75 -0
- package/skills/ai-ui/SKILL.md +1 -1
- package/skills/astro/SKILL.md +177 -132
- package/skills/astro/add-content-collection.md +29 -13
- package/skills/astro/check-build.md +19 -13
- package/skills/astro/create-component.md +36 -9
- package/skills/astro/create-page.md +39 -10
- package/skills/astro/optimize-performance.md +36 -8
- package/skills/cli/SKILL.md +254 -220
- package/skills/dev/SKILL.md +1 -1
- package/skills/directory-autofill/SKILL.md +68 -0
- package/skills/docs/SKILL.md +4 -4
- package/skills/mcp/SKILL.md +186 -156
- package/skills/promise-make/SKILL.md +2 -2
- package/skills/puck/SKILL.md +465 -250
- package/skills/react19/SKILL.md +196 -296
- package/skills/reactflow/SKILL.md +387 -412
- package/skills/sdk/SKILL.md +167 -106
- package/skills/shadcn/SKILL.md +245 -201
- package/skills/signal/SKILL.md +5 -0
- package/skills/sui/SKILL.md +6 -1
- package/skills/tutorial/SKILL.md +15 -0
- package/skills/typedb/SKILL.md +427 -217
- package/skills/typedb/examples/python-patterns.py +11 -4
- package/skills/typedb/examples/query-patterns.tql +8 -3
- package/skills/typedb/examples/schema-patterns.tql +4 -2
- package/skills/typedb/reference/migration-2x-3x.md +19 -1
- package/skills/typedb/reference/python-driver.md +17 -1
- package/skills/voice/SKILL.md +117 -0
- package/skills/writer/SKILL.md +29 -12
- package/templates/template-feature.md +26 -1
- package/templates/template-tests.md +4 -0
- package/templates/template-todo.md +65 -10
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# do-ui-gate.sh — IC9: executable proof rows for a promise's screens/states.
|
|
3
|
+
#
|
|
4
|
+
# Reads the ```ui fenced blocks out of text/<slug>-ui.md — same fenced-block
|
|
5
|
+
# convention as do-walk.sh's ```walk blocks — and executes them:
|
|
6
|
+
#
|
|
7
|
+
# ```ui
|
|
8
|
+
# row: 1
|
|
9
|
+
# component: LoginButton
|
|
10
|
+
# state: default
|
|
11
|
+
# route: /login
|
|
12
|
+
# expect_text: "Sign in" # optional — substring in the served HTML
|
|
13
|
+
# expect_cmd: test -f x.tsx # optional bash, exit 0 = pass
|
|
14
|
+
# check: renders a blue primary button
|
|
15
|
+
# ```
|
|
16
|
+
#
|
|
17
|
+
# Rows may be moved under a `## Deferred` heading, but that ONLY skips them
|
|
18
|
+
# if the Deferred section's body contains a line `promise: <slug>` (ties the
|
|
19
|
+
# deferral to a tracked promise — otherwise the gate FAILs; UI work can't be
|
|
20
|
+
# silently waived).
|
|
21
|
+
#
|
|
22
|
+
# Usage:
|
|
23
|
+
# do-ui-gate.sh <slug> [--list] # print parsed rows (active vs deferred), run nothing
|
|
24
|
+
# do-ui-gate.sh <slug> # run active rows against a live/self-started dev server
|
|
25
|
+
# do-ui-gate.sh --self-test
|
|
26
|
+
#
|
|
27
|
+
# Env:
|
|
28
|
+
# UI_GATE_BASE_URL force the base URL (skips sentinel detection + self-start)
|
|
29
|
+
#
|
|
30
|
+
# Base URL resolution (when UI_GATE_BASE_URL unset):
|
|
31
|
+
# 1. derive port = 4400 + (cksum of slug) % 100 (same derivation as do-auto.sh's
|
|
32
|
+
# _sentinel_port, IC1) and check .do-worktrees/<slug>/.do-loop-running for a
|
|
33
|
+
# live pid on that port
|
|
34
|
+
# 2. else self-start `astro dev --port <port>` from one.ie/web, poll for 200
|
|
35
|
+
# up to 60s, run rows, then kill the server it started
|
|
36
|
+
#
|
|
37
|
+
# Exit codes: 0 = every active row passed (or --list) · 1 = a row failed, or an
|
|
38
|
+
# undeferred waive (rows after ## Deferred with no `promise: <slug>` line).
|
|
39
|
+
set -euo pipefail
|
|
40
|
+
|
|
41
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
42
|
+
|
|
43
|
+
# ---------- parse helpers (copied from do-walk.sh, fence tag `ui`) ----------
|
|
44
|
+
|
|
45
|
+
# extract_stops <file> → lines "N<TAB>key: value" for every ```ui block
|
|
46
|
+
extract_stops() {
|
|
47
|
+
awk '
|
|
48
|
+
/^```ui[[:space:]]*$/ { inb=1; n++; next }
|
|
49
|
+
/^```[[:space:]]*$/ { inb=0; next }
|
|
50
|
+
inb { print n "\t" $0 }
|
|
51
|
+
' "$1"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# field <stops> <n> <key> → value, empty if absent (fence-agnostic)
|
|
55
|
+
field() {
|
|
56
|
+
printf '%s\n' "$1" | awk -F'\t' -v n="$2" -v k="$3" '
|
|
57
|
+
$1 == n {
|
|
58
|
+
line = $2
|
|
59
|
+
prefix = k ": "
|
|
60
|
+
if (index(line, prefix) == 1) {
|
|
61
|
+
v = substr(line, length(prefix) + 1)
|
|
62
|
+
sub(/[ \t]+$/, "", v)
|
|
63
|
+
len = length(v)
|
|
64
|
+
if (len >= 2) {
|
|
65
|
+
first = substr(v, 1, 1); last = substr(v, len, 1)
|
|
66
|
+
if ((first == "\"" && last == "\"") || (first == "'"'"'" && last == "'"'"'")) {
|
|
67
|
+
v = substr(v, 2, len - 2)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
print v
|
|
71
|
+
exit
|
|
72
|
+
}
|
|
73
|
+
}'
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# _sentinel_port <slug> — must match do-auto.sh's derivation exactly
|
|
77
|
+
_sentinel_port() {
|
|
78
|
+
echo $(( 4400 + $(cksum <<<"$1" | cut -d' ' -f1) % 100 ))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# split_rows <file> <stops> <ids> → prints two files: $1.active $1.deferred
|
|
82
|
+
# (as global vars ACTIVE_IDS / DEFERRED_IDS, newline-separated row numbers)
|
|
83
|
+
split_deferred() { # $1 = ui.md file, $2 = stops
|
|
84
|
+
local file="$1"
|
|
85
|
+
awk '/^##[[:space:]]*Deferred/{print NR; exit}' "$file"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
deferred_section_names_promise() { # $1 = file, $2 = slug
|
|
89
|
+
local file="$1" slug="$2" ln
|
|
90
|
+
ln="$(awk '/^##[[:space:]]*Deferred/{print NR; exit}' "$file")"
|
|
91
|
+
[ -z "$ln" ] && return 1
|
|
92
|
+
tail -n "+$ln" "$file" | grep -qE "^promise:[[:space:]]*${slug}[[:space:]]*$"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
row_line_number() { # $1 = file, $2 = row n → line number of the ```ui fence that starts row n
|
|
96
|
+
awk -v want="$2" '
|
|
97
|
+
/^```ui[[:space:]]*$/ { n++; if (n == want) { print NR; exit } }
|
|
98
|
+
' "$1"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# ---------- self-test ----------
|
|
102
|
+
|
|
103
|
+
if [ "${1:-}" = "--self-test" ]; then
|
|
104
|
+
tmpdir="$(mktemp -d)"; trap 'rm -rf "$tmpdir"' EXIT
|
|
105
|
+
fails=0
|
|
106
|
+
|
|
107
|
+
# fixture 1: one passing row, one failing row (both expect_cmd, no route) — expect exit 1
|
|
108
|
+
f1="$tmpdir/f1-ui.md"
|
|
109
|
+
cat > "$f1" <<'EOF'
|
|
110
|
+
## Row 1
|
|
111
|
+
```ui
|
|
112
|
+
row: 1
|
|
113
|
+
component: A
|
|
114
|
+
state: default
|
|
115
|
+
expect_cmd: true
|
|
116
|
+
check: always passes
|
|
117
|
+
```
|
|
118
|
+
## Row 2
|
|
119
|
+
```ui
|
|
120
|
+
row: 2
|
|
121
|
+
component: B
|
|
122
|
+
state: default
|
|
123
|
+
expect_cmd: false
|
|
124
|
+
check: always fails
|
|
125
|
+
```
|
|
126
|
+
EOF
|
|
127
|
+
if UI_GATE_SELF_TEST=1 bash "${BASH_SOURCE[0]}" _fixture_f1 --file "$f1" >/dev/null 2>&1; then
|
|
128
|
+
echo "FAIL: fixture1 expected exit 1 (one failing row), got 0"; fails=$((fails+1))
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# fixture 2: failing row moved under ## Deferred, no `promise: <slug>` line — expect exit 1 (undeferred waive)
|
|
132
|
+
f2="$tmpdir/f2-ui.md"
|
|
133
|
+
cat > "$f2" <<'EOF'
|
|
134
|
+
## Row 1
|
|
135
|
+
```ui
|
|
136
|
+
row: 1
|
|
137
|
+
component: A
|
|
138
|
+
state: default
|
|
139
|
+
expect_cmd: true
|
|
140
|
+
check: always passes
|
|
141
|
+
```
|
|
142
|
+
## Deferred
|
|
143
|
+
Some other note, no promise line here.
|
|
144
|
+
```ui
|
|
145
|
+
row: 2
|
|
146
|
+
component: B
|
|
147
|
+
state: default
|
|
148
|
+
expect_cmd: false
|
|
149
|
+
check: always fails
|
|
150
|
+
```
|
|
151
|
+
EOF
|
|
152
|
+
if UI_GATE_SELF_TEST=1 bash "${BASH_SOURCE[0]}" _fixture_f2 --file "$f2" >/dev/null 2>&1; then
|
|
153
|
+
echo "FAIL: fixture2 expected exit 1 (undeferred waive), got 0"; fails=$((fails+1))
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# fixture 3: same deferred row, but section names `promise: <slug>` — expect exit 0
|
|
157
|
+
f3="$tmpdir/f3-ui.md"
|
|
158
|
+
cat > "$f3" <<'EOF'
|
|
159
|
+
## Row 1
|
|
160
|
+
```ui
|
|
161
|
+
row: 1
|
|
162
|
+
component: A
|
|
163
|
+
state: default
|
|
164
|
+
expect_cmd: true
|
|
165
|
+
check: always passes
|
|
166
|
+
```
|
|
167
|
+
## Deferred
|
|
168
|
+
promise: _fixture_f3
|
|
169
|
+
```ui
|
|
170
|
+
row: 2
|
|
171
|
+
component: B
|
|
172
|
+
state: default
|
|
173
|
+
expect_cmd: false
|
|
174
|
+
check: always fails
|
|
175
|
+
```
|
|
176
|
+
EOF
|
|
177
|
+
if ! UI_GATE_SELF_TEST=1 bash "${BASH_SOURCE[0]}" _fixture_f3 --file "$f3" >/dev/null 2>&1; then
|
|
178
|
+
echo "FAIL: fixture3 expected exit 0 (properly deferred), got nonzero"; fails=$((fails+1))
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
echo "[ui-gate] self-test: 3 fixtures, $fails failed"
|
|
182
|
+
exit "$fails"
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# ---------- args ----------
|
|
186
|
+
|
|
187
|
+
slug="" list=0 file_override=""
|
|
188
|
+
while [ "$#" -gt 0 ]; do
|
|
189
|
+
case "$1" in
|
|
190
|
+
--list) list=1; shift ;;
|
|
191
|
+
--file) [ "$#" -ge 2 ] || { echo "UI-GATE: --file requires a value"; exit 1; }
|
|
192
|
+
file_override="$2"; shift 2 ;; # internal: used by --self-test fixtures
|
|
193
|
+
-*) echo "UI-GATE: unknown flag $1"; exit 1 ;;
|
|
194
|
+
*) slug="$1"; shift ;;
|
|
195
|
+
esac
|
|
196
|
+
done
|
|
197
|
+
[ -n "$slug" ] || { echo "UI-GATE: usage: do-ui-gate.sh <slug> [--list]"; exit 1; }
|
|
198
|
+
|
|
199
|
+
file="${file_override:-$ROOT/text/${slug}-ui.md}"
|
|
200
|
+
[ -f "$file" ] || { echo "UI-GATE: no ui doc at text/${slug}-ui.md"; exit 1; }
|
|
201
|
+
|
|
202
|
+
stops="$(extract_stops "$file")"
|
|
203
|
+
[ -n "$stops" ] || { echo "UI-GATE: text/${slug}-ui.md has no \`\`\`ui blocks"; exit 1; }
|
|
204
|
+
ids="$(printf '%s\n' "$stops" | awk -F'\t' '{print $1}' | sort -un)"
|
|
205
|
+
|
|
206
|
+
deferred_at_line="$(awk '/^##[[:space:]]*Deferred/{print NR; exit}' "$file")"
|
|
207
|
+
|
|
208
|
+
active_ids=""; deferred_ids=""
|
|
209
|
+
for n in $ids; do
|
|
210
|
+
rln="$(row_line_number "$file" "$n")"
|
|
211
|
+
if [ -n "$deferred_at_line" ] && [ "$rln" -gt "$deferred_at_line" ]; then
|
|
212
|
+
deferred_ids="$deferred_ids $n"
|
|
213
|
+
else
|
|
214
|
+
active_ids="$active_ids $n"
|
|
215
|
+
fi
|
|
216
|
+
done
|
|
217
|
+
|
|
218
|
+
if [ -n "$deferred_ids" ]; then
|
|
219
|
+
if ! deferred_section_names_promise "$file" "$slug"; then
|
|
220
|
+
echo "UI-GATE: FAIL — rows$deferred_ids are under ## Deferred but that section does not name \`promise: $slug\` — UI work cannot be silently waived"
|
|
221
|
+
exit 1
|
|
222
|
+
fi
|
|
223
|
+
echo "UI-GATE: rows$deferred_ids deferred to promise: $slug (skipped)"
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
echo "UI-GATE: $slug · $(printf '%s\n' "$active_ids" | wc -w | tr -d ' ') active row(s)"
|
|
227
|
+
|
|
228
|
+
# ---------- list mode ----------
|
|
229
|
+
|
|
230
|
+
if [ "$list" -eq 1 ]; then
|
|
231
|
+
for n in $active_ids; do
|
|
232
|
+
c="$(field "$stops" "$n" component)"; s="$(field "$stops" "$n" state)"
|
|
233
|
+
r="$(field "$stops" "$n" route)"; ck="$(field "$stops" "$n" check)"
|
|
234
|
+
echo " active $n. $c/$s${r:+ → $r}"
|
|
235
|
+
[ -n "$ck" ] && echo " $ck"
|
|
236
|
+
done
|
|
237
|
+
for n in $deferred_ids; do
|
|
238
|
+
c="$(field "$stops" "$n" component)"; s="$(field "$stops" "$n" state)"
|
|
239
|
+
echo " deferred $n. $c/$s"
|
|
240
|
+
done
|
|
241
|
+
exit 0
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
# ---------- zero-network short-circuit (self-test fixtures) ----------
|
|
245
|
+
# If no active row needs a route/expect_text (only expect_cmd rows), skip the
|
|
246
|
+
# base-URL bootstrap entirely — keeps --self-test zero-network.
|
|
247
|
+
needs_network=0
|
|
248
|
+
for n in $active_ids; do
|
|
249
|
+
r="$(field "$stops" "$n" route)"
|
|
250
|
+
[ -n "$r" ] && needs_network=1
|
|
251
|
+
done
|
|
252
|
+
|
|
253
|
+
base=""
|
|
254
|
+
started_server=0
|
|
255
|
+
server_pid=""
|
|
256
|
+
|
|
257
|
+
if [ "$needs_network" -eq 1 ]; then
|
|
258
|
+
base="${UI_GATE_BASE_URL:-}"
|
|
259
|
+
port="$(_sentinel_port "$slug")"
|
|
260
|
+
|
|
261
|
+
if [ -z "$base" ]; then
|
|
262
|
+
sentinel="$ROOT/.do-worktrees/$slug/.do-loop-running"
|
|
263
|
+
if [ -f "$sentinel" ]; then
|
|
264
|
+
spid="$(grep -o '"pid":[0-9]*' "$sentinel" | head -1 | cut -d: -f2 || true)"
|
|
265
|
+
sport="$(grep -o '"port":[0-9]*' "$sentinel" | head -1 | cut -d: -f2 || true)"
|
|
266
|
+
if [ -n "$spid" ] && kill -0 "$spid" 2>/dev/null && [ -n "$sport" ]; then
|
|
267
|
+
if curl -sf -o /dev/null --max-time 3 "http://localhost:$sport/" 2>/dev/null; then
|
|
268
|
+
base="http://localhost:$sport"
|
|
269
|
+
fi
|
|
270
|
+
fi
|
|
271
|
+
fi
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
if [ -z "$base" ]; then
|
|
275
|
+
echo "UI-GATE: self-starting dev server on port $port"
|
|
276
|
+
( cd "$ROOT/one.ie/web" && exec ./node_modules/.bin/astro dev --port "$port" ) >/tmp/ui-gate-$slug.log 2>&1 &
|
|
277
|
+
server_pid=$!
|
|
278
|
+
started_server=1
|
|
279
|
+
base="http://localhost:$port"
|
|
280
|
+
ok=0
|
|
281
|
+
for _ in $(seq 1 30); do
|
|
282
|
+
if curl -sf -o /dev/null --max-time 2 "$base/" 2>/dev/null; then ok=1; break; fi
|
|
283
|
+
sleep 2
|
|
284
|
+
done
|
|
285
|
+
if [ "$ok" -ne 1 ]; then
|
|
286
|
+
echo "UI-GATE: FAIL — dev server on $base did not come up within 60s"
|
|
287
|
+
kill "$server_pid" 2>/dev/null || true
|
|
288
|
+
exit 1
|
|
289
|
+
fi
|
|
290
|
+
fi
|
|
291
|
+
echo "UI-GATE: base=$base"
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
cleanup() {
|
|
295
|
+
if [ "$started_server" -eq 1 ] && [ -n "$server_pid" ]; then
|
|
296
|
+
kill "$server_pid" 2>/dev/null || true
|
|
297
|
+
fi
|
|
298
|
+
}
|
|
299
|
+
trap cleanup EXIT
|
|
300
|
+
|
|
301
|
+
# ---------- run ----------
|
|
302
|
+
|
|
303
|
+
fail=0
|
|
304
|
+
for n in $active_ids; do
|
|
305
|
+
c="$(field "$stops" "$n" component)"; s="$(field "$stops" "$n" state)"
|
|
306
|
+
r="$(field "$stops" "$n" route)"
|
|
307
|
+
et="$(field "$stops" "$n" expect_text)"
|
|
308
|
+
ec="$(field "$stops" "$n" expect_cmd)"
|
|
309
|
+
|
|
310
|
+
ok=1; why=""
|
|
311
|
+
if [ -n "$r" ] && [ -n "$base" ]; then
|
|
312
|
+
body="$(curl -s --max-time 20 "$base$r" 2>/dev/null || true)"
|
|
313
|
+
if [ -z "$body" ]; then ok=0; why="no response from $base$r"; fi
|
|
314
|
+
if [ "$ok" -eq 1 ] && [ -n "$et" ] && ! printf '%s' "$body" | grep -qiF "$et"; then
|
|
315
|
+
ok=0; why="text '$et' not in served HTML"
|
|
316
|
+
fi
|
|
317
|
+
fi
|
|
318
|
+
if [ "$ok" -eq 1 ] && [ -n "$ec" ]; then
|
|
319
|
+
if ! (cd "$ROOT" && bash -c "$ec" >/dev/null 2>&1); then ok=0; why="expect_cmd failed: $ec"; fi
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
if [ "$ok" -eq 1 ]; then
|
|
323
|
+
echo "UI-GATE: ok row $n — $c/$s"
|
|
324
|
+
else
|
|
325
|
+
echo "UI-GATE: FAIL row $n — $c/$s — $why"
|
|
326
|
+
fail=1
|
|
327
|
+
fi
|
|
328
|
+
done
|
|
329
|
+
|
|
330
|
+
echo ""
|
|
331
|
+
if [ "$fail" -eq 1 ]; then echo "UI-GATE: fail"; exit 1; fi
|
|
332
|
+
echo "UI-GATE: pass"; exit 0
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# do-untracked-gate.sh — a deliverable that exists on disk but is NOT in git has
|
|
3
|
+
# not shipped. This gate is the only check in the harness that can say so.
|
|
4
|
+
#
|
|
5
|
+
# WHY (the real failure, 2026-07-28, plan `factory`): two shipped deliverables —
|
|
6
|
+
# packages/sdk/src/fn-allowlist.ts (C8's single source of truth)
|
|
7
|
+
# one.ie/web/tests/unit/fn-entity-args.test.ts (C7's proof)
|
|
8
|
+
# sat UNTRACKED for hours while every acceptance check stayed GREEN, because
|
|
9
|
+
# every check reads the WORKING TREE. Built, tested, passing, and on a path to
|
|
10
|
+
# never reach main. A human running `git status` caught it; no gate did.
|
|
11
|
+
#
|
|
12
|
+
# That is the promise's own failure class — "built but not wired" — one level
|
|
13
|
+
# lower than it was being looked for. factory-check.sh asks "is it live in the
|
|
14
|
+
# substrate?"; this asks the question underneath it: "is it even in the repo?"
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# do-untracked-gate.sh <slug> [--verbose] # e.g. do-untracked-gate.sh factory
|
|
18
|
+
# do-untracked-gate.sh --self-test # temp git fixtures, zero network
|
|
19
|
+
#
|
|
20
|
+
# Exit: 0 = green (every existing deliverable file is tracked)
|
|
21
|
+
# 1 = red (a deliverable exists on disk and is untracked or gitignored)
|
|
22
|
+
# 3 = cannot run (not a git repo, or neither text/<slug>.md nor -todo.md)
|
|
23
|
+
# 4 = bad usage
|
|
24
|
+
#
|
|
25
|
+
# 1 vs 3 is the same distinction factory-check.sh draws: red sends someone to
|
|
26
|
+
# `git add`; cannot-run sends them to fix the environment, and proves nothing
|
|
27
|
+
# either way. Never widen a cannot-run into a red to make a chain simpler.
|
|
28
|
+
#
|
|
29
|
+
# WHAT IT READS — the `deliverables:` frontmatter block of BOTH text/<slug>.md
|
|
30
|
+
# (the promise: `- item:` / `accept:` prose) and text/<slug>-todo.md (the plan:
|
|
31
|
+
# `- <kind>: "path — description"`). Paths live in prose, so every path-shaped
|
|
32
|
+
# token in the block is a candidate. Comments inside the block are scanned too:
|
|
33
|
+
# a path worth naming in the schedule is a path worth having in git.
|
|
34
|
+
#
|
|
35
|
+
# WHAT IT DOES NOT DO — it never reports a MISSING file. A deliverable path that
|
|
36
|
+
# does not exist is simply not built yet; that is factory-check.sh's / the
|
|
37
|
+
# promise proof's job. This gate has exactly one question: of the files that
|
|
38
|
+
# DO exist, is any of them invisible to git?
|
|
39
|
+
set -uo pipefail
|
|
40
|
+
|
|
41
|
+
ROOT="${DO_UNTRACKED_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)}"
|
|
42
|
+
VERBOSE=0
|
|
43
|
+
say() { [ "$VERBOSE" -eq 1 ] && echo " $*" >&2; return 0; }
|
|
44
|
+
red() { echo "RED $1" >&2; return 1; }
|
|
45
|
+
cannot() { echo "CANNOT RUN: $1" >&2; return 3; }
|
|
46
|
+
|
|
47
|
+
# Paths that are legitimately absent from git and are never a deliverable.
|
|
48
|
+
# A `#` comment inside a deliverables block may name one (e.g. an assumes-style
|
|
49
|
+
# credential file); reporting those as red would be noise, not a finding.
|
|
50
|
+
# `.env` / `.dev.vars` are named inside real `accept:` command lines (sui-upgrade's
|
|
51
|
+
# accept sources a key from pay/backend/.dev.vars) — a local secrets file is never
|
|
52
|
+
# a deliverable, and flagging it as "can never ship" is noise that trains the
|
|
53
|
+
# reader to ignore the gate.
|
|
54
|
+
SKIP_RE='(^|/)(node_modules|dist|build|\.wrangler|\.astro)/|(^|/)\.env|(^|/)\.dev\.vars|\.local\.md$|\.keys\.local'
|
|
55
|
+
|
|
56
|
+
# ── the deliverables: frontmatter block ──────────────────────────────────
|
|
57
|
+
# First frontmatter block only. Opens on `^deliverables:` — WITH an optional
|
|
58
|
+
# trailing comment: text/marketplace-deal.md and text/template-feature.md both
|
|
59
|
+
# write `deliverables: # THE SCHEDULE …`, and an anchored
|
|
60
|
+
# `[[:space:]]*$` never opened the block for them. That produced the worst
|
|
61
|
+
# possible verdict — `ok (0 files checked)`, a green earned by reading nothing.
|
|
62
|
+
#
|
|
63
|
+
# Closes on the first column-0 KEY (`assumes:`, `ux_before:`, `proof:`), not on
|
|
64
|
+
# any column-0 non-space char: the equally-valid YAML style puts `- item:` at
|
|
65
|
+
# column 0, and closing on `-` would end the block at its first entry — the same
|
|
66
|
+
# read-nothing green by a different door. Blank lines and comments inside the
|
|
67
|
+
# block are tolerated; the closing `---` ends it via the fm counter.
|
|
68
|
+
deliverables_block() { # $1 = file
|
|
69
|
+
awk '
|
|
70
|
+
/^---[[:space:]]*$/ { fm++; if (fm>=2) exit; next }
|
|
71
|
+
fm!=1 { next }
|
|
72
|
+
/^deliverables:[[:space:]]*(#.*)?$/ { ind=1; next }
|
|
73
|
+
ind==1 && /^[A-Za-z_][A-Za-z0-9_.-]*:/ { ind=0 }
|
|
74
|
+
ind==1 { print }
|
|
75
|
+
' "$1"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# ── candidate path extraction ────────────────────────────────────────────
|
|
79
|
+
# A candidate is a repo-relative-looking token with a file extension. It must
|
|
80
|
+
# contain a `/` — a bare `one.tql` or `do-walk.sh` names a file the writer did
|
|
81
|
+
# not locate, and resolving it by basename alone across 7k files invites a
|
|
82
|
+
# false positive worse than the miss.
|
|
83
|
+
#
|
|
84
|
+
# Globs (`text/template-*.md`), brace sets and URLs are dropped: a glob is not a
|
|
85
|
+
# path, and a URL is not in the repo.
|
|
86
|
+
candidates() { # stdin = block text
|
|
87
|
+
grep -oE '[A-Za-z0-9_@.][A-Za-z0-9_@./+-]*\.[A-Za-z0-9]{1,6}' \
|
|
88
|
+
| grep '/' \
|
|
89
|
+
| grep -vE '://' \
|
|
90
|
+
| grep -vE "$SKIP_RE" \
|
|
91
|
+
| sort -u
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# ── resolution ───────────────────────────────────────────────────────────
|
|
95
|
+
# Two shapes appear in real plans, and the gate is worthless unless it handles
|
|
96
|
+
# both — the 2026-07-28 failure had one of each:
|
|
97
|
+
# (a) repo-relative packages/sdk/src/fn-allowlist.ts → direct hit
|
|
98
|
+
# (b) package-relative tests/unit/fn-entity-args.test.ts → suffix match
|
|
99
|
+
# (that file lives at one.ie/web/tests/unit/…)
|
|
100
|
+
# (b) is resolved against the repo's file universe (tracked + untracked-not-
|
|
101
|
+
# ignored, one `git ls-files` call). An AMBIGUOUS suffix — more than one hit —
|
|
102
|
+
# is NOT skipped: every hit is checked. "Ambiguous → skip" is exactly how this
|
|
103
|
+
# bug walks through the gate a second time.
|
|
104
|
+
resolve() { # $1 = candidate; prints 0+ repo-relative paths
|
|
105
|
+
local cand="$1"
|
|
106
|
+
if [ -e "$ROOT/$cand" ]; then printf '%s\n' "$cand"; return 0; fi
|
|
107
|
+
printf '%s\n' "$INDEX" | awk -v c="$cand" '
|
|
108
|
+
BEGIN { n=length(c) }
|
|
109
|
+
$0==c { print; next }
|
|
110
|
+
length($0) > n && substr($0, length($0)-n) == "/" c { print }
|
|
111
|
+
'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
check() { # $1 = slug
|
|
115
|
+
local slug="$1"
|
|
116
|
+
local promise="$ROOT/text/$slug.md" todo="$ROOT/text/$slug-todo.md"
|
|
117
|
+
local block="" found=0 bad=0 checked=0 unresolved=0 cand path
|
|
118
|
+
|
|
119
|
+
git -C "$ROOT" rev-parse --git-dir >/dev/null 2>&1 \
|
|
120
|
+
|| { cannot "$ROOT is not a git repo — trackedness is unanswerable here"; return 3; }
|
|
121
|
+
|
|
122
|
+
if [ -f "$promise" ]; then block+=$(deliverables_block "$promise")$'\n'; found=1
|
|
123
|
+
else say "no promise at text/$slug.md"; fi
|
|
124
|
+
if [ -f "$todo" ]; then block+=$(deliverables_block "$todo")$'\n'; found=1
|
|
125
|
+
else say "no todo at text/$slug-todo.md"; fi
|
|
126
|
+
[ "$found" -eq 1 ] || { cannot "$slug — neither text/$slug.md nor text/$slug-todo.md exists"; return 3; }
|
|
127
|
+
|
|
128
|
+
INDEX="$(git -C "$ROOT" ls-files --cached --others --exclude-standard)"
|
|
129
|
+
|
|
130
|
+
while IFS= read -r cand; do
|
|
131
|
+
[ -z "$cand" ] && continue
|
|
132
|
+
local hits; hits="$(resolve "$cand")"
|
|
133
|
+
if [ -z "$hits" ]; then
|
|
134
|
+
unresolved=$((unresolved+1)); say "skip (not on disk): $cand"; continue
|
|
135
|
+
fi
|
|
136
|
+
while IFS= read -r path; do
|
|
137
|
+
[ -z "$path" ] && continue
|
|
138
|
+
[ -d "$ROOT/$path" ] && { say "skip (directory): $path"; continue; }
|
|
139
|
+
checked=$((checked+1))
|
|
140
|
+
if git -C "$ROOT" ls-files --error-unmatch -- "$path" >/dev/null 2>&1; then
|
|
141
|
+
say "tracked: $path"; continue
|
|
142
|
+
fi
|
|
143
|
+
# tracked-first is deliberate: a file that is both tracked and matched by
|
|
144
|
+
# .gitignore is tracked, and git ships it.
|
|
145
|
+
if git -C "$ROOT" check-ignore -q -- "$path" 2>/dev/null; then
|
|
146
|
+
red "GITIGNORED deliverable: $path — exists, matched by .gitignore, CAN NEVER SHIP (named by $slug)"
|
|
147
|
+
else
|
|
148
|
+
red "UNTRACKED deliverable: $path — exists on disk, absent from git; \`git add $path\` or it never reaches main"
|
|
149
|
+
fi
|
|
150
|
+
bad=$((bad+1))
|
|
151
|
+
done <<<"$hits"
|
|
152
|
+
done < <(printf '%s' "$block" | candidates)
|
|
153
|
+
|
|
154
|
+
if [ "$bad" -gt 0 ]; then
|
|
155
|
+
echo "[untracked-gate] $slug — $bad/$checked existing deliverable file(s) not in git" >&2
|
|
156
|
+
return 1
|
|
157
|
+
fi
|
|
158
|
+
echo "[untracked-gate] $slug — ok ($checked existing deliverable file(s) tracked, $unresolved path(s) not on disk / not resolvable)"
|
|
159
|
+
return 0
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# ── self-test ────────────────────────────────────────────────────────────
|
|
163
|
+
# Drives a GREEN fixture and a RED fixture through a real temp git repo. No
|
|
164
|
+
# commit is needed: `git ls-files --error-unmatch` answers from the INDEX, so
|
|
165
|
+
# `git add` alone makes a file tracked and the test needs no user.name/email.
|
|
166
|
+
# The red fixture also exercises the package-relative suffix resolver — the
|
|
167
|
+
# shape that carried half the real 2026-07-28 failure.
|
|
168
|
+
self_test() {
|
|
169
|
+
local fails=0 rc out
|
|
170
|
+
dir="$(mktemp -d)"; trap 'rm -rf "$dir"' EXIT
|
|
171
|
+
git -C "$dir" init -q
|
|
172
|
+
mkdir -p "$dir/text" "$dir/packages/sdk/src" "$dir/web/tests/unit"
|
|
173
|
+
|
|
174
|
+
# A promise + todo naming two deliverables: one repo-relative, one package-relative.
|
|
175
|
+
printf -- '---\nslug: green\ndeliverables:\n - item: "the allowlist"\n accept: "test -f packages/sdk/src/fn-allowlist.ts"\n---\n' \
|
|
176
|
+
>"$dir/text/green.md"
|
|
177
|
+
printf -- '---\nslug: green\ndeliverables:\n - test: "tests/unit/fn-entity-args.test.ts — entity-param binding proven (C7)"\n\nux_before: "x"\n---\n' \
|
|
178
|
+
>"$dir/text/green-todo.md"
|
|
179
|
+
sed 's/green/red/' "$dir/text/green.md" >"$dir/text/red.md"
|
|
180
|
+
sed 's/green/red/' "$dir/text/green-todo.md" >"$dir/text/red-todo.md"
|
|
181
|
+
|
|
182
|
+
: >"$dir/packages/sdk/src/fn-allowlist.ts"
|
|
183
|
+
: >"$dir/web/tests/unit/fn-entity-args.test.ts"
|
|
184
|
+
git -C "$dir" add text packages/sdk/src/fn-allowlist.ts >/dev/null 2>&1
|
|
185
|
+
|
|
186
|
+
# RED: the package-relative test file exists but was never `git add`ed —
|
|
187
|
+
# the exact 2026-07-28 shape.
|
|
188
|
+
ROOT="$dir" VERBOSE=0 check red >/dev/null 2>&1; rc=$?
|
|
189
|
+
[ "$rc" -eq 1 ] || { echo "FAIL red fixture: expected exit 1, got $rc"; fails=$((fails+1)); }
|
|
190
|
+
# captured, never piped into `grep -q`: pipefail + grep's early exit SIGPIPEs
|
|
191
|
+
# the producer and reports 141 for a pipeline that actually matched.
|
|
192
|
+
out="$(ROOT="$dir" VERBOSE=0 check red 2>&1)"
|
|
193
|
+
case "$out" in *fn-entity-args.test.ts*) ;; *)
|
|
194
|
+
echo "FAIL red fixture: reason must name the untracked file"; fails=$((fails+1)) ;; esac
|
|
195
|
+
|
|
196
|
+
# GREEN: same fixture once the file is tracked.
|
|
197
|
+
git -C "$dir" add web/tests/unit/fn-entity-args.test.ts >/dev/null 2>&1
|
|
198
|
+
ROOT="$dir" VERBOSE=0 check green >/dev/null 2>&1; rc=$?
|
|
199
|
+
[ "$rc" -eq 0 ] || { echo "FAIL green fixture: expected exit 0, got $rc"; fails=$((fails+1)); }
|
|
200
|
+
|
|
201
|
+
# GITIGNORED: a deliverable git can never ship must be loud, not silent.
|
|
202
|
+
printf 'packages/sdk/src/fn-allowlist.ts\n' >"$dir/.gitignore"
|
|
203
|
+
git -C "$dir" rm --cached -q packages/sdk/src/fn-allowlist.ts >/dev/null 2>&1
|
|
204
|
+
out="$(ROOT="$dir" VERBOSE=0 check green 2>&1)"
|
|
205
|
+
case "$out" in *GITIGNORED*) ;; *)
|
|
206
|
+
echo "FAIL gitignored fixture: expected a GITIGNORED reason"; fails=$((fails+1)) ;; esac
|
|
207
|
+
|
|
208
|
+
# BLOCK BOUNDARY: a `deliverables: # trailing comment` header with its entries
|
|
209
|
+
# at COLUMN 0. Both shapes exist in text/ and both used to yield an empty block
|
|
210
|
+
# — i.e. exit 0 having checked nothing. The fixture must go RED.
|
|
211
|
+
mkdir -p "$dir/text" "$dir/src"
|
|
212
|
+
: >"$dir/src/lonely.ts"
|
|
213
|
+
printf -- '---\nslug: shape\ndeliverables: # THE SCHEDULE\n- item: "a thing"\n accept: "test -f src/lonely.ts"\n\nproof: "x"\n---\n' \
|
|
214
|
+
>"$dir/text/shape.md"
|
|
215
|
+
ROOT="$dir" VERBOSE=0 check shape >/dev/null 2>&1; rc=$?
|
|
216
|
+
[ "$rc" -eq 1 ] || { echo "FAIL block-boundary: comment header + column-0 entries must still parse (got $rc)"; fails=$((fails+1)); }
|
|
217
|
+
|
|
218
|
+
# CANNOT RUN: no promise and no todo → 3, never 1.
|
|
219
|
+
ROOT="$dir" check nosuchslug >/dev/null 2>&1; rc=$?
|
|
220
|
+
[ "$rc" -eq 3 ] || { echo "FAIL missing-slug: expected exit 3, got $rc"; fails=$((fails+1)); }
|
|
221
|
+
|
|
222
|
+
echo "[untracked-gate] self-test: 6 assertions, $fails failed"
|
|
223
|
+
return "$fails"
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
case "${1:-}" in
|
|
227
|
+
--self-test) self_test; exit $? ;;
|
|
228
|
+
""|-*) echo "usage: do-untracked-gate.sh <slug> [--verbose] | --self-test" >&2; exit 4 ;;
|
|
229
|
+
esac
|
|
230
|
+
[ "${2:-}" = "--verbose" ] && VERBOSE=1
|
|
231
|
+
check "$1"
|