@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
package/scripts/do-plan-json.mjs
CHANGED
|
@@ -49,25 +49,83 @@ const fm = t.match(/^---\n([\s\S]*?)\n---/)
|
|
|
49
49
|
if (!fm) fail('no frontmatter')
|
|
50
50
|
const F = fm[1]
|
|
51
51
|
|
|
52
|
-
// batches: `- [C0, C2]`
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
// batches: `- [C0, C2]` rows under `batches:`
|
|
53
|
+
//
|
|
54
|
+
// DEFECT this shape exists to prevent (measured 2026-09-08 on text/ads-loop-todo.md):
|
|
55
|
+
// the capture used to be /^batches:\n((?:[ \t]+- \[.*\]\n?)+)/m. A trailing
|
|
56
|
+
// `# comment` after the closing `]` is legal YAML and carried by most plans in
|
|
57
|
+
// text/ — it leaves text after `]`, so `\n?` matched empty, the `+` quantifier
|
|
58
|
+
// stopped at that row, and EVERY batch from there on was dropped. Nothing
|
|
59
|
+
// called fail(): the script still exited 0 and still emitted a well-formed
|
|
60
|
+
// object. ads-loop parsed as [["C1"…"C9"]] instead of [["C1"…"C9"],["C10"]],
|
|
61
|
+
// so /do would have run nine cycles of a ten-cycle plan and reported complete.
|
|
62
|
+
//
|
|
63
|
+
// Two changes, and the second is the load-bearing one:
|
|
64
|
+
// 1. a row may carry a trailing comment and trailing whitespace;
|
|
65
|
+
// 2. the block is delimited by INDENTATION (YAML's own rule) and every line
|
|
66
|
+
// inside it must parse — a line that does not is a REFUSAL, never a
|
|
67
|
+
// shorter list. Comment-tolerance alone would leave the next unreadable
|
|
68
|
+
// row failing in exactly the same silent way.
|
|
69
|
+
// Blank lines and whole-line `#` comments inside the block are legal and skipped.
|
|
70
|
+
// Inline flow style (`batches: [[C0]]`) still yields [] and fails at the
|
|
71
|
+
// `no batches:` invariant below, exactly as before.
|
|
72
|
+
const BATCH_ROW = /^[ \t]+-[ \t]*\[([^\]]*)\][ \t]*(?:#.*)?$/
|
|
73
|
+
const fLines = F.split('\n')
|
|
74
|
+
const bStart = fLines.findIndex((l) => /^batches:[ \t]*$/.test(l))
|
|
75
|
+
let batches = []
|
|
76
|
+
if (bStart >= 0) {
|
|
77
|
+
const rows = []
|
|
78
|
+
const bad = []
|
|
79
|
+
for (let i = bStart + 1; i < fLines.length; i++) {
|
|
80
|
+
const line = fLines[i]
|
|
81
|
+
if (!line.trim()) continue // blank line inside the block
|
|
82
|
+
if (!/^[ \t]/.test(line)) break // a column-0 line is the next key: block over
|
|
83
|
+
if (/^[ \t]*#/.test(line)) continue // whole-line comment
|
|
84
|
+
const m = line.match(BATCH_ROW)
|
|
85
|
+
if (m) rows.push(m[1])
|
|
86
|
+
else bad.push(line)
|
|
87
|
+
}
|
|
88
|
+
if (bad.length) {
|
|
89
|
+
fail(
|
|
90
|
+
`\`batches:\` carries ${bad.length} row(s) this parser cannot read. ` +
|
|
91
|
+
'Refusing: a truncated batch list must never read as a complete one. ' +
|
|
92
|
+
`A row is \`- [C1, C2]\` with an optional trailing # comment. First offender: ${JSON.stringify(bad[0])}`,
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
batches = rows.map((r) => r.split(',').map((s) => s.trim()).filter(Boolean))
|
|
96
|
+
}
|
|
58
97
|
|
|
59
98
|
// outcome: the kill-switch. A YAML double-quoted scalar carries \" and \\ escapes;
|
|
60
99
|
// stripping the outer quotes WITHOUT unescaping leaves a stray backslash that
|
|
61
100
|
// JSON.stringify then doubles, emitting invalid JSON (measured: lifecycle-money,
|
|
62
101
|
// lifecycle-human, both of whose outcomes contain a quoted grep pattern).
|
|
102
|
+
// A YAML scalar may carry a trailing `# comment`, and text/template-todo.md puts one
|
|
103
|
+
// on EVERY key a plan copies — `anchor: "" # filled by do-board.sh`,
|
|
104
|
+
// `board_workspace: one # where the rows live…`. The old body tested
|
|
105
|
+
// `endsWith('"')`, which a commented value fails, so it returned the raw line
|
|
106
|
+
// INCLUDING the comment. Measured 2026-09-12: boardAnchor came back as
|
|
107
|
+
// `"task:01a0…" # filled by do-board.sh` and do-board handed that to
|
|
108
|
+
// tasks:subtask as `parent`, dying `{"error":"invalid parent"}` — a message that
|
|
109
|
+
// never names the anchor. boardWorkspace was worse and silent: the whole comment
|
|
110
|
+
// became the workspace name, so rows would be minted somewhere that does not exist.
|
|
111
|
+
// A comment starts at `#` only when it is outside quotes and preceded by
|
|
112
|
+
// whitespace (or begins the value); inside quotes `#` is data.
|
|
63
113
|
function unquote(raw) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
114
|
+
const q = raw[0]
|
|
115
|
+
if ((q === '"' || q === "'") && raw.length > 1) {
|
|
116
|
+
// walk to the matching close quote; everything after it is comment/whitespace
|
|
117
|
+
for (let i = 1; i < raw.length; i++) {
|
|
118
|
+
if (q === '"' && raw[i] === '\\') { i++; continue }
|
|
119
|
+
if (raw[i] !== q) continue
|
|
120
|
+
if (q === "'" && raw[i + 1] === "'") { i++; continue } // '' is an escaped quote
|
|
121
|
+
const body = raw.slice(1, i)
|
|
122
|
+
return q === '"' ? body.replace(/\\(["\\])/g, '$1') : body.replace(/''/g, "'")
|
|
123
|
+
}
|
|
124
|
+
return raw // unterminated quote — hand it back verbatim rather than guess
|
|
69
125
|
}
|
|
70
|
-
|
|
126
|
+
// plain scalar: a comment needs leading whitespace, so `a#b` stays `a#b`
|
|
127
|
+
const c = raw.search(/[ \t]#/)
|
|
128
|
+
return (c === -1 ? raw : raw.slice(0, c)).trim()
|
|
71
129
|
}
|
|
72
130
|
let outcome = ''
|
|
73
131
|
const om = F.match(/^outcome:[ \t]*(?:\|[-+]?|>[-+]?)?[ \t]*(.*)$/m)
|
|
@@ -146,6 +204,42 @@ const sharedRecon = srm
|
|
|
146
204
|
.filter((f) => f && fs.existsSync(path.join(repo, f)))
|
|
147
205
|
: []
|
|
148
206
|
|
|
207
|
+
// ── the BOARD CONTRACT ──────────────────────────────────────────────────────
|
|
208
|
+
// A plan's cycles and the board's rows are the same work twice, and until this
|
|
209
|
+
// block existed nothing joined them: `batches:` held the ordering in markdown,
|
|
210
|
+
// D1 held it in `blocks` edges, and they drifted silently.
|
|
211
|
+
//
|
|
212
|
+
// It matters because `tasks:claim` is BLOCKER-GATED and reads the very `blocks`
|
|
213
|
+
// edge `tasks:depend` writes. A plan mirrored onto the board cannot be claimed
|
|
214
|
+
// out of order; an unmirrored one hands every agent every cycle at once.
|
|
215
|
+
//
|
|
216
|
+
// So emit both renderings and let the caller SEE the drift. `boardDrift` names
|
|
217
|
+
// every cycle the plan schedules that has no row — the ones invisible to every
|
|
218
|
+
// agent but the one running /do. It is reported, never fatal: a plan may be
|
|
219
|
+
// deliberately off-board, and a parser that refused would block that.
|
|
220
|
+
const anchorM = F.match(/^anchor:[ \t]*(.*)$/m)
|
|
221
|
+
const boardAnchor = anchorM ? unquote(anchorM[1].trim()) : ''
|
|
222
|
+
const bwM = F.match(/^board_workspace:[ \t]*(.*)$/m)
|
|
223
|
+
const boardWorkspace = bwM && bwM[1].trim() ? unquote(bwM[1].trim()) : 'one'
|
|
224
|
+
|
|
225
|
+
// cycle_rows: either inline flow (`cycle_rows: []`) or a block of
|
|
226
|
+
// ` - {cycle: C1, tid: "task:…"}` rows. Tolerant on purpose — this list is
|
|
227
|
+
// written back by a script and by hand, and a shape it cannot read must read as
|
|
228
|
+
// "no row" (surfaced in boardDrift) rather than crash a plan that is otherwise
|
|
229
|
+
// runnable.
|
|
230
|
+
const cycleRows = {}
|
|
231
|
+
const crStart = fLines.findIndex((l) => /^cycle_rows:[ \t]*$/.test(l))
|
|
232
|
+
if (crStart !== -1) {
|
|
233
|
+
for (let i = crStart + 1; i < fLines.length; i++) {
|
|
234
|
+
const l = fLines[i]
|
|
235
|
+
if (!/^[ \t]+-/.test(l)) { if (l.trim() && !/^[ \t]/.test(l)) break; else continue }
|
|
236
|
+
const c = l.match(/cycle:[ \t]*['"]?(C\d+)['"]?/)
|
|
237
|
+
const tid = l.match(/tid:[ \t]*['"]?(task:[0-9a-f]{24})['"]?/)
|
|
238
|
+
if (c && tid) cycleRows[c[1]] = tid[1]
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const boardDrift = batches.flat().filter((c) => !cycleRows[c])
|
|
242
|
+
|
|
149
243
|
process.stdout.write(JSON.stringify({
|
|
150
244
|
slug,
|
|
151
245
|
sharedRecon,
|
|
@@ -154,6 +248,10 @@ process.stdout.write(JSON.stringify({
|
|
|
154
248
|
worktree: path.join(repo, '.do-worktrees', slug),
|
|
155
249
|
outcome,
|
|
156
250
|
batches,
|
|
251
|
+
boardAnchor,
|
|
252
|
+
boardWorkspace,
|
|
253
|
+
cycleRows,
|
|
254
|
+
boardDrift,
|
|
157
255
|
openCycles,
|
|
158
256
|
closedCycles,
|
|
159
257
|
deps,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# do-prove-selftest.sh — the red proof for do-prove.sh's argument contract.
|
|
3
|
+
# Invoked as `do-prove.sh --self-test`; $1 is the do-prove.sh under test.
|
|
4
|
+
#
|
|
5
|
+
# Asked for by .claude/improvements.queue.md:307 ("it has no --self-test, unlike
|
|
6
|
+
# its sibling gates ... or this regresses silently the way it arrived").
|
|
7
|
+
#
|
|
8
|
+
# Every GREEN check here is paired with a RED one that GUTS the guard it depends
|
|
9
|
+
# on and asserts the check then fails. A checker that stays green against gutted
|
|
10
|
+
# code proves nothing.
|
|
11
|
+
#
|
|
12
|
+
# Needs no live server: the parser half is pure argument handling, and the base
|
|
13
|
+
# half points at a closed port on purpose.
|
|
14
|
+
set -uo pipefail
|
|
15
|
+
|
|
16
|
+
TARGET="${1:-}"
|
|
17
|
+
[ -n "$TARGET" ] && [ -f "$TARGET" ] || { echo "selftest: need the path to do-prove.sh"; exit 2; }
|
|
18
|
+
|
|
19
|
+
pass=0; fail=0
|
|
20
|
+
ok() { echo " ok — $1"; pass=$((pass+1)); }
|
|
21
|
+
no() { echo " FAIL — $1"; fail=$((fail+1)); }
|
|
22
|
+
|
|
23
|
+
# A port nothing is listening on. Verified closed, not assumed.
|
|
24
|
+
DEAD=""
|
|
25
|
+
for p in 45871 45872 45873 45874; do
|
|
26
|
+
if ! curl -sf -o /dev/null --max-time 1 "http://127.0.0.1:$p/" 2>/dev/null; then DEAD="$p"; break; fi
|
|
27
|
+
done
|
|
28
|
+
[ -n "$DEAD" ] || { echo "selftest: could not find a closed port"; exit 2; }
|
|
29
|
+
DEADBASE="http://127.0.0.1:$DEAD"
|
|
30
|
+
|
|
31
|
+
# A gutted copy of do-prove.sh with one guard removed, laid out so its own
|
|
32
|
+
# ROOT-from-BASH_SOURCE derivation still works.
|
|
33
|
+
# A mutant must still PARSE. Deleting the body of an `if` leaves an orphaned
|
|
34
|
+
# `fi`, and bash exits 2 on a syntax error — which is indistinguishable from a
|
|
35
|
+
# usage error and would have let a broken mutant masquerade as a red proof.
|
|
36
|
+
# Measured while writing this file: both base-half mutants "failed" that way.
|
|
37
|
+
# So mutate by SUBSTITUTION (guard → `:`), and refuse a mutant that does not
|
|
38
|
+
# parse rather than scoring it.
|
|
39
|
+
gut() { # $1 = sed program → echoes the path of the mutant, or empty on failure
|
|
40
|
+
local d; d="$(mktemp -d)"
|
|
41
|
+
mkdir -p "$d/.claude/scripts"
|
|
42
|
+
sed "$1" "$TARGET" > "$d/.claude/scripts/do-prove.sh"
|
|
43
|
+
if ! cmp -s "$TARGET" "$d/.claude/scripts/do-prove.sh"; then :; else
|
|
44
|
+
echo "" ; return 0 # sed matched nothing — the guard moved
|
|
45
|
+
fi
|
|
46
|
+
bash -n "$d/.claude/scripts/do-prove.sh" 2>/dev/null || { echo ""; return 0; }
|
|
47
|
+
echo "$d/.claude/scripts/do-prove.sh"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
echo "do-prove --self-test"
|
|
51
|
+
echo "[1/2] parser — an unrecognised FLAG is a hard error"
|
|
52
|
+
|
|
53
|
+
out="$(bash "$TARGET" --route / --bogus </dev/null 2>&1)"; rc=$?
|
|
54
|
+
[ "$rc" -eq 2 ] && ok "unknown flag --bogus exits 2 (got $rc)" \
|
|
55
|
+
|| no "unknown flag --bogus should exit 2, got $rc"
|
|
56
|
+
case "$out" in *"unknown flag '--bogus'"*) ok "…and names the flag" ;;
|
|
57
|
+
*) no "output did not name the flag: $out" ;; esac
|
|
58
|
+
|
|
59
|
+
out="$(bash "$TARGET" --base </dev/null 2>&1)"; rc=$?
|
|
60
|
+
[ "$rc" -eq 2 ] && ok "--base with no value exits 2" || no "--base with no value: rc=$rc"
|
|
61
|
+
out="$(bash "$TARGET" --route </dev/null 2>&1)"; rc=$?
|
|
62
|
+
[ "$rc" -eq 2 ] && ok "--route with no value exits 2" || no "--route with no value: rc=$rc"
|
|
63
|
+
|
|
64
|
+
# The leniency that must SURVIVE: a bare token (cycle slug) is still ignored,
|
|
65
|
+
# not refused. do-w4-gates.sh:210 and do-smoke.sh:85-88 depend on this.
|
|
66
|
+
out="$(bash "$TARGET" some-cycle-slug --route / --base "$DEADBASE" </dev/null 2>&1)"; rc=$?
|
|
67
|
+
[ "$rc" -ne 2 ] && ok "a bare cycle slug is still tolerated (rc=$rc, not a usage error)" \
|
|
68
|
+
|| no "a bare cycle slug was refused as a usage error — this breaks existing callers"
|
|
69
|
+
case "$out" in *"is not a path (cycle slug?)"*) ok "…and still says so" ;;
|
|
70
|
+
*) no "the lenient note is gone" ;; esac
|
|
71
|
+
|
|
72
|
+
# RED: remove the -*) branch and the flag guard must stop biting.
|
|
73
|
+
m="$(gut 's|^ exit 2 ;;$| shift ;;|')"
|
|
74
|
+
if [ -z "$m" ]; then no "RED PROOF FAILED: could not build the flag-guard mutant"; else
|
|
75
|
+
out="$(bash "$m" --route / --bogus --base "$DEADBASE" </dev/null 2>&1)"; rc=$?
|
|
76
|
+
[ "$rc" -ne 2 ] && ok "RED PROOF: gutted flag guard no longer exits 2 (got $rc) — check 1 is live" \
|
|
77
|
+
|| no "RED PROOF FAILED: mutant still exits 2; check 1 proves nothing"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
echo "[2/2] base — --base pins the leg, and an unrun proof is not a pass"
|
|
81
|
+
|
|
82
|
+
out="$(bash "$TARGET" --route / --base "$DEADBASE" </dev/null 2>&1)"; rc=$?
|
|
83
|
+
[ "$rc" -ne 0 ] && ok "explicit --base that answers nothing exits non-zero (got $rc)" \
|
|
84
|
+
|| no "explicit --base answered nothing and still exited 0 — the fail-open is back"
|
|
85
|
+
case "$out" in *"an unrun proof is not a pass"*) ok "…and says why" ;;
|
|
86
|
+
*) no "no unrun message: $out" ;; esac
|
|
87
|
+
case "$out" in *"https://one.ie"*) no "--base did not pin the fallback — it reached for https://one.ie" ;;
|
|
88
|
+
*) ok "--base pinned the fallback (production never named)" ;; esac
|
|
89
|
+
|
|
90
|
+
# RED: remove the fallback pin → the mutant must reach for https://one.ie.
|
|
91
|
+
m="$(gut 's|^ PROD_URL="\$BASE_URL"$| :|')"
|
|
92
|
+
if [ -z "$m" ]; then no "RED PROOF FAILED: could not build the pin mutant"; else
|
|
93
|
+
out="$(bash "$m" --route / --base "$DEADBASE" </dev/null 2>&1)"
|
|
94
|
+
case "$out" in *"https://one.ie"*) ok "RED PROOF: unpinned mutant reaches production — the pin is load-bearing" ;;
|
|
95
|
+
*) no "RED PROOF FAILED: mutant did not reach production; the pin check proves nothing" ;; esac
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# RED: remove the unrun-is-not-a-pass block → the mutant must go back to exit 0.
|
|
99
|
+
m="$(gut 's|^ exit 1$| :|')"
|
|
100
|
+
if [ -z "$m" ]; then no "RED PROOF FAILED: could not build the fail-open mutant"; else
|
|
101
|
+
out="$(bash "$m" --route / --base "$DEADBASE" </dev/null 2>&1)"; rc=$?
|
|
102
|
+
if [ "$rc" -eq 0 ]; then ok "RED PROOF: gutted mutant exits 0 on an unreachable base — the guard is live"
|
|
103
|
+
else no "RED PROOF FAILED: mutant still exits $rc; the fail-open check proves nothing"; fi
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
echo
|
|
107
|
+
echo "do-prove --self-test: $pass passed, $fail failed"
|
|
108
|
+
[ "$fail" -eq 0 ] || exit 1
|
package/scripts/do-prove.sh
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
# (chrome-headless-shell page check via chrome.mjs, curl fallback against prod).
|
|
5
5
|
#
|
|
6
6
|
# Usage:
|
|
7
|
-
# do-prove.sh [--route <path>]... [<changed-path>|<cycle-slug>]...
|
|
7
|
+
# do-prove.sh [--route <path>]... [--base <url>] [<changed-path>|<cycle-slug>]...
|
|
8
|
+
# do-prove.sh --self-test
|
|
8
9
|
# git diff --name-only | do-prove.sh
|
|
9
10
|
#
|
|
10
11
|
# Arg contract:
|
|
@@ -12,10 +13,36 @@
|
|
|
12
13
|
# - Positional args that do NOT exist on disk (e.g. a cycle slug) are ignored
|
|
13
14
|
# for surface detection; changed paths then come from `git diff --name-only HEAD`.
|
|
14
15
|
# - --route <path> (repeatable) forces frontend surface + names the route(s) to prove.
|
|
16
|
+
# - --base <url> proves against <url> instead of the default localhost base.
|
|
15
17
|
# - stdin (non-tty) supplies changed paths when no positional paths are given.
|
|
18
|
+
# - AN UNRECOGNISED FLAG IS A HARD ERROR (exit 2). It was not, until 2026-09-07,
|
|
19
|
+
# and that is how the launch promise came to prove a laptop. `text/bring-one-to-life.md`
|
|
20
|
+
# deliverable 6 read `--route /keys --base https://one.ie`; `--base` did not
|
|
21
|
+
# exist, so BOTH tokens fell through the lenient `*)` branch as "not a path
|
|
22
|
+
# (cycle slug?)" and the run silently used the localhost default. Measured that
|
|
23
|
+
# morning with a dev server up on :4321:
|
|
24
|
+
# PROVE: note — '--base' is not a path (cycle slug?); …
|
|
25
|
+
# PROVE: ok / — http=200, 0 console/js errors (dev http://localhost:4321)
|
|
26
|
+
# PROVE: pass (1 route(s) proven) ← exit 0, prod never contacted
|
|
27
|
+
# The leniency is deliberate for BARE tokens (a cycle slug is passed
|
|
28
|
+
# legitimately — do-w4-gates.sh:210, do-smoke.sh:85-88). It is never correct
|
|
29
|
+
# for a token starting with `-`: nobody passes a flag they do not mean.
|
|
30
|
+
#
|
|
31
|
+
# --base semantics (matches land.sh:344-345, which pins both bases by hand and
|
|
32
|
+
# says why): --base PINS THE FALLBACK TOO, unless PROVE_PROD_URL is explicitly
|
|
33
|
+
# exported. Setting only the dev base would rebuild the same defect one layer
|
|
34
|
+
# down — an unreachable `--base https://staging.x` would fall through to
|
|
35
|
+
# PROD_URL and prove PRODUCTION while reporting it as staging.
|
|
36
|
+
# And an explicit --base that ANSWERS NOTHING exits non-zero rather than
|
|
37
|
+
# `PROVE: skipped` (exit 0): do-promise-settle.sh runs a promise's proof: string
|
|
38
|
+
# verbatim and reads the exit code, so a skipped prod probe would settle the
|
|
39
|
+
# promise KEPT with the site down. An unrun gate is not a pass. Bare invocations
|
|
40
|
+
# keep the old skip-is-0 semantics — land.sh:346-350 reads the ROUTE COUNT and
|
|
41
|
+
# factory-walk.sh:294 matches `PROVE: skipped` → unrun, and both rely on it.
|
|
16
42
|
#
|
|
17
43
|
# Env:
|
|
18
|
-
# PROVE_BASE_URL dev base for browser check (default http://localhost:4321)
|
|
44
|
+
# PROVE_BASE_URL dev base for browser check (default http://localhost:4321).
|
|
45
|
+
# `--base <url>` is the flag form of exactly this, and wins.
|
|
19
46
|
# PROVE_PROD_URL prod fallback base (default https://one.ie)
|
|
20
47
|
# PROVE_CHECK_CMD optional check command for non-frontend surfaces; if set and
|
|
21
48
|
# it fails, do-prove.sh exits nonzero.
|
|
@@ -44,7 +71,10 @@
|
|
|
44
71
|
#
|
|
45
72
|
# Exit codes: 0 = pass or skipped (no reachable environment) · 1 = a reachable
|
|
46
73
|
# page failed (HTTP >= 500 / 404 / status 0 / console+JS errors / landed on a
|
|
47
|
-
# different path than requested)
|
|
74
|
+
# different path than requested), a declared check failed, or an explicit --base
|
|
75
|
+
# answered nothing · 2 = usage error (unknown flag, or a flag missing its value).
|
|
76
|
+
#
|
|
77
|
+
# --self-test drives both halves red and green with no live server required.
|
|
48
78
|
set -euo pipefail
|
|
49
79
|
|
|
50
80
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
@@ -54,11 +84,34 @@ BROWSER_CHECK="$ROOT/.claude/scripts/chrome.mjs"
|
|
|
54
84
|
|
|
55
85
|
routes=()
|
|
56
86
|
paths=()
|
|
87
|
+
base_explicit=0
|
|
88
|
+
self_test=0
|
|
89
|
+
|
|
90
|
+
prove_usage() {
|
|
91
|
+
echo "PROVE: usage: do-prove.sh [--route <path>]... [--base <url>] [--self-test] [<changed-path>|<cycle-slug>]..."
|
|
92
|
+
}
|
|
93
|
+
|
|
57
94
|
while [ "$#" -gt 0 ]; do
|
|
58
95
|
case "$1" in
|
|
59
96
|
--route)
|
|
60
|
-
[ "$#" -ge 2 ] || { echo "PROVE: --route requires a value"; exit
|
|
97
|
+
[ "$#" -ge 2 ] || { echo "PROVE: --route requires a value"; exit 2; }
|
|
61
98
|
routes+=("$2"); shift 2 ;;
|
|
99
|
+
--base)
|
|
100
|
+
# The flag form of PROVE_BASE_URL. Trailing slash stripped so
|
|
101
|
+
# "$BASE_URL$route" never produces a double slash.
|
|
102
|
+
[ "$#" -ge 2 ] || { echo "PROVE: --base requires a value"; exit 2; }
|
|
103
|
+
BASE_URL="${2%/}"; base_explicit=1; shift 2 ;;
|
|
104
|
+
--self-test)
|
|
105
|
+
self_test=1; shift ;;
|
|
106
|
+
-h|--help)
|
|
107
|
+
prove_usage; exit 0 ;;
|
|
108
|
+
-*)
|
|
109
|
+
# THE ROOT CAUSE, closed. A token that looks like a flag and is not one is
|
|
110
|
+
# a caller asking for something this script cannot do — never a cycle slug.
|
|
111
|
+
# Silently continuing is how `--base https://one.ie` became a no-op.
|
|
112
|
+
echo "PROVE: unknown flag '$1'" >&2
|
|
113
|
+
prove_usage >&2
|
|
114
|
+
exit 2 ;;
|
|
62
115
|
*)
|
|
63
116
|
# A real path (exists on disk relative to root or cwd) counts as a changed
|
|
64
117
|
# path; anything else (a cycle slug) is noted and ignored for detection.
|
|
@@ -71,6 +124,17 @@ while [ "$#" -gt 0 ]; do
|
|
|
71
124
|
esac
|
|
72
125
|
done
|
|
73
126
|
|
|
127
|
+
# An explicit --base means "prove HERE, nowhere else". Only an explicitly
|
|
128
|
+
# exported PROVE_PROD_URL may name a different fallback (land.sh does exactly
|
|
129
|
+
# that, pinning both to the dev URL).
|
|
130
|
+
if [ "$base_explicit" -eq 1 ] && [ -z "${PROVE_PROD_URL:-}" ]; then
|
|
131
|
+
PROD_URL="$BASE_URL"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
if [ "$self_test" -eq 1 ]; then
|
|
135
|
+
exec bash "$ROOT/.claude/scripts/do-prove-selftest.sh" "$ROOT/.claude/scripts/do-prove.sh"
|
|
136
|
+
fi
|
|
137
|
+
|
|
74
138
|
# stdin (piped) paths
|
|
75
139
|
if [ "${#paths[@]}" -eq 0 ] && [ ! -t 0 ]; then
|
|
76
140
|
while IFS= read -r l; do [ -n "$l" ] && paths+=("$l"); done || true
|
|
@@ -265,6 +329,11 @@ report_redirect() { # $1 = route, $2 = landed path, $3 = leg label
|
|
|
265
329
|
esac
|
|
266
330
|
}
|
|
267
331
|
|
|
332
|
+
# The result line used to hardcode the word "dev" — so a production run
|
|
333
|
+
# printed "(dev https://one.ie)" on the very receipt someone reads to decide
|
|
334
|
+
# whether the site is live. Name the leg for what it is.
|
|
335
|
+
if _prove_local_base; then LEG="dev $BASE_URL"; else LEG="base $BASE_URL"; fi
|
|
336
|
+
|
|
268
337
|
fail=0
|
|
269
338
|
checked=0
|
|
270
339
|
for route in "${routes[@]}"; do
|
|
@@ -341,14 +410,14 @@ for route in "${routes[@]}"; do
|
|
|
341
410
|
# the browser leg never did, so a nonexistent route proved itself green
|
|
342
411
|
# whenever the page also logged no errors. 401/403 stay ok — gated, not gone.
|
|
343
412
|
if [ -n "$final" ] && [ "$landed" != "$(path_of "$route")" ]; then
|
|
344
|
-
report_redirect "$route" "$landed" "
|
|
413
|
+
report_redirect "$route" "$landed" "$LEG"
|
|
345
414
|
fail=1
|
|
346
415
|
elif [ "$status" -ge 500 ] || [ "$status" -eq 404 ] || [ "$errs" -gt 0 ]; then
|
|
347
|
-
echo "PROVE: FAIL $route — http=$status console/js errors=$errs (
|
|
416
|
+
echo "PROVE: FAIL $route — http=$status console/js errors=$errs ($LEG)"
|
|
348
417
|
printf '%s' "$out" | jq -r "$counted[]" 2>/dev/null | head -5 | sed 's/^/PROVE: error: /'
|
|
349
418
|
fail=1
|
|
350
419
|
else
|
|
351
|
-
echo "PROVE: ok $route — http=$status, 0 console/js errors (
|
|
420
|
+
echo "PROVE: ok $route — http=$status, 0 console/js errors ($LEG)"
|
|
352
421
|
fi
|
|
353
422
|
# Never silent: an ignored error is still an error someone should see.
|
|
354
423
|
ign="$(printf '%s' "$out" | jq -r "$ignored" 2>/dev/null || echo 0)"
|
|
@@ -372,11 +441,11 @@ for route in "${routes[@]}"; do
|
|
|
372
441
|
# running a tree without the page, and that line was the 17th leg of a
|
|
373
442
|
# promise's proof. 401/403 stay ok: the route answered, it is merely gated.
|
|
374
443
|
if [ -n "$effective" ] && [ "$landed" != "$(path_of "$route")" ]; then
|
|
375
|
-
report_redirect "$route" "$landed" "
|
|
444
|
+
report_redirect "$route" "$landed" "$LEG, curl"; fail=1
|
|
376
445
|
elif [ "$code" -ge 500 ] || [ "$code" -eq 404 ]; then
|
|
377
|
-
echo "PROVE: FAIL $route — http=$code (
|
|
446
|
+
echo "PROVE: FAIL $route — http=$code ($LEG, curl)"; fail=1
|
|
378
447
|
else
|
|
379
|
-
echo "PROVE: ok $route — http=$code (
|
|
448
|
+
echo "PROVE: ok $route — http=$code ($LEG, curl)"
|
|
380
449
|
fi
|
|
381
450
|
fi
|
|
382
451
|
fi
|
|
@@ -405,6 +474,13 @@ if [ "$fail" -eq 1 ]; then
|
|
|
405
474
|
echo "PROVE: fail"; exit 1
|
|
406
475
|
fi
|
|
407
476
|
if [ "$checked" -eq 0 ]; then
|
|
477
|
+
# An explicitly named base that answered nothing is an UNRUN PROOF, and
|
|
478
|
+
# do-promise-settle.sh reads this exit code verbatim. Exit 0 here would settle
|
|
479
|
+
# a promise KEPT because the site was unreachable.
|
|
480
|
+
if [ "$base_explicit" -eq 1 ]; then
|
|
481
|
+
echo "PROVE: FAIL — --base $BASE_URL answered nothing; an unrun proof is not a pass"
|
|
482
|
+
exit 1
|
|
483
|
+
fi
|
|
408
484
|
echo "PROVE: skipped (no reachable environment)"; exit 0
|
|
409
485
|
fi
|
|
410
486
|
echo "PROVE: pass ($checked route(s) proven)"; exit 0
|