@mmerterden/multi-agent-pipeline 15.6.1 → 15.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -16,6 +16,20 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [15.7.0] - 2026-08-19
20
+
21
+ ### Added
22
+ - **Setup walks missing credentials one by one**: Step 3 gained a strict sequential onboarding loop - fixed service order, one Token Save Flow prompt per service (token -> author -> host), per-service skips that never abort the loop, and `figma` / `figma_mcp` as distinct passes. The discovery summary can no longer end setup.
23
+ - **Per-repo Jira project keys in setup**: the Jira pass closes with an optional multi-select mapping (repo -> project key) written to `prefs.projects[{slug}].jiraProjectKeys`; per-repo keys resolve before `global.defaultJiraKey` everywhere a key is needed. Re-open with `/multi-agent:setup jira-keys`.
24
+ - **Figma MCP mode question**: the `figma_mcp` pass opens with Remote / Local. Remote generates the `figu_` OAuth token for the current user (Dynamic Client Registration + PKCE; `prefs.global.figmaMcp.remoteGeneratorScript` drives the flow when set); Local wires the PAT-based `@anthropic-ai/figma-mcp` server (`localGeneratorScript` supported). Prefs schema: new `global.figmaMcp` block plus `supabase_access` / `supabase_service_role` keychainMapping keys.
25
+
26
+ ### Changed
27
+ - **Help spells out the --dev pipeline**: the Pipeline section now carries the dev chain (Phases 1-2 skipped, Review never skipped, Opus dev). Stale notes claiming Test or Review are skipped in --dev were corrected in both languages.
28
+ - **Setup Step 5 (Repo Discovery) split to a reference**: the full contract moved to `multi-agent-refs/setup/repo-discovery.md`; the SKILL keeps the opt-in prompt and a summary.
29
+
30
+ ### Fixed
31
+ - **build-stack-plugins.mjs dead import**: unused `APPLE_ONLY` import removed (superseded by the `STACK_ONLY` routing table).
32
+
19
33
  ## [15.6.1] - 2026-08-19
20
34
 
21
35
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "15.6.1",
3
+ "version": "15.7.0",
4
4
  "description": "8-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -68,6 +68,10 @@ Pipeline (after Phase 0) - shown as visual cards in terminal:
68
68
 
69
69
  Autopilot always pauses at the Phase 7 channels menu (30-min timeout → session ends cleanly).
70
70
 
71
+ --dev pipeline (Phases 1-2 skipped; Review is NEVER skipped):
72
+ Init (same 8 steps), Dev with Opus, Review (same gates + triage),
73
+ Test (skipped in autopilot / --local), Commit + Report (menu still pauses).
74
+
71
75
  Every step is logged. Error in any phase -> pause -> resume to continue.
72
76
 
73
77
  ------------------------------------------------------------
@@ -80,7 +84,7 @@ Modes:
80
84
  autopilot Skip all confirmations INCLUDING plan gate, auto commit/PR
81
85
  (EXCEPT Phase 7 channels menu - always pauses)
82
86
  --dev autopilot Fastest path
83
- --local --dev Local + fast (no worktree, no analysis/review, no plan gate)
87
+ --local --dev Local + fast (no worktree, no analysis/plan gate - review still runs)
84
88
 
85
89
  Dedicated commands (same as flags above):
86
90
 
@@ -213,8 +217,7 @@ Design Check (mock-mode vs Figma, local-only - powered by dev-toolkit-mcp desi
213
217
  # "Requires a scenario / prefix / launch-arg" is not a reason; reaching those states is the run's job.
214
218
  # When a live screen has no confident Figma match, it SHOWS you the screenshot and asks for the node-id.
215
219
 
216
- dev-toolkit-mcp design_* tools it drives: design_mock_detect, design_scenario_inventory, design_mock_launch,
217
- design_ui_geometry, design_visual_compare, design_report (+ ios_* / android_* device control).
220
+ Drives the dev-toolkit-mcp design_* tool suite (+ ios_* / android_* device control).
218
221
 
219
222
  ------------------------------------------------------------
220
223
 
@@ -330,13 +333,18 @@ Pipeline (Phase 0'dan sonra) - terminalde görsel kart olarak görünür:
330
333
  Phase 3: Dev -> TDD: test -> kod -> build (Sonnet) + build queue
331
334
  Phase 4: Review -> Deterministik kapılar + paralel AI review + Fable triage
332
335
  (Claude Code: Fable + Sonnet · Copilot CLI: GPT-5.4 + Opus + Sonnet)
333
- Phase 5: Test -> Opsiyonel: branch'e geç, Xcode'da test (--dev / autopilot'ta skip)
336
+ Phase 5: Test -> Opsiyonel: branch'e geç, Xcode'da test
337
+ (dev + full'de koşar; her autopilot ve local varyantında skip)
334
338
  Phase 6: Commit -> Commit -> push -> PR + issue body güncelleme (hiç auto-close yok)
335
339
  Phase 7: Report -> Channels dispatcher (PR · Jira · Confluence · Wiki, multi-select)
336
340
  + internal capture (agent-log · knowledge · memory)
337
341
 
338
342
  Autopilot Phase 7'deki channels menüsünde HER ZAMAN durur (30 dk timeout → session temiz biter).
339
343
 
344
+ --dev pipeline'ı (Faz 1-2 atlanır; Review ASLA atlanmaz):
345
+ Init (aynı 8 adım), Dev Opus ile, Review (aynı kapılar + triage),
346
+ Test (autopilot / --local'de atlanır), Commit + Report (menü yine durur).
347
+
340
348
  Her adım loglanır. Herhangi bir fazdaki hata -> pause -> resume ile devam et.
341
349
 
342
350
  ------------------------------------------------------------
@@ -349,7 +357,7 @@ Modlar:
349
357
  autopilot Plan gate dahil tüm onayları atla, otomatik commit/PR
350
358
  (İSTİSNA: Phase 7 channels menüsü - her zaman durur)
351
359
  --dev autopilot En hızlı yol
352
- --local --dev Local + hızlı (worktree yok, analiz/review yok, plan gate yok)
360
+ --local --dev Local + hızlı (worktree yok, analiz/plan gate yok - review yine koşar)
353
361
 
354
362
  Dedicated komutlar (yukarıdaki flag'lerin eşdeğeri):
355
363
 
@@ -483,8 +491,7 @@ Design Check (mock-mod vs Figma, yalnızca lokal - dev-toolkit-mcp design_* to
483
491
  # "Senaryo / prefix / launch-arg gerektirir" gerekçe değildir; o state'lere ulaşmak koşunun işidir.
484
492
  # Canlı ekranın Figma karşılığı kesin bulunamazsa SANA screenshot'ı gösterip node-id sorar.
485
493
 
486
- Sürdüğü dev-toolkit-mcp design_* tool'ları: design_mock_detect, design_scenario_inventory, design_mock_launch,
487
- design_ui_geometry, design_visual_compare, design_report (+ ios_* / android_* cihaz kontrolü).
494
+ dev-toolkit-mcp design_* tool takımını sürer (+ ios_* / android_* cihaz kontrolü).
488
495
 
489
496
  ------------------------------------------------------------
490
497
 
@@ -247,7 +247,7 @@ Security notes to surface to the user: the token is **write-only** (append-only
247
247
 
248
248
  ### Step 3 - Interactive token onboarding (for missing tokens)
249
249
 
250
- Git identities are NOT asked as a separate step. They are learned when tokens are added in Step 4 (Token Save Flow). Each token save triggers an identity prompt:
250
+ Git identities are NOT asked as a separate step. They are learned when tokens are added via the Token Save Flow below. Each token save triggers an identity prompt:
251
251
 
252
252
  ```
253
253
  Token saved. Which git identity uses this token?
@@ -269,6 +269,30 @@ Token saved. Which git identity uses this token?
269
269
 
270
270
  For each MISSING service, run the **Token Save Flow** below. When creating new tokens, use the **standard key name** from Step 1b table.
271
271
 
272
+ #### Sequential onboarding loop (strict)
273
+
274
+ The Step 2 discovery summary is never the end of setup. Immediately after showing it, walk the missing services ONE BY ONE in this fixed order and run the Token Save Flow for each:
275
+
276
+ 1. `jira`
277
+ 2. `bitbucket_token` (then `bitbucket_user` right after, if missing)
278
+ 3. `github`
279
+ 4. `confluence`
280
+ 5. `figma` - Figma Personal Access Token (Tier 2, REST)
281
+ 6. `figma_mcp` - Figma MCP auth (Tier 1); its own prompt, never folded into the PAT prompt
282
+ 7. `fortify`
283
+ 8. `graylog`
284
+ 9. `firebase`
285
+ 10. `jenkins`
286
+ 11. App Store Connect block (Step 3b; only when the target repo is iOS)
287
+
288
+ Loop rules:
289
+
290
+ - **One prompt per service.** Never collapse the list into a single "add missing tokens now?" yes/no, and never offer one multi-select covering all services: each service has its own source URL, key name, identity binding and host prompt, so each gets its own Token Save Flow pass.
291
+ - **Each service pass asks, in order: token → author → host.** Step A/B collect the secret, Step C binds the git identity (author) - Bitbucket and GitHub included; every saved token gets an author - and Step 3.5 asks the host domain inline for hosted services (jira, confluence, bitbucket, fortify, graylog). GitHub and Figma skip the host question (fixed domains); `bitbucket_user` skips author binding (username, not a PAT). Never end a service pass with the token saved but the author or a required host unasked.
292
+ - **Skip is per-service.** A Skip answer leaves that service's `keychainMapping` at `null` and the loop continues with the next service. Skips never abort the loop.
293
+ - **`figma` and `figma_mcp` are distinct credentials for distinct tiers - ask both.** Skipping the PAT does not imply skipping MCP, and vice versa. The `figma_mcp` pass opens with a Remote / Local mode question instead of a clipboard paste (see "Figma MCP Setup" below): Remote generates a `figu_` OAuth token for the current user, Local wires the PAT-based local server.
294
+ - The loop ends only when every missing service has been asked exactly once; then continue to Step 4 (Verify all).
295
+
272
296
  ---
273
297
 
274
298
  ### Token Save Flow (reusable)
@@ -424,6 +448,23 @@ Default Jira project key (e.g. PROJ): ___
424
448
 
425
449
  Save to `prefs.global.defaultJiraKey`. From Jira host, derive `corpDomain` (e.g. `jira.example.com` → `example.com`) and pre-fill it so the Confluence / Bitbucket prompts only need the subdomain.
426
450
 
451
+ `defaultJiraKey` stays a single global fallback, but one Jira host usually serves several repos with DIFFERENT project keys, so the Jira pass continues with a per-repo key mapping (multi-select, optional):
452
+
453
+ ```
454
+ Per-repo Jira keys (optional) - repos whose project key differs from {defaultJiraKey}:
455
+
456
+ [ ] 1. my-ios-app (current: PROJA)
457
+ [ ] 2. my-other-app (current: - )
458
+ ...
459
+
460
+ Toggle repos, then enter the project key for each selected repo. Enter to skip.
461
+ ```
462
+
463
+ - The repo list comes from `prefs.projects` + `recentProjects` (when Step 5 already ran); a free-text repo path is accepted for repos not discovered yet.
464
+ - Each answer is PREPENDED to `prefs.projects[{slug}].jiraProjectKeys` (deduped, max 10 per schema). Example: `my-ios-app` → `PROJA`, `my-other-app` → `PROJB` - two repos on the same Jira host, two different project keys.
465
+ - Resolution order everywhere a Jira key is needed (placeholder replacement, branch names, new-issue creation): `prefs.projects[{slug}].jiraProjectKeys[0]` first, `global.defaultJiraKey` as fallback.
466
+ - **Re-run / update**: `/multi-agent:setup jira-keys` re-opens only this mapping without touching tokens or hosts.
467
+
427
468
  Resulting shape:
428
469
 
429
470
  ```json
@@ -512,11 +553,8 @@ If still missing, show which ones and let user skip (pipeline will re-ask for an
512
553
 
513
554
  ### Step 5 - Repo Discovery (optional)
514
555
 
515
- Pre-populates the Phase 0 project picker. Without this, every task types the repo path by hand; with this, the picker shows a curated list of repos the user has been active in.
516
-
517
- Three sources merge into one flat list. Each source is independent - any one failing does NOT block the others.
556
+ Pre-populates the Phase 0 project picker with repos the user is active in. Three independent sources merge into one flat list: local `$HOME` scan (depth 3, repo markers), Bitbucket (last 90 days, token-gated), GitHub (owner + collaborator, push in last 90 days, token-gated). Any source failing skips that source only, is reported (never swallowed), and discovery is never destructive and idempotent.
518
557
 
519
- **Prompt:**
520
558
  ```
521
559
  Step 5 - Repo Discovery (optional)
522
560
  Scan for recent repos now to pre-populate the Phase 0 picker?
@@ -526,103 +564,7 @@ Scan for recent repos now to pre-populate the Phase 0 picker?
526
564
  n - skip (Phase 0 will prompt for repo path manually)
527
565
  ```
528
566
 
529
- **7a. Local scan** - `$HOME` depth-3, repo markers
530
-
531
- ```bash
532
- find "$HOME" -maxdepth 3 -type d -name ".git" 2>/dev/null | while read gitdir; do
533
- repo_dir="$(dirname "$gitdir")"
534
- origin="$(git -C "$repo_dir" config --get remote.origin.url 2>/dev/null)"
535
- last="$(git -C "$repo_dir" log -1 --format=%cI 2>/dev/null)"
536
- printf '%s\t%s\t%s\n' "$repo_dir" "$origin" "$last"
537
- done
538
- ```
539
-
540
- Auxiliary markers (recorded but not required): `*.xcworkspace`, `*.xcodeproj`, `Podfile`, `Package.swift`, `package.json`, `pom.xml`, `build.gradle`. Their presence tags the repo as iOS/macOS, Cocoapods, SPM, Node, Maven, Gradle in the picker.
541
-
542
- **7b. Bitbucket scan** - last 90 days active
543
-
544
- Runs only if `keychainMapping.bitbucket_token` is non-null AND `serviceStatus.bitbucket.ok === true` (or cache older than `settings.serviceStatusCacheSeconds`, re-ping first).
545
-
546
- Bitbucket Data Center (self-hosted) - the host comes from `prefs.global.bitbucketHost` (or prompts once if unset; NEVER hardcoded):
547
- ```bash
548
- BB_HOST="$(jq -r '.global.bitbucketHost // empty' "$PREFS_FILE")"
549
- BB_USER="$(~/.claude/lib/credential-store.sh get "$(jq -r '.global.keychainMapping.bitbucket_user' "$PREFS_FILE")" 2>/dev/null)"
550
- BB_TOK="$(~/.claude/lib/credential-store.sh get "$(jq -r '.global.keychainMapping.bitbucket_token' "$PREFS_FILE")" 2>/dev/null)"
551
- CUTOFF_MS=$(( ( $(date +%s) - 7776000 ) * 1000 )) # 90 days
552
- curl -sfS -u "$BB_USER:$BB_TOK" \
553
- "https://${BB_HOST}/rest/api/1.0/repos?permission=REPO_WRITE&limit=200" \
554
- | jq -r --argjson cutoff "$CUTOFF_MS" '
555
- .values[] | select((.updatedDate // 0) >= $cutoff) |
556
- { slug: "\(.project.key)/\(.slug)", clone: (.links.clone[] | select(.name=="http") | .href) }'
557
- ```
558
-
559
- Bitbucket Cloud - swap to `https://api.bitbucket.org/2.0/repositories/{workspace}?role=member&sort=-updated_on`.
560
-
561
- **7c. GitHub scan** - owner + collaborator, push in last 90 days
562
-
563
- Runs only if `keychainMapping.github` is non-null AND `serviceStatus.github.ok === true`.
564
-
565
- ```bash
566
- GH_TOK="$(~/.claude/lib/credential-store.sh get "$(jq -r '.global.keychainMapping.github' "$PREFS_FILE")" 2>/dev/null)"
567
- CUTOFF_ISO="$(date -u -v-90d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '90 days ago' +%Y-%m-%dT%H:%M:%SZ)"
568
- # gh CLI path (preferred when available)
569
- gh api --paginate 'user/repos?affiliation=owner,collaborator&sort=pushed&per_page=100' \
570
- -q ".[] | select(.pushed_at > \"$CUTOFF_ISO\") | {full: .full_name, clone: .clone_url}"
571
- # Fallback without gh:
572
- curl -sfS -H "Authorization: Bearer $GH_TOK" \
573
- 'https://api.github.com/user/repos?affiliation=owner,collaborator&sort=pushed&per_page=100'
574
- ```
575
-
576
- **7d. Merge by origin URL**
577
-
578
- Normalize clone URLs (strip `git@`, `https://`, trailing `.git`) to a canonical `host/owner/repo` key. Deduplicate:
579
- - Local + remote match on canonical key → single entry with `path = <local>`, `platform = <remote source>`.
580
- - Remote-only match → entry with `path: null` (user clones on first use from Phase 0).
581
- - Local-only (no remote clone URL matches) → kept as-is, `platform: "local"`.
582
-
583
- **7e. Multi-select picker**
584
-
585
- ```
586
- Discovered 24 active repos (local + remote ≤ 90 days):
587
-
588
- [ ] 1. Booking ~/dev/Booking bitbucket iOS · SPM
589
- [ ] 2. UIComponents ~/dev/UIComponents bitbucket iOS · SPM
590
- [ ] 3. multi-agent-pipeline ~/multi-agent-pipeline github Node
591
- [ ] 4. dotfiles (not cloned) github -
592
- [ ] 5. backend-services ~/work/backend-services bitbucket Maven
593
- ...
594
-
595
- Toggle with numbers (e.g. "1 3 4"), 'all', 'none', or Enter to skip.
596
- Selected repos become defaults in the Phase 0 project picker.
597
- ```
598
-
599
- **7f. Save to preferences**
600
-
601
- ```json
602
- {
603
- "global": {
604
- "recentProjects": [
605
- {"path": "~/dev/Booking", "label": "Booking", "lastUsed": "2026-04-15T21:08:00Z"},
606
- {"path": "~/dev/UIComponents", "label": "UIComponents", "lastUsed": "2026-04-15T21:08:00Z"},
607
- {"path": "~/multi-agent-pipeline", "label": "multi-agent-pipeline","lastUsed": "2026-04-15T21:08:00Z"}
608
- ],
609
- "serviceStatus": {
610
- "bitbucket": {"ok": true, "checkedAt": "2026-04-15T21:08:00Z"},
611
- "github": {"ok": true, "checkedAt": "2026-04-15T21:08:00Z"}
612
- }
613
- }
614
- }
615
- ```
616
-
617
- Entries are objects (not bare strings). The schema `recentProjects.items` is a `oneOf` - both shapes validate, but v2.1.0 writes objects so `lastUsed` + `label` are first-class.
618
-
619
- **7g. Skip / degrade rules**
620
-
621
- - Source failure (network, VPN, 401, rate-limit) skips that source; the others still run. Failure is recorded in `serviceStatus.{service} = { ok: false, checkedAt: <now>, reason: <short> }` so Phase 0 can also read that cache.
622
- - **Report the skip, do not swallow it.** Setup is where the user is actively configuring things, so a source that could not be reached is exactly what they need to hear about, and the reason decides their next move: a 401 means refresh that token, no response at all on a corporate host means connect the VPN, and a missing `global.hosts.<service>` means the token has nowhere to point. Print one line per skipped source with its classification (see `refs/keychain.md` Rule 2) before moving on. A discovery pass that quietly returned three repos out of forty reads as "you only have three repos".
623
- - If all three sources yield zero hits, Step 5 is a no-op - the picker in Phase 0 falls back to manual path entry.
624
- - Discovery is **never destructive**: it never modifies git state, never writes outside `PREFS_FILE`.
625
- - Step 5 is idempotent. Re-running it refreshes `recentProjects` and `serviceStatus` but preserves user-curated entries unless explicitly deselected.
567
+ The scan commands, merge-by-origin dedup, the multi-select picker, the `recentProjects` / `serviceStatus` prefs shapes and the skip / degrade rules live in `$HOME/.claude/multi-agent-refs/setup/repo-discovery.md` - read that file when the user opts in.
626
568
 
627
569
  ### Step 6 - Remember as Multi-Repo Group (optional)
628
570
 
@@ -801,23 +743,33 @@ For maximum stdin hygiene (the secret never lands on argv / shell history), use
801
743
  pbpaste | ~/.claude/scripts/keychain.py set "<SERVICE_NAME>" -
802
744
  ```
803
745
 
804
- ### Figma MCP Setup (optional)
746
+ ### Figma MCP Setup (Remote / Local question)
747
+
748
+ The `figma_mcp` pass of the Step 3 loop opens with a mode question BEFORE any token is collected:
805
749
 
806
- Figma MCP works in two modes:
750
+ ```
751
+ Figma MCP mode?
807
752
 
808
- **Remote MCP** (Claude Code built-in):
809
- - Automatic - comes as `claude_ai_Figma` in Claude Code settings.json
810
- - If token renewal needed: re-auth from within Claude Code
753
+ [1] Remote - mcp.figma.com, OAuth token (figu_) (recommended)
754
+ [2] Local - @anthropic-ai/figma-mcp via npx, uses the Figma PAT
755
+ ```
811
756
 
812
- **Local MCP** (@anthropic-ai/figma-mcp package, with PAT):
757
+ **Remote** (`figu_` OAuth token - generate it FOR the current user, never just point at docs):
758
+ - No OAuth app and no client_id/secret needed: Dynamic Client Registration against `api.figma.com/v1/oauth/mcp/register` + PKCE, the browser opens, the user clicks Allow, and the resulting access + refresh token pair is saved to the credential store under a name the user confirms. Map that entry to `keychainMapping.figma_mcp` (Token Save Flow Step D applies as usual).
759
+ - If `prefs.global.figmaMcp.remoteGeneratorScript` is set, run that script - it drives the whole flow end to end (registration, browser, keychain save) for the current user. Otherwise guide the flow inline.
760
+ - Renewal is silent afterwards via `$HOME/.claude/lib/figma-mcp-refresh.sh` (refresh token). Never echo token values.
761
+
762
+ **Local** (PAT-based):
763
+ - Validates the Figma PAT against the REST API, saves it to the credential store (reusing the `figma` mapping when present), and writes the `@anthropic-ai/figma-mcp` server block into Claude Code `settings.json` `mcpServers`:
813
764
  ```bash
814
- # 1. Save PAT to keychain (stdin sentinel keeps the value out of shell history)
765
+ # stdin sentinel keeps the value out of shell history
815
766
  pbpaste | ~/.claude/scripts/keychain.py set "${USER}_Figma_Access_Token" -
816
767
  pbcopy < /dev/null
817
-
818
- # 2. Add MCP to Claude Code settings.json
819
- # ~/.claude/settings.json -> mcpServers -> figma
768
+ # then: ~/.claude/settings.json -> mcpServers -> figma
820
769
  ```
770
+ - If `prefs.global.figmaMcp.localGeneratorScript` is set, run that script instead.
771
+
772
+ Choosing Remote does NOT skip the `figma` PAT pass (Tier 2 still uses the PAT); choosing Local leaves `keychainMapping.figma_mcp` null and Tier 1 unavailable until Remote is onboarded.
821
773
 
822
774
  ### Security Rules
823
775
 
@@ -850,7 +802,7 @@ To set up multi-agent on a new machine:
850
802
  /multi-agent "bug description"
851
803
  ```
852
804
 
853
- All tokens are optional - pipeline will ask for any needed token at Phase 0. Setup only does pre-preparation.
805
+ All tokens are optional in the sense that every service can be answered with Skip - but the ASKING is not optional: the Step 3 sequential loop still walks every missing service one by one (token author → host). Phase 0 re-asks at runtime only for tokens the user skipped here.
854
806
 
855
807
  ### Step 8 - Enforcement hook (optional, Claude Code)
856
808
 
@@ -0,0 +1,116 @@
1
+ # Setup Step 5 - Repo Discovery (full contract)
2
+
3
+ > Split out of `setup/SKILL.md` (token budget). The SKILL keeps the opt-in prompt and a summary; this file carries the scan commands, merge rules, picker, prefs shapes and skip / degrade rules. Read it when the user opts into repo discovery.
4
+
5
+ Pre-populates the Phase 0 project picker. Without this, every task types the repo path by hand; with this, the picker shows a curated list of repos the user has been active in.
6
+
7
+ Three sources merge into one flat list. Each source is independent - any one failing does NOT block the others.
8
+
9
+ **Prompt:**
10
+ ```
11
+ Step 5 - Repo Discovery (optional)
12
+ Scan for recent repos now to pre-populate the Phase 0 picker?
13
+
14
+ y - all three (local + Bitbucket + GitHub, ~10-30s)
15
+ l - local scan only (fastest, no network)
16
+ n - skip (Phase 0 will prompt for repo path manually)
17
+ ```
18
+
19
+ **7a. Local scan** - `$HOME` depth-3, repo markers
20
+
21
+ ```bash
22
+ find "$HOME" -maxdepth 3 -type d -name ".git" 2>/dev/null | while read gitdir; do
23
+ repo_dir="$(dirname "$gitdir")"
24
+ origin="$(git -C "$repo_dir" config --get remote.origin.url 2>/dev/null)"
25
+ last="$(git -C "$repo_dir" log -1 --format=%cI 2>/dev/null)"
26
+ printf '%s\t%s\t%s\n' "$repo_dir" "$origin" "$last"
27
+ done
28
+ ```
29
+
30
+ Auxiliary markers (recorded but not required): `*.xcworkspace`, `*.xcodeproj`, `Podfile`, `Package.swift`, `package.json`, `pom.xml`, `build.gradle`. Their presence tags the repo as iOS/macOS, Cocoapods, SPM, Node, Maven, Gradle in the picker.
31
+
32
+ **7b. Bitbucket scan** - last 90 days active
33
+
34
+ Runs only if `keychainMapping.bitbucket_token` is non-null AND `serviceStatus.bitbucket.ok === true` (or cache older than `settings.serviceStatusCacheSeconds`, re-ping first).
35
+
36
+ Bitbucket Data Center (self-hosted) - the host comes from `prefs.global.bitbucketHost` (or prompts once if unset; NEVER hardcoded):
37
+ ```bash
38
+ BB_HOST="$(jq -r '.global.bitbucketHost // empty' "$PREFS_FILE")"
39
+ BB_USER="$(~/.claude/lib/credential-store.sh get "$(jq -r '.global.keychainMapping.bitbucket_user' "$PREFS_FILE")" 2>/dev/null)"
40
+ BB_TOK="$(~/.claude/lib/credential-store.sh get "$(jq -r '.global.keychainMapping.bitbucket_token' "$PREFS_FILE")" 2>/dev/null)"
41
+ CUTOFF_MS=$(( ( $(date +%s) - 7776000 ) * 1000 )) # 90 days
42
+ curl -sfS -u "$BB_USER:$BB_TOK" \
43
+ "https://${BB_HOST}/rest/api/1.0/repos?permission=REPO_WRITE&limit=200" \
44
+ | jq -r --argjson cutoff "$CUTOFF_MS" '
45
+ .values[] | select((.updatedDate // 0) >= $cutoff) |
46
+ { slug: "\(.project.key)/\(.slug)", clone: (.links.clone[] | select(.name=="http") | .href) }'
47
+ ```
48
+
49
+ Bitbucket Cloud - swap to `https://api.bitbucket.org/2.0/repositories/{workspace}?role=member&sort=-updated_on`.
50
+
51
+ **7c. GitHub scan** - owner + collaborator, push in last 90 days
52
+
53
+ Runs only if `keychainMapping.github` is non-null AND `serviceStatus.github.ok === true`.
54
+
55
+ ```bash
56
+ GH_TOK="$(~/.claude/lib/credential-store.sh get "$(jq -r '.global.keychainMapping.github' "$PREFS_FILE")" 2>/dev/null)"
57
+ CUTOFF_ISO="$(date -u -v-90d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '90 days ago' +%Y-%m-%dT%H:%M:%SZ)"
58
+ # gh CLI path (preferred when available)
59
+ gh api --paginate 'user/repos?affiliation=owner,collaborator&sort=pushed&per_page=100' \
60
+ -q ".[] | select(.pushed_at > \"$CUTOFF_ISO\") | {full: .full_name, clone: .clone_url}"
61
+ # Fallback without gh:
62
+ curl -sfS -H "Authorization: Bearer $GH_TOK" \
63
+ 'https://api.github.com/user/repos?affiliation=owner,collaborator&sort=pushed&per_page=100'
64
+ ```
65
+
66
+ **7d. Merge by origin URL**
67
+
68
+ Normalize clone URLs (strip `git@`, `https://`, trailing `.git`) to a canonical `host/owner/repo` key. Deduplicate:
69
+ - Local + remote match on canonical key → single entry with `path = <local>`, `platform = <remote source>`.
70
+ - Remote-only match → entry with `path: null` (user clones on first use from Phase 0).
71
+ - Local-only (no remote clone URL matches) → kept as-is, `platform: "local"`.
72
+
73
+ **7e. Multi-select picker**
74
+
75
+ ```
76
+ Discovered 24 active repos (local + remote ≤ 90 days):
77
+
78
+ [ ] 1. Booking ~/dev/Booking bitbucket iOS · SPM
79
+ [ ] 2. UIComponents ~/dev/UIComponents bitbucket iOS · SPM
80
+ [ ] 3. multi-agent-pipeline ~/multi-agent-pipeline github Node
81
+ [ ] 4. dotfiles (not cloned) github -
82
+ [ ] 5. backend-services ~/work/backend-services bitbucket Maven
83
+ ...
84
+
85
+ Toggle with numbers (e.g. "1 3 4"), 'all', 'none', or Enter to skip.
86
+ Selected repos become defaults in the Phase 0 project picker.
87
+ ```
88
+
89
+ **7f. Save to preferences**
90
+
91
+ ```json
92
+ {
93
+ "global": {
94
+ "recentProjects": [
95
+ {"path": "~/dev/Booking", "label": "Booking", "lastUsed": "2026-04-15T21:08:00Z"},
96
+ {"path": "~/dev/UIComponents", "label": "UIComponents", "lastUsed": "2026-04-15T21:08:00Z"},
97
+ {"path": "~/multi-agent-pipeline", "label": "multi-agent-pipeline","lastUsed": "2026-04-15T21:08:00Z"}
98
+ ],
99
+ "serviceStatus": {
100
+ "bitbucket": {"ok": true, "checkedAt": "2026-04-15T21:08:00Z"},
101
+ "github": {"ok": true, "checkedAt": "2026-04-15T21:08:00Z"}
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ Entries are objects (not bare strings). The schema `recentProjects.items` is a `oneOf` - both shapes validate, but v2.1.0 writes objects so `lastUsed` + `label` are first-class.
108
+
109
+ **7g. Skip / degrade rules**
110
+
111
+ - Source failure (network, VPN, 401, rate-limit) skips that source; the others still run. Failure is recorded in `serviceStatus.{service} = { ok: false, checkedAt: <now>, reason: <short> }` so Phase 0 can also read that cache.
112
+ - **Report the skip, do not swallow it.** Setup is where the user is actively configuring things, so a source that could not be reached is exactly what they need to hear about, and the reason decides their next move: a 401 means refresh that token, no response at all on a corporate host means connect the VPN, and a missing `global.hosts.<service>` means the token has nowhere to point. Print one line per skipped source with its classification (see `refs/keychain.md` Rule 2) before moving on. A discovery pass that quietly returned three repos out of forty reads as "you only have three repos".
113
+ - If all three sources yield zero hits, Step 5 is a no-op - the picker in Phase 0 falls back to manual path entry.
114
+ - Discovery is **never destructive**: it never modifies git state, never writes outside `PREFS_FILE`.
115
+ - Step 5 is idempotent. Re-running it refreshes `recentProjects` and `serviceStatus` but preserves user-curated entries unless explicitly deselected.
116
+
@@ -123,7 +123,7 @@
123
123
  "keychainMapping": {
124
124
  "type": "object",
125
125
  "additionalProperties": false,
126
- "description": "Maps logical token names to actual Keychain item names. Pipeline looks up tokens via `security find-generic-password -s <mapped-name>` (or `pipeline/lib/credential-store.sh get` cross-platform). Covered keys: jira, bitbucket, bitbucket_token, bitbucket_user, github, confluence, figma, figma_mcp, fortify, graylog, firebase, jenkins, npm, claude_oauth_token, claude_oauth_token_fallback. Deprecated and accepted only for backward compatibility: figma_pat (superseded by figma), figma_user (unused).",
126
+ "description": "Maps logical token names to actual Keychain item names. Pipeline looks up tokens via `security find-generic-password -s <mapped-name>` (or `pipeline/lib/credential-store.sh get` cross-platform). Covered keys: jira, bitbucket, bitbucket_token, bitbucket_user, github, confluence, figma, figma_mcp, fortify, graylog, firebase, jenkins, npm, claude_oauth_token, claude_oauth_token_fallback, supabase_access, supabase_service_role. Deprecated and accepted only for backward compatibility: figma_pat (superseded by figma), figma_user (unused).",
127
127
  "properties": {
128
128
  "jira": {
129
129
  "type": [
@@ -188,6 +188,20 @@
188
188
  "null"
189
189
  ]
190
190
  },
191
+ "supabase_access": {
192
+ "type": [
193
+ "string",
194
+ "null"
195
+ ],
196
+ "description": "Supabase personal access token (Management API). Used by personal-site tooling; not consumed by pipeline phases."
197
+ },
198
+ "supabase_service_role": {
199
+ "type": [
200
+ "string",
201
+ "null"
202
+ ],
203
+ "description": "Supabase service_role key for the personal-site project. Server-side only; never shipped to a client."
204
+ },
191
205
  "usage_ingest": {
192
206
  "type": [
193
207
  "string",
@@ -621,6 +635,27 @@
621
635
  ],
622
636
  "description": "Default Jira project key used for placeholder replacement. null until /multi-agent:setup onboards a Jira project."
623
637
  },
638
+ "figmaMcp": {
639
+ "type": "object",
640
+ "additionalProperties": false,
641
+ "properties": {
642
+ "remoteGeneratorScript": {
643
+ "type": [
644
+ "string",
645
+ "null"
646
+ ],
647
+ "description": "Local-only path to a script that generates the remote MCP OAuth token (figu_) via Dynamic Client Registration + PKCE and saves it to the credential store. Run by the setup figma_mcp pass when the user picks Remote. Personal paths stay in prefs; never embedded in synced files."
648
+ },
649
+ "localGeneratorScript": {
650
+ "type": [
651
+ "string",
652
+ "null"
653
+ ],
654
+ "description": "Local-only path to a script that validates the Figma PAT and wires the PAT-based local MCP server (@anthropic-ai/figma-mcp) into Claude Code settings.json. Run by the setup figma_mcp pass when the user picks Local."
655
+ }
656
+ },
657
+ "description": "Figma MCP onboarding helpers for /multi-agent:setup. Both fields optional; when unset the setup flow guides the token generation inline."
658
+ },
624
659
  "modelFallback": {
625
660
  "type": "object",
626
661
  "additionalProperties": false,
@@ -55,7 +55,6 @@ const DRY = args.includes("--dry-run");
55
55
  import {
56
56
  COMMON_PLUGIN,
57
57
  COMMON_SKILLS,
58
- APPLE_ONLY,
59
58
  STACK_ONLY,
60
59
  STACK_PATTERNS,
61
60
  } from "./_stack-routing.mjs";