@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
@@ -34,7 +34,7 @@ Apply platform default + open Section 20 risk row
34
34
  | iOS | feature-first + clean arch | `Features/<Feature>/Sources/<Feature>/{Common,Data,Domain,Presentation}/` | Mirrors SwiftPM module boundaries; keeps each feature self-contained |
35
35
  | Android | feature module + clean arch | `feature/<feature>/src/main/java/<package>/{data,domain,ui}/` | Standard Now-in-Android pattern, aligns with Gradle module isolation |
36
36
  | Backend | layered | `src/{api,services,repositories,schemas,models}/<feature>/` | Standard FastAPI / Express layering, predictable for new contributors |
37
- | Frontend | feature folder | `src/features/<feature>/{components,hooks,api,types}/` | Standard Next.js / React feature-folder convention |
37
+ | Web | feature folder | `src/features/<feature>/{components,hooks,api,types}/` | Standard Next.js / React feature-folder convention |
38
38
 
39
39
  ## C2 - Class Naming
40
40
 
@@ -45,7 +45,7 @@ Apply platform default + open Section 20 risk row
45
45
  | iOS | `<Feature>ViewModel` | `PassengerFlightViewModel` |
46
46
  | Android | `<Feature>ViewModel` | `PassengerFlightViewModel` |
47
47
  | Backend | not applicable | (services replace state holders) |
48
- | Frontend | `use<Feature>` hook | `usePassengerFlight` |
48
+ | Web | `use<Feature>` hook | `usePassengerFlight` |
49
49
 
50
50
  ### C2b viewNaming
51
51
 
@@ -54,7 +54,7 @@ Apply platform default + open Section 20 risk row
54
54
  | iOS | `<Feature>View` | `PassengerFlightView` |
55
55
  | Android | `<Feature>Screen` | `PassengerFlightScreen` |
56
56
  | Backend | not applicable | |
57
- | Frontend | `<Feature>Page` | `PassengerFlightPage` |
57
+ | Web | `<Feature>Page` | `PassengerFlightPage` |
58
58
 
59
59
  ### C2c navigatorNaming
60
60
 
@@ -63,7 +63,7 @@ Apply platform default + open Section 20 risk row
63
63
  | iOS | `<Feature>Coordinator` | `PassengerFlightCoordinator` | Conforms to `<Domain>Router` protocol if present |
64
64
  | Android | `<Feature>Navigator` | `PassengerFlightNavigator` | Compose Navigation destination |
65
65
  | Backend | not applicable | | |
66
- | Frontend | route file under `app/` | `app/passenger-flight/page.tsx` | Next.js App Router default |
66
+ | Web | route file under `app/` | `app/passenger-flight/page.tsx` | Next.js App Router default |
67
67
 
68
68
  ### C2d useCaseNaming
69
69
 
@@ -72,7 +72,7 @@ Apply platform default + open Section 20 risk row
72
72
  | iOS | `<Verb><Feature>UseCase` | `FetchPassengerFlightUseCase` |
73
73
  | Android | `<Verb><Feature>UseCase` | `FetchPassengerFlightUseCase` |
74
74
  | Backend | `<Feature>Service` | `PassengerFlightService` |
75
- | Frontend | hook with side effect | `usePassengerFlightQuery` |
75
+ | Web | hook with side effect | `usePassengerFlightQuery` |
76
76
 
77
77
  ### C2e repositoryNaming
78
78
 
@@ -81,7 +81,7 @@ Apply platform default + open Section 20 risk row
81
81
  | iOS | `<Feature>Repository` (protocol) + `<Feature>RepositoryImpl` (class) | `PassengerFlightRepository`, `PassengerFlightRepositoryImpl` |
82
82
  | Android | `<Feature>Repository` (interface) + `<Feature>RepositoryImpl` (class) | same |
83
83
  | Backend | `<Feature>Repository` | `PassengerFlightRepository` |
84
- | Frontend | `<feature>Api` | `passengerFlightApi` |
84
+ | Web | `<feature>Api` | `passengerFlightApi` |
85
85
 
86
86
  ### C2f dtoNaming
87
87
 
@@ -90,7 +90,7 @@ Apply platform default + open Section 20 risk row
90
90
  | iOS | `<Feature>RequestDTO`, `<Feature>ResponseDTO` | `PassengerFlightRequestDTO`, `PassengerFlightResponseDTO` |
91
91
  | Android | `<Feature>Dto` | `PassengerFlightDto` |
92
92
  | Backend | Pydantic `<Feature>In`, `<Feature>Out` | `PassengerFlightIn`, `PassengerFlightOut` |
93
- | Frontend | TypeScript `<Feature>Dto` | `PassengerFlightDto` |
93
+ | Web | TypeScript `<Feature>Dto` | `PassengerFlightDto` |
94
94
 
95
95
  ## C3 - UI State Model
96
96
 
@@ -99,7 +99,7 @@ Apply platform default + open Section 20 risk row
99
99
  | iOS | sealed enum | `enum PassengerFlightUIState { case loading, idle(...), error(...) }` |
100
100
  | Android | sealed interface | `sealed interface PassengerFlightUiState { data object Loading; data class Idle(...); data class Error(...) }` |
101
101
  | Backend | not applicable | services do not hold UI state |
102
- | Frontend | discriminated union | `type PassengerFlightState = { kind: 'loading' } | { kind: 'idle'; ... } | { kind: 'error'; ... }` |
102
+ | Web | discriminated union | `type PassengerFlightState = { kind: 'loading' } | { kind: 'idle'; ... } | { kind: 'error'; ... }` |
103
103
 
104
104
  ## C4 - Test Method Naming
105
105
 
@@ -109,7 +109,7 @@ Apply platform default + open Section 20 risk row
109
109
  | iOS XCTest fallback | `test<Scenario>_<Expected>()` | `testFetch_ValidInput_ReturnsIdle()` |
110
110
  | Android JUnit5 | `fun <funName>_<scenario>_<expectedBehavior>()` | `fun fetch_validInput_returnsIdle()` |
111
111
  | Backend pytest | `def test_<scenario>_<expected>():` | `def test_fetch_valid_input_returns_idle():` |
112
- | Frontend Vitest | `it('<does X> when <condition>', ...)` | `it('returns idle when input is valid', ...)` |
112
+ | Web Vitest | `it('<does X> when <condition>', ...)` | `it('returns idle when input is valid', ...)` |
113
113
 
114
114
  ## C5 - Accessibility Identifier
115
115
 
@@ -118,7 +118,7 @@ Apply platform default + open Section 20 risk row
118
118
  | iOS | dot notation `<feature>.<element>` | `passengerFlight.continueButton` | XCUITest query syntax friendliness |
119
119
  | Android | camelCase testTag | `passengerFlightContinueButton` | Compose testTag convention |
120
120
  | Backend | OpenAPI operationId camelCase | `fetchPassengerFlight` | Code generation friendly |
121
- | Frontend | kebab data-testid `<feature>-<element>` | `passenger-flight-continue-button` | DOM attribute readability |
121
+ | Web | kebab data-testid `<feature>-<element>` | `passenger-flight-continue-button` | DOM attribute readability |
122
122
 
123
123
  ## C6 - Localization Key
124
124
 
@@ -127,7 +127,7 @@ Apply platform default + open Section 20 risk row
127
127
  | iOS | hierarchical dot PascalCase | `PassengerFlight.ContinueButton` | Matches `Localizable.xcstrings` convention |
128
128
  | Android | flat snake_case | `passenger_flight_continue_button` | Matches `strings.xml` convention |
129
129
  | Backend | not applicable (server-side messages live in error code tables) | | |
130
- | Frontend | hierarchical dot camelCase | `passengerFlight.continueButton` | i18next + ICU MessageFormat default |
130
+ | Web | hierarchical dot camelCase | `passengerFlight.continueButton` | i18next + ICU MessageFormat default |
131
131
 
132
132
  ## C8 - SwiftUI Preview macro (iOS only)
133
133
 
@@ -137,7 +137,7 @@ Apply platform default + open Section 20 risk row
137
137
  | iOS legacy | `PreviewProvider` struct | `struct FooView_Previews: PreviewProvider { static var previews: some View { FooView(viewModel: .preview(.idle)) } }` | Pre-Swift 5.9 fallback |
138
138
  | Android | not applicable | (Compose `@Preview` is handled under C3 / C4 conventions, no separate field) | |
139
139
  | Backend | not applicable | | |
140
- | Frontend | not applicable | (Storybook stories live in `.stories.tsx` files, tracked under C2 conventions) | |
140
+ | Web | not applicable | (Storybook stories live in `.stories.tsx` files, tracked under C2 conventions) | |
141
141
 
142
142
  Detection: scan up to 10 SwiftUI view files in the candidate set; majority pick wins. Confidence `high` if 5+ matching examples, `medium` if 3-4, `low` if 2, `none` if 0 SwiftUI views found (Pass B then omits Section 13.6 with `(N/A: UIKit-only feature)` note per Locked 29).
143
143
 
@@ -148,7 +148,7 @@ Detection: scan up to 10 SwiftUI view files in the candidate set; majority pick
148
148
  | iOS | Manual configurator | `<Domain>DependencyConfigurator.register<Feature>()` | Avoids framework lock-in; testable |
149
149
  | Android | Hilt module | `@Module @InstallIn(SingletonComponent::class) class <Feature>Module` | Standard Android DI |
150
150
  | Backend FastAPI | `Depends(get_<feature>_service)` | `service: PassengerFlightService = Depends(get_passenger_flight_service)` | Native FastAPI pattern |
151
- | Frontend | hook factory | `usePassengerFlightApi()` returns a memoized client | No framework DI needed |
151
+ | Web | hook factory | `usePassengerFlightApi()` returns a memoized client | No framework DI needed |
152
152
 
153
153
  ## Risk row template (Section 20)
154
154
 
@@ -41,7 +41,7 @@ Nothing enabled is not an error: a repo whose stack was never selected legitimat
41
41
  no toolkit. Record the no-op with the enabled set that was read, so "none applied" is
42
42
  distinguishable from "never looked".
43
43
 
44
- **Not enabled is not an error here**, unlike component dispatch: a repo whose stack was never selected legitimately has no toolkit, and halting would make the pipeline unusable there. Record the no-op and continue. (This paragraph used to say "a backend or web repo legitimately has no toolkit" - that stopped being true when the frontend and backend toolkits shipped, and the sentence outlived the fact by several releases.)
44
+ **Not enabled is not an error here**, unlike component dispatch: a repo whose stack was never selected legitimately has no toolkit, and halting would make the pipeline unusable there. Record the no-op and continue. (This paragraph used to say "a backend or web repo legitimately has no toolkit" - that stopped being true when the web and backend toolkits shipped, and the sentence outlived the fact by several releases.)
45
45
 
46
46
  Two marketplaces may ship the same toolkit name (a public one and a corporate one). Resolve whichever is enabled and record its **name and version** in the ledger entry, because the routing table and the skill set differ between versions - a finding that cites a skill has to be traceable to the version that defined it.
47
47
 
@@ -69,7 +69,7 @@ Bu is icin hangi pipeline? / Which pipeline for this task?
69
69
  2. Kisa / Short Dev (self-contained, Opus) -> Review -> Test -> Commit -> Report
70
70
  ```
71
71
 
72
- `bugfix` and `chore` recommend Short; `feature`, `refactor` and `component` recommend Full. The recommendation is presented first and passed as `ASK_CHOICE_DEFAULT` on hosts without a native picker, because `ask-choice.sh` picks the first option on a non-TTY and option order is not a contract.
72
+ `bugfix` and `chore` recommend Short; `feature`, `refactor` and `component` recommend Full. The recommendation is presented first and passed as `ASK_CHOICE_DEFAULT` on hosts without a native picker, because `ask-choice.sh` picks the first option on a non-TTY and option order is not a contract. Pass it as the **1-based index** (Full = 1, Short = 2), not as the label: labels render in `outputLanguage`, so a label-valued default matches nothing on a `tr` run.
73
73
 
74
74
  **Who is asked.** `/multi-agent` and `/multi-agent:local`. Both autopilot entries always run Full without asking.
75
75
 
@@ -35,7 +35,7 @@ Read preferences: `PREFS_FILE="$HOME/.claude/multi-agent-preferences.json"` (if
35
35
  OUTPUT_LANG=$(jq -r '.global.outputLanguage // "en"' "$PREFS_FILE" 2>/dev/null || echo en)
36
36
  ```
37
37
 
38
- From this point on, everything the user reads renders in `$OUTPUT_LANG`: conversational lines, `AskUserQuestion` `question`/`description`, and external payload bodies (PR/Jira/Confluence). English stays only on `label`/`header`, commit messages, branch names, PR titles, identifiers. Full matrix: `rules.md` "Language Application".
38
+ From this point on, everything the user reads renders in `$OUTPUT_LANG`: conversational lines, `AskUserQuestion` `question`/`label`/`description`, and external payload bodies (PR/Jira/Confluence). English stays only on `header`, commit messages, branch names, PR title prefixes, identifiers. Full matrix: `rules.md` "Language Application".
39
39
 
40
40
  **Model fallback date gate** (same step, once per run): read `prefs.global.modelFallback`. If `premiumTierUntil` is set and in the past, apply the date-gate trigger from `$HOME/.claude/multi-agent-refs/features/model-fallback.md` - `preferredModel` personas dispatch on `fallbackModel` for this run, with the one-line WARN. Dispatch-error and budget triggers in that contract apply per-dispatch later; nothing else to do here.
41
41
 
@@ -270,7 +270,8 @@ Scan `$HOME` (maxdepth 2) for project markers (`.xcodeproj`, `Package.swift`, `b
270
270
  6. Sort: `develop*` first, then `release/*`, then `main`/`master`. Surface through the
271
271
  **native picker** per `picker-contract.md` (`AskUserQuestion` on Claude Code,
272
272
  `ask_choice.sh` on Copilot CLI) - `question` + `description` in `outputLanguage`,
273
- `label` English, the recent branch first and marked `(Recommended)`. The ASCII sketch
273
+ `label` = the branch name verbatim (a proper noun, never translated) with the recent
274
+ branch first and marked `(Recommended)`. The ASCII sketch
274
275
  below is what the options carry, not a menu to print:
275
276
 
276
277
  ```
@@ -550,11 +551,14 @@ Ask the depth question from `$HOME/.claude/multi-agent-refs/phases/modes.md` "Pi
550
551
 
551
552
  When the intake carried an analysis document or a Figma reference, say so **inside** the question: Short skips the only two phases that would turn that document into a task breakdown, and the user should learn that before choosing, not after.
552
553
 
553
- **Pass the default explicitly.** `ask-choice.sh` picks the FIRST option on a non-TTY, so relying on option order breaks the first time someone reorders them for readability, on the host where nobody is watching:
554
+ **Pass the default explicitly, as an index.** `ask-choice.sh` picks the FIRST option on a non-TTY, so relying on option order breaks the first time someone reorders them for readability, on the host where nobody is watching. The default must be the 1-based index, never the label: labels follow `outputLanguage` (`rules.md` matrix), so `ASK_CHOICE_DEFAULT="Full"` matches nothing once the options render as `Tam` / `Kisa`, falls through, and silently takes option 1 on a non-TTY - the exact failure this step exists to prevent.
554
555
 
555
556
  ```bash
556
- ASK_CHOICE_DEFAULT="$DEPTH_RECOMMENDATION" \
557
- $HOME/.claude/lib/ask-choice.sh "Which pipeline for this task?" "Full" "Short"
557
+ # Full is option 1, Short is option 2 (modes.md "Pipeline depth")
558
+ DEPTH_DEFAULT_INDEX=1; [ "$DEPTH_RECOMMENDATION" = "short" ] && DEPTH_DEFAULT_INDEX=2
559
+ ASK_CHOICE_DEFAULT="$DEPTH_DEFAULT_INDEX" \
560
+ $HOME/.claude/lib/ask-choice.sh "<localized: 'Which pipeline for this task?'>" \
561
+ "<localized: 'Full'>" "<localized: 'Short'>"
558
562
  ```
559
563
 
560
564
  **Persist.** Short sets `state.onlyDevelop = true`; Full leaves it `false`. The key is unchanged - only who sets it changed - so every downstream reader keeps working. Short also flips the Phase 1 and Phase 2 tiles to `skipped` (tracker-contract.md, "Late skip"); pre-marking is forbidden.
@@ -583,7 +587,7 @@ Log: `Phase 0 Step 7.6: test baseline = {green|red|unknown} ({N} pre-existing fa
583
587
  3. If `clarityScore >= prefs.clarifyAmbiguous.minScoreToProceed` (default 6) or `stopAndAsk == false` → write `state.clarification` (score + rationale, no questions), proceed to Phase 1 silently.
584
588
  4. If `stopAndAsk == true`:
585
589
 
586
- - **Interactive runs:** render the questions via `AskUserQuestion` (label + header per `$HOME/.claude/multi-agent-refs/rules.md` Language Application matrix; `outputLanguage` for `question` + `option.description`, English for `label` + `header`). Up to `maxQuestions` questions, each with the recommended option flagged. Persist the user's answers under `state.clarification.userAnswers`.
590
+ - **Interactive runs:** render the questions via `AskUserQuestion` (per `$HOME/.claude/multi-agent-refs/rules.md` Language Application matrix; `outputLanguage` for `question` + `option.label` + `option.description`, English for `header`). Up to `maxQuestions` questions, each with the recommended option flagged. Persist the user's answers under `state.clarification.userAnswers`.
587
591
  - **Autopilot runs:** follow `clarifyAmbiguous.autopilotMode`:
588
592
 
589
593
  | Mode | Behavior |
@@ -283,15 +283,16 @@ Then ask for the decision with a **native `AskUserQuestion` picker** (never a ty
283
283
 
284
284
  - `question`: "Do you approve this plan?" (rendered in `outputLanguage`)
285
285
  - `header`: "Plan" (English, <=12 chars)
286
- - `options`:
287
- - `{ label: "Approve", description: "Proceed to development (Phase 3)" }`
288
- - `{ label: "Cancel", description: "Pause the task; resume later with :resume" }`
286
+ - `options` (label + description in `outputLanguage`; the names below are the option
287
+ SEMANTICS, not strings to print):
288
+ - option 1 - "Approve": proceed to development (Phase 3)
289
+ - option 2 - "Cancel": pause the task; resume later with `:resume`
289
290
 
290
- The picker's built-in **Other** field is the free-text edit channel: the user types an edit request there (e.g. "also look at the auth service but keep LoginView out of scope") instead of typing a keyword. (`option.label` stays English per the language matrix; `question` + `description` follow `outputLanguage`.)
291
+ The picker's built-in **Other** field is the free-text edit channel: the user types an edit request there (e.g. "also look at the auth service but keep LoginView out of scope") instead of typing a keyword. Per the `rules.md` matrix `question`, `label` and `description` all follow `outputLanguage`; only `header` stays English. Branch on which option was picked, never on its rendered text.
291
292
 
292
293
  Handle the selection:
293
294
 
294
- - **Approve**:
295
+ - **Option 1 (Approve)**:
295
296
  - Set `state.phases["2"].planApprovedAt = now()`, bump `planIterations` if not yet set (default 1)
296
297
  - Log `🧠 Phase 2: Plan approved (iterations={N}, clarificationRounds={M})`
297
298
  - Proceed to Phase 3
@@ -132,7 +132,7 @@ For each task (respecting dependency order):
132
132
  release_build_lock ;;
133
133
  android) ./gradlew test --tests "{testClass}.{testMethod}" 2>&1 | tail -5 ;;
134
134
  backend) pytest "{test_file}::{test_name}" 2>&1 | tail -5 ;;
135
- frontend) npm test -- --testPathPattern="{file}" 2>&1 | tail -5 ;;
135
+ web) npm test -- --testPathPattern="{file}" 2>&1 | tail -5 ;;
136
136
  esac
137
137
  ```
138
138
  - Must fail for the RIGHT reason (expected assertion, not compilation error)
@@ -156,7 +156,7 @@ For each task (respecting dependency order):
156
156
  - Only if duplication or naming is poor
157
157
  - Re-run tests after refactor → still GREEN
158
158
 
159
- **Target resolution** (auto-detect once per project, cache in `agent-state.json`; ios resolves scheme + simulator, android resolves module + variant, backend/frontend need none):
159
+ **Target resolution** (auto-detect once per project, cache in `agent-state.json`; ios resolves scheme + simulator, android resolves module + variant, backend/web need none):
160
160
  ```bash
161
161
  case "$STACK" in
162
162
  ios) xcodebuild -list -json -project "{projectPath}" 2>/dev/null || xcodebuild -list -json -workspace "{workspacePath}" 2>/dev/null
@@ -170,7 +170,7 @@ For each task (respecting dependency order):
170
170
  - **ios, preferred (MCP, multi-agent-toolkit >= 3.0.0)**: `acquire_build_lock` → `mcp__multi-agent-toolkit__ios_xcodebuild({project|workspace, scheme, configuration: "Release", destination: "generic/platform=iOS", derived_data_path: "{worktreePath}/.DerivedData"})` → `release_build_lock`. Returns one line `Build: SUCCESS|FAILURE (E errors, W warnings) [xcresult-<id>]`; on failure drill in via `mcp__multi-agent-toolkit__ios_xcresult({id, mode: "errors"})`, never dump the full log.
171
171
  - **ios, fallback (raw)**: same lock pair around `xcodebuild build -scheme "{scheme}" -destination "generic/platform=iOS" -derivedDataPath "{worktreePath}/.DerivedData" 2>&1 | tail -5`.
172
172
  - **android**: lock pair around `./gradlew assembleDebug 2>&1 | tail -5` (the Gradle daemon and `build/` outputs contend across parallel worktrees exactly as DerivedData does - the lock applies).
173
- - **backend / frontend**: `python -m compileall .` / `npm run build --if-present 2>&1 | tail -5`; no lock.
173
+ - **backend / web**: `python -m compileall .` / `npm run build --if-present 2>&1 | tail -5`; no lock.
174
174
  5. If build fails → fix → rebuild (max 3 attempts, track `retryCount` in state).
175
175
  6. **Intermediate commit** (after each completed task in the plan):
176
176
  ```bash
@@ -222,7 +222,7 @@ release_build_lock() { rm -rf "$BUILD_LOCK"; }
222
222
  - Phase 4 Step 1 Gate 1 (build gate before review)
223
223
  - Phase 4 Step 1 Gate 3 (test gate before review)
224
224
 
225
- **Android**: same lock discipline - the Gradle daemon and `build/` outputs contend across parallel worktrees. **Backend/frontend** (Python, Node.js): no lock needed - these build/test in parallel without conflicts.
225
+ **Android**: same lock discipline - the Gradle daemon and `build/` outputs contend across parallel worktrees. **Backend/web** (Python, Node.js): no lock needed - these build/test in parallel without conflicts.
226
226
 
227
227
  ---
228
228
 
@@ -90,17 +90,17 @@ Branch **deterministically**, no implicit fallback. Read `agent-state.json` and
90
90
 
91
91
  **Standard path (also used as fallback when instructionDriven=true but file missing):**
92
92
 
93
- 1. **Local checkout test prompt** (skip in autopilot): If still in worktree AND Phase 5 was skipped, ask the user. Per `$HOME/.claude/multi-agent-refs/rules.md` Language Application matrix: `AskUserQuestion.label` and `header` stay English (UI button + chip contract); `question` and `description` follow `outputLanguage`. Render the picker accordingly:
93
+ 1. **Local checkout test prompt** (skip in autopilot): If still in worktree AND Phase 5 was skipped, ask the user. Per `$HOME/.claude/multi-agent-refs/rules.md` Language Application matrix: `question`, `label` and `description` follow `outputLanguage`; only `header` stays English (<=12-char chip). Render the picker accordingly:
94
94
  - `question` (in `outputLanguage`) - semantically: "Run a quick WIP-checkout test before commit?"
95
95
  - `header` (English, ≤12 chars): `"WIP checkout"`
96
- - `options` (description in `outputLanguage`, label in English):
97
- - label: `"No - continue to commit (Recommended)"`, description (in `outputLanguage`): proceed straight to commit + push + PR
98
- - label: `"Yes - WIP checkout"`, description (in `outputLanguage`): pause so the user can checkout the branch locally and poke around
99
- - Default: option 1 (No - continue to commit)
100
- - If user picks "Yes - WIP checkout": follow Phase 5 Steps 2-6. On `"fix:"` → Phase 3. On `"ok"` → resume Phase 6.
96
+ - `options` (label + description both in `outputLanguage`; the semantics below, not these English strings, are what to render):
97
+ - option 1 - semantically "No, continue to commit" marked `(Recommended)`; description: proceed straight to commit + push + PR
98
+ - option 2 - semantically "Yes, WIP checkout"; description: pause so the user can checkout the branch locally and poke around
99
+ - Default: option 1 (by index, not by label - the label is localized)
100
+ - If the user picks option 2 (WIP checkout): follow Phase 5 Steps 2-6. On `"fix:"` → Phase 3. On `"ok"` → resume Phase 6.
101
101
  2. Commit confirm prompt (skip in autopilot). Same language matrix:
102
102
  - `question` in `outputLanguage` (semantic: "Commit now?")
103
- - labels English: `"Commit"`, `"Pause and resume later"`
103
+ - labels in `outputLanguage`, semantically "Commit" and "Pause and resume later"; branch on the option index, not on the rendered string
104
104
  - No → Pause, user can `resume` later. Yes → continue:
105
105
  3. If WIP commit exists from Phase 5 or Step 1: `git reset HEAD~1` to unstage, then re-commit properly
106
106
  4. Stage changes: `git add` with specific files (NOT `git add -A` - avoid sensitive files, `.worktrees/`, agent files)
@@ -10,8 +10,8 @@
10
10
  `ask_choice(question, options[], { header?, default?, allowFreeText? })`
11
11
 
12
12
  - `question` - rendered in `outputLanguage`.
13
- - `options[]` - each `{ label (English), description (outputLanguage) }`.
14
- - `default` - recommended option (label or 1-based index); used by autopilot / non-interactive runs.
13
+ - `options[]` - each `{ label (outputLanguage), description (outputLanguage) }`. A label naming a branch, an account or another proper noun is passed through verbatim rather than translated.
14
+ - `default` - recommended option, given as a **1-based index**; used by autopilot / non-interactive runs. Labels are localized, so a label-valued default resolves differently on `en` and `tr`.
15
15
  - `allowFreeText` - when true, a free-text answer is accepted as an extra channel (e.g. the plan-gate's edit request).
16
16
 
17
17
  Returns the selected option label (or the free-text string when `allowFreeText` and the user typed instead of choosing).
@@ -33,7 +33,7 @@ This is the surface that makes the native picker show, step by step, what it is
33
33
 
34
34
  | Platform | Render |
35
35
  |---|---|
36
- | **Claude Code** | native `AskUserQuestion` (question/description in `outputLanguage`, label English); free-text via the built-in **Other** field |
36
+ | **Claude Code** | native `AskUserQuestion` (question/label/description in `outputLanguage`, `header` English); free-text via the built-in **Other** field, which the host injects in English and no run can localize |
37
37
  | **Copilot CLI** | `pipeline/lib/ask-choice.sh` (numbered menu); a future MCP elicitation tool can replace this once host support is broad |
38
38
 
39
39
  ## Universal fallback: `pipeline/lib/ask-choice.sh`
@@ -45,11 +45,36 @@ choice=$(pipeline/lib/ask-choice.sh "Do you approve this plan?" "Approve" "Cance
45
45
  ```
46
46
 
47
47
  - Prints the menu + prompt on **stderr**; echoes ONLY the chosen label on **stdout**.
48
- - `ASK_CHOICE_DEFAULT=<label|index>` selects without prompting (autopilot / CI).
48
+ - `ASK_CHOICE_DEFAULT=<1-based-index|label>` selects without prompting (autopilot / CI). The script still resolves either, but pipeline callers **must** pass the index: a label is localized copy and stops matching the moment `outputLanguage` changes.
49
49
  - No TTY + no default -> picks the first option and never blocks an automated run.
50
50
 
51
51
  Enforced by `smoke-ask-choice.sh`.
52
52
 
53
+ ## Localized labels: what the caller owns
54
+
55
+ `label` is copy the user reads, so it renders in `outputLanguage` like `question`
56
+ and `description`. Three consequences belong to whoever calls the picker:
57
+
58
+ - **Branch on the choice, not the string.** The returned label is localized, so a
59
+ literal comparison against an English word resolves differently on `en` and `tr`.
60
+ Match on which option was selected and map it back to its canonical English value
61
+ before it reaches `agent-state.json`, a commit message, a branch name or a log line.
62
+ - **Defaults are indices.** Pass `default` and `ASK_CHOICE_DEFAULT` as a 1-based index.
63
+ Both still accept a label, which is exactly the trap: a label-valued default keeps
64
+ working until someone switches `outputLanguage`, then silently matches nothing and
65
+ `ask-choice.sh` falls through to the prompt (or, with no TTY, to option 1).
66
+ - **Proper nouns pass through.** A label naming a branch, an account, a repo or a
67
+ stack id is rendered verbatim, never translated.
68
+
69
+ **Reading a spec file.** Every `label: "..."` written in a phase doc, a ref or a
70
+ command spec is the option's semantics, recorded in English because instruction prose
71
+ is English. It is not the string to print. Render it in `outputLanguage` at call time.
72
+ The same goes for prose that names an option (`If the user picks "Cancel"`): it
73
+ identifies which option, not what the button said.
74
+
75
+ The host injects its own **Other** free-text row in English on every run. Nothing in
76
+ the pipeline can localize it, and no option may depend on its wording.
77
+
53
78
  ## Autopilot / non-interactive contract
54
79
 
55
80
  In autopilot, `ask_choice` resolves to `default` (or the safe first option) without prompting - identical to how the native gates auto-proceed today. A picker is only surfaced for genuinely ambiguous or destructive decisions, matching the maturity-check model.
@@ -24,7 +24,7 @@ Maturity is generic; add these pipeline-readiness dimensions by reading the fetc
24
24
  | Reproduction | Bugs have steps + expected/actual | none (aligns with maturity `no_repro_steps`) |
25
25
  | Design reference | UI work links a Figma frame or attaches a screenshot | UI implied but no design source |
26
26
  | API contract | backend/integration work names endpoints or a Swagger/OpenAPI ref | data/contract implied, none given |
27
- | Platform / stack signal | the target stack is inferable (iOS / Android / frontend / backend) | ambiguous - Phase 1 could not route |
27
+ | Platform / stack signal | the target stack is inferable (iOS / Android / web / backend) | ambiguous - Phase 1 could not route |
28
28
  | Dependencies | blocking deps/links are called out when they exist | hidden dependency likely |
29
29
 
30
30
  Only emit a dimension as a gap when its trigger applies (do not demand an API contract on a copy-change task). Merge: `gaps = maturity.blockers + maturity.warnings + rubric gaps`.
@@ -8,8 +8,8 @@ The pipeline has two language axes. Both MUST be applied from the very first tur
8
8
 
9
9
  | Axis | Source | Used for |
10
10
  |---|---|---|
11
- | `prefs.global.outputLanguage` (default `"en"`) | Read at Phase 0 Step 0 | All assistant-authored conversational text: status updates, findings, phase headers in chat, summaries, error explanations, audit reports, picker `question` + `description` text, anything the user reads outside the structural UI chrome. |
12
- | `prefs.global.promptLanguage` (locked to `"en"`) | Hard-coded `"en"` | `AskUserQuestion` `label` (button text) + `header` (chip), CLI host error UI, internal contract identifiers. |
11
+ | `prefs.global.outputLanguage` (default `"en"`) | Read at Phase 0 Step 0 | All assistant-authored conversational text: status updates, findings, phase headers in chat, summaries, error explanations, audit reports, picker `question` + `label` + `description` text, anything the user reads outside the structural UI chrome. |
12
+ | `prefs.global.promptLanguage` (locked to `"en"`) | Hard-coded `"en"` | `AskUserQuestion` `header` (the <=12-char chip), CLI host error UI, internal contract identifiers. |
13
13
 
14
14
  ### Per-field language matrix (canonical)
15
15
 
@@ -22,7 +22,7 @@ This is the single source of truth. When a contributor or model is unsure where
22
22
  | Phase tracker render labels | English (script chrome) |
23
23
  | `AskUserQuestion.question` | `outputLanguage` |
24
24
  | `AskUserQuestion.options[].description` | `outputLanguage` |
25
- | `AskUserQuestion.options[].label` | English (UI button contract) |
25
+ | `AskUserQuestion.options[].label` | `outputLanguage` |
26
26
  | `AskUserQuestion.header` | English (≤12-char chip) |
27
27
  | Confirmation prompt body (e.g. push y/n) | `outputLanguage` |
28
28
  | GitHub issue comment body | `outputLanguage` |
@@ -41,7 +41,7 @@ This is the single source of truth. When a contributor or model is unsure where
41
41
 
42
42
  1. At the very start of every run (Phase 0 Step 0, before any status output), call `jq -r '.global.outputLanguage // "en"'` on `$HOME/.claude/multi-agent-preferences.json`. Cache as `OUTPUT_LANG` for the session.
43
43
  2. Render every assistant-authored line in `OUTPUT_LANG`. If the user types Turkish but `outputLanguage="en"` is set, follow the pref but suggest `/multi-agent:language tr` once.
44
- 3. `AskUserQuestion` `label` and `header` stay English regardless of `OUTPUT_LANG` (UI contract). `question` and `description` follow `OUTPUT_LANG` - the user reads them as conversational copy, not as button affordances.
44
+ 3. `AskUserQuestion` `question`, `options[].label` and `options[].description` all follow `OUTPUT_LANG`. Only `header` stays English: a <=12-char chip Turkish overflows. Callers branch on which option was picked, never on its literal text, and pass `default` / `ASK_CHOICE_DEFAULT` as a 1-based index. The host's own **Other** row is always English. Caller rules: `picker-contract.md`.
45
45
  4. Always English regardless of either axis: commit messages, PR titles, branch names, code identifiers, agent-state.json values, agent-log.md, reviewer/triage system prompts.
46
46
 
47
47
  **Failure mode this prevents.** Entering `/multi-agent`, `/multi-agent:autopilot`, `/multi-agent:local`, etc. and switching the assistant's conversational text or picker question copy to English while `outputLanguage="tr"` is set. The user sees a half-English half-Turkish dialogue, flagged as a pipeline bug, not a stylistic choice.
@@ -1,6 +1,6 @@
1
- ## Frontend Development Guide
1
+ ## Web Development Guide
2
2
 
3
- When the task involves frontend development (React, Next.js, Vue), follow these patterns.
3
+ When the task involves web development (React, Next.js, Vue), follow these patterns.
4
4
 
5
5
  ### Component Architecture
6
6
 
@@ -15,7 +15,7 @@
15
15
  "properties": {
16
16
  "primary": {
17
17
  "type": "string",
18
- "enum": ["ios", "android", "backend", "frontend", "mobile", "unknown"],
18
+ "enum": ["ios", "android", "web", "backend", "mobile", "frontend", "unknown"],
19
19
  "description": "Dominant stack inferred from project markers (.xcodeproj, build.gradle, package.json, etc.)."
20
20
  },
21
21
  "language": {
@@ -102,7 +102,7 @@
102
102
  "platforms": {
103
103
  "type": "array",
104
104
  "minItems": 1,
105
- "items": { "type": "string", "enum": ["ios", "android", "backend", "frontend"] },
105
+ "items": { "type": "string", "enum": ["ios", "android", "web", "backend", "mobile", "frontend"] },
106
106
  "description": "Platforms selected in Phase 0 step 3 (multi-select)."
107
107
  },
108
108
  "repos": {
@@ -112,7 +112,7 @@
112
112
  "additionalProperties": false,
113
113
  "required": ["platform", "name", "canPush"],
114
114
  "properties": {
115
- "platform": { "type": "string", "enum": ["ios", "android", "backend", "frontend"] },
115
+ "platform": { "type": "string", "enum": ["ios", "android", "web", "backend", "mobile", "frontend"] },
116
116
  "name": { "type": "string", "minLength": 1 },
117
117
  "cloneUrl": { "type": ["string", "null"] },
118
118
  "canPush": { "type": "boolean" }
@@ -417,7 +417,7 @@
417
417
  "type": "array",
418
418
  "items": {
419
419
  "type": "string",
420
- "enum": ["ios", "android", "backend", "frontend"]
420
+ "enum": ["ios", "android", "web", "backend", "mobile", "frontend"]
421
421
  }
422
422
  },
423
423
  "citation": { "type": ["string", "null"] }
@@ -1704,13 +1704,29 @@
1704
1704
  "maxItems": 20,
1705
1705
  "description": "Extra writable repos to offer in the dev-context picker beyond auto-detected submodules (iOS/Android/Backend)."
1706
1706
  },
1707
+ "webRepos": {
1708
+ "type": "array",
1709
+ "items": {
1710
+ "type": "string"
1711
+ },
1712
+ "maxItems": 20,
1713
+ "description": "Web repos for this project, used by /multi-agent:analysis Step 4 (Web is rarely in the iOS/Android submodule tree). Readers fall back to the pre-rename frontendRepos when this is absent."
1714
+ },
1715
+ "webRoots": {
1716
+ "type": "array",
1717
+ "items": {
1718
+ "type": "string"
1719
+ },
1720
+ "maxItems": 20,
1721
+ "description": "Whitelist source roots for the Web repo-evidence scan (overrides the default src/ app/ components/ features/ lib/). Readers fall back to the pre-rename frontendRoots when this is absent."
1722
+ },
1707
1723
  "frontendRepos": {
1708
1724
  "type": "array",
1709
1725
  "items": {
1710
1726
  "type": "string"
1711
1727
  },
1712
1728
  "maxItems": 20,
1713
- "description": "Frontend repos for this project, used by /multi-agent:analysis Step 4 (Frontend is rarely in the iOS/Android submodule tree)."
1729
+ "description": "Deprecated spelling of webRepos, still read so prefs written before the Web rename keep working. Do not write it for new projects."
1714
1730
  },
1715
1731
  "frontendRoots": {
1716
1732
  "type": "array",
@@ -1718,7 +1734,7 @@
1718
1734
  "type": "string"
1719
1735
  },
1720
1736
  "maxItems": 20,
1721
- "description": "Whitelist source roots for the Frontend repo-evidence scan (overrides the default src/ app/ components/ features/ lib/)."
1737
+ "description": "Deprecated spelling of webRoots, still read so prefs written before the Web rename keep working. Do not write it for new projects."
1722
1738
  },
1723
1739
  "appStoreConnect": {
1724
1740
  "type": "object",
@@ -67,7 +67,7 @@ const titles = {
67
67
  pipeline: "Pipeline Orchestration",
68
68
  ios: "iOS / Apple Ecosystem",
69
69
  android: "Android / Kotlin",
70
- web: "Web / Frontend",
70
+ web: "Web",
71
71
  backend: "Backend / API",
72
72
  misc: "Cross-cutting",
73
73
  };
@@ -42,9 +42,13 @@
42
42
 
43
43
  import { readFileSync } from "node:fs";
44
44
 
45
- // "none" is the stack-optional render (Locked 35): a document produced before
46
- // any repo was chosen. It is a real platform value, not a missing one.
47
- const KNOWN_PLATFORMS = new Set(["ios", "android", "backend", "frontend", "none"]);
45
+ // A repo-less run (Locked 35) still splits by channel, derived from the evidence:
46
+ // "mobile" and "web" are its platform values. "none" is the narrower case where the
47
+ // evidence describes no interface at all. All three are real values, not missing ones.
48
+ // "web" is the pre-"web" spelling, still accepted so older documents validate.
49
+ const KNOWN_PLATFORMS = new Set([
50
+ "ios", "android", "web", "backend", "mobile", "frontend", "none",
51
+ ]);
48
52
  const REQUIRED_FM = ["feature", "platform", "language", "mode", "template_version"];
49
53
 
50
54
  // Never-omitted sections (Locked 2), matched by bilingual title keyword so the
@@ -17,7 +17,12 @@
17
17
 
18
18
  import { readFileSync } from "node:fs";
19
19
 
20
- const ALLOWED_STACKS = new Set(["ios", "android", "backend", "frontend", "mobile", "unknown"]);
20
+ // "frontend" is the pre-v16.11.0 spelling of "web". Kept so a stored Phase 1
21
+ // output, a golden-task fixture or a resumed run written before the rename still
22
+ // validates; new output uses "web". Mirrors analysis-output.schema.json.
23
+ const ALLOWED_STACKS = new Set([
24
+ "ios", "android", "backend", "web", "mobile", "frontend", "unknown",
25
+ ]);
21
26
  const ALLOWED_SEVERITIES = new Set(["low", "medium", "high"]);
22
27
 
23
28
  function readInput() {
@@ -100,12 +100,26 @@ async function acquireLock(
100
100
  /* already gone */
101
101
  }
102
102
  if (err.code !== "EEXIST") throw err;
103
- if (isLockStale(lockPath, staleMs)) {
104
- // Holder crashed or the lock outlived any plausible writer - reclaim it.
103
+ // Reclaim by identity, never by path. Between judging a lock stale and
104
+ // deleting it, the holder can release and a THIRD writer can acquire a
105
+ // fresh one - and an unlink by path deletes that live lock, after which
106
+ // two writers hold it and one update is lost. Same failure the PID window
107
+ // above once caused, at a different point in the loop; it survived because
108
+ // it only reproduces under load, where the gap is wide enough to lose.
109
+ //
110
+ // The inode is the identity: a newly acquired lock is a different file
111
+ // even at the same path. Delete only if the file we judged is still the
112
+ // file that is there.
113
+ const stale = lockIdentityIfStale(lockPath, staleMs);
114
+ if (stale) {
105
115
  try {
106
- unlinkSync(lockPath);
116
+ const now = statSync(lockPath);
117
+ if (now.ino === stale.ino && now.mtimeMs === stale.mtimeMs) {
118
+ unlinkSync(lockPath);
119
+ }
120
+ // Changed under us: somebody else's live lock. Leave it and retry.
107
121
  } catch {
108
- /* another writer won the race - fall through and retry */
122
+ /* vanished on its own - fall through and retry */
109
123
  }
110
124
  continue;
111
125
  }
@@ -128,28 +142,32 @@ async function acquireLock(
128
142
  * @param {number} staleMs
129
143
  * @returns {boolean}
130
144
  */
131
- function isLockStale(lockPath, staleMs) {
145
+ function lockIdentityIfStale(lockPath, staleMs) {
132
146
  let pid;
133
147
  let mtimeMs;
148
+ let ino;
134
149
  try {
135
150
  pid = parseInt(readFileSync(lockPath, "utf-8").trim(), 10);
136
- mtimeMs = statSync(lockPath).mtimeMs;
151
+ const st = statSync(lockPath);
152
+ mtimeMs = st.mtimeMs;
153
+ ino = st.ino;
137
154
  } catch {
138
155
  // Lock vanished between EEXIST and our read - let the retry re-open it.
139
- return false;
156
+ return null;
140
157
  }
141
- if (Date.now() - mtimeMs > staleMs) return true;
158
+ const identity = { ino, mtimeMs };
159
+ if (Date.now() - mtimeMs > staleMs) return identity;
142
160
  // An unreadable PID is NOT proof of staleness. Treating it as such is what
143
161
  // let a writer delete a live lock and lose another writer's update. With the
144
162
  // link-based acquire above a lock is never observable without its PID, so
145
163
  // this can only be genuine corruption - which the staleMs check reclaims
146
164
  // anyway, without racing a writer that is merely mid-flight.
147
- if (!Number.isInteger(pid) || pid <= 0) return false;
165
+ if (!Number.isInteger(pid) || pid <= 0) return null;
148
166
  try {
149
167
  process.kill(pid, 0); // probe liveness without signalling
150
- return false; // holder alive
168
+ return null; // holder alive
151
169
  } catch (err) {
152
- return err.code === "ESRCH"; // no such process - stale
170
+ return err.code === "ESRCH" ? identity : null; // no such process - stale
153
171
  }
154
172
  }
155
173