@mmerterden/multi-agent-pipeline 14.2.1 → 15.0.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.
Files changed (184) hide show
  1. package/CHANGELOG.md +118 -6
  2. package/README.md +20 -9
  3. package/README.tr.md +150 -0
  4. package/docs/FIGMA_PIPELINE.md +3 -3
  5. package/docs/adr/0006-skills-core-external-split.md +1 -1
  6. package/docs/adr/0009-claude-stack-skills-plugin-only.md +31 -0
  7. package/docs/adr/README.md +1 -0
  8. package/docs/architecture.md +24 -9
  9. package/docs/ecosystem.md +237 -0
  10. package/docs/features.md +5 -5
  11. package/index.js +2 -0
  12. package/install/_codex-agents.mjs +11 -2
  13. package/install/_common.mjs +65 -1
  14. package/install/_dev-only-files.mjs +0 -1
  15. package/install/_platform-filter.mjs +73 -7
  16. package/install/_plugin-skills.mjs +33 -8
  17. package/install/claude.mjs +144 -59
  18. package/install/codex.mjs +37 -7
  19. package/install/copilot.mjs +36 -11
  20. package/install/index.mjs +6 -2
  21. package/install/templates/codex-instructions.md +1 -1
  22. package/install/templates/copilot-instructions.md +15 -12
  23. package/package.json +1 -2
  24. package/pipeline/commands/multi-agent/SKILL.md +4 -2
  25. package/pipeline/commands/multi-agent/analysis/SKILL.md +5 -5
  26. package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +2 -2
  27. package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -2
  28. package/pipeline/commands/multi-agent/build-optimize/SKILL.md +9 -9
  29. package/pipeline/commands/multi-agent/channels/SKILL.md +16 -5
  30. package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +186 -0
  31. package/pipeline/commands/multi-agent/create-jira/SKILL.md +4 -4
  32. package/pipeline/commands/multi-agent/dev/SKILL.md +10 -23
  33. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +10 -2
  34. package/pipeline/commands/multi-agent/dev-local/SKILL.md +10 -24
  35. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +10 -3
  36. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
  37. package/pipeline/commands/multi-agent/help/SKILL.md +19 -4
  38. package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
  39. package/pipeline/commands/multi-agent/jira/SKILL.md +13 -2
  40. package/pipeline/commands/multi-agent/language/SKILL.md +1 -1
  41. package/pipeline/commands/multi-agent/local/SKILL.md +6 -2
  42. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +6 -2
  43. package/pipeline/commands/multi-agent/log/SKILL.md +7 -1
  44. package/pipeline/commands/multi-agent/prune-prompts/SKILL.md +81 -0
  45. package/pipeline/commands/multi-agent/resume/SKILL.md +1 -1
  46. package/pipeline/commands/multi-agent/{ship → resume-local}/SKILL.md +13 -9
  47. package/pipeline/commands/multi-agent/setup/SKILL.md +5 -5
  48. package/pipeline/commands/multi-agent/stack/SKILL.md +55 -43
  49. package/pipeline/commands/multi-agent/store-ready/SKILL.md +3 -3
  50. package/pipeline/commands/multi-agent/sync/SKILL.md +18 -11
  51. package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +1 -1
  52. package/pipeline/commands/multi-agent/uninstall/SKILL.md +2 -0
  53. package/pipeline/commands/multi-agent/update/SKILL.md +1 -1
  54. package/pipeline/lib/extract-conventions.sh +44 -15
  55. package/pipeline/lib/fetch-figma-annotations.sh +8 -1
  56. package/pipeline/lib/fetch-fortify.sh +23 -8
  57. package/pipeline/lib/figma-screenshot.sh +11 -1
  58. package/pipeline/lib/issue-fetcher.sh +77 -10
  59. package/pipeline/lib/md2confluence-v3.py +16 -2
  60. package/pipeline/lib/parse-complaints.sh +306 -0
  61. package/pipeline/lib/plan-todos.sh +5 -2
  62. package/pipeline/lib/post-pr-review.sh +8 -6
  63. package/pipeline/lib/shadow-git.sh +50 -9
  64. package/pipeline/lib/submodule-detector.sh +8 -1
  65. package/pipeline/multi-agent-refs/_input-parser.md +1 -1
  66. package/pipeline/multi-agent-refs/channels/confluence.md +3 -0
  67. package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
  68. package/pipeline/multi-agent-refs/channels/jira.md +13 -2
  69. package/pipeline/multi-agent-refs/channels/pr-review-actions.md +1 -1
  70. package/pipeline/multi-agent-refs/channels/pr.md +20 -0
  71. package/pipeline/multi-agent-refs/channels/wiki.md +4 -4
  72. package/pipeline/multi-agent-refs/complaint-analysis-template.md +99 -0
  73. package/pipeline/multi-agent-refs/component-dispatch.md +6 -6
  74. package/pipeline/multi-agent-refs/cross-cli-contract.md +16 -16
  75. package/pipeline/multi-agent-refs/features/external-context-injection.md +1 -1
  76. package/pipeline/multi-agent-refs/features/stack-skill-routing.md +5 -5
  77. package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
  78. package/pipeline/multi-agent-refs/generate-issue.md +3 -3
  79. package/pipeline/multi-agent-refs/issue-jira-triad.md +3 -3
  80. package/pipeline/multi-agent-refs/payload-contracts.md +67 -0
  81. package/pipeline/multi-agent-refs/phases/modes.md +20 -0
  82. package/pipeline/multi-agent-refs/phases/phase-0-init.md +2 -2
  83. package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +7 -7
  84. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +5 -5
  85. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +3 -3
  86. package/pipeline/multi-agent-refs/phases/phase-4-review.md +12 -12
  87. package/pipeline/multi-agent-refs/phases/phase-5-test.md +1 -1
  88. package/pipeline/multi-agent-refs/phases/phase-6-commit.md +8 -40
  89. package/pipeline/multi-agent-refs/phases/phase-7-report.md +5 -3
  90. package/pipeline/multi-agent-refs/phases.md +6 -0
  91. package/pipeline/multi-agent-refs/rules.md +2 -0
  92. package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
  93. package/pipeline/multi-agent-refs/wiki-capture.md +2 -2
  94. package/pipeline/preferences-template.json +13 -5
  95. package/pipeline/rules/figma-pipeline.md +2 -2
  96. package/pipeline/schemas/agent-state.schema.json +1 -1
  97. package/pipeline/schemas/complaint-analysis-spec.schema.json +216 -0
  98. package/pipeline/schemas/migrations/prefs-2.5.0-to-2.6.0.mjs +46 -0
  99. package/pipeline/schemas/prefs.schema.json +277 -67
  100. package/pipeline/schemas/token-budget.json +2 -2
  101. package/pipeline/scripts/_stack-routing.mjs +79 -0
  102. package/pipeline/scripts/audit-log-rotate.sh +14 -1
  103. package/pipeline/scripts/build-skills-index.mjs +11 -0
  104. package/pipeline/scripts/build-stack-plugins.mjs +35 -60
  105. package/pipeline/scripts/check-derived-drift.mjs +65 -29
  106. package/pipeline/scripts/diff-explain.mjs +41 -3
  107. package/pipeline/scripts/diff-risk-score.mjs +72 -8
  108. package/pipeline/scripts/gc-worktrees.sh +4 -1
  109. package/pipeline/scripts/gen-mode-dispatch.mjs +1 -1
  110. package/pipeline/scripts/gen-skills-index.mjs +1 -1
  111. package/pipeline/scripts/learning-curve.mjs +8 -2
  112. package/pipeline/scripts/match-skills.mjs +8 -2
  113. package/pipeline/scripts/migrate-prefs.mjs +28 -20
  114. package/pipeline/scripts/output-quality-check.sh +15 -4
  115. package/pipeline/scripts/phase-tracker.sh +33 -12
  116. package/pipeline/scripts/phase0-exit-gate.mjs +3 -2
  117. package/pipeline/scripts/pre-commit-check.sh +69 -22
  118. package/pipeline/scripts/render-agent-log-cost.sh +8 -3
  119. package/pipeline/scripts/render-cost-summary.sh +42 -22
  120. package/pipeline/scripts/render-work-summary.sh +47 -13
  121. package/pipeline/scripts/review-scope.mjs +1 -1
  122. package/pipeline/scripts/run-aggregator.mjs +43 -14
  123. package/pipeline/scripts/scan-agent-config.sh +1 -1
  124. package/pipeline/scripts/skill-conformance.mjs +165 -30
  125. package/pipeline/scripts/smoke-cross-cli-behavior.sh +1 -1
  126. package/pipeline/scripts/smoke-schema-validation.sh +5 -1
  127. package/pipeline/scripts/test-gap-rules/android.json +25 -0
  128. package/pipeline/scripts/test-gap-rules/ios.json +34 -0
  129. package/pipeline/scripts/test-gap-rules/node.json +29 -0
  130. package/pipeline/scripts/test-gap-rules/python.json +25 -0
  131. package/pipeline/scripts/test-gap-scan.mjs +45 -6
  132. package/pipeline/scripts/uninstall.mjs +196 -14
  133. package/pipeline/scripts/update-issue-progress.sh +12 -16
  134. package/pipeline/scripts/validate-complaint-doc.mjs +229 -0
  135. package/pipeline/scripts/validate-reviewer.mjs +9 -3
  136. package/pipeline/scripts/worktree-finalize.sh +23 -2
  137. package/pipeline/skills/.skill-manifest.json +156 -108
  138. package/pipeline/skills/.skills-index.json +459 -13
  139. package/pipeline/skills/shared/README.md +15 -11
  140. package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +1 -1
  141. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +4 -0
  142. package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +1 -1
  143. package/pipeline/skills/shared/core/multi-agent-complaint-analysis/SKILL.md +49 -0
  144. package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
  145. package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +4 -17
  146. package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +8 -0
  147. package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +5 -18
  148. package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +8 -0
  149. package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +2 -2
  150. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +1 -1
  151. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +4 -0
  152. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +4 -0
  153. package/pipeline/skills/shared/core/multi-agent-prune-prompts/SKILL.md +83 -0
  154. package/pipeline/skills/shared/core/{multi-agent-ship → multi-agent-resume-local}/SKILL.md +10 -6
  155. package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +79 -22
  156. package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +1 -1
  157. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +8 -8
  158. package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +1 -1
  159. package/pipeline/skills/shared/external/ios-coding-standard/modules/_TEMPLATE.yml +2 -2
  160. package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +368 -33
  161. package/pipeline/skills/shared/external/ios-coding-standard/references/swiftlint.draft.yml +1 -2
  162. package/pipeline/skills/shared/external/ios-coding-standard/scripts/check_structure.py +765 -0
  163. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +75 -0
  164. package/pipeline/skills/shared/external/ios-module-structure/modules/_TEMPLATE.yml +131 -0
  165. package/pipeline/skills/shared/external/ios-module-structure/references/rules.yml +559 -0
  166. package/pipeline/skills/shared/external/ios-module-structure/scripts/check_structure.py +765 -0
  167. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +302 -0
  168. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +187 -0
  169. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +156 -0
  170. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +108 -0
  171. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +176 -0
  172. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +865 -0
  173. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +335 -0
  174. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +344 -0
  175. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +130 -0
  176. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +264 -0
  177. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +298 -0
  178. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +529 -0
  179. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +187 -0
  180. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +171 -0
  181. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +184 -0
  182. package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +26 -0
  183. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +173 -0
  184. package/pipeline/skills/skills-index.md +9 -5
@@ -0,0 +1,306 @@
1
+ #!/bin/bash
2
+ #
3
+ # parse-complaints.sh
4
+ # Normalize a batch of customer complaints into the uniform JSON list the
5
+ # /multi-agent:complaint-analysis command consumes. Accepts a file export
6
+ # (csv / xlsx / txt / json) or pasted free text on stdin, extracts any
7
+ # transaction / conversation ids embedded in the text, and redacts common
8
+ # PII shapes before anything leaves this script. The raw input is never
9
+ # copied into pipeline state - only the redacted view is.
10
+ #
11
+ # Input forms (exactly one source is required):
12
+ # ./parse-complaints.sh --file <path> [--format csv|xlsx|txt|json|auto]
13
+ # ./parse-complaints.sh --stdin [--format text]
14
+ #
15
+ # Options:
16
+ # --no-redact keep complaint text verbatim (debugging only; the skill
17
+ # never passes this flag)
18
+ #
19
+ # Column mapping (file formats, case-insensitive header match):
20
+ # trxId <- trxId | trx | transaction id / transaction_id / transactionId
21
+ # convId <- convId | conversationId | x-conversationId
22
+ # date <- date | created | createdAt | timestamp
23
+ # platformHint <- platform | channel | os | device
24
+ # text <- comment | complaint | text | description | message, else the
25
+ # longest text column
26
+ #
27
+ # Free-text mode splits on blank lines (or numbered bullets when the paste is
28
+ # one block) and pre-fills ids per block using the same label set as
29
+ # context-link-extractor.sh GRAYLOG_RE (x-conversationId | conversationId |
30
+ # convId | trxId | trx | transaction id).
31
+ #
32
+ # Redaction (default ON, applied after id extraction so ids survive):
33
+ # email -> [REDACTED:email]
34
+ # 13-19 digit card-like runs -> [REDACTED:card]
35
+ # 11 digit national-id-like numbers -> [REDACTED:national-id]
36
+ # 9+ digit phone-like runs -> [REDACTED:phone]
37
+ # 6-char PNR-like tokens (A-Z0-9 mix)-> [REDACTED:pnr]
38
+ #
39
+ # Output (stdout, single JSON object):
40
+ # {
41
+ # "parsedAt": "<ISO8601>",
42
+ # "source": "<path>"|"stdin",
43
+ # "format": "csv|xlsx|txt|json|text",
44
+ # "complaints": [
45
+ # { "id": "C-01", "text": "...", "trxId": "..."|null,
46
+ # "convId": "..."|null, "date": "..."|null,
47
+ # "platformHint": "..."|null }
48
+ # ],
49
+ # "redacted": <bool>, "degraded": <bool>, "degradeReason": "..."|null
50
+ # }
51
+ #
52
+ # Exit codes:
53
+ # 0 success
54
+ # 4 bad usage (no source, unknown flag, unreadable file)
55
+ # 5 format degrade (xlsx requested but openpyxl is not installed; the
56
+ # degraded JSON on stdout tells the user to export as CSV and re-run)
57
+
58
+ set -euo pipefail
59
+
60
+ FILE=""
61
+ USE_STDIN="false"
62
+ FORMAT="auto"
63
+ REDACT="true"
64
+
65
+ while [ $# -gt 0 ]; do
66
+ case "$1" in
67
+ --file) FILE="$2"; shift 2 ;;
68
+ --stdin) USE_STDIN="true"; shift ;;
69
+ --format) FORMAT="$2"; shift 2 ;;
70
+ --no-redact) REDACT="false"; shift ;;
71
+ -h|--help)
72
+ echo "usage: $0 --file <path> [--format csv|xlsx|txt|json|auto] [--no-redact] | --stdin [--format text]" >&2
73
+ exit 4 ;;
74
+ *)
75
+ echo "ERR: unexpected arg $1" >&2; exit 4 ;;
76
+ esac
77
+ done
78
+
79
+ if [ -z "$FILE" ] && [ "$USE_STDIN" != "true" ]; then
80
+ echo "ERR: no source (pass --file <path> or --stdin)" >&2
81
+ exit 4
82
+ fi
83
+ if [ -n "$FILE" ] && [ "$USE_STDIN" = "true" ]; then
84
+ echo "ERR: pass either --file or --stdin, not both" >&2
85
+ exit 4
86
+ fi
87
+ if [ -n "$FILE" ] && [ ! -r "$FILE" ]; then
88
+ echo "ERR: file not readable: $FILE" >&2
89
+ exit 4
90
+ fi
91
+
92
+ if [ "$FORMAT" = "auto" ]; then
93
+ if [ "$USE_STDIN" = "true" ]; then
94
+ FORMAT="text"
95
+ else
96
+ case "$FILE" in
97
+ *.csv) FORMAT="csv" ;;
98
+ *.xlsx) FORMAT="xlsx" ;;
99
+ *.json) FORMAT="json" ;;
100
+ *.txt|*.md) FORMAT="txt" ;;
101
+ *)
102
+ echo "ERR: cannot infer format from extension; pass --format" >&2
103
+ exit 4 ;;
104
+ esac
105
+ fi
106
+ fi
107
+
108
+ case "$FORMAT" in
109
+ csv|xlsx|txt|json|text) : ;;
110
+ *) echo "ERR: unknown format $FORMAT" >&2; exit 4 ;;
111
+ esac
112
+
113
+ STDIN_PAYLOAD=""
114
+ if [ "$USE_STDIN" = "true" ]; then
115
+ STDIN_PAYLOAD=$(cat)
116
+ fi
117
+
118
+ PC_FILE="$FILE" PC_FORMAT="$FORMAT" PC_REDACT="$REDACT" PC_STDIN="$STDIN_PAYLOAD" \
119
+ python3 - <<'PY'
120
+ import csv, datetime, io, json, os, re, sys
121
+
122
+ fmt = os.environ["PC_FORMAT"]
123
+ path = os.environ.get("PC_FILE") or ""
124
+ redact_on = os.environ.get("PC_REDACT") == "true"
125
+
126
+ # Same label set as context-link-extractor.sh GRAYLOG_RE.
127
+ ID_LABEL = re.compile(
128
+ r"(?P<label>x-conversationId|conversationId|convId|trxId|trx|transaction[ _-]?id)"
129
+ r"\s*[:=#]?\s*(?P<value>[A-Za-z0-9][A-Za-z0-9._-]{5,63})",
130
+ re.IGNORECASE,
131
+ )
132
+
133
+ def extract_ids(text):
134
+ trx, conv = None, None
135
+ for m in ID_LABEL.finditer(text or ""):
136
+ label = m.group("label").lower()
137
+ value = m.group("value")
138
+ if "conv" in label:
139
+ conv = conv or value
140
+ else:
141
+ trx = trx or value
142
+ return trx, conv
143
+
144
+ # Order matters: card (13-19 digits, separators allowed) before national-id
145
+ # (exactly 11) before phone (9+); a 16-digit card must not survive as a
146
+ # "phone". PNR-like runs last so redacted placeholders are not re-matched.
147
+ REDACTIONS = [
148
+ (re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}"), "[REDACTED:email]"),
149
+ (re.compile(r"\b\d(?:[ -]?\d){12,18}\b"), "[REDACTED:card]"),
150
+ (re.compile(r"\b\d{11}\b"), "[REDACTED:national-id]"),
151
+ (re.compile(r"(?<![\w.-])\+?\d(?:[ ()-]?\d){8,14}\b"), "[REDACTED:phone]"),
152
+ (re.compile(r"\b(?=[A-Z0-9]{6}\b)(?=[A-Z0-9]*[A-Z])(?=[A-Z0-9]*\d)[A-Z0-9]{6}\b"), "[REDACTED:pnr]"),
153
+ ]
154
+
155
+ def redact(text):
156
+ if not redact_on:
157
+ return text
158
+ for rx, repl in REDACTIONS:
159
+ text = rx.sub(repl, text)
160
+ return text
161
+
162
+ TEXT_KEYS = ("comment", "complaint", "text", "description", "message", "yorum", "sikayet")
163
+ TRX_KEYS = re.compile(r"^(trxid|trx|transaction[ _-]?id)$", re.IGNORECASE)
164
+ CONV_KEYS = re.compile(r"^(convid|conversationid|x-conversationid)$", re.IGNORECASE)
165
+ DATE_KEYS = re.compile(r"^(date|created|createdat|timestamp)$", re.IGNORECASE)
166
+ PLATFORM_KEYS = re.compile(r"^(platform|channel|os|device)$", re.IGNORECASE)
167
+
168
+ def row_to_complaint(row):
169
+ text_val, trx, conv, date, platform = None, None, None, None, None
170
+ longest = ""
171
+ for key, raw in row.items():
172
+ if key is None or raw is None:
173
+ continue
174
+ val = str(raw).strip()
175
+ if not val:
176
+ continue
177
+ k = str(key).strip()
178
+ if TRX_KEYS.match(k):
179
+ trx = val
180
+ elif CONV_KEYS.match(k):
181
+ conv = val
182
+ elif DATE_KEYS.match(k):
183
+ date = val
184
+ elif PLATFORM_KEYS.match(k):
185
+ platform = val
186
+ elif k.lower() in TEXT_KEYS:
187
+ text_val = val
188
+ elif len(val) > len(longest):
189
+ longest = val
190
+ text = text_val or longest
191
+ if not text:
192
+ return None
193
+ found_trx, found_conv = extract_ids(text)
194
+ return {
195
+ "text": text,
196
+ "trxId": trx or found_trx,
197
+ "convId": conv or found_conv,
198
+ "date": date,
199
+ "platformHint": platform,
200
+ }
201
+
202
+ def blocks_from_text(payload):
203
+ payload = payload.strip()
204
+ if not payload:
205
+ return []
206
+ blocks = [b.strip() for b in re.split(r"\n\s*\n", payload) if b.strip()]
207
+ if len(blocks) == 1 and re.search(r"^\s*(?:\d+[.)]|-)\s+", payload, re.MULTILINE):
208
+ blocks = [
209
+ b.strip()
210
+ for b in re.split(r"^\s*(?:\d+[.)]|-)\s+", payload, flags=re.MULTILINE)
211
+ if b.strip()
212
+ ]
213
+ return blocks
214
+
215
+ degraded = False
216
+ degrade_reason = None
217
+ raw_complaints = []
218
+
219
+ if fmt == "text":
220
+ for block in blocks_from_text(os.environ.get("PC_STDIN") or ""):
221
+ trx, conv = extract_ids(block)
222
+ raw_complaints.append(
223
+ {"text": block, "trxId": trx, "convId": conv, "date": None, "platformHint": None}
224
+ )
225
+ elif fmt == "txt":
226
+ with open(path, encoding="utf-8-sig", errors="replace") as f:
227
+ payload = f.read()
228
+ for block in blocks_from_text(payload):
229
+ trx, conv = extract_ids(block)
230
+ raw_complaints.append(
231
+ {"text": block, "trxId": trx, "convId": conv, "date": None, "platformHint": None}
232
+ )
233
+ elif fmt == "csv":
234
+ with open(path, encoding="utf-8-sig", errors="replace", newline="") as f:
235
+ sample = f.read(4096)
236
+ f.seek(0)
237
+ try:
238
+ dialect = csv.Sniffer().sniff(sample, delimiters=",;\t")
239
+ except csv.Error:
240
+ dialect = csv.excel
241
+ for row in csv.DictReader(f, dialect=dialect):
242
+ c = row_to_complaint(row)
243
+ if c:
244
+ raw_complaints.append(c)
245
+ elif fmt == "json":
246
+ with open(path, encoding="utf-8-sig") as f:
247
+ data = json.load(f)
248
+ if isinstance(data, dict):
249
+ data = data.get("complaints") or data.get("items") or []
250
+ if not isinstance(data, list):
251
+ data = []
252
+ for row in data:
253
+ if isinstance(row, str):
254
+ trx, conv = extract_ids(row)
255
+ raw_complaints.append(
256
+ {"text": row.strip(), "trxId": trx, "convId": conv, "date": None, "platformHint": None}
257
+ )
258
+ elif isinstance(row, dict):
259
+ c = row_to_complaint(row)
260
+ if c:
261
+ raw_complaints.append(c)
262
+ elif fmt == "xlsx":
263
+ try:
264
+ import openpyxl
265
+ except ImportError:
266
+ degraded = True
267
+ degrade_reason = "openpyxl not installed; export the sheet as CSV and re-run with --format csv"
268
+ if not degraded:
269
+ wb = openpyxl.load_workbook(path, read_only=True, data_only=True)
270
+ ws = wb.active
271
+ rows = ws.iter_rows(values_only=True)
272
+ header = next(rows, None)
273
+ if header:
274
+ keys = [str(h).strip() if h is not None else "" for h in header]
275
+ for values in rows:
276
+ row = {k: v for k, v in zip(keys, values) if k}
277
+ c = row_to_complaint(row)
278
+ if c:
279
+ raw_complaints.append(c)
280
+ wb.close()
281
+
282
+ complaints = []
283
+ for i, c in enumerate(raw_complaints, start=1):
284
+ complaints.append(
285
+ {
286
+ "id": "C-%02d" % i,
287
+ "text": redact(c["text"]),
288
+ "trxId": c["trxId"],
289
+ "convId": c["convId"],
290
+ "date": c["date"],
291
+ "platformHint": (c["platformHint"] or None) and str(c["platformHint"]).strip().lower(),
292
+ }
293
+ )
294
+
295
+ result = {
296
+ "parsedAt": datetime.datetime.utcnow().isoformat() + "Z",
297
+ "source": path or "stdin",
298
+ "format": fmt,
299
+ "complaints": complaints,
300
+ "redacted": redact_on,
301
+ "degraded": degraded,
302
+ "degradeReason": degrade_reason,
303
+ }
304
+ print(json.dumps(result, ensure_ascii=False))
305
+ sys.exit(5 if degraded else 0)
306
+ PY
@@ -45,8 +45,11 @@ usage() {
45
45
  resolve_state() {
46
46
  local task_id="$1"
47
47
  local found=""
48
- for dir in "$HOME"/.claude/projects/*; do
49
- local candidate="$dir/state/$task_id/agent-state.json"
48
+ # multi-repo-pipeline.sh writes agent-state.json under a per-project
49
+ # directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/) - not the
50
+ # ~/.claude/projects/*/state/ path this used to glob, which no writer in
51
+ # the pipeline ever populates.
52
+ for candidate in "$HOME"/.claude/logs/multi-agent/*/"$task_id"/agent-state.json; do
50
53
  if [ -f "$candidate" ]; then
51
54
  found="$candidate"
52
55
  break
@@ -45,10 +45,12 @@ if [ -z "$TASK_ID" ]; then
45
45
  exit 64
46
46
  fi
47
47
 
48
- # Resolve agent-state.json (matches phase-tracker convention).
48
+ # Resolve agent-state.json. multi-repo-pipeline.sh writes it under a
49
+ # per-project directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/)
50
+ # - not the ~/.claude/projects/*/state/ path this used to glob, which no
51
+ # writer in the pipeline ever populates.
49
52
  AGENT_STATE=""
50
- for dir in "$HOME"/.claude/projects/*; do
51
- candidate="$dir/state/$TASK_ID/agent-state.json"
53
+ for candidate in "$HOME"/.claude/logs/multi-agent/*/"$TASK_ID"/agent-state.json; do
52
54
  if [ -f "$candidate" ]; then
53
55
  AGENT_STATE="$candidate"
54
56
  break
@@ -302,11 +304,11 @@ post_bitbucket_server() {
302
304
 
303
305
  # Resolve credential keys from prefs.keychainMapping - never hardcode.
304
306
  prefs="$HOME/.claude/multi-agent-preferences.json"
305
- user_key=$(jq -r '.keychainMapping.bitbucket_user // empty' "$prefs" 2>/dev/null || true)
306
- token_key=$(jq -r '.keychainMapping.bitbucket_token // empty' "$prefs" 2>/dev/null || true)
307
+ user_key=$(jq -r '.global.keychainMapping.bitbucket_user // empty' "$prefs" 2>/dev/null || true)
308
+ token_key=$(jq -r '.global.keychainMapping.bitbucket_token // empty' "$prefs" 2>/dev/null || true)
307
309
 
308
310
  if [ -z "$user_key" ] || [ -z "$token_key" ]; then
309
- echo "post-pr-review: keychainMapping.bitbucket_user / .bitbucket_token unset in $prefs" >&2
311
+ echo "post-pr-review: global.keychainMapping.bitbucket_user / .bitbucket_token unset in $prefs" >&2
310
312
  return 4
311
313
  fi
312
314
 
@@ -222,19 +222,60 @@ do_prune() {
222
222
  rm -rf "$sd"
223
223
  printf 'shadow-git: pruned shadow repo for %s\n' "$task_id" >&2
224
224
  else
225
- # Trim history (truncate to last N snapshots within the window). For now,
226
- # implement as "rewind to oldest commit within window".
227
225
  [ ! -d "$sd/.git" ] && { err "no shadow for $task_id"; exit 1; }
226
+ # Retention never touches a real work-tree: this used to run
227
+ # `reset --hard` with --work-tree=$(pwd), silently rewriting whatever
228
+ # directory prune happened to be invoked from, and it kept the OLDEST
229
+ # commit inside the window while discarding every newer one - backwards
230
+ # for a "keep the last N days" policy. Every git call below is
231
+ # git-dir-only plumbing (log/rev-parse/commit-tree/update-ref/gc - no
232
+ # checkout, no reset, no clean); the one --work-tree `sg` still wants
233
+ # points at $sd itself, which prune already owns.
234
+ local branch
235
+ branch=$(sg "$sd" "$sd" symbolic-ref --short HEAD)
228
236
  local cutoff_epoch
229
237
  cutoff_epoch=$(($(date +%s) - older * 86400))
230
- local keep_sha
231
- keep_sha=$(sg "$sd" "$(pwd)" log --pretty='%H %ct' | awk -v c="$cutoff_epoch" '$2 >= c {print $1}' | tail -1)
232
- if [ -n "$keep_sha" ]; then
233
- printf 'shadow-git: oldest snapshot kept within window: %s\n' "$keep_sha" >&2
234
- # Hard-reset history to that snapshot. (Conservative: GC unreachable.)
235
- sg "$sd" "$(pwd)" reset --hard --quiet "$keep_sha" || true
236
- sg "$sd" "$(pwd)" gc --prune=now --quiet 2>/dev/null || true
238
+ # Commits strictly within the window, oldest first: what survives.
239
+ local keep_shas
240
+ keep_shas=$(sg "$sd" "$sd" log --reverse --pretty='%H %ct' "$branch" \
241
+ | awk -v c="$cutoff_epoch" '$2 >= c {print $1}')
242
+ if [ -z "$keep_shas" ]; then
243
+ printf 'shadow-git: no snapshot for %s is within %s day(s); nothing pruned\n' "$task_id" "$older" >&2
244
+ return 0
237
245
  fi
246
+ # Replay the surviving commits onto a fresh chain (oldest becomes the new
247
+ # root) so everything older than the cutoff becomes unreachable and
248
+ # gc-able, while every kept snapshot's tree/message/author/committer date
249
+ # is preserved exactly (committer date matters: it's what the NEXT prune
250
+ # run's cutoff math reads, so it must stay the original snapshot time,
251
+ # not "now").
252
+ local prev="" sha tree msg adate cdate aname aemail cname cemail
253
+ while IFS= read -r sha; do
254
+ [ -z "$sha" ] && continue
255
+ tree=$(sg "$sd" "$sd" rev-parse "$sha^{tree}")
256
+ msg=$(sg "$sd" "$sd" log -1 --format=%B "$sha")
257
+ adate=$(sg "$sd" "$sd" log -1 --format=%ad --date=raw "$sha")
258
+ cdate=$(sg "$sd" "$sd" log -1 --format=%cd --date=raw "$sha")
259
+ aname=$(sg "$sd" "$sd" log -1 --format=%an "$sha")
260
+ aemail=$(sg "$sd" "$sd" log -1 --format=%ae "$sha")
261
+ cname=$(sg "$sd" "$sd" log -1 --format=%cn "$sha")
262
+ cemail=$(sg "$sd" "$sd" log -1 --format=%ce "$sha")
263
+ if [ -n "$prev" ]; then
264
+ prev=$(printf '%s' "$msg" \
265
+ | GIT_AUTHOR_NAME="$aname" GIT_AUTHOR_EMAIL="$aemail" GIT_AUTHOR_DATE="$adate" \
266
+ GIT_COMMITTER_NAME="$cname" GIT_COMMITTER_EMAIL="$cemail" GIT_COMMITTER_DATE="$cdate" \
267
+ git --git-dir="$sd/.git" commit-tree "$tree" -p "$prev")
268
+ else
269
+ prev=$(printf '%s' "$msg" \
270
+ | GIT_AUTHOR_NAME="$aname" GIT_AUTHOR_EMAIL="$aemail" GIT_AUTHOR_DATE="$adate" \
271
+ GIT_COMMITTER_NAME="$cname" GIT_COMMITTER_EMAIL="$cemail" GIT_COMMITTER_DATE="$cdate" \
272
+ git --git-dir="$sd/.git" commit-tree "$tree")
273
+ fi
274
+ done <<<"$keep_shas"
275
+ git --git-dir="$sd/.git" update-ref "refs/heads/$branch" "$prev"
276
+ sg "$sd" "$sd" gc --prune=now --quiet 2>/dev/null || true
277
+ printf 'shadow-git: pruned snapshots older than %s day(s) for %s (kept %s)\n' \
278
+ "$older" "$task_id" "$(printf '%s\n' "$keep_shas" | wc -l | tr -d ' ')" >&2
238
279
  fi
239
280
  }
240
281
 
@@ -168,10 +168,17 @@ case "$MODE" in
168
168
  echo "[]"
169
169
  exit 0
170
170
  fi
171
+ # emit_json already prints a valid "[]" on empty/unparseable stdin (a
172
+ # failed `gh api` call, under pipefail, still runs it through the rest of
173
+ # this pipe with empty input) - the `|| echo "[]"` fallback used to ALSO
174
+ # fire on the pipeline's own (gh api's) non-zero exit, printing a SECOND
175
+ # "[]" line and leaving invalid double-JSON on stdout. `|| true` keeps
176
+ # the local-mode contract (unreachable submodule data -> empty list,
177
+ # exit 0, never a hard error) without re-printing anything.
171
178
  gh api "repos/$TARGET/contents/.gitmodules" \
172
179
  --jq '.content' 2>/dev/null \
173
180
  | tr -d '\n' \
174
181
  | base64 --decode 2>/dev/null \
175
- | emit_json || echo "[]"
182
+ | emit_json || true
176
183
  ;;
177
184
  esac
@@ -6,7 +6,7 @@ description: "Internal - input type detection for multi-agent dispatcher."
6
6
 
7
7
  The top-level `multi-agent` command classifies user arguments per the rules below.
8
8
 
9
- > **Language**: Schema reference only - no user-facing text. Picker `label` + `header` always English (`promptLanguage` is locked to `"en"`); `question` + `description` follow `outputLanguage` per the `rules.md` matrix. Commit/PR/Jira payloads stay English.
9
+ > **Language**: Schema reference only - no user-facing text. Picker `label` + `header` always English (`promptLanguage` is locked to `"en"`); `question` + `description` follow `outputLanguage` per the `rules.md` matrix. Commit messages, branch names and PR titles stay English; PR/Jira/Confluence **bodies** follow `outputLanguage`.
10
10
 
11
11
  ## Type Table
12
12
 
@@ -116,11 +116,14 @@ Auto-suggested as `"{jiraId} - {taskTitle}"` - user can edit at the prompt.
116
116
  | Inline `code` | `<code>code</code>` |
117
117
  | Links `[t](u)` | `<a href="u">t</a>` |
118
118
  | Lists | `<ul><li>...</li></ul>` / `<ol><li>...</li></ol>` |
119
+ | ` ```mermaid ` fences | `ac:name="mermaid"` macro (`md2confluence-v3.py` implements it, with a numbered-list fallback when the space lacks the plugin) - not the generic code macro |
119
120
 
120
121
  Body is run through humanizer before XML conversion - Confluence tone is the most formal of the four channels (target audience: stakeholders, PMs, designers).
121
122
 
122
123
  ## POST contract
123
124
 
125
+ The paths below are relative to the API base, which depends on the Confluence flavor - resolve it the way `lib/fetch-confluence.sh` already does: hosts matching `*atlassian.net` (Cloud) use `https://<host>/wiki/rest/api`, everything else (Server / Data Center) uses `https://<host>/rest/api`. On Cloud the un-prefixed form 404s on every call. (Cloud also serves a v2 API at `/wiki/api/v2` with ADF bodies; this adapter stays on v1 + storage format, which both flavors accept.)
126
+
124
127
  ```
125
128
  POST /rest/api/content
126
129
  {
@@ -43,7 +43,7 @@ The template (this doc) is English because `promptLanguage="en"` is locked. At w
43
43
  <one line per repo touched; never collapse multiple repos into one>
44
44
 
45
45
  ### Acceptance Criteria
46
- - [<x|·>] <AC text - `x` if delivered, `·` if intentionally deferred (with `(deferred: <reason>)` suffix)>
46
+ - [<x| >] <AC text - `x` if delivered, space if intentionally deferred (with `(deferred: <reason>)` suffix)>
47
47
  <one bullet per AC item from the issue body, or per implicit AC if the issue had none>
48
48
 
49
49
  ### Build & Test
@@ -65,7 +65,7 @@ Ref: #<issueNumber>
65
65
  - **`title`** - always wraps the component or task name in backticks. No emoji.
66
66
  - **`summary`** - 1-3 paragraphs, **why** before **what**. No bullet lists in the summary block - those belong in `acceptance_criteria`.
67
67
  - **`prs_branches`** - every repo the run touched gets exactly ONE line. If the PR was deferred, write the branch + commit SHA explicitly so the reader can `gh pr create` from there. Never collapse multiple repos into one line.
68
- - **`acceptance_criteria`** - copy from the issue body verbatim when an `### Acceptance Criteria` section exists. If the section is `_No response_`, the agent enumerates the implicit ACs it satisfied during Phase 1/3. `[x]` only when delivered and verified; `[·]` for deferred (with reason in parens).
68
+ - **`acceptance_criteria`** - copy from the issue body verbatim when an `### Acceptance Criteria` section exists. If the section is `_No response_`, the agent enumerates the implicit ACs it satisfied during Phase 1/3. `[x]` only when delivered and verified; `[ ]` for deferred, with the reason in parens - GitHub's task-list syntax accepts only `[ ]` and `[x]`, any other marker (a `·` was used here once) renders as literal bracket text instead of a checkbox, and the `(deferred: <reason>)` suffix is what distinguishes deferred from merely unchecked.
69
69
  - **`build_and_test`** - required. If a build failure was pre-existing, the line includes the baseline SHA where the failure was reproduced (per `feedback_build_baseline_proof.md`).
70
70
  - **`figma_extras`** - emitted only when `state.taskType == "component"` and the figma sub-pipeline ran.
71
71
  - **`next_steps`** - empty list is fine; the section header itself stays. Empty signals "fully complete, no follow-ups".
@@ -64,14 +64,25 @@ Body markdown is rewritten to Jira wiki markup before POST:
64
64
 
65
65
  | Markdown | Jira wiki |
66
66
  |---|---|
67
- | `### Heading` | `*Heading*` |
67
+ | `## Heading` | `h2. Heading` |
68
+ | `### Heading` | `h3. Heading` |
69
+ | `**bold**` | `*bold*` |
70
+ | `1. item` / `2. item` | `# item` (Jira does not render `1.` as an ordered list) |
71
+ | `- item` | `* item` |
68
72
  | `- [ ] task` / `- [x] task` | `# task` (numbered list - Jira renders TODO checkboxes inconsistently) |
69
73
  | `` `code` `` | `{{code}}` |
70
- | Tables (` | h | h | / | --- | --- |`) | `||h||h||` then `|c|c|` rows |
74
+ | `[text](url)` | `[text\|url]` |
75
+ | Tables (`\| h \| h \|` / `\| --- \| --- \|`) | `\|\|h\|\|h\|\|` then `\|c\|c\|` rows |
71
76
  | Code fences ` ```lang ` | `{code:lang} ... {code}` |
72
77
 
78
+ The `\|` sequences in the two link/table rows are this table's own escape for a literal pipe - the emitted Jira markup carries `|`, never `\|`.
79
+
73
80
  Lines outside these patterns pass through verbatim. Multi-paragraph blocks are joined with one blank line.
74
81
 
82
+ Every row above is load-bearing, including the ones that look cosmetic. The table must cover each construct the section templates in this file actually emit - `##` for the three required headings, `**Given**` / `**When**` / `**Then**` in the test-scenario skeleton, and `1.`-numbered scenarios. A missing row does not degrade gracefully: the "pass through verbatim" fallback POSTs `## Test Senaryoları` and `**Given**` as literal text, so the comment renders with visible `##` and stray asterisks. Single `*bold*` in Markdown means *italic* in Jira wiki - never map `**bold**` to `*bold*` by dropping one asterisk mechanically without checking the source was bold, not italic.
83
+
84
+ There is no markdown→Jira-wiki converter program in the pipeline (`lib/` ships `md2confluence-v3.py` for Confluence only). This table is applied by the model, by hand, which is exactly why it has to be complete.
85
+
75
86
  ## Cross-link injection
76
87
 
77
88
  The adapter prepends the linked PR URL on the **first line** so reviewers can jump to the PR with one click:
@@ -78,7 +78,7 @@ If a finding's `line` falls outside the diff (review of unchanged code), the anc
78
78
  | Provider | Approve | Needs Work / Request Changes |
79
79
  |---|---|---|
80
80
  | GitHub | `gh pr review <N> --approve` | `gh pr review <N> --request-changes --body "<empty>"` |
81
- | Bitbucket Server | `PUT /pull-requests/{id}/participants/{userSlug}` body `{"status":"APPROVED"}` | `PUT /pull-requests/{id}/participants/{userSlug}` body `{"status":"NEEDS_WORK"}` |
81
+ | Bitbucket Server | `PUT /rest/api/1.0/projects/{key}/repos/{slug}/pull-requests/{id}/participants/{userSlug}` body `{"status":"APPROVED"}` | same path, body `{"status":"NEEDS_WORK"}` |
82
82
 
83
83
  The Bitbucket needs-work pattern keeps the user as REVIEWER role; status flip is the visible "Request changes" signal in the UI.
84
84
 
@@ -75,6 +75,22 @@ Never use `Closes #N`, `Fixes #N`, `Resolves PROJ-X`. Issues require 4-approval
75
75
  5. Dispatch per the Behaviour-by-remote table.
76
76
  ```
77
77
 
78
+ ## Markup dialect (per surface, not per pipeline)
79
+
80
+ The PR body is **Markdown** on every supported remote - GitHub, Bitbucket Server, and GitLab all render Markdown in the description field. Emit the assembled markdown verbatim; there is no conversion step on this adapter.
81
+
82
+ Jira wiki markup in a PR body is a defect, not a style choice. The Jira adapter's conversion table (`channels/jira.md` "Wiki markup conversion") applies to the **Jira comment only** and must never be reached from here. Concretely, in a PR body:
83
+
84
+ | Never in a PR body | Renders as | Use instead |
85
+ |---|---|---|
86
+ | `h2. Title` / `h3. Title` | literal text `h2. Title` | `## Title` / `### Title` |
87
+ | `{{identifier}}` | literal braces `{{identifier}}` | `` `identifier` `` |
88
+ | `# item` for a numbered list | an H1 heading, one giant line per item | `1. item` |
89
+ | `{code:swift} ... {code}` | literal braces | fenced ` ```swift ` block |
90
+ | `*bold*` | italic in Markdown, not bold | `**bold**` |
91
+
92
+ The two adapters run in the same phase over the same source markdown, so the failure mode is a converter applied to the wrong target: the Jira comment is correct and the PR body ships raw wiki markup. Assemble once in markdown, then convert **only** on the Jira branch.
93
+
78
94
  ## Behaviour by remote
79
95
 
80
96
  | Remote | API | Reviewer handling |
@@ -106,6 +122,10 @@ curl -s -X PUT -u "$BB_USER:$BB_TOKEN" -H "Content-Type: application/json" \
106
122
  --data-binary @/tmp/channels-$TASK_ID-pr-payload.json "$PR_URL"
107
123
  ```
108
124
 
125
+ Verify after every PUT: refetch the PR and compare `reviewers | length` against the pre-PUT count. A drop means the payload lost the field - repair immediately.
126
+
127
+ `POST /pull-requests/{id}/participants` is **repair-only**, never the primary path. It accepts one user per call, so restoring N reviewers writes N separate "added 1 reviewer" rows into the PR activity feed, and Bitbucket activity entries cannot be deleted. A PR that opened with 22 reviewers in one clean create call and then shows 22 individual re-adds is a visible, permanent record of a dropped-reviewer PUT. Carry `reviewers` through the payload above instead.
128
+
109
129
  ## Multi-repo cross-links
110
130
 
111
131
  When `state.projects[].length > 1`, `channels-multi-repo.sh render-pr <state> <body> <repoName>` prepends a `## Related PRs` block:
@@ -34,9 +34,9 @@ The selected scope routes to a platform skill:
34
34
 
35
35
  | Platform | Skill | Notes |
36
36
  |---|---|---|
37
- | iOS / SwiftUI | `ai-ios-engineering-toolkit:figma-component-wiki` | Reads `Configuration` + `+Modifiers` from the source, captures Figma screenshots via MCP / REST, runs Snapshot tests for iOS dark/light/RTL. |
38
- | Android / Compose | `ai-android-engineering-toolkit:figma-component-wiki` | Reads Compose composables + previews, runs Paparazzi screenshots if available, otherwise generates from layout inspector. |
39
- | Generic | `ai-ios-engineering-toolkit:figma-component-wiki` (fallback) | Used when `state.platform` is unknown (fallback path - typically a flag). |
37
+ | iOS / SwiftUI | `ai-ios-toolkit:figma-component-wiki` | Reads `Configuration` + `+Modifiers` from the source, captures Figma screenshots via MCP / REST, runs Snapshot tests for iOS dark/light/RTL. |
38
+ | Android / Compose | `ai-android-toolkit:figma-component-wiki` | Reads Compose composables + previews, runs Paparazzi screenshots if available, otherwise generates from layout inspector. |
39
+ | Generic | `ai-ios-toolkit:figma-component-wiki` (fallback) | Used when `state.platform` is unknown (fallback path - typically a flag). |
40
40
 
41
41
  Full per-skill contract: `$HOME/.claude/multi-agent-refs/wiki-capture.md`.
42
42
 
@@ -62,6 +62,6 @@ Adapter implementation chooses the right git push target + commit message format
62
62
  - Wiki adapter is the **only** channel allowed to skip silently when its precondition gate fails. PR / Jira / Confluence always run when selected.
63
63
  - Per the `figma-pipeline.md` rules, every component wiki page must include a variant matrix, accessibility identifiers, analytics events, and 3-layer test summary.
64
64
  - Screenshots cover light/dark + LTR/RTL - wiki adapter refuses to commit if any quadrant is missing.
65
- - Body content language follows `prefs.global.outputLanguage`. Code identifiers, file paths, type names, design token names, and wiki markup stay verbatim across languages. The template (this doc) is English because `promptLanguage="en"` is locked.
65
+ - Body content language follows `prefs.global.outputLanguage`. Code identifiers, file paths, type names, design token names, and Markdown formatting stay verbatim across languages (wiki pages are `.md` files - "wiki markup" in this doc set means Jira's dialect, which never appears here). The template (this doc) is English because `promptLanguage="en"` is locked.
66
66
  - No decorative/emotive emoji or smileys (😊 🙂 🎉 👍 🚀 ✨) in the page prose. Only functional/structural marks a fixed template defines are allowed.
67
67
  - Autopilot always pauses at the channels menu (per `phase-7-report.md` autopilot contract) - even in autopilot mode the user gets to confirm wiki scope.