agentic-team 0.0.0-stage → 6.2.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 (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +189 -2
  3. package/bin/build-template.js +208 -0
  4. package/bin/index.js +1334 -0
  5. package/bin/platforms.js +510 -0
  6. package/package.json +41 -4
  7. package/template/.claude-plugin/plugin.json +10 -0
  8. package/template/.orchestra/README.md +391 -0
  9. package/template/.orchestra/blueprints/README.md +87 -0
  10. package/template/.orchestra/blueprints/api-only.md +46 -0
  11. package/template/.orchestra/blueprints/component-crud-resource.md +25 -0
  12. package/template/.orchestra/blueprints/saas-starter.md +69 -0
  13. package/template/.orchestra/config.yml +293 -0
  14. package/template/.orchestra/roles/orchestrator.md +105 -0
  15. package/template/.orchestra/roles/product-manager.md +439 -0
  16. package/template/.orchestra/scripts/compact-diff.awk +45 -0
  17. package/template/.orchestra/scripts/context.mjs +373 -0
  18. package/template/.orchestra/stacks/README.md +81 -0
  19. package/template/.orchestra/stacks/_lib/common.sh +3095 -0
  20. package/template/.orchestra/stacks/_repo/setup.sh +1113 -0
  21. package/template/.orchestra/stacks/backend-hono/claude/commands/endpoint.md +13 -0
  22. package/template/.orchestra/stacks/backend-hono/claude/skills/api-endpoint/SKILL.md +111 -0
  23. package/template/.orchestra/stacks/backend-hono/claude/skills/testing/SKILL.md +75 -0
  24. package/template/.orchestra/stacks/backend-hono/setup.sh +5945 -0
  25. package/template/.orchestra/stacks/backend-hono/stack.md +195 -0
  26. package/template/.orchestra/stacks/mobile-android-native/claude/commands/component.md +13 -0
  27. package/template/.orchestra/stacks/mobile-android-native/claude/skills/design-component/SKILL.md +58 -0
  28. package/template/.orchestra/stacks/mobile-android-native/setup.sh +607 -0
  29. package/template/.orchestra/stacks/mobile-android-native/stack.md +138 -0
  30. package/template/.orchestra/stacks/mobile-expo/claude/commands/component.md +12 -0
  31. package/template/.orchestra/stacks/mobile-expo/claude/skills/design-component/SKILL.md +92 -0
  32. package/template/.orchestra/stacks/mobile-expo/setup.sh +470 -0
  33. package/template/.orchestra/stacks/mobile-expo/stack.md +141 -0
  34. package/template/.orchestra/stacks/mobile-ios-native/claude/commands/component.md +12 -0
  35. package/template/.orchestra/stacks/mobile-ios-native/claude/skills/design-component/SKILL.md +58 -0
  36. package/template/.orchestra/stacks/mobile-ios-native/setup.sh +468 -0
  37. package/template/.orchestra/stacks/mobile-ios-native/stack.md +130 -0
  38. package/template/.orchestra/stacks/web-nextjs/claude/commands/component.md +12 -0
  39. package/template/.orchestra/stacks/web-nextjs/claude/skills/design-component/SKILL.md +100 -0
  40. package/template/.orchestra/stacks/web-nextjs/claude/skills/design-tokens/SKILL.md +61 -0
  41. package/template/.orchestra/stacks/web-nextjs/setup.sh +128 -0
  42. package/template/.orchestra/stacks/web-nextjs/stack.md +170 -0
  43. package/template/.orchestra/stacks/web-vite-react/claude/commands/component.md +12 -0
  44. package/template/.orchestra/stacks/web-vite-react/claude/skills/design-component/SKILL.md +100 -0
  45. package/template/.orchestra/stacks/web-vite-react/claude/skills/design-tokens/SKILL.md +61 -0
  46. package/template/.orchestra/stacks/web-vite-react/setup.sh +185 -0
  47. package/template/.orchestra/stacks/web-vite-react/stack.md +177 -0
  48. package/template/.orchestra/trackers/github.sh +467 -0
  49. package/template/CLAUDE.md +54 -0
  50. package/template/agents/architect.md +83 -0
  51. package/template/agents/engineer-high.md +17 -0
  52. package/template/agents/engineer-low.md +17 -0
  53. package/template/agents/engineer-max.md +17 -0
  54. package/template/agents/engineer-medium.md +17 -0
  55. package/template/agents/engineer-xhigh.md +17 -0
  56. package/template/agents/lead.md +925 -0
  57. package/template/agents/milestone-reviewer.md +65 -0
  58. package/template/agents/orchestrator.md +105 -0
  59. package/template/agents/product-manager.md +439 -0
  60. package/template/agents/re-reviewer.md +25 -0
  61. package/template/agents/reviewer.md +303 -0
  62. package/template/agents/scaffolder.md +206 -0
  63. package/template/agents/tracker.md +202 -0
  64. package/template/commands/blueprint.md +23 -0
  65. package/template/commands/create-role.md +64 -0
  66. package/template/commands/export.md +45 -0
  67. package/template/commands/help.md +56 -0
  68. package/template/commands/hotfix.md +14 -0
  69. package/template/commands/orchestrator.md +12 -0
  70. package/template/commands/pm.md +18 -0
  71. package/template/commands/rewind.md +63 -0
  72. package/template/commands/setup.md +15 -0
  73. package/template/commands/start.md +29 -0
  74. package/template/commands/status.md +11 -0
  75. package/template/commands/sync.md +76 -0
  76. package/template/commands/verifier.md +53 -0
  77. package/template/hooks/audit-context.sh +166 -0
  78. package/template/hooks/enforce-role.sh +193 -0
  79. package/template/hooks/protect-system-files.sh +123 -0
  80. package/template/hooks/review-rounds.sh +172 -0
  81. package/template/rules/acceptance-check.orchestra.md +11 -0
  82. package/template/rules/analytics-kpi.orchestra.md +56 -0
  83. package/template/rules/code-standards.orchestra.md +35 -0
  84. package/template/rules/comments.orchestra.md +77 -0
  85. package/template/rules/commit-format.orchestra.md +20 -0
  86. package/template/rules/dependency-upgrade.orchestra.md +112 -0
  87. package/template/rules/design-parity.orchestra.md +181 -0
  88. package/template/rules/observability.orchestra.md +121 -0
  89. package/template/rules/performance.orchestra.md +190 -0
  90. package/template/rules/phase-limits.orchestra.md +3 -0
  91. package/template/rules/pull-request.orchestra.md +218 -0
  92. package/template/rules/role-boundaries.orchestra.md +132 -0
  93. package/template/rules/security.orchestra.md +106 -0
  94. package/template/rules/stuck-detection.orchestra.md +25 -0
  95. package/template/rules/testing-standards.orchestra.md +219 -0
  96. package/template/rules/tracker-authority.orchestra.md +198 -0
  97. package/template/rules/verification-gate.orchestra.md +56 -0
  98. package/template/skills/accessibility/SKILL.md +31 -0
  99. package/template/skills/android-testing/SKILL.md +112 -0
  100. package/template/skills/architecture/SKILL.md +175 -0
  101. package/template/skills/auth-setup/SKILL.md +105 -0
  102. package/template/skills/best-practices/SKILL.md +29 -0
  103. package/template/skills/code-optimizer/SKILL.md +27 -0
  104. package/template/skills/core-web-vitals/SKILL.md +32 -0
  105. package/template/skills/crud-api/SKILL.md +32 -0
  106. package/template/skills/debug/SKILL.md +28 -0
  107. package/template/skills/deployment/SKILL.md +104 -0
  108. package/template/skills/error-handling/SKILL.md +154 -0
  109. package/template/skills/frontend-design/SKILL.md +30 -0
  110. package/template/skills/fullstack-infrastructure/SKILL.md +108 -0
  111. package/template/skills/ios-testing/SKILL.md +100 -0
  112. package/template/skills/mobile-testing/SKILL.md +102 -0
  113. package/template/skills/observability/SKILL.md +91 -0
  114. package/template/skills/project-structure/SKILL.md +114 -0
  115. package/template/skills/push-notifications/SKILL.md +146 -0
  116. package/template/skills/react-best-practices/SKILL.md +31 -0
  117. package/template/skills/realtime/SKILL.md +146 -0
  118. package/template/skills/review/SKILL.md +82 -0
  119. package/template/skills/testing/SKILL.md +45 -0
  120. package/template/skills/web-quality-audit/SKILL.md +47 -0
  121. package/template/skills/web-testing/SKILL.md +120 -0
@@ -0,0 +1,468 @@
1
+ #!/usr/bin/env bash
2
+ # Native iOS — SwiftUI, generated from an XcodeGen manifest.
3
+ #
4
+ # The project is generated from project.yml, never hand-edited as a .pbxproj.
5
+ # A binary project file cannot be reviewed in a diff and corrupts under
6
+ # concurrent edits — both fatal for an agent-driven workflow.
7
+ set -euo pipefail
8
+
9
+ DIR="${1:?usage: setup.sh <target-dir>}"
10
+ RAW_NAME="${2:-$(basename "$DIR")}"
11
+ # Swift type names cannot contain dashes.
12
+ NAME="$(printf '%s' "$RAW_NAME" | sed 's/[^A-Za-z0-9]//g')"
13
+ [ -n "$NAME" ] || NAME="App"
14
+
15
+ LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../_lib" && pwd)"
16
+ # shellcheck source=../_lib/common.sh
17
+ source "$LIB/common.sh"
18
+
19
+ command -v xcodegen >/dev/null 2>&1 || die "xcodegen not found. Install it first: brew install xcodegen"
20
+ command -v xcodebuild >/dev/null 2>&1 || die "xcodebuild not found. Install Xcode, then: xcode-select --install"
21
+
22
+ # Resolved now, from each project's latest release. A version written from
23
+ # memory is the single most reliable way to waste a debugging cycle.
24
+ resolve_version() {
25
+ local repo="$1" fallback="$2" tag
26
+ tag="$(curl -fsSL "https://api.github.com/repos/${repo}/releases/latest" 2>/dev/null \
27
+ | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{process.stdout.write(String(JSON.parse(s).tag_name||""))}catch{}})' 2>/dev/null || true)"
28
+ tag="${tag#v}"
29
+ case "$tag" in
30
+ ''|*[!0-9.]*) echo "$fallback" ;;
31
+ *) echo "$tag" ;;
32
+ esac
33
+ }
34
+ SENTRY_VERSION="$(resolve_version getsentry/sentry-cocoa 9.26.0)"
35
+ POSTHOG_VERSION="$(resolve_version PostHog/posthog-ios 3.69.10)"
36
+ log "sentry-cocoa ${SENTRY_VERSION}, posthog-ios ${POSTHOG_VERSION}"
37
+
38
+ log "scaffolding native iOS into $DIR (target $NAME)"
39
+ mkdir -p "$DIR"/Sources/{App,DesignSystem,Observability} "$DIR"/Tests/DesignSystemTests "$DIR"/UITests "$DIR"/Resources
40
+ cd "$DIR"
41
+
42
+ cat > project.yml <<YML
43
+ name: ${NAME}
44
+ options:
45
+ bundleIdPrefix: com.example
46
+ deploymentTarget:
47
+ iOS: "17.0"
48
+ # Resolved from each project's releases at scaffold time, never recalled.
49
+ # Pinned to a major with upToNextMajor so a patch arrives without a decision
50
+ # and a breaking change does not.
51
+ packages:
52
+ Sentry:
53
+ url: https://github.com/getsentry/sentry-cocoa
54
+ majorVersion: ${SENTRY_VERSION}
55
+ PostHog:
56
+ url: https://github.com/PostHog/posthog-ios
57
+ majorVersion: ${POSTHOG_VERSION}
58
+ targets:
59
+ ${NAME}:
60
+ type: application
61
+ platform: iOS
62
+ sources: [Sources]
63
+ resources: [Resources]
64
+ dependencies:
65
+ - package: Sentry
66
+ - package: PostHog
67
+ info:
68
+ path: Resources/Info.plist
69
+ properties:
70
+ UILaunchScreen: {}
71
+ # Fed from build settings, which are fed from the environment. Absent
72
+ # in a checkout with no credentials, and the app says so rather than
73
+ # crashing or reporting nowhere in silence.
74
+ SentryDSN: \$(SENTRY_DSN)
75
+ PostHogAPIKey: \$(POSTHOG_API_KEY)
76
+ PostHogHost: \$(POSTHOG_HOST)
77
+ AppEnvironment: \$(APP_ENVIRONMENT)
78
+ settings:
79
+ base:
80
+ SWIFT_STRICT_CONCURRENCY: complete
81
+ GENERATE_INFOPLIST_FILE: NO
82
+ SENTRY_DSN: \$(SENTRY_DSN)
83
+ POSTHOG_API_KEY: \$(POSTHOG_API_KEY)
84
+ POSTHOG_HOST: \$(POSTHOG_HOST)
85
+ APP_ENVIRONMENT: \$(APP_ENVIRONMENT)
86
+ ${NAME}Tests:
87
+ type: bundle.unit-test
88
+ platform: iOS
89
+ sources: [Tests]
90
+ settings:
91
+ base:
92
+ # The test bundle needs its own Info.plist to code sign. Generating
93
+ # it is correct here — only the app target ships a real one.
94
+ GENERATE_INFOPLIST_FILE: YES
95
+ dependencies:
96
+ - target: ${NAME}
97
+ ${NAME}UITests:
98
+ type: bundle.ui-testing
99
+ platform: iOS
100
+ sources: [UITests]
101
+ settings:
102
+ base:
103
+ GENERATE_INFOPLIST_FILE: YES
104
+ dependencies:
105
+ - target: ${NAME}
106
+ # xcodegen does generate a scheme covering both test bundles on its own, so
107
+ # this is not a fix for anything currently broken. It is written down because
108
+ # the alternative is depending on a default: which targets a generated scheme
109
+ # includes is xcodegen's choice, and a change there would silently stop the
110
+ # UI tests running while the gate kept reporting green.
111
+ schemes:
112
+ ${NAME}:
113
+ build:
114
+ targets:
115
+ ${NAME}: all
116
+ test:
117
+ targets:
118
+ - ${NAME}Tests
119
+ - ${NAME}UITests
120
+ YML
121
+
122
+ cat > Resources/Info.plist <<'PLIST'
123
+ <?xml version="1.0" encoding="UTF-8"?>
124
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
125
+ <plist version="1.0">
126
+ <dict>
127
+ <key>UILaunchScreen</key>
128
+ <dict/>
129
+ </dict>
130
+ </plist>
131
+ PLIST
132
+
133
+ cat > Sources/App/App.swift <<SWIFT
134
+ import SwiftUI
135
+
136
+ @main
137
+ struct ${NAME}App: App {
138
+ init() {
139
+ // Before the first view, so a crash during startup is still reported.
140
+ Observability.start()
141
+ }
142
+
143
+ var body: some Scene {
144
+ WindowGroup {
145
+ ContentView()
146
+ // Every screen reports a view. At the screen, not in a
147
+ // wrapper three layers away that eventually fires for the
148
+ // wrong reason.
149
+ .onAppear { analytics.capture(.screenViewed(name: "home")) }
150
+ }
151
+ }
152
+ }
153
+
154
+ private let analytics: AnalyticsSink = PostHogSink()
155
+
156
+ struct ContentView: View {
157
+ var body: some View {
158
+ Text("${NAME}")
159
+ .padding(Tokens.Spacing.gutter)
160
+ }
161
+ }
162
+ SWIFT
163
+
164
+ mkdir -p Sources/Observability Tests/ObservabilityTests
165
+
166
+ cat > Sources/Observability/Events.swift <<'SWIFT'
167
+ import Foundation
168
+
169
+ /// Every analytics event this app can send, and its payload.
170
+ ///
171
+ /// One place, and a closed enum rather than strings at call sites. A string
172
+ /// literal is how analytics quietly rots: the name is changed here, the call
173
+ /// site still compiles, and the funnel goes silent with nobody the wiser.
174
+ ///
175
+ /// Naming is `object_action` and never mixes styles. See
176
+ /// .claude/rules/analytics-kpi.orchestra.md.
177
+ public enum AnalyticsEvent: Equatable, Sendable {
178
+ /// Every screen reports a view. `name` is the screen, not the file.
179
+ case screenViewed(name: String)
180
+ /// A critical flow starting, succeeding, and failing — with a reason.
181
+ /// A funnel that only records success cannot say why people left.
182
+ case flowStarted(flow: String)
183
+ case flowCompleted(flow: String)
184
+ case flowFailed(flow: String, reason: String)
185
+
186
+ public var name: String {
187
+ switch self {
188
+ case .screenViewed: return "screen_viewed"
189
+ case .flowStarted: return "flow_started"
190
+ case .flowCompleted: return "flow_completed"
191
+ case .flowFailed: return "flow_failed"
192
+ }
193
+ }
194
+
195
+ /// No PII. No email, no name, nothing the user typed — the reason on a
196
+ /// failure is a code, not a message.
197
+ public var properties: [String: String] {
198
+ switch self {
199
+ case let .screenViewed(name): return ["screen": name]
200
+ case let .flowStarted(flow): return ["flow": flow]
201
+ case let .flowCompleted(flow): return ["flow": flow]
202
+ case let .flowFailed(flow, reason): return ["flow": flow, "reason": reason]
203
+ }
204
+ }
205
+ }
206
+
207
+ /// The seam. Production sends to PostHog; a test captures instead.
208
+ public protocol AnalyticsSink: Sendable {
209
+ func capture(_ event: AnalyticsEvent)
210
+ }
211
+ SWIFT
212
+
213
+ cat > Sources/Observability/Observability.swift <<'SWIFT'
214
+ import Foundation
215
+ import PostHog
216
+ import Sentry
217
+
218
+ /// Error monitoring and analytics, started once at launch.
219
+ ///
220
+ /// Both are configured from Info.plist rather than hardcoded, and both are
221
+ /// skipped when their key is absent — a checkout with no credentials has to
222
+ /// build, run and test, or nobody can work on the app without production
223
+ /// secrets. Absent is logged, not silent: see
224
+ /// .claude/rules/observability.orchestra.md.
225
+ public enum Observability {
226
+ /// Set by the build. Release tagging is required — an error you cannot
227
+ /// tie to a build is an error you cannot fix.
228
+ public static var release: String {
229
+ let info = Bundle.main.infoDictionary
230
+ let version = info?["CFBundleShortVersionString"] as? String ?? "0"
231
+ let build = info?["CFBundleVersion"] as? String ?? "0"
232
+ return "\(version)+\(build)"
233
+ }
234
+
235
+ private static func setting(_ key: String) -> String? {
236
+ guard let value = Bundle.main.object(forInfoDictionaryKey: key) as? String,
237
+ !value.isEmpty,
238
+ // xcconfig substitutes an empty variable as the literal name.
239
+ !value.hasPrefix("$(")
240
+ else { return nil }
241
+ return value
242
+ }
243
+
244
+ public static func start() {
245
+ if let dsn = setting("SentryDSN") {
246
+ SentrySDK.start { options in
247
+ options.dsn = dsn
248
+ options.releaseName = release
249
+ options.environment = setting("AppEnvironment") ?? "development"
250
+ // Scrub before send, not after. A crash report is not a place
251
+ // for anything the user typed.
252
+ options.beforeSend = { event in
253
+ event.user?.email = nil
254
+ event.user?.username = nil
255
+ event.user?.ipAddress = nil
256
+ return event
257
+ }
258
+ }
259
+ } else {
260
+ print("[observability] no SentryDSN in Info.plist — crash reporting is off")
261
+ }
262
+
263
+ if let key = setting("PostHogAPIKey") {
264
+ let config = PostHogConfig(projectToken: key, host: setting("PostHogHost") ?? "https://eu.i.posthog.com")
265
+ // Identify with a stable anonymous id; never with an email.
266
+ config.personProfiles = .identifiedOnly
267
+ PostHogSDK.shared.setup(config)
268
+ } else {
269
+ print("[observability] no PostHogAPIKey in Info.plist — analytics is off")
270
+ }
271
+ }
272
+ }
273
+
274
+ /// Sends to PostHog when it is configured, and nowhere when it is not.
275
+ public struct PostHogSink: AnalyticsSink {
276
+ public init() {}
277
+ public func capture(_ event: AnalyticsEvent) {
278
+ PostHogSDK.shared.capture(event.name, properties: event.properties)
279
+ }
280
+ }
281
+ SWIFT
282
+
283
+ cat > Tests/ObservabilityTests/EventsTests.swift <<'SWIFT'
284
+ import XCTest
285
+ @testable import __NAME__
286
+
287
+ /// Instrumentation is code, so it is tested like code.
288
+ ///
289
+ /// The sink is the seam: this drives the real event type through a capturing
290
+ /// implementation, so a renamed case or a changed payload fails here rather
291
+ /// than in a dashboard three weeks later.
292
+ final class CapturingSink: AnalyticsSink, @unchecked Sendable {
293
+ private(set) var captured: [AnalyticsEvent] = []
294
+ func capture(_ event: AnalyticsEvent) { captured.append(event) }
295
+ }
296
+
297
+ final class EventsTests: XCTestCase {
298
+ func testScreenViewFiresOnceWithTheScreenName() {
299
+ let sink = CapturingSink()
300
+ sink.capture(.screenViewed(name: "home"))
301
+
302
+ XCTAssertEqual(sink.captured.count, 1)
303
+ XCTAssertEqual(sink.captured.first?.name, "screen_viewed")
304
+ XCTAssertEqual(sink.captured.first?.properties, ["screen": "home"])
305
+ }
306
+
307
+ func testEveryEventNameIsObjectAction() {
308
+ // Boring and consistent, decided once. A mixed convention is
309
+ // unqueryable six months later.
310
+ let events: [AnalyticsEvent] = [
311
+ .screenViewed(name: "home"),
312
+ .flowStarted(flow: "signup"),
313
+ .flowCompleted(flow: "signup"),
314
+ .flowFailed(flow: "signup", reason: "network"),
315
+ ]
316
+ for event in events {
317
+ XCTAssertTrue(
318
+ event.name.contains("_"),
319
+ "\(event.name) is not object_action"
320
+ )
321
+ XCTAssertEqual(event.name, event.name.lowercased())
322
+ }
323
+ }
324
+
325
+ func testAFailureCarriesAReasonCode() {
326
+ // A funnel that only records success cannot tell you why people left,
327
+ // which is the only thing you actually wanted to know.
328
+ let event = AnalyticsEvent.flowFailed(flow: "checkout", reason: "card_declined")
329
+ XCTAssertEqual(event.properties["reason"], "card_declined")
330
+ }
331
+
332
+ func testNoEventCarriesFreeTextTheUserTyped() {
333
+ // Property values are identifiers, not sentences. The check is crude
334
+ // on purpose: a space is the cheapest signal that something typed has
335
+ // leaked into a property.
336
+ let events: [AnalyticsEvent] = [
337
+ .screenViewed(name: "home"),
338
+ .flowFailed(flow: "checkout", reason: "card_declined"),
339
+ ]
340
+ for event in events {
341
+ for (key, value) in event.properties {
342
+ XCTAssertFalse(value.contains(" "), "\(key) looks like free text: \(value)")
343
+ }
344
+ }
345
+ }
346
+ }
347
+ SWIFT
348
+ sed -i.bak "s/__NAME__/${NAME}/" Tests/ObservabilityTests/EventsTests.swift
349
+ rm -f Tests/ObservabilityTests/EventsTests.swift.bak
350
+
351
+ cat > Sources/DesignSystem/Tokens.swift <<'SWIFT'
352
+ import SwiftUI
353
+
354
+ /// Design tokens. Populated from the Claude Design source before any
355
+ /// component is written. Color is one axis of several — do not stop there.
356
+ public enum Tokens {
357
+ public enum Spacing {
358
+ public static let gutter: CGFloat = 16
359
+ }
360
+ public enum Radius {
361
+ public static let control: CGFloat = 8
362
+ public static let surface: CGFloat = 12
363
+ }
364
+ public enum Motion {
365
+ public static let fast: Double = 0.12
366
+ }
367
+ }
368
+ SWIFT
369
+
370
+ cat > Tests/DesignSystemTests/TokensTests.swift <<SWIFT
371
+ import XCTest
372
+ @testable import ${NAME}
373
+
374
+ final class TokensTests: XCTestCase {
375
+ func testSpacingScaleIsPositive() {
376
+ XCTAssertGreaterThan(Tokens.Spacing.gutter, 0)
377
+ }
378
+
379
+ func testControlRadiusIsSmallerThanSurface() {
380
+ XCTAssertLessThan(Tokens.Radius.control, Tokens.Radius.surface)
381
+ }
382
+ }
383
+ SWIFT
384
+
385
+ # The highest-value single check on iOS: the app launches.
386
+ #
387
+ # No XCTest sees a missing Info.plist key, an entitlement the profile lacks,
388
+ # a @main that throws before the first view, or a resource that was never
389
+ # copied. The unit suite is green and the app dies on the splash screen.
390
+ cat > UITests/LaunchTests.swift <<'SWIFT'
391
+ import XCTest
392
+
393
+ final class LaunchTests: XCTestCase {
394
+ override func setUp() {
395
+ super.setUp()
396
+ continueAfterFailure = false
397
+ }
398
+
399
+ func testAppLaunches() {
400
+ let app = XCUIApplication()
401
+ app.launch()
402
+
403
+ // Not a screenshot and not a copy match — either would need editing
404
+ // the day the design changes. This asserts the app reached a running
405
+ // state with a rendered window.
406
+ XCTAssertTrue(
407
+ app.wait(for: .runningForeground, timeout: 30),
408
+ "the app did not reach the foreground"
409
+ )
410
+ XCTAssertTrue(app.windows.firstMatch.exists, "no window was rendered")
411
+ }
412
+ }
413
+ SWIFT
414
+
415
+ cat > .swift-format <<'JSON'
416
+ {
417
+ "indentation": { "spaces": 4 },
418
+ "lineLength": 100
419
+ }
420
+ JSON
421
+
422
+ cat > verify.sh <<'V'
423
+ #!/usr/bin/env bash
424
+ # Normalised verification gate. Orchestra runs this instead of the npm
425
+ # commands in config.yml, because this app is not npm-based.
426
+ set -euo pipefail
427
+
428
+ proj="$(find . -maxdepth 1 -name '*.xcodeproj' | head -1)"
429
+ [ -n "$proj" ] || { echo "no .xcodeproj here — run 'xcodegen generate' first" >&2; exit 1; }
430
+ SCHEME="${SCHEME:-$(basename "$proj" .xcodeproj)}"
431
+ DEST="${DEST:-generic/platform=iOS Simulator}"
432
+
433
+ echo "── build"
434
+ xcodebuild build -scheme "$SCHEME" -destination "$DEST" -quiet
435
+
436
+ echo "── test"
437
+ # Resolve a concrete simulator by UDID. Grepping the human-readable device
438
+ # list yields names with trailing whitespace, which xcodebuild rejects with
439
+ # "Unable to find a device matching the provided destination specifier".
440
+ if [ -n "${SIM_UDID:-}" ]; then
441
+ udid="$SIM_UDID"
442
+ else
443
+ udid="$(xcrun simctl list devices available -j | node -e '
444
+ let s = "";
445
+ process.stdin.on("data", (d) => (s += d)).on("end", () => {
446
+ const j = JSON.parse(s);
447
+ const all = Object.values(j.devices).flat()
448
+ .filter((d) => d.isAvailable && /^iPhone/.test(d.name));
449
+ process.stdout.write(all[0] ? all[0].udid : "");
450
+ });
451
+ ')"
452
+ fi
453
+ [ -n "$udid" ] || { echo "no iPhone simulator available — set SIM_UDID" >&2; exit 1; }
454
+ xcodebuild test -scheme "$SCHEME" -destination "id=$udid" -quiet
455
+
456
+ echo "── format"
457
+ if command -v swift-format >/dev/null 2>&1; then
458
+ swift-format lint -r Sources Tests
459
+ else
460
+ echo "swift-format not installed — skipped"
461
+ fi
462
+ V
463
+ chmod +x verify.sh
464
+
465
+ xcodegen generate
466
+
467
+ log "mobile-ios-native ready in $DIR"
468
+ warn "verification runs through ./verify.sh, not npm — config.yml reflects that"
@@ -0,0 +1,130 @@
1
+ # Mobile — Native iOS (Swift)
2
+
3
+ ## Who you are
4
+
5
+ You are a **senior native iOS engineer**. Not a web developer writing
6
+ Swift. You know the platform's conventions and you follow them, because an
7
+ app that ignores them feels wrong to users in ways they cannot name.
8
+
9
+ You think in value types, you understand the main actor, and you treat
10
+ retain cycles and unstructured concurrency as bugs you catch yourself —
11
+ not things review finds for you.
12
+
13
+ SOLID, KISS, YAGNI. No overengineering: no protocol with one conformer, no
14
+ coordinator for two screens.
15
+
16
+ ## Project generation
17
+
18
+ The Xcode project is **generated from `project.yml` by XcodeGen**. Never
19
+ hand-edit a `.pbxproj`.
20
+
21
+ This is not a style preference. A binary project file cannot be reviewed in
22
+ a diff, and it corrupts when two changes land at once — both fatal when
23
+ agents are doing the editing. If the project needs a new target or setting,
24
+ edit `project.yml` and regenerate.
25
+
26
+ ## Structure
27
+
28
+ ```
29
+ Sources/
30
+ ├── App/ # features, screens, navigation
31
+ └── DesignSystem/ # the UI kit. no business logic. ever.
32
+ ├── Tokens.swift
33
+ └── Button/
34
+ ├── Button.swift
35
+ └── ButtonStyle.swift
36
+ Tests/
37
+ ├── AppTests/
38
+ └── DesignSystemTests/
39
+ ```
40
+
41
+ Every component gets its own folder under `DesignSystem/`, mirroring the
42
+ web and Expo stacks — one mental model across the repo.
43
+
44
+ `DesignSystem/` contains nothing that is not design. A view may hold logic
45
+ about itself; never about your product.
46
+
47
+ ## Order of work
48
+
49
+ Tokens before components. Extract every axis from the design source —
50
+ color, spacing, typography, radii, shadows, motion — into `Tokens.swift`
51
+ and the asset catalog. Semantic colors resolve per theme; views reference
52
+ `Color.surface`, never a raw hex or palette entry.
53
+
54
+ A view with a hardcoded `Color(hex:)` or `padding(13)` is a bug.
55
+
56
+ ## No ready-made components
57
+
58
+ The kit is built from SwiftUI primitives. No third-party UI package.
59
+
60
+ SwiftUI is the platform, the way UIKit was — it is the substrate, not a UI
61
+ kit. Wrapping its primitives in our own `DesignSystem` components is the
62
+ job; adopting an outside component library is not.
63
+
64
+ ## Swift rules
65
+
66
+ - Value types by default. Reach for a class when identity or reference
67
+ semantics are genuinely required.
68
+ - Strict concurrency is on. Fix the warnings; do not silence them with
69
+ `@unchecked Sendable`.
70
+ - UI work on the main actor, explicitly.
71
+ - No force unwrap and no `try!` outside tests. Model absence honestly.
72
+ - `weak self` in escaping closures that capture self.
73
+
74
+ ## Memory — ARC does not collect cycles
75
+
76
+ There is no tracing collector here. A reference cycle is never freed, and
77
+ nothing warns you. So:
78
+
79
+ - A closure stored by an object — a handler, a callback, a Combine sink —
80
+ captures `self` weakly. The rule above, applied wherever the closure
81
+ outlives the call.
82
+ - Delegates and parent references are `weak`. The child never owns the
83
+ parent.
84
+ - A `Task` stored in a property is cancelled in `deinit`. A detached task
85
+ that captures `self` strongly keeps the object alive until the task ends.
86
+ - Combine subscriptions live in a `Set<AnyCancellable>` owned by the
87
+ subscriber, so they end when it does.
88
+ - **Every view model has a `deinit` test**: create it, hold it weakly,
89
+ release the strong reference, assert nil. It is five lines, and it is the
90
+ only thing that catches a cycle before a user does.
91
+ - Instruments' Leaks and the memory graph debugger before a release — a
92
+ simulator run with the debugger attached, not an audit from reading.
93
+
94
+ ## SwiftUI
95
+
96
+ - Small views. A body longer than a screen wants decomposing.
97
+ - State at the lowest level that can own it.
98
+ - `@Observable` over `ObservableObject` on modern deployment targets.
99
+ - Do not fight the framework with `GeometryReader` where a layout modifier
100
+ exists.
101
+
102
+ ## Testing
103
+
104
+ Coverage target is effectively total. The deciding rule:
105
+
106
+ > If one of our functions calls into a third-party library, that function
107
+ > has a test. When a dependency updates and behavior changes, the test
108
+ > must fail.
109
+
110
+ - XCTest for logic. Snapshot tests (`swift-snapshot-testing`) for views.
111
+ - Snapshots catch visual drift and nothing else. Behavior needs its own
112
+ tests — a snapshot passes while a button stops responding.
113
+ - Test both light and dark, and the largest Dynamic Type size.
114
+
115
+ ## Accessibility
116
+
117
+ Structural. Every interactive view has a label, a trait, and a value where
118
+ one applies. VoiceOver navigation must be sensible in order, not just
119
+ present. Dynamic Type must not break layout. Minimum touch target 44×44pt.
120
+
121
+ ## Tooling
122
+
123
+ `swift-format` with the checked-in configuration. Verification runs through
124
+ `xcodebuild`, not npm — `.orchestra/config.yml` reflects that.
125
+
126
+ ## Toolchain
127
+
128
+ Xcode and XcodeGen (`brew install xcodegen`). The scaffold builds and tests
129
+ as generated — `./verify.sh` runs build, test on a simulator, and format.
130
+ Verified against a real simulator run.
@@ -0,0 +1,12 @@
1
+ Scaffold a new design system component.
2
+
3
+ Usage: `/web:component {Name}`
4
+
5
+ 1. Load the `design-component` skill and follow it exactly.
6
+ 2. Confirm every value the component needs has a token. If one is missing,
7
+ add it to `src/design/tokens.css` first — do not hardcode.
8
+ 3. Create `src/design/{Name}/` with the component, types, tests, fixture,
9
+ and `index.ts`.
10
+ 4. Export it from `src/design/index.ts`.
11
+ 5. Run `npm run typecheck && npm test && npm run lint`.
12
+ 6. Report the a11y checklist with each item explicitly checked or flagged.