@oneie/claude 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/abm-strategist.md +89 -0
- package/agents/ads-meta.md +97 -0
- package/agents/analyst.md +173 -0
- package/agents/architect.md +280 -0
- package/agents/brand-guardian.md +88 -0
- package/agents/brand-strategist.md +92 -0
- package/agents/campaign-content.md +90 -0
- package/agents/campaign-email.md +88 -0
- package/agents/campaign-sms.md +87 -0
- package/agents/campaign-social.md +87 -0
- package/agents/cco.md +99 -0
- package/agents/ceo.md +106 -0
- package/agents/chairman.md +120 -0
- package/agents/cmo.md +101 -0
- package/agents/community-greeter.md +89 -0
- package/agents/community-moderator.md +92 -0
- package/agents/compliance.md +92 -0
- package/agents/copywriter.md +100 -0
- package/agents/creative-strategist.md +95 -0
- package/agents/cro.md +98 -0
- package/agents/cto.md +267 -0
- package/agents/customer-interviewer.md +93 -0
- package/agents/customer-researcher.md +89 -0
- package/agents/customer-success-manager.md +90 -0
- package/agents/customer-trainer.md +90 -0
- package/agents/cxo.md +100 -0
- package/agents/demand-creator.md +89 -0
- package/agents/demo-mover.md +83 -0
- package/agents/demo-specialist.md +89 -0
- package/agents/demo-thai-family-law.md +87 -0
- package/agents/designer.md +112 -0
- package/agents/discovery-caller.md +98 -0
- package/agents/doctor.md +139 -0
- package/agents/educate-coach.md +100 -0
- package/agents/elevate-tutor.md +83 -0
- package/agents/email-lifecycle-marketer.md +91 -0
- package/agents/engage-specialist.md +99 -0
- package/agents/events-coordinator.md +90 -0
- package/agents/foundation-builder.md +94 -0
- package/agents/funnel-architect.md +89 -0
- package/agents/gift-creator.md +100 -0
- package/agents/google-ads.md +103 -0
- package/agents/guide.md +292 -0
- package/agents/helpdesk-dispatcher.md +88 -0
- package/agents/hook-specialist.md +91 -0
- package/agents/identify-optimizer.md +101 -0
- package/agents/implementer.md +305 -0
- package/agents/incident-commander.md +120 -0
- package/agents/insights-lead.md +93 -0
- package/agents/journey-runner.md +113 -0
- package/agents/linkedin-ads.md +123 -0
- package/agents/live-sales-chat.md +90 -0
- package/agents/market-researcher.md +88 -0
- package/agents/media-buyer.md +96 -0
- package/agents/movers-customer-researcher.md +95 -0
- package/agents/movers-foundation-builder.md +96 -0
- package/agents/movers-market-researcher.md +97 -0
- package/agents/movers-pricing-strategist.md +171 -0
- package/agents/nurture-architect.md +99 -0
- package/agents/offer-architect.md +92 -0
- package/agents/onboarder.md +222 -0
- package/agents/onboarding-specialist.md +92 -0
- package/agents/operations-dashboard.md +98 -0
- package/agents/perf-engineer.md +348 -0
- package/agents/playbook-writer.md +71 -0
- package/agents/plg-strategist.md +91 -0
- package/agents/positioning-architect.md +88 -0
- package/agents/press-officer.md +89 -0
- package/agents/pricing-strategist.md +94 -0
- package/agents/privacy-officer.md +92 -0
- package/agents/referral-manager.md +91 -0
- package/agents/refine-analyst.md +102 -0
- package/agents/release-manager.md +261 -0
- package/agents/renewals-upsell-rep.md +90 -0
- package/agents/review-engineer.md +327 -0
- package/agents/rewards-steward.md +82 -0
- package/agents/sales-call-coach.md +94 -0
- package/agents/sales-closer.md +98 -0
- package/agents/security-auditor.md +313 -0
- package/agents/sell-closer.md +101 -0
- package/agents/share-amplifier.md +99 -0
- package/agents/social-media-manager.md +92 -0
- package/agents/storyteller.md +250 -0
- package/agents/strategist.md +83 -0
- package/agents/strategy-aligner.md +94 -0
- package/agents/support-agent.md +90 -0
- package/agents/tagger.md +245 -0
- package/agents/tech-writer.md +216 -0
- package/agents/test-engineer.md +243 -0
- package/agents/tiktok-ads.md +105 -0
- package/agents/tracking-engineer.md +92 -0
- package/agents/upsell-strategist.md +101 -0
- package/agents/voice.md +85 -0
- package/agents/w4-tools.md +153 -0
- package/agents/w4-verify.md +20 -138
- package/agents/workflow-optimiser.md +84 -0
- package/commands/close.md +814 -160
- package/commands/create.md +2 -2
- package/commands/deploy.md +554 -175
- package/commands/do.md +119 -109
- package/commands/fast.md +67 -0
- package/commands/improve.md +2 -2
- package/commands/one.md +418 -0
- package/commands/see.md +1 -1
- package/commands/sync.md +1 -1
- package/commands/tasks.md +222 -0
- package/commands/vespio.md +110 -0
- package/commands/vespio.remote.md +227 -0
- package/hooks/hooks.json +25 -79
- package/hooks/lib/governor-escape-match.sh +88 -0
- package/hooks/lib/hook.sh +4 -13
- package/hooks/lib/signal.sh +9 -2
- package/hooks/scripts/branch-pin.sh +6 -0
- package/hooks/scripts/config-protect.sh +6 -0
- package/hooks/scripts/do-outcome-gate.sh +19 -2
- package/hooks/scripts/git-add-guard.sh +43 -2
- package/hooks/scripts/governor-escape.sh +96 -0
- package/hooks/scripts/load-guard.sh +127 -0
- package/hooks/scripts/post-edit-check.sh +36 -29
- package/hooks/scripts/session-start.sh +34 -80
- package/hooks/scripts/task-complete-verify.sh +46 -40
- package/package.json +1 -1
- package/rules/documentation.md +9 -0
- package/scripts/ad-copy-lint.sh +656 -0
- package/scripts/agentverse-audit.sh +256 -0
- package/scripts/asi-walk.sh +435 -0
- package/scripts/astro-build-cached.sh +143 -0
- package/scripts/blocks-manifest-cached.sh +100 -0
- package/scripts/blocks-manifest.mjs +497 -0
- package/scripts/blocks-ratchet.sh +116 -0
- package/scripts/blocks-render-probe.mjs +529 -0
- package/scripts/blocks-usage.mjs +334 -0
- package/scripts/chat-context-check.sh +89 -0
- package/scripts/close-metrics.sh +558 -0
- package/scripts/close-owner.sh +268 -0
- package/scripts/db-sync-lock-check.sh +116 -0
- package/scripts/deploy-dev.sh +35 -0
- package/scripts/deploy-emit.sh +298 -0
- package/scripts/deploy-gate-check.sh +128 -0
- package/scripts/deploy-ready.sh +78 -0
- package/scripts/deploy-record.sh +605 -0
- package/scripts/deploy.sh +1273 -0
- package/scripts/do-auto.sh +598 -48
- package/scripts/do-brief.sh +113 -0
- package/scripts/do-close.sh +1137 -0
- package/scripts/do-commit.sh +75 -0
- package/scripts/do-consumer-sweep.sh +18 -1
- package/scripts/do-cycle-shape-check.sh +160 -0
- package/scripts/do-decide.sh +476 -0
- package/scripts/do-derive-check.sh +436 -0
- package/scripts/do-fleet.sh +106 -28
- package/scripts/do-folder.sh +10 -1
- package/scripts/do-next.sh +106 -0
- package/scripts/do-orchestrate.sh +17 -5
- package/scripts/do-plan-json.mjs +201 -0
- package/scripts/do-plan-json.sh +8 -0
- package/scripts/do-preflight.sh +117 -0
- package/scripts/do-project.sh +157 -0
- package/scripts/do-prove-selftest.sh +108 -0
- package/scripts/do-prove.sh +295 -23
- package/scripts/do-rank.py +31 -1
- package/scripts/do-recon-cache.sh +7 -1
- package/scripts/do-recon-pack.sh +196 -0
- package/scripts/do-reconcile.sh +121 -4
- package/scripts/do-signal.sh +280 -23
- package/scripts/do-smoke.sh +18 -1
- package/scripts/do-test-gate.sh +80 -0
- package/scripts/do-tick.sh +102 -0
- package/scripts/do-tier.sh +6 -0
- package/scripts/do-triage.sh +182 -0
- package/scripts/do-ui-gate.sh +1 -1
- package/scripts/do-w4-gates.sh +451 -0
- package/scripts/do-walk.sh +12 -1
- package/scripts/env-sync.sh +173 -0
- package/scripts/factory-brief-check.sh +330 -0
- package/scripts/factory-check.sh +68 -14
- 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 +118 -3
- package/scripts/factory-review-check.mjs +61 -0
- package/scripts/factory-ship.sh +61 -0
- package/scripts/factory-tasks-check.sh +18 -1
- package/scripts/factory-turn.sh +326 -0
- package/scripts/factory-walk.sh +396 -0
- package/scripts/factory-width.sh +57 -0
- package/scripts/fade-toxic.sh +4 -3
- package/scripts/fixtures/factory-brief-real.md +44 -0
- package/scripts/fixtures/triage-dupe.md +5 -0
- package/scripts/fleet-manifest.mjs +108 -0
- package/scripts/fleet-status.sh +110 -0
- package/scripts/full-suite-paths-check.sh +144 -0
- package/scripts/gate-reaper-check.sh +98 -0
- package/scripts/gate-reaper.sh +125 -0
- package/scripts/gate-run.sh +73 -0
- package/scripts/gc-content-check.sh +140 -0
- package/scripts/gen-dev-config.py +20 -0
- package/scripts/govern-bound-check.sh +60 -0
- package/scripts/govern-claims-check.sh +233 -0
- package/scripts/govern-mem-check.sh +290 -0
- package/scripts/governor-doors-check.sh +362 -0
- package/scripts/governor-escape-check.sh +171 -0
- package/scripts/health.sh +413 -0
- package/scripts/id-inventory.mjs +418 -0
- package/scripts/land.sh +551 -0
- package/scripts/lib/gc-finished.sh +77 -0
- package/scripts/lib/govern.sh +361 -0
- package/scripts/lib/govern.ts +756 -0
- package/scripts/lighthouse-run.sh +187 -0
- package/scripts/livekit-live-check.sh +61 -0
- package/scripts/livekit-ratchet.sh +46 -0
- package/scripts/load-guard-check.sh +49 -0
- package/scripts/machine-check.sh +102 -0
- package/scripts/machine-watch.sh +177 -0
- package/scripts/one-agents.mjs +415 -0
- package/scripts/one-resume.sh +78 -0
- package/scripts/orphan-baseline.json +182 -0
- package/scripts/orphan-modules.mjs +179 -0
- package/scripts/pr-body.sh +335 -0
- package/scripts/preview-fd-check.sh +289 -0
- package/scripts/promise-manifest.mjs +24 -1
- package/scripts/release.sh +322 -0
- package/scripts/roles-check.sh +946 -0
- package/scripts/sdk-build-cached.sh +64 -0
- package/scripts/signal-watch.sh +241 -0
- package/scripts/skills-publish.sh +94 -0
- package/scripts/speed-cache-check.sh +173 -0
- package/scripts/speed-check.mjs +907 -0
- package/scripts/speed-parity-check.sh +648 -0
- package/scripts/speed-waterfall-check.sh +355 -0
- package/scripts/substrate-env-parity.mjs +156 -0
- package/scripts/tasks-claim-race.mjs +108 -0
- package/scripts/tasks-loop.sh +185 -0
- package/scripts/test-cached.sh +255 -0
- package/scripts/test-full.sh +87 -0
- package/scripts/test-honesty.mjs +137 -0
- package/scripts/test-lanes.sh +166 -0
- package/scripts/test-speed.sh +94 -0
- package/scripts/triage-shape-check.sh +149 -0
- package/scripts/tsc-cached.sh +179 -0
- package/scripts/typedb-flake-check.sh +197 -0
- package/scripts/urls-lint.sh +15 -0
- package/scripts/verify-fast.sh +445 -0
- package/scripts/vespio-sync.sh +149 -0
- package/scripts/wf-check.mjs +104 -1
- package/scripts/worktree-preview.sh +879 -0
- package/scripts/worktree-up.sh +208 -0
- package/skills/livekit-agents/SKILL.md +285 -0
- package/skills/livekit-agents/references/freshness-rules.md +168 -0
- package/skills/shadcn/SKILL.md +1 -1
- package/skills/signal/SKILL.md +0 -1
- package/skills/voice/SKILL.md +94 -6
- package/skills/voice/corpus-check.sh +87 -0
- package/hooks/scripts/compact-hint.sh +0 -35
- package/hooks/scripts/gate-guard.sh +0 -83
- package/hooks/scripts/read-tracker.sh +0 -26
- package/hooks/scripts/session-end-verify.sh +0 -51
- package/hooks/scripts/stop-reflect.sh +0 -140
- package/hooks/scripts/sync-priority-todo.sh +0 -57
- package/hooks/scripts/sync-todo-docs.sh +0 -46
- package/hooks/scripts/tool-signal.sh +0 -48
- package/scripts/do-tasks-bridge.py +0 -366
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# SYNC-TODO-DOCS — Keep TODO.md + todo.json in sync with TODO-*.md edits.
|
|
3
|
-
#
|
|
4
|
-
# Fires after Write/Edit on any docs/TODO*.md file.
|
|
5
|
-
# Best-effort, non-blocking: pings POST /api/tasks/sync on the dev server
|
|
6
|
-
# if it's running (full pipeline: KV → TypeDB → regenerate TODO.md + todo.json).
|
|
7
|
-
# If the dev server isn't up, we silently skip — the sandwich's deterministic
|
|
8
|
-
# PRE check fails closed without breaking the edit flow.
|
|
9
|
-
#
|
|
10
|
-
# The dev server does all the real work; this hook is just a trigger.
|
|
11
|
-
# To run the full sync manually: `/sync` (or `POST /api/tasks/sync`).
|
|
12
|
-
# Emits hook:todo-sync:{ok,dissolved} signal per Rule 1 (see .claude/skills/signal.md).
|
|
13
|
-
|
|
14
|
-
# shellcheck source=lib/signal.sh
|
|
15
|
-
source "$CLAUDE_PROJECT_DIR/.claude/hooks/lib/signal.sh"
|
|
16
|
-
|
|
17
|
-
FILE=$(echo "$1" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null)
|
|
18
|
-
|
|
19
|
-
# Only fire for TODO markdown files under docs/
|
|
20
|
-
case "$FILE" in
|
|
21
|
-
*/docs/TODO*.md) ;;
|
|
22
|
-
*) exit 0 ;;
|
|
23
|
-
esac
|
|
24
|
-
|
|
25
|
-
# Don't recurse: if the edit IS TODO.md itself, it was likely regenerated. Skip.
|
|
26
|
-
case "$FILE" in
|
|
27
|
-
*/text/TODO-plan.md) exit 0 ;;
|
|
28
|
-
esac
|
|
29
|
-
|
|
30
|
-
# Best-effort POST with a 2s cap so edits stay snappy.
|
|
31
|
-
# Silently swallow all output — this is a background sync, not a gate.
|
|
32
|
-
DEV_URL="${ONE_DEV_URL:-http://localhost:4321}"
|
|
33
|
-
RESP=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
34
|
-
--max-time 2 \
|
|
35
|
-
-X POST "${DEV_URL}/api/tasks/sync" 2>/dev/null)
|
|
36
|
-
|
|
37
|
-
BASE=$(basename "$FILE")
|
|
38
|
-
if [ "$RESP" = "200" ]; then
|
|
39
|
-
echo "todo-sync: regenerated TODO.md + todo.json from $BASE" >&2
|
|
40
|
-
emit_signal "hook:todo-sync:ok" 1 "file=$BASE"
|
|
41
|
-
else
|
|
42
|
-
# Server down/timeout/error → dissolved (missing unit semantics, mild warn)
|
|
43
|
-
emit_signal "hook:todo-sync:dissolved" -0.5 "file=$BASE http=$RESP"
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
exit 0
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Tool-call signal emitter — PostToolUse hook.
|
|
3
|
-
# Emits tool:<ToolName>:{ok,fail} with +1/-1 weight. Receiver + weight
|
|
4
|
-
# semantics: .claude/skills/signal.md.
|
|
5
|
-
|
|
6
|
-
# shellcheck source=lib/signal.sh
|
|
7
|
-
source "$CLAUDE_PROJECT_DIR/.claude/hooks/lib/signal.sh"
|
|
8
|
-
|
|
9
|
-
PAYLOAD="$1"
|
|
10
|
-
[[ -z "$PAYLOAD" ]] && exit 0
|
|
11
|
-
|
|
12
|
-
TOOL=$(echo "$PAYLOAD" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
13
|
-
[[ -z "$TOOL" ]] && exit 0
|
|
14
|
-
|
|
15
|
-
# Receiver must match ^[a-zA-Z0-9:_-]+$ (validateUid in signal.ts).
|
|
16
|
-
# MCP tools (mcp__server__tool) contain underscores — still safe.
|
|
17
|
-
SAFE_TOOL="${TOOL//[^a-zA-Z0-9_-]/}"
|
|
18
|
-
[[ -z "$SAFE_TOOL" ]] && exit 0
|
|
19
|
-
|
|
20
|
-
# Outcome: error field → fail; Bash non-zero or interrupted → fail; else ok
|
|
21
|
-
ERROR=$(echo "$PAYLOAD" | jq -r '.tool_response.error // empty' 2>/dev/null)
|
|
22
|
-
OUTCOME="ok"
|
|
23
|
-
WEIGHT=1
|
|
24
|
-
|
|
25
|
-
if [[ -n "$ERROR" ]]; then
|
|
26
|
-
OUTCOME="fail"; WEIGHT=-1
|
|
27
|
-
elif [[ "$TOOL" == "Bash" ]]; then
|
|
28
|
-
EXIT_CODE=$(echo "$PAYLOAD" | jq -r '.tool_response.exit_code // .tool_response.returncode // 0' 2>/dev/null)
|
|
29
|
-
INTERRUPTED=$(echo "$PAYLOAD" | jq -r '.tool_response.interrupted // false' 2>/dev/null)
|
|
30
|
-
[[ "$EXIT_CODE" != "0" || "$INTERRUPTED" == "true" ]] && { OUTCOME="fail"; WEIGHT=-1; }
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
# Human-greppable crumb per tool family. Not structured — don't serialize objects.
|
|
34
|
-
DATA=""
|
|
35
|
-
case "$TOOL" in
|
|
36
|
-
Write|Edit|MultiEdit|Read|NotebookEdit)
|
|
37
|
-
FILE=$(echo "$PAYLOAD" | jq -r '.tool_input.file_path // .tool_input.notebook_path // .tool_input.path // empty' 2>/dev/null)
|
|
38
|
-
[[ -n "$FILE" ]] && DATA="file=$(basename "$FILE")" ;;
|
|
39
|
-
Bash)
|
|
40
|
-
CMD=$(echo "$PAYLOAD" | jq -r '.tool_input.command // empty' 2>/dev/null | head -c 60 | tr '\n' ' ')
|
|
41
|
-
[[ -n "$CMD" ]] && DATA="cmd=$CMD" ;;
|
|
42
|
-
Grep|Glob)
|
|
43
|
-
PATTERN=$(echo "$PAYLOAD" | jq -r '.tool_input.pattern // empty' 2>/dev/null | head -c 40)
|
|
44
|
-
[[ -n "$PATTERN" ]] && DATA="pattern=$PATTERN" ;;
|
|
45
|
-
esac
|
|
46
|
-
|
|
47
|
-
emit_signal "tool:${SAFE_TOOL}:${OUTCOME}" "$WEIGHT" "$DATA"
|
|
48
|
-
exit 0
|
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""do-tasks-bridge — make /do an actor in the world's task queue (tasks-integration Wire B).
|
|
3
|
-
|
|
4
|
-
The /do build queue (do-rank.py over text/*-todo.md + task-paths.json) and the world's
|
|
5
|
-
task queue (tasks:announce / tasks:mine, subscriptions in D1) are two views of one thing.
|
|
6
|
-
This bridge reconciles them in three directions:
|
|
7
|
-
|
|
8
|
-
C1 EMIT do-rank's OPEN tasks → tasks:announce payloads (humans + agents see the
|
|
9
|
-
build queue; a saved lens / subscription then routes them — tasks-push). ✓
|
|
10
|
-
C2 INGEST world tasks tagged with build-words → do-rank's pick (anyone can post
|
|
11
|
-
build work and /do will consider it). ✓
|
|
12
|
-
C3 RECONCILE the local weight tier (task-paths.json path.strength) and the substrate
|
|
13
|
-
tier (path-context.context-strength) become ONE number. ✓
|
|
14
|
-
|
|
15
|
-
`--selftest` asserts ALL THREE — the plan-level kill-switch in text/tasks-bridge-todo.md.
|
|
16
|
-
|
|
17
|
-
Design: text/tasks-integration.md § Wire B · text/loop-plan.md (task-paths.json ⇄ path.strength).
|
|
18
|
-
"""
|
|
19
|
-
import json
|
|
20
|
-
import os
|
|
21
|
-
import subprocess
|
|
22
|
-
import sys
|
|
23
|
-
import urllib.error
|
|
24
|
-
import urllib.request
|
|
25
|
-
from pathlib import Path
|
|
26
|
-
|
|
27
|
-
HERE = Path(__file__).resolve().parent
|
|
28
|
-
DO_RANK = HERE / "do-rank.py"
|
|
29
|
-
ROOT = HERE.parents[1]
|
|
30
|
-
|
|
31
|
-
# Tags that route world tasks into /do's pick. Must intersect a world task's tags.
|
|
32
|
-
BUILD_WORDS = frozenset({
|
|
33
|
-
"builds", "build", "loop", "do", "tasks", "engine", "cycles",
|
|
34
|
-
"work", "code", "dev", "infrastructure", "platform",
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# ── C1 EMIT — do-rank open tasks → tasks:announce payloads ────────────────────
|
|
39
|
-
|
|
40
|
-
def tasks_json():
|
|
41
|
-
"""The do-rank task seed (the JSON contract this bridge consumes). One source of
|
|
42
|
-
truth for the open-task set + their tags + learned weight — never re-parsed here."""
|
|
43
|
-
out = subprocess.run(
|
|
44
|
-
[sys.executable, str(DO_RANK), "--tasks-json"],
|
|
45
|
-
capture_output=True, text=True, check=True,
|
|
46
|
-
).stdout
|
|
47
|
-
return json.loads(out)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def emit_payloads(seed):
|
|
51
|
-
"""Map do-rank's OPEN tasks to tasks:announce payloads. The shape matches the
|
|
52
|
-
resolver contract in one.ie/web/src/lib/resolvers/subscriptions.ts:
|
|
53
|
-
{ taskId, tags[], title?, payload? }
|
|
54
|
-
Blocked tasks are excluded — they are not yet actionable, so announcing them would
|
|
55
|
-
wake a worker for work it cannot start. Weight rides in the payload (the receiver
|
|
56
|
-
fans by tag; the weight is what tasks:mine later ranks by)."""
|
|
57
|
-
payloads = []
|
|
58
|
-
for t in seed.get("tasks", []):
|
|
59
|
-
if t.get("taskStatus") != "open":
|
|
60
|
-
continue
|
|
61
|
-
tags = [x for x in t.get("tag", []) if x]
|
|
62
|
-
if not tags:
|
|
63
|
-
continue # a task with no words can reach no follower — skip
|
|
64
|
-
payloads.append({
|
|
65
|
-
"taskId": t.get("tid", ""),
|
|
66
|
-
"tags": tags,
|
|
67
|
-
"title": t.get("name", ""),
|
|
68
|
-
"weight": t.get("weight", 50),
|
|
69
|
-
"dept": t.get("dept", ""),
|
|
70
|
-
})
|
|
71
|
-
return payloads
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
# ── C2 INGEST — world tasks (by build-word) → do-rank queue ───────────────────
|
|
75
|
-
|
|
76
|
-
def ingest(world_tasks):
|
|
77
|
-
"""Filter world tasks tagged with build-words into do-rank-compatible task dicts.
|
|
78
|
-
|
|
79
|
-
world_tasks: list of task dicts in the tasks:mine / tasks:announce shape:
|
|
80
|
-
{ id: str, name: str, tags: [str], weight: int, status?: str }
|
|
81
|
-
Returns: list of do-rank task dicts (tid, name, tag, taskStatus, weight, dept)
|
|
82
|
-
ready to be merged into do-rank's pick alongside local text/*-todo.md tasks.
|
|
83
|
-
|
|
84
|
-
Only tasks whose tags intersect BUILD_WORDS reach /do's queue. Blocked tasks
|
|
85
|
-
and tag-less tasks are already excluded by the world-side resolver; this filter
|
|
86
|
-
adds the semantic gate (build-domain only, not sales/service/etc.).
|
|
87
|
-
"""
|
|
88
|
-
result = []
|
|
89
|
-
for t in world_tasks:
|
|
90
|
-
tags = [x for x in t.get("tags", []) if x]
|
|
91
|
-
if not any(w in BUILD_WORDS for w in tags):
|
|
92
|
-
continue # no build-word → not for /do's pick
|
|
93
|
-
result.append({
|
|
94
|
-
"tid": t.get("id", ""),
|
|
95
|
-
"name": t.get("name", ""),
|
|
96
|
-
"tag": tags,
|
|
97
|
-
"taskStatus": t.get("status", "open"),
|
|
98
|
-
"weight": t.get("weight", 50),
|
|
99
|
-
"dept": "builds", # world build-word tasks land in builds dept
|
|
100
|
-
})
|
|
101
|
-
return result
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
# ── C3 RECONCILE — task-paths.json ⇄ substrate path-context.context-strength ──
|
|
105
|
-
|
|
106
|
-
_RECONCILE_TOLERANCE = 0.05 # divergence below this is noise — don't write
|
|
107
|
-
|
|
108
|
-
def reconcile(local, substrate):
|
|
109
|
-
"""Merge local task-paths.json strength with substrate context-strength into one number.
|
|
110
|
-
|
|
111
|
-
local: { "task:<slug>": {strength, resistance, traversals, closed} }
|
|
112
|
-
— the on-disk tier written by do-rank.py --mark / --sync-closes
|
|
113
|
-
substrate: { word: context_strength }
|
|
114
|
-
— from TypeDB path-context.context-strength via readContextStrength()
|
|
115
|
-
(subscriptions.ts:144), keyed by the word the path-context entity carries
|
|
116
|
-
|
|
117
|
-
Strategy: for each local task, derive its implied substrate score by looking up the
|
|
118
|
-
max context-strength across the words in its slug. If substrate_score > local net
|
|
119
|
-
strength (closed + strength − resistance), raise local.strength so both tiers agree
|
|
120
|
-
and the next do-rank pick reflects the substrate's learned knowledge.
|
|
121
|
-
|
|
122
|
-
Returns a copy of `local` with updated `strength` fields. The caller is responsible
|
|
123
|
-
for writing it back to task-paths.json (do-rank.py --sync-closes idiom).
|
|
124
|
-
|
|
125
|
-
Unidirectional: substrate → local only. The reverse direction (local → substrate)
|
|
126
|
-
happens via do-rank.py --mark which calls mark() over HTTP, already shipped.
|
|
127
|
-
"""
|
|
128
|
-
merged = {k: dict(v) for k, v in local.items()} # shallow-copy each entry
|
|
129
|
-
|
|
130
|
-
for key, entry in merged.items():
|
|
131
|
-
# key shape: "task:<slug>" — split slug words (hyphen-separated)
|
|
132
|
-
slug = key[len("task:"):] if key.startswith("task:") else key
|
|
133
|
-
slug_words = [w for w in slug.replace("-", " ").split() if w]
|
|
134
|
-
|
|
135
|
-
substrate_scores = [substrate[w] for w in slug_words if w in substrate]
|
|
136
|
-
if not substrate_scores:
|
|
137
|
-
continue
|
|
138
|
-
|
|
139
|
-
substrate_strength = max(substrate_scores)
|
|
140
|
-
local_net = (
|
|
141
|
-
float(entry.get("closed", 0))
|
|
142
|
-
+ float(entry.get("strength", 0))
|
|
143
|
-
- float(entry.get("resistance", 0))
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
if substrate_strength > local_net + _RECONCILE_TOLERANCE:
|
|
147
|
-
# Substrate learned this word is stronger than local records — sync up
|
|
148
|
-
entry["strength"] = round(
|
|
149
|
-
max(float(entry.get("strength", 0)), substrate_strength), 3
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
return merged
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
# ── send — do-rank's OPEN tasks → delivered tasks:announce calls (code-signals wire 3) ──
|
|
156
|
-
# Same auth + identity pairing as .claude/hooks/lib/signal.sh's emit_world (see
|
|
157
|
-
# text/code-signals-plan.md): the local dev server reads secrets from .dev.vars (a
|
|
158
|
-
# DIFFERENT value than .env — verified 2026-07-02), so which file to read must match
|
|
159
|
-
# ONE_API_URL's target. slug:"one" + board:"marketplace" let this service-nominated
|
|
160
|
-
# sender reach a subscriber registered under a different actor (fanOut never delivers
|
|
161
|
-
# to its own sender — tag-match.ts).
|
|
162
|
-
|
|
163
|
-
def _one_env_file():
|
|
164
|
-
override = os.environ.get("DO_ENV_FILE")
|
|
165
|
-
if override:
|
|
166
|
-
return Path(override)
|
|
167
|
-
url = os.environ.get("ONE_API_URL", "http://localhost:4321")
|
|
168
|
-
web = ROOT / "one.ie" / "web"
|
|
169
|
-
return web / ".dev.vars" if ("localhost" in url or "127.0.0.1" in url) else web / ".env"
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def _one_secret(name):
|
|
173
|
-
val = os.environ.get(name)
|
|
174
|
-
if val:
|
|
175
|
-
return val
|
|
176
|
-
f = _one_env_file()
|
|
177
|
-
if not f.is_file():
|
|
178
|
-
return ""
|
|
179
|
-
prefix = f"{name}="
|
|
180
|
-
for line in f.read_text().splitlines():
|
|
181
|
-
if line.startswith(prefix):
|
|
182
|
-
return line[len(prefix):].strip().strip('"')
|
|
183
|
-
return ""
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def tag_for_bridge(payload):
|
|
187
|
-
"""Append 'cc' to a payload's tags (dedup, order-preserving) — marks it as a
|
|
188
|
-
code-signal, so the standing ['cc','session'] subscription (wire 2) catches wire 3's
|
|
189
|
-
task announces too, without widening what the subscription itself follows."""
|
|
190
|
-
tags = list(dict.fromkeys([*payload.get("tags", []), "cc"]))
|
|
191
|
-
return {**payload, "tags": tags, "slug": "one", "board": "marketplace"}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def send_payloads(payloads, dry_run=False):
|
|
195
|
-
"""POST each payload to tasks:announce. Returns one result dict per payload —
|
|
196
|
-
either the parsed {outcome,result:{ok,matched,...}} response, or {"dry_run":True,
|
|
197
|
-
"payload":...} when dry_run (no network, provable by inspection)."""
|
|
198
|
-
url = os.environ.get("ONE_API_URL", "http://localhost:4321") + "/api/ask/tasks:announce"
|
|
199
|
-
api_key = _one_secret("GATEWAY_API_KEY") if not dry_run else ""
|
|
200
|
-
results = []
|
|
201
|
-
for payload in payloads:
|
|
202
|
-
tagged = tag_for_bridge(payload)
|
|
203
|
-
if dry_run:
|
|
204
|
-
results.append({"dry_run": True, "payload": tagged})
|
|
205
|
-
continue
|
|
206
|
-
headers = {"Content-Type": "application/json"}
|
|
207
|
-
if api_key:
|
|
208
|
-
headers["Authorization"] = f"Bearer {api_key}"
|
|
209
|
-
req = urllib.request.Request(
|
|
210
|
-
url, data=json.dumps({"data": tagged}).encode(), method="POST", headers=headers,
|
|
211
|
-
)
|
|
212
|
-
try:
|
|
213
|
-
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
214
|
-
results.append(json.loads(resp.read().decode()))
|
|
215
|
-
except urllib.error.URLError as e:
|
|
216
|
-
results.append({"error": str(e)})
|
|
217
|
-
return results
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
# ── selftest — the plan kill-switch (all three; RED until C3) ─────────────────
|
|
221
|
-
|
|
222
|
-
def _check_emit():
|
|
223
|
-
"""Emit round-trip on a fixture in the do-rank --tasks-json contract shape."""
|
|
224
|
-
seed = {
|
|
225
|
-
"tasks": [
|
|
226
|
-
{"tid": "task:alpha", "name": "Alpha", "tag": ["builds", "loop"],
|
|
227
|
-
"taskStatus": "open", "weight": 72, "dept": "builds"},
|
|
228
|
-
{"tid": "task:beta", "name": "Beta", "tag": ["sales"],
|
|
229
|
-
"taskStatus": "blocked", "weight": 50, "dept": "sales"}, # excluded (blocked)
|
|
230
|
-
{"tid": "task:gamma", "name": "Gamma", "tag": [],
|
|
231
|
-
"taskStatus": "open", "weight": 50, "dept": "builds"}, # excluded (no words)
|
|
232
|
-
],
|
|
233
|
-
}
|
|
234
|
-
out = emit_payloads(seed)
|
|
235
|
-
if len(out) != 1:
|
|
236
|
-
return False, f"expected 1 open+tagged payload, got {len(out)}"
|
|
237
|
-
p = out[0]
|
|
238
|
-
if p["taskId"] != "task:alpha":
|
|
239
|
-
return False, f"wrong task emitted: {p['taskId']}"
|
|
240
|
-
if set(p["tags"]) != {"builds", "loop"}:
|
|
241
|
-
return False, f"tags not preserved: {p['tags']}"
|
|
242
|
-
if p["weight"] != 72:
|
|
243
|
-
return False, f"weight not preserved: {p['weight']}"
|
|
244
|
-
# contract: every payload carries the fields tasks:announce requires
|
|
245
|
-
if not (p["taskId"] and p["tags"]):
|
|
246
|
-
return False, "payload missing taskId/tags (tasks:announce contract)"
|
|
247
|
-
return True, "emit: open+tagged tasks → tasks:announce payloads, weight preserved"
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def _check_ingest():
|
|
251
|
-
world = [
|
|
252
|
-
# ✓ build-word hit → ingested
|
|
253
|
-
{"id": "world:1", "name": "Build the loop", "tags": ["builds", "loop"], "weight": 80, "status": "open"},
|
|
254
|
-
# ✗ no build-word → excluded
|
|
255
|
-
{"id": "world:2", "name": "Sales email", "tags": ["sales", "email"], "weight": 60, "status": "open"},
|
|
256
|
-
# ✓ build-word hit → ingested
|
|
257
|
-
{"id": "world:3", "name": "Coding tasks", "tags": ["code", "tasks"], "weight": 70, "status": "open"},
|
|
258
|
-
# ✗ no tags → excluded
|
|
259
|
-
{"id": "world:4", "name": "Empty", "tags": [], "weight": 50, "status": "open"},
|
|
260
|
-
]
|
|
261
|
-
out = ingest(world)
|
|
262
|
-
if len(out) != 2:
|
|
263
|
-
return False, f"expected 2 build-word tasks, got {len(out)}: {[t['tid'] for t in out]}"
|
|
264
|
-
tids = {t["tid"] for t in out}
|
|
265
|
-
if tids != {"world:1", "world:3"}:
|
|
266
|
-
return False, f"wrong tasks ingested: {tids}"
|
|
267
|
-
if any(t["dept"] != "builds" for t in out):
|
|
268
|
-
return False, f"dept not 'builds' for all: {[t['dept'] for t in out]}"
|
|
269
|
-
return True, "ingest: build-word tasks selected + shaped, non-build + no-tag excluded"
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
def _check_reconcile():
|
|
273
|
-
local = {
|
|
274
|
-
# local_net = 0.5 + 1.0 - 0.5 = 1.0 ; substrate max(loop=3.5) = 3.5 → update
|
|
275
|
-
"task:loop-plan": {"strength": 1.0, "resistance": 0.5, "traversals": 3, "closed": 0.5},
|
|
276
|
-
# local_net = 2.0 + 0.0 - 0.0 = 2.0 ; substrate max(tasks=2.0) = 2.0 → within tolerance, no-op
|
|
277
|
-
"task:tasks-bridge": {"strength": 0.0, "resistance": 0.0, "traversals": 0, "closed": 2.0},
|
|
278
|
-
# no substrate entry for any word → unchanged
|
|
279
|
-
"task:pay-links": {"strength": 0.5, "resistance": 0.0, "traversals": 1, "closed": 0.0},
|
|
280
|
-
}
|
|
281
|
-
substrate = {"loop": 3.5, "plan": 0.5, "tasks": 2.0, "bridge": 1.5}
|
|
282
|
-
merged = reconcile(local, substrate)
|
|
283
|
-
|
|
284
|
-
lp = merged.get("task:loop-plan", {})
|
|
285
|
-
if lp.get("strength", 0) < 3.0:
|
|
286
|
-
return False, f"loop-plan strength not raised from substrate: {lp}"
|
|
287
|
-
tb = merged.get("task:tasks-bridge", {})
|
|
288
|
-
if tb.get("strength", 0) != 0.0:
|
|
289
|
-
return False, f"tasks-bridge strength should be unchanged (within tolerance): {tb}"
|
|
290
|
-
pl = merged.get("task:pay-links", {})
|
|
291
|
-
if pl.get("strength") != 0.5:
|
|
292
|
-
return False, f"pay-links strength should be unchanged (no substrate word): {pl}"
|
|
293
|
-
return True, "reconcile: substrate-stronger words raise local tier; equal/weaker and no-match are untouched"
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
def selftest():
|
|
297
|
-
checks = [
|
|
298
|
-
("emit", _check_emit()),
|
|
299
|
-
("ingest", _check_ingest()),
|
|
300
|
-
("reconcile", _check_reconcile()),
|
|
301
|
-
]
|
|
302
|
-
passed = 0
|
|
303
|
-
for name, (ok, msg) in checks:
|
|
304
|
-
print(f" {'✓' if ok else '✗'} {name:<10} {msg}")
|
|
305
|
-
passed += 1 if ok else 0
|
|
306
|
-
print(f"SELFTEST: {passed}/{len(checks)} pass "
|
|
307
|
-
f"({'bridge complete' if passed == len(checks) else 'C2 ingest + C3 reconcile remain'})")
|
|
308
|
-
return 0 if passed == len(checks) else 1
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
def main():
|
|
312
|
-
args = sys.argv[1:]
|
|
313
|
-
if "--selftest" in args:
|
|
314
|
-
sys.exit(selftest())
|
|
315
|
-
if "--emit" in args:
|
|
316
|
-
print(json.dumps(emit_payloads(tasks_json()), indent=2))
|
|
317
|
-
return
|
|
318
|
-
if "--send" in args:
|
|
319
|
-
dry_run = "--dry-run" in args
|
|
320
|
-
payloads = emit_payloads(tasks_json())
|
|
321
|
-
if not payloads:
|
|
322
|
-
print("[]")
|
|
323
|
-
print("send: 0 open+tagged task(s) — nothing to announce", file=sys.stderr)
|
|
324
|
-
return
|
|
325
|
-
results = send_payloads(payloads, dry_run=dry_run)
|
|
326
|
-
print(json.dumps(results, indent=2))
|
|
327
|
-
if not dry_run:
|
|
328
|
-
ok = sum(
|
|
329
|
-
1 for r in results
|
|
330
|
-
if isinstance(r, dict) and r.get("outcome") == "result" and r.get("result", {}).get("ok")
|
|
331
|
-
)
|
|
332
|
-
print(f"send: {ok}/{len(results)} task(s) announced ok", file=sys.stderr)
|
|
333
|
-
return
|
|
334
|
-
if "--ingest" in args:
|
|
335
|
-
# Read world tasks from stdin (JSON array) or print usage
|
|
336
|
-
try:
|
|
337
|
-
world_tasks = json.load(sys.stdin)
|
|
338
|
-
except (json.JSONDecodeError, ValueError) as e:
|
|
339
|
-
print(f"error: --ingest reads a JSON array of world tasks from stdin: {e}", file=sys.stderr)
|
|
340
|
-
sys.exit(1)
|
|
341
|
-
print(json.dumps(ingest(world_tasks), indent=2))
|
|
342
|
-
return
|
|
343
|
-
if "--reconcile" in args:
|
|
344
|
-
# Read substrate context-strength map from stdin: {"word": strength}
|
|
345
|
-
try:
|
|
346
|
-
substrate = json.load(sys.stdin)
|
|
347
|
-
except (json.JSONDecodeError, ValueError) as e:
|
|
348
|
-
print(f"error: --reconcile reads a JSON {{word: strength}} map from stdin: {e}", file=sys.stderr)
|
|
349
|
-
sys.exit(1)
|
|
350
|
-
from pathlib import Path as _P
|
|
351
|
-
task_paths_file = _P(__file__).resolve().parents[2] / "text" / "task-paths.json"
|
|
352
|
-
try:
|
|
353
|
-
local = json.loads(task_paths_file.read_text())
|
|
354
|
-
except (FileNotFoundError, ValueError):
|
|
355
|
-
local = {}
|
|
356
|
-
merged = reconcile(local, substrate)
|
|
357
|
-
task_paths_file.write_text(json.dumps(merged, indent=2, sort_keys=True) + "\n")
|
|
358
|
-
changed = sum(1 for k in merged if merged[k].get("strength") != local.get(k, {}).get("strength"))
|
|
359
|
-
print(f"reconciled {changed} task(s) from substrate context-strength → {task_paths_file.name}")
|
|
360
|
-
return
|
|
361
|
-
print(__doc__)
|
|
362
|
-
print("usage: do-tasks-bridge.py [--emit | --send [--dry-run] | --ingest | --reconcile | --selftest]")
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if __name__ == "__main__":
|
|
366
|
-
main()
|