agentic-team 0.0.0-stage → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +189 -2
- package/bin/build-template.js +208 -0
- package/bin/index.js +1334 -0
- package/bin/platforms.js +510 -0
- package/package.json +41 -4
- package/template/.claude-plugin/plugin.json +10 -0
- package/template/.orchestra/README.md +391 -0
- package/template/.orchestra/blueprints/README.md +87 -0
- package/template/.orchestra/blueprints/api-only.md +46 -0
- package/template/.orchestra/blueprints/component-crud-resource.md +25 -0
- package/template/.orchestra/blueprints/saas-starter.md +69 -0
- package/template/.orchestra/config.yml +293 -0
- package/template/.orchestra/roles/orchestrator.md +105 -0
- package/template/.orchestra/roles/product-manager.md +439 -0
- package/template/.orchestra/scripts/compact-diff.awk +45 -0
- package/template/.orchestra/scripts/context.mjs +373 -0
- package/template/.orchestra/stacks/README.md +81 -0
- package/template/.orchestra/stacks/_lib/common.sh +3095 -0
- package/template/.orchestra/stacks/_repo/setup.sh +1113 -0
- package/template/.orchestra/stacks/backend-hono/claude/commands/endpoint.md +13 -0
- package/template/.orchestra/stacks/backend-hono/claude/skills/api-endpoint/SKILL.md +111 -0
- package/template/.orchestra/stacks/backend-hono/claude/skills/testing/SKILL.md +75 -0
- package/template/.orchestra/stacks/backend-hono/setup.sh +5945 -0
- package/template/.orchestra/stacks/backend-hono/stack.md +195 -0
- package/template/.orchestra/stacks/mobile-android-native/claude/commands/component.md +13 -0
- package/template/.orchestra/stacks/mobile-android-native/claude/skills/design-component/SKILL.md +58 -0
- package/template/.orchestra/stacks/mobile-android-native/setup.sh +607 -0
- package/template/.orchestra/stacks/mobile-android-native/stack.md +138 -0
- package/template/.orchestra/stacks/mobile-expo/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/mobile-expo/claude/skills/design-component/SKILL.md +92 -0
- package/template/.orchestra/stacks/mobile-expo/setup.sh +470 -0
- package/template/.orchestra/stacks/mobile-expo/stack.md +141 -0
- package/template/.orchestra/stacks/mobile-ios-native/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/mobile-ios-native/claude/skills/design-component/SKILL.md +58 -0
- package/template/.orchestra/stacks/mobile-ios-native/setup.sh +468 -0
- package/template/.orchestra/stacks/mobile-ios-native/stack.md +130 -0
- package/template/.orchestra/stacks/web-nextjs/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/web-nextjs/claude/skills/design-component/SKILL.md +100 -0
- package/template/.orchestra/stacks/web-nextjs/claude/skills/design-tokens/SKILL.md +61 -0
- package/template/.orchestra/stacks/web-nextjs/setup.sh +128 -0
- package/template/.orchestra/stacks/web-nextjs/stack.md +170 -0
- package/template/.orchestra/stacks/web-vite-react/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/web-vite-react/claude/skills/design-component/SKILL.md +100 -0
- package/template/.orchestra/stacks/web-vite-react/claude/skills/design-tokens/SKILL.md +61 -0
- package/template/.orchestra/stacks/web-vite-react/setup.sh +185 -0
- package/template/.orchestra/stacks/web-vite-react/stack.md +177 -0
- package/template/.orchestra/trackers/github.sh +467 -0
- package/template/CLAUDE.md +54 -0
- package/template/agents/architect.md +83 -0
- package/template/agents/engineer-high.md +17 -0
- package/template/agents/engineer-low.md +17 -0
- package/template/agents/engineer-max.md +17 -0
- package/template/agents/engineer-medium.md +17 -0
- package/template/agents/engineer-xhigh.md +17 -0
- package/template/agents/lead.md +925 -0
- package/template/agents/milestone-reviewer.md +65 -0
- package/template/agents/orchestrator.md +105 -0
- package/template/agents/product-manager.md +439 -0
- package/template/agents/re-reviewer.md +25 -0
- package/template/agents/reviewer.md +303 -0
- package/template/agents/scaffolder.md +206 -0
- package/template/agents/tracker.md +202 -0
- package/template/commands/blueprint.md +23 -0
- package/template/commands/create-role.md +64 -0
- package/template/commands/export.md +45 -0
- package/template/commands/help.md +56 -0
- package/template/commands/hotfix.md +14 -0
- package/template/commands/orchestrator.md +12 -0
- package/template/commands/pm.md +18 -0
- package/template/commands/rewind.md +63 -0
- package/template/commands/setup.md +15 -0
- package/template/commands/start.md +29 -0
- package/template/commands/status.md +11 -0
- package/template/commands/sync.md +76 -0
- package/template/commands/verifier.md +53 -0
- package/template/hooks/audit-context.sh +166 -0
- package/template/hooks/enforce-role.sh +193 -0
- package/template/hooks/protect-system-files.sh +123 -0
- package/template/hooks/review-rounds.sh +172 -0
- package/template/rules/acceptance-check.orchestra.md +11 -0
- package/template/rules/analytics-kpi.orchestra.md +56 -0
- package/template/rules/code-standards.orchestra.md +35 -0
- package/template/rules/comments.orchestra.md +77 -0
- package/template/rules/commit-format.orchestra.md +20 -0
- package/template/rules/dependency-upgrade.orchestra.md +112 -0
- package/template/rules/design-parity.orchestra.md +181 -0
- package/template/rules/observability.orchestra.md +121 -0
- package/template/rules/performance.orchestra.md +190 -0
- package/template/rules/phase-limits.orchestra.md +3 -0
- package/template/rules/pull-request.orchestra.md +218 -0
- package/template/rules/role-boundaries.orchestra.md +132 -0
- package/template/rules/security.orchestra.md +106 -0
- package/template/rules/stuck-detection.orchestra.md +25 -0
- package/template/rules/testing-standards.orchestra.md +219 -0
- package/template/rules/tracker-authority.orchestra.md +198 -0
- package/template/rules/verification-gate.orchestra.md +56 -0
- package/template/skills/accessibility/SKILL.md +31 -0
- package/template/skills/android-testing/SKILL.md +112 -0
- package/template/skills/architecture/SKILL.md +175 -0
- package/template/skills/auth-setup/SKILL.md +105 -0
- package/template/skills/best-practices/SKILL.md +29 -0
- package/template/skills/code-optimizer/SKILL.md +27 -0
- package/template/skills/core-web-vitals/SKILL.md +32 -0
- package/template/skills/crud-api/SKILL.md +32 -0
- package/template/skills/debug/SKILL.md +28 -0
- package/template/skills/deployment/SKILL.md +104 -0
- package/template/skills/error-handling/SKILL.md +154 -0
- package/template/skills/frontend-design/SKILL.md +30 -0
- package/template/skills/fullstack-infrastructure/SKILL.md +108 -0
- package/template/skills/ios-testing/SKILL.md +100 -0
- package/template/skills/mobile-testing/SKILL.md +102 -0
- package/template/skills/observability/SKILL.md +91 -0
- package/template/skills/project-structure/SKILL.md +114 -0
- package/template/skills/push-notifications/SKILL.md +146 -0
- package/template/skills/react-best-practices/SKILL.md +31 -0
- package/template/skills/realtime/SKILL.md +146 -0
- package/template/skills/review/SKILL.md +82 -0
- package/template/skills/testing/SKILL.md +45 -0
- package/template/skills/web-quality-audit/SKILL.md +47 -0
- package/template/skills/web-testing/SKILL.md +120 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# GitHub Projects adapter.
|
|
3
|
+
#
|
|
4
|
+
# A script rather than instructions in an agent prompt, for the same reason
|
|
5
|
+
# scaffolding is a script: an agent improvising GraphQL produces a different
|
|
6
|
+
# board every run and leaves nothing to review. Every subcommand here is
|
|
7
|
+
# idempotent, so re-running updates rather than duplicating.
|
|
8
|
+
#
|
|
9
|
+
# discover <project-number> read the board's real fields
|
|
10
|
+
# export <milestone-dir> <project-num> create/update epic and sub-issues
|
|
11
|
+
# status-get <issue-number> print the board status
|
|
12
|
+
# status-set <issue-number> <status> move it — board from tracker.board
|
|
13
|
+
# comments <issue-number> the thread, each marked allowed or not
|
|
14
|
+
# reply <issue> <comment-id> <file> answer one comment
|
|
15
|
+
#
|
|
16
|
+
# Every mutation used here was verified against the live schema before it was
|
|
17
|
+
# written — addProjectV2ItemById, updateProjectV2ItemFieldValue, addSubIssue.
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
die() { printf ' [x] %s\n' "$*" >&2; exit 1; }
|
|
21
|
+
log() { printf ' → %s\n' "$*" >&2; }
|
|
22
|
+
|
|
23
|
+
command -v gh >/dev/null 2>&1 || die "gh is not installed"
|
|
24
|
+
gh auth status >/dev/null 2>&1 || die "gh is not authenticated — run: gh auth login"
|
|
25
|
+
|
|
26
|
+
REPO="$(gh repo view --json nameWithOwner --jq .nameWithOwner)"
|
|
27
|
+
OWNER="${REPO%%/*}"
|
|
28
|
+
|
|
29
|
+
# ── the board ────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
# A Projects v2 board can hang off a user or an organisation, and the query
|
|
32
|
+
# differs. Try the viewer first, then the owner as an organisation, rather
|
|
33
|
+
# than asking the caller to know which they have.
|
|
34
|
+
project_query() {
|
|
35
|
+
local num="$1" scope
|
|
36
|
+
for scope in "viewer" "organization(login: \"$OWNER\")" "user(login: \"$OWNER\")"; do
|
|
37
|
+
local out
|
|
38
|
+
out="$(gh api graphql -f query="
|
|
39
|
+
{ $scope { projectV2(number: $num) {
|
|
40
|
+
id title
|
|
41
|
+
fields(first: 50) { nodes {
|
|
42
|
+
... on ProjectV2SingleSelectField { id name options { id name } }
|
|
43
|
+
... on ProjectV2FieldCommon { id name }
|
|
44
|
+
} }
|
|
45
|
+
} } }" 2>/dev/null)" || continue
|
|
46
|
+
# A scope that does not hold the board answers with a null projectV2
|
|
47
|
+
# rather than an error, so the exit code proves nothing on its own.
|
|
48
|
+
printf '%s' "$out" | grep -q '"projectV2":null' && continue
|
|
49
|
+
printf '%s' "$out"
|
|
50
|
+
return 0
|
|
51
|
+
done
|
|
52
|
+
die "no Projects v2 board number $num on $OWNER"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
cmd_discover() {
|
|
56
|
+
local num="${1:?usage: discover <project-number>}"
|
|
57
|
+
local json status_opts
|
|
58
|
+
json="$(project_query "$num")"
|
|
59
|
+
|
|
60
|
+
status_opts="$(printf '%s' "$json" \
|
|
61
|
+
| python3 -c '
|
|
62
|
+
import json,sys
|
|
63
|
+
d = json.load(sys.stdin)["data"]
|
|
64
|
+
p = next(v for v in d.values() if v)["projectV2"]
|
|
65
|
+
f = next((n for n in p["fields"]["nodes"] if n.get("name") == "Status"), None)
|
|
66
|
+
if not f or "options" not in f:
|
|
67
|
+
sys.exit("the board has no Status single-select field")
|
|
68
|
+
print(p["id"])
|
|
69
|
+
for o in f["options"]:
|
|
70
|
+
print(o["name"])
|
|
71
|
+
')"
|
|
72
|
+
|
|
73
|
+
local board_id; board_id="$(printf '%s' "$status_opts" | head -1)"
|
|
74
|
+
local names; names="$(printf '%s' "$status_opts" | tail -n +2)"
|
|
75
|
+
|
|
76
|
+
# Printed as the config block to paste, not written in place: config.yml is
|
|
77
|
+
# merged on upgrade and hand-edited by the user, and a script that rewrites
|
|
78
|
+
# it silently is a script that loses a comment somebody wrote.
|
|
79
|
+
printf 'tracker:\n provider: github\n board: "%s"\n states:\n' "$num"
|
|
80
|
+
# Orchestra's internal states are fixed; only the mapping varies. Matched
|
|
81
|
+
# case-insensitively against what the board actually calls them, and left
|
|
82
|
+
# blank when the board has no such column — blank is an honest "you decide",
|
|
83
|
+
# a guess is an agent moving a task to a column that does not exist.
|
|
84
|
+
# Alternation, first match wins: a stock board calls the backlog "Todo",
|
|
85
|
+
# a renamed one "Backlog", and either is the column export lands in.
|
|
86
|
+
map_state() {
|
|
87
|
+
local want="$1" match
|
|
88
|
+
match="$(printf '%s\n' "$names" | grep -ixE "$want" | head -1 || true)"
|
|
89
|
+
printf ' %s: "%s"\n' "$2" "$match"
|
|
90
|
+
}
|
|
91
|
+
map_state "Backlog|To do|Todo" backlog
|
|
92
|
+
map_state "Ready" ready
|
|
93
|
+
map_state "In progress" in_progress
|
|
94
|
+
map_state "In review" in_review
|
|
95
|
+
map_state "Done" done
|
|
96
|
+
map_state "Blocked" blocked
|
|
97
|
+
map_state "Tech debt|Tech-debt" tech_debt
|
|
98
|
+
printf '\n' >&2
|
|
99
|
+
log "board id $board_id — columns: $(printf '%s' "$names" | tr '\n' ',' | sed 's/,$//')"
|
|
100
|
+
printf '%s\n' "$names" | grep -qix "in review" \
|
|
101
|
+
|| log "warning: no 'In review' column — the sync loop has nowhere to move a task"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# ── export ───────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
# The issue whose title matches, or empty. This is what makes export
|
|
107
|
+
# idempotent without storing anything: a second run finds the first run's
|
|
108
|
+
# issue instead of opening another. A second title is the one an earlier
|
|
109
|
+
# version of this script gave the same item — `[slug] phase-1` — so a board
|
|
110
|
+
# exported before titles became sentences is updated, not duplicated.
|
|
111
|
+
find_issue() { # title [legacy-title]
|
|
112
|
+
local num
|
|
113
|
+
num="$(gh issue list --state all --search "$1 in:title" --json number,title \
|
|
114
|
+
--jq "[.[] | select(.title == \"$1\")] | .[0].number // empty" 2>/dev/null)"
|
|
115
|
+
if [ -z "$num" ] && [ -n "${2:-}" ]; then
|
|
116
|
+
num="$(gh issue list --state all --search "$2 in:title" --json number,title \
|
|
117
|
+
--jq "[.[] | select(.title == \"$2\")] | .[0].number // empty" 2>/dev/null)"
|
|
118
|
+
# Found under the old name: rename it, so the next run finds it directly.
|
|
119
|
+
[ -n "$num" ] && gh issue edit "$num" --title "$1" >/dev/null
|
|
120
|
+
fi
|
|
121
|
+
printf '%s' "$num"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# What the board shows. The phase file is the engineer's document — a
|
|
125
|
+
# frontmatter of knobs, a Result section lead fills in later — and none of
|
|
126
|
+
# that belongs in front of a person reading the task. Frontmatter and Result
|
|
127
|
+
# are dropped; everything else stays, because with a provider this body is
|
|
128
|
+
# what lead reads back and hands to the sub-agent, and Scope and Acceptance
|
|
129
|
+
# Criteria have to arrive intact.
|
|
130
|
+
board_body() { # phase-or-milestone-file → path of a temp file
|
|
131
|
+
local out
|
|
132
|
+
out="$(mktemp)"
|
|
133
|
+
python3 - "$1" > "$out" <<'PY'
|
|
134
|
+
import re, sys
|
|
135
|
+
s = open(sys.argv[1]).read()
|
|
136
|
+
if s.startswith("---\n"):
|
|
137
|
+
end = s.find("\n---\n", 4)
|
|
138
|
+
if end != -1:
|
|
139
|
+
s = s[end + 5:]
|
|
140
|
+
s = re.sub(r"\n## Result\n[\s\S]*$", "\n", s)
|
|
141
|
+
sys.stdout.write(s.strip() + "\n")
|
|
142
|
+
PY
|
|
143
|
+
printf '%s' "$out"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# `title:` from a file's frontmatter, or nothing.
|
|
147
|
+
front_title() {
|
|
148
|
+
python3 - "$1" <<'PY'
|
|
149
|
+
import re, sys
|
|
150
|
+
s = open(sys.argv[1]).read()
|
|
151
|
+
if not s.startswith("---\n"): sys.exit(0)
|
|
152
|
+
end = s.find("\n---\n", 4)
|
|
153
|
+
m = re.search(r"^title:[ \t]*(.+)$", s[4:end], re.M) if end != -1 else None
|
|
154
|
+
if m: print(m.group(1).split(" #")[0].strip().strip('"').strip("'"))
|
|
155
|
+
PY
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Prints the number, and sets UPSERT_CREATED to 1 when the issue is new. A
|
|
159
|
+
# re-export updates the body and must not touch status: the task may be in
|
|
160
|
+
# progress, and moving it back to the backlog would tell lead a human did.
|
|
161
|
+
upsert_issue() { # title body-file [legacy-title]
|
|
162
|
+
local title="$1" body="$2" num
|
|
163
|
+
UPSERT_CREATED=0
|
|
164
|
+
num="$(find_issue "$title" "${3:-}")"
|
|
165
|
+
if [ -n "$num" ]; then
|
|
166
|
+
gh issue edit "$num" --body-file "$body" >/dev/null
|
|
167
|
+
printf '%s' "$num"
|
|
168
|
+
else
|
|
169
|
+
UPSERT_CREATED=1
|
|
170
|
+
gh issue create --title "$title" --body-file "$body" \
|
|
171
|
+
| grep -oE '[0-9]+$'
|
|
172
|
+
fi
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
issue_node_id() { gh issue view "$1" --json id --jq .id; }
|
|
176
|
+
|
|
177
|
+
link_sub_issue() { # parent-number child-number
|
|
178
|
+
local parent child
|
|
179
|
+
parent="$(issue_node_id "$1")"
|
|
180
|
+
child="$(issue_node_id "$2")"
|
|
181
|
+
# Already linked is not an error worth stopping for — export is meant to be
|
|
182
|
+
# re-runnable, and GitHub rejects a duplicate link rather than ignoring it.
|
|
183
|
+
gh api graphql -f query='
|
|
184
|
+
mutation($p: ID!, $c: ID!) {
|
|
185
|
+
addSubIssue(input: {issueId: $p, subIssueId: $c}) { issue { number } }
|
|
186
|
+
}' -f p="$parent" -f c="$child" >/dev/null 2>&1 || true
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
board_add() { # project-number issue-number → item id
|
|
190
|
+
local proj_id issue_id
|
|
191
|
+
proj_id="$(project_query "$1" | python3 -c 'import json,sys; d=json.load(sys.stdin)["data"]; print(next(v for v in d.values() if v)["projectV2"]["id"])')"
|
|
192
|
+
issue_id="$(issue_node_id "$2")"
|
|
193
|
+
gh api graphql -f query='
|
|
194
|
+
mutation($p: ID!, $c: ID!) {
|
|
195
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
196
|
+
}' -f p="$proj_id" -f c="$issue_id" --jq '.data.addProjectV2ItemById.item.id'
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
cmd_export() {
|
|
200
|
+
local dir="${1:?usage: export <milestone-dir> <project-number>}"
|
|
201
|
+
local proj="${2:?usage: export <milestone-dir> <project-number>}"
|
|
202
|
+
[ -d "$dir" ] || die "no milestone directory at $dir"
|
|
203
|
+
[ -f "$dir/milestone.md" ] || die "$dir has no milestone.md"
|
|
204
|
+
|
|
205
|
+
local slug epic
|
|
206
|
+
slug="$(basename "$dir")"
|
|
207
|
+
log "exporting $slug to project $proj"
|
|
208
|
+
|
|
209
|
+
# The epic is titled by the milestone's heading — "Milestone: M1 — User
|
|
210
|
+
# auth" becomes "User auth" — and the task by the phase's `title:`. A phase
|
|
211
|
+
# without one falls back to the old shape, and the PM's reviewer flags it.
|
|
212
|
+
local epic_title body
|
|
213
|
+
epic_title="$(grep -m1 -E '^# ' "$dir/milestone.md" | sed -E 's/^# *(Milestone: *)?(M[0-9]+ *[—-] *)?//')"
|
|
214
|
+
[ -n "$epic_title" ] || epic_title="$slug"
|
|
215
|
+
body="$(board_body "$dir/milestone.md")"
|
|
216
|
+
epic="$(upsert_issue "$epic_title" "$body" "[epic] $slug")"
|
|
217
|
+
rm -f "$body"
|
|
218
|
+
board_add "$proj" "$epic" >/dev/null
|
|
219
|
+
log "epic #$epic"
|
|
220
|
+
|
|
221
|
+
{
|
|
222
|
+
printf 'provider: github\n'
|
|
223
|
+
printf 'board: "%s"\n' "$proj"
|
|
224
|
+
printf 'epic:\n number: %s\n url: "https://github.com/%s/issues/%s"\n' "$epic" "$REPO" "$epic"
|
|
225
|
+
printf 'tasks:\n'
|
|
226
|
+
} > "$dir/tracker.yml"
|
|
227
|
+
|
|
228
|
+
# A new task lands in the backlog, and the human moves it to ready when
|
|
229
|
+
# lead may take it. Read from config so the column is the board's own name;
|
|
230
|
+
# blank means discovery found no such column, and the task is left where
|
|
231
|
+
# the board puts it rather than moved to a guess.
|
|
232
|
+
local backlog
|
|
233
|
+
backlog="$(config_state backlog)"
|
|
234
|
+
[ -n "$backlog" ] || log "warning: tracker.states.backlog is empty — new tasks keep the board's default column"
|
|
235
|
+
|
|
236
|
+
local phase name task
|
|
237
|
+
for phase in "$dir"/phases/*.md; do
|
|
238
|
+
[ -e "$phase" ] || break
|
|
239
|
+
name="$(basename "$phase" .md)"
|
|
240
|
+
local title
|
|
241
|
+
title="$(front_title "$phase")"
|
|
242
|
+
[ -n "$title" ] || { title="[$slug] $name"; log "warning: $name has no title: — using $title"; }
|
|
243
|
+
body="$(board_body "$phase")"
|
|
244
|
+
task="$(upsert_issue "$title" "$body" "[$slug] $name")"
|
|
245
|
+
rm -f "$body"
|
|
246
|
+
link_sub_issue "$epic" "$task"
|
|
247
|
+
board_add "$proj" "$task" >/dev/null
|
|
248
|
+
if [ "$UPSERT_CREATED" = 1 ] && [ -n "$backlog" ]; then
|
|
249
|
+
cmd_status_set "$task" "$backlog" "$proj"
|
|
250
|
+
fi
|
|
251
|
+
printf ' - phase: "%s"\n number: %s\n url: "https://github.com/%s/issues/%s"\n' \
|
|
252
|
+
"$name" "$task" "$REPO" "$task" >> "$dir/tracker.yml"
|
|
253
|
+
log "task #$task — $name"
|
|
254
|
+
done
|
|
255
|
+
|
|
256
|
+
log "wrote $dir/tracker.yml"
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# ── status ───────────────────────────────────────────────────────────────────
|
|
260
|
+
|
|
261
|
+
cmd_status_get() {
|
|
262
|
+
local num="${1:?usage: status-get <issue-number>}" out
|
|
263
|
+
# `repository.issue` answers null for a pull request number rather than
|
|
264
|
+
# erroring cleanly, and gh then prints the whole GraphQL envelope and exits
|
|
265
|
+
# 1. Read the output instead of the exit code, so the caller gets a sentence.
|
|
266
|
+
out="$(gh api graphql -f query='
|
|
267
|
+
query($owner: String!, $repo: String!, $n: Int!) {
|
|
268
|
+
repository(owner: $owner, name: $repo) { issue(number: $n) {
|
|
269
|
+
projectItems(first: 10) { nodes {
|
|
270
|
+
fieldValueByName(name: "Status") {
|
|
271
|
+
... on ProjectV2ItemFieldSingleSelectValue { name }
|
|
272
|
+
} } } } } }' \
|
|
273
|
+
-f owner="$OWNER" -f repo="${REPO#*/}" -F n="$num" 2>/dev/null || true)"
|
|
274
|
+
|
|
275
|
+
printf '%s' "$out" | grep -q '"issue":null' \
|
|
276
|
+
&& die "#$num is not an issue in $REPO — a pull request number will do this"
|
|
277
|
+
|
|
278
|
+
# No output and exit 0 is the honest answer for an issue that is on no
|
|
279
|
+
# board: it has no status, which is different from a failure to read one.
|
|
280
|
+
printf '%s' "$out" \
|
|
281
|
+
| python3 -c '
|
|
282
|
+
import json,sys
|
|
283
|
+
try: d = json.load(sys.stdin)["data"]["repository"]["issue"]["projectItems"]["nodes"]
|
|
284
|
+
except Exception: sys.exit(0)
|
|
285
|
+
for n in d:
|
|
286
|
+
v = n.get("fieldValueByName") or {}
|
|
287
|
+
if v.get("name"):
|
|
288
|
+
print(v["name"]); break
|
|
289
|
+
'
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# tracker.board from config.yml.
|
|
293
|
+
#
|
|
294
|
+
# Read here rather than demanded as an argument. github.sh needs the board to
|
|
295
|
+
# resolve a status option and a connector-backed provider does not, so
|
|
296
|
+
# requiring it positionally
|
|
297
|
+
# gave the two adapters different signatures — and the agents call one
|
|
298
|
+
# interface, so lead ran the same command and it worked on one provider only.
|
|
299
|
+
# A third argument still overrides, for a one-off.
|
|
300
|
+
config_board() {
|
|
301
|
+
local cfg
|
|
302
|
+
cfg="$(git rev-parse --show-toplevel 2>/dev/null)/.orchestra/config.yml"
|
|
303
|
+
[ -f "$cfg" ] || return 0
|
|
304
|
+
python3 - "$cfg" <<'CFG'
|
|
305
|
+
import re, sys
|
|
306
|
+
for b in re.split(r"\n(?=[a-z_]+:)", open(sys.argv[1]).read()):
|
|
307
|
+
if b.startswith("tracker:"):
|
|
308
|
+
m = re.search(r"^[ \t]*board:[ \t]*(.*)$", b, re.M)
|
|
309
|
+
if m:
|
|
310
|
+
print(m.group(1).strip().strip(chr(34)).strip(chr(39)))
|
|
311
|
+
break
|
|
312
|
+
CFG
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
# tracker.states.<name> from config.yml — the board's own column name for one
|
|
316
|
+
# of Orchestra's states, or nothing when discovery left it blank.
|
|
317
|
+
config_state() {
|
|
318
|
+
local cfg
|
|
319
|
+
cfg="$(git rev-parse --show-toplevel 2>/dev/null)/.orchestra/config.yml"
|
|
320
|
+
[ -f "$cfg" ] || return 0
|
|
321
|
+
python3 - "$cfg" "$1" <<'CFG'
|
|
322
|
+
import re, sys
|
|
323
|
+
for b in re.split(r"\n(?=[a-z_]+:)", open(sys.argv[1]).read()):
|
|
324
|
+
if b.startswith("tracker:"):
|
|
325
|
+
m = re.search(r"^[ \t]*" + re.escape(sys.argv[2]) + r":[ \t]*(.*)$", b, re.M)
|
|
326
|
+
if m:
|
|
327
|
+
print(m.group(1).split("#")[0].strip().strip(chr(34)).strip(chr(39)))
|
|
328
|
+
break
|
|
329
|
+
CFG
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
cmd_status_set() {
|
|
333
|
+
local num="${1:?usage: status-set <issue-number> <status> [project-number]}"
|
|
334
|
+
local want="${2:?usage: status-set <issue-number> <status> [project-number]}"
|
|
335
|
+
local proj="${3:-$(config_board)}"
|
|
336
|
+
[ -n "$proj" ] || die "no board — pass the project number, or set tracker.board in .orchestra/config.yml"
|
|
337
|
+
|
|
338
|
+
local json proj_id field_id opt_id item_id
|
|
339
|
+
json="$(project_query "$proj")"
|
|
340
|
+
proj_id="$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin)["data"]; print(next(v for v in d.values() if v)["projectV2"]["id"])')"
|
|
341
|
+
# Into a variable first, and the exit code checked. `read <<< "$(...)"`
|
|
342
|
+
# succeeds even when the substitution died, so a board with no such column
|
|
343
|
+
# printed the right message and then fired the mutation with an empty field
|
|
344
|
+
# id — "could not resolve a node with the global id of ''".
|
|
345
|
+
local resolved
|
|
346
|
+
resolved="$(printf '%s' "$json" | python3 -c "
|
|
347
|
+
import json,sys
|
|
348
|
+
d = json.load(sys.stdin)['data']
|
|
349
|
+
p = next(v for v in d.values() if v)['projectV2']
|
|
350
|
+
f = next((n for n in p['fields']['nodes'] if n.get('name') == 'Status'), None)
|
|
351
|
+
if not f: sys.exit('the board has no Status field')
|
|
352
|
+
o = next((o for o in f['options'] if o['name'].lower() == '''$want'''.lower()), None)
|
|
353
|
+
if not o: sys.exit('the board has no column named \"$want\" — it has: ' + ', '.join(x['name'] for x in f['options']))
|
|
354
|
+
print(f['id'], o['id'])
|
|
355
|
+
")" || exit 1
|
|
356
|
+
read -r field_id opt_id <<< "$resolved"
|
|
357
|
+
|
|
358
|
+
item_id="$(gh api graphql -f query='
|
|
359
|
+
query($owner: String!, $repo: String!, $n: Int!) {
|
|
360
|
+
repository(owner: $owner, name: $repo) { issue(number: $n) {
|
|
361
|
+
projectItems(first: 10) { nodes { id project { id } } } } } }' \
|
|
362
|
+
-f owner="$OWNER" -f repo="${REPO#*/}" -F n="$num" \
|
|
363
|
+
--jq ".data.repository.issue.projectItems.nodes[] | select(.project.id == \"$proj_id\") | .id")"
|
|
364
|
+
[ -n "$item_id" ] || item_id="$(board_add "$proj" "$num")"
|
|
365
|
+
|
|
366
|
+
gh api graphql -f query='
|
|
367
|
+
mutation($p: ID!, $i: ID!, $f: ID!, $o: String!) {
|
|
368
|
+
updateProjectV2ItemFieldValue(input: {
|
|
369
|
+
projectId: $p, itemId: $i, fieldId: $f,
|
|
370
|
+
value: {singleSelectOptionId: $o}
|
|
371
|
+
}) { projectV2Item { id } } }' \
|
|
372
|
+
-f p="$proj_id" -f i="$item_id" -f f="$field_id" -f o="$opt_id" >/dev/null
|
|
373
|
+
log "#$num → $want"
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
# ── comments ─────────────────────────────────────────────────────────────────
|
|
377
|
+
|
|
378
|
+
# tracker.actors from config.yml, one login per line.
|
|
379
|
+
#
|
|
380
|
+
# Read here rather than left to the agent's judgement. The allowlist is what
|
|
381
|
+
# stops a board comment from being an instruction channel, and a rule enforced
|
|
382
|
+
# by a prompt is a rule that holds until the prompt is summarised.
|
|
383
|
+
allowlist() {
|
|
384
|
+
local cfg
|
|
385
|
+
cfg="$(git rev-parse --show-toplevel 2>/dev/null)/.orchestra/config.yml"
|
|
386
|
+
[ -f "$cfg" ] || return 0
|
|
387
|
+
python3 - "$cfg" <<'PY'
|
|
388
|
+
import re, sys
|
|
389
|
+
block = re.split(r"\n(?=[a-z_]+:)", open(sys.argv[1]).read())
|
|
390
|
+
for b in block:
|
|
391
|
+
if not b.startswith("tracker:"):
|
|
392
|
+
continue
|
|
393
|
+
# [ \t]* and not \s*: \s matches a newline, so a greedy \s* after the
|
|
394
|
+
# colon swallowed the first item of a block list into the inline capture
|
|
395
|
+
# and the allowlist silently lost its first entry.
|
|
396
|
+
m = re.search(r"^[ \t]*actors:[ \t]*(.*)$", b, re.M)
|
|
397
|
+
if not m:
|
|
398
|
+
break
|
|
399
|
+
inline = m.group(1).strip()
|
|
400
|
+
if inline.startswith("["):
|
|
401
|
+
for name in re.findall(r'"([^"]+)"|\'([^\']+)\'', inline):
|
|
402
|
+
print(name[0] or name[1])
|
|
403
|
+
else:
|
|
404
|
+
# lstrip the newline the match ends on: without it the first element
|
|
405
|
+
# is an empty string, which is not a list item, and a block-style
|
|
406
|
+
# actors: parsed as nobody while the inline form worked.
|
|
407
|
+
for line in b[m.end():].lstrip("\n").split("\n"):
|
|
408
|
+
item = re.match(r'^\s+-\s*["\']?([^"\'\s]+)', line)
|
|
409
|
+
if not item:
|
|
410
|
+
break
|
|
411
|
+
print(item.group(1))
|
|
412
|
+
PY
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
cmd_comments() {
|
|
416
|
+
local num="${1:?usage: comments <issue-number>}" allowed
|
|
417
|
+
allowed="$(allowlist)"
|
|
418
|
+
# Every comment is reported, allowed or not — one so it can be acted on, the
|
|
419
|
+
# other so the user is told somebody else is writing on their board. Silently
|
|
420
|
+
# dropping it would hide exactly the event worth noticing.
|
|
421
|
+
gh api "repos/$REPO/issues/$num/comments" --paginate 2>/dev/null \
|
|
422
|
+
| ALLOWED="$allowed" python3 -c '
|
|
423
|
+
import json, os, sys
|
|
424
|
+
allowed = {l.strip() for l in os.environ["ALLOWED"].splitlines() if l.strip()}
|
|
425
|
+
for c in json.load(sys.stdin):
|
|
426
|
+
login = (c.get("user") or {}).get("login", "")
|
|
427
|
+
print(json.dumps({
|
|
428
|
+
"id": c["id"],
|
|
429
|
+
"author": login,
|
|
430
|
+
"allowed": login in allowed,
|
|
431
|
+
"created_at": c["created_at"],
|
|
432
|
+
"updated_at": c["updated_at"],
|
|
433
|
+
"body": c["body"],
|
|
434
|
+
}))
|
|
435
|
+
'
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
cmd_reply() {
|
|
439
|
+
local num="${1:?usage: reply <issue-number> <comment-id> <body-file>}"
|
|
440
|
+
local cid="${2:?usage: reply <issue-number> <comment-id> <body-file>}"
|
|
441
|
+
local file="${3:?usage: reply <issue-number> <comment-id> <body-file>}"
|
|
442
|
+
[ -f "$file" ] || die "no body file at $file"
|
|
443
|
+
|
|
444
|
+
local author
|
|
445
|
+
author="$(gh api "repos/$REPO/issues/comments/$cid" --jq '.user.login' 2>/dev/null || true)"
|
|
446
|
+
[ -n "$author" ] || die "no comment $cid on $REPO"
|
|
447
|
+
allowlist | grep -qx "$author" \
|
|
448
|
+
|| die "comment $cid is from @$author, who is not in tracker.actors — reporting, not acting"
|
|
449
|
+
|
|
450
|
+
# The marker that makes the loop idempotent with no local state: the reply
|
|
451
|
+
# names the comment it answers, so a second run sees the work was done. An
|
|
452
|
+
# edited comment is caught by comparing its updated_at to this reply's time.
|
|
453
|
+
local tmp; tmp="$(mktemp)"
|
|
454
|
+
{ printf '> in reply to comment %s by @%s\n\n' "$cid" "$author"; cat "$file"; } > "$tmp"
|
|
455
|
+
gh issue comment "$num" --body-file "$tmp"
|
|
456
|
+
rm -f "$tmp"
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
case "${1:-}" in
|
|
460
|
+
discover) shift; cmd_discover "$@" ;;
|
|
461
|
+
export) shift; cmd_export "$@" ;;
|
|
462
|
+
status-get) shift; cmd_status_get "$@" ;;
|
|
463
|
+
status-set) shift; cmd_status_set "$@" ;;
|
|
464
|
+
comments) shift; cmd_comments "$@" ;;
|
|
465
|
+
reply) shift; cmd_reply "$@" ;;
|
|
466
|
+
*) die "usage: github.sh {discover|export|status-get|status-set|comments|reply} ..." ;;
|
|
467
|
+
esac
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
<!-- orchestra -->
|
|
6
|
+
## Orchestra — AI Team Orchestration System
|
|
7
|
+
|
|
8
|
+
This project uses Orchestra for multi-agent coordination via `.orchestra/` and `.claude/`.
|
|
9
|
+
|
|
10
|
+
**FIRST RESPONSE — check the project before asking anything.**
|
|
11
|
+
|
|
12
|
+
If `.orchestra/milestones/` holds no milestone directory — a `.gitkeep` is
|
|
13
|
+
not a milestone — **and** there is no app yet, meaning no `apps/` and no root
|
|
14
|
+
`package.json`, then this is a fresh repo. Say so in one line
|
|
15
|
+
and point at `/orchestra:setup`. Do not offer a role: there is nothing for a
|
|
16
|
+
PM to plan into and nothing for a lead to build, and asking which hat to wear
|
|
17
|
+
before the project exists is the wrong first question.
|
|
18
|
+
|
|
19
|
+
Otherwise, unless the user explicitly names a role:
|
|
20
|
+
|
|
21
|
+
Call `AskUserQuestion` with:
|
|
22
|
+
- header: "Role"
|
|
23
|
+
- question: "Which role will you take for this session? (pick one)"
|
|
24
|
+
- multiSelect: false
|
|
25
|
+
- options:
|
|
26
|
+
1. Orchestrator — Maintain and evolve Orchestra system files, roles, and rules
|
|
27
|
+
2. Product Manager — Write PRDs, create milestones with phases, orchestrate pipeline
|
|
28
|
+
3. Discussion — Just brainstorm, no role needed
|
|
29
|
+
|
|
30
|
+
If user skips or starts giving instructions directly — work with them normally.
|
|
31
|
+
Sub-agents spawned by lead skip role selection — they already have a role assigned in their prompt.
|
|
32
|
+
Do NOT greet. Do NOT explain. The role selection IS your greeting.
|
|
33
|
+
|
|
34
|
+
**AFTER ROLE SELECTED:**
|
|
35
|
+
|
|
36
|
+
1. Read `.orchestra/roles/{role-id}.md` for identity and ownership
|
|
37
|
+
2. Read `.orchestra/config.yml` for pipeline settings
|
|
38
|
+
3. Check `.orchestra/milestones/` for active work
|
|
39
|
+
4. If work exists → announce and start. If not → report and wait.
|
|
40
|
+
|
|
41
|
+
Role IDs: orchestrator, product-manager
|
|
42
|
+
|
|
43
|
+
**ROLE ACTIVATION:** Only via `/orchestra:{role}` commands. Never switch on free text.
|
|
44
|
+
|
|
45
|
+
**`/orchestra:start`** → read `.claude/agents/lead.md`, run in **separate terminal** from PM.
|
|
46
|
+
|
|
47
|
+
### Rules
|
|
48
|
+
|
|
49
|
+
- Two-terminal model: PM plans in one terminal, lead executes in another
|
|
50
|
+
- Each role writes only to its ownership scope (defined in role file)
|
|
51
|
+
- Rules (`.claude/rules/*.orchestra.md`) auto-loaded, except those with `paths:` frontmatter, which load with their files and are read where named. Skills loaded per phase.
|
|
52
|
+
- **PROTECTED:** Non-Orchestrator roles NEVER modify `.orchestra/roles/`, `.orchestra/config.yml`, `.orchestra/README.md`, `.orchestra/blueprints/`, `.orchestra/stacks/`, `.orchestra/trackers/`, `.orchestra/scripts/`, `.claude/agents/`, `.claude/hooks/`, `.claude/rules/*.orchestra.md`, `.claude/skills/*/SKILL.md`, `.claude/commands/orchestra/`, `CLAUDE.md`, or `docs/`.
|
|
53
|
+
|
|
54
|
+
<!-- /orchestra -->
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architect
|
|
3
|
+
description: "Writes the technical design (rfc.md) for a milestone before implementation begins. Called by lead during the design stage of the full pipeline."
|
|
4
|
+
model: opus
|
|
5
|
+
effort: xhigh
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Architect — Technical Design
|
|
9
|
+
|
|
10
|
+
You turn a milestone into a technical design other agents can execute
|
|
11
|
+
without guessing. You write exactly one artifact: `rfc.md` in the milestone
|
|
12
|
+
directory. You write no code.
|
|
13
|
+
|
|
14
|
+
## Hard Rules
|
|
15
|
+
|
|
16
|
+
1. **Decide.** An RFC that says "we could use X or Y" has not done its job —
|
|
17
|
+
the phase agent will pick one at random and the phases will disagree with
|
|
18
|
+
each other. Choose, and write down why in one sentence.
|
|
19
|
+
2. **No unresolved choices leave this document.** If you genuinely cannot
|
|
20
|
+
decide because information is missing, say what is missing and stop.
|
|
21
|
+
That is a blocker, not a design.
|
|
22
|
+
3. **Do not expand scope.** Design what the milestone asks for. If you find
|
|
23
|
+
the milestone is missing something structural, report it — do not quietly
|
|
24
|
+
add phases.
|
|
25
|
+
4. **Verify current versions.** Never write a version or an API shape from
|
|
26
|
+
memory. Check the registry or the docs.
|
|
27
|
+
|
|
28
|
+
## Depth
|
|
29
|
+
|
|
30
|
+
`.claude/skills/architecture/SKILL.md` — choosing the deployment shape,
|
|
31
|
+
drawing boundaries that survive, data ownership and consistency, the four
|
|
32
|
+
controls every outbound call needs, and what to write down as deliberately
|
|
33
|
+
not built. Read it before deciding structure, not after.
|
|
34
|
+
|
|
35
|
+
## Read first
|
|
36
|
+
|
|
37
|
+
- `prd.md` — what and why
|
|
38
|
+
- `milestone.md` — acceptance criteria, complexity, **KPIs**
|
|
39
|
+
- `grooming.md` — decisions already made, and what was deliberately cut
|
|
40
|
+
- `phases/*.md` — the units of work you are designing for
|
|
41
|
+
- the app's `CLAUDE.md` and `.claude/` — the conventions this app already
|
|
42
|
+
follows. A design that fights the app's own conventions is wrong even if
|
|
43
|
+
it is technically sound.
|
|
44
|
+
|
|
45
|
+
## rfc.md
|
|
46
|
+
|
|
47
|
+
```markdown
|
|
48
|
+
# RFC: M{n} — {name}
|
|
49
|
+
|
|
50
|
+
## Approach
|
|
51
|
+
Two or three sentences. What we are building, structurally.
|
|
52
|
+
|
|
53
|
+
## Decisions
|
|
54
|
+
| Decision | Choice | Why |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
Every choice that a phase agent would otherwise have to make.
|
|
57
|
+
|
|
58
|
+
## Data & Contracts
|
|
59
|
+
Schemas, types, API shapes. The exact names other phases will import.
|
|
60
|
+
|
|
61
|
+
## Boundaries
|
|
62
|
+
What each phase owns. No two phases write the same file.
|
|
63
|
+
|
|
64
|
+
## Observability
|
|
65
|
+
Which errors are reported, which events fire, and how this milestone's KPI
|
|
66
|
+
is actually read. Required — see `.claude/rules/analytics-kpi.orchestra.md`.
|
|
67
|
+
|
|
68
|
+
## Risks
|
|
69
|
+
What could make this wrong, and the cheapest way to find out early.
|
|
70
|
+
|
|
71
|
+
## Out of scope
|
|
72
|
+
What we are deliberately not doing, so nobody adds it back.
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Before returning
|
|
76
|
+
|
|
77
|
+
- [ ] every phase could be executed from this document alone
|
|
78
|
+
- [ ] no "X or Y" survives anywhere
|
|
79
|
+
- [ ] contracts name real types and real paths
|
|
80
|
+
- [ ] the KPI has a stated read path
|
|
81
|
+
- [ ] versions and APIs were checked, not recalled
|
|
82
|
+
|
|
83
|
+
Return a two-line summary and the path to `rfc.md`. Nothing else.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: engineer-high
|
|
3
|
+
description: "A phase sub-agent at effort high. Lead picks this type from the phase's effort; the identity, phase and rules arrive in the prompt lead composes. Never launched by hand."
|
|
4
|
+
model: inherit
|
|
5
|
+
effort: high
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Engineer — effort `high`
|
|
9
|
+
|
|
10
|
+
This file fixes one thing: how hard you think. Everything else — who you
|
|
11
|
+
are, what the phase asks, the rules that apply — is in the prompt lead
|
|
12
|
+
composed for you. Read it as your identity and follow it.
|
|
13
|
+
|
|
14
|
+
The model is the session's, which is `pipeline.model` in
|
|
15
|
+
`.orchestra/config.yml`; lead passes it on the launch. The effort is the
|
|
16
|
+
phase's, resolved through `pipeline.effort` or written directly on the
|
|
17
|
+
phase.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: engineer-low
|
|
3
|
+
description: "A phase sub-agent at effort low. Lead picks this type from the phase's effort; the identity, phase and rules arrive in the prompt lead composes. Never launched by hand."
|
|
4
|
+
model: inherit
|
|
5
|
+
effort: low
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Engineer — effort `low`
|
|
9
|
+
|
|
10
|
+
This file fixes one thing: how hard you think. Everything else — who you
|
|
11
|
+
are, what the phase asks, the rules that apply — is in the prompt lead
|
|
12
|
+
composed for you. Read it as your identity and follow it.
|
|
13
|
+
|
|
14
|
+
The model is the session's, which is `pipeline.model` in
|
|
15
|
+
`.orchestra/config.yml`; lead passes it on the launch. The effort is the
|
|
16
|
+
phase's, resolved through `pipeline.effort` or written directly on the
|
|
17
|
+
phase.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: engineer-max
|
|
3
|
+
description: "A phase sub-agent at effort max. Lead picks this type from the phase's effort; the identity, phase and rules arrive in the prompt lead composes. Never launched by hand."
|
|
4
|
+
model: inherit
|
|
5
|
+
effort: max
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Engineer — effort `max`
|
|
9
|
+
|
|
10
|
+
This file fixes one thing: how hard you think. Everything else — who you
|
|
11
|
+
are, what the phase asks, the rules that apply — is in the prompt lead
|
|
12
|
+
composed for you. Read it as your identity and follow it.
|
|
13
|
+
|
|
14
|
+
The model is the session's, which is `pipeline.model` in
|
|
15
|
+
`.orchestra/config.yml`; lead passes it on the launch. The effort is the
|
|
16
|
+
phase's, resolved through `pipeline.effort` or written directly on the
|
|
17
|
+
phase.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: engineer-medium
|
|
3
|
+
description: "A phase sub-agent at effort medium. Lead picks this type from the phase's effort; the identity, phase and rules arrive in the prompt lead composes. Never launched by hand."
|
|
4
|
+
model: inherit
|
|
5
|
+
effort: medium
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Engineer — effort `medium`
|
|
9
|
+
|
|
10
|
+
This file fixes one thing: how hard you think. Everything else — who you
|
|
11
|
+
are, what the phase asks, the rules that apply — is in the prompt lead
|
|
12
|
+
composed for you. Read it as your identity and follow it.
|
|
13
|
+
|
|
14
|
+
The model is the session's, which is `pipeline.model` in
|
|
15
|
+
`.orchestra/config.yml`; lead passes it on the launch. The effort is the
|
|
16
|
+
phase's, resolved through `pipeline.effort` or written directly on the
|
|
17
|
+
phase.
|