@mmerterden/multi-agent-pipeline 18.0.0 → 19.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 (209) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +34 -18
  3. package/README.tr.md +14 -16
  4. package/docs/adr/0002-instruction-driven-flag.md +1 -0
  5. package/docs/adr/0005-lazy-phase-docs.md +11 -1
  6. package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +1 -0
  7. package/docs/adr/0010-own-code-graph.md +1 -0
  8. package/docs/adr/0014-six-phase-consolidation.md +134 -0
  9. package/docs/adr/README.md +2 -1
  10. package/docs/architecture.md +37 -38
  11. package/docs/best-practices.md +1 -1
  12. package/docs/ecosystem.md +37 -26
  13. package/docs/engineering.md +1 -1
  14. package/docs/facts.json +45 -0
  15. package/docs/features.md +54 -53
  16. package/docs/performance.md +5 -5
  17. package/docs/recovery-guide.md +9 -9
  18. package/docs/token-budget-history.md +3 -1
  19. package/index.js +2 -2
  20. package/install/_codex-agents.mjs +1 -1
  21. package/install/templates/claude-hooks.json +1 -1
  22. package/install/templates/codex-instructions.md +1 -1
  23. package/install/templates/copilot-instructions.md +28 -28
  24. package/manifest.json +209 -193
  25. package/package.json +2 -2
  26. package/pipeline/agents/dev-critic.md +3 -3
  27. package/pipeline/commands/figma-to-swiftui.md +1 -1
  28. package/pipeline/commands/multi-agent/SKILL.md +8 -8
  29. package/pipeline/commands/multi-agent/analysis/SKILL.md +9 -9
  30. package/pipeline/commands/multi-agent/autopilot/SKILL.md +7 -7
  31. package/pipeline/commands/multi-agent/channels/SKILL.md +15 -15
  32. package/pipeline/commands/multi-agent/diff-explain/SKILL.md +6 -6
  33. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
  34. package/pipeline/commands/multi-agent/graph/SKILL.md +1 -1
  35. package/pipeline/commands/multi-agent/help/SKILL.md +62 -62
  36. package/pipeline/commands/multi-agent/language/SKILL.md +2 -2
  37. package/pipeline/commands/multi-agent/local/SKILL.md +11 -11
  38. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +13 -13
  39. package/pipeline/commands/multi-agent/log/SKILL.md +2 -2
  40. package/pipeline/commands/multi-agent/manual-test/SKILL.md +9 -9
  41. package/pipeline/commands/multi-agent/model/SKILL.md +69 -0
  42. package/pipeline/commands/multi-agent/refactor/SKILL.md +3 -3
  43. package/pipeline/commands/multi-agent/resume/SKILL.md +4 -4
  44. package/pipeline/commands/multi-agent/resume-local/SKILL.md +19 -17
  45. package/pipeline/commands/multi-agent/review/SKILL.md +1 -1
  46. package/pipeline/commands/multi-agent/route-off/SKILL.md +36 -0
  47. package/pipeline/commands/multi-agent/route-on/SKILL.md +74 -0
  48. package/pipeline/commands/multi-agent/route-status/SKILL.md +56 -0
  49. package/pipeline/commands/multi-agent/setup/SKILL.md +2 -2
  50. package/pipeline/commands/multi-agent/status/SKILL.md +5 -5
  51. package/pipeline/commands/multi-agent/steer/SKILL.md +2 -2
  52. package/pipeline/commands/multi-agent/sync/SKILL.md +12 -13
  53. package/pipeline/commands/multi-agent/test/SKILL.md +1 -1
  54. package/pipeline/lib/credential-inventory.sh +1 -1
  55. package/pipeline/lib/fetch-fortify.sh +1 -1
  56. package/pipeline/lib/model-rung.sh +142 -0
  57. package/pipeline/lib/phase-schema.mjs +88 -0
  58. package/pipeline/lib/plan-todos.sh +5 -5
  59. package/pipeline/lib/route-state.sh +161 -0
  60. package/pipeline/lib/run-paths.sh +2 -2
  61. package/pipeline/multi-agent-refs/_account-picker.md +1 -1
  62. package/pipeline/multi-agent-refs/_dev-context.md +1 -1
  63. package/pipeline/multi-agent-refs/_input-parser.md +1 -1
  64. package/pipeline/multi-agent-refs/analysis/evidence.md +0 -9
  65. package/pipeline/multi-agent-refs/analysis/intake.md +1 -1
  66. package/pipeline/multi-agent-refs/analysis/locked.md +21 -22
  67. package/pipeline/multi-agent-refs/analysis/render.md +1 -1
  68. package/pipeline/multi-agent-refs/analysis/synthesis.md +12 -6
  69. package/pipeline/multi-agent-refs/android-guide.md +1 -1
  70. package/pipeline/multi-agent-refs/audit-guide.md +13 -13
  71. package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
  72. package/pipeline/multi-agent-refs/channels/jira.md +3 -3
  73. package/pipeline/multi-agent-refs/channels/pr.md +4 -4
  74. package/pipeline/multi-agent-refs/channels/wiki.md +1 -1
  75. package/pipeline/multi-agent-refs/component-dispatch.md +3 -3
  76. package/pipeline/multi-agent-refs/cross-cli-contract.md +31 -6
  77. package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +4 -4
  78. package/pipeline/multi-agent-refs/features/code-graph.md +5 -5
  79. package/pipeline/multi-agent-refs/features/design-conformance.md +1 -1
  80. package/pipeline/multi-agent-refs/features/dev-critic.md +3 -3
  81. package/pipeline/multi-agent-refs/features/doctor.md +2 -2
  82. package/pipeline/multi-agent-refs/features/external-context-injection.md +3 -3
  83. package/pipeline/multi-agent-refs/features/maturity-followup.md +3 -3
  84. package/pipeline/multi-agent-refs/features/model-fallback.md +5 -5
  85. package/pipeline/multi-agent-refs/features/plan-todos.md +1 -1
  86. package/pipeline/multi-agent-refs/features/repo-map.md +1 -1
  87. package/pipeline/multi-agent-refs/features/review-delta.md +3 -3
  88. package/pipeline/multi-agent-refs/features/review-multi-repo.md +1 -1
  89. package/pipeline/multi-agent-refs/features/scope-check.md +4 -4
  90. package/pipeline/multi-agent-refs/features/skill-conformance.md +2 -2
  91. package/pipeline/multi-agent-refs/features/stack-skill-routing.md +1 -1
  92. package/pipeline/multi-agent-refs/features/verify-by-test.md +4 -4
  93. package/pipeline/multi-agent-refs/features/visual-evidence.md +19 -19
  94. package/pipeline/multi-agent-refs/features/worktree-finalize.md +6 -6
  95. package/pipeline/multi-agent-refs/issue-jira-triad.md +10 -10
  96. package/pipeline/multi-agent-refs/knowledge.md +11 -11
  97. package/pipeline/multi-agent-refs/multi-repo-integration-build.md +13 -13
  98. package/pipeline/multi-agent-refs/payload-contracts.md +8 -8
  99. package/pipeline/multi-agent-refs/phases/log-format.md +10 -10
  100. package/pipeline/multi-agent-refs/phases/modes.md +30 -30
  101. package/pipeline/multi-agent-refs/phases/operations.md +8 -8
  102. package/pipeline/multi-agent-refs/phases/phase-0-init.md +24 -24
  103. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +599 -0
  104. package/pipeline/multi-agent-refs/phases/{phase-3-dev.md → phase-2-dev.md} +129 -49
  105. package/pipeline/multi-agent-refs/phases/{phase-4-review.md → phase-3-review.md} +225 -107
  106. package/pipeline/multi-agent-refs/phases/{phase-6-commit.md → phase-4-commit.md} +23 -23
  107. package/pipeline/multi-agent-refs/phases/{phase-7-report.md → phase-5-report.md} +29 -29
  108. package/pipeline/multi-agent-refs/phases.md +44 -48
  109. package/pipeline/multi-agent-refs/picker-contract.md +1 -1
  110. package/pipeline/multi-agent-refs/progress-contract.md +6 -6
  111. package/pipeline/multi-agent-refs/readiness-review.md +1 -1
  112. package/pipeline/multi-agent-refs/rules.md +7 -7
  113. package/pipeline/multi-agent-refs/swiftui-guide.md +2 -2
  114. package/pipeline/multi-agent-refs/tracker-contract.md +31 -32
  115. package/pipeline/multi-agent-refs/wiki-capture.md +14 -14
  116. package/pipeline/preferences-template.json +9 -1
  117. package/pipeline/rules/outside-the-pipeline.md +1 -1
  118. package/pipeline/schemas/agent-state.schema.json +50 -50
  119. package/pipeline/schemas/analysis-output.schema.json +2 -2
  120. package/pipeline/schemas/autopilot-config.schema.json +1 -1
  121. package/pipeline/schemas/code-graph.schema.json +1 -1
  122. package/pipeline/schemas/criteria-manifest.schema.json +1 -1
  123. package/pipeline/schemas/dev-critic-output.schema.json +1 -1
  124. package/pipeline/schemas/diff-risk.schema.json +1 -1
  125. package/pipeline/schemas/migrations/prefs-2.4.0-to-2.5.0.mjs +2 -2
  126. package/pipeline/schemas/migrations/prefs-2.6.0-to-2.7.0.mjs +31 -0
  127. package/pipeline/schemas/migrations/state-2.1.0-to-2.2.0.mjs +129 -0
  128. package/pipeline/schemas/phases.json +105 -0
  129. package/pipeline/schemas/plan-todos.schema.json +5 -5
  130. package/pipeline/schemas/planning-output.schema.json +1 -1
  131. package/pipeline/schemas/prefs.schema.json +100 -56
  132. package/pipeline/schemas/reviewer-output.schema.json +3 -3
  133. package/pipeline/schemas/route-config.schema.json +74 -0
  134. package/pipeline/schemas/scope-check.schema.json +1 -1
  135. package/pipeline/schemas/test-gap.schema.json +1 -1
  136. package/pipeline/schemas/token-budget.json +12 -18
  137. package/pipeline/schemas/triage-output.schema.json +6 -6
  138. package/pipeline/scripts/README.md +3 -3
  139. package/pipeline/scripts/_code-graph.mjs +2 -2
  140. package/pipeline/scripts/_run-paths.mjs +2 -2
  141. package/pipeline/scripts/_smoke-root.sh +1 -1
  142. package/pipeline/scripts/aggregate-metrics.mjs +1 -1
  143. package/pipeline/scripts/capture-flush.sh +8 -8
  144. package/pipeline/scripts/capture-resume.sh +3 -3
  145. package/pipeline/scripts/classify-plan-safety.mjs +1 -1
  146. package/pipeline/scripts/diff-explain.mjs +1 -1
  147. package/pipeline/scripts/doctor.mjs +2 -2
  148. package/pipeline/scripts/gc-abandoned.sh +3 -3
  149. package/pipeline/scripts/gc-tmp.sh +1 -1
  150. package/pipeline/scripts/gc-worktrees.sh +1 -1
  151. package/pipeline/scripts/gen-facts.mjs +175 -0
  152. package/pipeline/scripts/gen-mode-dispatch.mjs +32 -37
  153. package/pipeline/scripts/gen-ref-toc.mjs +1 -1
  154. package/pipeline/scripts/graph-report.mjs +1 -1
  155. package/pipeline/scripts/jira-attach.sh +1 -1
  156. package/pipeline/scripts/learn-from-transcripts.mjs +1 -1
  157. package/pipeline/scripts/learning-curve.mjs +2 -2
  158. package/pipeline/scripts/log-metric.sh +17 -4
  159. package/pipeline/scripts/memory-save.sh +1 -1
  160. package/pipeline/scripts/migrate-prefs.mjs +22 -5
  161. package/pipeline/scripts/phase-banner.sh +20 -20
  162. package/pipeline/scripts/phase-tracker.sh +7 -7
  163. package/pipeline/scripts/plan-coverage-gate.mjs +2 -2
  164. package/pipeline/scripts/render-agent-log-cost.sh +1 -1
  165. package/pipeline/scripts/render-work-summary.sh +3 -3
  166. package/pipeline/scripts/review-file-filter.mjs +1 -1
  167. package/pipeline/scripts/run-aggregator.mjs +13 -6
  168. package/pipeline/scripts/run-metrics.mjs +1 -1
  169. package/pipeline/scripts/runs-index.mjs +11 -1
  170. package/pipeline/scripts/smoke-cross-cli-behavior.sh +6 -6
  171. package/pipeline/scripts/smoke-schema-validation.sh +26 -7
  172. package/pipeline/scripts/token-budget-report.mjs +13 -2
  173. package/pipeline/scripts/triage-memory.mjs +2 -2
  174. package/pipeline/scripts/validate-analysis-doc.mjs +73 -17
  175. package/pipeline/scripts/validate-planning.mjs +1 -1
  176. package/pipeline/scripts/validate-reviewer.mjs +1 -1
  177. package/pipeline/scripts/validate-state.mjs +45 -5
  178. package/pipeline/scripts/validate-triage.mjs +3 -3
  179. package/pipeline/scripts/worktree-finalize.sh +5 -5
  180. package/pipeline/skills/.skill-manifest.json +37 -21
  181. package/pipeline/skills/.skills-index.json +49 -5
  182. package/pipeline/skills/shared/README.md +10 -6
  183. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +2 -2
  184. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +2 -2
  185. package/pipeline/skills/shared/core/multi-agent/SKILL.md +69 -71
  186. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +3 -3
  187. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +14 -14
  188. package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +5 -5
  189. package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +1 -1
  190. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +25 -23
  191. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -2
  192. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +2 -2
  193. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +8 -8
  194. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +6 -6
  195. package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +71 -0
  196. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +3 -3
  197. package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +1 -1
  198. package/pipeline/skills/shared/core/multi-agent-resume-local/SKILL.md +7 -7
  199. package/pipeline/skills/shared/core/multi-agent-route-off/SKILL.md +39 -0
  200. package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +76 -0
  201. package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +59 -0
  202. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -1
  203. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +5 -5
  204. package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +2 -2
  205. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +6 -5
  206. package/pipeline/skills/skills-index.md +8 -4
  207. package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +0 -263
  208. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +0 -344
  209. package/pipeline/multi-agent-refs/phases/phase-5-test.md +0 -182
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @file phase-schema.mjs - read a phase number that may have been written
3
+ * under either phase vocabulary.
4
+ *
5
+ * metrics.jsonl is append-only and v19.0.0 renumbered the phases, so the same
6
+ * field means two different things depending on when the line was written:
7
+ * `phase: 3` is Dev in a pre-v19 row and Review in a post-v19 one. Rows from
8
+ * v19.0.0 on carry `phaseSchema: 2`; rows without the field are generation 1.
9
+ *
10
+ * Every aggregator that compares a phase number to a literal, or prints a
11
+ * phase name, goes through here. Comparing directly is the bug this module
12
+ * exists to prevent - it does not throw, it silently attributes work to the
13
+ * wrong phase and the roll-up still looks plausible.
14
+ *
15
+ * @module pipeline/lib/phase-schema
16
+ */
17
+
18
+ import { readFileSync } from "node:fs";
19
+
20
+ const CONTRACT = JSON.parse(
21
+ readFileSync(new URL("../schemas/phases.json", import.meta.url), "utf8"),
22
+ );
23
+
24
+ export const CURRENT_SCHEMA = CONTRACT.phaseSchema;
25
+
26
+ /** Generation 1 number -> current number, derived from each phase's `was`. */
27
+ const GEN1_TO_CURRENT = new Map();
28
+ for (const p of CONTRACT.phases) {
29
+ for (const old of p.was) GEN1_TO_CURRENT.set(Number(old), p.id);
30
+ }
31
+
32
+ const CURRENT_NAMES = new Map(CONTRACT.phases.map((p) => [p.id, p.name]));
33
+ const GEN1_NAMES = new Map(
34
+ Object.entries(CONTRACT.legacyPhaseNames)
35
+ .filter(([k]) => /^\d+$/.test(k))
36
+ .map(([k, v]) => [Number(k), v]),
37
+ );
38
+
39
+ /**
40
+ * Which vocabulary a metrics row was written under.
41
+ *
42
+ * @param {object} row - a parsed metrics.jsonl line
43
+ * @returns {number} - 1 or 2
44
+ */
45
+ export function schemaOf(row) {
46
+ const s = Number(row?.phaseSchema);
47
+ return Number.isInteger(s) && s > 0 ? s : 1;
48
+ }
49
+
50
+ /**
51
+ * A row's phase expressed in the CURRENT vocabulary, so rows from both
52
+ * generations can be counted together.
53
+ *
54
+ * @param {string|number} phase
55
+ * @param {number} schema - from schemaOf()
56
+ * @returns {number|null} - null when the value is not a phase number at all
57
+ */
58
+ export function toCurrentPhase(phase, schema) {
59
+ const n = Number(phase);
60
+ if (!Number.isInteger(n)) return null;
61
+ if (schema >= CURRENT_SCHEMA) return CURRENT_NAMES.has(n) ? n : null;
62
+ return GEN1_TO_CURRENT.has(n) ? GEN1_TO_CURRENT.get(n) : null;
63
+ }
64
+
65
+ /**
66
+ * The name the phase number carried WHEN IT WAS WRITTEN. A historical row is
67
+ * labelled with its own vocabulary's name; relabelling it with today's name
68
+ * would make the archive disagree with the log it came from.
69
+ *
70
+ * @param {string|number} phase
71
+ * @param {number} schema
72
+ * @returns {string}
73
+ */
74
+ export function phaseNameAsWritten(phase, schema) {
75
+ const n = Number(phase);
76
+ const table = schema >= CURRENT_SCHEMA ? CURRENT_NAMES : GEN1_NAMES;
77
+ return table.get(n) ?? `Phase ${phase}`;
78
+ }
79
+
80
+ /**
81
+ * Convenience for the common shape: a row in, its current-vocabulary phase out.
82
+ *
83
+ * @param {object} row
84
+ * @returns {number|null}
85
+ */
86
+ export function rowPhase(row) {
87
+ return toCurrentPhase(row?.phase, schemaOf(row));
88
+ }
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env bash
2
2
  #
3
- # plan-todos.sh - manage the Phase 2 plan as a live Todo list.
3
+ # plan-todos.sh - manage the Phase 1 plan as a live Todo list.
4
4
  #
5
- # The Phase 2 plan is broken into a live, reviewable Todo list that updates
5
+ # The Phase 1 plan is broken into a live, reviewable Todo list that updates
6
6
  # step-by-step as Phase 3 works through it.
7
7
  #
8
8
  # State lives in `agent-state.json` under `.plan.todos[]` per
9
9
  # pipeline/schemas/plan-todos.schema.json. Phase 2 (Planning) emits the
10
10
  # initial plan; Phase 3 (Dev) iterates step-by-step; Phase 4 (Review)
11
- # inspects completion; Phase 7 (Report) renders the rollup.
11
+ # inspects completion; Phase 5 (Report) renders the rollup.
12
12
  #
13
13
  # Subcommands:
14
14
  # init <task-id> [title] Initialize with empty todos[] (Phase 2 usually pipes the JSON instead)
@@ -19,7 +19,7 @@
19
19
  # skip <task-id> <todo-id> <reason> Mark skipped (counts as satisfied for dependents)
20
20
  # next <task-id> Print next pending todo (deps-respecting); empty if none
21
21
  # list <task-id> Render Markdown checklist
22
- # status <task-id> Print one-line summary: "3/7 done, 1 in progress"
22
+ # status <task-id> Print one-line summary: "2/5 done, 1 in progress"
23
23
  # show <task-id> Print the full plan JSON
24
24
  #
25
25
  # Exit codes:
@@ -117,7 +117,7 @@ do_set() {
117
117
  fi
118
118
  # A planning-output document is accepted directly and converted here.
119
119
  #
120
- # The conversion used to live as a jq blob inside phase-2-planning.md, which
120
+ # The conversion used to live as a jq blob inside phase-1-plan.md, which
121
121
  # made the mapping from `tasks[]` to `todos[]` a thing two files defined - and
122
122
  # the phase doc was the copy nothing tested. Accepting both shapes costs four
123
123
  # lines and removes the second definition.
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env bash
2
+ # route-state.sh - read and write prefs.global.modelRouting.
3
+ #
4
+ # Backs /multi-agent:route-on, route-off and route-status. Ships disabled and
5
+ # stays that way until someone turns it on; while `enabled` is false nothing in
6
+ # this file changes one dispatch.
7
+ #
8
+ # The `off` contract is borrowed from autopilot-off and for the same reason: it
9
+ # clears the ON STATE, never the rules. Turning routing back on must not re-ask
10
+ # for a configuration the user already gave.
11
+ #
12
+ # Usage:
13
+ # route-state.sh status print the current policy
14
+ # route-state.sh on [--strategy=S] [--scope=a,b]
15
+ # route-state.sh off
16
+ # route-state.sh set-rules <file.json> replace rules[] from a JSON array
17
+ #
18
+ # Exit codes:
19
+ # 0 - done
20
+ # 2 - bad usage
21
+ # 3 - preferences missing/unparseable, or a scope value this refuses
22
+
23
+ set -uo pipefail
24
+
25
+ PREFS="${MULTI_AGENT_PREFS:-$HOME/.claude/multi-agent-preferences.json}"
26
+
27
+ # The scope values this accepts, spelled once. `host-session` is NOT here and
28
+ # must never be added: it would mean rewriting the host's base URL, which routes
29
+ # the user's whole session - work that has nothing to do with this pipeline
30
+ # included - through a third layer, breaks the subscription's auth model, and
31
+ # silently changes which model answers. The schema encodes the same list so a
32
+ # hand-edited preferences file is rejected too, rather than only this entry point.
33
+ ALLOWED_SCOPE="subagent bulk-read research"
34
+
35
+ die() { echo "route-state: $1" >&2; exit "${2:-3}"; }
36
+
37
+ # A missing `jq` must not look like routing being off. Every read below would
38
+ # come back empty, `route-status` would report disabled, and a user with rules
39
+ # armed would be told there are none.
40
+ command -v jq >/dev/null 2>&1 || die "jq not found - cannot read or write preferences (install: brew install jq)"
41
+ [ -f "$PREFS" ] || die "preferences not found at $PREFS - run /multi-agent:setup first"
42
+ jq -e . "$PREFS" >/dev/null 2>&1 || die "$PREFS is not valid JSON - refusing to write over it"
43
+
44
+ ACTION="${1:-status}"; shift 2>/dev/null || true
45
+
46
+ write_prefs() {
47
+ local filter="$1"; shift
48
+ local tmp
49
+ tmp=$(mktemp) || die "mktemp failed"
50
+ if ! jq "$@" "$filter" "$PREFS" > "$tmp"; then rm -f "$tmp"; die "jq failed - preferences left untouched"; fi
51
+ jq -e . "$tmp" >/dev/null 2>&1 || { rm -f "$tmp"; die "produced invalid JSON - preferences left untouched"; }
52
+ mv "$tmp" "$PREFS"
53
+ }
54
+
55
+ case "$ACTION" in
56
+ status)
57
+ ENABLED=$(jq -r '.global.modelRouting.enabled // false' "$PREFS")
58
+ STRATEGY=$(jq -r '.global.modelRouting.strategy // "manual"' "$PREFS")
59
+ SCOPE=$(jq -r '(.global.modelRouting.scope // ["subagent"]) | join(", ")' "$PREFS")
60
+ NRULES=$(jq -r '(.global.modelRouting.rules // []) | length' "$PREFS")
61
+ CEIL=$(jq -r '.global.modelRouting.budgetCeilingUsd // "none"' "$PREFS")
62
+ REC=$(jq -r '.global.modelRouting.recordDecisions // true' "$PREFS")
63
+
64
+ echo "routing: $ENABLED"
65
+ echo "strategy: $STRATEGY"
66
+ echo "scope: $SCOPE"
67
+ echo "rules: $NRULES"
68
+ echo "ceiling: $CEIL"
69
+ echo "decisions: $REC"
70
+ echo ""
71
+
72
+ if [ "$ENABLED" != "true" ]; then
73
+ echo "Disabled. Rules are stored but read by nothing; dispatch is unchanged."
74
+ [ "$NRULES" -gt 0 ] && echo "route-on turns these $NRULES rule(s) back on without re-asking."
75
+ exit 0
76
+ fi
77
+
78
+ if [ "$NRULES" -eq 0 ]; then
79
+ echo "Armed with no rules. Nothing matches, so nothing is routed - this is a"
80
+ echo "configuration state, not an error."
81
+ else
82
+ jq -r '(.global.modelRouting.rules // [])[] |
83
+ " when " + ([.when | to_entries[] | "\(.key)=\(.value)"] | join(" ")) +
84
+ " -> " + (.prefer | join(" > "))' "$PREFS"
85
+ fi
86
+
87
+ echo ""
88
+ # The limit is printed every time rather than documented once, because the
89
+ # question it answers ("routing is on, why is the reviewer still on Opus")
90
+ # otherwise arrives days later as a bug report.
91
+ echo "Honest limit: on Claude Code a subagent cannot be sent to a non-Anthropic"
92
+ echo "model - subagent dispatch belongs to the host. Phase 1/2/3 personas stay"
93
+ echo "inside the Anthropic ladder whatever the rules say. External providers apply"
94
+ echo "only where this pipeline makes the call itself (bulk-read, research)."
95
+ ;;
96
+
97
+ on)
98
+ STRATEGY="manual"; SCOPE_ARG=""
99
+ for arg in "$@"; do
100
+ case "$arg" in
101
+ --strategy=*) STRATEGY="${arg#*=}" ;;
102
+ --scope=*) SCOPE_ARG="${arg#*=}" ;;
103
+ *) die "unknown option: $arg" 2 ;;
104
+ esac
105
+ done
106
+ case "$STRATEGY" in
107
+ manual|task-fit|cost-ceiling) ;;
108
+ *) die "strategy must be manual, task-fit or cost-ceiling (got '$STRATEGY')" 2 ;;
109
+ esac
110
+
111
+ if [ -n "$SCOPE_ARG" ]; then
112
+ SCOPE_JSON="[]"
113
+ IFS=',' read -r -a parts <<< "$SCOPE_ARG"
114
+ for one in "${parts[@]}"; do
115
+ one="${one// /}"
116
+ # shellcheck disable=SC2076
117
+ case " $ALLOWED_SCOPE " in
118
+ *" $one "*) ;;
119
+ *) die "scope '$one' is not allowed (allowed: $ALLOWED_SCOPE). 'host-session' is refused by design: it would route the whole session, not this pipeline's calls." ;;
120
+ esac
121
+ SCOPE_JSON=$(jq -c --arg s "$one" '. + [$s]' <<< "$SCOPE_JSON")
122
+ done
123
+ write_prefs '
124
+ .global.modelRouting //= {}
125
+ | .global.modelRouting.enabled = true
126
+ | .global.modelRouting.strategy = $st
127
+ | .global.modelRouting.scope = ($sc | fromjson)
128
+ ' --arg st "$STRATEGY" --arg sc "$SCOPE_JSON"
129
+ else
130
+ write_prefs '
131
+ .global.modelRouting //= {}
132
+ | .global.modelRouting.enabled = true
133
+ | .global.modelRouting.strategy = $st
134
+ | .global.modelRouting.scope //= ["subagent"]
135
+ ' --arg st "$STRATEGY"
136
+ fi
137
+ echo "routing enabled (strategy: $STRATEGY)"
138
+ exec "$0" status
139
+ ;;
140
+
141
+ off)
142
+ # Rules survive on purpose. Clearing them here would make route-off a
143
+ # destructive action wearing the name of a toggle.
144
+ write_prefs '.global.modelRouting //= {} | .global.modelRouting.enabled = false'
145
+ echo "routing disabled. Rules kept - route-on restores this configuration as it is."
146
+ ;;
147
+
148
+ set-rules)
149
+ FILE="${1:-}"
150
+ [ -n "$FILE" ] || die "set-rules needs a JSON file containing an array of rules" 2
151
+ [ -f "$FILE" ] || die "no such file: $FILE" 2
152
+ jq -e 'type == "array"' "$FILE" >/dev/null 2>&1 || die "$FILE must contain a JSON ARRAY of rules" 2
153
+ write_prefs '.global.modelRouting //= {} | .global.modelRouting.rules = $r' --slurpfile _ignore /dev/null --argjson r "$(cat "$FILE")"
154
+ echo "rules replaced: $(jq -r 'length' "$FILE")"
155
+ ;;
156
+
157
+ *)
158
+ echo "usage: route-state.sh [status|on|off|set-rules <file>]" >&2
159
+ exit 2
160
+ ;;
161
+ esac
@@ -40,7 +40,7 @@ ma_logs_root() {
40
40
  }
41
41
 
42
42
  # ma_is_run_dir <dir> -> 0 when the directory holds at least one run marker
43
- # Phase 6 removes the worktree and salvages the run's files into `artifacts/`
43
+ # Phase 4 removes the worktree and salvages the run's files into `artifacts/`
44
44
  # inside the same run directory, so a shipped run keeps its state one level
45
45
  # deeper. A reader that only looks at the top level reports it as stateless.
46
46
  MA_ARTIFACTS_SUBDIR=artifacts
@@ -242,7 +242,7 @@ ma_resolve_run_file() {
242
242
  printf '%s\n' "$dir/$filename"
243
243
  return 0
244
244
  fi
245
- # The salvaged copy Phase 6 leaves behind.
245
+ # The salvaged copy Phase 4 leaves behind.
246
246
  if [ -f "$dir/$MA_ARTIFACTS_SUBDIR/$filename" ]; then
247
247
  printf '%s\n' "$dir/$MA_ARTIFACTS_SUBDIR/$filename"
248
248
  return 0
@@ -12,7 +12,7 @@ First step of every `multi-agent` flow **that touches a remote provider**.
12
12
  > question, no token lookup. Detection: `freetext` flow first fetches local
13
13
  > repos (`repo-cache.sh local "$HOME"`); if the picker resolves to local-only
14
14
  > repos, account-picker is bypassed and the state file's `accountId` is left
15
- > `null` with `tokens={}`. Phases 6/7 read these as "local-only" signals.
15
+ > `null` with `tokens={}`. Phases 4/5 read these as "local-only" signals.
16
16
 
17
17
  > **Language**: see `picker-contract.md` + `rules.md` Language Application matrix.
18
18
 
@@ -70,7 +70,7 @@ Selects extra repos the pipeline may touch beyond the primary repo(s) - typica
70
70
  is not written here is a candidate the check can never see.
71
71
 
72
72
  Resolve each entry's `stack` from its local checkout, with the marker table
73
- in `phases/phase-1-analysis.md` Step 2 (`.xcodeproj` / `Package.swift` →
73
+ in `phases/phase-1-plan.md` Step 2 (`.xcodeproj` / `Package.swift` →
74
74
  `ios`, `build.gradle(.kts)` → `android`, and so on). No checkout, or no
75
75
  marker matched → `stack: "unknown"` and `root: null`. Never infer a stack
76
76
  from the repo NAME: `my-app-android` is a naming convention, not a marker,
@@ -35,7 +35,7 @@ The top-level `multi-agent` command classifies user arguments per the rules belo
35
35
 
36
36
  ¹ Account picker is **skipped** when the resolved primary repo is local-only
37
37
  (`provider="local"`). No token lookup, no provider auth - the pipeline
38
- proceeds with `accountId=null` and Phases 6/7 honor the local-only mode.
38
+ proceeds with `accountId=null` and Phases 4/5 honor the local-only mode.
39
39
 
40
40
  ² Free-text flow merges `github`+`bitbucket`+`local` repo caches into a
41
41
  single picker list; `(local)` rows come from `repo-cache.sh local "$HOME"`.
@@ -225,12 +225,3 @@ The tier boundary is not advisory. A signal row that reaches Section 4 (business
225
225
 
226
226
  Per source: reachable and answered, reachable and empty, or unreachable. All three are recorded; only the third produces a `fetchErrors[]` entry, and none of them halts.
227
227
 
228
- **Lite mode auto-detection (Locked 25, v9.1.0 scoring)**: at the end of Phase 1c, evaluate three signals and score each:
229
-
230
- | Signal | True condition | Score |
231
- |---|---|---|
232
- | Confluence spec body lines | < 100 | 1 |
233
- | Figma frames count | <= 1 | 1 |
234
- | Repo evidence direct-match count | >= 8 | 1 |
235
-
236
- `liteModeAuto = (totalScore >= 2)`. Two of three signals true is enough; the v8.12.0..v9.0.x AND-threshold (all three) forced too many small features into Full mode when one signal was marginal (e.g. a tiny spec with 2 Figma frames). User overrides via `--full` or `--lite` always win over scoring.
@@ -1,6 +1,6 @@
1
1
  # Intake (analysis Phase 0)
2
2
 
3
- > The picker chain that fills `state.analysisSpec.*` before any fetch runs. Loaded by `/multi-agent:analysis`. Pipeline Phase 1 does NOT load this file: in a pipeline run the account, project, repos and task already came from the orchestrator's own Phase 0, and only the source and coverage batches below are asked (see `phase-1-analysis.md` Step 4).
3
+ > The picker chain that fills `state.analysisSpec.*` before any fetch runs. Loaded by `/multi-agent:analysis`. Pipeline Phase 1 does NOT load this file: in a pipeline run the account, project, repos and task already came from the orchestrator's own Phase 0, and only the source and coverage batches below are asked (see `phase-1-plan.md` Step 4).
4
4
 
5
5
  ### Phase 0 - Intake
6
6
 
@@ -1,25 +1,25 @@
1
- # Locked decisions (37)
1
+ # Locked decisions (36)
2
2
 
3
- > The 37 Locked decisions of the analysis flow. Loaded by `/multi-agent:analysis`, by `/multi-agent:analysis-resolve` (which inherits them) and by pipeline Phase 1 when it runs the analysis engine. Numbering is canonical: cite as `Locked <n> (<short label>)`.
3
+ > The 36 Locked decisions of the analysis flow. Loaded by `/multi-agent:analysis`, by `/multi-agent:analysis-resolve` (which inherits them) and by pipeline Phase 1 when it runs the analysis engine. Numbering is canonical: cite as `Locked <n> (<short label>)`.
4
4
 
5
5
  ### Index by category (v9.1.0+)
6
6
 
7
- Browse-friendly grouping of the 37 Locked decisions. Numbering stays canonical (matches the list below); the index is read-only navigation.
7
+ Browse-friendly grouping of the 36 Locked decisions. Numbering stays canonical (matches the list below); the index is read-only navigation.
8
8
 
9
9
  | Category | Decisions | Concern |
10
10
  |---|---|---|
11
- | **A. Governance** | 1, 5, 6, 7, 10, 26, 27, 32, 36 | Run-level process rules: one feature per run, default output, auto-commit ban, punctuation policy, output picker timing, Pass B preview, evidence digest cache, analysis profile, document reviewed before publish |
12
- | **B. Citation and Evidence** | 3, 4, 8, 11, 24, 30, 34 | Every fact in the doc traces back to a source: citation discipline, forward-looking spec, standards binding, repo-evidence reuse-first, Pass B footnote mandatory, analysis self-contained (pipeline-wide), references built from the evidence record |
13
- | **C. Output Format and Structure** | 2, 9, 13, 14, 16, 17, 20, 21, 25, 33, 35, 37 | How the document is laid out: section omission rule, per-platform output split, Gherkin user stories, Goals + Non-Goals paired, Files-to-Add tag, API response variants exhaustive, localization mode (ownership-aware), References at the bottom, Lite mode, corporate backbone always renders, stack-optional render, redesign records v1 before planning v2 |
11
+ | **A. Governance** | 1, 5, 6, 7, 10, 25, 26, 31, 35 | Run-level process rules: one feature per run, default output, auto-commit ban, punctuation policy, output picker timing, Pass B preview, evidence digest cache, analysis profile, document reviewed before publish |
12
+ | **B. Citation and Evidence** | 3, 4, 8, 11, 24, 29, 33 | Every fact in the doc traces back to a source: citation discipline, forward-looking spec, standards binding, repo-evidence reuse-first, Pass B footnote mandatory, analysis self-contained (pipeline-wide), references built from the evidence record |
13
+ | **C. Output Format and Structure** | 2, 9, 13, 14, 16, 17, 20, 21, 32, 34, 36 | How the document is laid out: section omission rule, per-platform output split, Gherkin user stories, Goals + Non-Goals paired, Files-to-Add tag, API response variants exhaustive, localization mode (ownership-aware), References at the bottom, corporate backbone always renders, stack-optional render, redesign records v1 before planning v2 |
14
14
  | **D. Design Source and Pipeline Architecture** | 12, 22, 23 | Where design comes from and how the pipeline renders: Figma 3-tier access (BLOCKING), platform-agnostic template + Pass B render, convention extraction (Phase 1c) |
15
- | **E. UI, Variant, and Test Coverage** | 15, 18, 19, 28, 29, 31 | UI artefact rules: SVG default for new assets, screenshots embedded, all Figma variants drilled, SwiftUI Preview block (iOS), variant usage explicit, business-rule to acceptance-criterion to test traceability |
15
+ | **E. UI, Variant, and Test Coverage** | 15, 18, 19, 27, 28, 30 | UI artefact rules: SVG default for new assets, screenshots embedded, all Figma variants drilled, SwiftUI Preview block (iOS), variant usage explicit, business-rule to acceptance-criterion to test traceability |
16
16
 
17
17
  When citing a Locked decision in code or docs, prefer `Locked <n> (<short label>)` form so the category is inferable (e.g. `Locked 30 (analysis self-contained, category B)`).
18
18
 
19
19
  ### Full list
20
20
 
21
21
  1. **One feature per run.** Every Figma URL, Confluence page, Jira ID, and Standards source the user supplies belongs to the **same feature**. The command never asks "which feature is this for?" or "which URL is primary?". Mixed inputs covering multiple features are treated as user error: surface the conflict, stop, and ask the user to split into separate runs.
22
- 2. **Section omission rule.** Sections with zero evidence are dropped entirely; no `TBD` placeholder section. Numbering remains sequential `1..N` over the rendered set.
22
+ 2. **Section omission rule.** Sections with zero evidence are dropped entirely; no `TBD` placeholder section. **A rendered section keeps its canonical template number**, so the set has gaps and that is correct: `1, 2, 4, 9, 13, 14, 21` is a valid rendered document. This clause used to say numbering re-flows sequentially `1..N`, which could not hold alongside Locked 30 - that decision threads ids across sections BY NUMBER (`Section 15.1 scenario`, `Section 4.4`, `Section 3/5 layout cells`), and a re-flowed document sends every one of those cross-references to the wrong section or to nothing. No emitted document ever re-flowed; the rule was the part that was wrong. What IS checked: every rendered number is a real template number, and the numbers ascend without repeating.
23
23
  3. **Citation discipline.** Every quoted UI string, endpoint path, error code, or analytics event name in Sections 2-4 must carry an inline citation: `[Figma annotation <nodeId>]` for copy taken from a Dev Mode annotation, `[Figma <nodeId>]` (MCP) or `[figma-export: <project-slug>/<screen-slug>:<nodeId>]` (local source) for design strings; `file:line` for repo evidence; `Confluence:<pageId>:<heading-slug>` for spec text. **Annotation-as-copy precedence:** when the project's `figma-config` has `annotations.enabled` and a node carries a Dev Mode annotation, that annotation is the authoritative copy for the node and the visible text layer is treated as a placeholder; cite the annotation, not the layer text. Never invent copy - blank beats a guess; a node whose annotation has the base language but is missing a target language emits a Section 20 (Risks) row rather than a fabricated value. Uncited quotes are downgraded to `[label TBD - see Open Questions]` and a row is added to Section 7 Risks. Subagent prose and Code Connect snippets are not citations.
24
24
  4. **The spec is forward-looking.** Section bodies describe the new feature as drawn / specified. Findings that exist only in legacy code or the existing branch appear as `> Legacy reference: <text> (file:line)` blockquotes inside the relevant section, never as the lead sentence or a primary table row. A legacy-only finding with no forward counterpart goes to Section 7 Risks as a decision item: "current code does X; should the new feature keep, change, or drop this?".
25
25
  5. **Output default = Local file.** The Phase 3.5 output picker keeps `Local file` pre-selected. Confluence and Jira outputs are never default-selected (see `analysis-output-confluence-on-request` memory).
@@ -44,17 +44,16 @@ When citing a Locked decision in code or docs, prefer `Locked <n> (<short label>
44
44
  22. **Platform-agnostic template + Pass B render.** The template (`$HOME/.claude/multi-agent-refs/analysis-template.md`) describes concepts (state holder, view, navigator, use case, repository, DTO, state model, DI register, localization key, accessibility identifier, test method) without platform-specific class names. Pass B (Phase 2b) projects each concept onto the selected platform using conventions extracted at Phase 1c.
45
45
  23. **Convention extraction mandatory (Phase 1c).** After Phase 1b repo evidence, Phase 1c extracts seven pattern groups (folder structure, class naming, UI state model, test method naming, accessibility identifier, localization key, DI registration) per selected repo via `~/.claude/lib/extract-conventions.sh`. Output lands in `state.analysisSpec.evidence.conventions[<repo>]` with confidence levels (high / medium / low / none) and evidence file citations.
46
46
  24. **Pass B cell footnote mandatory.** Every cell Pass B fills in Section 13 (Architecture Plan concept table) and any other per-platform projection carries a footnote of the form `^[<convention-key> <confidence>: <evidence-source>]` pointing back to Phase 1c output. Cells with `confidence: low | none` cite `conventions-defaults.md:C<n>-<platform>` and emit a row in Section 20 Risks ("convention fallback applied"). Footnote-less cells fail the dispatch gate.
47
- 25. **Lite mode for small features.** When Phase 1 signals indicate a small feature, Lite mode auto-activates and renders only Sections 1, 2, 4, 9, 13, 14, 21, plus optional 23 Changelog. The user can force Lite with `--lite` or force Full with `--full`. Full mode is the default for new feature analyses. **Scoring (v9.1.0+):** three independent signals - `confluenceSpecLines < 100`, `figmaFramesCount <= 1`, `repoDirectMatchCount >= 8`. Each true signal scores 1 point; Lite auto-activates at `score >= 2`. Previous AND-threshold (`v8.12.0..v9.0.x`) was too strict and forced small features into Full mode when one signal was just over the line. Explicit user flags (`--lite` / `--full`) always win over auto-scoring.
48
- 26. **Pass B preview before render.** Phase 2a presents the resolved convention table to the user before Phase 2b emits any platform file. The user can approve, override individual cells, or cancel. Empty answers do not imply consent (`feedback_no-inferred-defaults-from-empty-answer`); the picker re-asks on empty submit.
49
- 27. **Evidence digest caches Phase 1b and 1c.** `evidence_digest = sha256(featureName || sorted(platforms) || repoEvidence.summary || conventions.summary)`. When the same feature name is invoked again against the same set of repos and the digest matches, Phase 1b and 1c are skipped and the cached `evidence.repoEvidence` / `evidence.conventions` is reused. Cache TTL is 24 hours; manual invalidation via `--no-cache` flag.
50
- 28. **SwiftUI Preview block mandatory (iOS projection, SwiftUI only).** When the iOS file is produced AND the affected view is a SwiftUI view (detected via `import SwiftUI` + `: View` protocol conformance in `evidence.repoEvidence[<repo>].buckets.uiComponents`), Section 13.6 renders a Preview block table covering at minimum: canonical default (LTR Light), Dark, RTL, Dynamic Type accessibilityLarge, and one error variant. Loading state and edge-case variants are added when distinct from canonical. UIKit-only features (no SwiftUI view artefact) drop Section 13.6 with note `(N/A: UIKit-only feature)`. Preview macro convention (`#Preview` for Swift 5.9+ vs legacy `PreviewProvider`) is read from `evidence.conventions[<repo>].previewMacro`. Each Preview variant listed in Section 13.6 must have a matching row in Section 15.2 Snapshot Tests; a Preview without a snapshot row triggers a Section 20 Risk.
51
- 29. **Variant usage explicit and bounded.** Section 6 inventory rows list which variants this feature consumes per component (concrete enum case + bool value). New Section 6.X (Variant Usage Matrix) catalogues the full variant axis vs. used subset with a rationale per excluded variant. Sections 13.6 (Preview) and 15.2 (Snapshot) cover only the used subset; expanding the variant set requires updating Section 6.X first.
52
- 30. **Analysis as self-contained design bridge - no MCP outside analysis phase (BLOCKING, pipeline-wide).** The analysis document is the sole design source for every downstream phase. After Phase 1 of `/multi-agent:analysis` produces `analysis/<feature>-<platform>.md`, Phase 2 Planning, Phase 3 Dev, Phase 4 Review, Phase 5 Test, Phase 6 Commit, and Phase 7 Report consume only the analysis document plus repo Code Connect mappings (`*.figma.swift` / `*.figma.kt`). Calling `mcp__claude_ai_Figma__*`, hitting `api.figma.com`, or fetching a `figma.com/design/...` URL during Phase 2+ is a violation. Applies to every mode that runs Phase 2+: `/multi-agent`, `/multi-agent:autopilot`, `/multi-agent:local`, `/multi-agent:local-autopilot`, at either depth. Hard requirement (v9.0.0): Phase 2 Pre-item and Phase 3 Pre-item (BLOCKING) abort the run when the analysis document is missing. Memory: `[[mcp-only-in-analysis]]`. Generic rule rationale and access matrix: see `$HOME/.claude/rules/figma-pipeline.md` "MUST: No MCP outside analysis phase".
53
- 31. **Business-rule to acceptance-criterion to test traceability (AI + human spine).** The analysis is a development handoff that both an AI implementer and a human reviewer must act on, so it is bound by one shared-ID vocabulary. Every business rule carries a stable id `BR-<slug>-NN` (Section 4.4). Each rule maps to at least one acceptance criterion written Given / When / Then (binary - two readers must not be able to disagree on pass/fail). Each acceptance criterion maps to unit-test scenarios in Section 15.1, one row per case across happy / boundary / error / empty-nil (enumerate at least the failure modes; agents hallucinate error handling when it is omitted). The same ids thread onward: Section 15.6 UI-test flows reference the `BR-` ids and use stable selectors (accessibilityIdentifier / testTag), Section 16 accessibility items reuse those identifiers, Section 11 analytics events cite their triggering rule or story, and Section 5/7 layout cells carry token + Figma node refs. Never invent copy or values (blank beats a guess; a missing source becomes a Section 20 Open Question). **Mode-aware gate:** in Full mode a business rule with no acceptance criterion, or an acceptance criterion with no Section 15.1 scenario, fails the dispatch gate. In **Lite mode Section 15 is not rendered**, so the rule-to-test half does not apply - Section 4.4 still lists each rule with its Given/When/Then acceptance criterion (the acceptance criterion is itself the testable statement), and the 15.1 mapping is deferred to whenever the feature is later analyzed in Full or implemented by a dev run. The rule-to-acceptance-criterion half always holds, in both modes.
54
-
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
- 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
- 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. **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.
59
- 36. **The document is reviewed before it is published.** An analysis run used to go from draft straight to dispatch behind a deterministic validator, so nothing read what it was about to publish: one run put a channel it never searched for, an open question about a frame it never opened, and twenty-three unowned `EKLENECEK` markers onto a live page. Every one is what a reader catches on the first pass. Phase 3.2 runs `phases/phase-4-review.md` Step 0 (strict validator, the host's three-reviewer set, triage) on the draft before the destination is chosen: a finding is cheap while nothing is written. Reviewers are subagents holding `analysis/review.md`, never the context that wrote the document, which cannot notice a search it never thought to run. A blocking finding returns to Phase 2b with dispatch closed and never becomes an open question, since "the document is wrong" is not something to ask the reader; capped at two returns. Phase 3.3 sorts every remaining gap into searched-and-closed, asked-and-answered, or `AS-NN`; an unstamped gap fails the dispatch gate. Autopilot runs both; only the asking degrades, into rows stamped `autopilot: could not ask`.
60
- 37. **A redesign records v1 before it plans v2.** `options.redesign` is an opt-in on the `options.uiTests` axis, never a third `mode` value: `mode` says how many sections, `redesign` says which content, and a `mode: redesign` would switch off the Full-mode traceability, Test Plan and rule-to-test gates in exactly the documents that need them. It adds three sub-sections and no top-level section: current behaviour with `CB-<slug>-NN` ids and `repo/file:line` citations, the v1 to v2 endpoint mapping, and a difference list over a closed status vocabulary. Every `Missing` and `Partial` owes a Section 20 row by `AS-NN`, and a `CB-` id in one table but not the other fails in both directions - a behaviour that is in the code and on nobody's difference list is what a redesign loses and production finds. Evidence and certainty are derived from `repoEvidence`, never graded by the writer (Locked 24, same reason), and `options.redesign` is an `evidence_digest` input. Contract and the eight checks: `analysis/redesign.md`, loaded only on a redesign run.
47
+ 25. **Pass B preview before render.** Phase 2a presents the resolved convention table to the user before Phase 2b emits any platform file. The user can approve, override individual cells, or cancel. Empty answers do not imply consent (`feedback_no-inferred-defaults-from-empty-answer`); the picker re-asks on empty submit.
48
+ 26. **Evidence digest caches Phase 1b and 1c.** `evidence_digest = sha256(featureName || sorted(platforms) || repoEvidence.summary || conventions.summary)`. When the same feature name is invoked again against the same set of repos and the digest matches, Phase 1b and 1c are skipped and the cached `evidence.repoEvidence` / `evidence.conventions` is reused. Cache TTL is 24 hours; manual invalidation via `--no-cache` flag.
49
+ 27. **SwiftUI Preview block mandatory (iOS projection, SwiftUI only).** When the iOS file is produced AND the affected view is a SwiftUI view (detected via `import SwiftUI` + `: View` protocol conformance in `evidence.repoEvidence[<repo>].buckets.uiComponents`), Section 13.6 renders a Preview block table covering at minimum: canonical default (LTR Light), Dark, RTL, Dynamic Type accessibilityLarge, and one error variant. Loading state and edge-case variants are added when distinct from canonical. UIKit-only features (no SwiftUI view artefact) drop Section 13.6 with note `(N/A: UIKit-only feature)`. Preview macro convention (`#Preview` for Swift 5.9+ vs legacy `PreviewProvider`) is read from `evidence.conventions[<repo>].previewMacro`. Each Preview variant listed in Section 13.6 must have a matching row in Section 15.2 Snapshot Tests; a Preview without a snapshot row triggers a Section 20 Risk.
50
+ 28. **Variant usage explicit and bounded.** Section 6 inventory rows list which variants this feature consumes per component (concrete enum case + bool value). New Section 6.X (Variant Usage Matrix) catalogues the full variant axis vs. used subset with a rationale per excluded variant. Sections 13.6 (Preview) and 15.2 (Snapshot) cover only the used subset; expanding the variant set requires updating Section 6.X first.
51
+ 29. **Analysis as self-contained design bridge - no MCP outside analysis phase (BLOCKING, pipeline-wide).** The analysis document is the sole design source for every downstream phase. After Phase 1 of `/multi-agent:analysis` produces `analysis/<feature>-<platform>.md`, Phase 1 Plan, Phase 2 Dev, Phase 3 Review, Phase 3 Review, Phase 4 Commit, and Phase 5 Report consume only the analysis document plus repo Code Connect mappings (`*.figma.swift` / `*.figma.kt`). Calling `mcp__claude_ai_Figma__*`, hitting `api.figma.com`, or fetching a `figma.com/design/...` URL during Phase 2+ is a violation. Applies to every mode that runs Phase 2+: `/multi-agent`, `/multi-agent:autopilot`, `/multi-agent:local`, `/multi-agent:local-autopilot`, at either depth. Hard requirement (v9.0.0): Phase 2 Pre-item and Phase 3 Pre-item (BLOCKING) abort the run when the analysis document is missing. Memory: `[[mcp-only-in-analysis]]`. Generic rule rationale and access matrix: see `$HOME/.claude/rules/figma-pipeline.md` "MUST: No MCP outside analysis phase".
52
+ 30. **Business-rule to acceptance-criterion to test traceability (AI + human spine).** The analysis is a development handoff that both an AI implementer and a human reviewer must act on, so it is bound by one shared-ID vocabulary. Every business rule carries a stable id `BR-<slug>-NN` (Section 4.4). Each rule maps to at least one acceptance criterion written Given / When / Then (binary - two readers must not be able to disagree on pass/fail). Each acceptance criterion maps to unit-test scenarios in Section 15.1, one row per case across happy / boundary / error / empty-nil (enumerate at least the failure modes; agents hallucinate error handling when it is omitted). The same ids thread onward: Section 15.6 UI-test flows reference the `BR-` ids and use stable selectors (accessibilityIdentifier / testTag), Section 16 accessibility items reuse those identifiers, Section 11 analytics events cite their triggering rule or story, and Section 3/5 layout cells carry token + Figma node refs. Never invent copy or values (blank beats a guess; a missing source becomes a Section 20 Open Question). **Gate:** a business rule with no acceptance criterion, or an acceptance criterion with no Section 15.1 scenario, fails the dispatch gate. There is no mode clause: the rule used to be suspended in Lite mode, which deferred the rule-to-test half to "whenever the feature is later analyzed in Full" - a debt nothing tracked and nothing ever paid. Section 15 renders when there is evidence for it and is dropped when there is not, like every other section, and the gate applies to whatever was rendered.
53
+
54
+ 31. **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.
55
+ 32. **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.
56
+ 33. **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.
57
+ 34. **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.
58
+ 35. **The document is reviewed before it is published.** An analysis run used to go from draft straight to dispatch behind a deterministic validator, so nothing read what it was about to publish: one run put a channel it never searched for, an open question about a frame it never opened, and twenty-three unowned `EKLENECEK` markers onto a live page. Every one is what a reader catches on the first pass. Phase 3.2 runs `phases/phase-3-review.md` Step 0 (strict validator, the host's three-reviewer set, triage) on the draft before the destination is chosen: a finding is cheap while nothing is written. Reviewers are subagents holding `analysis/review.md`, never the context that wrote the document, which cannot notice a search it never thought to run. A blocking finding returns to Phase 2b with dispatch closed and never becomes an open question, since "the document is wrong" is not something to ask the reader; capped at two returns. Phase 3.3 sorts every remaining gap into searched-and-closed, asked-and-answered, or `AS-NN`; an unstamped gap fails the dispatch gate. Autopilot runs both; only the asking degrades, into rows stamped `autopilot: could not ask`.
59
+ 36. **A redesign records v1 before it plans v2.** `options.redesign` is an opt-in on the `options.uiTests` axis, never a third `mode` value: `mode` says how many sections, `redesign` says which content, and a `mode: redesign` would switch off the Full-mode traceability, Test Plan and rule-to-test gates in exactly the documents that need them. It adds three sub-sections and no top-level section: current behaviour with `CB-<slug>-NN` ids and `repo/file:line` citations, the v1 to v2 endpoint mapping, and a difference list over a closed status vocabulary. Every `Missing` and `Partial` owes a Section 20 row by `AS-NN`, and a `CB-` id in one table but not the other fails in both directions - a behaviour that is in the code and on nobody's difference list is what a redesign loses and production finds. Evidence and certainty are derived from `repoEvidence`, never graded by the writer (Locked 24, same reason), and `options.redesign` is an `evidence_digest` input. Contract and the eight checks: `analysis/redesign.md`, loaded only on a redesign run.
@@ -46,7 +46,7 @@
46
46
 
47
47
  Locked 36 carries the rule. Operationally:
48
48
 
49
- 1. Run `phases/phase-4-review.md` Step 0, the analysis-mode branch. It already defines
49
+ 1. Run `phases/phase-3-review.md` Step 0, the analysis-mode branch. It already defines
50
50
  the strict validator, the host's three-reviewer set with its model routing, and the
51
51
  triage. Do not restate it here; a second definition is the one that rots.
52
52
  2. Dispatch reviewers as subagents, each given the draft path, the state JSON and
@@ -52,8 +52,8 @@ Convention preview - Pass B will render with:
52
52
  | DI | UserProfileDependencyConfigurator ^[C7 high] | UserProfileModule (Hilt) ^[C7 fallback: defaults] |
53
53
 
54
54
  Confidence summary:
55
- iOS: 7/7 high, 0 medium, 0 low, 0 fallback
56
- Android: 5/7 high, 1 medium, 0 low, 1 fallback
55
+ iOS: 5/5 high, 0 medium, 0 low, 0 fallback
56
+ Android: 3/5 high, 1 medium, 0 low, 1 fallback
57
57
  ```
58
58
 
59
59
  AskUserQuestion shape:
@@ -86,11 +86,17 @@ For each `platform` in `state.analysisSpec.platforms[]`:
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
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
- 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
- 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>`, `a11y_depth: <state.analysisSpec.options.a11yDepth | basic>` and `redesign: <state.analysisSpec.options.redesign | false>` 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, 4.5 / 4.6 / 9.5 present when redesign). `status` is written only by `/multi-agent:analysis-resolve`; a rendered document is a draft.
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; their numbers are canonical and never re-flowed.
90
+ 3. **Resolve the section set from evidence, not from a mode.** There is one
91
+ pipeline. A section renders when it has evidence and is dropped when it does
92
+ not, per Locked 2. Lite mode used to answer this question with a fixed list
93
+ (1, 2, 4, 9, 13, 14, 21 plus optional 23) chosen by a three-signal score, and
94
+ that list fought Locked 2 in both directions: a small feature with rich
95
+ business rules lost Section 15 because it was not on the list, and a feature
96
+ with no API contract kept Section 9 because it was. Evidence decides now.
97
+ 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), plus `ui_tests: <state.analysisSpec.options.uiTests | false>`, `a11y_depth: <state.analysisSpec.options.a11yDepth | basic>` and `redesign: <state.analysisSpec.options.redesign | false>` 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, 4.5 / 4.6 / 9.5 present when redesign). `status` is written only by `/multi-agent:analysis-resolve`; a rendered document is a draft.
92
98
  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.
93
- 6. **Concatenate non-null sections in canonical order.** Numbering stays sequential `1..N` over the rendered set (omitted sections do not create gaps).
99
+ 6. **Concatenate non-null sections in canonical order.** Each rendered section keeps its canonical template number, so omitted sections DO leave gaps - `1, 2, 4, 9, 13, 14, 21` is a correct rendered document. Locked 30 cites sections by number across the whole document; re-flowing them would break every one of those references.
94
100
  7. **Schema validation** on the per-platform spec object:
95
101
  ```bash
96
102
  python3 -c "import json,jsonschema; jsonschema.validate(json.load(open('state/<feature>-<platform>.json')), json.load(open('$HOME/.claude/schemas/analysis-spec.schema.json')))"
@@ -14,7 +14,7 @@
14
14
  - [Compliance Rules (maps to multi-agent-toolkit MCP audit tools)](#compliance-rules-maps-to-multi-agent-toolkit-mcp-audit-tools)
15
15
  <!-- /toc -->
16
16
 
17
- > **MUST: Figma MCP-first (BLOCKING).** If the task references any Figma frame (URL, node ID, or "from the design"), the Dev phase MUST call `mcp__claude_ai_Figma__get_design_context` for every frame BEFORE writing a single Composable line. Use the `CodeConnectSnippet` component name verbatim - no sound-alike substitutions. Authentication failure is not a skip path. Full rule, trigger conditions, and gate failure modes: `$HOME/.claude/rules/figma-pipeline.md` "MUST: Figma MCP-first (BLOCKING)". Phase wiring: `$HOME/.claude/multi-agent-refs/phases/phase-3-dev.md` "MUST: Figma MCP-first (BLOCKING pre-step)".
17
+ > **MUST: Figma MCP-first (BLOCKING).** If the task references any Figma frame (URL, node ID, or "from the design"), the Dev phase MUST call `mcp__claude_ai_Figma__get_design_context` for every frame BEFORE writing a single Composable line. Use the `CodeConnectSnippet` component name verbatim - no sound-alike substitutions. Authentication failure is not a skip path. Full rule, trigger conditions, and gate failure modes: `$HOME/.claude/rules/figma-pipeline.md` "MUST: Figma MCP-first (BLOCKING)". Phase wiring: `$HOME/.claude/multi-agent-refs/phases/phase-2-dev.md` "MUST: Figma MCP-first (BLOCKING pre-step)".
18
18
 
19
19
  When the task involves creating an Android UI component (Jetpack Compose), follow this architecture.
20
20