@mmerterden/multi-agent-pipeline 20.25.0 → 20.26.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/CHANGELOG.md +18 -0
- package/README.md +6 -7
- package/README.tr.md +6 -7
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +4 -5
- package/install/catalog-history.json +1 -1
- package/manifest.json +36 -37
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/channels/SKILL.md +4 -2
- package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
- package/pipeline/lib/_jira-auth.sh +3 -1
- package/pipeline/lib/jira-family-write.sh +339 -0
- package/pipeline/lib/jira-publish.sh +28 -1
- package/pipeline/multi-agent-refs/channels/jira.md +2 -0
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
- package/pipeline/multi-agent-refs/features/jira-family.md +175 -0
- package/pipeline/multi-agent-refs/help/en.md +0 -1
- package/pipeline/multi-agent-refs/help/tr.md +0 -1
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +3 -1
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +0 -2
- package/pipeline/schemas/agent-state.schema.json +36 -5
- package/pipeline/schemas/phases.json +0 -4
- package/pipeline/schemas/prefs.schema.json +23 -23
- package/pipeline/scripts/jira-family.mjs +947 -0
- package/pipeline/scripts/jira-wiki-escape.mjs +3 -3
- package/pipeline/skills/.skill-manifest.json +5 -9
- package/pipeline/skills/.skills-index.json +1 -12
- package/pipeline/skills/shared/README.md +3 -4
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +5 -0
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +7 -0
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
- package/pipeline/skills/skills-index.md +1 -2
- package/pipeline/commands/multi-agent/deps/SKILL.md +0 -61
- package/pipeline/multi-agent-refs/features/dep-bump.md +0 -176
- package/pipeline/scripts/dep-bump.mjs +0 -1556
- package/pipeline/skills/shared/core/multi-agent-deps/SKILL.md +0 -60
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# jira-family-write.sh - fetch the issue family of a Jira-started run, and
|
|
4
|
+
# apply the writes scripts/jira-family.mjs planned for it.
|
|
5
|
+
#
|
|
6
|
+
# The planning half is offline and testable; this half is the one that talks
|
|
7
|
+
# to Jira, so it stays small. Every write goes through jira-publish.sh, which
|
|
8
|
+
# escapes the body, runs the outbound gate and backs a description up before it
|
|
9
|
+
# replaces one. A write the plan marks `raw` (the story reformat: the author's
|
|
10
|
+
# lines plus escaped macros) is sent with --raw, so the author's lines reach
|
|
11
|
+
# Jira unchanged. Reads go through jira_api in _jira-auth.sh.
|
|
12
|
+
#
|
|
13
|
+
# A write is checked against the site right before it is sent:
|
|
14
|
+
# comment skipped when a comment by the authenticated user on the issue
|
|
15
|
+
# already carries its marker
|
|
16
|
+
# description skipped when the story already conforms, or when it changed
|
|
17
|
+
# since the plan read it (jira-publish.sh --expect-file)
|
|
18
|
+
# so running the same plan twice writes nothing the second time.
|
|
19
|
+
#
|
|
20
|
+
# Usage, from a run (the state file is agent-state.json; the family's files go
|
|
21
|
+
# to jira-family/ beside it):
|
|
22
|
+
# jira-family-write.sh resolve --state FILE
|
|
23
|
+
# fetch and resolve the family, record state.jiraFamily; no Jira write
|
|
24
|
+
# jira-family-write.sh plan --state FILE --dev-body FILE [--skip id,id]
|
|
25
|
+
# [--dev-task KEY] [--test-task KEY] [--build TEXT]
|
|
26
|
+
# re-resolve and plan; run id, PR URL and mode come from the state
|
|
27
|
+
# jira-family-write.sh apply --state FILE [--dry-run]
|
|
28
|
+
# apply jira-family/plan.json, write jira-family/writes.json, merge it
|
|
29
|
+
# into state.jiraFamily.writes by write id
|
|
30
|
+
#
|
|
31
|
+
# Lower level:
|
|
32
|
+
# jira-family-write.sh fetch --issue KEY --out DIR
|
|
33
|
+
# DIR/input.json, DIR/story.json (the parent when KEY is a sub-task, else
|
|
34
|
+
# the same issue), DIR/site-types.txt (empty when unreadable)
|
|
35
|
+
# jira-family-write.sh apply --plan FILE [--state FILE] [--result FILE] [--dry-run]
|
|
36
|
+
# --state the plan's autopilot decisions are recorded there
|
|
37
|
+
# (decisions-log.mjs) once their write has gone out
|
|
38
|
+
# --result the per-write outcome as JSON, written on every exit
|
|
39
|
+
#
|
|
40
|
+
# Exit: 0 done, 3 the input does not parse or the plan still has questions,
|
|
41
|
+
# 4 auth (also when a write failed on auth), 5 a write failed (the others
|
|
42
|
+
# went out; --result lists which, and a re-run sends only what is missing),
|
|
43
|
+
# 6 unattended (nothing read or written; the runner owns Jira),
|
|
44
|
+
# 64 usage or a malformed issue key.
|
|
45
|
+
|
|
46
|
+
set -uo pipefail
|
|
47
|
+
|
|
48
|
+
SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
49
|
+
# shellcheck source=/dev/null
|
|
50
|
+
. "$SELF_DIR/_jira-auth.sh"
|
|
51
|
+
# shellcheck source=/dev/null
|
|
52
|
+
. "$SELF_DIR/unattended.sh"
|
|
53
|
+
|
|
54
|
+
USAGE="usage:
|
|
55
|
+
jira-family-write.sh resolve --state FILE
|
|
56
|
+
jira-family-write.sh plan --state FILE --dev-body FILE [--skip id,id] [--dev-task KEY] [--test-task KEY] [--build TEXT]
|
|
57
|
+
jira-family-write.sh apply --state FILE [--plan FILE] [--result FILE] [--dry-run]
|
|
58
|
+
jira-family-write.sh fetch --issue KEY --out DIR
|
|
59
|
+
jira-family-write.sh apply --plan FILE [--state FILE] [--result FILE] [--dry-run]"
|
|
60
|
+
usage() { echo "$USAGE" >&2; exit 64; }
|
|
61
|
+
|
|
62
|
+
CMD="${1:-}"; [ -n "$CMD" ] || usage; shift
|
|
63
|
+
case "$CMD" in -h|--help) sed -n '2,45p' "$0"; exit 0 ;; esac
|
|
64
|
+
ISSUE=""; OUT=""; PLAN=""; STATE=""; RESULT=""; DRY=0
|
|
65
|
+
DEV_BODY=""; SKIP=""; DEV_TASK=""; TEST_TASK=""; BUILD=""
|
|
66
|
+
while [ $# -gt 0 ]; do
|
|
67
|
+
case "$1" in
|
|
68
|
+
-h|--help) sed -n '2,45p' "$0"; exit 0 ;;
|
|
69
|
+
--dev-body) DEV_BODY="${2:-}"; shift 2 ;;
|
|
70
|
+
--skip) SKIP="${2:-}"; shift 2 ;;
|
|
71
|
+
--dev-task) DEV_TASK="${2:-}"; shift 2 ;;
|
|
72
|
+
--test-task) TEST_TASK="${2:-}"; shift 2 ;;
|
|
73
|
+
--build) BUILD="${2:-}"; shift 2 ;;
|
|
74
|
+
--issue) ISSUE="${2:-}"; shift 2 ;;
|
|
75
|
+
--out) OUT="${2:-}"; shift 2 ;;
|
|
76
|
+
--plan) PLAN="${2:-}"; shift 2 ;;
|
|
77
|
+
--state) STATE="${2:-}"; shift 2 ;;
|
|
78
|
+
--result) RESULT="${2:-}"; shift 2 ;;
|
|
79
|
+
--dry-run) DRY=1; shift ;;
|
|
80
|
+
*) usage ;;
|
|
81
|
+
esac
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
command -v jq >/dev/null 2>&1 || { echo "ERR: jq is required" >&2; exit 3; }
|
|
85
|
+
|
|
86
|
+
FAMILY_MJS="$SELF_DIR/../scripts/jira-family.mjs"
|
|
87
|
+
DECISIONS_MJS="$SELF_DIR/../scripts/decisions-log.mjs"
|
|
88
|
+
PUBLISH="$SELF_DIR/jira-publish.sh"
|
|
89
|
+
FIELDS="summary,issuetype,parent,subtasks,description,project,status"
|
|
90
|
+
|
|
91
|
+
# A key goes into a REST path, so anything but a key is refused before use.
|
|
92
|
+
valid_key() { printf '%s' "$1" | grep -Eq '^[A-Z][A-Z0-9_]+-[0-9]+$'; }
|
|
93
|
+
|
|
94
|
+
# What a jira-publish.sh exit code means, for the result line.
|
|
95
|
+
publish_reason() {
|
|
96
|
+
case "$1" in
|
|
97
|
+
4) echo "Jira host or token could not be resolved (exit 4)" ;;
|
|
98
|
+
5) echo "the Jira call failed (exit 5)" ;;
|
|
99
|
+
64) echo "refused before sending, e.g. by the outbound gate (exit 64): $2" ;;
|
|
100
|
+
*) echo "jira-publish.sh exit $1: $2" ;;
|
|
101
|
+
esac
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fetch_issue() { # fetch_issue <key> <file>
|
|
105
|
+
jira_api GET "/rest/api/2/issue/$1?fields=$FIELDS" > "$2" 2>/dev/null || return 5
|
|
106
|
+
jq -e '.key' "$2" >/dev/null 2>&1 || return 5
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
do_fetch() { # do_fetch <key> <dir>
|
|
110
|
+
local issue="$1" out="$2" parent project
|
|
111
|
+
valid_key "$issue" || { echo "ERR: not an issue key: $issue" >&2; return 64; }
|
|
112
|
+
if ma_unattended; then
|
|
113
|
+
echo "unattended run: the family is not read here; the runner owns Jira" >&2
|
|
114
|
+
return 6
|
|
115
|
+
fi
|
|
116
|
+
jira_auth_resolve || return 4
|
|
117
|
+
mkdir -p "$out"
|
|
118
|
+
fetch_issue "$issue" "$out/input.json" || { echo "ERR: could not read $issue" >&2; return 5; }
|
|
119
|
+
parent="$(jq -r 'if .fields.issuetype.subtask == true then .fields.parent.key // empty else empty end' "$out/input.json")"
|
|
120
|
+
if [ -n "$parent" ] && ! valid_key "$parent"; then
|
|
121
|
+
echo "WARN: the parent key does not look like a key: $parent" >&2; parent=""
|
|
122
|
+
fi
|
|
123
|
+
if [ -n "$parent" ]; then
|
|
124
|
+
fetch_issue "$parent" "$out/story.json" || { echo "WARN: could not read the parent $parent" >&2; rm -f "$out/story.json"; }
|
|
125
|
+
else
|
|
126
|
+
cp "$out/input.json" "$out/story.json"
|
|
127
|
+
fi
|
|
128
|
+
project="$(jq -r '.fields.project.key // empty' "$out/input.json")"
|
|
129
|
+
[ -n "$project" ] || project="${issue%%-*}"
|
|
130
|
+
bash "$SELF_DIR/analysis-jira-write.sh" --subtask-types --project "$project" \
|
|
131
|
+
> "$out/site-types.txt" 2>/dev/null || : > "$out/site-types.txt"
|
|
132
|
+
echo "fetched $issue${parent:+ (story $parent)} into $out"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# The run's own files: the issue key and the family directory beside agent-state.json.
|
|
136
|
+
state_key() { jq -r '.jiraId // .taskId // empty' "$STATE"; }
|
|
137
|
+
family_dir() { printf '%s/jira-family' "$(cd "$(dirname "$STATE")" && pwd)"; }
|
|
138
|
+
|
|
139
|
+
do_resolve() { # fetch, resolve, and record state.jiraFamily; asks and writes nothing in Jira
|
|
140
|
+
local key dir story=""
|
|
141
|
+
key="$(state_key)"; dir="$(family_dir)"
|
|
142
|
+
do_fetch "$key" "$dir" >/dev/null || return $?
|
|
143
|
+
[ -f "$dir/story.json" ] && story="--story $dir/story.json"
|
|
144
|
+
# shellcheck disable=SC2086
|
|
145
|
+
node "$FAMILY_MJS" resolve --input "$dir/input.json" $story \
|
|
146
|
+
--site-types "$dir/site-types.txt" > "$dir/family.json" || return 3
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if [ "$CMD" = "fetch" ]; then
|
|
150
|
+
[ -n "$ISSUE" ] && [ -n "$OUT" ] || usage
|
|
151
|
+
do_fetch "$ISSUE" "$OUT"
|
|
152
|
+
exit $?
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
WRITE_STATE="$SELF_DIR/../scripts/write-state.mjs"
|
|
156
|
+
|
|
157
|
+
if [ "$CMD" = "resolve" ]; then
|
|
158
|
+
[ -n "$STATE" ] && [ -f "$STATE" ] || usage
|
|
159
|
+
do_resolve || exit $?
|
|
160
|
+
DIR="$(family_dir)"
|
|
161
|
+
jq -n --slurpfile f "$DIR/family.json" --slurpfile s "$STATE" \
|
|
162
|
+
'{jiraFamily: ($f[0].stateEntry + {writes: ($s[0].jiraFamily.writes // [])})}' \
|
|
163
|
+
| node "$WRITE_STATE" "$STATE" || exit 3
|
|
164
|
+
jq -r '"family: story \(.stateEntry.storyKey), dev \(.stateEntry.devTaskKey // "-"), test \(.stateEntry.testTaskKey // "-"), story format \(.stateEntry.storyFormat.status)"' "$DIR/family.json"
|
|
165
|
+
exit 0
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
if [ "$CMD" = "plan" ]; then
|
|
169
|
+
[ -n "$STATE" ] && [ -f "$STATE" ] && [ -n "$DEV_BODY" ] && [ -f "$DEV_BODY" ] || usage
|
|
170
|
+
do_resolve || exit $?
|
|
171
|
+
DIR="$(family_dir)"
|
|
172
|
+
RUN_ID="$(jq -r '"\(.taskId)-\(.shortId // 0)"' "$STATE")"
|
|
173
|
+
PR_URL="$(jq -r '.pr.url // .projects[0].pr.url // empty' "$STATE")"
|
|
174
|
+
MODE="$(jq -r 'if .autopilot == true then "autopilot" else "interactive" end' "$STATE")"
|
|
175
|
+
set -- --family "$DIR/family.json" --dev-body "$DEV_BODY" --run-id "$RUN_ID" --mode "$MODE"
|
|
176
|
+
[ -z "$PR_URL" ] || set -- "$@" --pr-url "$PR_URL"
|
|
177
|
+
[ -z "$BUILD" ] || set -- "$@" --build "$BUILD"
|
|
178
|
+
[ -z "$SKIP" ] || set -- "$@" --skip "$SKIP"
|
|
179
|
+
[ -z "$DEV_TASK" ] || set -- "$@" --dev-task "$DEV_TASK"
|
|
180
|
+
[ -z "$TEST_TASK" ] || set -- "$@" --test-task "$TEST_TASK"
|
|
181
|
+
node "$FAMILY_MJS" plan "$@" > "$DIR/plan.json" || exit 3
|
|
182
|
+
jq -r '.confirm, (.skipped[] | " not written: \(.id) (\(.reason))"), (.questions[] | " question \(.id): \(.question) [\(.options | join(", "))]")' "$DIR/plan.json"
|
|
183
|
+
echo "plan: $DIR/plan.json"
|
|
184
|
+
exit 0
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
[ "$CMD" = "apply" ] || usage
|
|
188
|
+
MERGE=0
|
|
189
|
+
if [ -z "$PLAN" ] && [ -n "$STATE" ] && [ -f "$STATE" ]; then
|
|
190
|
+
PLAN="$(family_dir)/plan.json"
|
|
191
|
+
[ -n "$RESULT" ] || RESULT="$(family_dir)/writes.json"
|
|
192
|
+
MERGE=1
|
|
193
|
+
fi
|
|
194
|
+
[ -n "$PLAN" ] && [ -f "$PLAN" ] || usage
|
|
195
|
+
|
|
196
|
+
# Every exit leaves a result file, so the caller's follow-up never reads nothing.
|
|
197
|
+
early_exit() { # early_exit <code> <status> <reason>
|
|
198
|
+
if [ -n "$RESULT" ]; then
|
|
199
|
+
jq -c --arg s "$2" --arg r "$3" '[.writes[]? | {id, issue, status: $s, reason: $r}]' "$PLAN" \
|
|
200
|
+
> "$RESULT" 2>/dev/null || echo '[]' > "$RESULT"
|
|
201
|
+
fi
|
|
202
|
+
exit "$1"
|
|
203
|
+
}
|
|
204
|
+
if ma_unattended; then
|
|
205
|
+
echo "unattended run: the family writes are left to the runner" >&2
|
|
206
|
+
early_exit 6 skipped "unattended run: left to the runner"
|
|
207
|
+
fi
|
|
208
|
+
jq -e . "$PLAN" >/dev/null 2>&1 || { echo "ERR: the plan does not parse: $PLAN" >&2; early_exit 3 failed "the plan does not parse"; }
|
|
209
|
+
if [ "$(jq -r '.ready' "$PLAN")" != "true" ]; then
|
|
210
|
+
echo "ERR: the plan still has open questions ($(jq -r '[.questions[].id] | join(", ")' "$PLAN")); answer them and re-plan" >&2
|
|
211
|
+
early_exit 3 skipped "the plan still has open questions"
|
|
212
|
+
fi
|
|
213
|
+
[ "$DRY" -eq 1 ] || jira_auth_resolve || exit 4
|
|
214
|
+
|
|
215
|
+
WORK="$(mktemp -d)"
|
|
216
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
217
|
+
RESULTS="$WORK/results.jsonl"; : > "$RESULTS"
|
|
218
|
+
ME=""
|
|
219
|
+
RC=0
|
|
220
|
+
|
|
221
|
+
record() { # record <id> <issue> <status> <reason>
|
|
222
|
+
jq -cn --arg i "$1" --arg k "$2" --arg s "$3" --arg r "$4" \
|
|
223
|
+
'{id: $i, issue: $k, status: $s, reason: $r}' >> "$RESULTS"
|
|
224
|
+
printf ' %-8s %-12s %s%s\n' "$3" "$1" "$2" "${4:+ ($4)}"
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
fail_rc() { # an auth failure (4) outranks any other failure (5) in the exit code
|
|
228
|
+
if [ "$1" -eq 4 ]; then RC=4; elif [ "$RC" -ne 4 ]; then RC=5; fi
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
record_decisions() { # record_decisions <writeId>, or "" for the decisions that made no write
|
|
232
|
+
[ -n "$STATE" ] || return 0
|
|
233
|
+
jq -c --arg w "$1" '.decisions[]? | select(if $w == "" then .writeId == null else .writeId == $w end)' "$PLAN" | while IFS= read -r d; do
|
|
234
|
+
node "$DECISIONS_MJS" add --state "$STATE" \
|
|
235
|
+
--what "$(printf '%s' "$d" | jq -r .what)" \
|
|
236
|
+
--why "$(printf '%s' "$d" | jq -r .why)" \
|
|
237
|
+
--cost-if-wrong "$(printf '%s' "$d" | jq -r .costIfWrong)" \
|
|
238
|
+
--phase "$(printf '%s' "$d" | jq -r .phase)" >/dev/null 2>&1 \
|
|
239
|
+
|| echo " WARN: could not record the decision for $1" >&2
|
|
240
|
+
done
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
echo "jira-family: $(jq -r '.confirm' "$PLAN")$([ "$DRY" -eq 1 ] && echo " (dry run)")"
|
|
244
|
+
N="$(jq '.writes | length' "$PLAN")"
|
|
245
|
+
i=0
|
|
246
|
+
while [ "$i" -lt "$N" ]; do
|
|
247
|
+
W="$(jq -c ".writes[$i]" "$PLAN")"
|
|
248
|
+
i=$((i + 1))
|
|
249
|
+
id="$(printf '%s' "$W" | jq -r .id)"
|
|
250
|
+
kind="$(printf '%s' "$W" | jq -r .kind)"
|
|
251
|
+
issue="$(printf '%s' "$W" | jq -r .issue)"
|
|
252
|
+
printf '%s' "$W" | jq -j .body > "$WORK/body.txt"
|
|
253
|
+
if ! valid_key "$issue"; then
|
|
254
|
+
record "$id" "$issue" "failed" "not an issue key"; RC=5; continue
|
|
255
|
+
fi
|
|
256
|
+
if [ "$DRY" -eq 1 ]; then
|
|
257
|
+
record "$id" "$issue" "dry-run" "$kind"
|
|
258
|
+
continue
|
|
259
|
+
fi
|
|
260
|
+
if [ "$kind" = "comment" ]; then
|
|
261
|
+
mark="$(printf '%s' "$W" | jq -r .marker)"
|
|
262
|
+
if ! jira_api GET "/rest/api/2/issue/$issue/comment?maxResults=1000" > "$WORK/comments.json" 2>/dev/null \
|
|
263
|
+
|| ! jq -e '.comments' "$WORK/comments.json" >/dev/null 2>&1; then
|
|
264
|
+
record "$id" "$issue" "failed" "could not read the comments"; RC=5; continue
|
|
265
|
+
fi
|
|
266
|
+
if [ -z "$ME" ]; then
|
|
267
|
+
jira_api GET "/rest/api/2/myself" > "$WORK/me.json" 2>/dev/null || :
|
|
268
|
+
ME="$(jq -c '{name, key, accountId} | with_entries(select(.value != null))' "$WORK/me.json" 2>/dev/null || echo "")"
|
|
269
|
+
fi
|
|
270
|
+
if [ -z "$ME" ] || [ "$ME" = "{}" ]; then
|
|
271
|
+
ME=""; record "$id" "$issue" "failed" "could not read the authenticated user"; RC=5; continue
|
|
272
|
+
fi
|
|
273
|
+
# Only our own comments count: another user can paste a marker, and it
|
|
274
|
+
# must not be able to stop this one from being posted.
|
|
275
|
+
seen="$(jq --arg m "$mark" --argjson me "$ME" '[.comments[]?
|
|
276
|
+
| select((.author // {}) as $a | any($me | to_entries[]; $a[.key] == .value))
|
|
277
|
+
| .body // "" | select(contains($m))] | length' "$WORK/comments.json")"
|
|
278
|
+
if [ "${seen:-0}" -gt 0 ]; then
|
|
279
|
+
record "$id" "$issue" "exists" "already posted by this run"; continue
|
|
280
|
+
fi
|
|
281
|
+
bash "$PUBLISH" --issue "$issue" --body-file "$WORK/body.txt" --target comment >/dev/null 2>"$WORK/err.txt"
|
|
282
|
+
prc=$?
|
|
283
|
+
if [ "$prc" -eq 0 ]; then
|
|
284
|
+
record "$id" "$issue" "written" ""
|
|
285
|
+
record_decisions "$id"
|
|
286
|
+
else
|
|
287
|
+
record "$id" "$issue" "failed" "$(publish_reason "$prc" "$(head -1 "$WORK/err.txt")")"
|
|
288
|
+
fail_rc "$prc"
|
|
289
|
+
fi
|
|
290
|
+
continue
|
|
291
|
+
fi
|
|
292
|
+
# description
|
|
293
|
+
if ! jira_api GET "/rest/api/2/issue/$issue?fields=description" > "$WORK/current.json" 2>/dev/null \
|
|
294
|
+
|| ! jq -e '.fields' "$WORK/current.json" >/dev/null 2>&1; then
|
|
295
|
+
record "$id" "$issue" "failed" "could not read the description"; RC=5; continue
|
|
296
|
+
fi
|
|
297
|
+
jq -j '.fields.description // ""' "$WORK/current.json" > "$WORK/current.txt"
|
|
298
|
+
printf '%s' "$W" | jq -j '.original // ""' > "$WORK/original.txt"
|
|
299
|
+
jq '{global: {issueTree: (if .storyPanels == null then {} else {storyPanels: .storyPanels} end)}}' \
|
|
300
|
+
"$PLAN" > "$WORK/plan-prefs.json"
|
|
301
|
+
if node "$FAMILY_MJS" check --description "$WORK/current.txt" --prefs "$WORK/plan-prefs.json" >/dev/null 2>&1; then
|
|
302
|
+
record "$id" "$issue" "exists" "already in the story format"; continue
|
|
303
|
+
fi
|
|
304
|
+
if ! cmp -s "$WORK/current.txt" "$WORK/original.txt"; then
|
|
305
|
+
record "$id" "$issue" "skipped" "the description changed since the preview"; continue
|
|
306
|
+
fi
|
|
307
|
+
raw=""
|
|
308
|
+
[ "$(printf '%s' "$W" | jq -r '.raw == true')" = "true" ] && raw="--raw"
|
|
309
|
+
bash "$PUBLISH" --issue "$issue" --body-file "$WORK/body.txt" --target description \
|
|
310
|
+
--mode replace --confirm-overwrite --expect-file "$WORK/original.txt" $raw >/dev/null 2>"$WORK/err.txt"
|
|
311
|
+
prc=$?
|
|
312
|
+
if [ "$prc" -eq 0 ]; then
|
|
313
|
+
record "$id" "$issue" "written" "previous text backed up"
|
|
314
|
+
record_decisions "$id"
|
|
315
|
+
elif [ "$prc" -eq 6 ]; then
|
|
316
|
+
record "$id" "$issue" "skipped" "the description changed since the preview"
|
|
317
|
+
else
|
|
318
|
+
record "$id" "$issue" "failed" "$(publish_reason "$prc" "$(head -1 "$WORK/err.txt")")"
|
|
319
|
+
fail_rc "$prc"
|
|
320
|
+
fi
|
|
321
|
+
done
|
|
322
|
+
|
|
323
|
+
[ "$DRY" -eq 1 ] || record_decisions ""
|
|
324
|
+
[ -z "$RESULT" ] || jq -s '.' "$RESULTS" > "$RESULT"
|
|
325
|
+
# Merged by write id: a re-run that finds a comment already there ("exists")
|
|
326
|
+
# keeps the earlier "written" record instead of replacing it.
|
|
327
|
+
if [ "$MERGE" -eq 1 ] && [ "$DRY" -eq 0 ]; then
|
|
328
|
+
jq -n --slurpfile s "$STATE" --slurpfile n "$RESULT" '
|
|
329
|
+
($s[0].jiraFamily // {}) as $f
|
|
330
|
+
| ($f.writes // []) as $old
|
|
331
|
+
| ($n[0] | map({(.id): .}) | add // {}) as $new
|
|
332
|
+
| ($old | map(.id)) as $oldIds
|
|
333
|
+
| {jiraFamily: ($f + {writes: (
|
|
334
|
+
[$old[] | . as $o | ($new[$o.id] // null) as $x
|
|
335
|
+
| if $x == null or ($o.status == "written" and $x.status == "exists") then $o else $x end]
|
|
336
|
+
+ [$n[0][] | select(.id as $i | $oldIds | index($i) | not)])})}' \
|
|
337
|
+
| node "$WRITE_STATE" "$STATE" >/dev/null || echo " WARN: could not record the writes in $STATE" >&2
|
|
338
|
+
fi
|
|
339
|
+
exit "$RC"
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
# [--mode append|replace] (description only, default: append)
|
|
20
20
|
# [--confirm-overwrite] (required for mode=replace on a non-empty field)
|
|
21
21
|
# [--backup-dir DIR]
|
|
22
|
+
# [--raw] (body is already Jira wiki markup; no emoticon escape)
|
|
23
|
+
# [--expect-file FILE] (description only: write only if it still equals FILE)
|
|
22
24
|
# [--dry-run]
|
|
23
25
|
#
|
|
24
26
|
# Resolution:
|
|
@@ -29,6 +31,9 @@
|
|
|
29
31
|
#
|
|
30
32
|
# Every body is run through scripts/jira-wiki-escape.mjs before it is sent, so
|
|
31
33
|
# Jira cannot manufacture an emoticon out of a Swift selector or a table cell.
|
|
34
|
+
# --raw skips that one step for a body built only from text the author already
|
|
35
|
+
# wrote in Jira markup plus macros the caller escaped itself (the story reformat
|
|
36
|
+
# of lib/jira-family-write.sh); the outbound gate and the backup still apply.
|
|
32
37
|
#
|
|
33
38
|
# Exit codes:
|
|
34
39
|
# 0 published (or, with --dry-run, previewed)
|
|
@@ -36,6 +41,8 @@
|
|
|
36
41
|
# --confirm-overwrite was given. The backup is already on disk.
|
|
37
42
|
# 4 host or token could not be resolved
|
|
38
43
|
# 5 a Jira API call failed
|
|
44
|
+
# 6 refused: --expect-file no longer matches the description (someone
|
|
45
|
+
# edited it after the caller read it). Nothing was written.
|
|
39
46
|
# 64 usage error
|
|
40
47
|
|
|
41
48
|
set -euo pipefail
|
|
@@ -91,6 +98,8 @@ TARGET="comment"
|
|
|
91
98
|
MODE="append"
|
|
92
99
|
CONFIRM=0
|
|
93
100
|
DRY_RUN=0
|
|
101
|
+
RAW=0
|
|
102
|
+
EXPECT=""
|
|
94
103
|
BACKUP_DIR="${JIRA_BACKUP_DIR:-$HOME/.claude/logs/multi-agent/jira-backups}"
|
|
95
104
|
|
|
96
105
|
die() { echo "$1" >&2; exit "${2:-64}"; }
|
|
@@ -106,6 +115,8 @@ while [ "$#" -gt 0 ]; do
|
|
|
106
115
|
--backup-dir) need_value "$@"; BACKUP_DIR="$2"; shift 2 ;;
|
|
107
116
|
--confirm-overwrite) CONFIRM=1; shift ;;
|
|
108
117
|
--dry-run) DRY_RUN=1; shift ;;
|
|
118
|
+
--raw) RAW=1; shift ;;
|
|
119
|
+
--expect-file) need_value "$@"; EXPECT="$2"; shift 2 ;;
|
|
109
120
|
-h|--help) sed -n '2,40p' "$0"; exit 0 ;;
|
|
110
121
|
*) die "unknown argument: $1" ;;
|
|
111
122
|
esac
|
|
@@ -148,7 +159,16 @@ api() { "$CURL" -sS -m 30 -K <(auth_cfg "$TOKEN") -H "Content-Type: application/
|
|
|
148
159
|
WORK="$(mktemp -d)"
|
|
149
160
|
trap 'rm -rf "$WORK"' EXIT
|
|
150
161
|
|
|
151
|
-
|
|
162
|
+
if [ "$RAW" = "1" ]; then
|
|
163
|
+
cp "$BODY_FILE" "$WORK/new.txt"
|
|
164
|
+
else
|
|
165
|
+
node "$ESCAPER" "$BODY_FILE" > "$WORK/new.txt"
|
|
166
|
+
fi
|
|
167
|
+
# An escaper that printed nothing for a body that has text would post an empty
|
|
168
|
+
# comment or blank a description; that is refused, never sent.
|
|
169
|
+
if [ -s "$BODY_FILE" ] && [ ! -s "$WORK/new.txt" ]; then
|
|
170
|
+
die "the escaped body came out empty; nothing was posted" 5
|
|
171
|
+
fi
|
|
152
172
|
|
|
153
173
|
if [ "$TARGET" = "comment" ]; then
|
|
154
174
|
if [ "$DRY_RUN" = "1" ]; then
|
|
@@ -175,6 +195,13 @@ case "$http" in
|
|
|
175
195
|
*) echo "could not read the current description of $ISSUE (HTTP $http)" >&2; exit 5 ;;
|
|
176
196
|
esac
|
|
177
197
|
jq -r '.fields.description // ""' "$WORK/issue.json" > "$WORK/existing.txt"
|
|
198
|
+
if [ -n "$EXPECT" ]; then
|
|
199
|
+
jq -j '.fields.description // ""' "$WORK/issue.json" > "$WORK/existing-exact.txt"
|
|
200
|
+
if ! cmp -s "$WORK/existing-exact.txt" "$EXPECT"; then
|
|
201
|
+
echo "REFUSED: the description of $ISSUE changed since it was read; nothing was written." >&2
|
|
202
|
+
exit 6
|
|
203
|
+
fi
|
|
204
|
+
fi
|
|
178
205
|
|
|
179
206
|
existing_bytes=$(wc -c < "$WORK/existing.txt" | tr -d ' ')
|
|
180
207
|
existing_lines=$(grep -c . "$WORK/existing.txt" || true)
|
|
@@ -232,6 +232,8 @@ Jira's wiki renderer turned it into an emoticon, which is what section
|
|
|
232
232
|
*Emoticon escaping* below is about. A defence that has to be invoked by hand is
|
|
233
233
|
a defence that is eventually not invoked.
|
|
234
234
|
|
|
235
|
+
A run with `state.jiraFamily` posts this body on the development sub-task, plus a test comment on the test sub-task: `features/jira-family.md`.
|
|
236
|
+
|
|
235
237
|
The dispatch summary line includes the comment URL with `?focusedCommentId=...` so the user can paste it into Slack/Teams.
|
|
236
238
|
|
|
237
239
|
### Writing the issue description (not a comment)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Cross-CLI Contract (Claude Code · Copilot CLI · Codex CLI)
|
|
2
2
|
|
|
3
3
|
<!-- toc -->
|
|
4
|
-
- [1. Command Inventory (
|
|
4
|
+
- [1. Command Inventory (65 commands)](#1-command-inventory-65-commands)
|
|
5
5
|
- [2. Canonical Placeholder Vocabulary](#2-canonical-placeholder-vocabulary)
|
|
6
6
|
- [2.6 Intentional structural divergence - thin dispatcher vs inlined orchestrator](#26-intentional-structural-divergence---thin-dispatcher-vs-inlined-orchestrator)
|
|
7
7
|
- [2.7 One command, three different meanings: `/multi-agent:model`](#27-one-command-three-different-meanings-multi-agentmodel)
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
## 1. Command Inventory (
|
|
23
|
+
## 1. Command Inventory (65 commands)
|
|
24
24
|
|
|
25
25
|
```
|
|
26
26
|
analysis, analysis-jira, analysis-resolve, autopilot, autopilot-off,
|
|
27
27
|
autopilot-on, autopilot-status, bug-bash, build-optimize, channels, complaint-analysis,
|
|
28
|
-
create-jira,
|
|
28
|
+
create-jira, design-check, diff-explain, doctor, estimate, feedback, forget,
|
|
29
29
|
garbage-collect, graph, help, ios-coding-standard, issue, jira, kill,
|
|
30
30
|
language, log, manual-test, model, postmortem, prune-logs,
|
|
31
31
|
prune-prompts, purge, refactor, research, resume, review,
|
|
@@ -40,7 +40,7 @@ Categories:
|
|
|
40
40
|
|
|
41
41
|
- **Interactive pickers** (single-purpose, not modes): `jira`, `issue`
|
|
42
42
|
- **Issue generator** (one-shot, no worktree, asks type Task/Bug/Story, hard approval gate before create): `create-jira`
|
|
43
|
-
- **Pipeline entries**: `autopilot
|
|
43
|
+
- **Pipeline entries**: `autopilot` (plus the bare `/multi-agent` in the dispatcher). The phase set is a property of the command, and every mode runs its whole set.
|
|
44
44
|
- **Tail path** (the pipeline tail over work already on a branch, no run behind it): `resume`
|
|
45
45
|
- **Ops commands** (one-shot, no worktree): `status`, `log`, `kill`, `steer`, `purge`, `uninstall`, `resume`, `review`, `review-jira`, `review-issue`, `analysis`, `analysis-resolve`, `research`, `scaffold`, `complaint-analysis`, `bug-bash`, `build-optimize`, `channels`, `scan`, `search`, `diff-explain`, `garbage-collect`, `graph`, `postmortem`, `prune-logs`, `prune-prompts`, `serve`
|
|
46
46
|
- **Local audits** (worktree only to build; no commit, push, PR or channels): `design-check`, `testflight-validation`, `ios-coding-standard`. `testflight-validation` additionally never invokes `altool --upload-app` - a validation run must not be able to ship a build by accident.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Jira issue family
|
|
2
|
+
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [What it writes](#what-it-writes)
|
|
5
|
+
- [Resolving the family (Phase 0)](#resolving-the-family-phase-0)
|
|
6
|
+
- [The story format](#the-story-format)
|
|
7
|
+
- [The test comment](#the-test-comment)
|
|
8
|
+
- [Writing (channels, Phase 5)](#writing-channels-phase-5)
|
|
9
|
+
- [Idempotency and partial runs](#idempotency-and-partial-runs)
|
|
10
|
+
- [Settings](#settings)
|
|
11
|
+
- [State](#state)
|
|
12
|
+
<!-- /toc -->
|
|
13
|
+
|
|
14
|
+
A run that starts from a Jira link touches three issues, not one: the story,
|
|
15
|
+
the development sub-task and the test sub-task. Whether the link was the
|
|
16
|
+
development sub-task or its parent story, the run resolves that family once and
|
|
17
|
+
owes each member one write.
|
|
18
|
+
|
|
19
|
+
## What it writes
|
|
20
|
+
|
|
21
|
+
| # | Issue | Write | When |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| 1 | story | description reformatted into the story format | the story is of a story type, does not conform, and the reformat provably keeps the author's text |
|
|
24
|
+
| 2 | development sub-task | the channels Jira comment (`channels/jira.md`), unchanged | always; on the story when it has no development sub-task |
|
|
25
|
+
| 3 | test sub-task | test comment in the dev comment's format | the dev comment has test scenarios |
|
|
26
|
+
|
|
27
|
+
With no test sub-task, write 3 goes to the story instead.
|
|
28
|
+
|
|
29
|
+
Attended runs confirm once: every planned write is listed in one grouped,
|
|
30
|
+
multi-select confirmation ("3 yazma: story açıklaması, dev yorumu, test
|
|
31
|
+
yorumu"), the story reformat with its preview beside the comment bodies, and
|
|
32
|
+
the person deselects any write they do not want. Autopilot makes all of them
|
|
33
|
+
and records each choice it made in `state.decisions[]`.
|
|
34
|
+
|
|
35
|
+
## Resolving the family (Phase 0)
|
|
36
|
+
|
|
37
|
+
After Step 6 has created `agent-state.json` (`$STATE_FILE`, the path every
|
|
38
|
+
state write uses, `phases/operations.md`):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# unattended: skipped - the runner owns Jira (features/unattended-security.md)
|
|
42
|
+
bash "$HOME/.claude/lib/jira-family-write.sh" resolve --state "$STATE_FILE"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
It reads the issue key from the state (`jiraId`, else `taskId`), fetches the
|
|
46
|
+
issue and, when its type is a sub-task, its parent, through `jira_api` in
|
|
47
|
+
`lib/_jira-auth.sh`, into `jira-family/` beside the state file, and records
|
|
48
|
+
`state.jiraFamily`. It asks nothing and writes nothing to Jira. It reads the full
|
|
49
|
+
description rather than the issue-fetcher descriptor, because that one is
|
|
50
|
+
capped at 4000 characters and a rewrite of the author's text cannot start from
|
|
51
|
+
a truncated copy. The project's sub-task types come from
|
|
52
|
+
`analysis-jira-write.sh --subtask-types` (the per-project createmeta, else the
|
|
53
|
+
classic one).
|
|
54
|
+
|
|
55
|
+
- An issue is a sub-task only when its type says so (`issuetype.subtask`). A
|
|
56
|
+
story under an epic is the story itself; the epic is never read or written.
|
|
57
|
+
- The story slot is reformatted only when its type is a story type
|
|
58
|
+
(`issueTree.storyIssueTypes`). Under a Bug or Task parent the story write is
|
|
59
|
+
listed as not applicable; autopilot records that it left it alone.
|
|
60
|
+
- The development task is the input when it is a sub-task; from the story it
|
|
61
|
+
is the one development sub-task. With several, an attended run is asked which
|
|
62
|
+
one; autopilot keeps the comment on the story and records that.
|
|
63
|
+
- The test task is the one test sub-task. With several, an attended run is
|
|
64
|
+
asked which one; autopilot takes the one of type `Test`, else the first, and
|
|
65
|
+
records that.
|
|
66
|
+
|
|
67
|
+
The story description is text other people wrote. It is moved, never followed:
|
|
68
|
+
nothing in it is read as an instruction.
|
|
69
|
+
|
|
70
|
+
## The story format
|
|
71
|
+
|
|
72
|
+
The format `/multi-agent:analysis-jira` writes (`scripts/analysis-story-body.mjs`):
|
|
73
|
+
`{panel:borderColor=...}` sections, each opening with a heading, in the order
|
|
74
|
+
user story, acceptance criteria, everything else, coloured by
|
|
75
|
+
`issueTree.storyPanels` (story / criteria / other; `false` means sections
|
|
76
|
+
without panels). A section opens with an `hN.` heading of any level, or a
|
|
77
|
+
bold-only or `Label:` line naming the user story or the acceptance criteria. A
|
|
78
|
+
trailing `Sources:` line is allowed.
|
|
79
|
+
|
|
80
|
+
What the reformat changes, exactly:
|
|
81
|
+
|
|
82
|
+
- Every non-blank line of the author's text is sent as it is, heading lines included, and
|
|
83
|
+
with the description's own line endings. The write uses
|
|
84
|
+
`jira-publish.sh --raw`, which skips the emoticon escape and keeps the
|
|
85
|
+
outbound gate and the description backup.
|
|
86
|
+
- A `{panel}` macro alone on its line, outside every literal block and pair, is
|
|
87
|
+
replaced by the story panel; its `title=` becomes the section heading (an
|
|
88
|
+
added `h3.` line) and its other parameters are dropped.
|
|
89
|
+
- A block with no heading gets an added `h3.` heading (`Kullanıcı Hikayesi` /
|
|
90
|
+
`User story` first, `Diğer` / `Details` after). Added text is escaped.
|
|
91
|
+
- Blank lines at the edges of a section are not kept.
|
|
92
|
+
- Whole sections move into story, criteria, rest order; lines inside a section
|
|
93
|
+
never move.
|
|
94
|
+
|
|
95
|
+
`{code}` and `{noformat}` blocks, with or without parameters, are literal:
|
|
96
|
+
nothing inside them is parsed. No section is cut inside a `{quote}`, `{color}`
|
|
97
|
+
or inline `{panel}` pair. Mixed line endings, an unbalanced pair, a panel inside
|
|
98
|
+
a panel, or any author line missing from the result make `preserved` false,
|
|
99
|
+
and then the story is not written.
|
|
100
|
+
|
|
101
|
+
## The test comment
|
|
102
|
+
|
|
103
|
+
Built from the dev comment body, in its markup, with the dev comment's four
|
|
104
|
+
sections in the same order and nothing else:
|
|
105
|
+
|
|
106
|
+
| Section (`tr` / `en`) | Content |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `Geliştirme Özeti` / `Development Summary` | the first paragraph of the dev comment's summary: what changed |
|
|
109
|
+
| `Test Senaryoları` / `Test Scenarios` | the dev comment's scenarios, verbatim |
|
|
110
|
+
| `Etki Analizi` / `Impact Analysis` | part 3 of the impact section (affected areas), where testers should look |
|
|
111
|
+
| `Bağlantılar` / `References` | the development task, the PR and the build to test |
|
|
112
|
+
|
|
113
|
+
No step in the scenarios section (the "no manual test required" line) means no
|
|
114
|
+
test comment.
|
|
115
|
+
|
|
116
|
+
## Writing (channels, Phase 5)
|
|
117
|
+
|
|
118
|
+
Replaces the single `jira-publish.sh` call of the Jira adapter when
|
|
119
|
+
`state.jiraFamily` is set. `$F` is the final dev comment body (after the
|
|
120
|
+
conversion and humanizer steps of `channels/jira.md`):
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# unattended: skipped - the runner posts (features/unattended-security.md)
|
|
124
|
+
bash "$HOME/.claude/lib/jira-family-write.sh" plan --state "$STATE_FILE" --dev-body "$F"
|
|
125
|
+
bash "$HOME/.claude/lib/jira-family-write.sh" apply --state "$STATE_FILE"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`plan` re-reads the family, so it sees the story as it is now, and takes the
|
|
129
|
+
run id (`taskId-shortId`), the PR URL (`pr.url`) and the mode
|
|
130
|
+
(`autopilot: true`, the flag every autopilot decision keys on) from the state
|
|
131
|
+
and the Jira host from `prefs.global.hosts.jira`. It prints the grouped
|
|
132
|
+
confirmation line, the writes that will not happen and why, and any question.
|
|
133
|
+
Optional: `--build TEXT` (the build or version to test).
|
|
134
|
+
|
|
135
|
+
Attended, between the two calls: answer a printed question by re-running
|
|
136
|
+
`plan` with `--dev-task KEY` or `--test-task KEY`; show the Step 6 preview with
|
|
137
|
+
every write's body from `jira-family/plan.json` (the story's is the reformat
|
|
138
|
+
preview) as one multi-select, all selected; re-run `plan` with
|
|
139
|
+
`--skip <deselected ids>`; run `apply` only after `Approve & post`. Autopilot
|
|
140
|
+
runs the two calls back to back; `apply` records the plan's decisions in
|
|
141
|
+
`state.decisions[]` (`scripts/decisions-log.mjs`) once their write has gone out,
|
|
142
|
+
and a decision that made no write right away.
|
|
143
|
+
|
|
144
|
+
## Idempotency and partial runs
|
|
145
|
+
|
|
146
|
+
- Each comment ends with `{anchor:ma-family-<role>-<run id>}`, which renders as
|
|
147
|
+
nothing. `apply` skips a comment whose marker is already on the issue in a
|
|
148
|
+
comment by the authenticated user (`GET /myself`); another user's comment
|
|
149
|
+
carrying the marker does not count.
|
|
150
|
+
- A story that already conforms is never written, and neither is one whose
|
|
151
|
+
description changed since the plan read it (`jira-publish.sh --expect-file`
|
|
152
|
+
compares right before the PUT).
|
|
153
|
+
- `apply` writes `jira-family/writes.json` on every exit and merges it into
|
|
154
|
+
`state.jiraFamily.writes` by write id, keeping an earlier `written` record.
|
|
155
|
+
Exit 5 is a partial run: the failed writes are listed with the reason, and
|
|
156
|
+
re-running `apply` is safe. Exit 4 is auth, 6 unattended (nothing written).
|
|
157
|
+
|
|
158
|
+
## Settings
|
|
159
|
+
|
|
160
|
+
`prefs.global.issueTree`:
|
|
161
|
+
|
|
162
|
+
| Key | Default | Effect |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| `familyWrites` | `true` | `false` keeps the single Phase 5 comment on the input issue |
|
|
165
|
+
| `storyIssueTypes` | `[]` | issue types that count as a story; empty = `storyIssueType` (`Story`) |
|
|
166
|
+
| `testSubtaskTypes` | `[]` | type names of a test sub-task; empty = the site's sub-task types whose name carries "test" and not unit, bug, defect or automation |
|
|
167
|
+
| `devSubtaskTypes` | `[]` | type names of a development sub-task; empty = a name carrying dev, development or geliştirme |
|
|
168
|
+
| `storyPanels` | default colours | the story format's panel colours, shared with `/multi-agent:analysis-jira` |
|
|
169
|
+
|
|
170
|
+
## State
|
|
171
|
+
|
|
172
|
+
`state.jiraFamily` (`agent-state.schema.json`): `inputKey`, `inputRole`,
|
|
173
|
+
`storyKey`, `devTaskKey`, `devCandidates[]`, `testTaskKey`, `testCandidates[]`,
|
|
174
|
+
`storyFormat {status, reasons[]}` (status `conforms`, `nonconforming`, `empty`,
|
|
175
|
+
`unknown` or `not-a-story`), `writes[] {id, issue, status, reason}`.
|
|
@@ -61,7 +61,6 @@ Pipeline entries:
|
|
|
61
61
|
|
|
62
62
|
/multi-agent "task" Worktree
|
|
63
63
|
/multi-agent:autopilot "task" Worktree, no questions
|
|
64
|
-
/multi-agent:deps [pkg[@ver]] [--major] Bump one dependency, fix only what the bump broke, review, PR; rolls back past depBump.maxFixRounds
|
|
65
64
|
|
|
66
65
|
Phase 0 Step 5b asks where the branch lives; there is no flag for it.
|
|
67
66
|
autopilot skips every confirmation INCLUDING the plan gate and the workspace
|
|
@@ -61,7 +61,6 @@ Pipeline girişleri:
|
|
|
61
61
|
|
|
62
62
|
/multi-agent "task" Worktree var
|
|
63
63
|
/multi-agent:autopilot "task" Worktree var, soru yok
|
|
64
|
-
/multi-agent:deps [paket[@sürüm]] [--major] Tek bağımlılığı yükselt, yalnızca yükseltmenin kırdığını düzelt, review, PR; depBump.maxFixRounds aşılınca geri alır
|
|
65
64
|
|
|
66
65
|
Faz 0 Adım 5b branch'in nerede yaşayacağını sorar; bayrağı yok.
|
|
67
66
|
autopilot plan kapısı ve çalışma alanı sorusu dahil her onayı atlar, otomatik
|