@mmerterden/multi-agent-pipeline 20.17.2 → 20.18.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/manifest.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "20.17.2",
4
- "commit": "1f25114476708d621e7b0e6f74a6452cd59211f5",
5
- "generatedAt": "2026-10-08T15:58:19.854Z",
3
+ "version": "20.18.0",
4
+ "commit": "896371483bce326d6cb68a1f9609ebb99e7575d6",
5
+ "generatedAt": "2026-10-09T05:37:30.195Z",
6
6
  "algorithm": "sha256",
7
- "fileCount": 1354,
7
+ "fileCount": 1356,
8
8
  "files": {
9
- "CHANGELOG.md": "8f83b7059579509f39f401f745d5cd8bf8492f808b51446e773459bc43ee0809",
9
+ "CHANGELOG.md": "76773c616fec7b75d1db838ce716c2f25caebb605762c786c89381dc5aeb6141",
10
10
  "LICENSE": "8d8c51aaf1e1538b180c69965fa038db71be86798cc3e26a143bebfefe16a43e",
11
11
  "README.md": "1336cf131fdc0dbab1dd8b90fcd5614bd19d27b92b4f91ab99c3108d61cb51e1",
12
12
  "README.tr.md": "7979b32e6c72aedc3a6affe85f33b2840e72f056a836e8546566d259b236ff29",
@@ -33,7 +33,7 @@
33
33
  "docs/best-practices.md": "f8b75b0d38b5896a6a852bb30a1edddeb24dab8b5b7b1d9313290e1241c72024",
34
34
  "docs/ecosystem.md": "d504124f421748744599f6974ec71d52664affc47740665651cca5ddc17d0c71",
35
35
  "docs/engineering.md": "550528cd47c7d2c921729c68d0782a00ac8e77a4bdfec479f6749f9c3235d188",
36
- "docs/facts.json": "9ffa1909dc1f53797d498f15c324d627898e0819bbf80b0f1064fcaf3c24ab15",
36
+ "docs/facts.json": "fd737a1eef03c1cdda33d9d4173556998b39c7bc2d3cfca06c3bcb887a18ce20",
37
37
  "docs/features.md": "87ae163280fe05b36ff9f664b1bf9d98b697cc498731d47298751b439fa7a35c",
38
38
  "docs/performance.md": "bbf2f3922c25dcba0c1a9e69ef420e3871a729af5b35009a49b48a5d3c5cd151",
39
39
  "docs/recovery-guide.md": "59a8f1b66b34cb9ff5507b3d54dac3b9915e184aa044d9c758d18cac818d510e",
@@ -46,15 +46,16 @@
46
46
  "install/_copilot-instructions.mjs": "73e4b82b2af0f3e1669f772fa0788dac6b540c131e56d5fe38dcf29be85cc019",
47
47
  "install/_dev-only-files.mjs": "3391819c5e779b9e6d5a330d4937509c591e4c35ac937d25bafd86b37ca95b73",
48
48
  "install/_managed-block.mjs": "7328728a7c82744b66218034389fc295d1384311daa5415408afa94be14303d3",
49
- "install/_mcp-register.mjs": "1b1c294c3333c912aab3929ef67dc68b1e24e65d01f2073bf7e23193b3e37c71",
49
+ "install/_mcp-register.mjs": "d5a53ba2c8c779cd02e62c845270c5a2dbe717ffe852d9672998e643744c70ce",
50
50
  "install/_platform-filter.mjs": "1c66acc79da0da0c1aa5a57050989e84198c3153e6437ee03f1d3bacbb12ff2c",
51
51
  "install/_plugin-skills.mjs": "2de429260472a7b60bfe800eabc111624fa5f82a175e283481b05e5349dad572",
52
52
  "install/_telemetry.mjs": "5433d753f909beed2c8789272bba05e245482c472f83d3444f58000c4c9a634b",
53
53
  "install/_unattended-profile.mjs": "e1ed82bde35d19105ee12c2a551eb778912b0e5c93ab227fd15c993098d98de0",
54
- "install/catalog-history.json": "5ef515f98f3245e10ef377a338a4c8543ce4c279df7ea4a2fa4a6ac37f6412ee",
55
- "install/claude.mjs": "5a924a9cc51c9e67e26f4bbe9495d8f8226ca6d5065d2b5c9c9f0482fe4064dd",
56
- "install/codex.mjs": "88c3f90b29fff8579d972d97c90e40f969c8f5dc2d64a6fdc1dfa90155c5e45b",
57
- "install/copilot.mjs": "300a74f17084d1a38f16428ca7477394c29e33cd755156e19b92e457999be234",
54
+ "install/_xcode-skills.mjs": "1a7e20f1e6743324b941c5d3ecac9f94c65fff1534ffd9c63b39cbea4f8d101a",
55
+ "install/catalog-history.json": "0d6a9cb4eb8d228e4bb5133910fa7b7a0a3d223195dfb6643a42f7d75afcd201",
56
+ "install/claude.mjs": "106ce353ebc19c8319d057ce342e1f989519510465c798399076b5f3d8727904",
57
+ "install/codex.mjs": "08925fe9f3ecdf509b37f01105652c847475d20fcde2db0d679b9a7d663ca145",
58
+ "install/copilot.mjs": "41dc394b8a3ae77cfd194ce0ca9bbcafce9bdce349f1f72eeb86569f993e15fe",
58
59
  "install/index.mjs": "07ec26ecdd81cc983f146faf4b7095c98edce39c89a6fd83c24ce43229d2d9e7",
59
60
  "install/templates/claude-hooks.json": "12d6bb0c3cd7d71b1d2029e2eb4370ca7b5ee55bd88f96b09c927d8119a87be7",
60
61
  "install/templates/codex-instructions.md": "dd78eb9553e725c039380457245793a5fc748ae37ebf5ccfe11be5ed5b9a4025",
@@ -62,7 +63,7 @@
62
63
  "install/templates/multi-agent-autopilot-awake.plist.template": "ddd25c76c56fcbb38a89de8e35637372d70ace993d6bc487a88ca9cfb702fe1f",
63
64
  "install/templates/multi-agent-autopilot.plist.template": "122c8a61cd927edd99375d6e3da40f1d42bcceed5b6641785c8b28bb930bc77e",
64
65
  "install/unattended-profile-legacy.json": "f52e86b998956f09ad306b1735a6c10b58580d7082d8a55665a4c6bb15c3aa1f",
65
- "package.json": "afedc0164dfa997999b1fa891c50145096bb3f972ce4c27953ccc5a2f0a10664",
66
+ "package.json": "ea701923f2dbd97b1c712d609729f9cea25b3ccbf18eb447d9a604e22fc20e9b",
66
67
  "pipeline/agents/android-architect.md": "11fb74543cb0ebcf9525ca6654fe95d53da7229e0bf4a1149b31fd9005abc453",
67
68
  "pipeline/agents/backend-architect.md": "b02a606c301e4714131aed70ae6d16eeb77fbc819a001e2752cff2488d4246de",
68
69
  "pipeline/agents/bulk-reader.md": "6597052cd7d205bac2b1c7633f5232fe1a40730664a913b8eb77efd5a786dc25",
@@ -129,7 +130,7 @@
129
130
  "pipeline/commands/multi-agent/security-review/SKILL.md": "dfc2fad484c81169912062391f7ad97500ef113d95741902e5e18e7dd60b6da1",
130
131
  "pipeline/commands/multi-agent/serve/SKILL.md": "e931219d8be258a155a692d23c2b275c2e7e0a761b3f0354c943e26d2ce7b035",
131
132
  "pipeline/commands/multi-agent/setup/SKILL.md": "b6b4af9eac2ec9454dda08d960ab9ed559421e167f6726915b451e86fbb3f058",
132
- "pipeline/commands/multi-agent/stack/SKILL.md": "6d64e96b9a2d41d25ff077afa84300172cac9bf9dd78ec1d9fbbaf45df34f5dd",
133
+ "pipeline/commands/multi-agent/stack/SKILL.md": "26ecb48b28b61250330edb26fdfd014e401992e4f1329c572766ba4e2dc6e35d",
133
134
  "pipeline/commands/multi-agent/status/SKILL.md": "b08338841a5282f8ae2494751001aa31cbf4829146a58ed2c395b1c062b48443",
134
135
  "pipeline/commands/multi-agent/steer/SKILL.md": "07e738c228ba328f717f86e97e7af207df090b16433e28e613d430c3ff0aee3e",
135
136
  "pipeline/commands/multi-agent/store-ready/SKILL.md": "927181a7ea4890ea2ce3e8523c2f0563829e8827aa6ed92884b76a84521a378f",
@@ -238,6 +239,7 @@
238
239
  "pipeline/lib/user-facing.sh": "969038aa575a7075cbc8c2857b3e6bddfa5c8ea8a15811ad65c6660735539b76",
239
240
  "pipeline/lib/vercel-deploy.sh": "50d3d8a0b2c60daf90365396f37337d89a60230a94e7bd4c89b19acf1849de99",
240
241
  "pipeline/lib/workspace-trust.mjs": "baa33eb4bef709578c554fb6f1c5952d838a3fe154422e7e671ac485e49518dc",
242
+ "pipeline/lib/xcode-plugin.mjs": "79d1bef0ec5c7909f970ff8772c8422adbaf6217b6f72c44c531a124d1a12fd0",
241
243
  "pipeline/multi-agent-refs/_account-picker.md": "42715ba746e1fc681a57436d72fc643e3f16de023f0409fee8782b013f7a144a",
242
244
  "pipeline/multi-agent-refs/_dev-context.md": "4d0905cc537c7f8526d3c92cf09bb44d2ab50680470ee0baa2dd5c3b1d0565d5",
243
245
  "pipeline/multi-agent-refs/_input-parser.md": "4cf222b1c4a405bd71521f5290f45408cc8a47913a0a39166c68210104e90cc5",
@@ -290,7 +292,7 @@
290
292
  "pipeline/multi-agent-refs/features/cost-analysis.md": "42f1b90b41ef454f29ccfd3f93c959920f0e74cbca65e140695e9a30d6d90b87",
291
293
  "pipeline/multi-agent-refs/features/design-conformance.md": "a88ca595827cafa0c87c2facfcf9190dac69485e0596e5c198c6fc1613880452",
292
294
  "pipeline/multi-agent-refs/features/dev-critic.md": "08e01edef04b5ac216f8f43332126966d33da7b95625e47a8a6cd8ac105b263f",
293
- "pipeline/multi-agent-refs/features/doctor.md": "708aba37c507bf7559af816e6328a11ba0acdc6d61d023bc10afc727d068876a",
295
+ "pipeline/multi-agent-refs/features/doctor.md": "62ff75a085095b11818ffabb97bd76e62e58de6affd24f4eaf3d9f89f2fe1d66",
294
296
  "pipeline/multi-agent-refs/features/exploratory-testing.md": "3ee81dc5d42068b09b6e31c7649ee57ff73e30480237c8c4a0571c78d996928d",
295
297
  "pipeline/multi-agent-refs/features/external-context-injection.md": "fe0373f27c3e9aae660a55a3ba9a8163df329b4d95c727d6023b6b3afaf71d66",
296
298
  "pipeline/multi-agent-refs/features/jira-context.md": "7d14802ed9ad1747e1a06dec26f22ff9e8959e03c47d7a018af45b8274aea47c",
@@ -550,7 +552,7 @@
550
552
  "pipeline/scripts/crush-json.mjs": "303382670f8f170c8c6710202853a2de2915625813cd157185f9be4e2b27258e",
551
553
  "pipeline/scripts/diff-explain.mjs": "194d0635639607cb1af3de064453162e01c0d59dc0c2e187d21d45bae3bc559c",
552
554
  "pipeline/scripts/diff-risk-score.mjs": "b3e8da42fea1258162e40f4eb7809074831339b7563874527f7e825f7aa795ca",
553
- "pipeline/scripts/doctor.mjs": "c80e2fe229f9d77d922ff9162313b141923be9c6faf3128e11b0bb7c44986c93",
555
+ "pipeline/scripts/doctor.mjs": "a86a39b3eab13158166f44133c1fdc1fda3d4cc7ad0122f3dc47e3cfae2f5ad1",
554
556
  "pipeline/scripts/estimate.mjs": "9847e412dd74ac19a5c3b84bed343f79d25f256022b2df98e3eb1296db45ed3a",
555
557
  "pipeline/scripts/evidence-gate.mjs": "347a8d3fa1d17d996de345585c216bd7071f4c1d687999daf6e48881720ea5df",
556
558
  "pipeline/scripts/explore-findings.mjs": "997cca3ef42a170ff51ee2c382263eff8653affd6bb675877b94b8bba782e717",
@@ -660,7 +662,7 @@
660
662
  "pipeline/scripts/triage-memory.mjs": "2d8541145f1749593ae5728fb4c9eb3001484edb5e4d37be473aeec34d1dbe7c",
661
663
  "pipeline/scripts/ui-tree-dumper.swift": "a09e42ef069eafa08c05afd8333e05b754ef6a7d8b9687712039b0f2c9801273",
662
664
  "pipeline/scripts/unattended_policy.py": "f1df6470c798bd7d4bc9f8338eaabf98bdcfab00198d98b7e2e654aab84b709c",
663
- "pipeline/scripts/uninstall.mjs": "6ca82cb55910d085feca776a6cd4c28adcf0a42fb07358e4bab27c89a9d913a5",
665
+ "pipeline/scripts/uninstall.mjs": "ba11803733cf3bfcc966f0d4aa2cc5fbed000cb37a251005f067ce0289306233",
664
666
  "pipeline/scripts/update-check.sh": "9c00647e7ca4249414b2e5af08f61705ab5bd2106b6f66ae8ebce8aa143dc957",
665
667
  "pipeline/scripts/update-issue-progress.sh": "ccfef87743e91272e009e396ac07461d2784b2fa84a06c802a582526eeeb9ed9",
666
668
  "pipeline/scripts/usage-identity.mjs": "1ea8ffb5a52aa5f98a7ea491f12af02d86f711666ac0cc37cac59c70e182aaf4",
@@ -688,7 +690,7 @@
688
690
  "pipeline/scripts/worktrees.mjs": "90dfd09b70fffe9b56c331a4ae2692cc248f4898f4ee6aee4239ef0b20888a90",
689
691
  "pipeline/scripts/write-state.mjs": "e1198bf970eace07d3eff22b394808a526e473df65d3d58aa2d5a12cfdac8b49",
690
692
  "pipeline/scripts/write-store-config.mjs": "b994a061f6febb2ca3d396074d8b51d2c1c1f991d3072d321703380da3639a56",
691
- "pipeline/skills/.skill-manifest.json": "6fa60f2b1f95e3d863ab871f09961254688a4065d268a6e0b63ce62ded6ed436",
693
+ "pipeline/skills/.skill-manifest.json": "48f84aac9947e30a91e65ca97ff41b327b43af8b3643883f2a19e0169bfc89b9",
692
694
  "pipeline/skills/.skills-index.json": "b2917fa44e273b939549f1c3ab3dd81c26ba8168772bc7644c9fa076116d8a4c",
693
695
  "pipeline/skills/shared/README.md": "928fe4292be729c3b7d7d0898cade8be0dfa868f208e0c6d1fe7b2c6bd6223c5",
694
696
  "pipeline/skills/shared/core/apple-archive-compliance/SKILL.md": "7239fef717ea45b79276bfcc2c3903acd57950b6e00dd6a2a66399c4b09cbd51",
@@ -958,11 +960,11 @@
958
960
  "pipeline/skills/shared/external/ios-haptics/evals/trigger-eval.json": "f97a385ac2c72c0d76c48a5938186c9fdee419d2fbec2f64fc4c22098650daff",
959
961
  "pipeline/skills/shared/external/ios-haptics/references/core-haptics.md": "04c8b2a276890b4090b824a214699eb599135a1c48506217b973ffb27100cf70",
960
962
  "pipeline/skills/shared/external/ios-iphone-duo/NOTICE.md": "290de59e3d37adaef65f76533785a3c263b4a83776a44bbaa1e5e6244e33e69f",
961
- "pipeline/skills/shared/external/ios-iphone-duo/SKILL.md": "2da65582a8d86957ded84345e60095584a26942f9ded8f61e901f3d04fb66c37",
962
- "pipeline/skills/shared/external/ios-iphone-duo/evals/evals.json": "c1cf034cb6dcfc8ba37b2ef504f6231006126c0ef00937c81e834854f47e2a83",
963
+ "pipeline/skills/shared/external/ios-iphone-duo/SKILL.md": "934df39d42cac41473d4c7605891c4b15aca2d185099c21b36a3c1abbbea8359",
964
+ "pipeline/skills/shared/external/ios-iphone-duo/evals/evals.json": "75fbe9fad13725fa9e2ff0456dc31a3211a4a9455bd31b2e8cc5871aee6c5eb5",
963
965
  "pipeline/skills/shared/external/ios-iphone-duo/evals/trigger-eval.json": "30033fda1613982dd773a3bcf18d074b7ce7b4a02bc30bc08e7ed274a5732b4b",
964
966
  "pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md": "ec7d0b3dab323db1951c2b5319691f611466012b970823333a233e33c2a338d4",
965
- "pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md": "633f6f2e9e12ad0745dae285c1479c654d644bde029e6226bc1daea5d2081f8a",
967
+ "pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md": "4a3e52a72b48851adc43ec702c4dbbc9d9d313a7b56409553406885b5606fdab",
966
968
  "pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md": "f06bd8689cf793c239125327f4ec74f234c49beda277467cdebc7366ff76909f",
967
969
  "pipeline/skills/shared/external/ios-localization/SKILL.md": "3ee1a34bbf6c0b77e1e9f16cb7d8bc3d10f2ba7bf6d0b48e64126afcf9dbe2b6",
968
970
  "pipeline/skills/shared/external/ios-localization/evals/evals.json": "f80b7a9d9a4a262c6df824ba8c4bf1a762d98d0a12501bead04e8cd6a016cc6e",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "20.17.2",
3
+ "version": "20.18.0",
4
4
  "description": "6-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",
@@ -70,6 +70,7 @@ installer.
70
70
  - every stack toolkit **not** in the union is set to `false` (leave non-`@multi-agent-plugins` entries untouched)
71
71
  - `ai-common-toolkit@multi-agent-plugins` and `ai-analyst-toolkit@multi-agent-plugins` are always `true`
72
72
  - **legacy-key cleanup**: delete any `ai-*-engineering-toolkit@multi-agent-plugins` keys - those plugin names were retired by the `ai-<stack>-toolkit` rename and a stale `true` there enables a plugin that no longer exists in the marketplace
73
+ - **Apple's Xcode skills**: when `~/.claude/xcode-apple-marketplace` exists (the installer linked this Mac's Xcode 27.1+ plugin), set `xcode-integration@xcode-apple` to `true` if `ios` is in the selection and `false` otherwise. Apple's skills are the reference for iPhone Duo and window resizing, SwiftUI, App Intents and the accessibility audits, and they are the first route for those iOS tasks. Without that directory there is nothing to enable; leave the key absent
73
74
  4. **Unknown arg** → show the table above; do not guess.
74
75
  5. **Copilot/Codex refresh offer.** Their local skill copies are filtered to the enabled stacks at install time, so after a change ask (single `AskUserQuestion`, not silent): "Refresh Copilot/Codex local copies now?" - Yes runs `node <pipelineRepo>/install.js --copilot --codex`, No leaves them stale with a one-line warning naming the command to run later. Skip this question entirely when neither `~/.copilot` nor `~/.codex` exists.
75
76
 
@@ -109,6 +110,7 @@ After resolving `$ON`, edit `.claude/settings.json` (create `{ "enabledPlugins":
109
110
  - every stack toolkit **not** in `$ON` is set to `false` (leave non-`@multi-agent-plugins` entries untouched),
110
111
  - `ai-common-toolkit@multi-agent-plugins` and `ai-analyst-toolkit@multi-agent-plugins` are always `true`,
111
112
  - every `ai-*-engineering-toolkit@multi-agent-plugins` key is **deleted** (retired names).
113
+ - `xcode-integration@xcode-apple` follows `ios` (`true` with it, `false` without), only when `~/.claude/xcode-apple-marketplace` exists.
112
114
 
113
115
  Use the Read + Edit/Write tools (JSON must stay valid). Then print the resulting `enabledPlugins` block and run step 5 (Copilot/Codex refresh offer).
114
116
 
@@ -0,0 +1,131 @@
1
+ /**
2
+ * xcode-plugin.mjs - where this Mac's Xcode keeps its agent plugin, and whether
3
+ * Claude Code's link to it is installed and at the installed Xcode build.
4
+ *
5
+ * Xcode 27.1 and later materialize `xcode-integration` (Apple's skills plus the
6
+ * Xcode MCP bridge) and print its path with `xcrun agent plugin path`. The
7
+ * installer links it (`install/_xcode-skills.mjs`), uninstall removes it, and
8
+ * doctor reads the same answers from here, so none of them can disagree about
9
+ * what "linked" or "current" means.
10
+ *
11
+ * `MULTI_AGENT_XCRUN` names another `xcrun`, for tests.
12
+ *
13
+ * @module pipeline/lib/xcode-plugin
14
+ */
15
+
16
+ import { execFileSync } from "node:child_process";
17
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
18
+ import { basename, dirname, join } from "node:path";
19
+
20
+ export const XCODE_MARKETPLACE = "xcode-apple";
21
+ export const XCODE_PLUGIN = "xcode-integration";
22
+ export const XCODE_PLUGIN_ID = `${XCODE_PLUGIN}@${XCODE_MARKETPLACE}`;
23
+
24
+ /** Claude Code's directory marketplace, relative to the home directory. */
25
+ export const CLAUDE_MARKETPLACE_DIR = join(".claude", "xcode-apple-marketplace");
26
+
27
+ /** The Xcode build the marketplace copy was taken from, inside that directory. */
28
+ export const BUILD_STAMP = ".xcode-build";
29
+
30
+ /** Copy hosts: which delivered skill dirs came from Xcode, for refresh and uninstall. */
31
+ export const XCODE_SKILLS_MANIFEST = ".xcode-skills-manifest.json";
32
+
33
+ export const defaultRun = (cmd, args) =>
34
+ execFileSync(cmd, args, { stdio: "pipe", timeout: 30_000 }).toString();
35
+
36
+ /** One answer per process and format: an install asks for it once per host. */
37
+ const cache = new Map();
38
+
39
+ /**
40
+ * Where this Mac's Xcode materialized its plugin, and which build it came from.
41
+ * Null without developer tools, with an Xcode older than 27.1, or when `xcrun`
42
+ * fails.
43
+ *
44
+ * `xcode-select -p` is asked first because `/usr/bin/xcrun` on a Mac without
45
+ * developer tools opens the system's install-the-tools dialog instead of
46
+ * failing.
47
+ *
48
+ * @param {"claude"|"codex"} format
49
+ * @param {{run?: (cmd: string, args: string[]) => string, env?: object}} [opts]
50
+ * @returns {{path: string, build: string, skills: string[]}|null}
51
+ */
52
+ export function resolveXcodePlugin(
53
+ format = "claude",
54
+ { run = defaultRun, env = process.env } = {},
55
+ ) {
56
+ const xcrun = env.MULTI_AGENT_XCRUN || "xcrun";
57
+ const cacheable = run === defaultRun;
58
+ const key = `${format}\0${xcrun}`;
59
+ if (cacheable && cache.has(key)) return cache.get(key);
60
+ const answer = resolveUncached(format, xcrun, run, env);
61
+ if (cacheable) cache.set(key, answer);
62
+ return answer;
63
+ }
64
+
65
+ function resolveUncached(format, xcrun, run, env) {
66
+ let out;
67
+ try {
68
+ if (!env.MULTI_AGENT_XCRUN) run("xcode-select", ["-p"]);
69
+ out = run(xcrun, ["agent", "plugin", "path", "--plugin-format", format]);
70
+ } catch {
71
+ return null;
72
+ }
73
+ const path = String(out || "")
74
+ .trim()
75
+ .split("\n")
76
+ .pop()
77
+ ?.trim();
78
+ if (!path || !path.startsWith("/")) return null;
79
+ try {
80
+ const skillsDir = join(path, "skills");
81
+ const skills = readdirSync(skillsDir, { withFileTypes: true })
82
+ .filter((e) => e.isDirectory() && existsSync(join(skillsDir, e.name, "SKILL.md")))
83
+ .map((e) => e.name)
84
+ .sort();
85
+ return skills.length ? { path, build: basename(dirname(path)), skills } : null;
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+
91
+ /** The Xcode build Claude Code's link was copied from, or null when not linked. */
92
+ export function claudeLinkedBuild(home) {
93
+ try {
94
+ return readFileSync(join(home, CLAUDE_MARKETPLACE_DIR, BUILD_STAMP), "utf8").trim() || null;
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+
100
+ /** Does Claude Code's own plugin registry list `xcode-integration@xcode-apple`. */
101
+ export function claudePluginInstalled(home) {
102
+ try {
103
+ const j = JSON.parse(
104
+ readFileSync(join(home, ".claude", "plugins", "installed_plugins.json"), "utf8"),
105
+ );
106
+ const entry = (j.plugins || j)[XCODE_PLUGIN_ID];
107
+ return Array.isArray(entry) ? entry.length > 0 : Boolean(entry);
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Is Apple's plugin available, and is Claude Code's link installed and at the
115
+ * installed build.
116
+ *
117
+ * @param {string} home
118
+ * @param {{run?: (cmd: string, args: string[]) => string, env?: object}} [opts]
119
+ */
120
+ export function xcodeSkillsStatus(home, opts = {}) {
121
+ const plugin = resolveXcodePlugin("claude", opts);
122
+ const linked = claudeLinkedBuild(home);
123
+ return {
124
+ available: Boolean(plugin),
125
+ build: plugin?.build ?? null,
126
+ skills: plugin?.skills.length ?? 0,
127
+ claude: linked
128
+ ? { build: linked, current: linked === plugin?.build, installed: claudePluginInstalled(home) }
129
+ : null,
130
+ };
131
+ }
@@ -303,6 +303,16 @@ other outcome (`redirected`, `rejected`, `not-accepted`, `failed`, `skipped`),
303
303
  with the status code or reason and the step that fixes it. The emitter runs
304
304
  detached from phase-tracker with its output discarded, so this record is the
305
305
  only surface on which a reporting path that stopped working shows up.
306
+ ### xcode-skills
307
+
308
+ Is Apple's `xcode-integration` plugin, which Xcode 27.1 and later materialize
309
+ (`xcrun agent plugin path`), linked into Claude Code at the installed Xcode
310
+ build. SKIP when this Mac has no such plugin, because the pipeline's own iOS
311
+ skills then apply; INFO when it is available but not linked; WARN when the link
312
+ was copied from an older Xcode build than the one installed; OK otherwise. The
313
+ step is `/multi-agent:update`, which re-runs the installer's link. Apple's skills
314
+ are the reference for iPhone Duo and window resizing, SwiftUI, App Intents and
315
+ the accessibility audits, so a stale copy is a worse answer, never a failed run.
306
316
  ### unattended-contract
307
317
 
308
318
  Is `multi-agent-refs/unattended-contract.md` installed. It is the only place
@@ -60,6 +60,7 @@ import {
60
60
  UNATTENDED_SETTINGS_FILE,
61
61
  } from "../lib/unattended-settings-location.mjs";
62
62
  import { prefsHosts, profileGaps } from "../lib/unattended-profile-check.mjs";
63
+ import { xcodeSkillsStatus } from "../lib/xcode-plugin.mjs";
63
64
 
64
65
  const HERE = dirname(fileURLToPath(import.meta.url));
65
66
  const HOME = homedir();
@@ -103,6 +104,7 @@ const CHECK_IDS = [
103
104
  "disk-space",
104
105
  "worktree-residue",
105
106
  "usage-report",
107
+ "xcode-skills",
106
108
  ];
107
109
 
108
110
  // Only under `--profile server`. Kept in their own list so `--list-checks`
@@ -933,6 +935,41 @@ function checkUsageReport() {
933
935
  );
934
936
  }
935
937
 
938
+ // Apple's Xcode skills are the reference for iPhone Duo and window resizing,
939
+ // SwiftUI, App Intents and the accessibility audits, and Claude Code reads them
940
+ // from a copy the installer keeps at the installed Xcode build. A newer Xcode
941
+ // leaves that copy older than what the Mac offers, which degrades the answer
942
+ // rather than breaking a run, so it tops out at WARN.
943
+ function checkXcodeSkills() {
944
+ const s = xcodeSkillsStatus(HOME);
945
+ if (!s.available) {
946
+ skip(
947
+ "xcode-skills",
948
+ "no Xcode 27.1+ agent plugin on this Mac; the pipeline's own iOS skills apply",
949
+ );
950
+ return;
951
+ }
952
+ if (!s.claude || !s.claude.installed) {
953
+ report(
954
+ "xcode-skills",
955
+ "INFO",
956
+ `Apple's Xcode skills (${s.skills}, Xcode ${s.build}) are not linked into Claude Code`,
957
+ "run /multi-agent:update, or node install.js --claude from the pipeline checkout",
958
+ );
959
+ return;
960
+ }
961
+ if (!s.claude.current) {
962
+ report(
963
+ "xcode-skills",
964
+ "WARN",
965
+ `Apple's Xcode skills are linked from Xcode ${s.claude.build}, but this Mac now has ${s.build}`,
966
+ "run /multi-agent:update to refresh them from the installed Xcode",
967
+ );
968
+ return;
969
+ }
970
+ ok("xcode-skills");
971
+ }
972
+
936
973
  function checkDiskSpace() {
937
974
  let freeGb = null;
938
975
  try {
@@ -1226,6 +1263,7 @@ function main() {
1226
1263
  checkDiskSpace();
1227
1264
  checkWorktreeResidue();
1228
1265
  checkUsageReport();
1266
+ checkXcodeSkills();
1229
1267
  if (PROFILE === "server") checkServerProfile(resolved);
1230
1268
 
1231
1269
  const blocked = results.filter((r) => r.severity === "BLOCK");
@@ -53,6 +53,12 @@ import { createHash } from "crypto";
53
53
  import { join, dirname } from "path";
54
54
  import { homedir } from "os";
55
55
  import { pathToFileURL } from "url";
56
+ import {
57
+ CLAUDE_MARKETPLACE_DIR,
58
+ XCODE_MARKETPLACE,
59
+ XCODE_PLUGIN_ID,
60
+ XCODE_SKILLS_MANIFEST,
61
+ } from "../lib/xcode-plugin.mjs";
56
62
  import { createInterface } from "readline";
57
63
 
58
64
  const HOME = process.env.HOME;
@@ -425,6 +431,48 @@ function deregisterClaudeMcpServer() {
425
431
  deregisterMcpServer("claude", existsSync(localBin) ? localBin : undefined, ["--scope", "user"]);
426
432
  }
427
433
 
434
+ /**
435
+ * Apple's Xcode plugin is linked into Claude Code from a local directory
436
+ * marketplace the installer keeps at the installed Xcode build. Without the
437
+ * pipeline nothing refreshes that copy, so it goes with the pipeline; Xcode
438
+ * itself still offers the same skills.
439
+ */
440
+ function unlinkClaudeXcodePlugin(home) {
441
+ const mpDir = join(home, CLAUDE_MARKETPLACE_DIR);
442
+ if (!existsSync(mpDir)) return;
443
+ const localBin = join(homedir(), ".local", "bin", "claude");
444
+ const cli = existsSync(localBin) ? localBin : "claude";
445
+ const steps = [
446
+ ["plugin", "uninstall", XCODE_PLUGIN_ID, "--scope", "user"],
447
+ ["plugin", "marketplace", "remove", XCODE_MARKETPLACE],
448
+ ];
449
+ if (dryRun) {
450
+ for (const args of steps) report("would run", `claude ${args.join(" ")}`);
451
+ report("would remove", mpDir);
452
+ return;
453
+ }
454
+ const failed = [];
455
+ for (const args of steps) {
456
+ try {
457
+ execFileSync(cli, args, { stdio: "pipe", timeout: 20_000 });
458
+ } catch (e) {
459
+ const out = `${e?.stderr ?? ""}${e?.stdout ?? ""}`;
460
+ if (!/not (found|installed)|no such|does not exist|unknown/i.test(out)) failed.push(args);
461
+ }
462
+ }
463
+ // Claude Code's settings still point at the directory until the CLI drops
464
+ // them; deleting it first would leave a marketplace that fails to load at
465
+ // every start.
466
+ if (failed.length) {
467
+ console.log(" kept: Apple's Xcode skills link - claude could not remove it; run:");
468
+ for (const args of failed) console.log(` claude ${args.join(" ")}`);
469
+ console.log(` then delete ${mpDir}`);
470
+ return;
471
+ }
472
+ rmIfExists(mpDir);
473
+ console.log(" removed: Apple's Xcode skills link (xcode-apple marketplace)");
474
+ }
475
+
428
476
  /**
429
477
  * Copilot registers the same server, so leaving it behind would keep a stale entry
430
478
  * pointing at a package the user just removed.
@@ -589,7 +637,14 @@ function rmMatchingFiles(parent, predicate) {
589
637
  const PLUGIN_SKILLS_MANIFEST = ".plugin-skills-manifest.json";
590
638
 
591
639
  function rmPluginDeliveredSkills(skillsDir) {
592
- const manifestPath = join(skillsDir, PLUGIN_SKILLS_MANIFEST);
640
+ return (
641
+ rmManifestListedSkills(skillsDir, PLUGIN_SKILLS_MANIFEST) +
642
+ rmManifestListedSkills(skillsDir, XCODE_SKILLS_MANIFEST)
643
+ );
644
+ }
645
+
646
+ function rmManifestListedSkills(skillsDir, manifestName) {
647
+ const manifestPath = join(skillsDir, manifestName);
593
648
  if (!existsSync(manifestPath)) return 0;
594
649
  let names;
595
650
  try {
@@ -855,6 +910,7 @@ export async function main() {
855
910
  console.log(" [Claude Code] Removing...");
856
911
  deregisterClaudeMcpServer();
857
912
  const CLAUDE = join(HOME, ".claude");
913
+ unlinkClaudeXcodePlugin(HOME);
858
914
  rmCommandsPreservingLocalOnly(join(CLAUDE, "commands", "multi-agent"));
859
915
  rmIfExists(join(CLAUDE, "scripts"));
860
916
  // Pipeline-managed trees the installer lays down alongside scripts/.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-08T12:53:56Z",
3
+ "generatedAt": "2026-10-09T05:08:19Z",
4
4
  "skillCount": 228,
5
5
  "entries": [
6
6
  {
@@ -569,7 +569,7 @@
569
569
  },
570
570
  {
571
571
  "path": "shared/external/ios-iphone-duo/SKILL.md",
572
- "sha256": "2da65582a8d86957ded84345e60095584a26942f9ded8f61e901f3d04fb66c37"
572
+ "sha256": "934df39d42cac41473d4c7605891c4b15aca2d185099c21b36a3c1abbbea8359"
573
573
  },
574
574
  {
575
575
  "path": "shared/external/ios-localization/SKILL.md",
@@ -19,6 +19,13 @@ APIs where the fold or the vertical bars need them.
19
19
  in the iOS 27.1 SDK and may change before release. Check availability before
20
20
  shipping code that depends on it, and gate it with `if #available`.
21
21
 
22
+ **Apple's skill first.** Xcode 27.1 and later ship Apple's `app-resizability`
23
+ skill, which the pipeline links from this Mac's Xcode (Claude Code:
24
+ `xcode-integration:app-resizability`). When it is available, run it first for
25
+ the code scan and the API modernization; use this skill for what it adds: the
26
+ fold and poses, reserved regions, arrangements, vertical bars and the design
27
+ review. Without Xcode 27.1, this skill covers the whole job.
28
+
22
29
  ## One rule behind every fix
23
30
 
24
31
  Do not ask "is this iPhone Duo?". Ask "how much space do I have, and what is
@@ -54,6 +61,8 @@ Each hit is a candidate defect with a known replacement:
54
61
  | Symmetric insets (`insets.left * 2`) | Side-mounted bars make insets asymmetric | Each edge's inset on its own |
55
62
  | Hand-built `UIToolbar`, `UINavigationBar`, `UITabBar` | Only container-provided bars move to the side | Toolbar items on navigation containers |
56
63
  | Full-bleed fill media | Wider displays crop the subject | Choose fill or fit from size, set a focal point |
64
+ | App-delegate-only life cycle | From iOS 27 an app built with the latest SDK must adopt scenes or it fails to launch | Scene manifest and scene delegate, `UIWindow(windowScene:)` |
65
+ | `topLayoutGuide`, keyboard frames, stored insets | Deprecated or computed for a full screen | `safeAreaLayoutGuide`, `keyboardLayoutGuide`, insets read at layout |
57
66
 
58
67
  ## Step 3: let containers adapt
59
68
 
@@ -45,6 +45,21 @@
45
45
  "Uses ToolbarOverflowMenu or the system overflow instead of a custom menu",
46
46
  "Treats disabling vertical bars as a rare, stable per-screen choice"
47
47
  ]
48
+ },
49
+ {
50
+ "id": 3,
51
+ "name": "scene-lifecycle-and-legacy-guides",
52
+ "prompt": "Our UIKit app still does all its foreground and background work in AppDelegate (applicationDidBecomeActive and friends), creates its window with UIWindow(frame: UIScreen.main.bounds), pins the editor to topLayoutGuide, and moves the input bar up by reading keyboardFrameEndUserInfoKey. We are moving to the iOS 27 SDK and want iPhone Duo support. What breaks and what do we change?",
53
+ "expected_output": "States that from iOS 27 an app built with the latest SDK must adopt the scene-based life cycle or it fails to launch, and that foreground and background arrive per scene. Moves window creation to a scene delegate with UIWindow(windowScene:), the four lifecycle callbacks to their scene equivalents, keeps one-time work in didFinishLaunching, and treats multiple windows as optional. Replaces topLayoutGuide with safeAreaLayoutGuide and keyboard-frame math with keyboardLayoutGuide, and reads insets at layout time. Mentions running Apple's app-resizability skill first when Xcode provides it.",
54
+ "files": [],
55
+ "expectations": [
56
+ "Says an app built with the iOS 27 SDK without the scene life cycle fails to launch",
57
+ "Moves window creation to a scene delegate using UIWindow(windowScene:)",
58
+ "Maps the app delegate lifecycle callbacks to scene callbacks and keeps one-time work at launch",
59
+ "Replaces topLayoutGuide with safeAreaLayoutGuide",
60
+ "Replaces keyboard frame math with keyboardLayoutGuide",
61
+ "Points to Apple's app-resizability skill as the first route when Xcode 27.1+ provides it"
62
+ ]
48
63
  }
49
64
  ]
50
65
  }
@@ -15,7 +15,8 @@ the iPhone Duo simulator. Exclude vendored and generated code from the count.
15
15
  7. Insets
16
16
  8. Bars
17
17
  9. Media
18
- 10. Recording the result
18
+ 10. Life cycle, legacy guides and the keyboard
19
+ 11. Recording the result
19
20
 
20
21
  ## 1. The search
21
22
 
@@ -33,9 +34,18 @@ rg -n --type swift \
33
34
  -e '(width|height)\s*(==|>=|<=|>|<)\s*(320|375|390|393|402|414|428|430|440)\b' \
34
35
  -e 'UIToolbar\(|UINavigationBar\(|UITabBar\(' \
35
36
  -e 'scaleAspectFill|contentMode:\s*\.fill' \
37
+ -e 'topLayoutGuide|bottomLayoutGuide' \
38
+ -e 'keyboardWillShowNotification|keyboardFrameEndUserInfoKey' \
39
+ -e 'applicationDidBecomeActive|applicationWillResignActive|applicationDidEnterBackground|applicationWillEnterForeground' \
36
40
  .
37
41
  ```
38
42
 
43
+ The scene manifest lives in Info.plist, which the Swift search does not read:
44
+
45
+ ```bash
46
+ rg -l 'UIApplicationSceneManifest' -g '*.plist' . || echo "no scene manifest"
47
+ ```
48
+
39
49
  Add Objective-C (`--type objc`) and storyboards (`-g '*.storyboard'` for
40
50
  fixed-size constraints) when the project has them.
41
51
 
@@ -138,7 +148,54 @@ Replace: choose fill or fit from the current size or aspect ratio, crop around
138
148
  a focal point, and extend backgrounds under bars with
139
149
  `backgroundExtensionEffect()` rather than stretching the image.
140
150
 
141
- ## 10. Recording the result
151
+ ## 10. Life cycle, legacy guides and the keyboard
152
+
153
+ **Scene life cycle.** Hits: the four `application...` foreground and background
154
+ callbacks doing real work in the app delegate, no `UIApplicationSceneManifest`
155
+ in Info.plist, no `UIWindowSceneDelegate`.
156
+
157
+ Why: from iOS 27, an app built with the latest SDK that has not adopted the
158
+ scene-based life cycle fails to launch. The system also delivers foreground and
159
+ background per scene, so app-level callbacks miss what happens to one window
160
+ of several.
161
+
162
+ Replace: a scene manifest and a scene delegate that creates the window with
163
+ `UIWindow(windowScene:)`, and the per-window work moved to the scene callbacks
164
+ (`sceneDidBecomeActive`, `sceneWillResignActive`, `sceneDidEnterBackground`,
165
+ `sceneWillEnterForeground`). Work that runs once per process stays in
166
+ `application(_:didFinishLaunchingWithOptions:)`. Supporting several windows is
167
+ a separate, optional step.
168
+
169
+ **Legacy layout guides.** Hits: `topLayoutGuide`, `bottomLayoutGuide`, and
170
+ constants standing in for the status bar or navigation bar height.
171
+
172
+ Why: those guides are deprecated and know nothing about side insets; a fixed
173
+ bar height is wrong as soon as a bar moves or the status bar changes.
174
+
175
+ Replace: `safeAreaLayoutGuide` and layout margins.
176
+
177
+ **The keyboard.** Hits: frames computed from `keyboardFrameEndUserInfoKey` in
178
+ a `keyboardWillShowNotification` handler.
179
+
180
+ Why: a screen-space keyboard frame needs converting into the window by hand,
181
+ and frame arithmetic misses the cases a resized window creates.
182
+
183
+ Replace: constraints to `view.keyboardLayoutGuide` (iOS 15); SwiftUI already
184
+ avoids the keyboard through the safe area.
185
+
186
+ **Stored insets.** Hits: a safe-area inset copied into a property at load
187
+ time.
188
+
189
+ Why: insets change with pose, bar placement and Split View position.
190
+
191
+ Replace: read them where layout happens, and react in
192
+ `viewSafeAreaInsetsDidChange()` / `safeAreaInsetsDidChange()`.
193
+
194
+ **Before replacing, classify.** An idiom or orientation check is a defect
195
+ only when it stands in for available space. A check for a genuine capability of
196
+ the device, or an orientation lock for a camera or game, stays.
197
+
198
+ ## 11. Recording the result
142
199
 
143
200
  For each hit, record `file:line`, the category, keep or fix, and for fixes the
144
201
  replacement. Separate "confirmed on the simulator" from "found in code only".