@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-rank.py
CHANGED
|
@@ -23,6 +23,8 @@ Usage:
|
|
|
23
23
|
do-rank.py human table
|
|
24
24
|
do-rank.py --json machine queue for the fleet
|
|
25
25
|
do-rank.py --top N the N highest READY slugs, one per line
|
|
26
|
+
do-rank.py --board-check perform the live board read (floors print as `>=N`)
|
|
27
|
+
do-rank.py --untriaged [ws] the triage queue as JSON — tasks:board, every page
|
|
26
28
|
"""
|
|
27
29
|
import json
|
|
28
30
|
import os
|
|
@@ -392,6 +394,60 @@ def _gateway_key():
|
|
|
392
394
|
return found["one-"] or found["osk_"] or found["gw"]
|
|
393
395
|
|
|
394
396
|
|
|
397
|
+
# ── the cap, and whether it bit ───────────────────────────────────────────────
|
|
398
|
+
# Both board doors can hand back a PAGE and let it read as the board, and until
|
|
399
|
+
# 2026-09-13 neither caller here looked. The two doors do not mean the same thing
|
|
400
|
+
# by `total`, so they are not read the same way:
|
|
401
|
+
# tasks:everywhere — `total` is "visible rows BEFORE the limit was applied"
|
|
402
|
+
# (receivers.ts) and `truncated` is true when more exist. Both usable.
|
|
403
|
+
# /api/things — `total` is `tasks.length` of the slice it JUST CUT
|
|
404
|
+
# (api/things/index.ts:396: "We do NOT claim a grand total we never
|
|
405
|
+
# measured"). Only its `truncated` object is usable; printing its `total`
|
|
406
|
+
# as a board total would be a NEW dishonesty, so it is deliberately dropped.
|
|
407
|
+
# What this records is what the last read can honestly say. A number that is a
|
|
408
|
+
# floor prints as a floor — `>=N` — because the rows past the cap were never seen.
|
|
409
|
+
LAST_BOARD_READ = {"door": "", "returned": 0, "total": None, "truncated": False, "why": ""}
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _note_board_read(door, returned, total=None, truncated=False, asked=None):
|
|
413
|
+
"""Record what the last board read can honestly say about its own cap. Called by
|
|
414
|
+
EVERY board read, including the failing ones, so a stale note can never be read
|
|
415
|
+
as this read's answer.
|
|
416
|
+
|
|
417
|
+
`asked` is the limit WE SENT, and it is the load-bearing argument. Measured
|
|
418
|
+
2026-09-13 against prod: `tasks:everywhere` returned exactly 200 rows — our own
|
|
419
|
+
limit — with NO `total` and NO `truncated`, because those two fields are
|
|
420
|
+
declared in receivers.ts and the deployed worker does not yet send them. So a
|
|
421
|
+
cap that reports itself is a contract, not a fact, and trusting it would have
|
|
422
|
+
printed a silent floor as a clean count: exactly the defect this exists to fix.
|
|
423
|
+
A full page is structural evidence of a cap — it is derived from what we asked
|
|
424
|
+
for, so it holds whether or not the door is honest yet."""
|
|
425
|
+
truncated = bool(truncated)
|
|
426
|
+
why = "the door reported a cap" if truncated else ""
|
|
427
|
+
if not truncated and isinstance(asked, int) and asked > 0 and returned >= asked:
|
|
428
|
+
truncated, why = True, f"the page came back FULL at our own limit of {asked}, and the door reported no cap"
|
|
429
|
+
LAST_BOARD_READ.update(
|
|
430
|
+
door=door, returned=returned,
|
|
431
|
+
total=total if isinstance(total, int) else None,
|
|
432
|
+
truncated=truncated, why=why,
|
|
433
|
+
)
|
|
434
|
+
return LAST_BOARD_READ
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def board_count_phrase(n=None):
|
|
438
|
+
"""How many rows the last board read actually PROVES, as words.
|
|
439
|
+
|
|
440
|
+
Uncapped → the count. Capped with a usable pre-limit total → `N of T (capped)`.
|
|
441
|
+
Capped with no honest total → `>=N (capped)`. Never a bare count over a cap."""
|
|
442
|
+
r = LAST_BOARD_READ
|
|
443
|
+
n = r["returned"] if n is None else n
|
|
444
|
+
if not r["truncated"]:
|
|
445
|
+
return str(n)
|
|
446
|
+
if isinstance(r["total"], int) and r["total"] > n:
|
|
447
|
+
return f"{n} of {r['total']} (CAPPED — {r['total'] - n} row(s) never read)"
|
|
448
|
+
return f">={n} (CAPPED — {r['why'] or 'the door returned a page, not the board'})"
|
|
449
|
+
|
|
450
|
+
|
|
395
451
|
def fetch_tasks_everywhere(timeout=None):
|
|
396
452
|
"""Board-origin open tasks — the substrate half of the ranked queue (tasks-do C2).
|
|
397
453
|
|
|
@@ -470,7 +526,11 @@ def fetch_tasks_everywhere(timeout=None):
|
|
|
470
526
|
return [], f"receiver refused: {err}{hint}"
|
|
471
527
|
rows = inner.get("tasks", [])
|
|
472
528
|
if not isinstance(rows, list):
|
|
529
|
+
_note_board_read("tasks:everywhere", 0)
|
|
473
530
|
return [], "malformed response (tasks is not a list)"
|
|
531
|
+
# The receiver declares both (receivers.ts § tasks:everywhere) and this dropped
|
|
532
|
+
# them: a 595-row queue answered as 200 and read as the whole board.
|
|
533
|
+
_note_board_read("tasks:everywhere", len(rows), inner.get("total"), inner.get("truncated"), asked=200)
|
|
474
534
|
# tasks:everywhere answers "workspaces I BELONG TO" — assignment/follow/ownership. A key
|
|
475
535
|
# whose actor belongs to nothing (the MCP bootstrap agent is one) gets ok:true and an
|
|
476
536
|
# empty list while the operator's own board is full. That green-empty read is worse than
|
|
@@ -518,10 +578,125 @@ def fetch_workspace_board(timeout=None, workspace=None):
|
|
|
518
578
|
return [], f"unreachable ({type(e).__name__})"
|
|
519
579
|
rows = payload.get("things") if isinstance(payload, dict) else None
|
|
520
580
|
if not isinstance(rows, list):
|
|
581
|
+
_note_board_read("/api/things", 0)
|
|
521
582
|
return [], "malformed response (things is not a list)"
|
|
583
|
+
# `limit=200` above is a HARD cap and this read is the ranker's last word on
|
|
584
|
+
# what is on the board. /api/things reports its own cap as a `truncated`
|
|
585
|
+
# OBJECT (absent = nothing was cut) — truthy is the whole test. Its `total` is
|
|
586
|
+
# the length of the slice, so it is NOT passed on as a board total.
|
|
587
|
+
_note_board_read("/api/things", len(rows), None, bool(payload.get("truncated")), asked=200)
|
|
522
588
|
return rows, ""
|
|
523
589
|
|
|
524
590
|
|
|
591
|
+
def board_ask(receiver, data, timeout=None):
|
|
592
|
+
"""POST one receiver through the same door fetch_tasks_everywhere uses, with the
|
|
593
|
+
/api/ask envelope unwrapped. Returns (inner, reason) — never a silent empty."""
|
|
594
|
+
if timeout is None:
|
|
595
|
+
timeout = float(os.environ.get("DO_RANK_BOARD_TIMEOUT", "25"))
|
|
596
|
+
secret = _gateway_key()
|
|
597
|
+
if not secret:
|
|
598
|
+
return None, "no GATEWAY_API_KEY in one.ie/web/.env"
|
|
599
|
+
url = os.environ.get("DO_SIGNAL_URL", "https://one.ie") + "/api/ask/" + receiver
|
|
600
|
+
req = urllib.request.Request(
|
|
601
|
+
url, data=json.dumps({"data": data}).encode(), method="POST",
|
|
602
|
+
headers={
|
|
603
|
+
"Content-Type": "application/json",
|
|
604
|
+
"Authorization": f"Bearer {secret}",
|
|
605
|
+
# Cloudflare 403s the default urllib UA before the Worker runs.
|
|
606
|
+
"User-Agent": "one-do-rank/1.0 (+https://one.ie)",
|
|
607
|
+
},
|
|
608
|
+
)
|
|
609
|
+
try:
|
|
610
|
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
611
|
+
payload = json.loads(r.read().decode())
|
|
612
|
+
except urllib.error.HTTPError as e:
|
|
613
|
+
detail = "CF bot block (UA rejected)" if e.code == 403 else ""
|
|
614
|
+
return None, f"HTTP {e.code}{' — ' + detail if detail else ''}"
|
|
615
|
+
except Exception as e:
|
|
616
|
+
return None, f"unreachable ({type(e).__name__})"
|
|
617
|
+
if not isinstance(payload, dict):
|
|
618
|
+
return None, "malformed response (not an object)"
|
|
619
|
+
inner = payload.get("result")
|
|
620
|
+
inner = inner if isinstance(inner, dict) else payload
|
|
621
|
+
if not inner.get("ok"):
|
|
622
|
+
return None, str(inner.get("error") or payload.get("outcome") or "unknown")
|
|
623
|
+
return inner, ""
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
# What triage treats as already-shaped. A row whose prose goal opens with this has
|
|
627
|
+
# a one-line check that proves it done, which is the whole output of a triage pass.
|
|
628
|
+
TRIAGED_PREFIX = "accept:"
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def fetch_untriaged(workspace=None, pages=None, timeout=None):
|
|
632
|
+
"""Every OPEN row whose notes do NOT begin `accept:` — the triage queue, read
|
|
633
|
+
deterministically.
|
|
634
|
+
|
|
635
|
+
Why this is a script and not a prompt: triage.js used to hand a model the ONE
|
|
636
|
+
LINE `--board-check` prints and ask it for `{id,title,notes}[]`. That line
|
|
637
|
+
carries no rows, so the only possible answers were an invented list or an empty
|
|
638
|
+
one, and the only guard was the words "Do not invent rows." A fact the harness
|
|
639
|
+
needs gets a script (.claude/CLAUDE.md).
|
|
640
|
+
|
|
641
|
+
ONE door — tasks:board — followed to the LAST page. `nextCursor` is present iff
|
|
642
|
+
more rows match, so stopping at page one reports a page as the board; that is
|
|
643
|
+
the exact failure this mode exists to refuse. `include:["notes"]` is required:
|
|
644
|
+
a compact row carries no notes and the accept: test IS the filter. There is no
|
|
645
|
+
server-side notes filter, so the test runs here, over every page.
|
|
646
|
+
|
|
647
|
+
`truncated` is a DIFFERENT cut from paging — it means the snapshot itself hit a
|
|
648
|
+
budget, so `total` is only a floor. It is carried through verbatim."""
|
|
649
|
+
ws = workspace or os.environ.get("CC_TASKS_WORKSPACE", "one")
|
|
650
|
+
# A ceiling on the cursor walk so a runaway board cannot loop forever. 20 pages
|
|
651
|
+
# x 500 rows is 10,000 — far past any real board, and reported when it bites.
|
|
652
|
+
pages = int(pages or os.environ.get("DO_RANK_UNTRIAGED_PAGES", "20"))
|
|
653
|
+
out, cursor, total, truncated, seen, page = [], None, None, None, 0, 0
|
|
654
|
+
while page < pages:
|
|
655
|
+
req = {"workspace": ws, "status": "open", "view": "rows",
|
|
656
|
+
"include": ["notes"], "limit": 500, "sort": "priority"}
|
|
657
|
+
if cursor:
|
|
658
|
+
req["cursor"] = cursor
|
|
659
|
+
inner, reason = board_ask("tasks:board", req, timeout=timeout)
|
|
660
|
+
if reason:
|
|
661
|
+
# A failure mid-walk leaves a PARTIAL answer, and it must never read as
|
|
662
|
+
# the whole board: truncated=True is the honest state, always a 3-tuple.
|
|
663
|
+
_note_board_read("tasks:board", seen, total, True)
|
|
664
|
+
return out, f"page {page + 1}: {reason}", {
|
|
665
|
+
"workspace": ws, "scanned": seen, "total": total,
|
|
666
|
+
"truncated": truncated or None, "pagesExhausted": True, "pages": page,
|
|
667
|
+
}
|
|
668
|
+
rows = inner.get("tasks")
|
|
669
|
+
rows = rows if isinstance(rows, list) else []
|
|
670
|
+
if total is None and isinstance(inner.get("total"), int):
|
|
671
|
+
total = inner["total"]
|
|
672
|
+
if inner.get("truncated") and truncated is None:
|
|
673
|
+
truncated = inner["truncated"]
|
|
674
|
+
seen += len(rows)
|
|
675
|
+
for r in rows:
|
|
676
|
+
notes = str(r.get("notes") or "")
|
|
677
|
+
if notes.lstrip().lower().startswith(TRIAGED_PREFIX):
|
|
678
|
+
continue
|
|
679
|
+
out.append({"id": r.get("tid", ""), "title": r.get("name", ""),
|
|
680
|
+
"notes": notes, "priority": r.get("priority", 0),
|
|
681
|
+
"assignee": r.get("assignee", ""), "tags": r.get("tags") or []})
|
|
682
|
+
page += 1
|
|
683
|
+
cursor = inner.get("nextCursor")
|
|
684
|
+
if not cursor:
|
|
685
|
+
break
|
|
686
|
+
# Two independent cuts, reported separately: `truncated` = the snapshot was
|
|
687
|
+
# capped (total is a floor); `pagesExhausted` = WE stopped following cursors.
|
|
688
|
+
# `asked` is the third and it needs no cooperation from the door: a LAST page
|
|
689
|
+
# that came back full with no nextCursor is a cap that did not announce itself
|
|
690
|
+
# (measured on tasks:everywhere the day this was written — it returned exactly
|
|
691
|
+
# our limit and declared neither field).
|
|
692
|
+
pages_exhausted = bool(cursor)
|
|
693
|
+
_note_board_read("tasks:board", seen, total, bool(truncated) or pages_exhausted,
|
|
694
|
+
asked=None if cursor else 500 * page or None)
|
|
695
|
+
return out, "", {"workspace": ws, "scanned": seen, "total": total,
|
|
696
|
+
"truncated": truncated or None, "pagesExhausted": pages_exhausted,
|
|
697
|
+
"pages": page, "capNote": LAST_BOARD_READ["why"] or None}
|
|
698
|
+
|
|
699
|
+
|
|
525
700
|
def board_task_entry(row):
|
|
526
701
|
"""Shape one tasks:everywhere row as a plan-like dict — the same keys tasks_view()/
|
|
527
702
|
tasks_seed() already read off a local todo's `plans[slug]` entry, so a board-created
|
|
@@ -549,13 +724,17 @@ def merge_board_tasks(active, plans):
|
|
|
549
724
|
local match is a pure board-created task and is simply added. Empty/failed fetch is a
|
|
550
725
|
no-op — file-only ranking, unchanged."""
|
|
551
726
|
rows, reason = fetch_tasks_everywhere()
|
|
727
|
+
door = LAST_BOARD_READ["door"] or "tasks:everywhere"
|
|
552
728
|
if not rows:
|
|
553
729
|
why = reason or "board is empty (0 open tasks)"
|
|
554
|
-
print(f"[do-rank]
|
|
730
|
+
print(f"[do-rank] {door}: 0 rows — {why} — file-only fallback", file=sys.stderr)
|
|
555
731
|
return active
|
|
556
732
|
board_entries = [board_task_entry(r) for r in rows]
|
|
557
733
|
matched_slugs = {e["slug"] for e in board_entries if e["slug"] in plans}
|
|
558
|
-
|
|
734
|
+
# The door is named because the fallback inside fetch_tasks_everywhere can
|
|
735
|
+
# answer from /api/things, and this line said "tasks:everywhere" either way.
|
|
736
|
+
# The count is a FLOOR whenever that door capped — say so, don't round it off.
|
|
737
|
+
print(f"[do-rank] {door}: merged {board_count_phrase(len(board_entries))} row(s), "
|
|
559
738
|
f"{len(matched_slugs)} superseding a local todo", file=sys.stderr)
|
|
560
739
|
return [p for p in active if p["slug"] not in matched_slugs] + board_entries
|
|
561
740
|
|
|
@@ -1324,9 +1503,27 @@ def main():
|
|
|
1324
1503
|
if reason:
|
|
1325
1504
|
print(f"[board-check] RED — {target}: {reason}", file=sys.stderr)
|
|
1326
1505
|
return 1
|
|
1327
|
-
|
|
1506
|
+
door = LAST_BOARD_READ["door"] or "tasks:everywhere"
|
|
1507
|
+
print(f"[board-check] OK — {target}: {board_count_phrase(len(rows))} "
|
|
1508
|
+
f"open task(s) readable (door: {door})")
|
|
1328
1509
|
return 0
|
|
1329
1510
|
|
|
1511
|
+
# `--untriaged [workspace]` — the triage queue as JSON on stdout, read through
|
|
1512
|
+
# tasks:board and followed to the last page. This is what .claude/workflows/
|
|
1513
|
+
# triage.js consumes: it used to ask a model to enumerate rows out of
|
|
1514
|
+
# --board-check's single summary line, which carries none. Deterministic fact,
|
|
1515
|
+
# deterministic script. Always prints an object; `ok:false` carries the reason.
|
|
1516
|
+
if "--untriaged" in args:
|
|
1517
|
+
i = args.index("--untriaged")
|
|
1518
|
+
ws = args[i + 1] if len(args) > i + 1 and not args[i + 1].startswith("-") else None
|
|
1519
|
+
rows, reason, meta = fetch_untriaged(workspace=ws)
|
|
1520
|
+
out = {"ok": not reason, "tasks": rows, "returned": len(rows)}
|
|
1521
|
+
out.update(meta)
|
|
1522
|
+
if reason:
|
|
1523
|
+
out["error"] = reason
|
|
1524
|
+
print(json.dumps(out))
|
|
1525
|
+
return 0 if not reason else 1
|
|
1526
|
+
|
|
1330
1527
|
# `--lint` — mechanical deliverables hygiene across all OPEN plans. Flags phantom
|
|
1331
1528
|
# paths (under-qualified/wrong-rooted) that make a plan a fleet false-parallel, and
|
|
1332
1529
|
# plans with NO parseable deliverables (a fleet hazard). Exit 1 if any plan is dirty.
|
package/scripts/do-reconcile.sh
CHANGED
|
@@ -324,23 +324,84 @@ case "$CANON" in
|
|
|
324
324
|
_fp=$(tsc_tree_fingerprint "$folder" $_fp_extra)
|
|
325
325
|
_cache="$GOVERN_DIR/tsc-$(printf '%s' "$folder" | tr '/' '_').$_fp"
|
|
326
326
|
|
|
327
|
+
# Set HERE, outside the branches, so the rc=3 handler below can never read
|
|
328
|
+
# a value left by an earlier folder or an unset variable.
|
|
329
|
+
_tsc_rc=0
|
|
327
330
|
if [ -s "$_cache" ]; then
|
|
328
331
|
current=$(cat "$_cache" | tr -d '[:space:]') # someone already computed it
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
+
else
|
|
333
|
+
# ── SLOT BEFORE LOCK ─────────────────────────────────────────────────
|
|
334
|
+
# This block used to take the gate_lock HERE and then queue for a slot
|
|
335
|
+
# while holding it. tsc-cached.sh had the same inversion, on the SAME
|
|
336
|
+
# lock name (`tsc-<folder>` — the shared memo namespace is deliberate),
|
|
337
|
+
# and between them they deadlocked the whole box three times in ~35
|
|
338
|
+
# minutes on 2026-09-13: one side held a slot and waited for this lock,
|
|
339
|
+
# the other held this lock and waited for a slot. Neither owner was
|
|
340
|
+
# dead, so nothing reaped it, and killing the LOCK holder only handed
|
|
341
|
+
# the lock to the next waiter and re-formed the cycle within seconds.
|
|
342
|
+
# tsc-cached.sh § SLOT BEFORE LOCK has the full account; the proof is
|
|
343
|
+
# `bash .claude/scripts/govern-order-check.sh`.
|
|
344
|
+
#
|
|
345
|
+
# So the lock is taken INSIDE the gate-run command, never around it: a
|
|
346
|
+
# slot is held before anyone waits on this lock. The inner shell's exit
|
|
347
|
+
# code carries which of the three things happened — 3 = could not lock,
|
|
348
|
+
# 4 = someone else's answer appeared while we queued, 0 = we computed.
|
|
349
|
+
#
|
|
350
|
+
# Resolve gate-run from THIS script's own directory. An earlier version
|
|
351
|
+
# used "$ROOT/..." -- ROOT is never set in this file, and under `set -u`
|
|
352
|
+
# that is a hard "unbound variable" that killed the whole `types` canon.
|
|
353
|
+
# Caught by tests/integration/reconcile.test.ts, which is the reason
|
|
354
|
+
# that test exists.
|
|
355
|
+
_tsc_tmp=$(mktemp)
|
|
356
|
+
_tsc_rc=0
|
|
357
|
+
_gr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/gate-run.sh"
|
|
358
|
+
_gv="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/govern.sh"
|
|
359
|
+
# The compute is passed as DATA, not as nested quoting: three levels of
|
|
360
|
+
# `bash -c "... \"...\" ..."` is how a pipeline silently loses its
|
|
361
|
+
# redirect. Everything the inner shells read arrives through the
|
|
362
|
+
# environment instead.
|
|
363
|
+
_RC_SCRIPT='cd "$_RC_FOLDER" && bunx tsc --noEmit 2>&1 | grep -c "error TS" > "$_RC_TMP"'
|
|
364
|
+
_RC_FOLDER="$folder" _RC_TMP="$_tsc_tmp" _RC_CACHE="$_cache" _RC_SCRIPT="$_RC_SCRIPT" \
|
|
365
|
+
_RC_LOCK="tsc-$(printf '%s' "$folder" | tr '/' '_')" _RC_GV="$_gv" \
|
|
366
|
+
_RC_WAIT="${RECONCILE_TSC_WAIT:-900}" _RC_TIMEOUT="${RECONCILE_TSC_TIMEOUT:-600}" \
|
|
367
|
+
GOVERN_GATE_TIMEOUT=$(( ${RECONCILE_TSC_TIMEOUT:-600} + ${RECONCILE_TSC_WAIT:-900} + 60 )) \
|
|
368
|
+
bash "$_gr" "tsc-reconcile:$folder" -- bash -c '
|
|
369
|
+
. "$_RC_GV"
|
|
370
|
+
gate_lock "$_RC_LOCK" "$_RC_WAIT" || exit 3
|
|
371
|
+
trap "gate_release_all" EXIT INT TERM
|
|
372
|
+
[ -s "$_RC_CACHE" ] && exit 4
|
|
373
|
+
run_bounded "$_RC_TIMEOUT" bash -c "$_RC_SCRIPT" || true
|
|
374
|
+
exit 0' || _tsc_rc=$?
|
|
375
|
+
# `|| _tsc_rc=$?`, never a bare `$?` on the next line: this file runs
|
|
376
|
+
# under `set -e`, so an exit 3 or 4 from the inner shell would kill the
|
|
377
|
+
# canon before the code could be read. The `|| true` this replaces hid
|
|
378
|
+
# every one of those codes.
|
|
379
|
+
if [ "$_tsc_rc" = "4" ] && [ -s "$_cache" ]; then
|
|
332
380
|
current=$(cat "$_cache" | tr -d '[:space:]') # computed while we queued
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
run_bounded "${RECONCILE_TSC_TIMEOUT:-600}" \
|
|
336
|
-
bash -c "cd '$folder' && bunx tsc --noEmit 2>&1 | grep -c 'error TS' > '$_tsc_tmp'" \
|
|
337
|
-
2>/dev/null || true
|
|
381
|
+
rm -f "$_tsc_tmp"
|
|
382
|
+
elif [ "$_tsc_rc" != "3" ]; then
|
|
338
383
|
current=$(cat "$_tsc_tmp" 2>/dev/null | tr -d '[:space:]'); rm -f "$_tsc_tmp"
|
|
339
|
-
|
|
340
|
-
|
|
384
|
+
# AN UNRUN CHECK IS NEVER MEMOISED. This canon reports a DELTA and has
|
|
385
|
+
# always fallen open to 0 when it could not measure — that is its own
|
|
386
|
+
# call and is unchanged. What must not happen is that 0 being SHARED:
|
|
387
|
+
# this file is `$GOVERN_DIR/tsc-<folder>.<fp>`, the exact object
|
|
388
|
+
# tsc-cached.sh's `_cache_ok` accepts as a reusable PASS, so an unrun
|
|
389
|
+
# typecheck would become a green gate for every later reader on that
|
|
390
|
+
# tree, in every session and worktree.
|
|
391
|
+
#
|
|
392
|
+
# Reachable, and this is the line that made it likely: the slot queue
|
|
393
|
+
# is now the FIRST thing a miss does, so `gate-run … no slot after
|
|
394
|
+
# 1800s` on a saturated box lands here with an empty count file.
|
|
395
|
+
# Measured 2026-09-13 in a sandbox: GOVERN_QUEUE_WAIT=8 with the one
|
|
396
|
+
# slot held printed `OK — tsc delta=0` and wrote a `0` stamp.
|
|
397
|
+
case "$current" in
|
|
398
|
+
''|*[!0-9]*) current=0 ;; # could not measure: no stamp
|
|
399
|
+
*) printf '%s' "$current" > "$_cache" ;; # a real count is shareable
|
|
400
|
+
esac
|
|
341
401
|
fi
|
|
342
|
-
|
|
343
|
-
|
|
402
|
+
fi
|
|
403
|
+
if [ "${_tsc_rc:-0}" = "3" ]; then
|
|
404
|
+
rm -f "${_tsc_tmp:-}"
|
|
344
405
|
# Lock held for the full wait. Re-check the cache once — the holder may
|
|
345
406
|
# have finished and released between our last poll and now.
|
|
346
407
|
if [ -s "$_cache" ]; then
|
package/scripts/do-signal.sh
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# do-signal.sh [--print] --task-reap
|
|
14
14
|
# do-signal.sh [--print] --task-link <slug> [docs=a,b,c]
|
|
15
15
|
# do-signal.sh [--print] --task-heartbeat <slug> <body>
|
|
16
|
+
# do-signal.sh [--print] --outcome <slug> tags=a,b actor=<aid> success=<bool> [weight=<n>]
|
|
16
17
|
#
|
|
17
18
|
# --print: write JSON to stdout without POSTing (tests + dry runs).
|
|
18
19
|
# Extra key=value pairs are merged into the world:do-event payload.
|
|
@@ -117,6 +118,23 @@ _jsonesc() { printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\t/\\t
|
|
|
117
118
|
# session happened to be attested.
|
|
118
119
|
CC_TASKS_WORKSPACE="${CC_TASKS_WORKSPACE:-one}"
|
|
119
120
|
|
|
121
|
+
# CC_TASKS_AUTHOR — WHO the comment is from, when it is not simply "the
|
|
122
|
+
# workspace". tasks:comment records an author on the message and the inbox draws
|
|
123
|
+
# it (three directors on one task read as three speakers, not one anonymous
|
|
124
|
+
# run), but a field no caller supplies is a field production never sets: this
|
|
125
|
+
# script and the MCP door are the two writers, and both built their payload with
|
|
126
|
+
# tid/workspace/body only, so every /do close and every wave heartbeat landed
|
|
127
|
+
# `authorKind:"staff"`. The receiver honours the field only for an ATTESTED
|
|
128
|
+
# staff caller — which is exactly what GATEWAY_API_KEY auth above makes this
|
|
129
|
+
# script — and validates the slug shape; a caller that cannot be attested names
|
|
130
|
+
# nobody. UNSET is the default and emits the byte-identical payload this script
|
|
131
|
+
# has always sent, so nothing changes for a harness that does not set it.
|
|
132
|
+
CC_TASKS_AUTHOR="${CC_TASKS_AUTHOR:-}"
|
|
133
|
+
_author_json() { # emits `,"author":"<slug>"` or nothing
|
|
134
|
+
[ -n "$CC_TASKS_AUTHOR" ] || return 0
|
|
135
|
+
printf ',"author":"%s"' "$CC_TASKS_AUTHOR"
|
|
136
|
+
}
|
|
137
|
+
|
|
120
138
|
# signal_tasks_create — plan arm: files an open task tagged slug:<slug>, so the
|
|
121
139
|
# plan appears in tasks:everywhere the moment it's armed (C1/C2 interface contract).
|
|
122
140
|
signal_tasks_create() { # signal_tasks_create <slug> <title> [tags=a,b,c] [notes=...]
|
|
@@ -152,25 +170,16 @@ signal_tasks_create() { # signal_tasks_create <slug> <title> [tags=a,b,c] [note
|
|
|
152
170
|
# The cycle list is read from do-plan-json.sh, which is a parser, not a model.
|
|
153
171
|
# tasks:create dedupes on the slug tag, so re-running a plan is idempotent.
|
|
154
172
|
signal_tasks_plan() { # signal_tasks_plan <slug>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
#
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
fi
|
|
166
|
-
n=$((n+1))
|
|
167
|
-
# `.cycles` is an OBJECT KEYED BY CYCLE ID, not an array. The first draft
|
|
168
|
-
# used `.cycles[]? | .id` and got null for every id -- it still reported
|
|
169
|
-
# "filed 2 cycle task(s)", so the count looked right while every task was
|
|
170
|
-
# named `<slug>-` and tagged `cycle:`. A count is not a check.
|
|
171
|
-
done < <(printf '%s' "$plan" | jq -r '.cycles | to_entries[] | "\(.key)\t\(.value.title // .key)"' 2>/dev/null)
|
|
172
|
-
[ "$n" -gt 0 ] || { printf 'do-signal: %s parsed but yielded ZERO cycles — refusing to report success\n' "$slug" >&2; return 1; }
|
|
173
|
-
printf 'do-signal: filed %d cycle task(s) for %s\n' "$n" "$slug"
|
|
173
|
+
# ONE MINTER (2026-09-12). This used to file the cycles itself — flat, unchained,
|
|
174
|
+
# tagged `slug:<slug>-C1` in UPPER case. SLUG_RE is lowercase-only, so those rows
|
|
175
|
+
# never deduped on re-arm and `--task-cycle` (which lowercases) closed a DIFFERENT
|
|
176
|
+
# row: the armed rows sat open forever. do-board.sh mints the same rows chained
|
|
177
|
+
# (`tasks:subtask` + blockedBy from the batch DAG), tagged `slug:<slug>-c<n>` —
|
|
178
|
+
# the exact tag _tid_for_slug resolves — so the existing --task-cycle close moves
|
|
179
|
+
# THESE rows and the gate unblocks the next batch by itself.
|
|
180
|
+
local slug="$1"
|
|
181
|
+
if $PRINT; then bash "$_ROOT/.claude/scripts/do-board.sh" "$slug" --dry-run; return $?; fi
|
|
182
|
+
bash "$_ROOT/.claude/scripts/do-board.sh" "$slug"
|
|
174
183
|
}
|
|
175
184
|
|
|
176
185
|
# signal_tasks_claim — cycle open: leases the plan's substrate task so a fleet
|
|
@@ -224,7 +233,15 @@ signal_tasks_claim() { # signal_tasks_claim <slug>
|
|
|
224
233
|
# task:<traceId>, so "task:<slug>" never matched — claim silently 404'd for every /do
|
|
225
234
|
# run until this. The server resolves slug → tid (resolvers/tasks.ts resolveTaskBySlug).
|
|
226
235
|
local slug="$1" tid
|
|
227
|
-
|
|
236
|
+
# THE LEASE MOVES TO THE CYCLE. Once do-board.sh hangs cycle rows off the plan
|
|
237
|
+
# row (`containment`), blockersResolved refuses `picked` on the parent while any
|
|
238
|
+
# child is open — so a claim on the plan row reads `blocked` for the whole run.
|
|
239
|
+
# Claim the FIRST OPEN CYCLE's row instead: the thing actually in flight, gated by
|
|
240
|
+
# its own batch predecessors, which is the DAG doing its job. Falls back to the
|
|
241
|
+
# plan row for an unmirrored plan.
|
|
242
|
+
tid=$(DO_PLAN_TODO="${DO_PLAN_TODO:-}" bash "$_ROOT/.claude/scripts/do-plan-json.sh" "$slug" 2>/dev/null \
|
|
243
|
+
| jq -r '.cycleRows[.openCycles[0]] // empty' 2>/dev/null) || true
|
|
244
|
+
[ -n "$tid" ] || tid=$(_tid_for_slug "$slug") || true
|
|
228
245
|
local payload
|
|
229
246
|
if [ -n "$tid" ]; then
|
|
230
247
|
payload="{\"data\":{\"tid\":\"${tid}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\"}}"
|
|
@@ -248,6 +265,65 @@ signal_tasks_reap() { # signal_tasks_reap
|
|
|
248
265
|
# signal_tasks_link — cycle close: records the handoff-completion back on the
|
|
249
266
|
# origin task (provenance line + task-status:done), same shape lifecycle.ts's
|
|
250
267
|
# tasks_link MCP tool posts.
|
|
268
|
+
# signal_outcome — /close's loop-closer, and the reason it is world:outcome.
|
|
269
|
+
#
|
|
270
|
+
# THE NAMING GAP, measured. `/api/receivers/signals` ranks every emitted signal
|
|
271
|
+
# name as declared | candidate | unhomed against packages/sdk/src/receivers.ts,
|
|
272
|
+
# because 35 distinct event names were emitted in 90 days and exactly ONE
|
|
273
|
+
# (`world:in`) was a declared receiver. /close was a top contributor: measured
|
|
274
|
+
# 2026-09-09, `do:close` 0, `loop:feedback` 0, `world:do-event` 0 declarations —
|
|
275
|
+
# three of the four names it emitted could not be answered by anything, and it
|
|
276
|
+
# reported success anyway.
|
|
277
|
+
#
|
|
278
|
+
# `world:outcome` is not a new contract written for this. It already says exactly
|
|
279
|
+
# what /close does: "whoever consumed a route reports success/failure, moving the
|
|
280
|
+
# same tag->receiver paths the router reads - success marks, failure warns."
|
|
281
|
+
# So the fix converges four names to one and designs nothing.
|
|
282
|
+
#
|
|
283
|
+
# THE VERB IS READ BACK, NEVER ASSUMED. The receiver answers
|
|
284
|
+
# {ok, outcome:"marked"|"warned", ...}; a caller that prints "marked" because it
|
|
285
|
+
# sent success:true is reporting its own intent, which is the class of defect the
|
|
286
|
+
# whole four-outcome grammar exists to stop.
|
|
287
|
+
#
|
|
288
|
+
# TAGS ARE BARE WORDS. `marketing`, never `lifecycle:marketing` - a namespaced
|
|
289
|
+
# tag matches zero subscribers. `slug:`/`cycle:` are provenance, not stakes, and
|
|
290
|
+
# are appended separately so they can never be mistaken for a routable tag.
|
|
291
|
+
signal_outcome() { # signal_outcome <slug> tags=a,b actor=<aid> success=<bool> weight=<n> [workspace=<ws>]
|
|
292
|
+
local slug="$1"; shift
|
|
293
|
+
local tags="" actor="" success="true" weight="" ws="${CC_TASKS_WORKSPACE}"
|
|
294
|
+
local kv
|
|
295
|
+
for kv in "$@"; do
|
|
296
|
+
case "$kv" in
|
|
297
|
+
tags=*) tags="${kv#*=}" ;;
|
|
298
|
+
actor=*) actor="${kv#*=}" ;;
|
|
299
|
+
success=*) success="${kv#*=}" ;;
|
|
300
|
+
weight=*) weight="${kv#*=}" ;;
|
|
301
|
+
workspace=*) ws="${kv#*=}" ;;
|
|
302
|
+
esac
|
|
303
|
+
done
|
|
304
|
+
[ -n "$slug" ] || { echo "signal_outcome: slug required" >&2; return 2; }
|
|
305
|
+
[ -n "$actor" ] || { echo "signal_outcome: actor= required (world:outcome moves that actor's paths)" >&2; return 2; }
|
|
306
|
+
case "$success" in true|false) ;; *) echo "signal_outcome: success must be true|false" >&2; return 2 ;; esac
|
|
307
|
+
|
|
308
|
+
# A namespaced word here would route to nobody. Refuse it loudly rather than
|
|
309
|
+
# emit a signal that matches zero subscribers and still exits 0.
|
|
310
|
+
local t
|
|
311
|
+
for t in ${tags//,/ }; do
|
|
312
|
+
case "$t" in
|
|
313
|
+
slug:*|cycle:*) ;;
|
|
314
|
+
*:*) echo "signal_outcome: '$t' is namespaced - stake tags are BARE words (see subscribe-tags-parity.test.ts)" >&2; return 2 ;;
|
|
315
|
+
esac
|
|
316
|
+
done
|
|
317
|
+
|
|
318
|
+
tags="${tags:+${tags},}slug:${slug}"
|
|
319
|
+
local tags_json
|
|
320
|
+
tags_json=$(printf '%s' "$tags" | awk -F',' '{for(i=1;i<=NF;i++){if($i!=""){printf "%s\"%s\"", (n++?",":""), $i}}}')
|
|
321
|
+
|
|
322
|
+
local payload="{\"data\":{\"tags\":[${tags_json}],\"actor\":\"$(_jsonesc "$actor")\",\"success\":${success}${weight:+,\"weight\":${weight}},\"slug\":\"$(_jsonesc "$slug")\",\"workspace\":\"$(_jsonesc "$ws")\"}}"
|
|
323
|
+
if $PRINT; then printf '%s\n' "$payload"; return 0; fi
|
|
324
|
+
_post_ask "world:outcome" "$payload"
|
|
325
|
+
}
|
|
326
|
+
|
|
251
327
|
signal_tasks_link() { # signal_tasks_link <slug> [docs=a,b,c]
|
|
252
328
|
# Slug-addressed like claim — the server resolves slug → the origin tid create wrote.
|
|
253
329
|
local slug="$1" docs=""
|
|
@@ -269,7 +345,7 @@ signal_tasks_link() { # signal_tasks_link <slug> [docs=a,b,c]
|
|
|
269
345
|
if _post_ask "tasks:link" "$payload"; then return 0; fi
|
|
270
346
|
[ -n "$tid" ] || return 1
|
|
271
347
|
_post_ask "tasks:status" "{\"data\":{\"tid\":\"${tid}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"status\":\"done\"}}" >/dev/null || return 1
|
|
272
|
-
_post_ask "tasks:comment" "{\"data\":{\"tid\":\"${tid}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"body\":\"/do close — ${slug}${docs:+ (docs: ${docs})}\"}}"
|
|
348
|
+
_post_ask "tasks:comment" "{\"data\":{\"tid\":\"${tid}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"body\":\"/do close — ${slug}${docs:+ (docs: ${docs})}\"$(_author_json)}}"
|
|
273
349
|
}
|
|
274
350
|
|
|
275
351
|
# signal_tasks_heartbeat — batch/wave close: posts tasks:comment so the lease
|
|
@@ -285,9 +361,9 @@ signal_tasks_heartbeat() { # signal_tasks_heartbeat <slug> <body>
|
|
|
285
361
|
# accompanied, when the tid is known — otherwise the broken resolver runs anyway.
|
|
286
362
|
local payload
|
|
287
363
|
if [ -n "$tid" ]; then
|
|
288
|
-
payload="{\"data\":{\"tid\":\"${tid}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"body\":\"${body_esc}\"}}"
|
|
364
|
+
payload="{\"data\":{\"tid\":\"${tid}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"body\":\"${body_esc}\"$(_author_json)}}"
|
|
289
365
|
else
|
|
290
|
-
payload="{\"data\":{\"slug\":\"${slug}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"body\":\"${body_esc}\"}}"
|
|
366
|
+
payload="{\"data\":{\"slug\":\"${slug}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"body\":\"${body_esc}\"$(_author_json)}}"
|
|
291
367
|
fi
|
|
292
368
|
if $PRINT; then printf '%s\n' "$payload"; return 0; fi
|
|
293
369
|
_post_ask "tasks:comment" "$payload"
|
|
@@ -315,6 +391,7 @@ while [ "$#" -gt 0 ]; do
|
|
|
315
391
|
--task-reap) shift; signal_tasks_reap "$@"; exit $? ;;
|
|
316
392
|
--task-link) shift; signal_tasks_link "$@"; exit $? ;;
|
|
317
393
|
--task-heartbeat) shift; signal_tasks_heartbeat "$@"; exit $? ;;
|
|
394
|
+
--outcome) shift; signal_outcome "$@"; exit $? ;;
|
|
318
395
|
*) break ;;
|
|
319
396
|
esac
|
|
320
397
|
done
|
|
@@ -322,6 +399,7 @@ done
|
|
|
322
399
|
if [ "$#" -lt 4 ]; then
|
|
323
400
|
echo "usage: do-signal.sh [--print] <type> <slug> <tier> <cycle> [key=value ...]" >&2
|
|
324
401
|
echo " do-signal.sh [--print] --task-create|--task-claim|--task-link|--task-heartbeat <slug> ..." >&2
|
|
402
|
+
echo " do-signal.sh [--print] --outcome <slug> tags=a,b actor=<aid> success=<bool> [weight=<n>]" >&2
|
|
325
403
|
exit 1
|
|
326
404
|
fi
|
|
327
405
|
|
package/scripts/do-smoke.sh
CHANGED
|
@@ -4,6 +4,23 @@
|
|
|
4
4
|
# the seven-canon predicate (do-reconcile.sh) + navigation reachable/orphan fixtures.
|
|
5
5
|
# Exits 0 only if ALL pass.
|
|
6
6
|
set -uo pipefail
|
|
7
|
+
|
|
8
|
+
# _GATE — route a heavy compute through the machine governor. A gate_lock only
|
|
9
|
+
# dedupes IDENTICAL work; a SLOT is what bounds N worktrees each running one of
|
|
10
|
+
# these at once (measured 2026-09-07: three concurrent 2.5GB typecheckers, every
|
|
11
|
+
# lock uncontended, load 171). Empty when already inside a gate, so a nested call
|
|
12
|
+
# inherits the outer slot rather than taking a second one.
|
|
13
|
+
#
|
|
14
|
+
# Resolves gate-run.sh from its OWN directory, deliberately: an earlier version
|
|
15
|
+
# keyed off $ROOT and got inserted above the line that sets it, so _GATE was
|
|
16
|
+
# silently empty and every call ran ungoverned -- a fail-OPEN, which is the exact
|
|
17
|
+
# defect this preamble exists to close.
|
|
18
|
+
_GATE=()
|
|
19
|
+
if [ "${GOVERN_IN_GATE:-0}" != "1" ]; then
|
|
20
|
+
_GR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/gate-run.sh"
|
|
21
|
+
[ -f "$_GR" ] && _GATE=( bash "$_GR" "compute:$(basename "${BASH_SOURCE[0]}")" -- )
|
|
22
|
+
fi
|
|
23
|
+
|
|
7
24
|
cd "$(dirname "${BASH_SOURCE[0]}")/../.." || exit 2
|
|
8
25
|
S=.claude/scripts
|
|
9
26
|
fail=0
|
|
@@ -61,7 +78,7 @@ echo "== C2 do-reconcile — types canon =="
|
|
|
61
78
|
# Use a temp file to capture grep output — avoids pipefail swallowing the value when tsc
|
|
62
79
|
# exits non-zero (which it does whenever there are any type errors).
|
|
63
80
|
_tsc_tmp=$(mktemp)
|
|
64
|
-
( cd one.ie/web 2>/dev/null && bunx tsc --noEmit 2>&1 | grep -c 'error TS' > "$_tsc_tmp" ) 2>/dev/null || true
|
|
81
|
+
( cd one.ie/web 2>/dev/null && "${_GATE[@]}" bunx tsc --noEmit 2>&1 | grep -c 'error TS' > "$_tsc_tmp" ) 2>/dev/null || true
|
|
65
82
|
_tsc_now=$(cat "$_tsc_tmp" 2>/dev/null | tr -d '[:space:]'); rm -f "$_tsc_tmp"
|
|
66
83
|
echo "{\"tscErrors\": ${_tsc_now:-0}}" > .w0-baseline.json
|
|
67
84
|
"$S/do-reconcile.sh" types >/dev/null 2>&1 && ok "types delta=0 → exit 0" || no "types should pass when no delta"
|
package/scripts/do-w4-gates.sh
CHANGED
|
@@ -49,6 +49,16 @@
|
|
|
49
49
|
# because a skip is unrun, not pass; never set it for real.
|
|
50
50
|
set -euo pipefail
|
|
51
51
|
|
|
52
|
+
# _GATE — see governor-doors-check.sh "compute sites". A gate_lock dedupes
|
|
53
|
+
# identical work; only a SLOT bounds N worktrees running N computes at once.
|
|
54
|
+
# Empty inside an existing gate so the outer slot is inherited, never doubled.
|
|
55
|
+
_GATE=()
|
|
56
|
+
if [ "${GOVERN_IN_GATE:-0}" != "1" ]; then
|
|
57
|
+
_GR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/gate-run.sh"
|
|
58
|
+
[ -f "$_GR" ] && _GATE=( bash "$_GR" "compute:$(basename "${BASH_SOURCE[0]}")" -- )
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
|
|
52
62
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
53
63
|
S="$ROOT/.claude/scripts"
|
|
54
64
|
RESULTS_FILE=""
|
|
@@ -265,7 +275,7 @@ _gates() {
|
|
|
265
275
|
if [ -z "$base" ]; then
|
|
266
276
|
_add tsc-delta unrun ".w0-baseline.json has no tscErrors"
|
|
267
277
|
else
|
|
268
|
-
set +e; now="$(cd "$ROOT" && bunx tsc --noEmit 2>&1 | grep -c 'error TS')"; set -e
|
|
278
|
+
set +e; now="$(cd "$ROOT" && "${_GATE[@]}" bunx tsc --noEmit 2>&1 | grep -c 'error TS')"; set -e
|
|
269
279
|
if [ "$now" -le "$base" ]; then _add tsc-delta pass "baseline=$base now=$now"
|
|
270
280
|
else _add tsc-delta fail "baseline=$base now=$now — new type errors"; fi
|
|
271
281
|
fi
|