crewly 1.20.319 → 1.20.320

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 (147) hide show
  1. package/config/hooks/destructive-git-guard/guard.mjs +210 -0
  2. package/config/hooks/install-hooks.sh +6 -0
  3. package/config/hooks/pre-commit +29 -0
  4. package/config/skills/_conventions/skill-lint.allowlist.json +127 -0
  5. package/config/skills/_conventions/skill-lint.ts +66 -0
  6. package/config/skills/agent/core/publish-app/SKILL.md +30 -2
  7. package/config/skills/agent/core/publish-app/execute.sh +21 -3
  8. package/config/skills/agent/core/publish-app/execute.test.sh +9 -0
  9. package/config/skills/agent/core/report-status/SKILL.md +2 -0
  10. package/config/skills/team-leader/decompose-goal/SKILL.md +20 -6
  11. package/config/skills/team-leader/decompose-goal/execute.sh +66 -5
  12. package/config/skills/team-leader/decompose-goal/execute.test.sh +92 -0
  13. package/config/sops/common/dev-process-tiers.md +1 -1
  14. package/config/sops/common/writing-for-agents.md +45 -0
  15. package/config/sops/developer/debugging.md +53 -0
  16. package/config/sops/developer/git-workflow.md +4 -0
  17. package/config/sops/developer/testing-requirements.md +14 -0
  18. package/dist/backend/backend/src/constants.d.ts +79 -1
  19. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  20. package/dist/backend/backend/src/constants.js +80 -0
  21. package/dist/backend/backend/src/constants.js.map +1 -1
  22. package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts +4 -0
  23. package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts.map +1 -1
  24. package/dist/backend/backend/src/controllers/apps/apps.controller.js +9 -0
  25. package/dist/backend/backend/src/controllers/apps/apps.controller.js.map +1 -1
  26. package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts +2 -0
  27. package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts.map +1 -1
  28. package/dist/backend/backend/src/controllers/apps/apps.routes.js +5 -1
  29. package/dist/backend/backend/src/controllers/apps/apps.routes.js.map +1 -1
  30. package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
  31. package/dist/backend/backend/src/controllers/slack/slack.controller.js +11 -0
  32. package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
  33. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +9 -0
  34. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  35. package/dist/backend/backend/src/services/agent/agent-registration.service.js +41 -1
  36. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  37. package/dist/backend/backend/src/services/agent/control-plane-guard.service.d.ts +4 -1
  38. package/dist/backend/backend/src/services/agent/control-plane-guard.service.d.ts.map +1 -1
  39. package/dist/backend/backend/src/services/agent/control-plane-guard.service.js +11 -2
  40. package/dist/backend/backend/src/services/agent/control-plane-guard.service.js.map +1 -1
  41. package/dist/backend/backend/src/services/agent/destructive-git-guard.service.d.ts +21 -0
  42. package/dist/backend/backend/src/services/agent/destructive-git-guard.service.d.ts.map +1 -0
  43. package/dist/backend/backend/src/services/agent/destructive-git-guard.service.js +40 -0
  44. package/dist/backend/backend/src/services/agent/destructive-git-guard.service.js.map +1 -0
  45. package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts.map +1 -1
  46. package/dist/backend/backend/src/services/agent/follow-through.wiring.js +42 -14
  47. package/dist/backend/backend/src/services/agent/follow-through.wiring.js.map +1 -1
  48. package/dist/backend/backend/src/services/agent/fresh-task-conversation.service.d.ts.map +1 -1
  49. package/dist/backend/backend/src/services/agent/fresh-task-conversation.service.js +5 -1
  50. package/dist/backend/backend/src/services/agent/fresh-task-conversation.service.js.map +1 -1
  51. package/dist/backend/backend/src/services/agent/handover-pointers.d.ts +70 -0
  52. package/dist/backend/backend/src/services/agent/handover-pointers.d.ts.map +1 -0
  53. package/dist/backend/backend/src/services/agent/handover-pointers.js +122 -0
  54. package/dist/backend/backend/src/services/agent/handover-pointers.js.map +1 -0
  55. package/dist/backend/backend/src/services/agent/runtime-session-recovery.d.ts.map +1 -1
  56. package/dist/backend/backend/src/services/agent/runtime-session-recovery.js +6 -3
  57. package/dist/backend/backend/src/services/agent/runtime-session-recovery.js.map +1 -1
  58. package/dist/backend/backend/src/services/apps/apps.service.d.ts +40 -0
  59. package/dist/backend/backend/src/services/apps/apps.service.d.ts.map +1 -1
  60. package/dist/backend/backend/src/services/apps/apps.service.js +54 -0
  61. package/dist/backend/backend/src/services/apps/apps.service.js.map +1 -1
  62. package/dist/backend/backend/src/services/decisions/decision-routing.d.ts +4 -1
  63. package/dist/backend/backend/src/services/decisions/decision-routing.d.ts.map +1 -1
  64. package/dist/backend/backend/src/services/decisions/decision-routing.js +11 -7
  65. package/dist/backend/backend/src/services/decisions/decision-routing.js.map +1 -1
  66. package/dist/backend/backend/src/services/decisions/decision.service.d.ts +6 -0
  67. package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  68. package/dist/backend/backend/src/services/decisions/decision.service.js +17 -2
  69. package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
  70. package/dist/backend/backend/src/services/decisions/decision.wiring.d.ts.map +1 -1
  71. package/dist/backend/backend/src/services/decisions/decision.wiring.js +20 -1
  72. package/dist/backend/backend/src/services/decisions/decision.wiring.js.map +1 -1
  73. package/dist/backend/backend/src/services/decisions/recurring-change-handoff.d.ts +81 -0
  74. package/dist/backend/backend/src/services/decisions/recurring-change-handoff.d.ts.map +1 -0
  75. package/dist/backend/backend/src/services/decisions/recurring-change-handoff.js +132 -0
  76. package/dist/backend/backend/src/services/decisions/recurring-change-handoff.js.map +1 -0
  77. package/dist/backend/backend/src/services/messaging/owner-thread-sentinel.service.d.ts +14 -0
  78. package/dist/backend/backend/src/services/messaging/owner-thread-sentinel.service.d.ts.map +1 -1
  79. package/dist/backend/backend/src/services/messaging/owner-thread-sentinel.service.js +67 -13
  80. package/dist/backend/backend/src/services/messaging/owner-thread-sentinel.service.js.map +1 -1
  81. package/dist/backend/backend/src/services/open-items/open-item-card.d.ts +25 -1
  82. package/dist/backend/backend/src/services/open-items/open-item-card.d.ts.map +1 -1
  83. package/dist/backend/backend/src/services/open-items/open-item-card.js +108 -1
  84. package/dist/backend/backend/src/services/open-items/open-item-card.js.map +1 -1
  85. package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts +2 -0
  86. package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
  87. package/dist/backend/backend/src/services/open-items/open-item-extractor.js +21 -1
  88. package/dist/backend/backend/src/services/open-items/open-item-extractor.js.map +1 -1
  89. package/dist/backend/backend/src/services/open-items/open-items.service.d.ts +12 -1
  90. package/dist/backend/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  91. package/dist/backend/backend/src/services/open-items/open-items.service.js +54 -4
  92. package/dist/backend/backend/src/services/open-items/open-items.service.js.map +1 -1
  93. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.d.ts.map +1 -1
  94. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.js +3 -2
  95. package/dist/backend/backend/src/services/runtime-fallback/runtime-fallback.wiring.js.map +1 -1
  96. package/dist/backend/backend/src/services/session/tui-input-guard.js +1 -1
  97. package/dist/backend/backend/src/services/session/tui-input-guard.js.map +1 -1
  98. package/dist/backend/build-info.json +2 -2
  99. package/dist/cli/backend/src/constants.d.ts +79 -1
  100. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  101. package/dist/cli/backend/src/constants.js +80 -0
  102. package/dist/cli/backend/src/constants.js.map +1 -1
  103. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts +9 -0
  104. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  105. package/dist/cli/backend/src/services/agent/agent-registration.service.js +41 -1
  106. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
  107. package/dist/cli/backend/src/services/agent/control-plane-guard.service.d.ts +4 -1
  108. package/dist/cli/backend/src/services/agent/control-plane-guard.service.d.ts.map +1 -1
  109. package/dist/cli/backend/src/services/agent/control-plane-guard.service.js +11 -2
  110. package/dist/cli/backend/src/services/agent/control-plane-guard.service.js.map +1 -1
  111. package/dist/cli/backend/src/services/agent/destructive-git-guard.service.d.ts +21 -0
  112. package/dist/cli/backend/src/services/agent/destructive-git-guard.service.d.ts.map +1 -0
  113. package/dist/cli/backend/src/services/agent/destructive-git-guard.service.js +40 -0
  114. package/dist/cli/backend/src/services/agent/destructive-git-guard.service.js.map +1 -0
  115. package/dist/cli/backend/src/services/agent/fresh-task-conversation.service.d.ts.map +1 -1
  116. package/dist/cli/backend/src/services/agent/fresh-task-conversation.service.js +5 -1
  117. package/dist/cli/backend/src/services/agent/fresh-task-conversation.service.js.map +1 -1
  118. package/dist/cli/backend/src/services/agent/handover-pointers.d.ts +70 -0
  119. package/dist/cli/backend/src/services/agent/handover-pointers.d.ts.map +1 -0
  120. package/dist/cli/backend/src/services/agent/handover-pointers.js +122 -0
  121. package/dist/cli/backend/src/services/agent/handover-pointers.js.map +1 -0
  122. package/dist/cli/backend/src/services/agent/runtime-session-recovery.d.ts.map +1 -1
  123. package/dist/cli/backend/src/services/agent/runtime-session-recovery.js +6 -3
  124. package/dist/cli/backend/src/services/agent/runtime-session-recovery.js.map +1 -1
  125. package/dist/cli/backend/src/services/decisions/decision.service.d.ts +6 -0
  126. package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  127. package/dist/cli/backend/src/services/decisions/decision.service.js +17 -2
  128. package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -1
  129. package/dist/cli/backend/src/services/messaging/owner-thread-sentinel.service.d.ts +14 -0
  130. package/dist/cli/backend/src/services/messaging/owner-thread-sentinel.service.d.ts.map +1 -1
  131. package/dist/cli/backend/src/services/messaging/owner-thread-sentinel.service.js +67 -13
  132. package/dist/cli/backend/src/services/messaging/owner-thread-sentinel.service.js.map +1 -1
  133. package/dist/cli/backend/src/services/open-items/open-item-card.d.ts +25 -1
  134. package/dist/cli/backend/src/services/open-items/open-item-card.d.ts.map +1 -1
  135. package/dist/cli/backend/src/services/open-items/open-item-card.js +108 -1
  136. package/dist/cli/backend/src/services/open-items/open-item-card.js.map +1 -1
  137. package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts +2 -0
  138. package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
  139. package/dist/cli/backend/src/services/open-items/open-item-extractor.js +21 -1
  140. package/dist/cli/backend/src/services/open-items/open-item-extractor.js.map +1 -1
  141. package/dist/cli/backend/src/services/open-items/open-items.service.d.ts +12 -1
  142. package/dist/cli/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  143. package/dist/cli/backend/src/services/open-items/open-items.service.js +54 -4
  144. package/dist/cli/backend/src/services/open-items/open-items.service.js.map +1 -1
  145. package/dist/cli/backend/src/services/session/tui-input-guard.js +1 -1
  146. package/dist/cli/backend/src/services/session/tui-input-guard.js.map +1 -1
  147. package/package.json +1 -1
@@ -13,17 +13,19 @@ source "${SCRIPT_DIR}/../_common/lib.sh"
13
13
  # (the [TICKET:TKT-123 <id>] line of the message being worked on). Also
14
14
  # accepted as `"requestId"` in the JSON.
15
15
  REQUEST_ID=""
16
+ DRY_RUN=0
16
17
  POSITIONAL=()
17
18
  while [[ $# -gt 0 ]]; do
18
19
  case "$1" in
19
20
  --request-id|-R) REQUEST_ID="${2:-}"; shift 2 ;;
21
+ --dry-run) DRY_RUN=1; shift ;;
20
22
  *) POSITIONAL+=("$1"); shift ;;
21
23
  esac
22
24
  done
23
25
  set -- "${POSITIONAL[@]+"${POSITIONAL[@]}"}"
24
26
 
25
27
  INPUT=$(read_json_input "${1:-}")
26
- [ -z "$INPUT" ] && error_exit "Usage: execute.sh '{\"objective\":\"Build auth module\",\"projectPath\":\"/path/to/project\",\"requestId\":\"<ticket id, optional>\",\"tasks\":[{\"title\":\"...\",\"description\":\"...\",\"requiredRole\":\"developer\",\"acceptanceCriteria\":\"...\",\"priority\":\"high\"}]}' [--request-id <id>]"
28
+ [ -z "$INPUT" ] && error_exit "Usage: execute.sh '{\"objective\":\"Build auth module\",\"projectPath\":\"/path/to/project\",\"requestId\":\"<ticket id, optional>\",\"tasks\":[{\"title\":\"...\",\"description\":\"...\",\"requiredRole\":\"developer\",\"acceptanceCriteria\":\"...\",\"priority\":\"high\"}]}' [--request-id <id>] [--dry-run] (optional per task: \"blockedBy\":[<index or title>, ...])"
27
29
 
28
30
  OBJECTIVE=$(printf '%s' "$INPUT" | jq -r '.objective // empty')
29
31
  PROJECT_PATH=$(printf '%s' "$INPUT" | jq -r '.projectPath // empty')
@@ -39,11 +41,52 @@ if [ "$TASK_COUNT" = "0" ]; then
39
41
  error_exit "tasks array must contain at least one task"
40
42
  fi
41
43
 
44
+ # Blocking edges: tasks[].blockedBy lists tasks of this same call (0-based
45
+ # index or exact title). Resolve to indexes, reject unknown/ambiguous
46
+ # references and cycles (a self-reference is a cycle) BEFORE creating anything, and derive a creation order
47
+ # in which every blocker comes first.
48
+ PLAN=$(printf '%s' "$TASKS" | jq -c '
49
+ . as $t
50
+ | [ to_entries[] | (.value.blockedBy // []) | map(
51
+ if type == "number" then (if . >= 0 and . < ($t | length) and . == floor then . else null end)
52
+ elif type == "string" then (. as $s | [$t | to_entries[] | select(.value.title == $s) | .key] | if length == 1 then .[0] else null end)
53
+ else null end) ] as $deps
54
+ | ([ $deps | to_entries[] | select(.value | any(. == null)) | .key ]) as $bad
55
+ | { deps: $deps, bad: $bad }
56
+ | . as $p
57
+ | ($p.deps | length) as $n
58
+ | ({ done: [], rem: [range(0; $n)], stuck: false }
59
+ | until((.rem | length) == 0 or .stuck;
60
+ . as $s
61
+ | [ $s.rem[] | . as $i | select($p.deps[$i] | all(. as $d | any($s.done[]; . == $d))) ] as $ready
62
+ | if ($ready | length) == 0 then .stuck = true else (.done += $ready | .rem -= $ready) end)) as $o
63
+ | { deps: $p.deps, bad: $p.bad, order: $o.done, cyclic: $o.rem }')
64
+ BAD=$(printf '%s' "$PLAN" | jq -c '.bad')
65
+ CYCLIC=$(printf '%s' "$PLAN" | jq -c '.cyclic')
66
+ if [ "$BAD" != "[]" ]; then
67
+ error_exit "blockedBy has an unknown, ambiguous or invalid reference in task index(es) ${BAD}; nothing created"
68
+ fi
69
+ if [ "$CYCLIC" != "[]" ]; then
70
+ error_exit "blockedBy forms a cycle among task index(es) ${CYCLIC}; nothing created"
71
+ fi
72
+
73
+ if [ "$DRY_RUN" = "1" ]; then
74
+ printf '%s' "$TASKS" | jq -c --argjson plan "$PLAN" --arg objective "$OBJECTIVE" '
75
+ . as $t
76
+ | { dryRun: true, objective: $objective, tasks: [ $plan.order[] | . as $i | {
77
+ index: $i, title: $t[$i].title,
78
+ blockedBy: [ $plan.deps[$i][] | $t[.].title ],
79
+ delivers: ($t[$i].description // "") } ] }'
80
+ exit 0
81
+ fi
82
+
42
83
  CREATED_TASKS="[]"
43
84
  ERRORS="[]"
85
+ # index -> WorkItem id, for resolving blockedBy into dependsOn
86
+ declare -a CREATED_ID_BY_INDEX=()
44
87
 
45
- # Create each sub-task via the task-management API
46
- for i in $(seq 0 $((TASK_COUNT - 1))); do
88
+ # Create each sub-task via the task-pool API, blockers first
89
+ for i in $(printf '%s' "$PLAN" | jq -r '.order[]'); do
47
90
  TASK_TITLE=$(echo "$TASKS" | jq -r ".[$i].title // empty")
48
91
  TASK_DESC=$(echo "$TASKS" | jq -r ".[$i].description // empty")
49
92
  TASK_ROLE=$(echo "$TASKS" | jq -r ".[$i].requiredRole // \"developer\"")
@@ -55,6 +98,21 @@ for i in $(seq 0 $((TASK_COUNT - 1))); do
55
98
  continue
56
99
  fi
57
100
 
101
+ # Resolve blockers to WorkItem ids; a blocker that failed to create blocks this task too.
102
+ DEPENDS_ON="[]"
103
+ DEP_FAILED=0
104
+ for d in $(printf '%s' "$PLAN" | jq -r ".deps[$i][]"); do
105
+ if [ -n "${CREATED_ID_BY_INDEX[$d]:-}" ]; then
106
+ DEPENDS_ON=$(echo "$DEPENDS_ON" | jq --arg id "${CREATED_ID_BY_INDEX[$d]}" '. + [$id]')
107
+ else
108
+ DEP_FAILED=1
109
+ fi
110
+ done
111
+ if [ "$DEP_FAILED" = "1" ]; then
112
+ ERRORS=$(echo "$ERRORS" | jq --arg msg "Skipped (a blocker was not created): ${TASK_TITLE}" '. + [$msg]')
113
+ continue
114
+ fi
115
+
58
116
  # Compose the brief markdown for the WorkItem.
59
117
  BRIEF="${TASK_DESC}"
60
118
  [ -n "$TASK_CRITERIA" ] && BRIEF="${BRIEF}
@@ -83,13 +141,14 @@ Required role: ${TASK_ROLE}"
83
141
  --arg role "$TASK_ROLE" \
84
142
  --arg priority "$TASK_PRIORITY" \
85
143
  --arg requestId "$REQUEST_ID" \
144
+ --argjson dependsOn "$DEPENDS_ON" \
86
145
  '{
87
146
  title: $title,
88
147
  type: "delegate",
89
148
  owner: "system",
90
149
  briefMarkdown: $brief,
91
150
  metadata: { projectPath: $projectPath, milestone: $milestone, requiredRole: $role, priority: $priority }
92
- } + (if $requestId != "" then {requestId: $requestId} else {} end)')
151
+ } + (if $requestId != "" then {requestId: $requestId} else {} end) + (if ($dependsOn | length) > 0 then {dependsOn: $dependsOn} else {} end)')
93
152
 
94
153
  # No envelope: addItem reads req.body directly as the CreateWorkItemInput.
95
154
  CREATE_BODY="$WORK_ITEM"
@@ -97,12 +156,14 @@ Required role: ${TASK_ROLE}"
97
156
  CREATED_ID=$(echo "$CREATE_RESULT" | jq -r '.data.id // .workItem.id // empty' 2>/dev/null || true)
98
157
 
99
158
  if [ -n "$CREATED_ID" ]; then
159
+ CREATED_ID_BY_INDEX[$i]="$CREATED_ID"
100
160
  CREATED_TASKS=$(echo "$CREATED_TASKS" | jq \
101
161
  --arg title "$TASK_TITLE" \
102
162
  --arg role "$TASK_ROLE" \
103
163
  --arg id "$CREATED_ID" \
104
164
  --arg priority "$TASK_PRIORITY" \
105
- '. + [{title: $title, requiredRole: $role, workItemId: $id, priority: $priority}]')
165
+ --argjson dependsOn "$DEPENDS_ON" \
166
+ '. + [{title: $title, requiredRole: $role, workItemId: $id, priority: $priority} + (if ($dependsOn | length) > 0 then {dependsOn: $dependsOn} else {} end)]')
106
167
  else
107
168
  ERRORS=$(echo "$ERRORS" | jq --arg msg "Failed to create WorkItem: ${TASK_TITLE}" '. + [$msg]')
108
169
  fi
@@ -0,0 +1,92 @@
1
+ #!/bin/bash
2
+ # Tests for decompose-goal (TL) — run with: bash execute.test.sh
3
+ # A python HTTP stub plays the backend; asserts the POST /task-pool/add bodies.
4
+ set -u
5
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ EXEC="$HERE/execute.sh"
7
+ PASS=0; FAIL=0
8
+ check() {
9
+ local name="$1"; local got="$2"; local want="$3"
10
+ if [ "$got" = "$want" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $name"; echo " want: $want"; echo " got: $got"; fi
11
+ }
12
+
13
+ PORT=18843
14
+ STUB_LOG="$(mktemp)"; export STUB_LOG; export STUB_PORT="$PORT"
15
+ python3 -u <<'PY' >/dev/null 2>&1 &
16
+ from http.server import BaseHTTPRequestHandler, HTTPServer
17
+ import json, os
18
+ LOG = os.environ['STUB_LOG']; PORT = int(os.environ['STUB_PORT'])
19
+ N = [0]
20
+ class H(BaseHTTPRequestHandler):
21
+ def reply(self, code, obj):
22
+ self.send_response(code); self.send_header('Content-Type', 'application/json'); self.end_headers()
23
+ self.wfile.write(json.dumps(obj).encode())
24
+ def do_GET(self):
25
+ return self.reply(200, {'ok': True})
26
+ def do_POST(self):
27
+ n = int(self.headers.get('content-length', '0')); body = json.loads(self.rfile.read(n).decode() or '{}')
28
+ if not self.path.endswith('/task-pool/add'):
29
+ return self.reply(200, {'success': True})
30
+ with open(LOG, 'a') as f: f.write(json.dumps(body) + '\n')
31
+ if body.get('title') == 'Fails':
32
+ return self.reply(500, {'success': False})
33
+ return self.reply(200, {'success': True, 'data': {'id': 'wi-' + body['title']}})
34
+ def log_message(self, *a, **k): pass
35
+ HTTPServer(('127.0.0.1', PORT), H).serve_forever()
36
+ PY
37
+ STUB_PID=$!
38
+ disown "$STUB_PID" 2>/dev/null || true
39
+ trap 'kill "$STUB_PID" >/dev/null 2>&1; rm -f "$STUB_LOG"' EXIT
40
+ for i in $(seq 1 30); do curl -s -o /dev/null -m 0.2 "http://127.0.0.1:${PORT}/" 2>/dev/null && break; sleep 0.1; done
41
+
42
+ run() { CREWLY_SESSION_NAME=tl-sam CREWLY_API_URL="http://127.0.0.1:${PORT}" bash "$EXEC" "$@" 2>&1; }
43
+ posts() { wc -l < "$STUB_LOG" | tr -d ' '; }
44
+
45
+ # 1. No blockedBy: unchanged behaviour, no dependsOn on the wire.
46
+ : > "$STUB_LOG"
47
+ OUT=$(run '{"objective":"O","tasks":[{"title":"A","description":"a"},{"title":"B","description":"b"}]}')
48
+ check "plain: two created" "$(printf '%s' "$OUT" | jq -r .tasksCreated)" "2"
49
+ check "plain: no dependsOn" "$(jq -c 'has("dependsOn")' "$STUB_LOG" | sort -u)" "false"
50
+
51
+ # 2. Dependency chain (titles and indexes mixed), listed out of order: blockers are created first.
52
+ : > "$STUB_LOG"
53
+ OUT=$(run '{"objective":"Auth","tasks":[
54
+ {"title":"UI","description":"u","blockedBy":["API"]},
55
+ {"title":"Schema","description":"s"},
56
+ {"title":"API","description":"a","blockedBy":[1]},
57
+ {"title":"E2E","description":"e","blockedBy":["UI","API"]}]}')
58
+ check "chain: all created" "$(printf '%s' "$OUT" | jq -r .tasksCreated)" "4"
59
+ check "chain: creation order" "$(jq -r .title "$STUB_LOG" | paste -sd, -)" "Schema,API,UI,E2E"
60
+ check "chain: API depends on Schema" "$(jq -c 'select(.title=="API") | .dependsOn' "$STUB_LOG")" '["wi-Schema"]'
61
+ check "chain: UI depends on API" "$(jq -c 'select(.title=="UI") | .dependsOn' "$STUB_LOG")" '["wi-API"]'
62
+ check "chain: E2E depends on API and UI" "$(jq -c 'select(.title=="E2E") | .dependsOn | sort' "$STUB_LOG")" '["wi-API","wi-UI"]'
63
+ check "chain: Schema has no dependsOn" "$(jq -c 'select(.title=="Schema") | has("dependsOn")' "$STUB_LOG")" "false"
64
+
65
+ # 3. Cycle / unknown / self reference: error, nothing created.
66
+ : > "$STUB_LOG"
67
+ OUT=$(run '{"objective":"O","tasks":[{"title":"A","description":"a","blockedBy":["B"]},{"title":"B","description":"b","blockedBy":["A"]}]}')
68
+ check "cycle: error" "$(printf '%s' "$OUT" | grep -c 'cycle')" "1"
69
+ check "cycle: nothing created" "$(posts)" "0"
70
+ OUT=$(run '{"objective":"O","tasks":[{"title":"A","description":"a","blockedBy":["Nope"]}]}')
71
+ check "unknown title: error" "$(printf '%s' "$OUT" | grep -c 'unknown')" "1"
72
+ OUT=$(run '{"objective":"O","tasks":[{"title":"A","description":"a","blockedBy":[5]}]}')
73
+ check "index out of range: error" "$(printf '%s' "$OUT" | grep -c 'unknown')" "1"
74
+ OUT=$(run '{"objective":"O","tasks":[{"title":"A","description":"a","blockedBy":[0]}]}')
75
+ check "self reference: error" "$(printf '%s' "$OUT" | grep -c 'cycle')" "1"
76
+ check "errors: nothing created" "$(posts)" "0"
77
+
78
+ # 4. A blocker that fails to create also blocks its dependents from being created.
79
+ : > "$STUB_LOG"
80
+ OUT=$(run '{"objective":"O","tasks":[{"title":"Fails","description":"f"},{"title":"After","description":"x","blockedBy":["Fails"]}]}')
81
+ check "failed blocker: success false" "$(printf '%s' "$OUT" | jq -r .success)" "false"
82
+ check "failed blocker: dependent skipped" "$(grep -c '"After"' "$STUB_LOG")" "0"
83
+
84
+ # 5. --dry-run prints the breakdown, creates nothing.
85
+ : > "$STUB_LOG"
86
+ OUT=$(run '{"objective":"O","tasks":[{"title":"B","description":"b","blockedBy":["A"]},{"title":"A","description":"a"}]}' --dry-run)
87
+ check "dry-run: order" "$(printf '%s' "$OUT" | jq -c '[.tasks[] | .title]')" '["A","B"]'
88
+ check "dry-run: blockedBy titles" "$(printf '%s' "$OUT" | jq -c '.tasks[1].blockedBy')" '["A"]'
89
+ check "dry-run: nothing created" "$(posts)" "0"
90
+
91
+ echo "decompose-goal: ${PASS} passed, ${FAIL} failed"
92
+ [ "$FAIL" -eq 0 ]
@@ -99,7 +99,7 @@ If two rules disagree, the higher tier wins.
99
99
  4. One TL or senior reviewer + one peer reviewer.
100
100
  5. CHANGELOG entry if user-visible.
101
101
  6. Manual smoke test of the changed flow before merge.
102
- 7. Rollback plan stated in PR description (how to revert + what data state requires fixup).
102
+ 7. Merge Danger filled in on the PR (Door + Blast radius). A one-way door needs the rollback rehearsal from Tier 3. One-way = data migration, deletion, published artifact (npm, docker, deploy), outbound message.
103
103
 
104
104
  **Skipped vs Release Path:**
105
105
  - No staged rollout (merge-and-deploy together).
@@ -0,0 +1,45 @@
1
+ ---
2
+ id: common-writing-for-agents
3
+ version: 1
4
+ createdAt: 2026-10-13T00:00:00Z
5
+ updatedAt: 2026-10-13T00:00:00Z
6
+ createdBy: system
7
+ type: norm
8
+ role: all
9
+ category: quality
10
+ priority: 8
11
+ title: Writing for Agents
12
+ description: How to word skills, prompts, roles and SOPs so agents act on them at low context cost
13
+ triggers:
14
+ - write skill
15
+ - write prompt
16
+ - skill description
17
+ - author sop
18
+ - role prompt
19
+ - steering text
20
+ tags:
21
+ - authoring
22
+ - prompts
23
+ - skills
24
+ ---
25
+
26
+ # Writing for Agents
27
+
28
+ Long form: mattpocock/skills `writing-for-agents`. Skill descriptions and always-loaded steering text cost context on every turn. Check new text against these six points. `skill-lint` (config/skills/_conventions) enforces the mechanical ones.
29
+
30
+ 1. **Pointer wording decides when material is read.** Front-load the leading word of a description. One trigger per branch; synonyms of one trigger are one branch written twice. Cut identity the body already carries.
31
+ 2. **End every step on a completion criterion.** The agent must be able to tell done from not-done. The best criteria are checkable and exhaustive ("the command exits 0", not "make sure it works").
32
+ 3. **Say what to do, not what to avoid.** A prohibition puts the forbidden behavior into context and makes it more available. Prefer "Run the check, then report its exit code" over "NEVER report without checking".
33
+ 4. **Do not restate the environment.** A doc that copies `package.json` scripts or config is a cache that goes stale. Cache only what cannot be found by looking; point to the file otherwise.
34
+ 5. **Hunt no-ops.** If the model already does it by default, the sentence costs load and changes nothing. Keep a sentence only if removing it would change behavior (the same test for #877 rule cleanup).
35
+ 6. **Keep emphasis rare.** Capitals and HARD RULE lose force when everything has them.
36
+
37
+ ## Example (real skill: `agent/core/publish-app`)
38
+
39
+ Before (612 chars, why-it-works history and edge cases in the pointer):
40
+
41
+ > Publish a small web app (HTML/JS) you wrote to https://apps.crewlyai.com/<appId> so the owner can open it on their phone. Creates the app the first time, uploads a new version each time after (same app for the same directory), supports rollback, and can post an "Open app" card to the owner (...). Can ask the owner to make an app public, or to publish it as a template on the Crewly Marketplace (...). The owner's edits, comments on elements, and anonymous visitors' submissions on a public app, come back to you as an [APP CHANGES] message.
42
+
43
+ After (under 200 chars, one trigger, detail moves to the SKILL.md body):
44
+
45
+ > Publish an HTML/JS app you wrote to apps.crewlyai.com so the owner can open it on their phone. Also: new version, rollback, make public.
@@ -0,0 +1,53 @@
1
+ ---
2
+ id: dev-debugging
3
+ version: 1
4
+ createdAt: 2026-10-13T00:00:00Z
5
+ updatedAt: 2026-10-13T00:00:00Z
6
+ createdBy: system
7
+ type: sop
8
+ role: developer
9
+ category: debugging
10
+ priority: 9
11
+ title: Debugging
12
+ description: How to debug a failure — a red-capable loop first, ranked falsifiable hypotheses, tagged debug logs, cleanup
13
+ triggers:
14
+ - debug
15
+ - failing test
16
+ - bug
17
+ - regression
18
+ - flaky
19
+ - broken
20
+ - repro
21
+ tags:
22
+ - debugging
23
+ - testing
24
+ - quality
25
+ ---
26
+
27
+ # Debugging
28
+
29
+ Long form: mattpocock/skills `diagnosing-bugs`. Redact secrets in every command and artifact you show.
30
+
31
+ ## 1. Loop first
32
+
33
+ Name **one command** you have already run at least once that shows the user's exact symptom (red-capable), is deterministic, fast and runnable by you. If you are reading code to build a theory before this command exists, stop and build the command.
34
+
35
+ ## 2. Reproduce and minimise
36
+
37
+ Cut inputs one at a time. Done when every remaining element is load-bearing.
38
+
39
+ ## 3. Hypotheses
40
+
41
+ Write 3-5 ranked hypotheses before testing any. Each is falsifiable: "If X is the cause, then changing Y makes the bug disappear." If you cannot state the prediction, it is a guess. Test one at a time.
42
+
43
+ ## 4. Instrument
44
+
45
+ Tag every debug log with a unique prefix, e.g. `[DEBUG-a4f2]`, so cleanup is one grep. Untagged logs survive; tagged logs die. If no correct seam exists to test the bug, that is itself the finding.
46
+
47
+ ## 5. Fix with a regression test
48
+
49
+ Turn the loop into a test. See it fail first, then fix (see `testing-requirements`: red before green).
50
+
51
+ ## 6. Clean up
52
+
53
+ `grep -rn "\[DEBUG-" .` returns nothing. The pre-commit hook rejects a staged `[DEBUG-` line.
@@ -85,6 +85,10 @@ git worktree remove /tmp/crewly-{ticket-name}-{owner}
85
85
 
86
86
  **Solo work exception:** if you are the only developer active in the team and you're not parking work for parallel review, working directly in the main repo is acceptable. The worktree pattern is required only when there is a real risk of branch-state collision.
87
87
 
88
+ ## Destructive Git Is Blocked
89
+
90
+ A hook blocks these in your Bash tool: force-push (`--force`, `-f`, `--force-with-lease`, `+ref`), any push to `main`/`master`, `reset --hard`, `clean -f`, `branch -D`, `checkout .`, `restore .`. Plain `git push origin feat/x` is fine. Instead: `git stash -u` before discarding, `branch -d` for merged branches, push a normal commit and open a PR. If you truly need one, ask your team lead. Limit: interpreter one-liners and variable-built commands are not seen.
91
+
88
92
  ## Before Pushing
89
93
 
90
94
  1. Run `npm run typecheck`
@@ -56,6 +56,20 @@ describe('ComponentName', () => {
56
56
  - No test interdependencies
57
57
  - Clean up after tests
58
58
 
59
+ When a test fails and the cause is unclear, follow the `debugging` SOP (red-capable loop first).
60
+
61
+ ## Anti-patterns
62
+
63
+ Coverage percentage rewards all three. Avoid them.
64
+
65
+ - **Tautological**: the assertion recomputes the expected value the way the code does (`expect(add(a, b)).toBe(a + b)`). Tell: it can never disagree with the code. Take expected values from a literal, a worked example or the spec. Prove a test is not tautological with the mutation check: break the code on purpose and see the test fail.
66
+ - **Implementation-coupled**: the test checks how (private calls, call order), not what. Tell: it breaks on a refactor that changes no behavior.
67
+ - **Horizontal slicing**: all tests first, then all code. Tell: many tests, none ever seen red. Work one test, one implementation, repeat. **Red before green**: see the new test fail before the fix.
68
+
69
+ ## Seams
70
+
71
+ Test at agreed seams (public function, API route, CLI entry), not at every internal. For each seam write one line: what it catches and what it misses.
72
+
59
73
  ## Coverage Goals
60
74
 
61
75
  - Minimum 80% for new code
@@ -1040,7 +1040,7 @@ export declare const CONTROL_PLANE_GUARD_CONSTANTS: {
1040
1040
  /** Files under CREWLY_HOME that agents must not read with the built-in tools either. */
1041
1041
  readonly CREWLY_HOME_READ_DENIED_FILES: readonly ["api-token"];
1042
1042
  /** Write-protected directories under the install root (whole subtree). */
1043
- readonly INSTALL_DIRS: readonly ["config/skills/orchestrator/stop-agent", "config/skills/orchestrator/start-agent", "config/skills/orchestrator/terminate-agent", "config/skills/orchestrator/stop-team", "config/skills/orchestrator/start-team", "config/skills/orchestrator/restart-crewly", "config/hooks/control-plane-guard", "config/hooks/agent-status", "config/hooks/subagent-guard", "config/hooks/credential-guard", "config/hooks/live-checkout-guard", "dist"];
1043
+ readonly INSTALL_DIRS: readonly ["config/skills/orchestrator/stop-agent", "config/skills/orchestrator/start-agent", "config/skills/orchestrator/terminate-agent", "config/skills/orchestrator/stop-team", "config/skills/orchestrator/start-team", "config/skills/orchestrator/restart-crewly", "config/hooks/control-plane-guard", "config/hooks/agent-status", "config/hooks/subagent-guard", "config/hooks/credential-guard", "config/hooks/live-checkout-guard", "config/hooks/destructive-git-guard", "dist"];
1044
1044
  /** Write-protected files under the install root. */
1045
1045
  readonly INSTALL_FILES: readonly ["config/skills/_common/lib.sh"];
1046
1046
  /** Write-protected directories under the agent's project path (whole subtree). */
@@ -1063,6 +1063,21 @@ export declare const LIVE_CHECKOUT_GUARD_CONSTANTS: {
1063
1063
  /** Codex tools the hook is attached to (shell, plus its patch tool). */
1064
1064
  readonly CODEX_MATCHER: "Bash|apply_patch|Edit|Write";
1065
1065
  };
1066
+ /**
1067
+ * Destructive-git guard: blocks force-push, push to main/master, reset --hard,
1068
+ * clean -f, branch -D, checkout . and restore . in an agent's Bash tool, so an
1069
+ * agent cannot destroy its own unmerged work.
1070
+ */
1071
+ export declare const DESTRUCTIVE_GIT_GUARD_CONSTANTS: {
1072
+ /** Backend env var; `0` turns the guard off for sessions launched by this backend (the owner/TL override). */
1073
+ readonly KILL_SWITCH_ENV: "CREWLY_DESTRUCTIVE_GIT_GUARD";
1074
+ /** Value of KILL_SWITCH_ENV that disables the guard. */
1075
+ readonly KILL_SWITCH_OFF_VALUE: "0";
1076
+ /** The hook script (node), relative to the install root. */
1077
+ readonly HOOK_SCRIPT: "config/hooks/destructive-git-guard/guard.mjs";
1078
+ /** Claude Code tool the hook is attached to. */
1079
+ readonly CLAUDE_MATCHER: "Bash";
1080
+ };
1066
1081
  /**
1067
1082
  * Credential guard (specs/2026-10-04-agent-credential-isolation.md, layer 2):
1068
1083
  * a pre-tool hook, on every runtime that has one, that refuses an agent's
@@ -1482,6 +1497,14 @@ export declare const TUI_INPUT_GUARD: {
1482
1497
  readonly RULE_STALE_PREFIX_MIN_CHARS: 40;
1483
1498
  /** Most status/footer lines allowed below an input box */
1484
1499
  readonly FOOTER_MAX_LINES: 6;
1500
+ /**
1501
+ * Most rows below the bottom rule when searching for a Claude Code /
1502
+ * Antigravity box. Claude Code lists background agents and shells under
1503
+ * the box, so a busy session can have well over FOOTER_MAX_LINES rows
1504
+ * there (crewly#1113). Only the box search uses this; turn-signal reading
1505
+ * keeps FOOTER_MAX_LINES.
1506
+ */
1507
+ readonly BOX_FOOTER_MAX_LINES: 14;
1485
1508
  /** Most lines an input box may span */
1486
1509
  readonly MAX_BOX_LINES: 60;
1487
1510
  /**
@@ -3300,6 +3323,20 @@ export declare const ORC_CONVERSATION_CONSTANTS: {
3300
3323
  /** Most of an earlier handover carried into a new one when the source conversation is unreadable */
3301
3324
  readonly CHAINED_HANDOVER_MAX_CHARS: 16000;
3302
3325
  };
3326
+ /** Pointer header (open work, suggested skills) written at the top of a handover file. */
3327
+ export declare const HANDOVER_POINTERS_CONSTANTS: {
3328
+ /** Task pool file under CREWLY_HOME (dir, file) */
3329
+ readonly POOL_DIR: "task-pool";
3330
+ readonly POOL_FILE: "pool.json";
3331
+ /** WorkItem statuses that count as still open for the agent */
3332
+ readonly OPEN_STATUSES: readonly string[];
3333
+ readonly MAX_OPEN_WORK: 15;
3334
+ readonly MAX_LINKS: 10;
3335
+ readonly TITLE_CHARS: 80;
3336
+ /** Markers around the header, so a chained handover can strip it */
3337
+ readonly START_MARK: "<!-- handover-pointers -->";
3338
+ readonly END_MARK: "<!-- /handover-pointers -->";
3339
+ };
3303
3340
  /**
3304
3341
  * Fresh conversation per new task for Claude Code team members (never the
3305
3342
  * orchestrator): before a work item with a different root than the last one
@@ -6799,6 +6836,12 @@ export declare const DECISION_CONSTANTS: {
6799
6836
  * (specs/2026-10-02-decision-card-thread-answers.md §2) (ms)
6800
6837
  */
6801
6838
  readonly WAIT_REMINDER_DELAY_MS: number;
6839
+ /**
6840
+ * `wait` default: a card still unanswered this long after its one reminder
6841
+ * (a lifted reply question: after the deadline notice, it never gets a
6842
+ * reminder) expires instead of staying open for days (#1243) (ms)
6843
+ */
6844
+ readonly WAIT_EXPIRE_AFTER_REMINDER_MS: number;
6802
6845
  /** Max characters of a withdrawn card's reason ("Closed — <reason>") */
6803
6846
  readonly CLOSED_REASON_MAX_CHARS: 120;
6804
6847
  /** Withdraw reasons the harness itself uses (cancelWhere notes) */
@@ -6882,6 +6925,18 @@ export declare const OPEN_ITEMS_CONSTANTS: {
6882
6925
  readonly YES_LABEL: "Yes";
6883
6926
  readonly NO_LABEL: "No";
6884
6927
  readonly REPLY_LABEL: "Reply in thread";
6928
+ /** A lifted question shorter than this (characters) gets its subject prepended (#1242) */
6929
+ readonly QUESTION_MIN_CHARS: 20;
6930
+ /** Still shorter than this after the subject was added (characters): too vague for a card, tracked without one */
6931
+ readonly QUESTION_VAGUE_CHARS: 12;
6932
+ /** Suffix of the option the agent recommended */
6933
+ readonly RECOMMENDED_SUFFIX: " (recommended)";
6934
+ /** A sentence that recommends something ("我推荐 A", "I recommend B") */
6935
+ readonly RECOMMENDATION_PATTERN: RegExp;
6936
+ /** A recommendation that argues against doing it ("不建议", "don't recommend") */
6937
+ readonly RECOMMENDATION_NEGATIVE_PATTERN: RegExp;
6938
+ /** A project ticket id named in a promise ("CE-16"); the work done under it fulfils the promise wherever it ran (crewly#1037) */
6939
+ readonly PROJECT_TICKET_REF_PATTERN: RegExp;
6885
6940
  /** `WorkItem.metadata` key of a follow-up WorkItem */
6886
6941
  readonly FOLLOW_UP_METADATA_KEY: "openItemFollowUp";
6887
6942
  /**
@@ -7679,6 +7734,11 @@ export declare const OWNER_THREAD_SENTINEL_CONSTANTS: {
7679
7734
  readonly DEADLINE_FACTOR: 1.5;
7680
7735
  /** Floor of a promise deadline (ms) */
7681
7736
  readonly MIN_DEADLINE_MS: number;
7737
+ /**
7738
+ * A promise with no stated time ("做好发到 #x", "I'll post the result") is
7739
+ * overdue after this much silence from the agent in the thread (#1240) (ms)
7740
+ */
7741
+ readonly NO_ETA_SILENCE_MS: number;
7682
7742
  /** Longest promise the sentinel times (longer ones are not timed) */
7683
7743
  readonly MAX_PROMISE_MINUTES: number;
7684
7744
  /** Slot freeing / idle stop spare an agent whose owner message or promise is this recent */
@@ -8283,4 +8343,22 @@ export declare const CANVAS_CONSTANTS: {
8283
8343
  /** A Chrome session counts as the agent's browser while it was active this recently (ms) */
8284
8344
  readonly BROWSER_ACTIVE_MS: number;
8285
8345
  };
8346
+ /**
8347
+ * Hand-off of an owner-approved change to a recurring job to the agent that
8348
+ * runs it (crewly#1137).
8349
+ */
8350
+ export declare const RECURRING_CHANGE_HANDOFF_CONSTANTS: {
8351
+ /** Share of the schedule's name/title terms the decision text must contain */
8352
+ readonly MIN_COVERAGE: 0.6;
8353
+ /** Distinct terms the decision and the schedule must share */
8354
+ readonly MIN_SHARED_TERMS: 2;
8355
+ /** ...or this many shared terms whatever the coverage (labels mixing two languages) */
8356
+ readonly SURE_SHARED_TERMS: 3;
8357
+ /** Terms shorter than this (Latin words) are noise */
8358
+ readonly MIN_WORD_CHARS: 3;
8359
+ /** `WorkItem.metadata` marker on the hand-off WorkItem */
8360
+ readonly METADATA_KEY: "recurringChangeHandoff";
8361
+ /** Option labels / answers that decline a change (nothing to hand off) */
8362
+ readonly DECLINE_PATTERN: RegExp;
8363
+ };
8286
8364
  //# sourceMappingURL=constants.d.ts.map