crewly 1.20.214 → 1.20.216

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 (183) hide show
  1. package/config/hooks/agent-status/report.sh +45 -6
  2. package/config/roles/team-leader/fragments/role-boundary.md +2 -1
  3. package/config/roles/team-leader/prompt.md +9 -5
  4. package/config/roles/team-leader/role-boundaries.md +3 -2
  5. package/config/roles/team-leader/tl-addon.md +9 -5
  6. package/config/skills/agent/core/ask-owner/SKILL.md +9 -2
  7. package/config/skills/agent/core/ask-owner/execute.sh +1 -1
  8. package/config/skills/agent/core/project-tickets/SKILL.md +44 -12
  9. package/config/skills/agent/core/project-tickets/execute.sh +46 -13
  10. package/config/skills/agent/core/project-tickets/execute.test.sh +19 -0
  11. package/config/skills/team-leader/delegate-task/SKILL.md +40 -0
  12. package/config/skills/team-leader/delegate-task/execute.sh +39 -1
  13. package/dist/backend/backend/src/constants.d.ts +101 -10
  14. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  15. package/dist/backend/backend/src/constants.js +119 -9
  16. package/dist/backend/backend/src/constants.js.map +1 -1
  17. package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.d.ts +2 -1
  18. package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.d.ts.map +1 -1
  19. package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.js +26 -3
  20. package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.js.map +1 -1
  21. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +10 -2
  22. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts.map +1 -1
  23. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js +17 -2
  24. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js.map +1 -1
  25. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.d.ts +2 -1
  26. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.d.ts.map +1 -1
  27. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js +4 -2
  28. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js.map +1 -1
  29. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
  30. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +32 -0
  31. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
  32. package/dist/backend/backend/src/controllers/team/team-lead-share.controller.d.ts +27 -0
  33. package/dist/backend/backend/src/controllers/team/team-lead-share.controller.d.ts.map +1 -0
  34. package/dist/backend/backend/src/controllers/team/team-lead-share.controller.js +92 -0
  35. package/dist/backend/backend/src/controllers/team/team-lead-share.controller.js.map +1 -0
  36. package/dist/backend/backend/src/controllers/team/team.routes.d.ts.map +1 -1
  37. package/dist/backend/backend/src/controllers/team/team.routes.js +4 -0
  38. package/dist/backend/backend/src/controllers/team/team.routes.js.map +1 -1
  39. package/dist/backend/backend/src/index.d.ts.map +1 -1
  40. package/dist/backend/backend/src/index.js +16 -0
  41. package/dist/backend/backend/src/index.js.map +1 -1
  42. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  43. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +6 -0
  44. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  45. package/dist/backend/backend/src/services/decisions/decision-contract.d.ts +25 -1
  46. package/dist/backend/backend/src/services/decisions/decision-contract.d.ts.map +1 -1
  47. package/dist/backend/backend/src/services/decisions/decision-contract.js +39 -2
  48. package/dist/backend/backend/src/services/decisions/decision-contract.js.map +1 -1
  49. package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  50. package/dist/backend/backend/src/services/decisions/decision.service.js +17 -1
  51. package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
  52. package/dist/backend/backend/src/services/open-items/open-item-card.d.ts +26 -0
  53. package/dist/backend/backend/src/services/open-items/open-item-card.d.ts.map +1 -1
  54. package/dist/backend/backend/src/services/open-items/open-item-card.js +71 -1
  55. package/dist/backend/backend/src/services/open-items/open-item-card.js.map +1 -1
  56. package/dist/backend/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  57. package/dist/backend/backend/src/services/open-items/open-items.service.js +6 -3
  58. package/dist/backend/backend/src/services/open-items/open-items.service.js.map +1 -1
  59. package/dist/backend/backend/src/services/orc/reply-destination.wiring.d.ts.map +1 -1
  60. package/dist/backend/backend/src/services/orc/reply-destination.wiring.js +46 -1
  61. package/dist/backend/backend/src/services/orc/reply-destination.wiring.js.map +1 -1
  62. package/dist/backend/backend/src/services/orc/work-item-destination.d.ts +35 -0
  63. package/dist/backend/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
  64. package/dist/backend/backend/src/services/orc/work-item-destination.js +75 -2
  65. package/dist/backend/backend/src/services/orc/work-item-destination.js.map +1 -1
  66. package/dist/backend/backend/src/services/project-tickets/autopilot-speed-command.d.ts +108 -0
  67. package/dist/backend/backend/src/services/project-tickets/autopilot-speed-command.d.ts.map +1 -0
  68. package/dist/backend/backend/src/services/project-tickets/autopilot-speed-command.js +223 -0
  69. package/dist/backend/backend/src/services/project-tickets/autopilot-speed-command.js.map +1 -0
  70. package/dist/backend/backend/src/services/project-tickets/autopilot-speed.wiring.d.ts +57 -0
  71. package/dist/backend/backend/src/services/project-tickets/autopilot-speed.wiring.d.ts.map +1 -0
  72. package/dist/backend/backend/src/services/project-tickets/autopilot-speed.wiring.js +90 -0
  73. package/dist/backend/backend/src/services/project-tickets/autopilot-speed.wiring.js.map +1 -0
  74. package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.d.ts +18 -4
  75. package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.d.ts.map +1 -1
  76. package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.js +18 -4
  77. package/dist/backend/backend/src/services/project-tickets/project-ticket-workflow.service.js.map +1 -1
  78. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts +135 -14
  79. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts.map +1 -1
  80. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js +111 -18
  81. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js.map +1 -1
  82. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts +73 -4
  83. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts.map +1 -1
  84. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js +86 -7
  85. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js.map +1 -1
  86. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.d.ts +1 -1
  87. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.d.ts.map +1 -1
  88. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.js +7 -0
  89. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.js.map +1 -1
  90. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts +104 -4
  91. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts.map +1 -1
  92. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js +424 -12
  93. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js.map +1 -1
  94. package/dist/backend/backend/src/services/slack/slack-agent-dm.service.d.ts.map +1 -1
  95. package/dist/backend/backend/src/services/slack/slack-agent-dm.service.js +2 -1
  96. package/dist/backend/backend/src/services/slack/slack-agent-dm.service.js.map +1 -1
  97. package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
  98. package/dist/backend/backend/src/services/slack/slack-initializer.js +10 -0
  99. package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
  100. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +7 -0
  101. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  102. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +25 -2
  103. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  104. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +23 -0
  105. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  106. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +74 -2
  107. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  108. package/dist/backend/backend/src/services/tl-delegation/lead-share.d.ts +108 -0
  109. package/dist/backend/backend/src/services/tl-delegation/lead-share.d.ts.map +1 -0
  110. package/dist/backend/backend/src/services/tl-delegation/lead-share.js +170 -0
  111. package/dist/backend/backend/src/services/tl-delegation/lead-share.js.map +1 -0
  112. package/dist/backend/backend/src/services/tl-delegation/lead-share.wiring.d.ts +32 -0
  113. package/dist/backend/backend/src/services/tl-delegation/lead-share.wiring.d.ts.map +1 -0
  114. package/dist/backend/backend/src/services/tl-delegation/lead-share.wiring.js +47 -0
  115. package/dist/backend/backend/src/services/tl-delegation/lead-share.wiring.js.map +1 -0
  116. package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.d.ts +207 -0
  117. package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.d.ts.map +1 -0
  118. package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.js +377 -0
  119. package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.js.map +1 -0
  120. package/dist/backend/backend/src/types/decision.types.d.ts +6 -1
  121. package/dist/backend/backend/src/types/decision.types.d.ts.map +1 -1
  122. package/dist/backend/backend/src/types/decision.types.js +1 -1
  123. package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts +46 -4
  124. package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts.map +1 -1
  125. package/dist/backend/backend/src/types/ticket-autopilot.types.js +43 -2
  126. package/dist/backend/backend/src/types/ticket-autopilot.types.js.map +1 -1
  127. package/dist/backend/backend/src/types/v2/work-item.types.d.ts +1 -1
  128. package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  129. package/dist/backend/backend/src/types/v2/work-item.types.js +1 -0
  130. package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
  131. package/dist/backend/backend/src/utils/defaultPrompts.js +3 -3
  132. package/dist/backend/backend/src/utils/defaultPrompts.js.map +1 -1
  133. package/dist/backend/build-info.json +2 -2
  134. package/dist/cli/backend/src/constants.d.ts +101 -10
  135. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  136. package/dist/cli/backend/src/constants.js +119 -9
  137. package/dist/cli/backend/src/constants.js.map +1 -1
  138. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  139. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +6 -0
  140. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  141. package/dist/cli/backend/src/services/decisions/decision-contract.d.ts +25 -1
  142. package/dist/cli/backend/src/services/decisions/decision-contract.d.ts.map +1 -1
  143. package/dist/cli/backend/src/services/decisions/decision-contract.js +39 -2
  144. package/dist/cli/backend/src/services/decisions/decision-contract.js.map +1 -1
  145. package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  146. package/dist/cli/backend/src/services/decisions/decision.service.js +17 -1
  147. package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -1
  148. package/dist/cli/backend/src/services/open-items/open-item-card.d.ts +26 -0
  149. package/dist/cli/backend/src/services/open-items/open-item-card.d.ts.map +1 -1
  150. package/dist/cli/backend/src/services/open-items/open-item-card.js +71 -1
  151. package/dist/cli/backend/src/services/open-items/open-item-card.js.map +1 -1
  152. package/dist/cli/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  153. package/dist/cli/backend/src/services/open-items/open-items.service.js +6 -3
  154. package/dist/cli/backend/src/services/open-items/open-items.service.js.map +1 -1
  155. package/dist/cli/backend/src/services/orc/work-item-destination.d.ts +35 -0
  156. package/dist/cli/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
  157. package/dist/cli/backend/src/services/orc/work-item-destination.js +75 -2
  158. package/dist/cli/backend/src/services/orc/work-item-destination.js.map +1 -1
  159. package/dist/cli/backend/src/services/slack/slack-agent-dm.service.d.ts.map +1 -1
  160. package/dist/cli/backend/src/services/slack/slack-agent-dm.service.js +2 -1
  161. package/dist/cli/backend/src/services/slack/slack-agent-dm.service.js.map +1 -1
  162. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +7 -0
  163. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  164. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +25 -2
  165. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  166. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +23 -0
  167. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  168. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +74 -2
  169. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  170. package/dist/cli/backend/src/types/decision.types.d.ts +6 -1
  171. package/dist/cli/backend/src/types/decision.types.d.ts.map +1 -1
  172. package/dist/cli/backend/src/types/decision.types.js +1 -1
  173. package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts +46 -4
  174. package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts.map +1 -1
  175. package/dist/cli/backend/src/types/ticket-autopilot.types.js +43 -2
  176. package/dist/cli/backend/src/types/ticket-autopilot.types.js.map +1 -1
  177. package/dist/cli/backend/src/types/v2/work-item.types.d.ts +1 -1
  178. package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  179. package/dist/cli/backend/src/types/v2/work-item.types.js +1 -0
  180. package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
  181. package/frontend/dist/assets/{index-917db1b8.js → index-bc4e2a93.js} +274 -274
  182. package/frontend/dist/index.html +1 -1
  183. package/package.json +1 -1
@@ -15,10 +15,18 @@
15
15
  #
16
16
  # PRIVACY: the stdin JSON can hold tool_input, file contents, prompts and a
17
17
  # transcript path, any of which may contain secrets. This script extracts
18
- # exactly five TOP-LEVEL fields — hook_event_name, notification_type and
18
+ # exactly six TOP-LEVEL fields — hook_event_name, notification_type and
19
19
  # source (kept only as plain identifiers, [A-Za-z_], max 64 chars), tool_use_id and
20
- # agent_id (kept only as [A-Za-z0-9_-], max 128 chars) — and sends those plus
21
- # the session name. Nothing else from stdin is sent, printed or logged.
20
+ # agent_id (kept only as [A-Za-z0-9_-], max 128 chars), and tool_name for
21
+ # PostToolUse (kept only as [A-Za-z0-9_-], max 64 chars; never tool_input) —
22
+ # and sends those plus the session name. Nothing else from stdin is sent,
23
+ # printed or logged.
24
+ #
25
+ # Team-lead nudge (crewly#1083, specs/2026-10-04-tl-delegation.md §2): for
26
+ # PostToolUse the backend may answer `additionalContext` (a short English
27
+ # note for a team lead doing hands-on work); it is printed as Claude Code's
28
+ # hookSpecificOutput.additionalContext. Only that one string field of the
29
+ # response is used.
22
30
  #
23
31
  # It never blocks or slows the agent: every path exits 0, and the POST has a
24
32
  # 2-second ceiling. A failed POST is dropped silently (the backend's screen
@@ -68,8 +76,13 @@ ID_PATTERN='^[A-Za-z0-9_-]{1,128}$'
68
76
  TOOL_USE_ID=""
69
77
  AGENT_ID=""
70
78
  SOURCE=""
79
+ TOOL_NAME=""
71
80
  case "$EVENT" in
72
- PreToolUse|PostToolUse) TOOL_USE_ID="$(field tool_use_id "$ID_PATTERN")" ;;
81
+ PreToolUse) TOOL_USE_ID="$(field tool_use_id "$ID_PATTERN")" ;;
82
+ PostToolUse)
83
+ TOOL_USE_ID="$(field tool_use_id "$ID_PATTERN")"
84
+ TOOL_NAME="$(field tool_name '^[A-Za-z0-9_-]{1,64}$')"
85
+ ;;
73
86
  SubagentStart|SubagentStop) AGENT_ID="$(field agent_id "$ID_PATTERN")" ;;
74
87
  SessionStart) SOURCE="$(field source)" ;;
75
88
  esac
@@ -79,9 +92,10 @@ BODY="{\"event\":\"$EVENT\""
79
92
  [ -n "$TOOL_USE_ID" ] && BODY="$BODY,\"toolUseId\":\"$TOOL_USE_ID\""
80
93
  [ -n "$AGENT_ID" ] && BODY="$BODY,\"agentId\":\"$AGENT_ID\""
81
94
  [ -n "$SOURCE" ] && BODY="$BODY,\"source\":\"$SOURCE\""
95
+ [ -n "$TOOL_NAME" ] && BODY="$BODY,\"toolName\":\"$TOOL_NAME\""
82
96
  BODY="$BODY}"
83
97
 
84
- ARGS=(-s -o /dev/null --max-time 2 -X POST "$API_URL/api/agent-hooks"
98
+ ARGS=(-s --max-time 2 -X POST "$API_URL/api/agent-hooks"
85
99
  -H "Content-Type: application/json"
86
100
  -H "User-Agent: crewly-agent-status-hook/1"
87
101
  -H "X-Agent-Session: $SESSION")
@@ -93,5 +107,30 @@ if [ -n "${CREWLY_AGENT_AUTHORIZATION:-}" ]; then
93
107
  ARGS+=(-H "X-Agent-Authorization: b64:$(printf '%s' "$CREWLY_AGENT_AUTHORIZATION" | base64 | tr -d '\n')")
94
108
  fi
95
109
 
96
- curl "${ARGS[@]}" --data "$BODY" >/dev/null 2>&1 || true
110
+ # Only a PostToolUse with a tool name can get a note back; everything else
111
+ # discards the response.
112
+ if [ "$EVENT" != "PostToolUse" ] || [ -z "$TOOL_NAME" ]; then
113
+ curl "${ARGS[@]}" -o /dev/null --data "$BODY" >/dev/null 2>&1 || true
114
+ exit 0
115
+ fi
116
+
117
+ RESPONSE="$(curl "${ARGS[@]}" --data "$BODY" 2>/dev/null || true)"
118
+ [ -z "$RESPONSE" ] && exit 0
119
+ # Print {"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":<note>}}
120
+ # when the response carries a string additionalContext; nothing otherwise.
121
+ if command -v jq >/dev/null 2>&1; then
122
+ printf '%s' "$RESPONSE" | jq -c 'if type == "object" and (.additionalContext | type) == "string" and (.additionalContext | length) > 0 then {hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: .additionalContext}} else empty end' 2>/dev/null || true
123
+ elif command -v node >/dev/null 2>&1; then
124
+ printf '%s' "$RESPONSE" | node -e '
125
+ let s = "";
126
+ process.stdin.on("data", (c) => { s += c; });
127
+ process.stdin.on("end", () => {
128
+ try {
129
+ const o = JSON.parse(s);
130
+ if (o && typeof o.additionalContext === "string" && o.additionalContext.length > 0) {
131
+ process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: o.additionalContext } }));
132
+ }
133
+ } catch { /* not JSON: no note */ }
134
+ });' 2>/dev/null || true
135
+ fi
97
136
  exit 0
@@ -8,7 +8,8 @@
8
8
  ### What You Are NOT
9
9
  - Direct user communicator (route through orchestrator)
10
10
  - Orchestration-level router
11
- - Implementation executor (delegate to your team)
11
+ - Implementation executor (delegate to your team — any member can code, write and research; role is a preference, not a limit)
12
+ - The relay for a member's answer to the owner (delegate owner requests with `--thread <key>`; the member answers in that thread)
12
13
 
13
14
  ### Try-Before-Refuse Protocol
14
15
  Before refusing any task:
@@ -111,12 +111,16 @@ Each project keeps its own backlog in `<project>/.crewly/tickets/` (one markdown
111
111
  When the owner switched the ticket autopilot on for a project, Crewly sends you a `ticket_triage` WorkItem while someone on your team is idle and backlog tickets wait. Decide every ticket it lists, then complete the WorkItem (one line per ticket):
112
112
  - **ready + assign** (`assign-ticket`, or `project-tickets update --status ready` for the next idle member) — at most the stated number of in-progress tickets per member;
113
113
  - **split** into smaller ready tickets and cancel the original with a note;
114
- - **needs the owner** — `project-tickets ask-owner --project <P> --id <ID> --question "<one line>" --option "<choice>" --option "<choice>" --default "<choice or wait>"` (2–3 options; `--sensitive email|publish|deploy|spend` for the boundaries below). The ticket's assignee (or you) posts it as a card in the ticket's Slack thread; you do not message the owner about it;
114
+ - **needs the owner** — `project-tickets ask-owner --project <P> --id <ID> --question "<one line>" --option "<choice>" --option "<choice>" --default "<choice or wait>"` (2–5 options; `--sensitive email|publish|deploy|spend` for the boundaries below). The ticket's assignee (or you) posts it as a card in the ticket's Slack thread; you do not message the owner about it;
115
115
  - **cancel** with a reason.
116
116
 
117
117
  Tickets marked *worker-created — review first* were filed by a team member: check they are wanted before making them ready.
118
118
 
119
- **Who does what.** Delegate by role: give each ticket to the member whose role fits the work. A member shown *stopped* is available — assigning starts them; stopped is never "busy". Take a ticket yourself only for lead-level work (review, decisions, owner communication, cross-team coordination) or when no member fits. A split written in an old ticket ("Owen writes, Nova does the images") is only a hint: decide by current fit and availability, and split a mixed ticket so each part goes to the right role.
119
+ **Who does what.** Role is a preference, not a limit: every member runs the same runtime and can code, write and research. Any member can take any work that needs no special account, tool or permission. Prefer an idle member over doing it yourself; a member shown *stopped* is available too — assigning starts them; stopped is never "busy". Do hands-on work yourself only for lead-level work (review, decisions, owner communication, cross-team coordination), when every member is busy, or when the work truly needs your own judgment. "No member fits" is rare — a role mismatch is not a reason. When it happens, record what is missing (access, tool, permission): `delegate-task --no-member-fits "<what is missing>" --task "<the work>"`. A split written in an old ticket ("Owen writes, Nova does the images") is only a hint: decide by availability, and split a mixed ticket so the parts run in parallel.
120
+
121
+ **Owner requests from Slack.** When the owner asks you something in Slack that a member can do, delegate it with `--thread <key>` (the key from `[SLACK-THREAD:<key>]`). The member answers the owner in that thread itself, under its own name — you do not keep work to keep the owner informed, and you do not relay its result. A short "Sage is on it" from you is enough.
122
+
123
+ **Execution nudge.** If you edit files for a while, Crewly adds a `[CREWLY-NUDGE]` note naming members who can take the work. Delegate, or record why you keep it (`--no-member-fits`).
120
124
 
121
125
  **The autopilot does not lift the approval boundary.** Even with it on, these need the owner's explicit OK: sending email or messages to outside people; publishing content publicly; deploying to production; spending money. Work such a ticket up to a draft or a PR, then `ask-owner … --sensitive <kind>` for the final step (never auto-applied). The answer reaches the asking agent as a `[DECISION …]` message and the mark clears itself; act on it. Withdraw a question that is no longer needed with `ask-owner --clear`.
122
126
 
@@ -157,12 +161,12 @@ When you receive an Objective from the Orchestrator:
157
161
  ```bash
158
162
  bash {{AGENT_SKILLS_PATH}}/core/get-team-status/execute.sh '{}'
159
163
  ```
160
- 4. **Match task to worker** — Consider role, current workload, and past performance. Don't overload a busy worker when an idle one is available.
164
+ 4. **Match task to worker** — Prefer an idle member; role is a preference, not a limit (any member can code, write and research). Don't overload a busy worker when an idle one is available.
161
165
  5. **Delegate immediately** — Don't analyze endlessly. Once you have a clear task and an available worker, delegate. Speed matters.
162
166
 
163
167
  ### Step 3: Task Delegation
164
- 1. Evaluate each worker's role and current workload
165
- 2. Use **delegate-task** to assign tasks to the best-matched workers
168
+ 1. Evaluate each worker's current workload (idle first), then role as a tie-breaker
169
+ 2. Use **delegate-task** to assign tasks to available workers (idle first; pass `--thread <key>` for an owner request)
166
170
  3. **Rule**: Never give the same worker more than 2 concurrent tasks (prevents PTY blocking)
167
171
  4. Include clear acceptance criteria in every delegation
168
172
 
@@ -3,7 +3,7 @@
3
3
  ### What You ARE
4
4
  - **Objective owner**: you carry the team's deliverable from brief to acceptance
5
5
  - **Planner & decomposer**: you turn requests into worker-sized subtasks (Goal + Outcome + Eval)
6
- - **Delegator**: you assign each subtask to the most-suitable available worker — this is your default action
6
+ - **Delegator**: you assign each subtask to an available worker (idle first) — this is your default action. Role is a preference, not a limit: any member can code, write and research
7
7
  - **Unblocker**: you clear obstacles, answer questions, and escalate upward when scope or risk changes
8
8
  - **Verifier**: you review worker output against the Eval criteria before declaring done
9
9
  - **Status surface**: you report progress and outcomes upstream so the owner never has to chase
@@ -11,7 +11,8 @@
11
11
  ### What You Are NOT
12
12
  - **Not a direct user communicator**: route user-facing replies through the orchestrator unless explicitly tasked otherwise
13
13
  - **Not the orchestration-level router**: you own one team's deliverable, not cross-team scheduling
14
- - **Not the default implementer**: implementation is a worker's job — see the Self-Implementation Exception Rule before opening an editor
14
+ - **Not the default implementer**: implementation is a worker's job. Do hands-on work only for lead-level work, when every member is busy, or when it truly needs your judgment — and record "no member fits" with what is missing (`delegate-task --no-member-fits`)
15
+ - **Not the relay for a member's answer**: delegate owner requests with `--thread <key>`; the member answers the owner in that thread
15
16
  - **Not a rubber-stamp reviewer**: an "LGTM" without checking against Eval is a failure mode, not a shortcut
16
17
 
17
18
  ### Try-Before-Refuse Protocol
@@ -97,12 +97,16 @@ Each project keeps its own backlog in `<project>/.crewly/tickets/` (one markdown
97
97
  When the owner switched the ticket autopilot on for a project, Crewly sends you a `ticket_triage` WorkItem while someone on your team is idle and backlog tickets wait. Decide every ticket it lists, then complete the WorkItem (one line per ticket):
98
98
  - **ready + assign** (`assign-ticket`, or `project-tickets update --status ready` for the next idle member) — at most the stated number of in-progress tickets per member;
99
99
  - **split** into smaller ready tickets and cancel the original with a note;
100
- - **needs the owner** — `project-tickets ask-owner --project <P> --id <ID> --question "<one line>" --option "<choice>" --option "<choice>" --default "<choice or wait>"` (2–3 options; `--sensitive email|publish|deploy|spend` for the boundaries below). The ticket's assignee (or you) posts it as a card in the ticket's Slack thread; you do not message the owner about it;
100
+ - **needs the owner** — `project-tickets ask-owner --project <P> --id <ID> --question "<one line>" --option "<choice>" --option "<choice>" --default "<choice or wait>"` (2–5 options; `--sensitive email|publish|deploy|spend` for the boundaries below). The ticket's assignee (or you) posts it as a card in the ticket's Slack thread; you do not message the owner about it;
101
101
  - **cancel** with a reason.
102
102
 
103
103
  Tickets marked *worker-created — review first* were filed by a team member: check they are wanted before making them ready.
104
104
 
105
- **Who does what.** Delegate by role: give each ticket to the member whose role fits the work. A member shown *stopped* is available — assigning starts them; stopped is never "busy". Take a ticket yourself only for lead-level work (review, decisions, owner communication, cross-team coordination) or when no member fits. A split written in an old ticket ("Owen writes, Nova does the images") is only a hint: decide by current fit and availability, and split a mixed ticket so each part goes to the right role.
105
+ **Who does what.** Role is a preference, not a limit: every member runs the same runtime and can code, write and research. Any member can take any work that needs no special account, tool or permission. Prefer an idle member over doing it yourself; a member shown *stopped* is available too — assigning starts them; stopped is never "busy". Do hands-on work yourself only for lead-level work (review, decisions, owner communication, cross-team coordination), when every member is busy, or when the work truly needs your own judgment. "No member fits" is rare — a role mismatch is not a reason. When it happens, record what is missing (access, tool, permission): `delegate-task --no-member-fits "<what is missing>" --task "<the work>"`. A split written in an old ticket ("Owen writes, Nova does the images") is only a hint: decide by availability, and split a mixed ticket so the parts run in parallel.
106
+
107
+ **Owner requests from Slack.** When the owner asks you something in Slack that a member can do, delegate it with `--thread <key>` (the key from `[SLACK-THREAD:<key>]`). The member answers the owner in that thread itself, under its own name — you do not keep work to keep the owner informed, and you do not relay its result. A short "Sage is on it" from you is enough.
108
+
109
+ **Execution nudge.** If you edit files for a while, Crewly adds a `[CREWLY-NUDGE]` note naming members who can take the work. Delegate, or record why you keep it (`--no-member-fits`).
106
110
 
107
111
  **The autopilot does not lift the approval boundary.** Even with it on, these need the owner's explicit OK: sending email or messages to outside people; publishing content publicly; deploying to production; spending money. Work such a ticket up to a draft or a PR, then `ask-owner … --sensitive <kind>` for the final step (never auto-applied). The answer reaches the asking agent as a `[DECISION …]` message and the mark clears itself; act on it. Withdraw a question that is no longer needed with `ask-owner --clear`.
108
112
 
@@ -126,12 +130,12 @@ When you receive an Objective from the Orchestrator:
126
130
  ```bash
127
131
  bash {{AGENT_SKILLS_PATH}}/core/get-team-status/execute.sh '{}'
128
132
  ```
129
- 4. **Match task to worker** — Consider role, current workload, and past performance. Don't overload a busy worker when an idle one is available.
133
+ 4. **Match task to worker** — Prefer an idle member; role is a preference, not a limit (any member can code, write and research). Don't overload a busy worker when an idle one is available.
130
134
  5. **Delegate immediately** — Don't analyze endlessly. Once you have a clear task and an available worker, delegate. Speed matters.
131
135
 
132
136
  #### Step 3: Task Delegation
133
- 1. Evaluate each worker's role and current workload
134
- 2. Use **delegate-task** to assign tasks to the best-matched workers
137
+ 1. Evaluate each worker's current workload (idle first), then role as a tie-breaker
138
+ 2. Use **delegate-task** to assign tasks to available workers (idle first; pass `--thread <key>` for an owner request)
135
139
  3. **Rule**: Never give the same worker more than 2 concurrent tasks (prevents PTY blocking)
136
140
  4. Include clear acceptance criteria in every delegation
137
141
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: Ask Owner
3
- description: Ask the owner ONE decision with 2–3 options, a default and a deadline. Your own Slack bot posts it as a card with buttons where your work is (the ticket's thread, or your team channel); the owner taps an answer and you get a [DECISION …] message. Never ask the owner open-ended questions in chat.
3
+ description: Ask the owner ONE decision with 2–5 options, a default and a deadline. Your own Slack bot posts it as a card with buttons where your work is (the ticket's thread, or your team channel); the owner taps an answer and you get a [DECISION …] message. Never ask the owner open-ended questions in chat.
4
4
  version: 1.0.0
5
5
  category: communication
6
6
  skillType: claude-skill
@@ -52,12 +52,19 @@ bash execute.sh --question "Send the partner email on Monday?" \
52
52
  | Flag | Rule |
53
53
  |---|---|
54
54
  | `--question` | One line (8–280 characters). Name the decision, not "thoughts?" |
55
- | `--option` | 2–3 times. `"Label"` or `"Label — detail"`. The label is the button (≤ 40 characters). |
55
+ | `--option` | 2–5 times, one per alternative. `"Label"` or `"Label — detail"`. The label is the button (short, ≤ 40 characters). The owner picks exactly one; the answer comes back as that label. |
56
56
  | `--default` | The option you will take if there is no answer by the deadline, or `wait` |
57
57
  | `--deadline` | Optional, ISO (`2026-10-02T12:00`). Default: tomorrow 12:00 |
58
58
  | `--ticket APP-12 --project P` | Optional. Ask about a ticket; the card goes in the ticket's thread, asked by its assignee |
59
59
  | `--sensitive email\|publish\|deploy\|spend` | Required for messages to outside people, public publishing, prod deploys, spending money. These are never auto-applied: the owner is re-asked once, then the question is parked |
60
60
 
61
+ **"A or B?" is ONE card with options.** When the answers exclude each other (change it now / try the
62
+ current version first), post a single ask with one `--option` per alternative. Never post two Yes/No
63
+ cards for the two halves: the owner can tap Yes on both and you are left guessing. A second Yes/No
64
+ ask on the same ticket within 60 seconds that reads as an either/or (starts with 还是 / 或者 / "or ",
65
+ or the first one ends in an either/or) is rejected with an error telling you to post one card with
66
+ options. Use plain Yes/No only for a single question with one possible action.
67
+
61
68
  What happens:
62
69
 
63
70
  - The card is posted by your own Slack bot:
@@ -1,5 +1,5 @@
1
1
  #!/bin/bash
2
- # ask-owner — one structured owner decision (question + 2–3 options + default
2
+ # ask-owner — one structured owner decision (question + 2–5 options + default
3
3
  # + deadline), posted as a Slack card by the asking agent's own bot.
4
4
  # See specs/2026-10-01-decision-cards.md.
5
5
  set -euo pipefail
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: Project Tickets
3
- description: A project's own backlog — list, read, create, update, claim, release, assign, link, log and ask-owner (structured decision cards) on project tickets (markdown files in <project>/.crewly/tickets/, tracked in git), plus the per-project ticket autopilot switch, its stats and the daily retro.
3
+ description: A project's own backlog — list, read, create, update, claim, release, assign, link, log and ask-owner (structured decision cards) on project tickets (markdown files in <project>/.crewly/tickets/, tracked in git), plus the per-project ticket autopilot switch, its speed mode, its stats, the daily retro and the self-review.
4
4
  version: 1.1.0
5
5
  category: task-management
6
6
  skillType: claude-skill
@@ -102,6 +102,11 @@ bash execute.sh create --project P --title "Export the report as CSV" \
102
102
  (orchestrator / team lead): create it with `--source request:<TKT id>` when
103
103
  it came in as a harness ticket, and tell the owner the new ticket id. Do not
104
104
  start working on it unless the owner asked for that too.
105
+ - **Tickets you open for a goal replan** must name the goal metric they move
106
+ and the expected effect: `--metric "<goal metric> → <expected effect>"`
107
+ (e.g. `--metric "weekly /feed visitors → +150"`). It is written as the
108
+ description's first line (`Metric: …`). Without it the create is refused
109
+ with a message saying so — add the metric and create it again.
105
110
 
106
111
  ## Change a ticket
107
112
 
@@ -129,7 +134,7 @@ without a WorkItem). Team leads also have the `assign-ticket` skill.
129
134
 
130
135
  A **stopped** member is available: assigning starts it for the ticket (the
131
136
  answer's `wake` says `started`, or which gate refused it — then it picks the
132
- ticket up on its next start). Delegate by role; keep only lead-level work.
137
+ ticket up on its next start). Role is a preference, not a limit: prefer an idle member; keep only lead-level work.
133
138
 
134
139
  ## Link work already in flight (orchestrator / team lead)
135
140
 
@@ -158,7 +163,7 @@ bash execute.sh ask-owner --project P --id APP-12 \
158
163
  bash execute.sh ask-owner --project P --id APP-12 --clear --note "resolved in the review call"
159
164
  ```
160
165
 
161
- ONE question with 2–3 options and a default (an option, or `wait`). The deadline defaults to
166
+ ONE question with 2–5 options and a default (an option, or `wait`). The deadline defaults to
162
167
  tomorrow at 12:00. The ticket's assignee, else the lead, posts the question as a card in the
163
168
  ticket's Slack thread, using its own bot. The ticket gets `needs-owner` until the question is
164
169
  answered.
@@ -179,6 +184,7 @@ bash execute.sh autopilot --project P # show settings + st
179
184
  bash execute.sh autopilot --project P --on # switch on (the owner said so)
180
185
  bash execute.sh autopilot --project P --on --daily-budget 20M --max-in-flight 1 # budget in tokens
181
186
  bash execute.sh autopilot --project P --driver <lead session> # or --driver default
187
+ bash execute.sh autopilot --project P --speed rush # rush | normal | chill | default
182
188
  bash execute.sh autopilot --project P --off
183
189
  ```
184
190
 
@@ -195,15 +201,41 @@ while an autopilot experiment on the project is running).
195
201
 
196
202
  When the project has a goal (its goals log or an active project OKR) but
197
203
  nothing is left to triage and someone on the team is idle, the team lead gets
198
- ONE `goal_replan` WorkItem: the goal, the tickets closed in the last 7 days,
199
- any open experiment card, and the ask "open the next tickets toward this goal,
200
- or say why there are none". The lead opens the tickets; the autopilot never
201
- makes them ready or starts work. At most `--replans-per-day <0-5>` a day
202
- (default 1; 0 = off), within the same daily budget and in-progress cap. A
203
- replan still open after `--replan-ttl-hours` (default 4) is expired. After a
204
- replan that opened no tickets the autopilot backs off (skips 2 days, then 4,
205
- then 7) until a new goal / OKR is set or a ticket is created. Only goals-log
206
- entries from the last 30 days count as an active goal.
204
+ ONE `goal_replan` WorkItem: the goal, the lead's last self-review, the
205
+ tickets closed in the last 7 days, any open experiment card, and the ask
206
+ "open the next tickets toward this goal, or say why there are none". The lead
207
+ opens the tickets (each with `--metric`); the autopilot never makes them ready
208
+ or starts work. A replan still open after `--replan-ttl-hours` (default 4) is
209
+ expired. Only goals-log entries from the last 30 days count as an active goal.
210
+
211
+ **Speed** (`--speed`, the owner can also DM the orc "set CE to rush" /
212
+ "CE 切到 chill"):
213
+
214
+ | | Rush | Normal (default) | Chill |
215
+ |---|---|---|---|
216
+ | Replan when nothing is left | ≥ 1 h apart, ≤ 12 a day | ≥ 3 h apart, ≤ 4 a day | ≤ 1 a day |
217
+ | Self-review | hourly | daily | weekly |
218
+ | After a replan that opened nothing | retry in 1 h | next day | next week |
219
+ | Daily budget when none is set | 50M | 20M | 8M |
220
+
221
+ An explicit `--daily-budget` (and boosts) and `--replans-per-day <0-12>`
222
+ (0 = off) override the speed's. A new goal / OKR or a new ticket lifts the
223
+ wait after an empty replan.
224
+
225
+ **Self-review**: at the speed's cadence the lead gets one short
226
+ `autopilot_self_review` WorkItem (skipped when nothing changed and nobody is
227
+ idle, never for a paused team or over the budget). File it, then complete the
228
+ WorkItem:
229
+
230
+ ```bash
231
+ bash execute.sh self-review --project P --gap "620 of 1,000 weekly visitors" \
232
+ --moved "CE-41 feed cards (+90)" --next-bet "two feed cards a day on the top search queries"
233
+ ```
234
+
235
+ The status (`autopilot --project P`) and the evening digest show the latest
236
+ self-review and why the autopilot stopped, if it did: `paused` (every team
237
+ paused), `budget_reached`, `system_error` (failed work / a wake that never
238
+ landed), `waiting_on_owner`, `no_ideas` (the last replan opened nothing).
207
239
 
208
240
  ## Autopilot numbers and retro (owner / orchestrator / team lead)
209
241
 
@@ -10,6 +10,7 @@
10
10
  # bash execute.sh create --project P --title "…" [--description "…"] [--acceptance "…" …]
11
11
  # [--priority P1] [--labels a,b] [--team <teamId>] [--status ready]
12
12
  # [--source request:TKT-012] [--request-id <id>] [--owner-review]
13
+ # [--metric "<goal metric> → <expected effect>"]
13
14
  # bash execute.sh update --project P --id APP-12 [--title …] [--priority …] [--labels …]
14
15
  # [--description …] [--acceptance "…" …] [--status …] [--note "…"]
15
16
  # bash execute.sh claim --project P --id APP-12
@@ -23,11 +24,12 @@
23
24
  # bash execute.sh ask-owner --project P --id APP-12 --clear [--note "answer"]
24
25
  # bash execute.sh autopilot --project P [--on|--off] [--driver <session>|--driver default]
25
26
  # [--daily-budget <tokens, e.g. 20M>] [--max-in-flight <n>] (owner / orchestrator)
26
- # [--retro on|off|default] [--replans-per-day <0-5>|default]
27
- # [--replan-ttl-hours <1-48>|default]
27
+ # [--retro on|off|default] [--replans-per-day <0-12>|default]
28
+ # [--replan-ttl-hours <1-48>|default] [--speed rush|normal|chill|default]
28
29
  # bash execute.sh stats --project P [--days 14] [--label feed] (owner / orc / lead)
29
30
  # bash execute.sh runs --project P [--days 7] [--label feed] (owner / orc / lead)
30
31
  # bash execute.sh retro --project P --day YYYY-MM-DD --summary "…" [--problem "class|title|detail|evidence" …]
32
+ # bash execute.sh self-review --project P --gap "…" [--moved "…"] --next-bet "…"
31
33
  # bash execute.sh '{"action":"create","project":"P","title":"…"}'
32
34
  #
33
35
  # P = project id, name, or absolute path.
@@ -44,6 +46,10 @@ Usage:
44
46
  bash execute.sh create --project P --title "…" [--description "…"] [--acceptance "…" …]
45
47
  [--priority P0-P3] [--labels a,b] [--team <teamId>] [--status backlog|ready]
46
48
  [--source request:TKT-012] [--request-id <id>] [--owner-review]
49
+ [--metric "<goal metric> → <expected effect>"]
50
+ --metric: the goal metric the ticket moves and the
51
+ expected effect. Required for tickets you open during
52
+ a goal replan (refused without it)
47
53
  bash execute.sh update --project P --id APP-12 [--title …] [--priority …] [--labels …]
48
54
  [--description …] [--acceptance "…" …] [--status …] [--note "…"]
49
55
  bash execute.sh claim --project P --id APP-12 Take a ready ticket (creates your WorkItem)
@@ -56,7 +62,7 @@ Usage:
56
62
  flight (a live WorkItem) to this ticket
57
63
  bash execute.sh ask-owner --project P --id APP-12 --question "…" --option "A" --option "B — detail"
58
64
  --default "B"|wait [--deadline ISO] [--sensitive email|publish|deploy|spend]
59
- Ask the owner ONE question with 2–3 options. The
65
+ Ask the owner ONE question with 2–5 options. The
60
66
  ticket's assignee (else its lead) posts it as a card
61
67
  in the ticket's Slack thread; the answer comes back
62
68
  to that agent as a [DECISION …] message
@@ -64,15 +70,17 @@ Usage:
64
70
  The owner answered: remove the needs-owner mark
65
71
  bash execute.sh autopilot --project P [--on|--off] [--driver <session>|default]
66
72
  [--daily-budget <tokens, e.g. 20M>] [--max-in-flight <n>] [--retro on|off|default]
67
- [--replans-per-day <0-5>|default] [--replan-ttl-hours <1-48>|default]
73
+ [--replans-per-day <0-12>|default] [--replan-ttl-hours <1-48>|default]
74
+ [--speed rush|normal|chill|default]
68
75
  Owner / orchestrator: show or change the ticket
69
76
  autopilot (no flags = show). --retro: the lead's
70
77
  daily retro (default: on while an autopilot
71
- experiment runs). --replans-per-day: how often a
72
- day the lead is woken to plan toward the goal when
73
- nothing is left to triage (default 1, 0 = off);
74
- --replan-ttl-hours: a replan still open after this
75
- long is expired (default 4)
78
+ experiment runs). --speed: Rush (replan ≥1 h apart,
79
+ ≤12/day, hourly self-review), Normal (≥3 h, ≤4/day,
80
+ daily; the default) or Chill (≤1/day, weekly).
81
+ --replans-per-day: a hard daily cap that overrides
82
+ the speed's (0 = off); --replan-ttl-hours: a replan
83
+ still open after this long is expired (default 4)
76
84
  bash execute.sh stats --project P [--days 14] [--label feed]
77
85
  Autopilot numbers per day: tickets triaged /
78
86
  started / done / verified / sent back / stalled,
@@ -85,6 +93,9 @@ Usage:
85
93
  Team lead: file the daily autopilot retro.
86
94
  class = agent_judgment | missing_skill |
87
95
  harness_gap | owner_dependency
96
+ bash execute.sh self-review --project P --gap "…" [--moved "…"] --next-bet "…"
97
+ Team lead: file the autopilot self-review (gap to
98
+ the target, what moved it, the next bet)
88
99
 
89
100
  P = project id, name or absolute path. Workers' new tickets start in backlog;
90
101
  the owner, the orchestrator or a team lead makes them ready.
@@ -97,6 +108,7 @@ ACCEPTANCE_JSON="null"
97
108
  HAS_DESCRIPTION=0
98
109
  QUESTION=""; CLEAR=""; OPTIONS_JSON="[]"; DEFAULT_OPT=""; DEADLINE=""; SENSITIVE=""; AP_ENABLED=""; AP_DRIVER=""; AP_BUDGET=""; AP_MAX=""
99
110
  AP_RETRO=""; AP_REPLANS=""; AP_REPLAN_TTL=""; DAYS=""; LABEL=""; DAY=""; SUMMARY=""; PROBLEMS_JSON="[]"
111
+ METRIC=""; AP_SPEED=""; GAP=""; MOVED=""; NEXT_BET=""
100
112
 
101
113
  if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
102
114
  J="$1"; shift
@@ -137,6 +149,11 @@ if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
137
149
  DAY=$(printf '%s' "$J" | jq -r '.day // empty')
138
150
  SUMMARY=$(printf '%s' "$J" | jq -r '.summary // empty')
139
151
  PROBLEMS_JSON=$(printf '%s' "$J" | jq -c 'if (.problems|type) == "array" then .problems else [] end')
152
+ METRIC=$(printf '%s' "$J" | jq -r '.metric // empty')
153
+ AP_SPEED=$(printf '%s' "$J" | jq -r '.speedMode // .speed // empty')
154
+ GAP=$(printf '%s' "$J" | jq -r '.gap // empty')
155
+ MOVED=$(printf '%s' "$J" | jq -r '.moved // empty')
156
+ NEXT_BET=$(printf '%s' "$J" | jq -r '.nextBet // empty')
140
157
  fi
141
158
  if [[ -z "$ACTION" && $# -gt 0 && ${1:0:1} != '-' ]]; then ACTION="$1"; shift; fi
142
159
 
@@ -184,13 +201,19 @@ while [[ $# -gt 0 ]]; do
184
201
  --summary) [ $# -ge 2 ] || error_exit "--summary requires a value"; SUMMARY="$2"; shift 2 ;;
185
202
  --problem) [ $# -ge 2 ] || error_exit "--problem requires \"class|title|detail|evidence\""
186
203
  PROBLEMS_JSON=$(jq -c --arg p "$2" '. + [($p | split("|")) as $f | {class: ($f[0] // "" | gsub("^\\s+|\\s+$"; "")), title: ($f[1] // "")} + (if ($f[2] // "") != "" then {detail: $f[2]} else {} end) + (if ($f[3:] | join("|")) != "" then {evidence: ($f[3:] | join("|"))} else {} end)]' <<<"$PROBLEMS_JSON"); shift 2 ;;
204
+ --metric) [ $# -ge 2 ] || error_exit "--metric requires \"<goal metric> → <expected effect>\""; METRIC="$2"; shift 2 ;;
205
+ --speed|--speed-mode)
206
+ [ $# -ge 2 ] || error_exit "--speed requires rush, normal, chill or default"; AP_SPEED="$2"; shift 2 ;;
207
+ --gap) [ $# -ge 2 ] || error_exit "--gap requires a value"; GAP="$2"; shift 2 ;;
208
+ --moved) [ $# -ge 2 ] || error_exit "--moved requires a value"; MOVED="$2"; shift 2 ;;
209
+ --next-bet) [ $# -ge 2 ] || error_exit "--next-bet requires a value"; NEXT_BET="$2"; shift 2 ;;
187
210
  --full) shift ;;
188
211
  --help|-h) print_usage; exit 0 ;;
189
212
  *) error_exit "Unknown option: $1" ;;
190
213
  esac
191
214
  done
192
215
 
193
- [ -n "$ACTION" ] || { print_usage >&2; error_exit "Missing action: list | show | create | update | claim | release | assign | log | link | ask-owner | autopilot | stats | runs | retro"; }
216
+ [ -n "$ACTION" ] || { print_usage >&2; error_exit "Missing action: list | show | create | update | claim | release | assign | log | link | ask-owner | autopilot | stats | runs | retro | self-review"; }
194
217
 
195
218
  # URL-encode a path segment (project paths contain slashes).
196
219
  enc() { jq -rn --arg v "$1" '$v|@uri'; }
@@ -217,7 +240,9 @@ case "$ACTION" in
217
240
  BODY=$(jq -n --arg title "$TITLE" --arg description "$DESCRIPTION" --argjson hasDescription "$HAS_DESCRIPTION" \
218
241
  --arg priority "$PRIORITY" --arg labels "$LABELS" --arg team "$TEAM" --arg status "$STATUS" \
219
242
  --arg source "$SOURCE" --arg requestId "$REQUEST_ID" --arg ownerReview "$OWNER_REVIEW" --argjson acceptance "$ACCEPTANCE_JSON" \
243
+ --arg metric "$METRIC" \
220
244
  '{title: $title}
245
+ + (if $metric != "" then {metric: $metric} else {} end)
221
246
  + (if $hasDescription == 1 then {description: $description} else {} end)
222
247
  + (if $acceptance != null then {acceptance: $acceptance} else {} end)
223
248
  + (if $priority != "" then {priority: $priority} else {} end)
@@ -294,11 +319,12 @@ case "$ACTION" in
294
319
  ;;
295
320
  autopilot)
296
321
  require_param "project" "$PROJECT"
297
- if [ -z "$AP_ENABLED$AP_DRIVER$AP_BUDGET$AP_MAX$AP_RETRO$AP_REPLANS$AP_REPLAN_TTL" ]; then
322
+ if [ -z "$AP_ENABLED$AP_DRIVER$AP_BUDGET$AP_MAX$AP_RETRO$AP_REPLANS$AP_REPLAN_TTL$AP_SPEED" ]; then
298
323
  api_call GET "/project-ticket-autopilot/$(enc "$PROJECT")" | jq '{success, autopilot: .data}'
299
324
  else
300
- BODY=$(jq -n --arg enabled "$AP_ENABLED" --arg driver "$AP_DRIVER" --arg budget "$AP_BUDGET" --arg max "$AP_MAX" --arg retro "$AP_RETRO" --arg replans "$AP_REPLANS" --arg ttl "$AP_REPLAN_TTL" \
325
+ BODY=$(jq -n --arg enabled "$AP_ENABLED" --arg driver "$AP_DRIVER" --arg budget "$AP_BUDGET" --arg max "$AP_MAX" --arg retro "$AP_RETRO" --arg replans "$AP_REPLANS" --arg ttl "$AP_REPLAN_TTL" --arg speed "$AP_SPEED" \
301
326
  '{}
327
+ + (if $speed == "default" then {speedMode: null} elif $speed != "" then {speedMode: $speed} else {} end)
302
328
  + (if $enabled != "" then {enabled: ($enabled == "true")} else {} end)
303
329
  + (if $retro == "default" then {retro: null} elif $retro != "" then {retro: $retro} else {} end)
304
330
  + (if $replans == "default" then {replansPerDay: null} elif $replans != "" then {replansPerDay: ($replans | tonumber? // $replans)} else {} end)
@@ -329,7 +355,14 @@ case "$ACTION" in
329
355
  BODY=$(jq -n --arg d "$DAY" --arg s "$SUMMARY" --argjson p "$PROBLEMS_JSON" '{day: $d, summary: $s, problems: $p}')
330
356
  api_call POST "/project-ticket-autopilot/$(enc "$PROJECT")/retro" "$BODY" | jq '{success, retro: .data, error}'
331
357
  ;;
358
+ self-review)
359
+ require_param "project" "$PROJECT"
360
+ require_param "gap (--gap)" "$GAP"
361
+ require_param "next bet (--next-bet)" "$NEXT_BET"
362
+ BODY=$(jq -n --arg g "$GAP" --arg m "$MOVED" --arg n "$NEXT_BET" '{gap: $g, nextBet: $n} + (if $m != "" then {moved: $m} else {} end)')
363
+ api_call POST "/project-ticket-autopilot/$(enc "$PROJECT")/self-review" "$BODY" | jq '{success, selfReview: .data, error}'
364
+ ;;
332
365
  *)
333
- error_exit "Unknown action: $ACTION (use list | show | create | update | claim | release | assign | log | link | ask-owner | autopilot | stats | runs | retro)"
366
+ error_exit "Unknown action: $ACTION (use list | show | create | update | claim | release | assign | log | link | ask-owner | autopilot | stats | runs | retro | self-review)"
334
367
  ;;
335
368
  esac
@@ -88,6 +88,12 @@ run create --project p1 --title "Export CSV" --description "why" --acceptance "h
88
88
  check "create: path" "$(last .path)" "/api/project-tickets/p1"
89
89
  check "create: body" "$(last '.body | tostring')" '{"title":"Export CSV","description":"why","acceptance":["header row","opens in Excel"],"priority":"P1","labels":["ui","export"],"source":"request:TKT-012"}'
90
90
 
91
+ # --- create with the goal metric (required for goal-replan tickets) ---
92
+ run create --project p1 --title "Feed card 3" --metric "weekly /feed visitors → +150" --status ready >/dev/null
93
+ check "create metric: body" "$(last '.body | tostring')" '{"title":"Feed card 3","metric":"weekly /feed visitors → +150","status":"ready"}'
94
+ run '{"action":"create","project":"p1","title":"From JSON metric","metric":"returning visitors → +5%"}' >/dev/null
95
+ check "create json metric: body" "$(last '.body | tostring')" '{"title":"From JSON metric","metric":"returning visitors → +5%"}'
96
+
91
97
  # --- create from JSON input ---
92
98
  run '{"action":"create","project":"p1","title":"From JSON","status":"ready","ownerReview":true}' >/dev/null
93
99
  check "create json: body" "$(last '.body | tostring')" '{"title":"From JSON","status":"ready","ownerReview":true}'
@@ -165,6 +171,19 @@ check "autopilot replans default: body" "$(last '.body | tostring')" '{"replansP
165
171
  run autopilot --project p1 --replan-ttl-hours 6 >/dev/null
166
172
  check "autopilot replan ttl: body" "$(last '.body | tostring')" '{"replanTtlHours":6}'
167
173
 
174
+ run autopilot --project p1 --speed rush >/dev/null
175
+ check "autopilot speed: body" "$(last '.body | tostring')" '{"speedMode":"rush"}'
176
+ run autopilot --project p1 --speed default >/dev/null
177
+ check "autopilot speed default: body" "$(last '.body | tostring')" '{"speedMode":null}'
178
+ run '{"action":"autopilot","project":"p1","speedMode":"chill"}' >/dev/null
179
+ check "autopilot speed json: body" "$(last '.body | tostring')" '{"speedMode":"chill"}'
180
+
181
+ # --- self-review (specs/2026-10-04-autopilot-speed-modes.md) ---
182
+ run self-review --project p1 --gap "620 of 1,000 weekly visitors" --moved "CE-41 feed cards" --next-bet "two cards a day on the top queries" >/dev/null
183
+ check "self-review: path" "$(last .path)" "/api/project-ticket-autopilot/p1/self-review"
184
+ check "self-review: body" "$(last '.body | tostring')" '{"gap":"620 of 1,000 weekly visitors","nextBet":"two cards a day on the top queries","moved":"CE-41 feed cards"}'
185
+ check "self-review: missing next bet" "$(run_err self-review --project p1 --gap x | grep -c 'next bet')" "1"
186
+
168
187
  # --- stats / runs / retro (specs/2026-10-03-autopilot-experiments.md) ---
169
188
  OUT=$(run stats --project p1 --days 7 --label feed)
170
189
  check "stats: path" "$(last .path)" "/api/project-ticket-autopilot/p1/stats?days=7&label=feed"
@@ -49,6 +49,46 @@ Assigns a task to a worker within the Team Leader's subordinate scope. Validates
49
49
  | `--from` | `fromSession` | No | Delegating TL's session name (for monitoring) |
50
50
  | `--request-id` / `-R` | `requestId` | No | Ticket this work is for: the id (or `TKT-123`) from the `[TICKET:TKT-123 <id>]` line of the message you are acting on. Omit it and the task is still linked when your current turn has exactly one ticket |
51
51
  | `--ticket` | `ticket` | No | Project ticket this work is for (e.g. `APP-12`). Without it a ticket is created for you when the target works on a project — see Project tickets |
52
+ | `--thread` | `thread` | No | Owner request from Slack: the key from the `[SLACK-THREAD:<key>]` of the owner's message. The member answers the owner in that thread itself — see Owner requests |
53
+ | `--no-member-fits` | `noMemberFits` | No | You keep the work: record what is missing (access, tool, permission, everyone busy). Needs `--task`; nothing is delegated — see No member fits |
54
+ | `--work-item` | `workItemId` | No | With `--no-member-fits`: the WorkItem you keep (gets a `[NO-MEMBER-FITS]` note) |
55
+
56
+ ## Who to delegate to
57
+
58
+ Role is a preference, not a limit: every member runs the same runtime and can
59
+ code, write and research. Any member can take any work that needs no special
60
+ account, tool or permission. Prefer an idle (or stopped) member over doing it
61
+ yourself. Do hands-on work yourself only for lead-level work (review,
62
+ decisions, owner communication, cross-team coordination), when every member
63
+ is busy, or when the work truly needs your own judgment.
64
+
65
+ ## Owner requests (`--thread`)
66
+
67
+ When the owner asked in Slack, pass the thread key from the owner message's
68
+ `[SLACK-THREAD:<key>]`:
69
+
70
+ ```bash
71
+ bash execute.sh --to sage-session --task "Goal: … Outcome: … Eval: …" --thread C0THINK:1790000000.000100
72
+ ```
73
+
74
+ The WorkItem carries the thread and the member is told to post its progress
75
+ and result there itself (`reply --work-item <id>`), under its own name. You do
76
+ not relay its answer; the room's reply gate never holds the member's post.
77
+ Without `--thread` the thread of your last owner message is used. (A thread
78
+ in your DM with the owner: the member's bot cannot post there, so it answers
79
+ in its own DM with the owner, opened with `Re: <task>`.)
80
+
81
+ ## No member fits (`--no-member-fits`)
82
+
83
+ Rare. When you keep work yourself, record why — what is missing, not a role
84
+ mismatch — so the owner sees missing roles in the daily report:
85
+
86
+ ```bash
87
+ bash execute.sh --no-member-fits "needs the owner's Stripe login" --task "Update billing settings" [--work-item <id>] [--ticket CE-7]
88
+ ```
89
+
90
+ Nothing is delivered. With `--work-item` the WorkItem gets a
91
+ `[NO-MEMBER-FITS] <reason>` note.
52
92
 
53
93
  ## Project tickets
54
94