@mmerterden/multi-agent-pipeline 16.10.0 → 16.11.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 (67) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +110 -1
  3. package/README.tr.md +111 -1
  4. package/install/templates/copilot-instructions.md +1 -1
  5. package/package.json +1 -1
  6. package/pipeline/commands/multi-agent/SKILL.md +3 -3
  7. package/pipeline/commands/multi-agent/analysis/SKILL.md +9 -9
  8. package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +2 -2
  9. package/pipeline/commands/multi-agent/autopilot/SKILL.md +1 -1
  10. package/pipeline/commands/multi-agent/build-optimize/SKILL.md +2 -2
  11. package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
  12. package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +1 -1
  13. package/pipeline/commands/multi-agent/create-jira/SKILL.md +1 -1
  14. package/pipeline/commands/multi-agent/design-check/SKILL.md +2 -2
  15. package/pipeline/commands/multi-agent/help/SKILL.md +4 -4
  16. package/pipeline/commands/multi-agent/language/SKILL.md +3 -3
  17. package/pipeline/commands/multi-agent/local/SKILL.md +1 -1
  18. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -1
  19. package/pipeline/commands/multi-agent/purge/SKILL.md +2 -2
  20. package/pipeline/commands/multi-agent/resume-local/SKILL.md +2 -2
  21. package/pipeline/commands/multi-agent/review/SKILL.md +7 -6
  22. package/pipeline/commands/multi-agent/setup/SKILL.md +9 -9
  23. package/pipeline/commands/multi-agent/stack/SKILL.md +12 -13
  24. package/pipeline/commands/multi-agent/update/SKILL.md +1 -1
  25. package/pipeline/lib/extract-conventions.sh +19 -18
  26. package/pipeline/multi-agent-refs/_account-picker.md +1 -1
  27. package/pipeline/multi-agent-refs/_dev-context.md +2 -2
  28. package/pipeline/multi-agent-refs/_input-parser.md +1 -1
  29. package/pipeline/multi-agent-refs/analysis/evidence.md +4 -4
  30. package/pipeline/multi-agent-refs/analysis/intake.md +112 -36
  31. package/pipeline/multi-agent-refs/analysis/locked.md +1 -1
  32. package/pipeline/multi-agent-refs/analysis/render.md +37 -14
  33. package/pipeline/multi-agent-refs/analysis/synthesis.md +7 -7
  34. package/pipeline/multi-agent-refs/analysis-template-corporate.md +1 -1
  35. package/pipeline/multi-agent-refs/analysis-template.md +6 -6
  36. package/pipeline/multi-agent-refs/channels/confluence.md +1 -0
  37. package/pipeline/multi-agent-refs/channels/pr.md +2 -2
  38. package/pipeline/multi-agent-refs/conventions-defaults.md +13 -13
  39. package/pipeline/multi-agent-refs/features/stack-skill-routing.md +1 -1
  40. package/pipeline/multi-agent-refs/phases/modes.md +1 -1
  41. package/pipeline/multi-agent-refs/phases/phase-0-init.md +10 -6
  42. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +6 -5
  43. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +4 -4
  44. package/pipeline/multi-agent-refs/phases/phase-6-commit.md +7 -7
  45. package/pipeline/multi-agent-refs/picker-contract.md +29 -4
  46. package/pipeline/multi-agent-refs/readiness-review.md +1 -1
  47. package/pipeline/multi-agent-refs/rules.md +4 -4
  48. package/pipeline/multi-agent-refs/{frontend-guide.md → web-guide.md} +2 -2
  49. package/pipeline/schemas/analysis-output.schema.json +1 -1
  50. package/pipeline/schemas/analysis-spec.schema.json +3 -3
  51. package/pipeline/schemas/prefs.schema.json +18 -2
  52. package/pipeline/scripts/gen-skills-index.mjs +1 -1
  53. package/pipeline/scripts/validate-analysis-doc.mjs +7 -3
  54. package/pipeline/scripts/validate-analysis.mjs +6 -1
  55. package/pipeline/scripts/write-state.mjs +29 -11
  56. package/pipeline/skills/.skills-index.json +24 -2
  57. package/pipeline/skills/shared/README.md +9 -7
  58. package/pipeline/skills/shared/core/multi-agent/SKILL.md +1 -1
  59. package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +3 -3
  60. package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +1 -1
  61. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +1 -1
  62. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -2
  63. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -2
  64. package/pipeline/skills/shared/core/multi-agent-purge/SKILL.md +1 -1
  65. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -1
  66. package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +11 -12
  67. package/pipeline/skills/skills-index.md +5 -3
@@ -10,7 +10,7 @@ Sequential `AskUserQuestion` chain. Each answer is written to state under `state
10
10
 
11
11
  #### Step 0 - Language resolution (BLOCKING, runs before any picker)
12
12
 
13
- Before emitting the first `AskUserQuestion`, read `prefs.global.outputLanguage` (`tr` or `en`, default `tr`). Every `<localized: "...">` marker in the Phase 0 picker chain (Steps 1-5) MUST be rendered in the resolved language - this is not deferred to Phase 3. Per the Language note above: `question` and `description` follow `outputLanguage`; `label` and `header` stay English. If `outputLanguage == tr`, the user sees Turkish question text; do not emit the English literal inside the `<localized:>` marker.
13
+ Before emitting the first `AskUserQuestion`, read `prefs.global.outputLanguage` (`tr` or `en`, default `tr`). Every `<localized: "...">` marker in the Phase 0 picker chain (Steps 1-5) MUST be rendered in the resolved language - this is not deferred to Phase 3. Per the Language note above: `question`, `label` and `description` follow `outputLanguage`; only `header` stays English (<=12-char chip). If `outputLanguage == tr`, the user sees Turkish question text; do not emit the English literal inside the `<localized:>` marker.
14
14
 
15
15
  #### Step 1 - Analysis name
16
16
 
@@ -59,14 +59,49 @@ question: <localized: "Which platforms is this analysis for?">
59
59
  options:
60
60
  - label: "iOS"
61
61
  - label: "Android"
62
+ - label: "Web" -> platform id `web`
62
63
  - label: "Backend"
63
- - label: "Frontend"
64
64
  - label: "No platform yet"
65
- description: <localized: "Analysis and technical analysis only. The development analysis is skipped until a repo is chosen.">
65
+ description: <localized: "Derive the channels from the sources. Analysis and technical analysis only - the development analysis waits for a repo.">
66
66
  ```
67
67
  Empty submit → re-ask. Result: `state.analysisSpec.platforms[]`.
68
68
 
69
- **`No platform yet` is a real answer, not a cancel** (Locked 35). It leaves `platforms[]` empty, skips Step 4 entirely, and the run continues: evidence is still fetched from every declared source, and the document renders every layer that does not need a target repository. Only the development layer and the Pass B projection drop, and Section 20 records that they await a repo selection. Output is a single `analysis/<feature>.md` rather than one file per platform, since the per-platform split exists to carry per-platform projections and there are none.
69
+ `web` is the canonical id; the pre-rename `frontend` is still read back (older state,
70
+ `:stack frontend`, the `ai-frontend-toolkit` plugin id) and normalises to `web`.
71
+
72
+ **`No platform yet` is a real answer, not a cancel** (Locked 35). It leaves
73
+ `platforms[]` empty, skips Step 4 entirely, and the run continues: evidence is still
74
+ fetched from every declared source, and the document renders every layer that does not
75
+ need a target repository. Only the development layer and the Pass B projection drop,
76
+ and Section 20 records that they await a repo selection.
77
+
78
+ **The channel split is then derived from the evidence, not abandoned** (Locked 35,
79
+ which carries the reasoning). After evidence collection, classify the run's channels
80
+ from what the sources say:
81
+
82
+ | Signal | Reads as |
83
+ |---|---|
84
+ | Figma frames at a phone viewport or named for an app flow; text naming an app store, push, a deep link, a native permission | `mobile` |
85
+ | Figma frames at desktop / tablet width or named for a browser page; text naming a browser, a URL route, SEO, a breakpoint | `web` |
86
+ | A Swagger contract consumed by a named client | that client's channel |
87
+
88
+ `mobile` is one channel here, not iOS plus Android; once a repo is selected the normal
89
+ `ios` / `android` split applies.
90
+
91
+ **Confirm the derivation, do not act on it silently.** The evidence proposes; the
92
+ user decides how many documents this run produces:
93
+
94
+ ```
95
+ header: "Channels" question: <localized: "Hangi kanallar icin dokuman uretilsin?">
96
+ options (label + description in outputLanguage; semantics, not literals):
97
+ "Mobile and web" | "Mobile only" | "Web only" | "One combined"
98
+ ```
99
+
100
+ Each description names the evidence behind that channel (`8 frame, 2 dokuman`), so the
101
+ answer is informed. The derived set is the recommended option; autopilot takes it
102
+ without asking and logs what it picked and why. One channel: ask only split-out or
103
+ channel-agnostic. None: emit `analysis/<feature>.md` and say no channel signal was
104
+ found.
70
105
 
71
106
  **Platform coverage = provided platforms.** The analysis renders exactly one per-platform file (Locked 9) for each platform selected here and given a repo in Step 4: select iOS only -> a single iOS document; select iOS + Android -> one iOS and one Android document, each projected through that repo's own conventions (Phase 1c) and its own Code Connect index (Phase 1b.1, discovered from that repo's `*.figma.swift` / `*.figma.kt`). Do not analyze a platform the user did not select, and do not drop a selected platform that has a repo.
72
107
 
@@ -83,49 +118,82 @@ When `platforms[]` is empty (Step 3 resolved to `No platform yet`), skip Steps 4
83
118
  For each selected platform, run one AskUserQuestion round. Reuse `_dev-context.md` logic:
84
119
  - Run `~/.claude/lib/submodule-detector.sh "$REPO_PATH"` to enumerate submodules + `canPush`
85
120
  - Augment with `prefs.projects[<key>].editableRelatedRepos[]` for iOS / Android / Backend
86
- - For `Frontend`, read `prefs.projects[<key>].frontendRepos[]` as the primary source (since Frontend is rarely in the iOS submodule tree); fall back to Other input
121
+ - For `Web`, read `prefs.projects[<key>].webRepos[]` (falling back to the pre-rename `frontendRepos[]`) as the primary source (since Web is rarely in the iOS submodule tree); fall back to Other input
87
122
  - If no repos are detectable for a platform, present a single Other input asking for `<owner>/<repo>`
88
123
 
89
124
  Result: `state.analysisSpec.repos[]` (each entry has `platform`, `name`, `path`, `canPush`).
90
125
 
91
- #### Step 5 - Input URLs (single 6-question batch)
126
+ #### Step 5 - Input sources (7 questions, 2 fixed batches)
127
+
128
+ `AskUserQuestion` accepts at most **4 questions per call**, so the seven source types
129
+ are asked as two fixed batches. Do not improvise the split: seven at once is an invalid
130
+ tool call, and an improvised retry separates the spec sources from each other.
131
+
132
+ - **Batch 1/2 - what the feature IS**: Figma, Confluence, Document, Jira. A written
133
+ spec arrives as an attached `.docx` / `.pdf` at least as often as a Confluence page,
134
+ so both are on the same screen.
135
+ - **Batch 2/2 - what constrains it**: Swagger, Standards, Firebase.
136
+
137
+ **Development-layer questions are gated on a platform being selected.** With
138
+ `platforms[]` empty that layer does not render (Locked 35), so a question feeding only
139
+ it spends attention on an answer nothing consumes.
140
+
141
+ Two questions feed only that layer and are therefore skipped:
142
+
143
+ | Skipped question | Feeds |
144
+ |---|---|
145
+ | Standards | global 13.7 Standards binding = corporate 17 (Part C) |
146
+ | UI Tests (Step 5a) | global 15.6 = corporate 19 (Part C) |
147
+
148
+ Everything else is still asked: Firebase (global 11 = corporate 13) and A11y depth
149
+ (global 16 = corporate 15) land in Part B, and the five source questions feed Parts A
150
+ and B.
151
+
152
+ With no platform, batch 2 is a 2-question call (Swagger, Firebase) and Step 5a a
153
+ 1-question call (A11y depth). Name the skipped questions and the reason in the batch
154
+ breadcrumb, the way an auto-resolved step still prints its own line
155
+ (`picker-contract.md` Step narration).
92
156
 
93
- One AskUserQuestion call with **7 parallel questions**, one per source type. Each question shows a `Skip` option plus auto-available `Other` for free-text input. Empty / `Skip` selections yield no entry in `state.analysisSpec.contextLinks[]`.
157
+ Announce which batch is showing (`1/2`, `2/2`) in the narrator line above the picker.
158
+ Each question carries a skip option plus the host's auto-added `Other` for free-text
159
+ input. Skipped questions yield no entry in `state.analysisSpec.contextLinks[]`.
160
+
161
+ Option `label`s and `description`s render in `outputLanguage`; `header` stays English
162
+ per the `rules.md` matrix. The quoted strings below are the option SEMANTICS, not
163
+ literals to print, and the run branches on which option was chosen, never on its text.
94
164
 
95
165
  ```
166
+ BATCH 1/2 - what the feature is
167
+
96
168
  Q1: header="Figma URL"
97
169
  question: <localized: "Do you have a Figma URL for this feature?">
98
- options:
99
- - label: "No Figma input"
100
- - label: "Use repo Code Connect only"
170
+ options: "No Figma input" · "Use repo Code Connect only"
101
171
  (Other: paste URL - comma-separated for multiple frames)
102
172
 
103
- Q2: header="Swagger URL"
104
- question: <localized: "Do you have a Swagger / OpenAPI URL?">
105
- options:
106
- - label: "No Swagger input"
107
- - label: "Extract from Confluence instead"
108
- (Other: paste URL)
109
-
110
- Q3: header="Confluence"
173
+ Q2: header="Confluence"
111
174
  question: <localized: "Do you have a Confluence page URL for the feature spec?">
112
- options:
113
- - label: "No Confluence input"
175
+ options: "No Confluence input"
114
176
  (Other: paste URL - comma-separated for multiple pages)
115
177
 
178
+ Q3: header="Document"
179
+ question: <localized: "Do you have the spec as a file? A Word .docx, a .pdf, a .md
180
+ or a .txt - give a local path or a URL.">
181
+ options: "No document input"
182
+ (Other: absolute or tilde path, or a URL - comma-separated for multiple)
183
+
116
184
  Q4: header="Jira"
117
185
  question: <localized: "Do you have a related Jira ID?">
118
- options:
119
- - label: "No Jira input"
186
+ options: "No Jira input"
120
187
  (Other: type Jira ID like {JIRA_KEY}-12345 - comma-separated for multiple)
121
188
 
122
- Q4b: header="Document"
123
- question: <localized: "Do you have a spec document? A .docx, .pdf, .md or .txt file, as a local path or a URL.">
124
- options:
125
- - label: "No document input"
126
- (Other: absolute or tilde path, or a URL - comma-separated for multiple)
189
+ BATCH 2/2 - what constrains it
190
+
191
+ Q5: header="Swagger URL"
192
+ question: <localized: "Do you have a Swagger / OpenAPI URL?">
193
+ options: "No Swagger input" · "Extract from Confluence instead"
194
+ (Other: paste URL)
127
195
 
128
- Q5: header="Standards"
196
+ Q6: header="Standards"
129
197
  question: <localized: "Do you have coding documentation or standards to bind the development plan? Confluence URL, GitHub wiki URL, or local file path.">
130
198
  options:
131
199
  - label: "No standards input"
@@ -133,7 +201,7 @@ Q5: header="Standards"
133
201
  description: "Searches the auto-detect probe list below (canonical home-dir Standards file, repo CLAUDE.md / CONTRIBUTING.md, docs/architecture/*.md, and the wiki Home.md / Navigation.md if a GitHub wiki is configured)"
134
202
  (Other: comma-separated mix of Confluence URLs, GitHub wiki URLs, and absolute / tilde-expanded local file paths)
135
203
 
136
- Q6: header="Firebase"
204
+ Q7: header="Firebase"
137
205
  question: <localized: "Do you have Firebase Analytics events for this feature? Comma-separated event-name list, a JSON schema file path, or a Firebase Console URL.">
138
206
  options:
139
207
  - label: "No Firebase input"
@@ -142,13 +210,17 @@ Q6: header="Firebase"
142
210
  (Other: comma-separated mix of event names like 'profile.view,profile.opened', a /path/to/events.json, or a console.firebase.google.com URL)
143
211
  ```
144
212
 
145
- After submit, run `~/.claude/lib/context-link-extractor.sh` on each Other-provided string. Results are typed and written to `state.analysisSpec.contextLinks[]`. Q5 entries are additionally tagged `binding: true` so Phase 2 Section 7 treats them as hard constraints (see the Phase 1 type table and the Phase 2 Section 7 row).
213
+ After each batch is submitted, pipe every Other-provided string into
214
+ `~/.claude/lib/context-link-extractor.sh` on **stdin** (`printf '%s' "$answer" | ~/.claude/lib/context-link-extractor.sh`) - it takes no argv, and an
215
+ argv call silently returns `[]` rather than erroring. Results are typed and written to `state.analysisSpec.contextLinks[]`. Standards-question entries are additionally tagged `binding: true` so Phase 2 Section 7 treats them as hard constraints (see the Phase 1 type table and the Phase 2 Section 7 row).
146
216
 
147
- **Q5 + Q6 type detection rules** (also documented in `~/.claude/lib/context-link-extractor.sh`):
217
+ **Type detection rules for every Other input** (also documented in `~/.claude/lib/context-link-extractor.sh`):
148
218
 
149
219
  | Input shape | Detected type | Phase 1 strategy |
150
220
  |---|---|---|
151
221
  | Starts with `/` or `~` and ends with `.md` / `.markdown` / `.txt` | `local-file` | `Read` tool on the absolute path (tilde-expanded) |
222
+ | Starts with `/` or `~` and ends with `.docx` / `.pdf` / `.md` / `.markdown` / `.txt` | `document` (`metadata.source: path`) | `~/.claude/lib/fetch-document.sh <path>` - see the `evidence.md` fetch table |
223
+ | An `http(s)://` URL whose path ends with `.docx` / `.pdf` / `.md` / `.markdown` / `.txt` | `document` (`metadata.source: url`) | Same fetcher; the file is downloaded first |
152
224
  | Host = `github.com` AND path matches `/<owner>/<repo>/wiki/<PageName>` (no `.md`) | `wiki` | `git clone --depth 1 https://github.com/<owner>/<repo>.wiki.git /tmp/<repo>-wiki && Read /tmp/<repo>-wiki/<PageName>.md` (URL `-` to ` ` decoding) |
153
225
  | Host matches `confluence*.<tld>` AND path contains `/display/` or `/pages/viewpage.action?pageId=` | `standards-confluence` | Same fetcher as `confluence` but bucket goes to `evidence.standards[]` |
154
226
  | Comma-separated lowercase tokens with `.` or `_` (e.g. `profile.view,screen_view`) and no slash / no scheme | `firebase-events:names` | Scaffold rows from names alone |
@@ -156,23 +228,25 @@ After submit, run `~/.claude/lib/context-link-extractor.sh` on each Other-provid
156
228
  | Host `console.firebase.google.com` with `/analytics/` in path | `firebase-events:console` | Reference-only; INFO warning printed, no fetch |
157
229
  | Any other `http(s)://` URL | `generic-doc` | `WebFetch` |
158
230
 
159
- Q5 Auto-detect mode probe order (option 2):
231
+ Standards question - Auto-detect mode probe order (option 2):
160
232
  1. `~/<project>-Standards.md` - canonical home-dir reference file; the exact filename comes from `prefs.projects[<project>].standardsFile` (no hardcoded project name in this command)
161
233
  2. `<repo>/CLAUDE.md`, `<repo>/CONTRIBUTING.md`
162
234
  3. `<repo>/docs/architecture/*.md`
163
235
  4. If a `*.wiki.git` mirror is reachable for the primary repo, clone and ingest `Home.md` + any page named `Navigation*.md`
164
236
 
165
- Q6 Auto-detect mode probe order (option 2):
237
+ Firebase question - Auto-detect mode probe order (option 2):
166
238
  1. Per-repo grep for `Analytics\.logEvent\(`, `firebaseAnalytics\.logEvent\(`, `logEvent\(analytics,` and harvest the first string literal in each call as the event name
167
239
  2. Generated `AnalyticsEvents/*.swift` / `AnalyticsEvents/*.kt` if present (treat each public struct conforming to `AnalyticsEvent` as one event)
168
240
  3. Repo-level `firebase-events.json` / `analytics/events.json` files
169
241
 
170
242
  #### Step 5a - Coverage options (opt-in, 2 questions)
171
243
 
172
- One `AskUserQuestion` call with 2 parallel yes/no questions. These are opt-INs, not source intake: an empty answer is NOT consent (per `feedback_no-inferred-defaults-from-empty-answer`) - re-ask on empty rather than defaulting silently once the picker is shown.
244
+ One `AskUserQuestion` call with 2 parallel yes/no questions - or 1 when
245
+ `state.analysisSpec.platforms[]` is empty, since `uiTests` only gates a section the
246
+ development layer would have carried (see the gating table in Step 5). These are opt-INs, not source intake: an empty answer is NOT consent (per `feedback_no-inferred-defaults-from-empty-answer`) - re-ask on empty rather than defaulting silently once the picker is shown.
173
247
 
174
248
  ```
175
- Q1: header="UI Tests"
249
+ Q1: header="UI Tests" (skipped entirely when platforms[] is empty)
176
250
  question: <localized: "Should the analysis include UI test scenarios (XCUITest / Compose UI test)? Optional; unit + snapshot coverage is always included.">
177
251
  options:
178
252
  - label: "No UI tests" -> state.analysisSpec.options.uiTests = false (default)
@@ -185,7 +259,9 @@ Q2: header="A11y depth"
185
259
  - label: "Full walkthrough" -> state.analysisSpec.options.a11yDepth = "full"
186
260
  ```
187
261
 
188
- `options.uiTests` gates Section 15.6 (UI test flows); `options.a11yDepth` gates the Section 16 VoiceOver / TalkBack walkthrough. Both default to the lighter choice so the doc stays lean unless the user opts in.
262
+ `options.uiTests` gates Section 15.6 (UI test flows), which lives in the development
263
+ layer; `options.a11yDepth` gates the Section 16 VoiceOver / TalkBack walkthrough, which
264
+ does not. Both default to the lighter choice so the doc stays lean unless the user opts in.
189
265
 
190
266
  #### Step 5b - Repo-evidence collector (automatic, no prompt)
191
267
 
@@ -55,4 +55,4 @@ When citing a Locked decision in code or docs, prefer `Locked <n> (<short label>
55
55
  32. **Analysis profile selected at intake.** `state.analysisSpec.profile` is `global` (default) or `corporate`, asked once at Phase 0 Step 1b and never re-asked mid-run. `global` renders `$HOME/.claude/multi-agent-refs/analysis-template.md` (23 sections, development handoff). `corporate` renders `$HOME/.claude/multi-agent-refs/analysis-template-corporate.md` (requirements document: `IG -> UC -> FG` spine, three traceability matrices, current-to-target state with impact analysis, then Technical Analysis and Development Analysis). **Both profiles read the same `state.analysisSpec.evidence.*`** - intake, fetching, repo evidence and convention extraction are shared and profile-independent; only the projection differs. This is what keeps the two templates from drifting into two products. One run emits one profile: rendering both from a single run would produce two documents describing the same feature, and the next reader would have to decide which one is current. When only one profile is available (`prefs.global.analysisProfiles` lists one, or the corporate profile has no binding configuration), the step auto-resolves and prints its breadcrumb with the resolution noted, per the picker contract.
56
56
  33. **Corporate backbone always renders.** In the `corporate` profile the Locked 2 omission rule is replaced for Part A and the footer: those sections render even with zero evidence, carrying `N/A` when the section is genuinely out of scope for the feature and `EKLENECEK` when evidence is expected but missing. This is the point of a requirements document - a reader has to be able to tell "we considered hardware needs and there are none" from "nobody looked". Every `EKLENECEK` emits a matching Section 20 Risks and Open Questions row naming what is missing and who can answer it; an `EKLENECEK` with no such row fails the dispatch gate, because an unanswered question nobody owns is how a placeholder reaches production. **Missing inputs never block the run**: the corporate source practice of halting until every input arrives is deliberately not adopted - the document is produced with `EKLENECEK` in the gaps and the gaps are raised in Section 20. Part B follows the global omission table unchanged. In the `global` profile Locked 2 applies as written, with no placeholder of any kind.
57
57
  34. **References are built from the evidence record, not written.** Section 21 is emitted by `$HOME/.claude/scripts/build-references.mjs` from `state.analysisSpec.evidence.*` in both profiles. Each row carries a precision anchor in its `Sürüm / Ref` column - Figma node id, Confluence `pageId` plus page version, the commit SHA a repo was read at, the Swagger spec version - because a reference with no anchor points at a moving target. Each row carries an `Erişim / Access` cell: a declared source that could not be fetched still gets a row reading `erişilemedi (<reason>)`, since a silently dropped source reads to the next person as a source that never existed. User statements from the conversation that no fetched source contains are recorded as `Serbest metin` rows, quoted verbatim, with the decision they settled. **Coverage gate**: every entry in `evidence.figma[]`, `confluence[]`, `jira[]`, `swagger[]`, `repo[]`, `standards[]`, `firebase[]`, `documents[]`, `outside[]`, `freeText[]` and every entry in `evidence.fetchErrors[]` must appear as a row, and every row must map to an evidence entry. A source that shaped the document but is missing from References fails the dispatch gate; so does an invented row with no evidence behind it.
58
- 35. **Stack-optional render.** Platform and repo selection are optional. When `state.analysisSpec.platforms[]` is empty, the run still completes: the analysis layers that do not need a target repository render in full - Part A and Part B in the corporate profile, Sections 1-12 and 16-17 in the global profile - and only the development layer is dropped (corporate Part C; global Sections 13, 14, 15) along with the Pass B projection, since there are no conventions to project onto. A Section 20 row records that the development analysis awaits a repo selection. One document is emitted rather than one per platform, because the per-platform split (Locked 9) exists to carry per-platform projections and there are none. It lands at `~/Desktop/multiAgentAnalysis/<feature-name>/<feature>.md`: the repo-relative `analysis/` path has nothing to be relative to without a repo, and the current working directory is never used, since for a repo-less run it is arbitrary. Desktop rather than a hidden directory because the document is a deliverable somebody is meant to open and hand over, and `multiAgentAnalysis` rather than a bare `Analysis` because a generic word collides with whatever else is on a desktop while the producer name groups every run this command ever writes. The Phase 3.5 picker shows the resolved path and takes an override through its Other input. A requirements document is useful before anyone has decided which repository will hold the code, and refusing to produce one until that decision exists inverts the order the work actually happens in.
58
+ 35. **Stack-optional render.** Platform and repo selection are optional. When `state.analysisSpec.platforms[]` is empty, the run still completes: the analysis layers that do not need a target repository render in full - Part A and Part B in the corporate profile, Sections 1-12 and 16-17 in the global profile - and only the development layer is dropped (corporate Part C; global Sections 13, 14, 15) along with the Pass B projection, since there are no conventions to project onto. A Section 20 row records that the development analysis awaits a repo selection. **The channel split survives the missing repo.** Channels are derived from the evidence instead of repo stack tags (`intake.md` Step 3 carries the signal table) and one document is emitted per derived channel - `mobile`, `web`, or both. A phone screen and a browser screen carry different requirements before anyone has picked a repository; the split (Locked 9) exists to carry that difference and only its *projection* half needs conventions. `mobile` stays one channel rather than iOS plus Android, since without conventions nothing tells the two apart. Evidence with no interface at all yields a single channel-agnostic `<feature>.md`. Files land under `~/Desktop/multiAgentAnalysis/<feature-name>/`, named `<feature>-<channel>.md` (or `<feature>.md` for the channel-agnostic case): the repo-relative `analysis/` path has nothing to be relative to without a repo, and the current working directory is never used, since for a repo-less run it is arbitrary. Desktop rather than a hidden directory because the document is a deliverable somebody is meant to open and hand over, and `multiAgentAnalysis` rather than a bare `Analysis` because a generic word collides with whatever else is on a desktop while the producer name groups every run this command ever writes. The Phase 3.5 picker shows the resolved path and takes an override through its Other input. A requirements document is useful before anyone has decided which repository will hold the code, and refusing to produce one until that decision exists inverts the order the work actually happens in.
@@ -10,7 +10,7 @@
10
10
 
11
11
  2. **Markdown render**: for each platform in `state.analysisSpec.platforms[]`, concatenate the per-platform spec into one markdown file. Tables in pipe-syntax. Numbering uses plain `## 1.`, `## 2.`, ... - omitted sections do **not** create gaps. Visible numbering is sequential 1..N over the rendered set. In the corporate profile the Part A and footer numbers are fixed and do not re-flow, since the backbone never drops; Part B and Part C follow the omission table as usual.
12
12
 
13
- **When `platforms[]` is empty** (Locked 35), render one platform-agnostic file instead of one per platform: the development layer (corporate Part C, global Sections 13, 14, 15) and the Pass B projection are skipped, Section 20 carries a row recording that they await a repo selection, and the front-matter `platform` key reads `none`. Everything that does not need a target repository still renders in full.
13
+ **When `platforms[]` is empty** (Locked 35), the channels come from the evidence instead of from repo stack tags, so this loop runs once per derived channel (`mobile`, `web`, or one channel-agnostic pass). What drops is the development layer (corporate Part C, global Sections 13, 14, 15) and the Pass B projection are skipped, Section 20 carries a row recording that they await a repo selection, and the front-matter `platform` key reads `none`. Everything that does not need a target repository still renders in full.
14
14
 
15
15
  3. **Humanizer pass (MANDATORY: actually invoke the `ai-common-toolkit:humanizer` skill on the rendered markdown - the punctuation grep alone does NOT satisfy this step)** (`technical-explanatory` tone for the scratch buffer; per-channel re-humanize happens in Phase 4 when actually emitting):
16
16
  ```
@@ -31,7 +31,7 @@
31
31
 
32
32
  and paste its output under the References heading. The model does not hand-write this table; it is a projection of `state.analysisSpec.evidence.*`, which is what keeps a source the run actually read from going unlisted and a remembered-but-unread source from appearing.
33
33
 
34
- 4. **Write scratch drafts**: create `/tmp/analysis-<feature-slug>-<UTC-iso8601>/` and write `<feature>-<platform>.md` for each selected platform, or a single `<feature>.md` when `platforms[]` is empty. Update `state.analysisSpec.outputs.draftDir` with the path.
34
+ 4. **Write scratch drafts**: create `/tmp/analysis-<feature-slug>-<UTC-iso8601>/` and write `<feature>-<platform>.md` per selected platform. With `platforms[]` empty, write one file per derived channel (`<feature>-mobile.md`, `<feature>-web.md`; Locked 35), or a single `<feature>.md` when the evidence describes no interface. Update `state.analysisSpec.outputs.draftDir` with the path.
35
35
 
36
36
  5. **Surface the draft tree to the user**:
37
37
  ```
@@ -48,18 +48,18 @@ AskUserQuestion (multiSelect=true), at least one selection required. `Local file
48
48
 
49
49
  ```
50
50
  header: "Output"
51
- question: <localized: "Where should the per-platform analyses be written?">
51
+ question: <localized: "Where should the analyses be written?">
52
52
  options:
53
53
  - label: "Local file"
54
- description: <resolved path, shown literally: "<repo>/analysis/<feature>-<platform>.md" per selected repo, or "~/Desktop/multiAgentAnalysis/<feature-name>/<feature>.md" when no repo was selected>
54
+ description: <resolved path, shown literally: "<repo>/analysis/<feature>-<platform>.md" per selected repo, or "~/Desktop/multiAgentAnalysis/<feature-name>/<feature>-<channel>.md" when no repo was selected>
55
55
  - label: "Confluence page"
56
56
  - label: "Jira issue"
57
57
  description: <localized: "As a comment by default; writing the description is a separate, explicit choice">
58
58
  ```
59
59
 
60
60
  Conditional follow-ups:
61
- - If `Confluence` selected: ask `header="Parent page"`, free-text via Other for the parent page key or URL. One Confluence page per platform is created under this parent, each titled `<Feature> - <Platform>`.
62
- - If `Jira` selected: if Step 5 Q4 produced Jira IDs, AskUserQuestion (single-select) to pick which one; otherwise ask via Other. Per Locked decision 9 + design choice "single description with platform separators", the chosen issue receives one combined body holding all per-platform sections under `h2. Platform: <X>` separators (wiki markup - neither the comment nor the description field renders Markdown; conversion happens at dispatch, see Phase 4).
61
+ - If `Confluence` selected: ask `header="Parent page"`, free-text via Other for the parent page key or URL. One Confluence page per emitted document is created under this parent, each titled `<Feature> - <Platform>` (or `<Feature> - Mobile` / `<Feature> - Web` on a repo-less run, or a bare `<Feature>` for a single channel-agnostic document).
62
+ - If `Jira` selected: if the Step 5 Jira question produced Jira IDs, AskUserQuestion (single-select) to pick which one; otherwise ask via Other. Per Locked decision 9 + design choice "single description with platform separators", the chosen issue receives one combined body holding all per-platform sections under `h2. Platform: <X>` separators (wiki markup - neither the comment nor the description field renders Markdown; conversion happens at dispatch, see Phase 4).
63
63
 
64
64
  Then ask **where in the issue it goes**, because two of the three answers can destroy text somebody else wrote:
65
65
 
@@ -96,11 +96,11 @@ Iterate `state.analysisSpec.outputs.requested`. For each target:
96
96
 
97
97
  | Target | Action |
98
98
  |--------|--------|
99
- | Local | For each per-platform draft, `cp /tmp/analysis-<feature-slug>-<ts>/<feature>-<platform>.md` into `analysis/<feature>-<platform>.md` in the matching repo's working tree. When multiple repos exist for the same platform, the file is duplicated into each and the dispatch report lists every destination. **When no repo was selected** (Locked 35) there is no working tree to be relative to, so the file lands in `~/Desktop/multiAgentAnalysis/<feature-name>/<feature>.md`. The current working directory is never written to: for a repo-less run it is arbitrary, and creating a folder in whatever directory the command happened to be invoked from is the kind of surprise that costs a tool its trust. The user can override the path through the picker's Other input. **No commit.** |
100
- | Confluence | Re-humanize each per-platform draft with `formal-stakeholder` tone. One Confluence page per platform under the chosen parent, titled `<Feature> - <Platform>`. Cross-link siblings inside each page via `<ac:link><ri:page ri:content-title="<Feature> - <OtherPlatform>"/></ac:link>`. Markdown -> storage XML via `$HOME/.claude/multi-agent-refs/channels/confluence.md`. Re-emit on existing pages uses PUT with version bump. |
101
- | Jira | Re-humanize the combined body with `informal-technical` tone. Concatenate per-platform drafts under `h2. Platform: iOS`, `h2. Platform: Android`, `h2. Platform: Backend`, `h2. Platform: Frontend` separators (in the order platforms were selected), then run the whole body through the markdown → Jira wiki conversion table in `$HOME/.claude/multi-agent-refs/channels/jira.md` - both the comment body and the `description` field render wiki markup, so raw `##`/`**`/backticks arrive as literal text. Write the converted body to a file and publish it with `$HOME/.claude/lib/jira-publish.sh`, never with a hand-rolled `curl`: <br><br>`bash "$HOME/.claude/lib/jira-publish.sh" --issue "$KEY" --body-file "$F" --target comment` <br>`bash "$HOME/.claude/lib/jira-publish.sh" --issue "$KEY" --body-file "$F" --target description --mode append` <br><br>The script owns the parts that are easy to get wrong: it runs `jira-wiki-escape.mjs` on the body, resolves host + token without putting either in argv, and on the description path it GETs the current value, writes it to a backup under `~/.claude/logs/multi-agent/jira-backups/` and reports the path, appends below a `----` rule by default, and **refuses with exit 3** when `--mode replace` would discard a non-empty description unless `--confirm-overwrite` is passed. Exit 3 is reported to the user with the backup path, never retried with the flag added automatically - only the user's explicit "Description - replace" answer from Phase 3.5 supplies it. `--dry-run` previews the exact final body without writing. |
99
+ | Local | For each per-platform draft, `cp /tmp/analysis-<feature-slug>-<ts>/<feature>-<platform>.md` into `analysis/<feature>-<platform>.md` in the matching repo's working tree. When multiple repos exist for the same platform, the file is duplicated into each and the dispatch report lists every destination. **When no repo was selected** (Locked 35) there is no working tree to be relative to, so files land in `~/Desktop/multiAgentAnalysis/<feature-name>/`, one per derived channel (`<feature>-mobile.md`, `<feature>-web.md`) or a single `<feature>.md` when none could be derived. The current working directory is never written to: for a repo-less run it is arbitrary, and creating a folder in whatever directory the command happened to be invoked from is the kind of surprise that costs a tool its trust. The user can override the path through the picker's Other input. **No commit.** |
100
+ | Confluence | Re-humanize each draft with `formal-stakeholder` tone. One page per draft under the chosen parent, titled `<Feature> - <Platform>` - including a repo-less run, whose drafts are the derived channels (`<Feature> - Mobile` / `<Feature> - Web`, Locked 35). A single channel-agnostic draft becomes one page titled `<Feature>`, with no suffix implying a split that was not made. Cross-link siblings inside each page via `<ac:link><ri:page ri:content-title="<Feature> - <OtherPlatform>"/></ac:link>`; a lone page has no sibling block. Markdown -> storage XML via `$HOME/.claude/multi-agent-refs/channels/confluence.md`. Re-emit on existing pages uses PUT with version bump. |
101
+ | Jira | Re-humanize the combined body with `informal-technical` tone. Concatenate the drafts under `h2. Platform: <X>` separators in production order - `iOS`, `Android`, `Web`, `Backend` repo-backed, `Mobile` / `Web` for channels derived on a repo-less run (Locked 35). A single channel-agnostic draft gets no separator: a heading announcing a split of one is noise. then run the whole body through the markdown → Jira wiki conversion table in `$HOME/.claude/multi-agent-refs/channels/jira.md` - both the comment body and the `description` field render wiki markup, so raw `##`/`**`/backticks arrive as literal text. Write the converted body to a file and publish it with `$HOME/.claude/lib/jira-publish.sh`, never with a hand-rolled `curl`: <br><br>`bash "$HOME/.claude/lib/jira-publish.sh" --issue "$KEY" --body-file "$F" --target comment` <br>`bash "$HOME/.claude/lib/jira-publish.sh" --issue "$KEY" --body-file "$F" --target description --mode append` <br><br>The script owns the parts that are easy to get wrong: it runs `jira-wiki-escape.mjs` on the body, resolves host + token without putting either in argv, and on the description path it GETs the current value, writes it to a backup under `~/.claude/logs/multi-agent/jira-backups/` and reports the path, appends below a `----` rule by default, and **refuses with exit 3** when `--mode replace` would discard a non-empty description unless `--confirm-overwrite` is passed. Exit 3 is reported to the user with the backup path, never retried with the flag added automatically - only the user's explicit "Description - replace" answer from Phase 3.5 supplies it. `--dry-run` previews the exact final body without writing. |
102
102
 
103
- **Output capture**: fill `state.analysisSpec.outputs.localPaths[]` (one entry per per-platform-per-repo write), `outputs.confluencePageUrls[]` (one entry per platform), `outputs.jiraIssueKey` (single string).
103
+ **Output capture**: fill `state.analysisSpec.outputs.localPaths[]` (one entry per per-platform-per-repo write, or one per derived channel on a repo-less run), `outputs.confluencePageUrls[]` (one entry per emitted document), `outputs.jiraIssueKey` (single string).
104
104
 
105
105
  ### Phase 5 - Report & stop
106
106
 
@@ -169,9 +169,32 @@ Per the `analysis-output-confluence-on-request` memory, Confluence post is NEVER
169
169
  2. Use parent page URL from user input. No default parent is hardcoded here; the user picks one at the prompt (LRU recents come from `prefs.projects[<project>].confluenceUrls`).
170
170
 
171
171
  **Corporate profile exception.** When `state.analysisSpec.profile == "corporate"` and the bindings are configured, the destination is already settled and the prompt is skipped: the space comes from `prefs.global.analysisProfile.corporate.confluenceSpaceKey`, the parent from `prefs.global.analysisProfile.corporate.confluenceParentPageId`, and the page title is built from `prefs.global.analysisProfile.corporate.titleFormat` with `prefs.global.analysisProfile.corporate.titlePrefix` filling its `{prefix}` placeholder. A corporate analysis always lands in the same tree, so asking each time is a question whose answer never changes. Any of the four missing falls back to the prompt above rather than guessing, and a title that would collide with an existing page becomes an update (PUT with version bump), never a second page.
172
- 3. Convert markdown to storage XML using the table in `$HOME/.claude/multi-agent-refs/channels/confluence.md`.
173
- 4. Upload Figma frame screenshots as page attachments via `POST /rest/api/content/{pageId}/child/attachment` (cache the MCP asset locally first because the upstream URLs expire after 7 days).
174
- 5. Reference attachments inside the page body via `<ac:image><ri:attachment ri:filename="frame-<nodeId>.png"/></ac:image>`.
175
- 6. POST `/rest/api/content` to create the page (or PUT with version bump when updating). Surface the resulting page URL in the Phase 4 report.
172
+ 3. **Publish with `md2confluence-v3.py`, never a hand-rolled conversion + curl.** The
173
+ script owns the three steps that are easy to get wrong and that a hand-rolled path
174
+ has already dropped in practice: the storage-XML conversion (the table in
175
+ `$HOME/.claude/multi-agent-refs/channels/confluence.md`), the multipart upload of
176
+ every PNG in `--attachments-dir`, and the `![](file.png)` to
177
+ `<ac:image><ri:attachment ri:filename="file.png"/></ac:image>` injection that makes
178
+ a frame gallery render as pictures instead of as filenames (Locked 18). It also
179
+ warns when a body references an image no attachment matched, which is the signal
180
+ that a screenshot never downloaded.
181
+
182
+ ```bash
183
+ python3 "$HOME/.claude/lib/md2confluence-v3.py" create \
184
+ --space "$SPACE" --parent-page-id "$PARENT" --title "$TITLE" \
185
+ --markdown "$DRAFT" --attachments-dir "$(dirname "$DRAFT")"
186
+ # updating an existing page: `update --page-id <ID>` instead of `create --space ...`
187
+ ```
188
+
189
+ Screenshots must already sit in `--attachments-dir` before this runs. Point
190
+ `figma-screenshot.sh --output-dir` at the draft directory during evidence
191
+ collection rather than letting it default to its own `/tmp` folder, because a
192
+ frame gallery whose PNGs live somewhere else converts to `<a href="frame-NNN.png">`
193
+ and the page shows filenames where the pictures should be. MCP asset URLs expire
194
+ after 7 days, so the cell must reference the local file, never the remote URL.
195
+ 4. Read the script's JSON envelope: it reports the page id and URL, every attachment
196
+ uploaded, and the `image reference has no matching attachment` warnings. A non-empty
197
+ warning list means the page shipped with missing pictures; surface it rather than
198
+ reporting a clean publish. Surface the page URL in the Phase 4 report.
176
199
 
177
200
  Token miss handling: surface a single line `WARN: Confluence token not configured (prefs.global.keychainMapping.confluence). Run /multi-agent:setup or skip Confluence output.` and continue with local-only output.
@@ -13,11 +13,11 @@ Build one in-memory `synthesizedSections` object from all evidence. Apply the om
13
13
  ```
14
14
  synthesizedSections = {
15
15
  scope: { ... shared block ... },
16
- design: { shared: {frameGallery, codeConnect}, byPlatform: {ios, android, frontend} },
17
- localization: { keys: [...], byPlatform: {ios: {source: "Localizable.strings", rows}, android: {source: "strings.xml", rows}, frontend: {source: "i18n/*.json", rows}} },
16
+ design: { shared: {frameGallery, codeConnect}, byPlatform: {ios, android, web} },
17
+ localization: { keys: [...], byPlatform: {ios: {source: "Localizable.strings", rows}, android: {source: "strings.xml", rows}, web: {source: "i18n/*.json", rows}} },
18
18
  apiContracts: { ... shared verbatim ... },
19
- deeplinkPush: { byPlatform: {ios, android, frontend, backend: null} },
20
- business: { rules: [...], useCases: [...], firebaseEvents: [...], byPlatform: {ios: {testFramework: "Swift Testing", skeletons}, android: {testFramework: "JUnit5 + Turbine"}, frontend: {testFramework: "Vitest + RTL"}, backend: {testFramework: "pytest"}} },
19
+ deeplinkPush: { byPlatform: {ios, android, web, backend: null} },
20
+ business: { rules: [...], useCases: [...], firebaseEvents: [...], byPlatform: {ios: {testFramework: "Swift Testing", skeletons}, android: {testFramework: "JUnit5 + Turbine"}, web: {testFramework: "Vitest + RTL"}, backend: {testFramework: "pytest"}} },
21
21
  devPlan: { byPlatform: {ios: {tasks, standards}, android: {...}, ...} }
22
22
  }
23
23
  ```
@@ -28,7 +28,7 @@ synthesizedSections = {
28
28
  | 2. Design | If `evidence.figma[]` is empty AND no `evidence.repoEvidence[*].buckets.uiComponents` direct-match → `byPlatform[*]` = null. Otherwise produce frame gallery (shared) + per-platform component inventory (rows from `evidence.repoEvidence[<repo>].buckets.uiComponents`). |
29
29
  | 3. Localization | If both Figma annotations/text layers and `evidence.repoEvidence[*].buckets.localizationKeys` are empty → `byPlatform[*]` = null. Otherwise produce the key table per the project `localization.ownership` mode (Locked 20): `in-repo` fills the configured locale set (default `ar, de, en, es, fr, it, ru, tr`, RTL for ar); `externally-owned` lists key + status + copy source + base value and defers per-locale values. Base copy is sourced from the Figma annotation when present (Locked 3). |
30
30
  | 4. API Contracts | If `evidence.swagger[]` is empty AND no Confluence embedded API table AND no `evidence.repoEvidence[*].buckets.services` direct-match → null. Otherwise produce endpoint summary + per-endpoint tables (shared verbatim across files). |
31
- | 5. Deeplink / Push | Per-platform: iOS uses Universal Links / `UNUserNotificationCenter`; Android uses `intent-filter` / FCM; Frontend uses web URL routing; Backend file omits this section entirely. |
31
+ | 5. Deeplink / Push | Per-platform: iOS uses Universal Links / `UNUserNotificationCenter`; Android uses `intent-filter` / FCM; Web uses web URL routing; Backend file omits this section entirely. |
32
32
  | 6. Business + Tests | If sections 2 and 4 are both `null` AND `evidence.firebase[]` is empty → null. Otherwise produce use-case + mock stubs + per-platform test skeletons + shared Firebase events table. Reuse Red-Green-Refactor naming from `$HOME/.claude/rules/tdd.md`. |
33
33
  | 7. Development Plan | Always present. Tasks for the current platform only. Architecture standards come from `evidence.standards[]` filtered by platform (see Pass B step 1). **Reuse-first rule (Locked 11)**: when an item has `direct-match` in `evidence.repoEvidence[<repo>].buckets.<X>`, emit `Reuse existing <FQN> (<file>:<line>)` instead of `Add new <FQN>`. New-write task with a `direct-match` competitor becomes a Risk row. |
34
34
 
@@ -85,8 +85,8 @@ For each `platform` in `state.analysisSpec.platforms[]`:
85
85
  - `ios` → `prefs.projects[<key>].standardsFile` if present → glob `~/<project>-iOS-Standards.md` → `~/.claude/rules/swiftui-qa.md`
86
86
  - `android` → `~/.claude/rules/kotlin-android.md` first → `evidence.standards[]` entries whose path contains `android` or `kotlin`
87
87
  - `backend` → `evidence.standards[]` entries matching language hints (`python`, `go`, `node`, `fastapi`) → fall back to `~/.claude/rules/security.md` + `code-style.md`
88
- - `frontend` → `evidence.standards[]` entries matching `react`, `vue`, `next`, `sveltekit` → `~/.claude/rules/code-style.md`
89
- 2. **Apply per-platform omission rules.** Backend-only file drops Sections 5, 6, 7, 8, 16. Frontend with no UI inventory still keeps 5 (UI exists in code). Sections 1, 2, 4, 9, 13, 14, 20, 21 always present per Locked decision 2 + 13.
88
+ - `web` → `evidence.standards[]` entries matching `react`, `vue`, `next`, `sveltekit` → `~/.claude/rules/code-style.md`
89
+ 2. **Apply per-platform omission rules.** Backend-only file drops Sections 5, 6, 7, 8, 16. Web with no UI inventory still keeps 5 (UI exists in code). Sections 1, 2, 4, 9, 13, 14, 20, 21 always present per Locked decision 2 + 13.
90
90
  3. **Resolve mode.** If user passed `--lite` → Lite. If user passed `--full` → Full. Otherwise use `state.analysisSpec.liteModeAuto`. Lite mode renders only Sections 1, 2, 4, 9, 13, 14, 21 plus optional 23.
91
91
  4. **Produce YAML front-matter header** (see `$HOME/.claude/multi-agent-refs/analysis-template.md`). Include `profile: <state.analysisSpec.profile | global>` and `platform: <platform | none>` so the validator applies the right contract per profile (Locked 32) and recognises the stack-optional render (Locked 35), `mode: full | lite`, plus `ui_tests: <state.analysisSpec.options.uiTests | false>` and `a11y_depth: <state.analysisSpec.options.a11yDepth | basic>` so the pre-dispatch validator can enforce the opt-in coverage (15.6 present when ui_tests, 16.2 walkthrough present when a11y_depth is full).
92
92
  5. **Read conventions for this platform's repo.** For each cell Pass B fills in Section 13 and in any per-platform projection (Sections 5, 6, 7, 8, 10, 11, 13, 14, 15, 16, 17), read `state.analysisSpec.evidence.conventions[<repo>].<field>` and emit the value with a footnote (Locked 24). If `conventionOverrides` has an entry for that field, use the override and footnote with `^[user-override: <reason>]` instead of evidence path.
@@ -374,7 +374,7 @@ Which user-facing copy this feature needs, who owns it, and where it comes from.
374
374
 
375
375
  # Bölüm B - Teknik Analiz / Part B - Technical Analysis
376
376
 
377
- Part B carries what is technically true. It reuses the global template's scaffolds verbatim, renumbered. Read `analysis-template.md` for each one; only the number changes.
377
+ Part B carries what is technically true. It reuses the global template's scaffolds verbatim, renumbered. Read `analysis-template.md` for each one; only the number changes - the number, not the contents. Rebuilding a table from its heading instead of copying the scaffold is how the frame gallery lost its `![](frame-<prefix>-<nodeId>.png)` cells and shipped filenames as text.
378
378
 
379
379
  | Corporate | Global | Section |
380
380
  |---|---|---|
@@ -42,7 +42,7 @@ Every per-platform file starts with this YAML block:
42
42
  ```yaml
43
43
  ---
44
44
  feature: <FeatureName>
45
- platform: ios | android | backend | frontend | none
45
+ platform: ios | android | web | backend | mobile | none
46
46
  profile: global | corporate
47
47
  language: tr | en
48
48
  mode: full | lite
@@ -338,7 +338,7 @@ Precise-enough-to-reproduce layout. Spacing/padding are token names (no raw numb
338
338
  Capture order (tokens never hardcoded): `mcp__claude_ai_Figma__get_metadata` (tree) -> `mcp__claude_ai_Figma__get_variable_defs` (resolve spacing/color/type tokens) -> render -> `mcp__claude_ai_Figma__get_screenshot` (visual check). Run per section/frame for fidelity, not per whole page.
339
339
  ```
340
340
 
341
- Frame gallery images are uploaded to Confluence as multipart attachments and referenced via `<ac:image><ri:attachment ri:filename="..." /></ac:image>` (Locked 17). Section URL drilling enumerates all child frames automatically via `mcp__claude_ai_Figma__get_metadata` or `figma-screenshot.sh --section`.
341
+ Every Screenshot cell is a markdown image reference to a locally cached PNG - `![](frame-<prefix>-<nodeId>.png)`, never a bare filename and never a remote URL. Dispatch uploads them as Confluence multipart attachments and injects `<ac:image><ri:attachment ri:filename="..." /></ac:image>` (Locked 18, run by `md2confluence-v3.py --attachments-dir`). Section URL drilling enumerates all child frames automatically via `mcp__claude_ai_Figma__get_metadata` or `figma-screenshot.sh --section`.
342
342
 
343
343
  ## 6. Bileşen Envanteri / Component Inventory
344
344
 
@@ -414,7 +414,7 @@ Rendered when the design ships a dark variant (else drop with note `(N/A: no dar
414
414
  | <named asset> | icon@light | icon@dark | asset-catalog appearance variant |
415
415
  ```
416
416
 
417
- Per-platform projection translates token names: iOS uses `.Spacing.spacingN` enum + adaptive `Color(.systemBackground)` / asset appearances; Android uses `MaterialTheme.spacing.medium` + Material 3 color roles with tonal elevation in dark; Frontend uses CSS variables `--spacing-md` / `--color-*` per `prefers-color-scheme`; Backend section omitted.
417
+ Per-platform projection translates token names: iOS uses `.Spacing.spacingN` enum + adaptive `Color(.systemBackground)` / asset appearances; Android uses `MaterialTheme.spacing.medium` + Material 3 color roles with tonal elevation in dark; Web uses CSS variables `--spacing-md` / `--color-*` per `prefers-color-scheme`; Backend section omitted.
418
418
 
419
419
  ## 8. Asset Envanteri / Asset Inventory
420
420
 
@@ -539,7 +539,7 @@ Per-platform projection (both modes):
539
539
  | iOS | `Localizable.xcstrings` | hierarchical dot PascalCase | `Feature.Element` ^[C6 high] |
540
540
  | Android | `strings.xml` | flat snake_case | `feature_element` ^[C6 high] |
541
541
  | Backend | error code table | per server convention | uppercase enum |
542
- | Frontend | `i18n/*.json` | hierarchical dot camelCase | `feature.element` ^[C6 high] |
542
+ | Web | `i18n/*.json` | hierarchical dot camelCase | `feature.element` ^[C6 high] |
543
543
 
544
544
  ## 11. Analytics
545
545
 
@@ -735,7 +735,7 @@ Per-platform projection.
735
735
 
736
736
  Locked 31 - one sub-table per business rule from Section 4.4. Enumerate the cases: happy, boundary (min/max, off-by-one), error/failure, empty/nil. Each row is Given / When / Then plus a framework-correct test name that traces back to the `BR-` id. Boundary rows collapse into one parameterized test.
737
737
 
738
- Framework per platform (from Phase 1c conventions; these are the modern defaults): iOS Swift Testing (`@Test`, `@Suite`, `@Test(arguments:)` for boundary tables, `#expect` / `#require`); Android JUnit5 + MockK (`coEvery` / `coVerify`) + Turbine (`flow.test { awaitItem() }`) + coroutines-test (`runTest`, `StandardTestDispatcher`); Backend pytest (parametrize); Frontend Vitest.
738
+ Framework per platform (from Phase 1c conventions; these are the modern defaults): iOS Swift Testing (`@Test`, `@Suite`, `@Test(arguments:)` for boundary tables, `#expect` / `#require`); Android JUnit5 + MockK (`coEvery` / `coVerify`) + Turbine (`flow.test { awaitItem() }`) + coroutines-test (`runTest`, `StandardTestDispatcher`); Backend pytest (parametrize); Web Vitest.
739
739
 
740
740
  **BR-<slug>-01**
741
741
 
@@ -1094,7 +1094,7 @@ Folder: `app/api/profile/`, `app/services/profile/`, `app/schemas/profile/`
1094
1094
  Test name: `def test_fetch_valid_input_returns_profile():`
1095
1095
  OpenAPI operationId: `updateProfile`
1096
1096
 
1097
- ## Frontend render example
1097
+ ## Web render example
1098
1098
 
1099
1099
  Conventions extracted: Next.js App Router, feature folder, React Query hooks, Zustand stores, Vitest, kebab data-testid, hierarchical dot camelCase i18n.
1100
1100
 
@@ -115,6 +115,7 @@ Auto-suggested as `"{jiraId} - {taskTitle}"` - user can edit at the prompt.
115
115
  | Code fences ` ```lang ` | `<ac:structured-macro ac:name="code"><ac:parameter ac:name="language">lang</ac:parameter><ac:plain-text-body><![CDATA[...]]></ac:plain-text-body></ac:structured-macro>` |
116
116
  | Inline `code` | `<code>code</code>` |
117
117
  | Links `[t](u)` | `<a href="u">t</a>` |
118
+ | Images `![alt](file.png)` | `<ac:image><ri:attachment ri:filename="file.png"/></ac:image>`, with the file uploaded as a page attachment first (Locked 18). An `http(s)` src renders as an external image instead. `md2confluence-v3.py --attachments-dir <dir>` does both halves. **When the named file is not in that directory the converter degrades to `<a href="file.png">file.png</a>` - a page showing filenames instead of pictures means the PNGs never reached `--attachments-dir`, not that the syntax was wrong.** It emits `image reference has no matching attachment: <file>` for each one. |
118
119
  | Lists | `<ul><li>...</li></ul>` / `<ol><li>...</li></ol>` |
119
120
  | ` ```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 |
120
121
 
@@ -31,11 +31,11 @@ The PR description targets code reviewers - it stays technical. Every adapter
31
31
  - `<path/to/test.ext>` - <which scenarios were added/updated>
32
32
  ```
33
33
 
34
- Across stacks the same shape produces, for example: `LoginView.swift - ...` (iOS), `LoginScreen.kt - ...` (Android), `login-form.tsx - ...` (frontend), `auth_service.py - ...` (backend). File paths and symbol names are NOT translated - only the trailing description sentence follows `outputLanguage`.
34
+ Across stacks the same shape produces, for example: `LoginView.swift - ...` (iOS), `LoginScreen.kt - ...` (Android), `login-form.tsx - ...` (web), `auth_service.py - ...` (backend). File paths and symbol names are NOT translated - only the trailing description sentence follows `outputLanguage`.
35
35
 
36
36
  **`architecture`** - only when the change involves a non-trivial decision (new abstraction, pattern change, data flow shift, dependency direction). Format: short paragraph stating the decision and the alternative considered. Skip the section entirely for mechanical refactors / dependency bumps / formatting passes.
37
37
 
38
- **`verification`** - what the reviewer should run to confirm the change works. Commands first, manual steps next. Pick the commands for the project's stack - the pipeline supports iOS (Swift/Xcode), Android (Gradle), frontend (npm/pnpm/yarn), and backend (varies: pytest/jest/go test/etc). Do not hardcode one stack in the body; emit only the commands relevant to the repos touched by this PR.
38
+ **`verification`** - what the reviewer should run to confirm the change works. Commands first, manual steps next. Pick the commands for the project's stack - the pipeline supports iOS (Swift/Xcode), Android (Gradle), web (npm/pnpm/yarn), and backend (varies: pytest/jest/go test/etc). Do not hardcode one stack in the body; emit only the commands relevant to the repos touched by this PR.
39
39
 
40
40
  Skeleton (the adapter fills the body with the actual stack-appropriate lines at write-time; the template lists the shape only):
41
41