@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.
Files changed (263) hide show
  1. package/agents/abm-strategist.md +89 -0
  2. package/agents/ads-meta.md +97 -0
  3. package/agents/analyst.md +173 -0
  4. package/agents/architect.md +280 -0
  5. package/agents/brand-guardian.md +88 -0
  6. package/agents/brand-strategist.md +92 -0
  7. package/agents/campaign-content.md +90 -0
  8. package/agents/campaign-email.md +88 -0
  9. package/agents/campaign-sms.md +87 -0
  10. package/agents/campaign-social.md +87 -0
  11. package/agents/cco.md +99 -0
  12. package/agents/ceo.md +106 -0
  13. package/agents/chairman.md +120 -0
  14. package/agents/cmo.md +101 -0
  15. package/agents/community-greeter.md +89 -0
  16. package/agents/community-moderator.md +92 -0
  17. package/agents/compliance.md +92 -0
  18. package/agents/copywriter.md +100 -0
  19. package/agents/creative-strategist.md +95 -0
  20. package/agents/cro.md +98 -0
  21. package/agents/cto.md +267 -0
  22. package/agents/customer-interviewer.md +93 -0
  23. package/agents/customer-researcher.md +89 -0
  24. package/agents/customer-success-manager.md +90 -0
  25. package/agents/customer-trainer.md +90 -0
  26. package/agents/cxo.md +100 -0
  27. package/agents/demand-creator.md +89 -0
  28. package/agents/demo-mover.md +83 -0
  29. package/agents/demo-specialist.md +89 -0
  30. package/agents/demo-thai-family-law.md +87 -0
  31. package/agents/designer.md +112 -0
  32. package/agents/discovery-caller.md +98 -0
  33. package/agents/doctor.md +139 -0
  34. package/agents/educate-coach.md +100 -0
  35. package/agents/elevate-tutor.md +83 -0
  36. package/agents/email-lifecycle-marketer.md +91 -0
  37. package/agents/engage-specialist.md +99 -0
  38. package/agents/events-coordinator.md +90 -0
  39. package/agents/foundation-builder.md +94 -0
  40. package/agents/funnel-architect.md +89 -0
  41. package/agents/gift-creator.md +100 -0
  42. package/agents/google-ads.md +103 -0
  43. package/agents/guide.md +292 -0
  44. package/agents/helpdesk-dispatcher.md +88 -0
  45. package/agents/hook-specialist.md +91 -0
  46. package/agents/identify-optimizer.md +101 -0
  47. package/agents/implementer.md +305 -0
  48. package/agents/incident-commander.md +120 -0
  49. package/agents/insights-lead.md +93 -0
  50. package/agents/journey-runner.md +113 -0
  51. package/agents/linkedin-ads.md +123 -0
  52. package/agents/live-sales-chat.md +90 -0
  53. package/agents/market-researcher.md +88 -0
  54. package/agents/media-buyer.md +96 -0
  55. package/agents/movers-customer-researcher.md +95 -0
  56. package/agents/movers-foundation-builder.md +96 -0
  57. package/agents/movers-market-researcher.md +97 -0
  58. package/agents/movers-pricing-strategist.md +171 -0
  59. package/agents/nurture-architect.md +99 -0
  60. package/agents/offer-architect.md +92 -0
  61. package/agents/onboarder.md +222 -0
  62. package/agents/onboarding-specialist.md +92 -0
  63. package/agents/operations-dashboard.md +98 -0
  64. package/agents/perf-engineer.md +348 -0
  65. package/agents/playbook-writer.md +71 -0
  66. package/agents/plg-strategist.md +91 -0
  67. package/agents/positioning-architect.md +88 -0
  68. package/agents/press-officer.md +89 -0
  69. package/agents/pricing-strategist.md +94 -0
  70. package/agents/privacy-officer.md +92 -0
  71. package/agents/referral-manager.md +91 -0
  72. package/agents/refine-analyst.md +102 -0
  73. package/agents/release-manager.md +261 -0
  74. package/agents/renewals-upsell-rep.md +90 -0
  75. package/agents/review-engineer.md +327 -0
  76. package/agents/rewards-steward.md +82 -0
  77. package/agents/sales-call-coach.md +94 -0
  78. package/agents/sales-closer.md +98 -0
  79. package/agents/security-auditor.md +313 -0
  80. package/agents/sell-closer.md +101 -0
  81. package/agents/share-amplifier.md +99 -0
  82. package/agents/social-media-manager.md +92 -0
  83. package/agents/storyteller.md +250 -0
  84. package/agents/strategist.md +83 -0
  85. package/agents/strategy-aligner.md +94 -0
  86. package/agents/support-agent.md +90 -0
  87. package/agents/tagger.md +245 -0
  88. package/agents/tech-writer.md +216 -0
  89. package/agents/test-engineer.md +243 -0
  90. package/agents/tiktok-ads.md +105 -0
  91. package/agents/tracking-engineer.md +92 -0
  92. package/agents/upsell-strategist.md +101 -0
  93. package/agents/voice.md +85 -0
  94. package/agents/w4-tools.md +153 -0
  95. package/agents/w4-verify.md +20 -138
  96. package/agents/workflow-optimiser.md +84 -0
  97. package/commands/close.md +814 -160
  98. package/commands/create.md +2 -2
  99. package/commands/deploy.md +554 -175
  100. package/commands/do.md +119 -109
  101. package/commands/fast.md +67 -0
  102. package/commands/improve.md +2 -2
  103. package/commands/one.md +418 -0
  104. package/commands/see.md +1 -1
  105. package/commands/sync.md +1 -1
  106. package/commands/tasks.md +222 -0
  107. package/commands/vespio.md +110 -0
  108. package/commands/vespio.remote.md +227 -0
  109. package/hooks/hooks.json +25 -79
  110. package/hooks/lib/governor-escape-match.sh +88 -0
  111. package/hooks/lib/hook.sh +4 -13
  112. package/hooks/lib/signal.sh +9 -2
  113. package/hooks/scripts/branch-pin.sh +6 -0
  114. package/hooks/scripts/config-protect.sh +6 -0
  115. package/hooks/scripts/do-outcome-gate.sh +19 -2
  116. package/hooks/scripts/git-add-guard.sh +43 -2
  117. package/hooks/scripts/governor-escape.sh +96 -0
  118. package/hooks/scripts/load-guard.sh +127 -0
  119. package/hooks/scripts/post-edit-check.sh +36 -29
  120. package/hooks/scripts/session-start.sh +34 -80
  121. package/hooks/scripts/task-complete-verify.sh +46 -40
  122. package/package.json +1 -1
  123. package/rules/documentation.md +9 -0
  124. package/scripts/ad-copy-lint.sh +656 -0
  125. package/scripts/agentverse-audit.sh +256 -0
  126. package/scripts/asi-walk.sh +435 -0
  127. package/scripts/astro-build-cached.sh +143 -0
  128. package/scripts/blocks-manifest-cached.sh +100 -0
  129. package/scripts/blocks-manifest.mjs +497 -0
  130. package/scripts/blocks-ratchet.sh +116 -0
  131. package/scripts/blocks-render-probe.mjs +529 -0
  132. package/scripts/blocks-usage.mjs +334 -0
  133. package/scripts/chat-context-check.sh +89 -0
  134. package/scripts/close-metrics.sh +558 -0
  135. package/scripts/close-owner.sh +268 -0
  136. package/scripts/db-sync-lock-check.sh +116 -0
  137. package/scripts/deploy-dev.sh +35 -0
  138. package/scripts/deploy-emit.sh +298 -0
  139. package/scripts/deploy-gate-check.sh +128 -0
  140. package/scripts/deploy-ready.sh +78 -0
  141. package/scripts/deploy-record.sh +605 -0
  142. package/scripts/deploy.sh +1273 -0
  143. package/scripts/do-auto.sh +598 -48
  144. package/scripts/do-brief.sh +113 -0
  145. package/scripts/do-close.sh +1137 -0
  146. package/scripts/do-commit.sh +75 -0
  147. package/scripts/do-consumer-sweep.sh +18 -1
  148. package/scripts/do-cycle-shape-check.sh +160 -0
  149. package/scripts/do-decide.sh +476 -0
  150. package/scripts/do-derive-check.sh +436 -0
  151. package/scripts/do-fleet.sh +106 -28
  152. package/scripts/do-folder.sh +10 -1
  153. package/scripts/do-next.sh +106 -0
  154. package/scripts/do-orchestrate.sh +17 -5
  155. package/scripts/do-plan-json.mjs +201 -0
  156. package/scripts/do-plan-json.sh +8 -0
  157. package/scripts/do-preflight.sh +117 -0
  158. package/scripts/do-project.sh +157 -0
  159. package/scripts/do-prove-selftest.sh +108 -0
  160. package/scripts/do-prove.sh +295 -23
  161. package/scripts/do-rank.py +31 -1
  162. package/scripts/do-recon-cache.sh +7 -1
  163. package/scripts/do-recon-pack.sh +196 -0
  164. package/scripts/do-reconcile.sh +121 -4
  165. package/scripts/do-signal.sh +280 -23
  166. package/scripts/do-smoke.sh +18 -1
  167. package/scripts/do-test-gate.sh +80 -0
  168. package/scripts/do-tick.sh +102 -0
  169. package/scripts/do-tier.sh +6 -0
  170. package/scripts/do-triage.sh +182 -0
  171. package/scripts/do-ui-gate.sh +1 -1
  172. package/scripts/do-w4-gates.sh +451 -0
  173. package/scripts/do-walk.sh +12 -1
  174. package/scripts/env-sync.sh +173 -0
  175. package/scripts/factory-brief-check.sh +330 -0
  176. package/scripts/factory-check.sh +68 -14
  177. package/scripts/factory-close-check.sh +257 -0
  178. package/scripts/factory-emit.sh +211 -0
  179. package/scripts/factory-executor-check.mjs +353 -0
  180. package/scripts/factory-peak.sh +301 -0
  181. package/scripts/factory-repo.sh +118 -3
  182. package/scripts/factory-review-check.mjs +61 -0
  183. package/scripts/factory-ship.sh +61 -0
  184. package/scripts/factory-tasks-check.sh +18 -1
  185. package/scripts/factory-turn.sh +326 -0
  186. package/scripts/factory-walk.sh +396 -0
  187. package/scripts/factory-width.sh +57 -0
  188. package/scripts/fade-toxic.sh +4 -3
  189. package/scripts/fixtures/factory-brief-real.md +44 -0
  190. package/scripts/fixtures/triage-dupe.md +5 -0
  191. package/scripts/fleet-manifest.mjs +108 -0
  192. package/scripts/fleet-status.sh +110 -0
  193. package/scripts/full-suite-paths-check.sh +144 -0
  194. package/scripts/gate-reaper-check.sh +98 -0
  195. package/scripts/gate-reaper.sh +125 -0
  196. package/scripts/gate-run.sh +73 -0
  197. package/scripts/gc-content-check.sh +140 -0
  198. package/scripts/gen-dev-config.py +20 -0
  199. package/scripts/govern-bound-check.sh +60 -0
  200. package/scripts/govern-claims-check.sh +233 -0
  201. package/scripts/govern-mem-check.sh +290 -0
  202. package/scripts/governor-doors-check.sh +362 -0
  203. package/scripts/governor-escape-check.sh +171 -0
  204. package/scripts/health.sh +413 -0
  205. package/scripts/id-inventory.mjs +418 -0
  206. package/scripts/land.sh +551 -0
  207. package/scripts/lib/gc-finished.sh +77 -0
  208. package/scripts/lib/govern.sh +361 -0
  209. package/scripts/lib/govern.ts +756 -0
  210. package/scripts/lighthouse-run.sh +187 -0
  211. package/scripts/livekit-live-check.sh +61 -0
  212. package/scripts/livekit-ratchet.sh +46 -0
  213. package/scripts/load-guard-check.sh +49 -0
  214. package/scripts/machine-check.sh +102 -0
  215. package/scripts/machine-watch.sh +177 -0
  216. package/scripts/one-agents.mjs +415 -0
  217. package/scripts/one-resume.sh +78 -0
  218. package/scripts/orphan-baseline.json +182 -0
  219. package/scripts/orphan-modules.mjs +179 -0
  220. package/scripts/pr-body.sh +335 -0
  221. package/scripts/preview-fd-check.sh +289 -0
  222. package/scripts/promise-manifest.mjs +24 -1
  223. package/scripts/release.sh +322 -0
  224. package/scripts/roles-check.sh +946 -0
  225. package/scripts/sdk-build-cached.sh +64 -0
  226. package/scripts/signal-watch.sh +241 -0
  227. package/scripts/skills-publish.sh +94 -0
  228. package/scripts/speed-cache-check.sh +173 -0
  229. package/scripts/speed-check.mjs +907 -0
  230. package/scripts/speed-parity-check.sh +648 -0
  231. package/scripts/speed-waterfall-check.sh +355 -0
  232. package/scripts/substrate-env-parity.mjs +156 -0
  233. package/scripts/tasks-claim-race.mjs +108 -0
  234. package/scripts/tasks-loop.sh +185 -0
  235. package/scripts/test-cached.sh +255 -0
  236. package/scripts/test-full.sh +87 -0
  237. package/scripts/test-honesty.mjs +137 -0
  238. package/scripts/test-lanes.sh +166 -0
  239. package/scripts/test-speed.sh +94 -0
  240. package/scripts/triage-shape-check.sh +149 -0
  241. package/scripts/tsc-cached.sh +179 -0
  242. package/scripts/typedb-flake-check.sh +197 -0
  243. package/scripts/urls-lint.sh +15 -0
  244. package/scripts/verify-fast.sh +445 -0
  245. package/scripts/vespio-sync.sh +149 -0
  246. package/scripts/wf-check.mjs +104 -1
  247. package/scripts/worktree-preview.sh +879 -0
  248. package/scripts/worktree-up.sh +208 -0
  249. package/skills/livekit-agents/SKILL.md +285 -0
  250. package/skills/livekit-agents/references/freshness-rules.md +168 -0
  251. package/skills/shadcn/SKILL.md +1 -1
  252. package/skills/signal/SKILL.md +0 -1
  253. package/skills/voice/SKILL.md +94 -6
  254. package/skills/voice/corpus-check.sh +87 -0
  255. package/hooks/scripts/compact-hint.sh +0 -35
  256. package/hooks/scripts/gate-guard.sh +0 -83
  257. package/hooks/scripts/read-tracker.sh +0 -26
  258. package/hooks/scripts/session-end-verify.sh +0 -51
  259. package/hooks/scripts/stop-reflect.sh +0 -140
  260. package/hooks/scripts/sync-priority-todo.sh +0 -57
  261. package/hooks/scripts/sync-todo-docs.sh +0 -46
  262. package/hooks/scripts/tool-signal.sh +0 -48
  263. 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()