@oneie/claude 0.8.0 → 0.10.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/.claude-plugin/plugin.json +1 -1
- package/agents/abm-strategist.md +67 -1
- package/agents/ads-meta.md +67 -1
- package/agents/analyst.md +67 -1
- package/agents/animator.md +108 -0
- package/agents/architect.md +269 -20
- package/agents/brand-guardian.md +67 -1
- package/agents/brand-strategist.md +67 -1
- package/agents/campaign-content.md +67 -1
- package/agents/campaign-email.md +67 -1
- package/agents/campaign-sms.md +67 -1
- package/agents/campaign-social.md +67 -1
- package/agents/cco.md +83 -2
- package/agents/ceo.md +108 -11
- package/agents/chairman.md +197 -0
- package/agents/cmo.md +82 -2
- package/agents/community-greeter.md +67 -1
- package/agents/community-moderator.md +67 -1
- package/agents/compliance.md +67 -1
- package/agents/copywriter.md +67 -1
- package/agents/creative-strategist.md +67 -1
- package/agents/cro.md +81 -1
- package/agents/cto.md +266 -28
- package/agents/customer-interviewer.md +67 -1
- package/agents/customer-researcher.md +67 -1
- package/agents/customer-success-manager.md +67 -1
- package/agents/customer-trainer.md +67 -1
- package/agents/cxo.md +82 -1
- package/agents/demand-creator.md +67 -1
- package/agents/demo-mover.md +67 -1
- package/agents/demo-specialist.md +67 -1
- package/agents/demo-thai-family-law.md +67 -1
- package/agents/designer.md +67 -1
- package/agents/discovery-caller.md +67 -1
- package/agents/doctor.md +269 -0
- package/agents/educate-coach.md +67 -1
- package/agents/elevate-tutor.md +67 -1
- package/agents/email-lifecycle-marketer.md +67 -1
- package/agents/engage-specialist.md +67 -1
- package/agents/events-coordinator.md +67 -1
- package/agents/foundation-builder.md +67 -1
- package/agents/funnel-architect.md +67 -1
- package/agents/gift-creator.md +67 -1
- package/agents/google-ads.md +67 -1
- package/agents/guide.md +67 -1
- package/agents/helpdesk-dispatcher.md +67 -1
- package/agents/hook-specialist.md +67 -1
- package/agents/identify-optimizer.md +67 -1
- package/agents/implementer.md +313 -45
- package/agents/incident-commander.md +67 -1
- package/agents/insights-lead.md +87 -1
- package/agents/journey-runner.md +67 -1
- package/agents/linkedin-ads.md +67 -1
- package/agents/live-sales-chat.md +67 -1
- package/agents/market-researcher.md +67 -1
- package/agents/media-buyer.md +67 -1
- package/agents/memory-keeper.md +195 -0
- package/agents/movers-customer-researcher.md +67 -1
- package/agents/movers-foundation-builder.md +67 -1
- package/agents/movers-market-researcher.md +67 -1
- package/agents/movers-pricing-strategist.md +67 -1
- package/agents/nurture-architect.md +67 -1
- package/agents/offer-architect.md +67 -1
- package/agents/onboarder.md +67 -1
- package/agents/onboarding-specialist.md +67 -1
- package/agents/operations-dashboard.md +87 -1
- package/agents/perf-engineer.md +333 -37
- package/agents/playbook-writer.md +67 -1
- package/agents/plg-strategist.md +67 -1
- package/agents/positioning-architect.md +67 -1
- package/agents/press-officer.md +67 -1
- package/agents/pricing-strategist.md +67 -1
- package/agents/privacy-officer.md +67 -1
- package/agents/referral-manager.md +67 -1
- package/agents/refine-analyst.md +67 -1
- package/agents/release-manager.md +446 -39
- package/agents/renewals-upsell-rep.md +67 -1
- package/agents/review-engineer.md +319 -45
- package/agents/rewards-steward.md +67 -1
- package/agents/sales-call-coach.md +67 -1
- package/agents/sales-closer.md +67 -1
- package/agents/security-auditor.md +343 -48
- package/agents/sell-closer.md +67 -1
- package/agents/share-amplifier.md +67 -1
- package/agents/social-media-manager.md +67 -1
- package/agents/storyteller.md +301 -0
- package/agents/strategist.md +67 -1
- package/agents/strategy-aligner.md +67 -1
- package/agents/support-agent.md +67 -1
- package/agents/tagger.md +327 -0
- package/agents/tech-writer.md +195 -22
- package/agents/test-engineer.md +398 -29
- package/agents/tiktok-ads.md +67 -1
- package/agents/tracking-engineer.md +67 -1
- package/agents/trailkeeper.md +181 -0
- package/agents/upsell-strategist.md +67 -1
- package/agents/voice.md +67 -1
- package/agents/w1-recon.md +1 -1
- package/agents/w2-decide.md +1 -1
- package/agents/w3-edit.md +8 -2
- package/agents/w4-verify.md +13 -0
- package/agents/workflow-optimiser.md +81 -1
- package/commands/close.md +916 -160
- package/commands/deploy.md +102 -724
- package/commands/do.md +58 -2
- package/commands/sweep.md +159 -0
- package/commands/tasks.md +222 -0
- package/hooks/scripts/dev-only.sh +135 -0
- package/hooks/scripts/git-add-guard.sh +37 -2
- package/hooks/scripts/session-start.sh +32 -4
- package/package.json +1 -1
- package/rules/scripts.md +85 -0
- package/scripts/CLAUDE.md +315 -0
- package/scripts/ad-copy-lint.sh +656 -0
- package/scripts/agent-actor-parity.sh +129 -0
- package/scripts/blocks-manifest-cached.sh +100 -0
- package/scripts/chat-context-check.sh +89 -0
- package/scripts/chrome.mjs +18 -0
- package/scripts/close-metrics.sh +587 -0
- package/scripts/close-owner.sh +326 -0
- package/scripts/db-sync-lock-check.sh +116 -0
- package/scripts/deploy-emit.sh +311 -0
- package/scripts/deploy-gate-check.sh +155 -0
- package/scripts/deploy-ready.sh +78 -0
- package/scripts/deploy-record.sh +605 -0
- package/scripts/deploy-schema-check.sh +58 -0
- package/scripts/deploy.sh +393 -243
- package/scripts/do-auto.sh +127 -26
- package/scripts/do-board.sh +429 -0
- package/scripts/do-close.sh +1184 -0
- package/scripts/do-consumer-sweep.sh +18 -1
- package/scripts/do-decide.sh +476 -0
- package/scripts/do-fleet.sh +8 -2
- package/scripts/do-plan-json.mjs +110 -12
- package/scripts/do-prove-selftest.sh +108 -0
- package/scripts/do-prove.sh +86 -10
- package/scripts/do-rank.py +200 -3
- package/scripts/do-reconcile.sh +73 -12
- package/scripts/do-signal.sh +101 -23
- package/scripts/do-smoke.sh +18 -1
- package/scripts/do-w4-gates.sh +11 -1
- package/scripts/do-world-check.sh +153 -0
- package/scripts/download-stats.sh +172 -0
- package/scripts/factory-brief-check.sh +330 -0
- package/scripts/factory-check.sh +18 -1
- package/scripts/factory-close-check.sh +257 -0
- package/scripts/factory-emit.sh +211 -0
- package/scripts/factory-executor-check.mjs +353 -0
- package/scripts/factory-peak.sh +301 -0
- package/scripts/factory-repo.sh +71 -0
- package/scripts/factory-review-check.mjs +61 -0
- package/scripts/factory-tasks-check.sh +18 -1
- package/scripts/fixtures/factory-brief-real.md +44 -0
- package/scripts/flywheel-outcome.sh +63 -0
- package/scripts/gate-reaper-check.sh +98 -0
- package/scripts/gate-reaper.sh +9 -0
- package/scripts/gate-watchdog.sh +619 -0
- package/scripts/gc-content-check.sh +142 -0
- package/scripts/gh-traffic-capture.sh +153 -0
- package/scripts/govern-order-check.sh +202 -0
- package/scripts/governor-doors-check.sh +86 -5
- package/scripts/health.sh +448 -0
- package/scripts/id-inventory.mjs +418 -0
- package/scripts/incident.sh +212 -0
- package/scripts/land.sh +755 -45
- package/scripts/lib/gc-finished.sh +77 -0
- package/scripts/livekit-ratchet.sh +18 -1
- package/scripts/machine-check.sh +1 -1
- package/scripts/memory-index-budget.sh +79 -0
- package/scripts/npm-downloads.sh +109 -0
- package/scripts/one-agents.mjs +204 -8
- package/scripts/one-resume.sh +31 -3
- package/scripts/pr-body.sh +335 -0
- package/scripts/preview-fd-check.sh +289 -0
- package/scripts/redirect-lint.sh +169 -0
- package/scripts/release.sh +40 -6
- package/scripts/resume-lost-sessions.sh +68 -0
- package/scripts/shoot-pages.mjs +140 -0
- package/scripts/signal-meta-backfill.ts +451 -0
- package/scripts/signal-watch.sh +63 -6
- package/scripts/speed-cache-check.sh +12 -2
- package/scripts/sweep.sh +426 -0
- package/scripts/task-titles-dump.ts +101 -0
- package/scripts/test-cached.sh +47 -10
- package/scripts/test-lanes.sh +14 -0
- package/scripts/thread-name-backfill.ts +215 -0
- package/scripts/triage-shape-check.sh +149 -0
- package/scripts/tsc-cached.sh +155 -8
- package/scripts/typedb-flake-check.sh +3 -1
- package/scripts/urls-lint.sh +8 -0
- package/scripts/verify-board-doors.sh +80 -0
- package/scripts/verify-fast.sh +159 -6
- package/scripts/worktree-up.sh +21 -3
- package/skills/astro/SKILL.md +9 -3
- package/skills/astro/optimize-performance.md +3 -2
- package/skills/cloudflare/SKILL.md +3 -2
- package/skills/cloudflare-security-audit/AI-AND-LLM.md +83 -0
- package/skills/cloudflare-security-audit/ATTACK-CLASSES.md +130 -0
- package/skills/cloudflare-security-audit/CLIENT-SIDE.md +83 -0
- package/skills/cloudflare-security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
- package/skills/cloudflare-security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
- package/skills/cloudflare-security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
- package/skills/cloudflare-security-audit/HUNTING.md +251 -0
- package/skills/cloudflare-security-audit/LICENSE +21 -0
- package/skills/cloudflare-security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
- package/skills/cloudflare-security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
- package/skills/cloudflare-security-audit/PROVENANCE.md +78 -0
- package/skills/cloudflare-security-audit/RECONNAISSANCE.md +156 -0
- package/skills/cloudflare-security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
- package/skills/cloudflare-security-audit/SKILL.md +192 -0
- package/skills/cloudflare-security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
- package/skills/cloudflare-security-audit/VALIDATION-AND-REPORTING.md +186 -0
- package/skills/cloudflare-security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
- package/skills/cloudflare-security-audit/report-schema.json +461 -0
- package/skills/cloudflare-security-audit/validate-coverage-ledger.cjs +872 -0
- package/skills/cloudflare-security-audit/validate-coverage-ledger.test.cjs +740 -0
- package/skills/cloudflare-security-audit/validate-findings.cjs +773 -0
- package/skills/cloudflare-security-audit/validate-findings.test.cjs +652 -0
- package/skills/deploy/REFERENCE.md +713 -0
- package/skills/deploy/SKILL.md +140 -0
- package/skills/fleet-audit/SKILL.md +58 -0
- package/skills/meeting/SKILL.md +220 -0
- package/skills/planning/SKILL.md +256 -0
- package/skills/shadcn/SKILL.md +1 -1
- package/skills/typedb/SKILL.md +7 -0
- package/skills/voice/SKILL.md +94 -6
- package/skills/voice/corpus-check.sh +87 -0
- package/templates/template-agent.md +7 -1
- package/templates/template-feature.md +9 -0
- package/templates/template-todo.md +29 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# do-world-check.sh — assert every CODE surface a promise's `world.code:` block
|
|
3
|
+
# declares actually EXISTS on disk. Zero LLM; the path's presence is the verdict.
|
|
4
|
+
#
|
|
5
|
+
# WHY: a tag is an address, and the family named after it is what `world:`
|
|
6
|
+
# manifests. `/do` already presence-checks the block's agents, skills, workflow
|
|
7
|
+
# steps and views — but nothing checked for a `.ts`, `.astro` or `.tql` at the
|
|
8
|
+
# tag's name, so a promise could name a code surface it never built and settle
|
|
9
|
+
# green. `world.code:` declares those paths; this gate answers missing/present
|
|
10
|
+
# with the same rule already applied to agents: missing → a cycle · present → skip.
|
|
11
|
+
#
|
|
12
|
+
# SCOPE — `world.code.*` ONLY, and the line is deliberate. The other four kinds
|
|
13
|
+
# (agents · skills · workflow/lifecycle · views) stay the PROSE walk an agent runs
|
|
14
|
+
# at DESIGN/PLAN (.claude/commands/do.md § world, .claude/agents/w1-recon.md);
|
|
15
|
+
# they are named entities, not one deterministic path each. Every key under
|
|
16
|
+
# `code:` is a repo-root-relative path, so every key under `code:` is checkable.
|
|
17
|
+
# Not wired into do-w4-gates.sh or do-promise-settle.sh yet — that is the follow-on.
|
|
18
|
+
#
|
|
19
|
+
# The --self-test fixtures name paths under fixtures/ rather than schema/ only
|
|
20
|
+
# because factory-repo.sh:44 (MONOREPO_PATH_RE) refuses a `portable` script that
|
|
21
|
+
# names a monorepo path. The RED shape is identical either way; the demo C6 asks
|
|
22
|
+
# for — `code.tql` naming an absent schema/<tag>.tql — is recorded verbatim in
|
|
23
|
+
# text/deploy-todo.md § C6.
|
|
24
|
+
#
|
|
25
|
+
# Usage:
|
|
26
|
+
# do-world-check.sh <slug> # text/<slug>.md, or $PROMISE_FILE
|
|
27
|
+
# do-world-check.sh --self-test # temp fixtures, zero network
|
|
28
|
+
# exit: 0 all present (or no code: block) · 1 a surface missing · 2 no promise file · 4 bad usage
|
|
29
|
+
set -u
|
|
30
|
+
|
|
31
|
+
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
32
|
+
|
|
33
|
+
# print "<key>\t<path>" for every path declared under world: → code:.
|
|
34
|
+
# Two levels of nesting, so the col-0 terminator alone is not enough: the walk
|
|
35
|
+
# tracks indentation. Both YAML list forms are read — flow (`tql: [a, b]`) and
|
|
36
|
+
# block (`tql:` then `- a`) — and a trailing ` # comment` is stripped.
|
|
37
|
+
code_entries() { # $1 = promise file
|
|
38
|
+
awk '
|
|
39
|
+
function indent(s, n) { n = match(s, /[^ ]/); return n ? n - 1 : -1 }
|
|
40
|
+
function clean(v) {
|
|
41
|
+
sub(/[ \t]+#.*$/, "", v) # trailing comment
|
|
42
|
+
gsub(/^[ \t]+|[ \t]+$/, "", v)
|
|
43
|
+
gsub(/^["'"'"']|["'"'"']$/, "", v) # one layer of quoting
|
|
44
|
+
return v
|
|
45
|
+
}
|
|
46
|
+
function emit(k, v) { v = clean(v); if (v != "") printf "%s\t%s\n", k, v }
|
|
47
|
+
function emit_flow(k, v, n, i, parts) {
|
|
48
|
+
sub(/^\[/, "", v); sub(/\].*$/, "", v)
|
|
49
|
+
n = split(v, parts, ",")
|
|
50
|
+
for (i = 1; i <= n; i++) emit(k, parts[i])
|
|
51
|
+
}
|
|
52
|
+
/^---[ \t]*$/ { fm++; if (fm >= 2) exit; next }
|
|
53
|
+
fm != 1 { next }
|
|
54
|
+
/^world:[ \t]*$/ { inw = 1; next }
|
|
55
|
+
inw && /^[^ \t#]/ { inw = 0; inc = 0; key = "" } # a col-0 key ends world:
|
|
56
|
+
!inw { next }
|
|
57
|
+
/^[ \t]*$/ { next }
|
|
58
|
+
/^[ \t]*#/ { next }
|
|
59
|
+
{
|
|
60
|
+
i = indent($0)
|
|
61
|
+
if (inc && i <= 2) { inc = 0; key = "" } # a sibling of code: ends it
|
|
62
|
+
if (!inc) { if ($0 ~ /^[ \t]*code:[ \t]*(#.*)?$/ && i == 2) { inc = 1; key = "" } ; next }
|
|
63
|
+
if (i < 4) next
|
|
64
|
+
if ($0 ~ /^[ \t]*-[ \t]/) { # block list item
|
|
65
|
+
if (key == "") next
|
|
66
|
+
line = $0; sub(/^[ \t]*-[ \t]*/, "", line); emit(key, line); next
|
|
67
|
+
}
|
|
68
|
+
if (match($0, /^[ \t]*[A-Za-z][A-Za-z0-9_-]*:/)) { # a code: sub-key
|
|
69
|
+
key = $0; sub(/^[ \t]*/, "", key); sub(/:.*$/, "", key)
|
|
70
|
+
rest = $0; sub(/^[ \t]*[A-Za-z][A-Za-z0-9_-]*:[ \t]*/, "", rest)
|
|
71
|
+
if (rest ~ /^\[/) { emit_flow(key, rest); key = "" }
|
|
72
|
+
else emit(key, rest)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
' "$1"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
check() { # $1 = slug
|
|
79
|
+
local slug="$1"
|
|
80
|
+
local file="${PROMISE_FILE:-$ROOT/text/$slug.md}" missing=0 checked=0 line key path
|
|
81
|
+
if [ ! -f "$file" ]; then
|
|
82
|
+
echo "[world-check] $slug — no promise file at text/$slug.md"; return 2
|
|
83
|
+
fi
|
|
84
|
+
while IFS="$(printf '\t')" read -r key path; do
|
|
85
|
+
[ -z "${path:-}" ] && continue
|
|
86
|
+
checked=$((checked+1))
|
|
87
|
+
if [ -e "$ROOT/$path" ]; then
|
|
88
|
+
echo "[world-check] ok: world.code.$key → $path"
|
|
89
|
+
else
|
|
90
|
+
echo "[world-check] MISSING: world.code.$key declares $path — no such path"
|
|
91
|
+
missing=$((missing+1))
|
|
92
|
+
fi
|
|
93
|
+
done <<EOF
|
|
94
|
+
$(code_entries "$file")
|
|
95
|
+
EOF
|
|
96
|
+
if [ "$checked" -eq 0 ]; then
|
|
97
|
+
echo "[world-check] $slug — no world.code: surfaces declared (nothing to check)"; return 0
|
|
98
|
+
fi
|
|
99
|
+
if [ "$missing" -gt 0 ]; then
|
|
100
|
+
echo "[world-check] $slug — $missing/$checked declared code surface(s) missing"; return 1
|
|
101
|
+
fi
|
|
102
|
+
echo "[world-check] $slug — ok ($checked declared code surface(s) present)"; return 0
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if [ "${1:-}" = "--self-test" ]; then
|
|
106
|
+
dir="$(mktemp -d)"; trap 'rm -rf "$dir"' EXIT
|
|
107
|
+
mkdir -p "$dir/text" "$dir/fixtures"
|
|
108
|
+
fails=0
|
|
109
|
+
: >"$dir/fixtures/green.tql"; : >"$dir/fixtures/green.astro"
|
|
110
|
+
# green: flow list + block list, every declared path on disk
|
|
111
|
+
cat >"$dir/text/green.md" <<'YAML'
|
|
112
|
+
---
|
|
113
|
+
slug: green
|
|
114
|
+
world:
|
|
115
|
+
agents:
|
|
116
|
+
- { name: doctor, subscribes: [health] }
|
|
117
|
+
code: # a trailing comment on the key must not hide the block
|
|
118
|
+
tql: [fixtures/green.tql]
|
|
119
|
+
astro:
|
|
120
|
+
- fixtures/green.astro # a trailing comment must not break the path
|
|
121
|
+
tracking:
|
|
122
|
+
marks: ["a nested sibling at indent 4 must not read as a code surface"]
|
|
123
|
+
views: []
|
|
124
|
+
---
|
|
125
|
+
YAML
|
|
126
|
+
# red: a declared .tql that was never written
|
|
127
|
+
cat >"$dir/text/red.md" <<'YAML'
|
|
128
|
+
---
|
|
129
|
+
slug: red
|
|
130
|
+
world:
|
|
131
|
+
code:
|
|
132
|
+
tql: [fixtures/nonexistent-zzz.tql]
|
|
133
|
+
---
|
|
134
|
+
YAML
|
|
135
|
+
# bare: a world: block with no code: — passes, checks nothing
|
|
136
|
+
printf -- '---\nslug: bare\nworld:\n agents: []\n---\n' >"$dir/text/bare.md"
|
|
137
|
+
out="$(ROOT="$dir" PROMISE_FILE= check green 2>&1)"; rc=$?
|
|
138
|
+
[ "$rc" -eq 0 ] || { echo "FAIL green should pass: $out"; fails=$((fails+1)); }
|
|
139
|
+
case "$out" in *"ok (2 declared"*) ;; *) echo "FAIL green must count 2 surfaces: $out"; fails=$((fails+1)) ;; esac
|
|
140
|
+
out="$(ROOT="$dir" PROMISE_FILE= check red 2>&1)"; rc=$?
|
|
141
|
+
[ "$rc" -eq 1 ] || { echo "FAIL red should fail: $out"; fails=$((fails+1)); }
|
|
142
|
+
case "$out" in *"MISSING: world.code.tql"*) ;; *) echo "FAIL red must name the path: $out"; fails=$((fails+1)) ;; esac
|
|
143
|
+
out="$(ROOT="$dir" PROMISE_FILE= check bare 2>&1)"; rc=$?
|
|
144
|
+
[ "$rc" -eq 0 ] || { echo "FAIL bare should pass: $out"; fails=$((fails+1)); }
|
|
145
|
+
case "$out" in *"nothing to check"*) ;; *) echo "FAIL bare must say nothing to check: $out"; fails=$((fails+1)) ;; esac
|
|
146
|
+
echo "[world-check] self-test: 3 fixtures, $fails failed"
|
|
147
|
+
exit "$fails"
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
SLUG="${1:-}"
|
|
151
|
+
[ -z "$SLUG" ] && { echo "usage: do-world-check.sh <slug> | --self-test" >&2; exit 4; }
|
|
152
|
+
PROMISE_FILE="${PROMISE_FILE:-}"
|
|
153
|
+
check "$SLUG"
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# download-stats.sh — every lifetime reach number we can prove, in one table.
|
|
3
|
+
#
|
|
4
|
+
# bash .claude/scripts/download-stats.sh # print
|
|
5
|
+
# bash .claude/scripts/download-stats.sh --write # + record to text/download-stats.md
|
|
6
|
+
# bash .claude/scripts/download-stats.sh --json
|
|
7
|
+
# bash .claude/scripts/download-stats.sh --self-test
|
|
8
|
+
#
|
|
9
|
+
# manifest: needs-env — (npm + gh + network; writes only text/download-stats.md)
|
|
10
|
+
#
|
|
11
|
+
# FOLDS IN npm-downloads.sh, which this replaces. Both of that script's traps
|
|
12
|
+
# are live here and neither is obvious:
|
|
13
|
+
#
|
|
14
|
+
# 1. npm's downloads API REFUSES a range longer than 18 months — and answers
|
|
15
|
+
# that refusal with HTTP 200 and an `error` field. A naive all-time query
|
|
16
|
+
# therefore returns nothing useful without failing. All-time is a WALK: from
|
|
17
|
+
# each package's first publish (registry `time.created`), 17 months at a
|
|
18
|
+
# time, summed. Read `.error`, never the status code.
|
|
19
|
+
# 2. A scoped name must be percent-encoded — `@oneie%2Fsdk`. Unencoded, the
|
|
20
|
+
# slash reads as a path separator and the API 404s, which deserialises to a
|
|
21
|
+
# zero indistinguishable from a real one. Encoded once, in enc().
|
|
22
|
+
#
|
|
23
|
+
# And one about WHICH packages: the list comes from the REGISTRY (search by
|
|
24
|
+
# maintainer), not from manifests on disk and not from `npm access list
|
|
25
|
+
# packages` — that returns EMPTY under a granular token (measured 2026-09-14:
|
|
26
|
+
# "packages in scope: 0" while all 30 were readable). Reading manifests misses
|
|
27
|
+
# every package not in this repo, which was 10 of 30 — including `oneie` at
|
|
28
|
+
# 13,117 downloads, the single most-downloaded package we have.
|
|
29
|
+
#
|
|
30
|
+
# WHY A RECORDED .md: npm's per-day history is queryable back to a package's
|
|
31
|
+
# first publish, so npm needs no ledger. GitHub traffic is different — it keeps
|
|
32
|
+
# FOURTEEN DAYS and offers no backfill (see gh-traffic-capture.sh). The .md is
|
|
33
|
+
# the durable, committed, human-readable record; the gitignored ledger is the
|
|
34
|
+
# daily machine one. A snapshot appended here survives a dead laptop.
|
|
35
|
+
#
|
|
36
|
+
# HONESTY, because these numbers are for social proof and will be checked:
|
|
37
|
+
# npm counts mirrors, CI and crawlers. "57,819 downloads" is defensible;
|
|
38
|
+
# "57,819 developers" is a lie. The table separates the current @oneie scope
|
|
39
|
+
# from retired lines so a headline can be sourced to the right subtotal.
|
|
40
|
+
set -uo pipefail
|
|
41
|
+
|
|
42
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
43
|
+
OUT="$ROOT/text/download-stats.md"
|
|
44
|
+
MAINTAINER="${NPM_MAINTAINER:-oneie}"
|
|
45
|
+
ORG="${GH_ORG:-one-ie}"
|
|
46
|
+
MODE=print
|
|
47
|
+
|
|
48
|
+
while [[ $# -gt 0 ]]; do
|
|
49
|
+
case "$1" in
|
|
50
|
+
--write) MODE=write ;;
|
|
51
|
+
--json) MODE=json ;;
|
|
52
|
+
--self-test) MODE=selftest ;;
|
|
53
|
+
-h|--help) sed -n '2,8p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
|
54
|
+
*) echo "download-stats.sh: unknown flag '$1'" >&2; exit 2 ;;
|
|
55
|
+
esac
|
|
56
|
+
shift
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
command -v jq >/dev/null || { echo "download-stats.sh: needs jq" >&2; exit 2; }
|
|
60
|
+
|
|
61
|
+
enc() { printf '%s' "${1/\//%2F}"; } # @oneie/sdk -> @oneie%2Fsdk
|
|
62
|
+
today() { date -u '+%Y-%m-%d'; }
|
|
63
|
+
add_months() { date -j -v+"$2"m -f '%Y-%m-%d' "$1" '+%Y-%m-%d' 2>/dev/null || date -d "$1 + $2 months" '+%Y-%m-%d' 2>/dev/null; }
|
|
64
|
+
|
|
65
|
+
# all_time <pkg> — the 17-month walk described above.
|
|
66
|
+
all_time() {
|
|
67
|
+
local p="$1" e c created start end total=0 body err n
|
|
68
|
+
e="$(enc "$p")"
|
|
69
|
+
created="$(npm view "$p" time.created 2>/dev/null | head -1 | cut -c1-10)"
|
|
70
|
+
[[ -n "$created" ]] || created="2024-01-01"
|
|
71
|
+
start="$created"; end="$(today)"; cursor="$start"
|
|
72
|
+
while [[ "$cursor" < "$end" ]]; do
|
|
73
|
+
stop="$(add_months "$cursor" 17)"
|
|
74
|
+
[[ -z "$stop" || "$stop" > "$end" ]] && stop="$end"
|
|
75
|
+
body="$(curl -sS --max-time 25 "https://api.npmjs.org/downloads/point/${cursor}:${stop}/${e}" 2>/dev/null)"
|
|
76
|
+
err="$(printf '%s' "$body" | jq -r '.error // empty' 2>/dev/null)"
|
|
77
|
+
if [[ -z "$err" ]]; then
|
|
78
|
+
n="$(printf '%s' "$body" | jq -r '.downloads // 0' 2>/dev/null)"
|
|
79
|
+
[[ "$n" =~ ^[0-9]+$ ]] && total=$(( total + n ))
|
|
80
|
+
fi
|
|
81
|
+
[[ "$stop" == "$end" ]] && break
|
|
82
|
+
cursor="$stop"
|
|
83
|
+
done
|
|
84
|
+
printf '%s|%s' "$total" "$created"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if [[ "$MODE" == selftest ]]; then
|
|
88
|
+
fails=0
|
|
89
|
+
[[ "$(enc '@oneie/sdk')" == '@oneie%2Fsdk' ]] || { echo " FAIL: scoped name not encoded"; fails=1; }
|
|
90
|
+
[[ "$(enc 'oneie')" == 'oneie' ]] || { echo " FAIL: unscoped name mangled"; fails=1; }
|
|
91
|
+
[[ -n "$(add_months 2026-01-01 17)" ]] || { echo " FAIL: add_months returned nothing"; fails=1; }
|
|
92
|
+
# the 18-month refusal must be READ, not inferred from the status code
|
|
93
|
+
b="$(curl -sS --max-time 20 "https://api.npmjs.org/downloads/point/2000-01-01:$(today)/npm" 2>/dev/null)"
|
|
94
|
+
[[ -n "$(printf '%s' "$b" | jq -r '.error // empty')" ]] \
|
|
95
|
+
|| { echo " NOTE: the >18mo range did not error — the walk is still correct, the guard is just untested here"; }
|
|
96
|
+
(( fails )) && { echo "download-stats.sh: self-test FAILED"; exit 1; }
|
|
97
|
+
echo "download-stats.sh: self-test PASS"; exit 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# ── npm ──────────────────────────────────────────────────────────────────────
|
|
101
|
+
pkgs="$(curl -sS --max-time 30 "https://registry.npmjs.org/-/v1/search?text=maintainer:${MAINTAINER}&size=250" 2>/dev/null | jq -r '.objects[].package.name' | sort)"
|
|
102
|
+
[[ -n "$pkgs" ]] || { echo "download-stats.sh: registry search returned no packages for maintainer:${MAINTAINER}" >&2; exit 1; }
|
|
103
|
+
|
|
104
|
+
rows="[]"
|
|
105
|
+
while IFS= read -r p; do
|
|
106
|
+
[[ -n "$p" ]] || continue
|
|
107
|
+
at="$(all_time "$p")"
|
|
108
|
+
wk="$(curl -sS --max-time 15 "https://api.npmjs.org/downloads/point/last-week/$(enc "$p")" 2>/dev/null | jq -r '.downloads // 0')"
|
|
109
|
+
ver="$(npm view "$p" version 2>/dev/null | tail -1)"
|
|
110
|
+
rows="$(printf '%s' "$rows" | jq --arg p "$p" --arg v "$ver" --arg c "${at##*|}" \
|
|
111
|
+
--argjson t "${at%%|*}" --argjson w "${wk:-0}" \
|
|
112
|
+
'. + [{package:$p,version:$v,since:$c,all_time:$t,last_week:$w,scoped:($p|startswith("@"))}]')"
|
|
113
|
+
done <<< "$pkgs"
|
|
114
|
+
|
|
115
|
+
NPM_ALL="$(printf '%s' "$rows" | jq '[.[].all_time]|add // 0')"
|
|
116
|
+
NPM_WK="$(printf '%s' "$rows" | jq '[.[].last_week]|add // 0')"
|
|
117
|
+
NPM_SCOPED="$(printf '%s' "$rows" | jq '[.[]|select(.scoped)|.all_time]|add // 0')"
|
|
118
|
+
NPM_COUNT="$(printf '%s' "$rows" | jq 'length')"
|
|
119
|
+
|
|
120
|
+
# ── github ───────────────────────────────────────────────────────────────────
|
|
121
|
+
repos="$(gh api "orgs/$ORG/repos?per_page=100&type=public" 2>/dev/null)"
|
|
122
|
+
GH_STARS="$(printf '%s' "$repos" | jq '[.[].stargazers_count]|add // 0')"
|
|
123
|
+
GH_FORKS="$(printf '%s' "$repos" | jq '[.[].forks_count]|add // 0')"
|
|
124
|
+
GH_REPOS="$(printf '%s' "$repos" | jq 'length')"
|
|
125
|
+
GH_CLONES=0; GH_UNIQ=0
|
|
126
|
+
while IFS= read -r r; do
|
|
127
|
+
[[ -n "$r" ]] || continue
|
|
128
|
+
t="$(gh api "repos/$r/traffic/clones" --jq '"\(.count) \(.uniques)"' 2>/dev/null)" || continue
|
|
129
|
+
GH_CLONES=$(( GH_CLONES + ${t%% *} )); GH_UNIQ=$(( GH_UNIQ + ${t##* } ))
|
|
130
|
+
done < <(printf '%s' "$repos" | jq -r '.[].full_name')
|
|
131
|
+
|
|
132
|
+
if [[ "$MODE" == json ]]; then
|
|
133
|
+
jq -n --argjson pkgs "$rows" --arg d "$(today)" \
|
|
134
|
+
--argjson na "$NPM_ALL" --argjson nw "$NPM_WK" --argjson ns "$NPM_SCOPED" --argjson nc "$NPM_COUNT" \
|
|
135
|
+
--argjson gs "$GH_STARS" --argjson gf "$GH_FORKS" --argjson gr "$GH_REPOS" \
|
|
136
|
+
--argjson gc "$GH_CLONES" --argjson gu "$GH_UNIQ" \
|
|
137
|
+
'{date:$d, npm:{packages:$nc, all_time:$na, scope_all_time:$ns, last_week:$nw, rows:$pkgs},
|
|
138
|
+
github:{repos:$gr, stars:$gs, forks:$gf, clones_14d:$gc, unique_cloners_14d:$gu}}'
|
|
139
|
+
exit 0
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
emit() {
|
|
143
|
+
printf '# Download stats\n\n'
|
|
144
|
+
printf '_Measured %s by `.claude/scripts/download-stats.sh`. Every figure is re-derivable — no number here was typed by hand._\n\n' "$(today)"
|
|
145
|
+
printf '## Headline\n\n| metric | value | source |\n|---|---:|---|\n'
|
|
146
|
+
printf '| npm downloads, all time | **%s** | downloads API, per package since first publish |\n' "$NPM_ALL"
|
|
147
|
+
printf '| npm downloads, `@%s` scope | **%s** | the current product line only |\n' "$MAINTAINER" "$NPM_SCOPED"
|
|
148
|
+
printf '| npm downloads, last week | %s | downloads API |\n' "$NPM_WK"
|
|
149
|
+
printf '| packages published | **%s** | registry, maintainer `%s` |\n' "$NPM_COUNT" "$MAINTAINER"
|
|
150
|
+
printf '| GitHub stars | **%s** | %s public repos, org-wide |\n' "$GH_STARS" "$GH_REPOS"
|
|
151
|
+
printf '| GitHub forks | %s | org-wide |\n' "$GH_FORKS"
|
|
152
|
+
printf '| unique cloners, 14d | **%s** | %s clones — GitHub keeps only 14 days |\n\n' "$GH_UNIQ" "$GH_CLONES"
|
|
153
|
+
printf '## Packages\n\n| package | version | all time | last week | since |\n|---|---|---:|---:|---|\n'
|
|
154
|
+
printf '%s' "$rows" | jq -r 'sort_by(-.all_time)[] | "| `\(.package)` | \(.version) | \(.all_time) | \(.last_week) | \(.since) |"'
|
|
155
|
+
printf '\n## Using these for social proof\n\n'
|
|
156
|
+
printf 'npm counts mirrors, CI and crawlers alongside people. **%s downloads** is defensible; ' "$NPM_ALL"
|
|
157
|
+
printf '"%s developers" is not — say downloads, or cite unique cloners (**%s in 14 days**), which are closer to humans.\n\n' "$NPM_ALL" "$GH_UNIQ"
|
|
158
|
+
printf 'The all-time total spans retired lines as well as the current one. For claims about the product as it stands today, '
|
|
159
|
+
printf 'the honest subtotal is the **`@%s` scope: %s**.\n\n' "$MAINTAINER" "$NPM_SCOPED"
|
|
160
|
+
printf '## What cannot be recovered\n\n'
|
|
161
|
+
printf 'GitHub keeps **14 days** of clone and view traffic and offers no backfill, so all-time clones do not exist for any repo. '
|
|
162
|
+
printf '`.claude/scripts/gh-traffic-capture.sh` runs daily to stop further days being lost; everything before its first run is gone.\n'
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if [[ "$MODE" == write ]]; then
|
|
166
|
+
mkdir -p "$(dirname "$OUT")"
|
|
167
|
+
emit > "$OUT"
|
|
168
|
+
echo " ✓ recorded → text/download-stats.md ($(wc -l < "$OUT" | tr -d ' ') lines)"
|
|
169
|
+
echo " npm all-time=$NPM_ALL scope=$NPM_SCOPED packages=$NPM_COUNT stars=$GH_STARS uniq-cloners-14d=$GH_UNIQ"
|
|
170
|
+
else
|
|
171
|
+
emit
|
|
172
|
+
fi
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# factory-brief-check.sh — pre-flight a factory brief for DEFERRED DECISIONS.
|
|
3
|
+
#
|
|
4
|
+
# manifest: portable
|
|
5
|
+
# Reads no credential and names no monorepo path — every rule is a literal
|
|
6
|
+
# in this file and the brief arrives as an argument or on stdin. The red
|
|
7
|
+
# half of --self-test reads fixtures/factory-brief-real.md, which ships
|
|
8
|
+
# beside it; absent, the self-test goes RED rather than skipping.
|
|
9
|
+
#
|
|
10
|
+
# WHY THIS EXISTS. A brief that hands the doer a decision instead of a decision
|
|
11
|
+
# already made costs a whole run. The first factory run spent ~46 minutes and
|
|
12
|
+
# ended in a refutation because one parenthesis said "verify which is right" —
|
|
13
|
+
# the launching session had two candidate reads of the workspace and shipped
|
|
14
|
+
# both. The doer is the wrong place to settle that: it has no board, no
|
|
15
|
+
# neighbours, and no cheap way to be wrong. This gate reads the brief BEFORE a
|
|
16
|
+
# worktree, a port, or a model call is spent, and refuses.
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# factory-brief-check.sh <brief-file> check a brief file
|
|
20
|
+
# factory-brief-check.sh - check a brief on stdin
|
|
21
|
+
# cat brief.md | factory-brief-check.sh same
|
|
22
|
+
# factory-brief-check.sh --self-test prove the gate goes RED and GREEN
|
|
23
|
+
#
|
|
24
|
+
# Exit: 0 = brief is launchable · 1 = RED (or self-test failed) · 2 = usage.
|
|
25
|
+
#
|
|
26
|
+
# Env:
|
|
27
|
+
# FACTORY_BRIEF_FIXTURE override the --self-test red fixture (default:
|
|
28
|
+
# fixtures/factory-brief-real.md beside this script)
|
|
29
|
+
|
|
30
|
+
set -uo pipefail
|
|
31
|
+
|
|
32
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# The rules. Each is a NAMED pattern, matched case-insensitively, one per line
|
|
36
|
+
# of the brief. A rule fires when the brief leaves a decision for the doer.
|
|
37
|
+
#
|
|
38
|
+
# Third field is the input the pattern is matched against: `line` is the raw
|
|
39
|
+
# line, `stripped` is the line with every file-path token removed first.
|
|
40
|
+
#
|
|
41
|
+
# Deliberate deviation, documented: `tbd` and `todo` match against `stripped`,
|
|
42
|
+
# so `text/template-todo.md` and `<slug>-todo.md` — which nearly every real
|
|
43
|
+
# brief in this repo cites — are NOT hits, while a bare `TBD.` at the end of a
|
|
44
|
+
# sentence still is. A gate that goes red on the name of a file it expects you
|
|
45
|
+
# to name is decoration, and gets disabled; a gate that misses `TBD.` because
|
|
46
|
+
# of the full stop is worse.
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
RULES=(
|
|
49
|
+
'verify-which|verify[[:space:]]+which|line'
|
|
50
|
+
'check-whether|check[[:space:]]+whether|line'
|
|
51
|
+
'tbd|(^|[^-_/[:alnum:]])tbd([^-_[:alnum:]]|$)|stripped'
|
|
52
|
+
'todo|(^|[^-_/[:alnum:]])todo([^-_[:alnum:]]|$)|stripped'
|
|
53
|
+
'decide-later|decide[[:space:]]+later|line'
|
|
54
|
+
'or-alternatively|or[[:space:]]+alternatively|line'
|
|
55
|
+
'either-or|(^|[^[:alnum:]])either[^[:alnum:]].*[[:space:]]or[[:space:]]|line'
|
|
56
|
+
'trailing-question|\?[[:space:]]*$|line'
|
|
57
|
+
'if-possible|if[[:space:]]+possible|line'
|
|
58
|
+
'ideally|(^|[^[:alnum:]])ideally([^[:alnum:]]|$)|line'
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Rule 'code-span-options': two options joined by ` ?? ` or ` or ` INSIDE a code
|
|
62
|
+
# span. `a ?? b` in prose is a decision the doer has to make; the same string in
|
|
63
|
+
# shipped code is not, so only backtick spans count. Returns 0 when the line has
|
|
64
|
+
# one.
|
|
65
|
+
code_span_options() {
|
|
66
|
+
awk '
|
|
67
|
+
{
|
|
68
|
+
n = split($0, part, "`")
|
|
69
|
+
# odd-indexed parts (2,4,…) are inside a span
|
|
70
|
+
for (i = 2; i <= n; i += 2) {
|
|
71
|
+
s = part[i]
|
|
72
|
+
if (s ~ /[^[:space:]][[:space:]]+\?\?[[:space:]]+[^[:space:]]/) { found = 1 }
|
|
73
|
+
if (s ~ /[^[:space:]][[:space:]]+or[[:space:]]+[^[:space:]]/) { found = 1 }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
END { exit !found }
|
|
77
|
+
' <<< "$1"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# A file path token. The spec class is lowercase; this adds A-Z so the count and
|
|
81
|
+
# any listing read correctly (`ComponentBrowser.tsx`, not `omponentBrowser.tsx`).
|
|
82
|
+
# Presence is identical either way.
|
|
83
|
+
PATH_RE='[A-Za-z0-9_./-]+\.(tsx|astro|ts|sh|md|mjs|js)([^A-Za-z0-9]|$)'
|
|
84
|
+
|
|
85
|
+
# A 'Done' section: a line that STARTS with the word done, optionally behind a
|
|
86
|
+
# markdown heading or bold marker. "DONE LOOKS LIKE", "## Done", "**Done**".
|
|
87
|
+
DONE_RE='^[[:space:]]*(#+[[:space:]]*)?(\*\*)?[[:space:]]*done([^[:alnum:]]|$)'
|
|
88
|
+
|
|
89
|
+
check_brief() {
|
|
90
|
+
local text="$1" label="${2:-brief}"
|
|
91
|
+
local hits=0 line rule name pat
|
|
92
|
+
|
|
93
|
+
if [ -z "${text//[[:space:]]/}" ]; then
|
|
94
|
+
printf 'BRIEF RED: empty-brief — "<%s is empty — a brief that says nothing defers everything>"\n' "$label"
|
|
95
|
+
return 1
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
local rest src stripped
|
|
99
|
+
while IFS= read -r line; do
|
|
100
|
+
[ -z "${line//[[:space:]]/}" ] && continue
|
|
101
|
+
stripped="$(sed -E "s#$PATH_RE# #g" <<< "$line")"
|
|
102
|
+
for rule in ${RULES[@]+"${RULES[@]}"}; do
|
|
103
|
+
name="${rule%%|*}"; rest="${rule#*|}"
|
|
104
|
+
pat="${rest%|*}"; src="${rest##*|}"
|
|
105
|
+
if grep -qiE "$pat" <<< "$([ "$src" = stripped ] && printf '%s' "$stripped" || printf '%s' "$line")"; then
|
|
106
|
+
printf 'BRIEF RED: %s — "%s"\n' "$name" "$line"
|
|
107
|
+
hits=$((hits + 1))
|
|
108
|
+
fi
|
|
109
|
+
done
|
|
110
|
+
if code_span_options "$line"; then
|
|
111
|
+
printf 'BRIEF RED: code-span-options — "%s"\n' "$line"
|
|
112
|
+
hits=$((hits + 1))
|
|
113
|
+
fi
|
|
114
|
+
done <<< "$text"
|
|
115
|
+
|
|
116
|
+
local files
|
|
117
|
+
files="$(grep -oE "$PATH_RE" <<< "$text" | sed -E 's/[^A-Za-z0-9]$//' | sort -u)"
|
|
118
|
+
local nfiles=0
|
|
119
|
+
[ -n "$files" ] && nfiles="$(wc -l <<< "$files" | tr -d ' ')"
|
|
120
|
+
|
|
121
|
+
if [ "$nfiles" -eq 0 ]; then
|
|
122
|
+
printf 'BRIEF RED: no-file-path — "<%s names no file the doer can open>"\n' "$label"
|
|
123
|
+
hits=$((hits + 1))
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
if ! grep -qiE "$DONE_RE" <<< "$text"; then
|
|
127
|
+
printf 'BRIEF RED: no-done-section — "<%s has no Done section — nothing says when to stop>"\n' "$label"
|
|
128
|
+
hits=$((hits + 1))
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
if [ "$hits" -gt 0 ]; then
|
|
132
|
+
return 1
|
|
133
|
+
fi
|
|
134
|
+
printf 'BRIEF OK: %s files named, 0 deferred decisions\n' "$nfiles"
|
|
135
|
+
return 0
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
# --self-test — the red proof. Four cases; the red half runs against the ACTUAL
|
|
140
|
+
# brief that produced the first run's refutation, copied verbatim, not retyped.
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
self_test() {
|
|
143
|
+
local fixture="${FACTORY_BRIEF_FIXTURE:-$HERE/fixtures/factory-brief-real.md}"
|
|
144
|
+
local tmp out rc bad=0 cases=0
|
|
145
|
+
|
|
146
|
+
if [ ! -f "$fixture" ]; then
|
|
147
|
+
printf 'SELF-TEST RED: the red fixture is missing at %s — the red half cannot be run, so nothing here is proven. Refusing to pass.\n' "$fixture" >&2
|
|
148
|
+
printf 'SELF-TEST RED: 0 cases — fixture absent\n'
|
|
149
|
+
return 1
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# (a) the real brief MUST go red, and MUST name verify-which on the real line.
|
|
153
|
+
cases=$((cases + 1))
|
|
154
|
+
out="$(check_brief "$(cat "$fixture")" "$fixture")"; rc=$?
|
|
155
|
+
if [ $rc -eq 0 ]; then
|
|
156
|
+
printf ' FAIL (a) the real brief passed — the gate is blind\n' >&2; bad=1
|
|
157
|
+
elif ! grep -q '^BRIEF RED: verify-which — ' <<< "$out"; then
|
|
158
|
+
printf ' FAIL (a) the real brief went red, but not on verify-which:\n%s\n' "$out" >&2; bad=1
|
|
159
|
+
elif ! grep -qi 'verify which is right' <<< "$(grep '^BRIEF RED: verify-which — ' <<< "$out")"; then
|
|
160
|
+
printf ' FAIL (a) verify-which fired on the wrong line:\n%s\n' "$(grep '^BRIEF RED: verify-which' <<< "$out")" >&2; bad=1
|
|
161
|
+
else
|
|
162
|
+
printf ' ok (a) the real brief is RED on verify-which, quoting "verify which is right"\n'
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN
|
|
166
|
+
|
|
167
|
+
# (b) a clean brief MUST pass.
|
|
168
|
+
cat > "$tmp/clean.md" <<'CLEAN'
|
|
169
|
+
BUILD: the picker gate on the component gallery.
|
|
170
|
+
|
|
171
|
+
WHAT TO BUILD
|
|
172
|
+
Add an "Add to page" control to the preview footer in
|
|
173
|
+
src/components/puck/ComponentBrowser.tsx. It lists the workspace pages, takes
|
|
174
|
+
one, appends the block, then offers two exits.
|
|
175
|
+
|
|
176
|
+
THE DECISION, ALREADY MADE
|
|
177
|
+
Read the workspace slug server-side from Astro.locals.slug in
|
|
178
|
+
src/pages/components.astro and pass it down as an optional prop. There is no
|
|
179
|
+
session, so there is no slug, so the control does not render.
|
|
180
|
+
|
|
181
|
+
TEST
|
|
182
|
+
Extend tests/unit/puck/add-to-page.test.tsx with one case that names the wrong
|
|
183
|
+
result: a missing slug renders the control anyway.
|
|
184
|
+
|
|
185
|
+
DONE LOOKS LIKE
|
|
186
|
+
1. The control renders signed in and is absent signed out.
|
|
187
|
+
2. The block lands at the end of the picked page.
|
|
188
|
+
3. bash .claude/scripts/gate-run.sh doer -- bunx vitest related --run is green.
|
|
189
|
+
4. node .claude/scripts/wf-check.mjs is green.
|
|
190
|
+
CLEAN
|
|
191
|
+
cases=$((cases + 1))
|
|
192
|
+
out="$(check_brief "$(cat "$tmp/clean.md")" "$tmp/clean.md")"; rc=$?
|
|
193
|
+
if [ $rc -ne 0 ]; then
|
|
194
|
+
printf ' FAIL (b) the clean brief went red — the gate cries wolf:\n%s\n' "$out" >&2; bad=1
|
|
195
|
+
elif ! grep -q '^BRIEF OK: ' <<< "$out"; then
|
|
196
|
+
printf ' FAIL (b) the clean brief exited 0 without printing BRIEF OK\n' >&2; bad=1
|
|
197
|
+
else
|
|
198
|
+
printf ' ok (b) the clean brief is GREEN — %s\n' "$out"
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
# (c) a brief with no file path MUST go red, even with nothing deferred.
|
|
202
|
+
cat > "$tmp/nopath.md" <<'NOPATH'
|
|
203
|
+
BUILD: make the gallery let you put a block on a page.
|
|
204
|
+
|
|
205
|
+
DONE LOOKS LIKE
|
|
206
|
+
1. It works signed in and is absent signed out.
|
|
207
|
+
NOPATH
|
|
208
|
+
cases=$((cases + 1))
|
|
209
|
+
out="$(check_brief "$(cat "$tmp/nopath.md")" "$tmp/nopath.md")"; rc=$?
|
|
210
|
+
if [ $rc -eq 0 ] || ! grep -q '^BRIEF RED: no-file-path' <<< "$out"; then
|
|
211
|
+
printf ' FAIL (c) a brief naming no file passed:\n%s\n' "$out" >&2; bad=1
|
|
212
|
+
else
|
|
213
|
+
printf ' ok (c) a brief naming no file is RED on no-file-path\n'
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
# (d) a brief with no Done section MUST go red.
|
|
217
|
+
cat > "$tmp/nodone.md" <<'NODONE'
|
|
218
|
+
BUILD: the picker gate.
|
|
219
|
+
Edit src/components/puck/ComponentBrowser.tsx and read the slug server-side in
|
|
220
|
+
src/pages/components.astro. Extend tests/unit/puck/add-to-page.test.tsx.
|
|
221
|
+
NODONE
|
|
222
|
+
cases=$((cases + 1))
|
|
223
|
+
out="$(check_brief "$(cat "$tmp/nodone.md")" "$tmp/nodone.md")"; rc=$?
|
|
224
|
+
if [ $rc -eq 0 ] || ! grep -q '^BRIEF RED: no-done-section' <<< "$out"; then
|
|
225
|
+
printf ' FAIL (d) a brief with no Done section passed:\n%s\n' "$out" >&2; bad=1
|
|
226
|
+
else
|
|
227
|
+
printf ' ok (d) a brief with no Done section is RED on no-done-section\n'
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# (e) EVERY named rule must fire. A declared rule that no case exercises is a
|
|
231
|
+
# rule that can ship broken and pass a brief it exists to refuse — one line
|
|
232
|
+
# per rule, all eleven, and the count asserted so a line tripping two rules
|
|
233
|
+
# (or none) is caught rather than averaged away.
|
|
234
|
+
cat > "$tmp/allrules.md" <<'ALLRULES'
|
|
235
|
+
BUILD: one line per deferred decision, in src/lib/one.ts.
|
|
236
|
+
Verify which read is right.
|
|
237
|
+
Check whether the snapshot is warm.
|
|
238
|
+
The signal name is TBD.
|
|
239
|
+
Leave a TODO where the gate goes.
|
|
240
|
+
Name the signal now and decide later on the payload.
|
|
241
|
+
Append the block, or alternatively insert it at the top.
|
|
242
|
+
Use either the snapshot or a live read.
|
|
243
|
+
Should the picker show archived pages?
|
|
244
|
+
Reuse the existing helper if possible.
|
|
245
|
+
Ideally the control renders in the footer.
|
|
246
|
+
Set the base to `4321 or 4325` first.
|
|
247
|
+
DONE LOOKS LIKE
|
|
248
|
+
1. It renders.
|
|
249
|
+
ALLRULES
|
|
250
|
+
cases=$((cases + 1))
|
|
251
|
+
out="$(check_brief "$(cat "$tmp/allrules.md")" "$tmp/allrules.md")"; rc=$?
|
|
252
|
+
local missing="" r n
|
|
253
|
+
for r in verify-which check-whether tbd todo decide-later or-alternatively \
|
|
254
|
+
either-or trailing-question if-possible ideally code-span-options; do
|
|
255
|
+
grep -q "^BRIEF RED: $r — " <<< "$out" || missing="$missing $r"
|
|
256
|
+
done
|
|
257
|
+
n="$(grep -c '^BRIEF RED: ' <<< "$out" | tr -d ' ')"
|
|
258
|
+
if [ -n "$missing" ]; then
|
|
259
|
+
printf ' FAIL (e) rules declared but never fired:%s\n%s\n' "$missing" "$out" >&2; bad=1
|
|
260
|
+
elif [ "$n" != 11 ]; then
|
|
261
|
+
printf ' FAIL (e) expected 11 hits, one per rule, got %s:\n%s\n' "$n" "$out" >&2; bad=1
|
|
262
|
+
else
|
|
263
|
+
printf ' ok (e) all 11 named rules fire — 11 hits, one per rule, no structural hit\n'
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
# (f) THE RED PROOF ITSELF. Gut this script — empty the RULES array, keep
|
|
267
|
+
# everything else — and case (a) MUST flip: the real brief that cost ~46
|
|
268
|
+
# minutes now passes. A checker that stays green against its own gutted copy
|
|
269
|
+
# proves nothing, which is the failure this whole script exists to refuse.
|
|
270
|
+
#
|
|
271
|
+
# Gut by STRUCTURE, never by line number, so an edit above cannot quietly turn
|
|
272
|
+
# this into a no-op. Two things were measured before this assertion was
|
|
273
|
+
# written, and both are why it reads the way it does:
|
|
274
|
+
# - `"${RULES[@]}"` on an EMPTY array is an unbound variable under `set -u`
|
|
275
|
+
# in bash 3.2, so the gutted copy used to die with a bash error and exit 1.
|
|
276
|
+
# "exit non-zero" would have looked like proof and measured the crash. The
|
|
277
|
+
# expansion in check_brief is empty-safe for exactly this reason.
|
|
278
|
+
# - code_span_options lives OUTSIDE Rules and still runs in the gutted copy;
|
|
279
|
+
# the real fixture happens to trip none of its spans, so the gutted copy
|
|
280
|
+
# reaches BRIEF OK. Both halves are asserted separately so a future change
|
|
281
|
+
# to either one names itself instead of averaging away.
|
|
282
|
+
cases=$((cases + 1))
|
|
283
|
+
awk '/^RULES=\(/{print "RULES=()"; s=1; next} s&&/^\)/{s=0; next} !s' \
|
|
284
|
+
"${BASH_SOURCE[0]}" > "$tmp/gutted.sh"
|
|
285
|
+
if ! grep -q '^RULES=()$' "$tmp/gutted.sh"; then
|
|
286
|
+
printf ' FAIL (f) the gutting did not take — no empty RULES in the copy\n' >&2; bad=1
|
|
287
|
+
elif ! bash -n "$tmp/gutted.sh" 2>/dev/null; then
|
|
288
|
+
printf ' FAIL (f) the gutted copy does not parse — the proof measures a syntax error, not the rules\n' >&2; bad=1
|
|
289
|
+
else
|
|
290
|
+
out="$(bash "$tmp/gutted.sh" "$fixture" 2>&1)"; rc=$?
|
|
291
|
+
if grep -q '^BRIEF RED: verify-which' <<< "$out"; then
|
|
292
|
+
printf ' FAIL (f) the gutted copy STILL refused the brief on verify-which — the RULES array is not load-bearing:\n%s\n' "$out" >&2; bad=1
|
|
293
|
+
elif [ $rc -ne 0 ]; then
|
|
294
|
+
printf ' FAIL (f) the gutted copy stopped the brief for some OTHER reason (rc=%s) — the red half of (a) is not attributable to the rules:\n%s\n' "$rc" "$out" >&2; bad=1
|
|
295
|
+
else
|
|
296
|
+
printf ' ok (f) rules emptied ⇒ the real brief PASSES (%s) — case (a) is attributable to the rules, not to the structural checks\n' "$out"
|
|
297
|
+
fi
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
if [ $bad -ne 0 ]; then
|
|
301
|
+
printf 'SELF-TEST RED: %s cases — the gate does not hold\n' "$cases"
|
|
302
|
+
return 1
|
|
303
|
+
fi
|
|
304
|
+
printf 'SELF-TEST OK: %s cases — red half red, green half green\n' "$cases"
|
|
305
|
+
return 0
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
main() {
|
|
309
|
+
case "${1:-}" in
|
|
310
|
+
--self-test) self_test; exit $? ;;
|
|
311
|
+
-h|--help)
|
|
312
|
+
awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}"
|
|
313
|
+
exit 0 ;;
|
|
314
|
+
"")
|
|
315
|
+
if [ -t 0 ]; then
|
|
316
|
+
printf 'usage: factory-brief-check.sh <brief-file> | - | --self-test\n' >&2
|
|
317
|
+
exit 2
|
|
318
|
+
fi
|
|
319
|
+
check_brief "$(cat)" "stdin"; exit $? ;;
|
|
320
|
+
-) check_brief "$(cat)" "stdin"; exit $? ;;
|
|
321
|
+
*)
|
|
322
|
+
if [ ! -f "$1" ]; then
|
|
323
|
+
printf 'factory-brief-check.sh: no such brief file: %s\n' "$1" >&2
|
|
324
|
+
exit 2
|
|
325
|
+
fi
|
|
326
|
+
check_brief "$(cat "$1")" "$1"; exit $? ;;
|
|
327
|
+
esac
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
main "$@"
|