@3fn/core 13.0.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/.kiro/agents/ada-prompt.md +80 -132
  2. package/.kiro/agents/ada-prompt.md.attribution.json +45 -0
  3. package/.kiro/agents/ada.json +44 -59
  4. package/.kiro/agents/ada.json.attribution.json +13 -0
  5. package/.kiro/agents/data-prompt.md +83 -74
  6. package/.kiro/agents/data-prompt.md.attribution.json +53 -0
  7. package/.kiro/agents/data.json +31 -38
  8. package/.kiro/agents/data.json.attribution.json +13 -0
  9. package/.kiro/agents/kenya-prompt.md +83 -72
  10. package/.kiro/agents/kenya-prompt.md.attribution.json +53 -0
  11. package/.kiro/agents/kenya.json +27 -35
  12. package/.kiro/agents/kenya.json.attribution.json +13 -0
  13. package/.kiro/agents/leonardo-prompt.md +176 -234
  14. package/.kiro/agents/leonardo-prompt.md.attribution.json +45 -0
  15. package/.kiro/agents/leonardo.json +28 -36
  16. package/.kiro/agents/leonardo.json.attribution.json +13 -0
  17. package/.kiro/agents/lina-prompt.md +110 -151
  18. package/.kiro/agents/lina-prompt.md.attribution.json +53 -0
  19. package/.kiro/agents/lina.json +46 -59
  20. package/.kiro/agents/lina.json.attribution.json +13 -0
  21. package/.kiro/agents/sparky-prompt.md +89 -72
  22. package/.kiro/agents/sparky-prompt.md.attribution.json +53 -0
  23. package/.kiro/agents/sparky.json +37 -37
  24. package/.kiro/agents/sparky.json.attribution.json +13 -0
  25. package/.kiro/agents/stacy-prompt.md +74 -48
  26. package/.kiro/agents/stacy-prompt.md.attribution.json +45 -0
  27. package/.kiro/agents/stacy.json +28 -30
  28. package/.kiro/agents/stacy.json.attribution.json +13 -0
  29. package/.kiro/agents/thurgood-prompt.md +94 -138
  30. package/.kiro/agents/thurgood-prompt.md.attribution.json +45 -0
  31. package/.kiro/agents/thurgood.json +31 -35
  32. package/.kiro/agents/thurgood.json.attribution.json +13 -0
  33. package/.kiro/steering/AI-Collaboration-Principles.md +3 -3
  34. package/.kiro/steering/Civitas-System-Overview.md +5 -5
  35. package/.kiro/steering/DesignerPunk-Systems-Overview.md +6 -6
  36. package/.kiro/steering/Task-Completion-Protocol.md +98 -17
  37. package/.kiro/steering/core-goals.md +3 -3
  38. package/.kiro/steering/personal-note.md +1 -1
  39. package/.kiro/steering/start-up-tasks.md +14 -3
  40. package/application-mcp-server/src/index.ts +26 -0
  41. package/dist/ComponentTokens.android.kt +1 -1
  42. package/dist/ComponentTokens.ios.swift +1 -1
  43. package/dist/ComponentTokens.web.css +1 -1
  44. package/dist/DesignTokens.android.kt +1 -1
  45. package/dist/DesignTokens.dtcg.json +8 -5
  46. package/dist/DesignTokens.figma.json +2 -2
  47. package/dist/DesignTokens.ios.swift +1 -1
  48. package/dist/DesignTokens.web.css +1 -1
  49. package/dist/android/DesignTokens.android.kt +1 -1
  50. package/dist/blend/OklchBlendCalculator.js +1 -0
  51. package/dist/blend/ThemeAwareBlendUtilities.web.d.ts +13 -2
  52. package/dist/blend/ThemeAwareBlendUtilities.web.js +6 -1
  53. package/dist/browser/designerpunk.esm.js +22 -81
  54. package/dist/browser/designerpunk.esm.min.js +29 -32
  55. package/dist/browser/designerpunk.umd.js +22 -81
  56. package/dist/browser/designerpunk.umd.min.js +42 -45
  57. package/dist/browser/tokens.css +1 -1
  58. package/dist/components/core/Avatar-Base/platforms/web/Avatar.web.js +24 -5
  59. package/dist/components/core/Button-CTA/examples/BasicUsage.d.ts +16 -28
  60. package/dist/components/core/Button-CTA/examples/BasicUsage.js +18 -43
  61. package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.d.ts +3 -15
  62. package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.js +9 -58
  63. package/dist/components/core/Button-CTA/types.d.ts +0 -24
  64. package/dist/components/core/Button-CTA/types.js +6 -0
  65. package/dist/components/core/Input-Text-Base/types.d.ts +13 -1
  66. package/dist/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.js +11 -2
  67. package/dist/generators/DTCGFormatGenerator.js +8 -0
  68. package/dist/integration/BuildErrorHandler.js +2 -2
  69. package/dist/ios/DesignTokens.ios.swift +1 -1
  70. package/dist/mcp/application-mcp.js +24 -0
  71. package/dist/mcp/docs-mcp.js +130 -15
  72. package/dist/mcp/product-mcp.js +25 -0
  73. package/dist/tokens/OpacityTokens.js +1 -1
  74. package/dist/tokens/semantic/BlendTokens.d.ts +10 -3
  75. package/dist/tokens/semantic/BlendTokens.js +17 -5
  76. package/dist/tokens/semantic/OpacityTokens.d.ts +4 -4
  77. package/dist/tokens/semantic/OpacityTokens.js +4 -4
  78. package/dist/types/ComponentTypes.d.ts +1 -1
  79. package/dist/types/generated/TokenTypes.d.ts +1 -1
  80. package/dist/types/generated/TokenTypes.js +1 -1
  81. package/dist/validators/StemmaTokenUsageValidator.js +3 -2
  82. package/dist/web/DesignTokens.web.css +1 -1
  83. package/governance/Component-Development-Guide.md +22 -12
  84. package/governance/Component-Development-Standards.md +2 -2
  85. package/governance/Component-Family-Avatar.md +0 -1
  86. package/governance/Component-Family-Badge.md +0 -1
  87. package/governance/Component-Family-Button.md +4 -17
  88. package/governance/Component-Family-Chip.md +0 -1
  89. package/governance/Component-Family-Container.md +0 -1
  90. package/governance/Component-Family-Data-Display.md +1 -2
  91. package/governance/Component-Family-Divider.md +1 -2
  92. package/governance/Component-Family-Form-Inputs.md +5 -5
  93. package/governance/Component-Family-Icon.md +1 -2
  94. package/governance/Component-Family-Loading.md +1 -2
  95. package/governance/Component-Family-Modal.md +1 -2
  96. package/governance/Component-Family-Navigation.md +0 -1
  97. package/governance/Component-Family-Progress.md +0 -1
  98. package/governance/Component-Inheritance-Structures.md +195 -76
  99. package/governance/Component-MCP-Document-Template.md +6 -5
  100. package/governance/Component-Primitive-vs-Semantic-Philosophy.md +1 -1
  101. package/governance/Component-Quick-Reference.md +33 -33
  102. package/governance/Component-Readiness-Status.md +54 -38
  103. package/governance/Component-Templates.md +32 -36
  104. package/governance/Contract-System-Reference.md +6 -6
  105. package/governance/MCP-Integration-Guide.md +1 -1
  106. package/governance/Process-Cross-Reference-Standards.md +31 -13
  107. package/governance/Process-Development-Workflow.md +49 -59
  108. package/governance/Process-File-Organization.md +24 -24
  109. package/governance/Process-Hook-Operations.md +19 -7
  110. package/governance/Process-Orchestration-Model-Selection.md +92 -0
  111. package/governance/Process-Spec-Planning.md +91 -39
  112. package/governance/Process-Task-Type-Definitions.md +80 -4
  113. package/governance/Product-Handoff-Protocol.md +2 -0
  114. package/governance/Rosetta-System-Architecture.md +6 -6
  115. package/governance/Test-Behavioral-Contract-Validation.md +38 -31
  116. package/governance/Test-Failure-Audit-Methodology.md +1 -1
  117. package/governance/Token-Family-Accessibility.md +1 -2
  118. package/governance/Token-Family-Blend.md +18 -16
  119. package/governance/Token-Family-Blur.md +0 -1
  120. package/governance/Token-Family-Border.md +1 -2
  121. package/governance/Token-Family-Color.md +0 -1
  122. package/governance/Token-Family-Glow.md +1 -2
  123. package/governance/Token-Family-Layering.md +0 -1
  124. package/governance/Token-Family-Motion.md +1 -2
  125. package/governance/Token-Family-Opacity.md +0 -1
  126. package/governance/Token-Family-Radius.md +1 -2
  127. package/governance/Token-Family-Responsive.md +1 -2
  128. package/governance/Token-Family-Shadow.md +1 -2
  129. package/governance/Token-Family-Sizing.md +0 -1
  130. package/governance/Token-Family-Spacing.md +1 -2
  131. package/governance/Token-Family-Typography.md +1 -2
  132. package/governance/Token-Governance.md +8 -8
  133. package/governance/Token-Quick-Reference.md +21 -21
  134. package/governance/Token-Resolution-Patterns.md +1 -1
  135. package/governance/Token-Semantic-Structure.md +1 -1
  136. package/governance/Web-Authoring-Standards.md +5 -5
  137. package/governance/browser-distribution-guide.md +1 -4
  138. package/governance/classification-map.md +368 -0
  139. package/governance/completion-documentation-guide.md +11 -8
  140. package/governance/component-meta-authoring-guide.md +1 -1
  141. package/governance/cross-platform-vs-platform-specific-decision-framework.md +1 -1
  142. package/governance/platform-implementation-guidelines.md +1 -1
  143. package/governance/release-management-system.md +2 -2
  144. package/governance/rosetta-system-principles.md +8 -6
  145. package/governance/stemma-system-principles.md +18 -17
  146. package/mcp-server/src/index.ts +24 -6
  147. package/mcp-server/src/indexer/DocumentIndexer.ts +119 -9
  148. package/mcp-server/src/indexer/__tests__/bare-id-crossrefs.test.ts +250 -0
  149. package/mcp-server/src/indexer/cross-ref-parser.ts +29 -1
  150. package/mcp-server/src/indexer/index-health.ts +27 -2
  151. package/mcp-server/src/query/__tests__/find-docs-calibration.test.ts +11 -26
  152. package/mcp-server/src/relocation-integrity-gate/__tests__/relocation-integrity-gate.test.ts +72 -5
  153. package/mcp-server/src/relocation-integrity-gate/relocation-integrity-gate.ts +81 -24
  154. package/mcp-server/src/tools/list-cross-references.ts +2 -2
  155. package/package.json +23 -21
  156. package/src/__tests__/browser-distribution/css-bundling.test.ts +6 -4
  157. package/src/__tests__/console-allowlist.json +14 -0
  158. package/src/__tests__/console-fail-setup.ts +169 -0
  159. package/src/__tests__/integration/Spec107-DesignLanguageContext.test.ts +16 -0
  160. package/src/__tests__/stemma-system/behavioral-contract-validation.test.ts +70 -17
  161. package/src/__tests__/stemma-system/contract-catalog-name-validation.test.ts +28 -0
  162. package/src/__tests__/stemma-system/form-inputs-contracts.test.ts +223 -16
  163. package/src/__tests__/stemma-system/input-text-native-base-call-alignment.test.ts +298 -0
  164. package/src/blend/OklchBlendCalculator.ts +3 -0
  165. package/src/blend/ThemeAwareBlendUtilities.android.kt +3 -0
  166. package/src/blend/ThemeAwareBlendUtilities.ios.swift +3 -0
  167. package/src/blend/ThemeAwareBlendUtilities.web.ts +9 -1
  168. package/src/blend/__tests__/InteractionStateAudit.test.ts +12 -9
  169. package/src/build/errors/__tests__/ErrorHandler.integration.test.ts +8 -0
  170. package/src/build/errors/__tests__/ErrorHandler.test.ts +5 -0
  171. package/src/build/workflow/__tests__/CICDIntegration.test.ts +12 -1
  172. package/src/cli/__tests__/init.test.ts +45 -11
  173. package/src/components/core/Avatar-Base/Avatar-Base.schema.yaml +1 -1
  174. package/src/components/core/Avatar-Base/__tests__/Avatar.accessibility.test.ts +121 -7
  175. package/src/components/core/Avatar-Base/__tests__/Avatar.image.test.ts +3 -0
  176. package/src/components/core/Avatar-Base/__tests__/Avatar.test.ts +15 -6
  177. package/src/components/core/Avatar-Base/contracts.yaml +11 -1
  178. package/src/components/core/Avatar-Base/platforms/web/Avatar.web.ts +24 -5
  179. package/src/components/core/Badge-Count-Base/contracts.yaml +1 -1
  180. package/src/components/core/Badge-Label-Base/contracts.yaml +1 -1
  181. package/src/components/core/Button-CTA/Button-CTA.schema.yaml +2 -12
  182. package/src/components/core/Button-CTA/README.md +3 -6
  183. package/src/components/core/Button-CTA/__tests__/ButtonCTA.test.ts +35 -89
  184. package/src/components/core/Button-CTA/__tests__/setup.test.ts +0 -2
  185. package/src/components/core/Button-CTA/__tests__/test-utils.ts +0 -2
  186. package/src/components/core/Button-CTA/contracts.yaml +6 -29
  187. package/src/components/core/Button-CTA/examples/BasicUsage.html +2 -14
  188. package/src/components/core/Button-CTA/examples/BasicUsage.tsx +17 -44
  189. package/src/components/core/Button-CTA/platforms/android/ButtonCTA.android.kt +12 -20
  190. package/src/components/core/Button-CTA/platforms/ios/ButtonCTA.ios.swift +12 -51
  191. package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.css +2 -26
  192. package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.ts +18 -71
  193. package/src/components/core/Button-CTA/types.ts +10 -28
  194. package/src/components/core/Chip-Base/__tests__/ChipBase.test.ts +13 -0
  195. package/src/components/core/Chip-Filter/__tests__/ChipFilter.test.ts +13 -0
  196. package/src/components/core/Chip-Input/__tests__/ChipInput.test.ts +13 -0
  197. package/src/components/core/Input-Text-Base/Input-Text-Base.schema.yaml +30 -2
  198. package/src/components/core/Input-Text-Base/README.md +25 -2
  199. package/src/components/core/Input-Text-Base/__tests__/focusIndicators.test.ts +16 -15
  200. package/src/components/core/Input-Text-Base/contracts.yaml +90 -0
  201. package/src/components/core/Input-Text-Base/platforms/android/InputTextBase.android.kt +26 -12
  202. package/src/components/core/Input-Text-Base/platforms/ios/InputTextBase.ios.swift +195 -59
  203. package/src/components/core/Input-Text-Base/types.ts +13 -1
  204. package/src/components/core/Input-Text-Email/Input-Text-Email.schema.yaml +5 -1
  205. package/src/components/core/Input-Text-Email/README.md +8 -7
  206. package/src/components/core/Input-Text-Email/platforms/android/InputTextEmail.android.kt +1 -4
  207. package/src/components/core/Input-Text-Email/platforms/ios/InputTextEmail.ios.swift +2 -16
  208. package/src/components/core/Input-Text-Password/Input-Text-Password.schema.yaml +10 -3
  209. package/src/components/core/Input-Text-Password/README.md +9 -8
  210. package/src/components/core/Input-Text-Password/contracts.yaml +5 -0
  211. package/src/components/core/Input-Text-Password/platforms/android/InputTextPassword.android.kt +17 -7
  212. package/src/components/core/Input-Text-Password/platforms/ios/InputTextPassword.ios.swift +22 -20
  213. package/src/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.ts +11 -2
  214. package/src/components/core/Input-Text-PhoneNumber/Input-Text-PhoneNumber.schema.yaml +5 -1
  215. package/src/components/core/Input-Text-PhoneNumber/README.md +9 -8
  216. package/src/components/core/Input-Text-PhoneNumber/platforms/android/InputTextPhoneNumber.android.kt +2 -5
  217. package/src/components/core/Input-Text-PhoneNumber/platforms/ios/InputTextPhoneNumber.ios.swift +3 -17
  218. package/src/components/core/Nav-Header-App/contracts.yaml +1 -1
  219. package/src/components/core/Nav-SegmentedChoice-Base/contracts.yaml +1 -1
  220. package/src/components/core/Progress-Indicator-Connector-Base/contracts.yaml +1 -1
  221. package/src/components/core/Progress-Indicator-Label-Base/contracts.yaml +1 -1
  222. package/src/components/core/Progress-Indicator-Node-Base/contracts.yaml +1 -1
  223. package/src/components/core/Progress-Stepper-Base/__tests__/StepperBase.test.ts +5 -2
  224. package/src/components/core/Progress-Stepper-Detailed/__tests__/StepperDetailed.test.ts +5 -2
  225. package/src/generators/DTCGFormatGenerator.ts +6 -0
  226. package/src/generators/__tests__/DTCGConfigOptions.test.ts +14 -5
  227. package/src/integration/BuildErrorHandler.ts +2 -2
  228. package/src/tokens/OpacityTokens.ts +1 -1
  229. package/src/tokens/__tests__/OpacityTokens.test.ts +3 -1
  230. package/src/tokens/semantic/BlendTokens.ts +26 -5
  231. package/src/tokens/semantic/OpacityTokens.ts +4 -4
  232. package/src/tools/release/__tests__/ReleasePipeline.test.ts +1 -1
  233. package/src/types/ComponentTypes.ts +1 -1
  234. package/src/types/generated/TokenTypes.ts +1 -1
  235. package/src/validators/StemmaTokenUsageValidator.ts +3 -2
  236. package/token-index/semantics.yaml +1 -2
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Input-Text Native Base-Call Alignment Validation
3
+ *
4
+ * Static analysis tests that validate the native (iOS/Android) Input-Text
5
+ * semantic variants call InputTextBase with parameters the base actually
6
+ * declares. The native platform files are NOT compiled in CI — Swift/Kotlin
7
+ * sources are only validated by static-analysis Jest tests — so a call-site
8
+ * passing an undeclared parameter (or referencing a nonexistent enum member)
9
+ * would not compile on a real platform build but previously went undetected.
10
+ *
11
+ * Catches:
12
+ * - Callers passing named arguments the base does not declare
13
+ * (e.g., `trailingContent` before the base grew that slot)
14
+ * - iOS callers passing memberwise-init arguments out of declaration order
15
+ * - References to InputType enum members that do not exist
16
+ * (e.g., `InputType.PHONE` when the enum defines TEL)
17
+ *
18
+ * Stemma System: Form Inputs Family
19
+ */
20
+
21
+ import * as fs from 'fs';
22
+ import * as path from 'path';
23
+
24
+ const CORE = path.join(__dirname, '../../components/core');
25
+
26
+ const IOS_BASE = path.join(CORE, 'Input-Text-Base/platforms/ios/InputTextBase.ios.swift');
27
+ const ANDROID_BASE = path.join(CORE, 'Input-Text-Base/platforms/android/InputTextBase.android.kt');
28
+
29
+ const IOS_CALLERS = [
30
+ 'Input-Text-Password/platforms/ios/InputTextPassword.ios.swift',
31
+ 'Input-Text-Email/platforms/ios/InputTextEmail.ios.swift',
32
+ 'Input-Text-PhoneNumber/platforms/ios/InputTextPhoneNumber.ios.swift',
33
+ ].map((p) => path.join(CORE, p));
34
+
35
+ const ANDROID_CALLERS = [
36
+ 'Input-Text-Password/platforms/android/InputTextPassword.android.kt',
37
+ 'Input-Text-Email/platforms/android/InputTextEmail.android.kt',
38
+ 'Input-Text-PhoneNumber/platforms/android/InputTextPhoneNumber.android.kt',
39
+ ].map((p) => path.join(CORE, p));
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Source scanning helpers (string- and comment-aware, brace/paren-depth aware)
43
+ // ---------------------------------------------------------------------------
44
+
45
+ /**
46
+ * Extract the balanced-parentheses body starting at `openParenIndex`
47
+ * (which must point at a '('). Skips string literals and line comments.
48
+ */
49
+ function extractParenBody(source: string, openParenIndex: number): string {
50
+ let depth = 0;
51
+ for (let i = openParenIndex; i < source.length; i++) {
52
+ const ch = source[i];
53
+ if (ch === '"') {
54
+ // Skip string literal (handles escapes)
55
+ i++;
56
+ while (i < source.length && source[i] !== '"') {
57
+ if (source[i] === '\\') i++;
58
+ i++;
59
+ }
60
+ } else if (ch === '/' && source[i + 1] === '/') {
61
+ while (i < source.length && source[i] !== '\n') i++;
62
+ } else if (ch === '(') {
63
+ depth++;
64
+ } else if (ch === ')') {
65
+ depth--;
66
+ if (depth === 0) {
67
+ return source.slice(openParenIndex + 1, i);
68
+ }
69
+ }
70
+ }
71
+ throw new Error('Unbalanced parentheses in source');
72
+ }
73
+
74
+ /**
75
+ * Split an argument/parameter list into top-level entries — commas nested
76
+ * inside (), {}, [], strings, or line comments do not split.
77
+ */
78
+ function splitTopLevel(body: string): string[] {
79
+ const parts: string[] = [];
80
+ let depth = 0;
81
+ let current = '';
82
+ for (let i = 0; i < body.length; i++) {
83
+ const ch = body[i];
84
+ if (ch === '"') {
85
+ current += ch;
86
+ i++;
87
+ while (i < body.length && body[i] !== '"') {
88
+ if (body[i] === '\\') {
89
+ current += body[i];
90
+ i++;
91
+ }
92
+ current += body[i];
93
+ i++;
94
+ }
95
+ current += body[i] ?? '';
96
+ } else if (ch === '/' && body[i + 1] === '/') {
97
+ while (i < body.length && body[i] !== '\n') i++;
98
+ current += '\n';
99
+ } else if ('({['.includes(ch)) {
100
+ depth++;
101
+ current += ch;
102
+ } else if (')}]'.includes(ch)) {
103
+ depth--;
104
+ current += ch;
105
+ } else if (ch === ',' && depth === 0) {
106
+ parts.push(current);
107
+ current = '';
108
+ } else {
109
+ current += ch;
110
+ }
111
+ }
112
+ if (current.trim()) parts.push(current);
113
+ return parts;
114
+ }
115
+
116
+ /** Find every `InputTextBase(...)` call in a source file and return its args. */
117
+ function extractBaseCalls(source: string): string[][] {
118
+ const calls: string[][] = [];
119
+ const re = /\bInputTextBase\(/g;
120
+ let match: RegExpExecArray | null;
121
+ while ((match = re.exec(source)) !== null) {
122
+ const body = extractParenBody(source, match.index + match[0].length - 1);
123
+ calls.push(splitTopLevel(body));
124
+ re.lastIndex = match.index + match[0].length;
125
+ }
126
+ return calls;
127
+ }
128
+
129
+ /** Labels of named arguments: Swift `label: value` / Kotlin `name = value`. */
130
+ function argLabel(arg: string, platform: 'ios' | 'android'): string | null {
131
+ const m =
132
+ platform === 'ios'
133
+ ? arg.match(/^\s*(\w+)\s*:/)
134
+ : arg.match(/^\s*(\w+)\s*=(?!=)/);
135
+ return m ? m[1] : null;
136
+ }
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Base declaration parsers
140
+ // ---------------------------------------------------------------------------
141
+
142
+ /**
143
+ * iOS base: settable stored properties of the InputTextBase struct, in
144
+ * declaration order. These define the memberwise initializer, so caller
145
+ * argument labels must be an in-order subsequence of this list.
146
+ * Excludes private state (@State/@FocusState/@Environment are all private)
147
+ * and the `body` computed property.
148
+ */
149
+ function parseIOSBaseProperties(source: string): string[] {
150
+ const structStart = source.indexOf('struct InputTextBase: View {');
151
+ const bodyStart = source.indexOf('var body: some View', structStart);
152
+ const section = source.slice(structStart, bodyStart);
153
+ const props: string[] = [];
154
+ for (const line of section.split('\n')) {
155
+ if (line.includes('private')) continue;
156
+ const m = line.match(/^\s*(?:@Binding\s+)?(?:let|var)\s+(\w+)\s*[:=]/);
157
+ if (m) props.push(m[1]);
158
+ }
159
+ return props;
160
+ }
161
+
162
+ /** Android base: parameter names of the InputTextBase composable function. */
163
+ function parseAndroidBaseParams(source: string): string[] {
164
+ const funIndex = source.indexOf('fun InputTextBase(');
165
+ const body = extractParenBody(source, funIndex + 'fun InputTextBase'.length);
166
+ return splitTopLevel(body)
167
+ .map((p) => p.match(/^\s*(\w+)\s*:/)?.[1])
168
+ .filter((name): name is string => Boolean(name));
169
+ }
170
+
171
+ /** Enum members: Swift `case xyz` / Kotlin `enum class InputType { A, B }`. */
172
+ function parseInputTypeMembers(source: string, platform: 'ios' | 'android'): string[] {
173
+ if (platform === 'ios') {
174
+ const enumStart = source.indexOf('enum InputType {');
175
+ const enumBody = source.slice(enumStart, source.indexOf('}', enumStart));
176
+ return [...enumBody.matchAll(/case\s+(\w+)/g)].map((m) => m[1]);
177
+ }
178
+ const enumMatch = source.match(/enum class InputType \{([^}]*)\}/);
179
+ if (!enumMatch) return [];
180
+ return enumMatch[1]
181
+ .split(',')
182
+ .map((m) => m.trim())
183
+ .filter(Boolean);
184
+ }
185
+
186
+ /** Assert `subset` appears within `sequence` in order (subsequence check). */
187
+ function isInOrderSubsequence(subset: string[], sequence: string[]): boolean {
188
+ let cursor = 0;
189
+ for (const item of subset) {
190
+ const found = sequence.indexOf(item, cursor);
191
+ if (found === -1) return false;
192
+ cursor = found + 1;
193
+ }
194
+ return true;
195
+ }
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // Tests
199
+ // ---------------------------------------------------------------------------
200
+
201
+ describe('Input-Text native base-call alignment', () => {
202
+ const iosBaseSource = fs.readFileSync(IOS_BASE, 'utf-8');
203
+ const androidBaseSource = fs.readFileSync(ANDROID_BASE, 'utf-8');
204
+
205
+ const iosBaseProps = parseIOSBaseProperties(iosBaseSource);
206
+ const androidBaseParams = parseAndroidBaseParams(androidBaseSource);
207
+ const iosEnumMembers = parseInputTypeMembers(iosBaseSource, 'ios');
208
+ const androidEnumMembers = parseInputTypeMembers(androidBaseSource, 'android');
209
+
210
+ describe('base declaration parsing (sanity)', () => {
211
+ it('parses the iOS base memberwise properties', () => {
212
+ expect(iosBaseProps).toEqual(
213
+ expect.arrayContaining(['id', 'label', 'value', 'type', 'readOnly', 'trailingContent'])
214
+ );
215
+ expect(iosBaseProps.length).toBeGreaterThanOrEqual(15);
216
+ });
217
+
218
+ it('parses the Android base composable parameters', () => {
219
+ expect(androidBaseParams).toEqual(
220
+ expect.arrayContaining(['id', 'label', 'value', 'type', 'visualTransformation', 'trailingContent'])
221
+ );
222
+ expect(androidBaseParams.length).toBeGreaterThanOrEqual(15);
223
+ });
224
+
225
+ it('parses the InputType enums on both platforms', () => {
226
+ expect(iosEnumMembers).toEqual(['text', 'email', 'password', 'tel', 'url']);
227
+ expect(androidEnumMembers).toEqual(['TEXT', 'EMAIL', 'PASSWORD', 'TEL', 'URL']);
228
+ });
229
+ });
230
+
231
+ describe('iOS callers', () => {
232
+ // The iOS base preview also exercises the memberwise initializer
233
+ const files = [...IOS_CALLERS, IOS_BASE];
234
+
235
+ files.forEach((file) => {
236
+ const name = path.basename(file);
237
+
238
+ it(`${name}: every InputTextBase argument is a declared property, in declaration order`, () => {
239
+ const source = fs.readFileSync(file, 'utf-8');
240
+ const calls = extractBaseCalls(source);
241
+ expect(calls.length).toBeGreaterThan(0);
242
+
243
+ calls.forEach((args) => {
244
+ const labels = args
245
+ .map((a) => argLabel(a, 'ios'))
246
+ .filter((l): l is string => Boolean(l));
247
+
248
+ const undeclared = labels.filter((l) => !iosBaseProps.includes(l));
249
+ expect(undeclared).toEqual([]);
250
+
251
+ // Memberwise init requires arguments in property declaration order
252
+ expect(isInOrderSubsequence(labels, iosBaseProps)).toBe(true);
253
+ });
254
+ });
255
+
256
+ it(`${name}: type argument references a declared InputType case`, () => {
257
+ const source = fs.readFileSync(file, 'utf-8');
258
+ extractBaseCalls(source).forEach((args) => {
259
+ const typeArg = args.find((a) => argLabel(a, 'ios') === 'type');
260
+ if (!typeArg) return;
261
+ const caseMatch = typeArg.match(/:\s*\.(\w+)/);
262
+ if (caseMatch) {
263
+ expect(iosEnumMembers).toContain(caseMatch[1]);
264
+ }
265
+ });
266
+ });
267
+ });
268
+ });
269
+
270
+ describe('Android callers', () => {
271
+ ANDROID_CALLERS.forEach((file) => {
272
+ const name = path.basename(file);
273
+
274
+ it(`${name}: every InputTextBase named argument is a declared parameter`, () => {
275
+ const source = fs.readFileSync(file, 'utf-8');
276
+ const calls = extractBaseCalls(source);
277
+ expect(calls.length).toBeGreaterThan(0);
278
+
279
+ calls.forEach((args) => {
280
+ const labels = args
281
+ .map((a) => argLabel(a, 'android'))
282
+ .filter((l): l is string => Boolean(l));
283
+ expect(labels.length).toBeGreaterThan(0);
284
+
285
+ const undeclared = labels.filter((l) => !androidBaseParams.includes(l));
286
+ expect(undeclared).toEqual([]);
287
+ });
288
+ });
289
+
290
+ it(`${name}: every InputType reference is a declared enum member`, () => {
291
+ const source = fs.readFileSync(file, 'utf-8');
292
+ const refs = [...source.matchAll(/\bInputType\.(\w+)/g)].map((m) => m[1]);
293
+ const unknown = refs.filter((r) => !androidEnumMembers.includes(r));
294
+ expect(unknown).toEqual([]);
295
+ });
296
+ });
297
+ });
298
+ });
@@ -17,6 +17,8 @@
17
17
 
18
18
  import type { Oklch } from '../color/OklchConverter';
19
19
 
20
+ // 'disabled' is deprecated calculator capability: DesignerPunk supports no disabled
21
+ // states (adjudicated 2026-07-15) — no component may use it; removal at next major.
20
22
  export type InteractionState = 'hover' | 'pressed' | 'focused' | 'disabled';
21
23
 
22
24
  /** Thresholds for interaction state blends. */
@@ -24,6 +26,7 @@ export const INTERACTION_THRESHOLDS = {
24
26
  hover: { deltaL: { min: 0.02, max: 0.05 }, deltaC: 0 },
25
27
  pressed: { deltaL: { min: 0.05, max: 0.10 }, deltaC: 0 },
26
28
  focused: { deltaL: 0, deltaC: { min: 0.02 } },
29
+ // deprecated 2026-07-15 (no-disabled-states philosophy) — removal at next major
27
30
  disabled: { deltaL: 0, deltaC: { min: 0.03 } },
28
31
  } as const;
29
32
 
@@ -37,6 +37,7 @@ object BlendTokenValues {
37
37
  const val focusSaturate: Float = 0.08f
38
38
 
39
39
  /** Disabled state desaturation - blend300 (12%) */
40
+ @Deprecated("DesignerPunk does not support disabled states (adjudicated 2026-07-15). Removal at next major.")
40
41
  const val disabledDesaturate: Float = 0.12f
41
42
 
42
43
  /** Icon optical balance lightening - blend200 (8%) */
@@ -163,6 +164,7 @@ fun Color.focusBlend(): Color {
163
164
  *
164
165
  * @see Requirements: 11.4 - Theme-aware wrapper functions
165
166
  */
167
+ @Deprecated("DesignerPunk does not support disabled states (adjudicated 2026-07-15) — if an action is unavailable, do not render the component. Use desaturate() for non-disabled desaturation. Removal at next major.")
166
168
  fun Color.disabledBlend(): Color {
167
169
  return this.desaturate(BlendTokenValues.disabledDesaturate)
168
170
  }
@@ -280,6 +282,7 @@ object BlendUtilitiesProvider {
280
282
  * @param color Base color
281
283
  * @return Desaturated color for disabled state
282
284
  */
285
+ @Deprecated("DesignerPunk does not support disabled states (adjudicated 2026-07-15) — if an action is unavailable, do not render the component. Removal at next major.")
283
286
  fun disabledColor(color: Color): Color {
284
287
  return color.disabledBlend()
285
288
  }
@@ -28,6 +28,7 @@ public struct BlendTokenValues {
28
28
  public static let focusSaturate: Double = 0.08
29
29
 
30
30
  /// Disabled state desaturation - blend300 (12%)
31
+ @available(*, deprecated, message: "DesignerPunk does not support disabled states (adjudicated 2026-07-15). Removal at next major.")
31
32
  public static let disabledDesaturate: Double = 0.12
32
33
 
33
34
  /// Icon optical balance lightening - blend200 (8%)
@@ -172,6 +173,7 @@ extension Color {
172
173
  /// ```
173
174
  ///
174
175
  /// @see Requirements: 11.4 - Theme-aware wrapper functions
176
+ @available(*, deprecated, message: "DesignerPunk does not support disabled states (adjudicated 2026-07-15) — if an action is unavailable, do not render the component. Use desaturate(_:) for non-disabled desaturation. Removal at next major.")
175
177
  public func disabledBlend() -> Color {
176
178
  return self.desaturate(BlendTokenValues.disabledDesaturate)
177
179
  }
@@ -276,6 +278,7 @@ public class BlendUtilitiesProvider {
276
278
  /// Calculate disabled color for a given base color
277
279
  /// - Parameter color: Base color
278
280
  /// - Returns: Desaturated color for disabled state
281
+ @available(*, deprecated, message: "DesignerPunk does not support disabled states (adjudicated 2026-07-15) — if an action is unavailable, do not render the component. Removal at next major.")
279
282
  public func disabledColor(_ color: Color) -> Color {
280
283
  return color.disabledBlend()
281
284
  }
@@ -54,7 +54,11 @@ export const BlendTokenValues = {
54
54
  pressedDarker: 0.12,
55
55
  /** Focus state saturation increase - blend200 (8%) */
56
56
  focusSaturate: 0.08,
57
- /** Disabled state desaturation - blend300 (12%) */
57
+ /**
58
+ * Disabled state desaturation - blend300 (12%)
59
+ * @deprecated 2026-07-15 — DesignerPunk does not support disabled states; if an action
60
+ * is unavailable, do not render the component. Scheduled for removal at the next major.
61
+ */
58
62
  disabledDesaturate: 0.12,
59
63
  /** Icon optical balance lightening - blend200 (8%) */
60
64
  iconLighter: 0.08
@@ -89,6 +93,9 @@ export interface BlendUtilitiesResult {
89
93
  * Calculate disabled color by desaturating the base color
90
94
  * @param baseColor - Base color as hex string (e.g., "#A855F7")
91
95
  * @returns Desaturated hex color string for disabled state
96
+ * @deprecated 2026-07-15 — DesignerPunk does not support disabled states; if an action
97
+ * is unavailable, do not render the component. For non-disabled desaturation, use the
98
+ * generic desaturate() utility. Scheduled for removal at the next major.
92
99
  */
93
100
  disabledColor: (baseColor: string) => string;
94
101
 
@@ -242,6 +249,7 @@ export function createBlendUtilities(): BlendUtilitiesResult {
242
249
  hoverColor: (baseColor: string) => darkerBlend(baseColor, BlendTokenValues.hoverDarker),
243
250
  pressedColor: (baseColor: string) => darkerBlend(baseColor, BlendTokenValues.pressedDarker),
244
251
  focusColor: (baseColor: string) => saturate(baseColor, BlendTokenValues.focusSaturate),
252
+ // deprecated 2026-07-15 (no-disabled-states philosophy) — removal at next major
245
253
  disabledColor: (baseColor: string) => desaturate(baseColor, BlendTokenValues.disabledDesaturate),
246
254
  iconColor: (baseColor: string) => lighterBlend(baseColor, BlendTokenValues.iconLighter),
247
255
 
@@ -13,7 +13,10 @@
13
13
  * hover: ΔL ∈ [0.02, 0.05], preserve chroma
14
14
  * pressed: ΔL ∈ [0.05, 0.10], preserve chroma
15
15
  * focused: ΔC ≥ 0.02 (chroma boost)
16
- * disabled: ΔC ≥ 0.03 (chroma reduction)
16
+ * disabled: ΔC ≥ 0.03 (chroma reduction) — calculator capability only;
17
+ * no component declares a disabled state (state_disabled removed
18
+ * from Button-CTA 2026-07-15, completing the no-disabled-states
19
+ * philosophy across the corpus)
17
20
  *
18
21
  * Components audited:
19
22
  * Button-CTA, Button-Icon, Button-VerticalList-Item, Chip-Base,
@@ -125,14 +128,14 @@ describe('Spec 112 Task 5.1: Interaction State Visual Audit', () => {
125
128
  });
126
129
 
127
130
  describe('Disabled state (ΔC ≥ 0.03 reduction)', () => {
128
- // Only Button-CTA has a disabled state in DesignerPunk
129
- const cases: [string, Oklch][] = [
130
- ['Button-CTA (primary)', primaryButton],
131
- ];
132
-
133
- it.each(cases)('%s — ΔC reduction meets minimum', (name, base) => {
134
- const result = calc.interactionBlend(base, 'disabled', lightSurface);
135
- const dc = deltaC(base, result);
131
+ // No component declares a disabled state (no-disabled-states philosophy).
132
+ // blend.disabledDesaturate was deprecated 2026-07-15 (PR #83, Button-CTA
133
+ // disabled-state adjudication). This test validates the calculator
134
+ // capability only, retained for backward compatibility; remove at the
135
+ // next major version alongside the token itself.
136
+ it('calculator disabled blend — ΔC reduction meets minimum', () => {
137
+ const result = calc.interactionBlend(primaryButton, 'disabled', lightSurface);
138
+ const dc = deltaC(primaryButton, result);
136
139
  // dc should be negative (chroma reduced), absolute value ≥ 0.03
137
140
  expect(dc).toBeLessThan(0);
138
141
  expect(Math.abs(dc)).toBeGreaterThanOrEqual(INTERACTION_THRESHOLDS.disabled.deltaC.min);
@@ -13,6 +13,14 @@ import { createBuildError } from '../BuildError';
13
13
  import { BuildResultSummary } from '../ErrorReporter';
14
14
 
15
15
  describe('ErrorHandler - ErrorReporter Integration', () => {
16
+ beforeEach(() => {
17
+ jest.spyOn(console, 'error').mockImplementation(() => {});
18
+ });
19
+
20
+ afterEach(() => {
21
+ jest.restoreAllMocks();
22
+ });
23
+
16
24
  describe('generateReport', () => {
17
25
  it('should generate report from logged errors', () => {
18
26
  const handler = new ErrorHandler();
@@ -13,9 +13,14 @@ describe('ErrorHandler', () => {
13
13
  let errorHandler: ErrorHandler;
14
14
 
15
15
  beforeEach(() => {
16
+ jest.spyOn(console, 'error').mockImplementation(() => {});
16
17
  errorHandler = new ErrorHandler();
17
18
  });
18
19
 
20
+ afterEach(() => {
21
+ jest.restoreAllMocks();
22
+ });
23
+
19
24
  describe('constructor', () => {
20
25
  it('should create ErrorHandler with default options', () => {
21
26
  const handler = new ErrorHandler();
@@ -490,9 +490,20 @@ describe('CICDIntegration', () => {
490
490
 
491
491
  describe('Environment Detection', () => {
492
492
  const originalEnv = process.env;
493
-
493
+
494
494
  beforeEach(() => {
495
495
  process.env = { ...originalEnv };
496
+ // Neutralize the HOST's own CI environment before each scenario. On a real CI
497
+ // runner (e.g. GitHub Actions) the ambient GITHUB_ACTIONS/CI vars leak into the
498
+ // copied env and win detectCICDEnvironment()'s precedence order, so the GitLab
499
+ // and generic-CI scenarios detected 'github' instead — these tests only ever
500
+ // passed on laptops because laptops aren't CI (125-A Task 6, lane-timing run #2
501
+ // finding). Each scenario sets exactly the vars it means to test.
502
+ delete process.env.GITHUB_ACTIONS;
503
+ delete process.env.GITHUB_WORKFLOW;
504
+ delete process.env.GITLAB_CI;
505
+ delete process.env.CI_PIPELINE_ID;
506
+ delete process.env.CI;
496
507
  });
497
508
 
498
509
  afterEach(() => {
@@ -23,11 +23,45 @@ import * as fs from 'fs';
23
23
  import * as os from 'os';
24
24
  import * as path from 'path';
25
25
  import { runInit } from '../init';
26
+ import { resolvePackageRoot } from '../shared/resolvePackageRoot';
26
27
 
27
28
  // ---------------------------------------------------------------------------
28
29
  // Helpers
29
30
  // ---------------------------------------------------------------------------
30
31
 
32
+ /**
33
+ * Count files recursively under `dir`, matching `copyDir()`'s counting semantics
34
+ * in init.ts (files only, descending into subdirectories).
35
+ *
36
+ * Used to DERIVE the expected governance-doc count from the same source `init`
37
+ * copies from, rather than hard-coding a literal. The governance corpus is the
38
+ * relocated non-identity docs (Spec 119-A) and grows by design — a hard-coded
39
+ * count drifts (and repeatedly HAS drifted, e.g. 80 → 81 → 82) every time a doc
40
+ * is added, with no relation to the behavior under test. Deriving from source
41
+ * keeps the real check — "init copies EVERY governance doc, dropping none" —
42
+ * while decoupling it from the corpus's exact size.
43
+ */
44
+ function countFilesRecursive(dir: string): number {
45
+ let count = 0;
46
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
47
+ count += entry.isDirectory()
48
+ ? countFilesRecursive(path.join(dir, entry.name))
49
+ : entry.isFile()
50
+ ? 1
51
+ : 0;
52
+ }
53
+ return count;
54
+ }
55
+
56
+ // The package root `init` copies from is resolved by `resolvePackageRoot(<init's
57
+ // dir>)` = two levels up from `src/cli/`. This test file lives one level deeper
58
+ // (`src/cli/__tests__/`), so pass its parent (`src/cli/`) to resolve the SAME root.
59
+ const PKG_ROOT = resolvePackageRoot(path.join(__dirname, '..'));
60
+
61
+ // Expected governance-doc count, derived from source (see countFilesRecursive).
62
+ // NOT hard-coded: governance/ grows as the corpus grows.
63
+ const GOVERNANCE_DOC_COUNT = countFilesRecursive(path.join(PKG_ROOT, 'governance'));
64
+
31
65
  /**
32
66
  * Create a unique scratch directory under the OS temp dir.
33
67
  * Returns the absolute path. Caller is responsible for cleanup.
@@ -140,11 +174,12 @@ describe('CLI init — integration', () => {
140
174
  expect(secondOutput).toContain('existing files preserved');
141
175
  // Spec 119-A two-root split: 9 identity docs ship in .kiro/steering/
142
176
  // (8 identity + the NEW Task-Completion-Protocol added in Task 8; the
143
- // meta-guide was removed in 119-A Task 10.5 → back to 9),
144
- // and the 81 relocated non-identity docs ship in governance/
145
- // (80 + Steering-Addressing-Conventions.md added in Task 12).
177
+ // meta-guide was removed in 119-A Task 10.5 → back to 9). The identity set
178
+ // is a LOCKED always-set — asserted as exactly 9 on purpose, so a change to
179
+ // it is a signal worth catching. The governance corpus, by contrast, grows
180
+ // routinely, so its count is derived from source (GOVERNANCE_DOC_COUNT).
146
181
  expect(secondOutput).toContain('✓ steering docs: 9 existing files preserved');
147
- expect(secondOutput).toContain('✓ governance docs: 81 existing files preserved');
182
+ expect(secondOutput).toContain(`✓ governance docs: ${GOVERNANCE_DOC_COUNT} existing files preserved`);
148
183
  });
149
184
 
150
185
  test('preserves existing files — no overwrites after second run', async () => {
@@ -190,11 +225,11 @@ describe('CLI init — integration', () => {
190
225
  // Spec 119-A two-root split: the package contributes 9 identity steering
191
226
  // files (8 identity + the NEW Task-Completion-Protocol from Task 8; the
192
227
  // meta-guide was removed in Task 10.5; no conflict with designerpunk.md
193
- // because the package doesn't have a file by that name) and 81 relocated
194
- // docs into the separate governance/ dir
195
- // (80 + Steering-Addressing-Conventions.md added in Task 12).
228
+ // because the package doesn't have a file by that name) and the relocated
229
+ // non-identity docs into the separate governance/ dir. Steering is the
230
+ // locked 9; governance is derived from source (grows with the corpus).
196
231
  expect(output).toContain('✓ steering docs: 9 new files');
197
- expect(output).toContain('✓ governance docs: 81 new files');
232
+ expect(output).toContain(`✓ governance docs: ${GOVERNANCE_DOC_COUNT} new files`);
198
233
 
199
234
  // Custom file preserved
200
235
  expect(
@@ -205,10 +240,9 @@ describe('CLI init — integration', () => {
205
240
  const steeringFiles = fs.readdirSync(path.join(scratchDir, '.kiro/steering'));
206
241
  expect(steeringFiles.length).toBe(10);
207
242
 
208
- // The 81 relocated docs land in governance/
209
- // (80 + Steering-Addressing-Conventions.md added in Task 12)
243
+ // The relocated docs land in governance/ — every one init copied from source.
210
244
  const governanceFiles = fs.readdirSync(path.join(scratchDir, 'governance'));
211
- expect(governanceFiles.length).toBe(81);
245
+ expect(governanceFiles.length).toBe(GOVERNANCE_DOC_COUNT);
212
246
  });
213
247
  });
214
248
 
@@ -60,7 +60,7 @@ properties:
60
60
  alt:
61
61
  type: string
62
62
  required: false
63
- description: Alt text for accessibility. Required if src provided.
63
+ description: Alt text for accessibility. Required if src provided on human type, unless decorative is true (agent type ignores src entirely, per Req 5 AC 5).
64
64
  interactive:
65
65
  type: boolean
66
66
  required: false