@mmerterden/multi-agent-pipeline 20.25.1 → 20.27.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 +29 -0
- package/README.md +1 -1
- package/README.tr.md +1 -1
- package/docs/facts.json +3 -3
- package/install/catalog-history.json +1 -1
- package/manifest.json +50 -39
- package/package.json +5 -5
- package/pipeline/commands/multi-agent/channels/SKILL.md +23 -19
- package/pipeline/lib/_jira-auth.sh +3 -1
- package/pipeline/lib/issue-fetcher.sh +40 -2
- package/pipeline/lib/jira-family-write.sh +339 -0
- package/pipeline/lib/jira-publish.sh +28 -1
- package/pipeline/lib/jira-remote-links.sh +99 -0
- package/pipeline/multi-agent-refs/analysis/intake.md +2 -0
- package/pipeline/multi-agent-refs/analysis/render.md +4 -2
- package/pipeline/multi-agent-refs/channels/jira.md +12 -8
- package/pipeline/multi-agent-refs/channels/pr.md +11 -8
- package/pipeline/multi-agent-refs/channels/test-run.md +52 -0
- package/pipeline/multi-agent-refs/conventions-defaults.md +2 -0
- package/pipeline/multi-agent-refs/features/analysis-issue-links.md +42 -0
- package/pipeline/multi-agent-refs/features/jira-family.md +175 -0
- package/pipeline/multi-agent-refs/features/localization-reuse.md +25 -0
- package/pipeline/multi-agent-refs/features/url-enrichment.md +11 -3
- package/pipeline/multi-agent-refs/features/visual-evidence.md +35 -9
- package/pipeline/multi-agent-refs/generate-issue.md +9 -11
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +13 -7
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +2 -0
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +2 -1
- package/pipeline/multi-agent-refs/phases.md +1 -1
- package/pipeline/multi-agent-refs/platform-parity.md +1 -1
- package/pipeline/multi-agent-refs/tracker-contract.md +10 -6
- package/pipeline/schemas/agent-state.schema.json +86 -0
- package/pipeline/schemas/prefs.schema.json +34 -0
- package/pipeline/schemas/unattended-policy.json +1 -0
- package/pipeline/scripts/_sibling-root.mjs +38 -0
- package/pipeline/scripts/gen-facts.mjs +5 -3
- package/pipeline/scripts/jira-family.mjs +947 -0
- package/pipeline/scripts/jira-wiki-escape.mjs +3 -3
- package/pipeline/scripts/plan-remote-links.mjs +339 -0
- package/pipeline/scripts/render-flow-run.mjs +583 -0
- package/pipeline/scripts/render-jira-evidence.mjs +213 -0
- package/pipeline/skills/.skill-manifest.json +5 -5
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +5 -0
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +15 -2
- package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +5 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* render-jira-evidence.mjs - the evidence lines of a Jira comment.
|
|
4
|
+
*
|
|
5
|
+
* Screenshots and recordings are uploaded as Jira attachments
|
|
6
|
+
* (`jira-attach.sh`, which returns each file's attachment URL). This script
|
|
7
|
+
* turns `state.visualEvidence` into the lines that reference them.
|
|
8
|
+
*
|
|
9
|
+
* Links are the default. One full-size image embedded per scenario makes a
|
|
10
|
+
* comment many screens long; a link line keeps the comment readable and still
|
|
11
|
+
* opens the attachment in one click. `--mode thumbnails` embeds images as small
|
|
12
|
+
* thumbnails instead. Recordings are always links: an embedded player is the
|
|
13
|
+
* same length problem with a larger frame.
|
|
14
|
+
*
|
|
15
|
+
* An attachment without a URL is named and marked as attached, never linked to
|
|
16
|
+
* an address built by hand.
|
|
17
|
+
*
|
|
18
|
+
* Output (JSON): {summary, flow, scenarios: [{id, line}], gaps: [line]}
|
|
19
|
+
* summary the before/after line for the summary section, or null
|
|
20
|
+
* flow the flow recording line for the test scenarios section, or null
|
|
21
|
+
* scenarios one evidence line per scenario, placed under that scenario
|
|
22
|
+
* gaps one line per recorded gap, with its reason
|
|
23
|
+
*
|
|
24
|
+
* Lines are markdown; the Jira adapter's wiki conversion turns `[text](url)`
|
|
25
|
+
* into `[text|url]`. Thumbnail markup is already wiki syntax and passes through.
|
|
26
|
+
*
|
|
27
|
+
* Usage:
|
|
28
|
+
* node render-jira-evidence.mjs --state agent-state.json [--lang tr|en] [--mode links|thumbnails]
|
|
29
|
+
* node render-jira-evidence.mjs --measure <body file> [--limit N]
|
|
30
|
+
*/
|
|
31
|
+
import { readFileSync } from "node:fs";
|
|
32
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
33
|
+
|
|
34
|
+
export const DEFAULT_LIMIT = 8000;
|
|
35
|
+
|
|
36
|
+
const LABELS = {
|
|
37
|
+
tr: {
|
|
38
|
+
evidence: "Kanıt",
|
|
39
|
+
before: "Önce",
|
|
40
|
+
after: "Sonra",
|
|
41
|
+
screen: "Ekran",
|
|
42
|
+
recording: "Kayıt",
|
|
43
|
+
attached: "ekte",
|
|
44
|
+
notUploaded: "yüklenmedi",
|
|
45
|
+
none: "yok",
|
|
46
|
+
video: "Kayıt",
|
|
47
|
+
},
|
|
48
|
+
en: {
|
|
49
|
+
evidence: "Evidence",
|
|
50
|
+
before: "Before",
|
|
51
|
+
after: "After",
|
|
52
|
+
screen: "Screen",
|
|
53
|
+
recording: "Recording",
|
|
54
|
+
attached: "attached",
|
|
55
|
+
notUploaded: "not uploaded",
|
|
56
|
+
none: "none",
|
|
57
|
+
video: "Recording",
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Jira wiki markup a free-text value could open: links, embeds, macros, tables.
|
|
62
|
+
function plain(text) {
|
|
63
|
+
return String(text).replace(/[[\]()|!{}]/g, "\\$&");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function nameOf(item) {
|
|
67
|
+
return item?.jiraFilename || (item?.file ? String(item.file).split(/[\\/]/).pop() : null);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function link(item, labels) {
|
|
71
|
+
const name = nameOf(item);
|
|
72
|
+
if (!name) return null;
|
|
73
|
+
const text = name.replace(/[[\]()|]/g, "\\$&");
|
|
74
|
+
if (item.url) return `[${text}](${item.url})`;
|
|
75
|
+
return `${text} (${item.jiraFilename ? labels.attached : labels.notUploaded})`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Only an uploaded attachment can be embedded; anything else would render as a broken image.
|
|
79
|
+
function thumb(item) {
|
|
80
|
+
const name = item?.jiraFilename;
|
|
81
|
+
// `!` and `|` would end the embed early; such a name is linked instead.
|
|
82
|
+
return name && !/[!|]/.test(name) ? `!${name}|thumbnail!` : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function links(items, labels) {
|
|
86
|
+
return (items || []).map((item) => link(item, labels)).filter(Boolean);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function renderEvidence(visualEvidence, { lang = "tr", mode = "links" } = {}) {
|
|
90
|
+
const labels = LABELS[lang] || LABELS.en;
|
|
91
|
+
const empty = { summary: null, flow: null, scenarios: [], gaps: [] };
|
|
92
|
+
if (!visualEvidence || typeof visualEvidence !== "object") return empty;
|
|
93
|
+
const thumbnails = mode === "thumbnails";
|
|
94
|
+
|
|
95
|
+
let summary = null;
|
|
96
|
+
if (thumbnails) {
|
|
97
|
+
// An image that cannot be embedded is still named, as a link or as not uploaded.
|
|
98
|
+
const row = [...(visualEvidence.before || []), ...(visualEvidence.after || [])]
|
|
99
|
+
.map((item) => thumb(item) || link(item, labels))
|
|
100
|
+
.filter(Boolean);
|
|
101
|
+
if (row.length) summary = `${labels.before} / ${labels.after}\n${row.join(" ")}`;
|
|
102
|
+
} else {
|
|
103
|
+
const parts = [];
|
|
104
|
+
const before = links(visualEvidence.before, labels);
|
|
105
|
+
const after = links(visualEvidence.after, labels);
|
|
106
|
+
if (before.length) parts.push(`${labels.before} ${before.join(", ")}`);
|
|
107
|
+
if (after.length) parts.push(`${labels.after} ${after.join(", ")}`);
|
|
108
|
+
if (parts.length) summary = `${labels.evidence}: ${parts.join(" · ")}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let flow = null;
|
|
112
|
+
const video = link(visualEvidence.video, labels);
|
|
113
|
+
if (video) {
|
|
114
|
+
const tier = Number.isInteger(visualEvidence.videoTier)
|
|
115
|
+
? ` · tier ${visualEvidence.videoTier}`
|
|
116
|
+
: "";
|
|
117
|
+
flow = `${labels.recording}: ${video}${tier}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const scenarios = [];
|
|
121
|
+
for (const scenario of visualEvidence.scenarios || []) {
|
|
122
|
+
const parts = [];
|
|
123
|
+
if (thumbnails && thumb(scenario.image)) parts.push(thumb(scenario.image));
|
|
124
|
+
else if (link(scenario.image, labels))
|
|
125
|
+
parts.push(`${labels.screen} ${link(scenario.image, labels)}`);
|
|
126
|
+
const recording = link(scenario.video, labels);
|
|
127
|
+
if (recording) parts.push(`${labels.recording} ${recording}`);
|
|
128
|
+
if (!parts.length) continue;
|
|
129
|
+
const line =
|
|
130
|
+
thumbnails && thumb(scenario.image)
|
|
131
|
+
? parts.join(" · ")
|
|
132
|
+
: `${labels.evidence}: ${parts.join(" · ")}`;
|
|
133
|
+
scenarios.push({ id: scenario.id || null, line });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const gaps = (visualEvidence.gaps || [])
|
|
137
|
+
.filter((gap) => gap?.what)
|
|
138
|
+
.map((gap) => {
|
|
139
|
+
const what = Object.hasOwn(labels, gap.what) ? labels[gap.what] : plain(gap.what);
|
|
140
|
+
const label = what.charAt(0).toUpperCase() + what.slice(1);
|
|
141
|
+
return `${label}: ${labels.none}${gap.reason ? ` - ${plain(gap.reason)}` : ""}`;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return { summary, flow, scenarios, gaps };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Size of a comment body, and how many images it embeds at full size. */
|
|
148
|
+
export function measureBody(body, limit = DEFAULT_LIMIT) {
|
|
149
|
+
const text = String(body);
|
|
150
|
+
const embeds = [...text.matchAll(/!([^!\n|]+)(\|[^!\n]*)?!/g)].filter(
|
|
151
|
+
([, , params]) => !/thumbnail/i.test(params || ""),
|
|
152
|
+
);
|
|
153
|
+
const embeddedImages = embeds.filter(([, name]) =>
|
|
154
|
+
/\.(?:png|jpe?g|gif|webp)$/i.test(name),
|
|
155
|
+
).length;
|
|
156
|
+
const embeddedVideos = embeds.filter(([, name]) => /\.(?:mp4|mov|webm)$/i.test(name)).length;
|
|
157
|
+
return { chars: text.length, limit, over: text.length > limit, embeddedImages, embeddedVideos };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function main(argv) {
|
|
161
|
+
const opts = {};
|
|
162
|
+
for (let i = 0; i < argv.length; i++) {
|
|
163
|
+
const arg = argv[i];
|
|
164
|
+
if (
|
|
165
|
+
!["--state", "--lang", "--mode", "--measure", "--limit"].includes(arg) ||
|
|
166
|
+
argv[i + 1] === undefined
|
|
167
|
+
) {
|
|
168
|
+
process.stderr.write(`render-jira-evidence: unknown or incomplete argument: ${arg}\n`);
|
|
169
|
+
return 2;
|
|
170
|
+
}
|
|
171
|
+
opts[arg.slice(2)] = argv[++i];
|
|
172
|
+
}
|
|
173
|
+
const read = (path) => {
|
|
174
|
+
try {
|
|
175
|
+
return readFileSync(path, "utf-8");
|
|
176
|
+
} catch {
|
|
177
|
+
process.stderr.write(`render-jira-evidence: cannot read ${path}\n`);
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
if (opts.measure) {
|
|
182
|
+
const limit = opts.limit === undefined ? DEFAULT_LIMIT : Number(opts.limit);
|
|
183
|
+
if (!Number.isInteger(limit) || limit <= 0) {
|
|
184
|
+
process.stderr.write(`render-jira-evidence: --limit must be a positive integer\n`);
|
|
185
|
+
return 2;
|
|
186
|
+
}
|
|
187
|
+
const body = read(opts.measure);
|
|
188
|
+
if (body === null) return 2;
|
|
189
|
+
process.stdout.write(`${JSON.stringify(measureBody(body, limit))}\n`);
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
if (!opts.state) {
|
|
193
|
+
process.stderr.write("render-jira-evidence: --state or --measure is required\n");
|
|
194
|
+
return 2;
|
|
195
|
+
}
|
|
196
|
+
const raw = read(opts.state);
|
|
197
|
+
if (raw === null) return 2;
|
|
198
|
+
let state;
|
|
199
|
+
try {
|
|
200
|
+
state = JSON.parse(raw);
|
|
201
|
+
} catch {
|
|
202
|
+
process.stderr.write(`render-jira-evidence: ${opts.state} is not JSON\n`);
|
|
203
|
+
return 2;
|
|
204
|
+
}
|
|
205
|
+
const out = renderEvidence(state.visualEvidence, {
|
|
206
|
+
lang: opts.lang || "tr",
|
|
207
|
+
mode: opts.mode || "links",
|
|
208
|
+
});
|
|
209
|
+
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (invokedDirectly(import.meta.url)) process.exit(main(process.argv.slice(2)));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-16T05:27:33Z",
|
|
4
4
|
"skillCount": 229,
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"path": "shared/core/multi-agent-channels/SKILL.md",
|
|
52
|
-
"sha256": "
|
|
52
|
+
"sha256": "b55e8461a1bcdc1b4b88c42e086f599fccda2a89d7f56a29ac11e5ebc12a3623"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"path": "shared/core/multi-agent-complaint-analysis/SKILL.md",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"path": "shared/core/multi-agent-create-jira/SKILL.md",
|
|
60
|
-
"sha256": "
|
|
60
|
+
"sha256": "15efd1a0c661f848c2afa427015cd6c31e9f6680900111a4ab1a2e34ad705e81"
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
"path": "shared/core/multi-agent-design-check/SKILL.md",
|
|
@@ -273,7 +273,7 @@
|
|
|
273
273
|
},
|
|
274
274
|
{
|
|
275
275
|
"path": "shared/core/multi-agent/SKILL.md",
|
|
276
|
-
"sha256": "
|
|
276
|
+
"sha256": "079b49a1981c63d90798089a343b7f324571c4f7cc6ad3fb757e82719c35eb0a"
|
|
277
277
|
},
|
|
278
278
|
{
|
|
279
279
|
"path": "shared/external/accessibility-compliance-accessibility-audit/SKILL.md",
|
|
@@ -577,7 +577,7 @@
|
|
|
577
577
|
},
|
|
578
578
|
{
|
|
579
579
|
"path": "shared/external/ios-localization/SKILL.md",
|
|
580
|
-
"sha256": "
|
|
580
|
+
"sha256": "eb1e0dc8f68d4d6c29af6562186060f265ce4fadad633638d18b9aee098bfe4a"
|
|
581
581
|
},
|
|
582
582
|
{
|
|
583
583
|
"path": "shared/external/ios-module-structure/SKILL.md",
|
|
@@ -203,6 +203,11 @@ Per-project input parsing (my-ios-app / my-figma-app / generic), worktree creati
|
|
|
203
203
|
|
|
204
204
|
**IMPORTANT**: Update `agent-state.json` at EVERY phase transition. This file is the resume source of truth.
|
|
205
205
|
|
|
206
|
+
**Issue family** (Jira input, once `agent-state.json` exists): run
|
|
207
|
+
`bash "$HOME/.copilot/lib/jira-family-write.sh" resolve --state "$STATE_FILE"`. It records the
|
|
208
|
+
story, dev and test sub-tasks and the story format check in `state.jiraFamily`, and asks and writes nothing;
|
|
209
|
+
the channels Jira step writes to them.
|
|
210
|
+
|
|
206
211
|
### Phase 1: Plan
|
|
207
212
|
Two halves, one phase, one approval gate.
|
|
208
213
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: multi-agent-channels
|
|
3
3
|
description: "Multi-channel reporter - Jira/Confluence/Wiki/PR description. Multi-select channels + content, humanizer pass, reviewer-preserving Bitbucket PR PUT. Phase 5 delegates to this skill; users can invoke post-hoc by Jira ID / PR URL / branch."
|
|
4
4
|
user-invocable: true
|
|
5
|
-
argument-hint: '<Jira-id | PR-url | #N | Jira-url> [--channels pr,jira,confluence,wiki] [--content normal,technical,test,auto-diff,note] [--message "..."] [--message-file path] [--dry-run] [--append] [--ready]'
|
|
5
|
+
argument-hint: '<Jira-id | PR-url | #N | Jira-url> [--channels pr,jira,confluence,wiki] [--content normal,technical,test,auto-diff,note,test-run] [--flow-run <runId>[,<runId>...]] [--message "..."] [--message-file path] [--dry-run] [--append] [--ready]'
|
|
6
6
|
metadata:
|
|
7
7
|
language: en
|
|
8
8
|
---
|
|
@@ -62,6 +62,7 @@ Rendered in English (`promptLanguage` is locked to `en`).
|
|
|
62
62
|
│ [x] Normal analiz (Phase 1+2+4'ten - high-level impact + risks)
|
|
63
63
|
│ [ ] Teknik analiz (Changes · Architecture · Dependencies)
|
|
64
64
|
│ [x] Test senaryoları (Phase 1+2+4'ten)
|
|
65
|
+
│ [ ] Test Koşumu (Xray) (flow runner history.jsonl)
|
|
65
66
|
│ [ ] PR diff'ten auto-gen (PR varsa diff'i summarize eder)
|
|
66
67
|
│ [ ] Manuel not (--message / --message-file)
|
|
67
68
|
└───────────────────────────────────────────────────────
|
|
@@ -73,6 +74,7 @@ Rendered in English (`promptLanguage` is locked to `en`).
|
|
|
73
74
|
- Teknik analiz unselectable if no Phase 1 planning log AND no PR diff.
|
|
74
75
|
- Auto-diff unselectable if no PR found.
|
|
75
76
|
- Manuel not always available.
|
|
77
|
+
- Test Koşumu (Xray) unselectable when `render-flow-run.mjs --json` reports `available: false`; its `reason` is shown.
|
|
76
78
|
|
|
77
79
|
## Channel adapters
|
|
78
80
|
|
|
@@ -169,6 +171,13 @@ argv. Jira turns `:)` `(x)` `(!)` `(/)` into emoticon images, including inside
|
|
|
169
171
|
`{{monospace}}`; an escape that has to be remembered is one that is eventually
|
|
170
172
|
forgotten, and that is how the selector above shipped.
|
|
171
173
|
|
|
174
|
+
**Issue family** (`state.jiraFamily` set by Phase 0): the same body goes to the development sub-task, the test scenarios to the test sub-task (or, with none, the story) and a reformat of a story outside the story format to the story. `plan` prints one grouped confirmation ("3 yazma: story açıklaması, dev yorumu, test yorumu") and any question; show every write's body from `jira-family/plan.json` as one multi-select, re-run `plan` with `--skip <deselected ids>` (or `--dev-task` / `--test-task` for a question), then `apply`. Autopilot runs both back to back and records its choices in `state.decisions[]`:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
bash "$HOME/.copilot/lib/jira-family-write.sh" plan --state "$STATE_FILE" --dev-body "$F"
|
|
178
|
+
bash "$HOME/.copilot/lib/jira-family-write.sh" apply --state "$STATE_FILE"
|
|
179
|
+
```
|
|
180
|
+
|
|
172
181
|
### Confluence page
|
|
173
182
|
|
|
174
183
|
1. Token from `keychainMapping.confluence` via `~/.copilot/lib/credential-store.sh get` - missing→Token Save Flow from setup skill.
|
|
@@ -220,6 +229,9 @@ forgotten, and that is how the selector above shipped.
|
|
|
220
229
|
1. {step}
|
|
221
230
|
2. **Expected:** {what should still behave as before}
|
|
222
231
|
|
|
232
|
+
### Test Koşumu ← if "Test Koşumu (Xray)" selected
|
|
233
|
+
{`render-flow-run.mjs` output, unedited: Xray Test Execution, Test Set / Plan, run, results, one row per scenario. Read-only; a key the run history does not record is never written. Full contract: `channels/test-run.md`.}
|
|
234
|
+
|
|
223
235
|
### Notes ← if "Manuel not" selected
|
|
224
236
|
{user message}
|
|
225
237
|
|
|
@@ -257,7 +269,8 @@ Target length: < 80 lines (fits one screen). Auto-diff capped at 5 lines per sec
|
|
|
257
269
|
| Flag | Effect |
|
|
258
270
|
|------|--------|
|
|
259
271
|
| `--channels pr,jira,confluence,wiki` | Non-interactive channel selection. |
|
|
260
|
-
| `--content normal,test,auto-diff,note` | Non-interactive content selection. |
|
|
272
|
+
| `--content normal,test,auto-diff,note,test-run` | Non-interactive content selection. |
|
|
273
|
+
| `--flow-run <runId>[,<runId>...]` | Runs reported by the Test Koşumu (Xray) block; wins over `state.evidence.flowRuns[]` and discovery. |
|
|
261
274
|
| `--message "..."` | Manual mode - user text as source. Forces `content=note`. |
|
|
262
275
|
| `--message-file path` | Manual mode, long message from file. |
|
|
263
276
|
| `--append` | PR description: append instead of replace. |
|
|
@@ -44,7 +44,7 @@ A `C` section fires on: Design Reference → Figma URL; Screenshots → pasted i
|
|
|
44
44
|
7. **Draft + clarifying questions** - compose summary (`{minedPrefix} {title}`, 255 trunc) + description from the type's **standard template** in Jira wiki markup (`h3.`), auto-sizing conditional sections, humanizer pass, in `outputLanguage`; body to a file for `jq -n --rawfile` (UTF-8 verbatim, `--data-binary @file`, no re-encode), passed through `node "$HOME/.claude/scripts/jira-wiki-escape.mjs"` first - the description field expands `:)` `(x)` `(!)` `(/)` into emoticon images. Ask ONLY genuinely unknown fields: component, epic (when usage rate is high), priority (when norm ambiguous), labels, assignee, sprint vs backlog, required customs, and any always-present section still empty (Bug Steps/Environment, or a Task/Story with no derivable Scope/AC). Never invent content the user did not supply.
|
|
45
45
|
8. **Full preview + approval gate (never skipped)** - render the complete issue (all fields + full description + attachment plan, uploads default off). Ask: `Approve` / `Edit` (apply → re-render → re-ask, loop) / `Cancel` (nothing created). No flag or mode bypasses this gate.
|
|
46
46
|
9. **Create** - `POST /rest/api/2/issue` with `jq -n --rawfile` payload + `curl --data-binary @file`. 400 field errors → show verbatim, re-ask those fields, re-preview.
|
|
47
|
-
10. **Post-create** (each failure-isolated: warn + continue): sprint chosen → `POST /rest/agile/1.0/sprint/{id}/issue` `{"issues":["KEY"]}`; Figma →
|
|
47
|
+
10. **Post-create** (each failure-isolated: warn + continue): sprint chosen → `POST /rest/agile/1.0/sprint/{id}/issue` `{"issues":["KEY"]}`; Figma and Swagger → Issue Links through `plan-remote-links.mjs` + `jira-remote-links.sh apply` (one stable global id per link, so a retry adds nothing); screenshots opt-in → `POST /rest/api/2/issue/{KEY}/attachments` with `X-Atlassian-Token: no-check`.
|
|
48
48
|
11. **Report** - `Created {KEY}: https://{host}/browse/{KEY}` + which post-steps ran.
|
|
49
49
|
|
|
50
50
|
## Error paths
|
|
@@ -81,6 +81,11 @@ catalogs require Xcode 26, and do not tie them to iOS 17.
|
|
|
81
81
|
- Pick stable, hand-written keys for anything reached through a symbol. A key
|
|
82
82
|
derived from the English copy renames its symbol the moment someone edits
|
|
83
83
|
the wording, and every call site churns.
|
|
84
|
+
- When the catalog is generated from a source shared with other platforms
|
|
85
|
+
(mirrored by a sync bot, stamped "do not edit"), a new key is added upstream,
|
|
86
|
+
and only after searching that source by key and by display text: a key
|
|
87
|
+
another platform already added is reused, never duplicated. The procedure is
|
|
88
|
+
the stack toolkit's `reference/component/key-naming.md`, "Reuse before mint".
|
|
84
89
|
|
|
85
90
|
```swift
|
|
86
91
|
// Localizable.xcstrings: "cart_empty" -> "Your cart is empty"
|