@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.
- package/.kiro/agents/ada-prompt.md +80 -132
- package/.kiro/agents/ada-prompt.md.attribution.json +45 -0
- package/.kiro/agents/ada.json +44 -59
- package/.kiro/agents/ada.json.attribution.json +13 -0
- package/.kiro/agents/data-prompt.md +83 -74
- package/.kiro/agents/data-prompt.md.attribution.json +53 -0
- package/.kiro/agents/data.json +31 -38
- package/.kiro/agents/data.json.attribution.json +13 -0
- package/.kiro/agents/kenya-prompt.md +83 -72
- package/.kiro/agents/kenya-prompt.md.attribution.json +53 -0
- package/.kiro/agents/kenya.json +27 -35
- package/.kiro/agents/kenya.json.attribution.json +13 -0
- package/.kiro/agents/leonardo-prompt.md +176 -234
- package/.kiro/agents/leonardo-prompt.md.attribution.json +45 -0
- package/.kiro/agents/leonardo.json +28 -36
- package/.kiro/agents/leonardo.json.attribution.json +13 -0
- package/.kiro/agents/lina-prompt.md +110 -151
- package/.kiro/agents/lina-prompt.md.attribution.json +53 -0
- package/.kiro/agents/lina.json +46 -59
- package/.kiro/agents/lina.json.attribution.json +13 -0
- package/.kiro/agents/sparky-prompt.md +89 -72
- package/.kiro/agents/sparky-prompt.md.attribution.json +53 -0
- package/.kiro/agents/sparky.json +37 -37
- package/.kiro/agents/sparky.json.attribution.json +13 -0
- package/.kiro/agents/stacy-prompt.md +74 -48
- package/.kiro/agents/stacy-prompt.md.attribution.json +45 -0
- package/.kiro/agents/stacy.json +28 -30
- package/.kiro/agents/stacy.json.attribution.json +13 -0
- package/.kiro/agents/thurgood-prompt.md +94 -138
- package/.kiro/agents/thurgood-prompt.md.attribution.json +45 -0
- package/.kiro/agents/thurgood.json +31 -35
- package/.kiro/agents/thurgood.json.attribution.json +13 -0
- package/.kiro/steering/AI-Collaboration-Principles.md +3 -3
- package/.kiro/steering/Civitas-System-Overview.md +5 -5
- package/.kiro/steering/DesignerPunk-Systems-Overview.md +6 -6
- package/.kiro/steering/Task-Completion-Protocol.md +98 -17
- package/.kiro/steering/core-goals.md +3 -3
- package/.kiro/steering/personal-note.md +1 -1
- package/.kiro/steering/start-up-tasks.md +14 -3
- package/application-mcp-server/src/index.ts +26 -0
- package/dist/ComponentTokens.android.kt +1 -1
- package/dist/ComponentTokens.ios.swift +1 -1
- package/dist/ComponentTokens.web.css +1 -1
- package/dist/DesignTokens.android.kt +1 -1
- package/dist/DesignTokens.dtcg.json +8 -5
- package/dist/DesignTokens.figma.json +2 -2
- package/dist/DesignTokens.ios.swift +1 -1
- package/dist/DesignTokens.web.css +1 -1
- package/dist/android/DesignTokens.android.kt +1 -1
- package/dist/blend/OklchBlendCalculator.js +1 -0
- package/dist/blend/ThemeAwareBlendUtilities.web.d.ts +13 -2
- package/dist/blend/ThemeAwareBlendUtilities.web.js +6 -1
- package/dist/browser/designerpunk.esm.js +22 -81
- package/dist/browser/designerpunk.esm.min.js +29 -32
- package/dist/browser/designerpunk.umd.js +22 -81
- package/dist/browser/designerpunk.umd.min.js +42 -45
- package/dist/browser/tokens.css +1 -1
- package/dist/components/core/Avatar-Base/platforms/web/Avatar.web.js +24 -5
- package/dist/components/core/Button-CTA/examples/BasicUsage.d.ts +16 -28
- package/dist/components/core/Button-CTA/examples/BasicUsage.js +18 -43
- package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.d.ts +3 -15
- package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.js +9 -58
- package/dist/components/core/Button-CTA/types.d.ts +0 -24
- package/dist/components/core/Button-CTA/types.js +6 -0
- package/dist/components/core/Input-Text-Base/types.d.ts +13 -1
- package/dist/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.js +11 -2
- package/dist/generators/DTCGFormatGenerator.js +8 -0
- package/dist/integration/BuildErrorHandler.js +2 -2
- package/dist/ios/DesignTokens.ios.swift +1 -1
- package/dist/mcp/application-mcp.js +24 -0
- package/dist/mcp/docs-mcp.js +130 -15
- package/dist/mcp/product-mcp.js +25 -0
- package/dist/tokens/OpacityTokens.js +1 -1
- package/dist/tokens/semantic/BlendTokens.d.ts +10 -3
- package/dist/tokens/semantic/BlendTokens.js +17 -5
- package/dist/tokens/semantic/OpacityTokens.d.ts +4 -4
- package/dist/tokens/semantic/OpacityTokens.js +4 -4
- package/dist/types/ComponentTypes.d.ts +1 -1
- package/dist/types/generated/TokenTypes.d.ts +1 -1
- package/dist/types/generated/TokenTypes.js +1 -1
- package/dist/validators/StemmaTokenUsageValidator.js +3 -2
- package/dist/web/DesignTokens.web.css +1 -1
- package/governance/Component-Development-Guide.md +22 -12
- package/governance/Component-Development-Standards.md +2 -2
- package/governance/Component-Family-Avatar.md +0 -1
- package/governance/Component-Family-Badge.md +0 -1
- package/governance/Component-Family-Button.md +4 -17
- package/governance/Component-Family-Chip.md +0 -1
- package/governance/Component-Family-Container.md +0 -1
- package/governance/Component-Family-Data-Display.md +1 -2
- package/governance/Component-Family-Divider.md +1 -2
- package/governance/Component-Family-Form-Inputs.md +5 -5
- package/governance/Component-Family-Icon.md +1 -2
- package/governance/Component-Family-Loading.md +1 -2
- package/governance/Component-Family-Modal.md +1 -2
- package/governance/Component-Family-Navigation.md +0 -1
- package/governance/Component-Family-Progress.md +0 -1
- package/governance/Component-Inheritance-Structures.md +195 -76
- package/governance/Component-MCP-Document-Template.md +6 -5
- package/governance/Component-Primitive-vs-Semantic-Philosophy.md +1 -1
- package/governance/Component-Quick-Reference.md +33 -33
- package/governance/Component-Readiness-Status.md +54 -38
- package/governance/Component-Templates.md +32 -36
- package/governance/Contract-System-Reference.md +6 -6
- package/governance/MCP-Integration-Guide.md +1 -1
- package/governance/Process-Cross-Reference-Standards.md +31 -13
- package/governance/Process-Development-Workflow.md +49 -59
- package/governance/Process-File-Organization.md +24 -24
- package/governance/Process-Hook-Operations.md +19 -7
- package/governance/Process-Orchestration-Model-Selection.md +92 -0
- package/governance/Process-Spec-Planning.md +91 -39
- package/governance/Process-Task-Type-Definitions.md +80 -4
- package/governance/Product-Handoff-Protocol.md +2 -0
- package/governance/Rosetta-System-Architecture.md +6 -6
- package/governance/Test-Behavioral-Contract-Validation.md +38 -31
- package/governance/Test-Failure-Audit-Methodology.md +1 -1
- package/governance/Token-Family-Accessibility.md +1 -2
- package/governance/Token-Family-Blend.md +18 -16
- package/governance/Token-Family-Blur.md +0 -1
- package/governance/Token-Family-Border.md +1 -2
- package/governance/Token-Family-Color.md +0 -1
- package/governance/Token-Family-Glow.md +1 -2
- package/governance/Token-Family-Layering.md +0 -1
- package/governance/Token-Family-Motion.md +1 -2
- package/governance/Token-Family-Opacity.md +0 -1
- package/governance/Token-Family-Radius.md +1 -2
- package/governance/Token-Family-Responsive.md +1 -2
- package/governance/Token-Family-Shadow.md +1 -2
- package/governance/Token-Family-Sizing.md +0 -1
- package/governance/Token-Family-Spacing.md +1 -2
- package/governance/Token-Family-Typography.md +1 -2
- package/governance/Token-Governance.md +8 -8
- package/governance/Token-Quick-Reference.md +21 -21
- package/governance/Token-Resolution-Patterns.md +1 -1
- package/governance/Token-Semantic-Structure.md +1 -1
- package/governance/Web-Authoring-Standards.md +5 -5
- package/governance/browser-distribution-guide.md +1 -4
- package/governance/classification-map.md +368 -0
- package/governance/completion-documentation-guide.md +11 -8
- package/governance/component-meta-authoring-guide.md +1 -1
- package/governance/cross-platform-vs-platform-specific-decision-framework.md +1 -1
- package/governance/platform-implementation-guidelines.md +1 -1
- package/governance/release-management-system.md +2 -2
- package/governance/rosetta-system-principles.md +8 -6
- package/governance/stemma-system-principles.md +18 -17
- package/mcp-server/src/index.ts +24 -6
- package/mcp-server/src/indexer/DocumentIndexer.ts +119 -9
- package/mcp-server/src/indexer/__tests__/bare-id-crossrefs.test.ts +250 -0
- package/mcp-server/src/indexer/cross-ref-parser.ts +29 -1
- package/mcp-server/src/indexer/index-health.ts +27 -2
- package/mcp-server/src/query/__tests__/find-docs-calibration.test.ts +11 -26
- package/mcp-server/src/relocation-integrity-gate/__tests__/relocation-integrity-gate.test.ts +72 -5
- package/mcp-server/src/relocation-integrity-gate/relocation-integrity-gate.ts +81 -24
- package/mcp-server/src/tools/list-cross-references.ts +2 -2
- package/package.json +23 -21
- package/src/__tests__/browser-distribution/css-bundling.test.ts +6 -4
- package/src/__tests__/console-allowlist.json +14 -0
- package/src/__tests__/console-fail-setup.ts +169 -0
- package/src/__tests__/integration/Spec107-DesignLanguageContext.test.ts +16 -0
- package/src/__tests__/stemma-system/behavioral-contract-validation.test.ts +70 -17
- package/src/__tests__/stemma-system/contract-catalog-name-validation.test.ts +28 -0
- package/src/__tests__/stemma-system/form-inputs-contracts.test.ts +223 -16
- package/src/__tests__/stemma-system/input-text-native-base-call-alignment.test.ts +298 -0
- package/src/blend/OklchBlendCalculator.ts +3 -0
- package/src/blend/ThemeAwareBlendUtilities.android.kt +3 -0
- package/src/blend/ThemeAwareBlendUtilities.ios.swift +3 -0
- package/src/blend/ThemeAwareBlendUtilities.web.ts +9 -1
- package/src/blend/__tests__/InteractionStateAudit.test.ts +12 -9
- package/src/build/errors/__tests__/ErrorHandler.integration.test.ts +8 -0
- package/src/build/errors/__tests__/ErrorHandler.test.ts +5 -0
- package/src/build/workflow/__tests__/CICDIntegration.test.ts +12 -1
- package/src/cli/__tests__/init.test.ts +45 -11
- package/src/components/core/Avatar-Base/Avatar-Base.schema.yaml +1 -1
- package/src/components/core/Avatar-Base/__tests__/Avatar.accessibility.test.ts +121 -7
- package/src/components/core/Avatar-Base/__tests__/Avatar.image.test.ts +3 -0
- package/src/components/core/Avatar-Base/__tests__/Avatar.test.ts +15 -6
- package/src/components/core/Avatar-Base/contracts.yaml +11 -1
- package/src/components/core/Avatar-Base/platforms/web/Avatar.web.ts +24 -5
- package/src/components/core/Badge-Count-Base/contracts.yaml +1 -1
- package/src/components/core/Badge-Label-Base/contracts.yaml +1 -1
- package/src/components/core/Button-CTA/Button-CTA.schema.yaml +2 -12
- package/src/components/core/Button-CTA/README.md +3 -6
- package/src/components/core/Button-CTA/__tests__/ButtonCTA.test.ts +35 -89
- package/src/components/core/Button-CTA/__tests__/setup.test.ts +0 -2
- package/src/components/core/Button-CTA/__tests__/test-utils.ts +0 -2
- package/src/components/core/Button-CTA/contracts.yaml +6 -29
- package/src/components/core/Button-CTA/examples/BasicUsage.html +2 -14
- package/src/components/core/Button-CTA/examples/BasicUsage.tsx +17 -44
- package/src/components/core/Button-CTA/platforms/android/ButtonCTA.android.kt +12 -20
- package/src/components/core/Button-CTA/platforms/ios/ButtonCTA.ios.swift +12 -51
- package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.css +2 -26
- package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.ts +18 -71
- package/src/components/core/Button-CTA/types.ts +10 -28
- package/src/components/core/Chip-Base/__tests__/ChipBase.test.ts +13 -0
- package/src/components/core/Chip-Filter/__tests__/ChipFilter.test.ts +13 -0
- package/src/components/core/Chip-Input/__tests__/ChipInput.test.ts +13 -0
- package/src/components/core/Input-Text-Base/Input-Text-Base.schema.yaml +30 -2
- package/src/components/core/Input-Text-Base/README.md +25 -2
- package/src/components/core/Input-Text-Base/__tests__/focusIndicators.test.ts +16 -15
- package/src/components/core/Input-Text-Base/contracts.yaml +90 -0
- package/src/components/core/Input-Text-Base/platforms/android/InputTextBase.android.kt +26 -12
- package/src/components/core/Input-Text-Base/platforms/ios/InputTextBase.ios.swift +195 -59
- package/src/components/core/Input-Text-Base/types.ts +13 -1
- package/src/components/core/Input-Text-Email/Input-Text-Email.schema.yaml +5 -1
- package/src/components/core/Input-Text-Email/README.md +8 -7
- package/src/components/core/Input-Text-Email/platforms/android/InputTextEmail.android.kt +1 -4
- package/src/components/core/Input-Text-Email/platforms/ios/InputTextEmail.ios.swift +2 -16
- package/src/components/core/Input-Text-Password/Input-Text-Password.schema.yaml +10 -3
- package/src/components/core/Input-Text-Password/README.md +9 -8
- package/src/components/core/Input-Text-Password/contracts.yaml +5 -0
- package/src/components/core/Input-Text-Password/platforms/android/InputTextPassword.android.kt +17 -7
- package/src/components/core/Input-Text-Password/platforms/ios/InputTextPassword.ios.swift +22 -20
- package/src/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.ts +11 -2
- package/src/components/core/Input-Text-PhoneNumber/Input-Text-PhoneNumber.schema.yaml +5 -1
- package/src/components/core/Input-Text-PhoneNumber/README.md +9 -8
- package/src/components/core/Input-Text-PhoneNumber/platforms/android/InputTextPhoneNumber.android.kt +2 -5
- package/src/components/core/Input-Text-PhoneNumber/platforms/ios/InputTextPhoneNumber.ios.swift +3 -17
- package/src/components/core/Nav-Header-App/contracts.yaml +1 -1
- package/src/components/core/Nav-SegmentedChoice-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Indicator-Connector-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Indicator-Label-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Indicator-Node-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Stepper-Base/__tests__/StepperBase.test.ts +5 -2
- package/src/components/core/Progress-Stepper-Detailed/__tests__/StepperDetailed.test.ts +5 -2
- package/src/generators/DTCGFormatGenerator.ts +6 -0
- package/src/generators/__tests__/DTCGConfigOptions.test.ts +14 -5
- package/src/integration/BuildErrorHandler.ts +2 -2
- package/src/tokens/OpacityTokens.ts +1 -1
- package/src/tokens/__tests__/OpacityTokens.test.ts +3 -1
- package/src/tokens/semantic/BlendTokens.ts +26 -5
- package/src/tokens/semantic/OpacityTokens.ts +4 -4
- package/src/tools/release/__tests__/ReleasePipeline.test.ts +1 -1
- package/src/types/ComponentTypes.ts +1 -1
- package/src/types/generated/TokenTypes.ts +1 -1
- package/src/validators/StemmaTokenUsageValidator.ts +3 -2
- 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
|
-
/**
|
|
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
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
it
|
|
134
|
-
const result = calc.interactionBlend(
|
|
135
|
-
const dc = deltaC(
|
|
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
|
-
//
|
|
145
|
-
//
|
|
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(
|
|
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
|
|
194
|
-
// docs into the separate governance/ dir
|
|
195
|
-
//
|
|
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(
|
|
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
|
|
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(
|
|
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
|