@3fn/core 13.0.0 → 14.1.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 +7 -16
- package/.kiro/steering/DesignerPunk-Systems-Overview.md +6 -6
- package/.kiro/steering/Spec-Feedback-Protocol.md +2 -11
- 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 +17 -6
- package/application-mcp-server/src/index.ts +26 -0
- package/dist/ComponentTokens.android.kt +12 -12
- package/dist/ComponentTokens.ios.swift +12 -12
- package/dist/ComponentTokens.web.css +3 -3
- 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 +36 -90
- package/dist/browser/designerpunk.esm.min.js +33 -36
- package/dist/browser/designerpunk.umd.js +36 -90
- package/dist/browser/designerpunk.umd.min.js +47 -50
- package/dist/browser/tokens.css +3 -3
- package/dist/build/tokens/defineComponentTokens.d.ts +10 -0
- package/dist/build/tokens/defineComponentTokens.js +26 -0
- package/dist/components/core/Avatar-Base/avatar.tokens.d.ts +21 -26
- package/dist/components/core/Avatar-Base/avatar.tokens.js +31 -34
- package/dist/components/core/Avatar-Base/index.d.ts +1 -1
- package/dist/components/core/Avatar-Base/index.js +2 -2
- 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/Button-Icon/buttonIcon.tokens.d.ts +28 -14
- package/dist/components/core/Button-Icon/buttonIcon.tokens.js +35 -20
- 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/generators/TokenFileGenerator.js +7 -2
- 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/component/progress.d.ts +65 -5
- package/dist/tokens/component/progress.js +79 -18
- 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/BUILD-SYSTEM-SETUP.md +1 -2
- package/governance/Component-Development-Guide.md +23 -13
- package/governance/Component-Development-Standards.md +21 -20
- package/governance/Component-Family-Avatar.md +6 -7
- package/governance/Component-Family-Badge.md +19 -20
- package/governance/Component-Family-Button.md +30 -43
- package/governance/Component-Family-Chip.md +14 -15
- package/governance/Component-Family-Container.md +12 -13
- package/governance/Component-Family-Data-Display.md +1 -2
- package/governance/Component-Family-Divider.md +1 -2
- package/governance/Component-Family-Form-Inputs.md +65 -65
- package/governance/Component-Family-Icon.md +9 -10
- package/governance/Component-Family-Loading.md +1 -2
- package/governance/Component-Family-Modal.md +1 -2
- package/governance/Component-Family-Navigation.md +1 -2
- package/governance/Component-Family-Progress.md +0 -1
- package/governance/Component-Inheritance-Structures.md +219 -99
- 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 +59 -44
- package/governance/Component-Templates.md +57 -61
- package/governance/Contract-System-Reference.md +7 -7
- 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 +25 -28
- package/governance/Process-Hook-Operations.md +22 -11
- package/governance/Process-Orchestration-Model-Selection.md +92 -0
- package/governance/Process-Spec-Planning.md +98 -52
- package/governance/Process-Task-Type-Definitions.md +80 -4
- package/governance/Product-Handoff-Protocol.md +2 -0
- package/governance/Rosetta-System-Architecture.md +13 -11
- 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 +47 -34
- 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 +474 -0
- package/governance/completion-documentation-guide.md +23 -37
- 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 +2 -3
- package/governance/release-management-system.md +28 -63
- 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 +24 -26
- 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/tokens/__tests__/defineComponentTokens.test.ts +113 -0
- package/src/build/tokens/defineComponentTokens.ts +43 -1
- 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/avatar.tokens.ts +31 -34
- package/src/components/core/Avatar-Base/contracts.yaml +11 -1
- package/src/components/core/Avatar-Base/index.ts +1 -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/Button-Icon/buttonIcon.tokens.ts +43 -27
- 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/TokenFileGenerator.ts +7 -2
- 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/__tests__/ProgressTokenCompliance.test.ts +5 -3
- package/src/tokens/__tests__/ProgressTokenFormulas.test.ts +11 -11
- package/src/tokens/__tests__/ProgressTokenTranslation.test.ts +22 -20
- package/src/tokens/component/progress.ts +83 -21
- package/src/tokens/semantic/BlendTokens.ts +26 -5
- package/src/tokens/semantic/OpacityTokens.ts +4 -4
- 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/components.yaml +8 -8
- package/token-index/semantics.yaml +1 -2
- package/src/tools/release/__tests__/ChangeClassifier.test.ts +0 -133
- package/src/tools/release/__tests__/ChangeExtractor.test.ts +0 -222
- package/src/tools/release/__tests__/GitHubPublisher.test.ts +0 -240
- package/src/tools/release/__tests__/NotesRenderer.test.ts +0 -142
- package/src/tools/release/__tests__/NpmPublisher.test.ts +0 -289
- package/src/tools/release/__tests__/PipelineIntegration.test.ts +0 -188
- package/src/tools/release/__tests__/ReleasePipeline.test.ts +0 -192
- package/src/tools/release/__tests__/SemanticVersionValidator.test.ts +0 -49
- package/src/tools/release/__tests__/SummaryScanner.test.ts +0 -141
- package/src/tools/release/__tests__/TagResolver.test.ts +0 -91
- package/src/tools/release/__tests__/VersionCalculator.test.ts +0 -270
- package/src/tools/release/__tests__/helpers/NpmMockHelper.ts +0 -80
- package/src/tools/release/cli/ReleasePipeline.ts +0 -165
- package/src/tools/release/cli/release-tool.ts +0 -107
- package/src/tools/release/pipeline/ChangeClassifier.ts +0 -61
- package/src/tools/release/pipeline/ChangeExtractor.ts +0 -87
- package/src/tools/release/pipeline/NotesRenderer.ts +0 -66
- package/src/tools/release/pipeline/SummaryScanner.ts +0 -70
- package/src/tools/release/pipeline/TagResolver.ts +0 -40
- package/src/tools/release/pipeline/VersionCalculator.ts +0 -375
- package/src/tools/release/publishers/GitHubPublisher.ts +0 -228
- package/src/tools/release/publishers/NpmPublisher.ts +0 -196
- package/src/tools/release/release-config.json +0 -5
- package/src/tools/release/types/index.ts +0 -282
- package/src/tools/release/validators/SemanticVersionValidator.ts +0 -67
package/mcp-server/src/relocation-integrity-gate/__tests__/relocation-integrity-gate.test.ts
CHANGED
|
@@ -19,9 +19,12 @@ import {
|
|
|
19
19
|
assertMustFixCouplings,
|
|
20
20
|
assertScope,
|
|
21
21
|
runRelocationIntegrityGate,
|
|
22
|
+
scanPromptReferences,
|
|
22
23
|
LOCKED_IDENTITY_IDS,
|
|
23
24
|
DEFAULT_PROJECT_ROOT,
|
|
25
|
+
DEFAULT_GOVERNANCE_DIR,
|
|
24
26
|
} from '../relocation-integrity-gate';
|
|
27
|
+
import { DocumentIndexer } from '../../indexer/DocumentIndexer';
|
|
25
28
|
|
|
26
29
|
describe('classifyReference — identity vs served vs template (Req 8 AC5)', () => {
|
|
27
30
|
it('classifies a placeholder path-shape as template', () => {
|
|
@@ -198,20 +201,84 @@ describe('runRelocationIntegrityGate — full gate (the 119-A exit check, Req 8
|
|
|
198
201
|
expect(result.unresolved).toEqual([]);
|
|
199
202
|
});
|
|
200
203
|
|
|
201
|
-
it('resolves
|
|
204
|
+
it('resolves a served prompt ref via legacy-fallback — fixture prompt (Req 8 AC1–AC3)', async () => {
|
|
205
|
+
// Anti-vacuity moved OFF live data (2026-07-11, Spec 122 U6): the served floor
|
|
206
|
+
// (≥1 served `.kiro/steering/*.md` ref in the REAL agent prompts) died by progress —
|
|
207
|
+
// the 122 cutovers regenerate agent prompts into runtime-neutral MCP-cue form, removing
|
|
208
|
+
// `.kiro/steering/*.md` file references. With Leonardo (U6) cut over, ZERO live prompts
|
|
209
|
+
// carry served refs (same class as the template floor's death at U3). The served-
|
|
210
|
+
// resolution behavior is now exercised NON-VACUOUSLY against a fixture prompt carrying a
|
|
211
|
+
// real relocated-doc ref, resolved through a real indexer (frozen legacy manifest seeded);
|
|
212
|
+
// the live leg below keeps the every-served-ref-resolves invariant without an inventory floor.
|
|
213
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'rig-served-'));
|
|
214
|
+
const indexer = new DocumentIndexer();
|
|
215
|
+
await indexer.indexDirectory(DEFAULT_GOVERNANCE_DIR); // seeds the frozen legacy manifest at its tail
|
|
216
|
+
try {
|
|
217
|
+
fs.mkdirSync(path.join(root, '.kiro/agents'), { recursive: true });
|
|
218
|
+
fs.writeFileSync(
|
|
219
|
+
path.join(root, '.kiro/agents', 'fixture-prompt.md'),
|
|
220
|
+
'# Fixture\n\nSee `get_section({ path: ".kiro/steering/Token-Governance.md", heading: "Token Usage Governance" })`.\n',
|
|
221
|
+
);
|
|
222
|
+
const scan = scanPromptReferences(root, indexer);
|
|
223
|
+
const served = scan.references.filter((r) => r.role === 'served');
|
|
224
|
+
expect(served.length).toBeGreaterThanOrEqual(1);
|
|
225
|
+
expect(served.every((r) => r.resolved)).toBe(true);
|
|
226
|
+
expect(served.every((r) => r.strategy === 'legacy-fallback')).toBe(true);
|
|
227
|
+
} finally {
|
|
228
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('live prompts: every served ref (if any) resolves via legacy-fallback (Req 8 AC1–AC3)', async () => {
|
|
202
233
|
const result = await runRelocationIntegrityGate();
|
|
203
234
|
const served = result.references.filter((r) => r.role === 'served');
|
|
204
|
-
|
|
235
|
+
// NO count floor: the live corpus legitimately holds zero served refs since U6 (the
|
|
236
|
+
// fixture leg above covers non-vacuous resolution). If any served refs exist, each must
|
|
237
|
+
// resolve via legacy-fallback; the resolutionMechanism description holds regardless.
|
|
205
238
|
expect(served.every((r) => r.resolved)).toBe(true);
|
|
206
239
|
expect(served.every((r) => r.strategy === 'legacy-fallback')).toBe(true);
|
|
207
240
|
expect(result.resolutionMechanism).toMatch(/legacy-fallback/);
|
|
208
241
|
});
|
|
209
242
|
|
|
210
|
-
it('excludes template placeholders from pass/fail (Req 8 AC1 — not real refs)',
|
|
243
|
+
it('excludes template placeholders from pass/fail — fixture prompt (Req 8 AC1 — not real refs)', () => {
|
|
244
|
+
// Anti-vacuity moved OFF live data (2026-07-11, Spec 122 U3): the live floor
|
|
245
|
+
// (≥1 template in the real prompts) died by progress — Ada's U2 regeneration dropped
|
|
246
|
+
// `Token-Family-{Name}.md` (3 → 2) and Lina's U3 regeneration removed the corpus's
|
|
247
|
+
// LAST template placeholders (`Component-Family-{Name}.md` / `{FamilyName}`, 2 → 0),
|
|
248
|
+
// exactly as this test's old comment predicted. The exclusion behavior is now
|
|
249
|
+
// exercised non-vacuously via the extracted reference-axis seam on a fixture prompt;
|
|
250
|
+
// the live leg below keeps the invariant without an inventory floor.
|
|
251
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'rig-tpl-'));
|
|
252
|
+
try {
|
|
253
|
+
fs.mkdirSync(path.join(root, '.kiro/agents'), { recursive: true });
|
|
254
|
+
fs.writeFileSync(
|
|
255
|
+
path.join(root, '.kiro/agents', 'fixture-prompt.md'),
|
|
256
|
+
'# Fixture\n\nSee `get_document_summary({ path: ".kiro/steering/Component-Family-{Name}.md" })`.\n',
|
|
257
|
+
);
|
|
258
|
+
// A template-only scan must never touch the resolver — throwing stub proves it.
|
|
259
|
+
const neverCalled = {
|
|
260
|
+
resolveRef: (): never => {
|
|
261
|
+
throw new Error('resolveRef must not be called for a template placeholder');
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
const scan = scanPromptReferences(root, neverCalled);
|
|
265
|
+
const templates = scan.references.filter((r) => r.role === 'template');
|
|
266
|
+
expect(templates.length).toBe(1);
|
|
267
|
+
expect(templates[0].ref).toBe('.kiro/steering/Component-Family-{Name}.md');
|
|
268
|
+
expect(templates[0].resolved).toBe(false);
|
|
269
|
+
// a template ref must never appear in unresolved — the exclusion under test
|
|
270
|
+
expect(scan.unresolved).toEqual([]);
|
|
271
|
+
} finally {
|
|
272
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('live prompts: any template placeholder stays excluded from unresolved (Req 8 AC1)', async () => {
|
|
211
277
|
const result = await runRelocationIntegrityGate();
|
|
212
278
|
const templates = result.references.filter((r) => r.role === 'template');
|
|
213
|
-
|
|
214
|
-
//
|
|
279
|
+
// NO count floor: the live corpus legitimately holds zero placeholders since U3
|
|
280
|
+
// (see the fixture leg above for the non-vacuous exclusion assertion; the classifier
|
|
281
|
+
// routing itself is directly covered by the classifyReference tests).
|
|
215
282
|
for (const t of templates) {
|
|
216
283
|
expect(result.unresolved.some((u) => u.includes(t.ref))).toBe(false);
|
|
217
284
|
}
|
|
@@ -269,14 +269,44 @@ export function assertMustFixCouplings(projectRoot: string): CouplingCheck[] {
|
|
|
269
269
|
// non-identity .kiro/steering/ doc remains referenced in any agent JSON.
|
|
270
270
|
{
|
|
271
271
|
const dir = path.join(projectRoot, '.kiro/agents');
|
|
272
|
-
const jsons = fs
|
|
272
|
+
const jsons = fs
|
|
273
|
+
.readdirSync(dir)
|
|
274
|
+
.filter((f) => f.endsWith('.json') && !f.endsWith('.attribution.json'))
|
|
275
|
+
.sort();
|
|
276
|
+
|
|
277
|
+
// Spec 122 cutover-ledger awareness (added at Ada's cutover, U2/PR #55): a GENERATED
|
|
278
|
+
// config carries only its designed ambient members — Ada's trim took the fleet total
|
|
279
|
+
// from 122 to 100 governance/ entries and broke the old flat `govEntries >= 120`
|
|
280
|
+
// floor, and every later cutover would break any fixed total again. The floor's real
|
|
281
|
+
// job is anti-vacuity over the HAND-maintained configs (proof the scan actually saw
|
|
282
|
+
// the remediated entries), so it is now PER-CONFIG (≥5 governance/ entries each; the
|
|
283
|
+
// thinnest hand config today carries 9) over NON-ledger configs only. Generated
|
|
284
|
+
// configs are exempt from the floor — their ref integrity is the generator gate's job
|
|
285
|
+
// (122-sweep-1-refs + 122-canonical-vs-truth) — but they stay IN the stray-relocating
|
|
286
|
+
// scan below: no config, hand or generated, may point a relocating doc at
|
|
287
|
+
// .kiro/steering/. When the ledger covers every seat, the floor leg is vacuously true
|
|
288
|
+
// and this gate's remediation duty is fully handed to the generation-era checks.
|
|
289
|
+
let ledgerAgents = new Set<string>();
|
|
290
|
+
try {
|
|
291
|
+
const ledgerText = fs.readFileSync(path.join(projectRoot, 'canonical/cutover-ledger.yaml'), 'utf-8');
|
|
292
|
+
ledgerAgents = new Set([...ledgerText.matchAll(/^\s*-\s*([a-z0-9-]+)\s*$/gm)].map((m) => m[1]));
|
|
293
|
+
} catch {
|
|
294
|
+
ledgerAgents = new Set(); // pre-122 tree: floor applies to all configs, as before
|
|
295
|
+
}
|
|
296
|
+
|
|
273
297
|
let govEntries = 0;
|
|
298
|
+
const thinHandConfigs: string[] = [];
|
|
274
299
|
const strayRelocating = new Set<string>();
|
|
275
300
|
let strayIdentityLeak = false; // an .kiro/steering/ entry that is NOT an identity doc
|
|
276
301
|
for (const j of jsons) {
|
|
277
302
|
const content = fs.readFileSync(path.join(dir, j), 'utf-8');
|
|
303
|
+
const agentName = j.replace(/\.json$/, '');
|
|
278
304
|
const govRe = /(?:file|skill):\/\/[^"]*governance\/[^"]*\.md/g;
|
|
279
|
-
|
|
305
|
+
const configGovEntries = (content.match(govRe) || []).length;
|
|
306
|
+
govEntries += configGovEntries;
|
|
307
|
+
if (!ledgerAgents.has(agentName) && configGovEntries < 5) {
|
|
308
|
+
thinHandConfigs.push(`${j} (${configGovEntries})`);
|
|
309
|
+
}
|
|
280
310
|
const steerRe = /(?:file|skill):\/\/[^"]*\.kiro\/steering\/([^"/]+\.md)/g;
|
|
281
311
|
let mm: RegExpExecArray | null;
|
|
282
312
|
while ((mm = steerRe.exec(content)) !== null) {
|
|
@@ -287,13 +317,13 @@ export function assertMustFixCouplings(projectRoot: string): CouplingCheck[] {
|
|
|
287
317
|
}
|
|
288
318
|
}
|
|
289
319
|
}
|
|
290
|
-
const ok =
|
|
320
|
+
const ok = thinHandConfigs.length === 0 && !strayIdentityLeak;
|
|
291
321
|
checks.push({
|
|
292
322
|
surface: 'agent-definition resources[] (file:// + skill://)',
|
|
293
323
|
remediated: ok,
|
|
294
324
|
detail: ok
|
|
295
|
-
? `governance/ entries=${govEntries} (≥
|
|
296
|
-
: `governance/ entries=${govEntries}; UNREMEDIATED relocating docs still at .kiro/steering/: ${[...strayRelocating].join(', ')}`,
|
|
325
|
+
? `governance/ entries=${govEntries} (per-hand-config floor ≥5 held; ${ledgerAgents.size} generator-governed config(s) exempt); zero relocating docs left at .kiro/steering/`
|
|
326
|
+
: `governance/ entries=${govEntries}; hand configs under the ≥5 floor: ${thinHandConfigs.join(', ') || 'none'}; UNREMEDIATED relocating docs still at .kiro/steering/: ${[...strayRelocating].join(', ')}`,
|
|
297
327
|
});
|
|
298
328
|
}
|
|
299
329
|
|
|
@@ -511,27 +541,28 @@ export interface GateOptions {
|
|
|
511
541
|
indexer?: DocumentIndexer;
|
|
512
542
|
}
|
|
513
543
|
|
|
544
|
+
/** The reference axis's scan output — see {@link scanPromptReferences}. */
|
|
545
|
+
export interface PromptReferenceScan {
|
|
546
|
+
references: ReferenceCheck[];
|
|
547
|
+
unresolved: string[];
|
|
548
|
+
referencedIdentityIds: Set<string>;
|
|
549
|
+
}
|
|
550
|
+
|
|
514
551
|
/**
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
* `
|
|
520
|
-
*
|
|
552
|
+
* The reference axis (Req 8 AC1–AC5), extracted from the orchestrator as its own
|
|
553
|
+
* exported seam: scan every prompt under `projectRoot`, classify each steering ref
|
|
554
|
+
* (template / identity / served), and resolve served refs through the live indexer.
|
|
555
|
+
* Template placeholders are excluded from pass/fail here — they are recorded but
|
|
556
|
+
* NEVER pushed to `unresolved` — which is the behavior the fixture-based unit test
|
|
557
|
+
* exercises directly (the live corpus carries zero placeholders since the Spec 122
|
|
558
|
+
* U3 regeneration, so full-gate runs can no longer test this leg non-vacuously).
|
|
559
|
+
* Only served refs touch the indexer; a template-only or identity-only scan never
|
|
560
|
+
* calls `resolveRef`.
|
|
521
561
|
*/
|
|
522
|
-
export
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
let indexer = opts.indexer;
|
|
527
|
-
if (!indexer) {
|
|
528
|
-
indexer = new DocumentIndexer();
|
|
529
|
-
// indexDirectory seeds the frozen legacy manifest at its tail (re-seed
|
|
530
|
-
// obligation) — so the legacy-fallback resolution mechanism is live here.
|
|
531
|
-
await indexer.indexDirectory(governanceDir);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// --- Reference axis (Req 8 AC1–AC5) ---
|
|
562
|
+
export function scanPromptReferences(
|
|
563
|
+
projectRoot: string,
|
|
564
|
+
indexer: Pick<DocumentIndexer, 'resolveRef'>
|
|
565
|
+
): PromptReferenceScan {
|
|
535
566
|
const references: ReferenceCheck[] = [];
|
|
536
567
|
const unresolved: string[] = [];
|
|
537
568
|
const referencedIdentityIds = new Set<string>();
|
|
@@ -583,6 +614,32 @@ export async function runRelocationIntegrityGate(opts: GateOptions = {}): Promis
|
|
|
583
614
|
}
|
|
584
615
|
}
|
|
585
616
|
|
|
617
|
+
return { references, unresolved, referencedIdentityIds };
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Run the relocation-integrity gate (the 119-A exit check).
|
|
622
|
+
*
|
|
623
|
+
* Builds a real `DocumentIndexer` over `governance/` (the frozen legacy manifest is
|
|
624
|
+
* seeded automatically at the tail of `indexDirectory`), then exercises the live
|
|
625
|
+
* `resolveRef` per prompt reference. Identity refs bypass MCP (static presence);
|
|
626
|
+
* must-fix couplings + family-guidance + scope are asserted statically.
|
|
627
|
+
*/
|
|
628
|
+
export async function runRelocationIntegrityGate(opts: GateOptions = {}): Promise<GateResult> {
|
|
629
|
+
const projectRoot = opts.projectRoot ?? DEFAULT_PROJECT_ROOT;
|
|
630
|
+
const governanceDir = opts.governanceDir ?? DEFAULT_GOVERNANCE_DIR;
|
|
631
|
+
|
|
632
|
+
let indexer = opts.indexer;
|
|
633
|
+
if (!indexer) {
|
|
634
|
+
indexer = new DocumentIndexer();
|
|
635
|
+
// indexDirectory seeds the frozen legacy manifest at its tail (re-seed
|
|
636
|
+
// obligation) — so the legacy-fallback resolution mechanism is live here.
|
|
637
|
+
await indexer.indexDirectory(governanceDir);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// --- Reference axis (Req 8 AC1–AC5) ---
|
|
641
|
+
const { references, unresolved, referencedIdentityIds } = scanPromptReferences(projectRoot, indexer);
|
|
642
|
+
|
|
586
643
|
// --- Identity axis (Req 8 AC5) ---
|
|
587
644
|
const identity = assertIdentityPresence(projectRoot, referencedIdentityIds);
|
|
588
645
|
for (const c of identity) {
|
|
@@ -16,13 +16,13 @@ import { ErrorHandler, MCPError } from '../utils/error-handler';
|
|
|
16
16
|
*/
|
|
17
17
|
export const listCrossReferencesTool = {
|
|
18
18
|
name: 'list_cross_references',
|
|
19
|
-
description: 'List cross-references in a document. Returns all referenced documents with context, source section, and line number.',
|
|
19
|
+
description: 'List cross-references in a document. Returns all referenced documents with context, source section, and line number. Enumerates both `.md`-path links (target = the linked path, verbatim) and bare-id links validated against the doc-id index (target = the doc id); unresolvable bare-id targets are excluded (surfaced via index-health). Addressing contract (D5): the `path` parameter resolves through the same strategy chain as the other document tools — doc id, then indexed relative path, then legacy `.kiro/steering/…` path.',
|
|
20
20
|
inputSchema: {
|
|
21
21
|
type: 'object' as const,
|
|
22
22
|
properties: {
|
|
23
23
|
path: {
|
|
24
24
|
type: 'string',
|
|
25
|
-
description: 'Document
|
|
25
|
+
description: 'Document ref: doc id (e.g., "token-governance"), indexed relative path, or legacy `.kiro/steering/…` path — same resolver chain as get_document_summary'
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
required: ['path'] as string[]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@3fn/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.1.0",
|
|
4
4
|
"description": "True Native cross-platform design system with mathematical foundations",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Peter Michaels Allen",
|
|
@@ -42,42 +42,42 @@
|
|
|
42
42
|
],
|
|
43
43
|
"exports": {
|
|
44
44
|
".": {
|
|
45
|
-
"
|
|
46
|
-
"
|
|
45
|
+
"types": "./dist/browser-entry.d.ts",
|
|
46
|
+
"import": "./dist/browser/designerpunk.esm.js"
|
|
47
47
|
},
|
|
48
48
|
"./components": {
|
|
49
|
-
"
|
|
50
|
-
"
|
|
49
|
+
"types": "./dist/browser-entry.d.ts",
|
|
50
|
+
"import": "./dist/browser/designerpunk.esm.js"
|
|
51
51
|
},
|
|
52
52
|
"./tokens.css": "./dist/DesignTokens.web.css",
|
|
53
53
|
"./component-tokens.css": "./dist/ComponentTokens.web.css",
|
|
54
54
|
"./config": {
|
|
55
|
+
"types": "./dist/config/index.d.ts",
|
|
55
56
|
"import": "./dist/config/index.js",
|
|
56
|
-
"require": "./dist/config/index.js"
|
|
57
|
-
"types": "./dist/config/index.d.ts"
|
|
57
|
+
"require": "./dist/config/index.js"
|
|
58
58
|
},
|
|
59
59
|
"./types": {
|
|
60
|
+
"types": "./dist/types/index.d.ts",
|
|
60
61
|
"import": "./dist/types/index.js",
|
|
61
|
-
"require": "./dist/types/index.js"
|
|
62
|
-
"types": "./dist/types/index.d.ts"
|
|
62
|
+
"require": "./dist/types/index.js"
|
|
63
63
|
},
|
|
64
64
|
"./build": {
|
|
65
|
+
"types": "./dist/build/tokens/index.d.ts",
|
|
65
66
|
"import": "./dist/build/tokens/index.js",
|
|
66
|
-
"require": "./dist/build/tokens/index.js"
|
|
67
|
-
"types": "./dist/build/tokens/index.d.ts"
|
|
67
|
+
"require": "./dist/build/tokens/index.js"
|
|
68
68
|
},
|
|
69
69
|
"./blend": {
|
|
70
|
+
"types": "./dist/blend/index.d.ts",
|
|
70
71
|
"import": "./dist/blend/index.js",
|
|
71
|
-
"require": "./dist/blend/index.js"
|
|
72
|
-
"types": "./dist/blend/index.d.ts"
|
|
72
|
+
"require": "./dist/blend/index.js"
|
|
73
73
|
},
|
|
74
74
|
"./jest-preset": {
|
|
75
75
|
"require": "./dist/testing/jest-preset.js"
|
|
76
76
|
},
|
|
77
77
|
"./testing": {
|
|
78
|
+
"types": "./dist/testing/index.d.ts",
|
|
78
79
|
"import": "./dist/testing/index.js",
|
|
79
|
-
"require": "./dist/testing/index.js"
|
|
80
|
-
"types": "./dist/testing/index.d.ts"
|
|
80
|
+
"require": "./dist/testing/index.js"
|
|
81
81
|
},
|
|
82
82
|
"./grid.css": "./src/styles/responsive-grid.css",
|
|
83
83
|
"./fonts/inter.css": "./src/assets/fonts/inter/inter.css",
|
|
@@ -106,22 +106,18 @@
|
|
|
106
106
|
"build:verify": "node verify-build.js",
|
|
107
107
|
"build:validate": "npx tsx src/validators/buildValidation.ts",
|
|
108
108
|
"lint": "eslint 'src/components/**/*.ts' 'src/components/**/*.tsx' --no-error-on-unmatched-pattern",
|
|
109
|
-
"test": "jest --
|
|
109
|
+
"test": "jest --config jest.functional.config.js",
|
|
110
110
|
"test:all": "jest",
|
|
111
|
+
"test:agent-generator": "jest --config tools/agent-generator/jest.config.js",
|
|
111
112
|
"test:consumer": "jest --roots='<rootDir>/tests' --testMatch='**/consumer-integration.test.ts' --runInBand --testTimeout=120000 --forceExit",
|
|
112
113
|
"test:resolution-matrix": "node src/config/__resolution-matrix__/run-matrix.js",
|
|
113
114
|
"test:performance": "jest --testPathPatterns='performance/__tests__|__tests__/performance'",
|
|
114
115
|
"test:performance:isolated": "jest --config jest.performance.config.js --runInBand",
|
|
115
|
-
"test:
|
|
116
|
-
"test:
|
|
117
|
-
"test:coverage": "jest --coverage --testPathIgnorePatterns='performance/__tests__|__tests__/performance'",
|
|
118
|
-
"release:analyze": "npx tsx src/tools/release/cli/release-tool.ts analyze",
|
|
119
|
-
"release:notes": "npx tsx src/tools/release/cli/release-tool.ts notes",
|
|
120
|
-
"release:run": "npx tsx src/tools/release/cli/release-tool.ts release",
|
|
121
|
-
"validate:release-setup": "node scripts/validate-release-setup.js",
|
|
122
|
-
"diagnose:release-issues": "node scripts/diagnose-release-issues.js",
|
|
116
|
+
"test:watch": "jest --watch --config jest.functional.config.js",
|
|
117
|
+
"test:coverage": "jest --coverage --config jest.functional.config.js",
|
|
123
118
|
"audit:tokens": "node scripts/audit-component-tokens.js",
|
|
124
119
|
"audit:tokens:detailed": "node scripts/audit-component-tokens.js -- --detailed",
|
|
120
|
+
"audit:coverage-map": "npx tsx tools/agent-generator/coverage-map.ts",
|
|
125
121
|
"audit:mode-parity": "npx tsx src/validators/ModeParity.ts",
|
|
126
122
|
"audit:theme-drift": "npx tsx src/tools/ThemeFileGenerator.ts > /tmp/theme-skeleton-check.ts && diff -u src/tokens/themes/dark/SemanticOverrides.ts /tmp/theme-skeleton-check.ts || echo '⚠️ Theme file drift detected — regenerate with: npm run generate:theme-skeleton'",
|
|
127
123
|
"generate:theme-skeleton": "npx tsx src/tools/ThemeFileGenerator.ts",
|
|
@@ -132,9 +128,11 @@
|
|
|
132
128
|
"figma:generate-component": "npx tsx scripts/figma-component-generator.ts",
|
|
133
129
|
"check:drift": "node scripts/check-package-name-drift.js",
|
|
134
130
|
"check:id-uniqueness": "tsx scripts/check-id-uniqueness.ts",
|
|
131
|
+
"check:section-citations": "tsx scripts/check-section-citations.ts",
|
|
135
132
|
"prepack": "npm run build",
|
|
136
|
-
"
|
|
137
|
-
"
|
|
133
|
+
"verify:token-index-clean": "git diff --quiet token-index/ || (echo '✖ token-index/ is stale relative to the committed state. Regenerate and commit it on the RELEASE BRANCH (it traverses the PR gate with the version bump) before publishing. See .kiro/hooks/RELEASE-FLOW.md.' && exit 1)",
|
|
134
|
+
"prepublishOnly": "npm run build && npm run check:drift && npm run verify:token-index-clean",
|
|
135
|
+
"postpublish": "git diff --quiet token-index/ || echo '⚠️ token-index/ changed during publish — commit it via a PR (main is branch-protected; never push it directly). See .kiro/hooks/RELEASE-FLOW.md.'"
|
|
138
136
|
},
|
|
139
137
|
"devDependencies": {
|
|
140
138
|
"@types/glob": "^8.1.0",
|
|
@@ -183,11 +183,13 @@ describe('CSS Bundling for Browser Distribution', () => {
|
|
|
183
183
|
expect(bundleContent).toMatch(/:hover/);
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
-
it('should contain disabled state styles', () => {
|
|
186
|
+
it('should not contain disabled state styles (no-disabled-states philosophy)', () => {
|
|
187
187
|
const bundleContent = fs.readFileSync(ESM_BUNDLE_PATH, 'utf-8');
|
|
188
|
-
|
|
189
|
-
//
|
|
190
|
-
|
|
188
|
+
|
|
189
|
+
// DesignerPunk does not support disabled states: if an action is
|
|
190
|
+
// unavailable, the component should not be rendered. No component CSS
|
|
191
|
+
// should ship disabled selectors or modifier classes.
|
|
192
|
+
expect(bundleContent).not.toMatch(/:disabled|--disabled/);
|
|
191
193
|
});
|
|
192
194
|
});
|
|
193
195
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "suite": "src/components/core/Avatar-Base/__tests__/Avatar.accessibility.test.ts", "pattern": "^AvatarBaseElement: \"alt\" is required when \"src\" is provided\\.", "reason": "Spec 126 (O2, ratified 2026-07-09) resolved the original Req 5.4 / Req 9.2 tension this entry was seeded against: the warn condition is now `type === 'human' && src && alt == null && !decorative`, so a genuinely decorative or empty-alt (via setAttribute) fixture no longer warns at all. This entry is retained as a safety net, not a live suppression path — every fixture in this suite that legitimately triggers the warn (human + src + absent alt, incl. the property-form alt='' collapse) wraps it in a local jest.spyOn(console,'warn').mockImplementation(() => {}), which per console-fail-setup.ts's documented shadow behavior fully swallows the output before this hook ever sees it. Pattern updated to match the new Spec 126 message string (was: '\"alt\" prop is required when \"src\" is provided')." },
|
|
3
|
+
{ "suite": "src/components/core/Chip-Base/__tests__/ChipBase.test.ts", "pattern": "^ChipBase: Could not calculate blend colors", "reason": "jsdom stylesheet limitation: jsdom never applies real CSS, so the blend-color token lookup cannot succeed and the component's designed CSS-fallback warn fires every run (fired async via rAF). PR #39 adjudicated as environmental noise, not a defect. Component-prefixed pattern (Lina review, PR #39) — a bare 'Could not calculate blend colors' substring is shared by Button components too; prefix-matching prevents accidentally swallowing a cross-component warn. Discharges the pending jsdom-stylesheet-limitation doc-addition (125-B-backlog.md item 5; Thurgood chip, 2026-07-09)." },
|
|
4
|
+
{ "suite": "src/components/core/Chip-Filter/__tests__/ChipFilter.test.ts", "pattern": "^ChipFilter: Could not calculate blend colors", "reason": "jsdom stylesheet limitation: jsdom never applies real CSS, so the blend-color token lookup cannot succeed and the component's designed CSS-fallback warn fires every run (fired async via rAF). PR #39 adjudicated as environmental noise, not a defect. Component-prefixed pattern (Lina review, PR #39) — a bare 'Could not calculate blend colors' substring is shared by Button components too; prefix-matching prevents accidentally swallowing a cross-component warn. Discharges the pending jsdom-stylesheet-limitation doc-addition (125-B-backlog.md item 5; Thurgood chip, 2026-07-09)." },
|
|
5
|
+
{ "suite": "src/components/core/Chip-Input/__tests__/ChipInput.test.ts", "pattern": "^ChipInput: Could not calculate blend colors", "reason": "jsdom stylesheet limitation: jsdom never applies real CSS, so the blend-color token lookup cannot succeed and the component's designed CSS-fallback warn fires every run (fired async via rAF). PR #39 adjudicated as environmental noise, not a defect. Component-prefixed pattern (Lina review, PR #39) — a bare 'Could not calculate blend colors' substring is shared by Button components too; prefix-matching prevents accidentally swallowing a cross-component warn. Discharges the pending jsdom-stylesheet-limitation doc-addition (125-B-backlog.md item 5; Thurgood chip, 2026-07-09)." },
|
|
6
|
+
{ "suite": "src/build/errors/__tests__/ErrorHandler.test.ts", "pattern": "^\\[(ERROR|WARNING|INFO)\\]|^Suggestions:|^ {2}\\u2022 |^ {2}\\.\\.\\. and \\d+ more", "reason": "Deliberate error-path logging: this suite constructs BuildErrors with fake TEST_ERROR/CONFIG_ERROR/BUILD_ERROR codes specifically to assert ErrorHandler's own console-emitting default logger (ErrorHandler.ts defaultLogger/formatErrorMessage) — the console output IS the behavior under test. PR #39 adjudicated as expected noise (96 error blocks silenced fleet-wide, this suite's share among them)." },
|
|
7
|
+
{ "suite": "src/build/errors/__tests__/ErrorHandler.integration.test.ts", "pattern": "^\\[(ERROR|WARNING|INFO)\\]|^Suggestions:|^ {2}\\u2022 |^ {2}\\.\\.\\. and \\d+ more", "reason": "Deliberate error-path logging: this suite exercises ErrorHandler/ErrorReporter integration with fake BUILD_ERROR/CONFIG_ERROR codes — the console output IS the behavior under test. PR #39 adjudicated as expected noise." },
|
|
8
|
+
{ "suite": "product-mcp-server/src/__tests__/GapDetector.test.ts", "pattern": "^\\[mcp-product-server\\] Component directory not found: .* \\u2014 gap detection disabled$", "reason": "Deliberate error-path logging: the 'with missing component directory' tests intentionally point GapDetector at a nonexistent path to assert its disabled-gracefully behavior — the console.error IS the assertion target for the first test and expected repeat noise for the others in the same describe block. PR #39 adjudicated as expected." },
|
|
9
|
+
{ "suite": "product-mcp-server/src/__tests__/ProductIndexerWalk.test.ts", "pattern": "^\\[mcp-product-server\\] Indexed: \\d+ screens, \\d+ domain objects, \\d+ templates, \\d+ one-off components$", "reason": "ProductIndexer.index() logs an informational indexing summary via console.error (not a failure signal) on every index() call. PR #39 adjudicated as expected per-run noise, silenced via a beforeAll spy in this suite." },
|
|
10
|
+
{ "suite": "product-mcp-server/src/__tests__/Spec108-ProductTokens.test.ts", "pattern": "^\\[mcp-product-server\\] Indexed: \\d+ screens, \\d+ domain objects, \\d+ templates, \\d+ one-off components$", "reason": "ProductIndexer.index() logs an informational indexing summary via console.error (not a failure signal) on every index() call. PR #39 adjudicated as expected per-run noise, silenced via a beforeAll spy in this suite." },
|
|
11
|
+
{ "suite": "src/__tests__/integration/Spec107-DesignLanguageContext.test.ts", "pattern": "^\\[mcp-product-server\\] Indexed: \\d+ screens, \\d+ domain objects, \\d+ templates, \\d+ one-off components$", "reason": "ProductIndexer.index() logs an informational indexing summary via console.error (not a failure signal); this suite's ProductIndexer instances are constructed inside nested beforeAll hooks, so PR #39 installed the spy in an outer beforeAll (not beforeEach) to be active before them. Adjudicated as expected noise." },
|
|
12
|
+
{ "suite": "src/cli/__tests__/figma-extract.test.ts", "pattern": "^\\u274c |^Usage: npm run figma:extract|^\\s+npm run figma:extract|^\\s+Run the DTCG generator first|^$", "reason": "Genuinely novel class, adjudicated in this task (125-B Task 4.4, not part of PR #39): figma-extract is a CLI tool whose console.error output IS its product surface — every message this suite asserts on is the deliberate, deterministic error/usage text figma-extract.ts emits (arg validation, DTCG-load failures, extraction failures, usage banners, blank-line separators). This suite installs its own module-top-level console.error spy (mockConsoleError, restored per-file, not per-test) and reads its .mock.calls directly; this hook's beforeEach captures the CURRENT console.error before spying and calls through to it, so the suite's own assertions keep working unaffected." },
|
|
13
|
+
{ "suite": "src/cli/__tests__/figma-push.test.ts", "pattern": "^\\u274c |^Resolution:|^\\s+\\d+\\.\\s|^\\s+-\\s|^\\s+Run the DTCG generator first|^$", "reason": "Genuinely novel class, adjudicated in this task (125-B Task 4.4, not part of PR #39): figma-push is a CLI tool whose console.error output IS its product surface — every message this suite asserts on is the deliberate, deterministic error/resolution text figma-push.ts emits (arg validation, DTCG-load failures, drift/sync-failure reporting with its numbered resolution steps and ' - <detail>' lists, blank-line separators). Same module-top-level console.error spy pattern as figma-extract.test.ts; this hook chains through to it rather than shadowing it." }
|
|
14
|
+
]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console-fail hook (125-B, Requirement 11 / Design C8).
|
|
3
|
+
*
|
|
4
|
+
* Captures console.error/console.warn per test and FAILS the test when
|
|
5
|
+
* unallowlisted output is produced. Checked against the checked-in
|
|
6
|
+
* allowlist (`src/__tests__/console-allowlist.json`, a (suite × message-pattern)
|
|
7
|
+
* grain — see that file for entries + reasons, and
|
|
8
|
+
* governance/classification-map.md § "console-fail-root-lanes" for the rule
|
|
9
|
+
* this hook enforces).
|
|
10
|
+
*
|
|
11
|
+
* SCOPE — root functional lanes ONLY (wired via the root jest.config.js's
|
|
12
|
+
* `setupFilesAfterEnv`). Sub-package suites (mcp-server, application-mcp-server
|
|
13
|
+
* — own jest 29 configs outside this repo's root `roots`) are NOT covered by
|
|
14
|
+
* this hook; see governance/classification-map.md
|
|
15
|
+
* § "console-fail-subpackage-deferred" for the recorded deferral.
|
|
16
|
+
* `product-mcp-server/src/**` IS covered — it lives inside root `roots`.
|
|
17
|
+
*
|
|
18
|
+
* VERSION-AGNOSTIC BY DESIGN (125-B C8 constraint, recorded for any future
|
|
19
|
+
* sub-package replication): plain `jest.spyOn` / method-swap capture only —
|
|
20
|
+
* no jest-30-only API. Stable on jest 29 and jest 30.
|
|
21
|
+
*
|
|
22
|
+
* Known limitation (recorded, not solved here): suites that install their
|
|
23
|
+
* OWN console.error/warn spy on top of this hook's (26 files predate this
|
|
24
|
+
* hook — "house style", nested `describe`-level `beforeEach`, which runs
|
|
25
|
+
* AFTER this hook's root-level `beforeEach`) shadow this hook for the
|
|
26
|
+
* duration of their own mock. Output fully swallowed by a local mock's
|
|
27
|
+
* `mockImplementation(() => {})` never reaches this hook (nothing to check
|
|
28
|
+
* — correct no-op). Output a local mock passes through (e.g. Chip's
|
|
29
|
+
* "anything else passes through" pattern) DOES reach this hook via the
|
|
30
|
+
* bound reference the local suite captured. Because this hook is a plain
|
|
31
|
+
* property swap rather than a `jest.spyOn` mock (see below), a local
|
|
32
|
+
* suite's own `jest.restoreAllMocks()` restores only ITS OWN jest-tracked
|
|
33
|
+
* spy — never this hook's swap, which isn't in Jest's mock registry at all.
|
|
34
|
+
* The residual edge case is narrower than a spyOn-based hook would have:
|
|
35
|
+
* async/deferred output (e.g. an rAF-queued warn) that fires after this
|
|
36
|
+
* test's `afterEach` has already restored `console.error`/`console.warn`
|
|
37
|
+
* back to whatever was there before this test's `beforeEach` — a normal,
|
|
38
|
+
* expected end-of-test boundary, not a cross-hook interaction — can land on
|
|
39
|
+
* the real console unrecorded and unchecked. It does not produce false
|
|
40
|
+
* failures.
|
|
41
|
+
*
|
|
42
|
+
* Chain-through, not shadow (fixed during 4.4's own gate-bite run): the
|
|
43
|
+
* "before" console.error/warn reference is captured FRESH inside
|
|
44
|
+
* `beforeEach`, immediately before this hook installs itself — not once at
|
|
45
|
+
* module load. A handful of CLI suites (figma-extract.test.ts,
|
|
46
|
+
* figma-push.test.ts) install a PERSISTENT module-top-level console mock of
|
|
47
|
+
* their own (no per-test beforeEach/afterEach — restored once in `afterAll`,
|
|
48
|
+
* if at all) and assert against its `.mock.calls`. Capturing "before" once
|
|
49
|
+
* at this module's own load time would have bound to the pristine native
|
|
50
|
+
* console — permanently bypassing those suites' own mocks for every test
|
|
51
|
+
* and silently breaking their `consoleOutput()`-style assertions.
|
|
52
|
+
*
|
|
53
|
+
* PLAIN PROPERTY SWAP, NOT `jest.spyOn` (also fixed during 4.4's gate-bite
|
|
54
|
+
* run — the reason this hook does NOT use jest.spyOn even though the header
|
|
55
|
+
* above still calls the general technique "spyOn/method swap"): `jest.spyOn`
|
|
56
|
+
* has a documented reuse rule — spying on a property that is ALREADY a Jest
|
|
57
|
+
* mock function returns and mutates THAT SAME mock object rather than
|
|
58
|
+
* wrapping a new one. Against the figma-extract/figma-push suites'
|
|
59
|
+
* persistent console.error mock, that reuse rule made `jest.spyOn(console,
|
|
60
|
+
* 'error').mockImplementation(fn)` overwrite THEIR mock's implementation
|
|
61
|
+
* with `fn` — and this hook's own "before" reference, bound to that same
|
|
62
|
+
* mutable mock object, then called back into `fn` on every invocation:
|
|
63
|
+
* infinite recursion, observed as a stack overflow on the very first
|
|
64
|
+
* `console.error` call in a chained suite. A plain property assignment
|
|
65
|
+
* (`console.error = wrapper; ...; console.error = original;`) sidesteps
|
|
66
|
+
* Jest's mock-identity bookkeeping entirely — the same technique the header
|
|
67
|
+
* above documents as version-agnostic ("plain jest.spyOn / method swap"),
|
|
68
|
+
* using the method-swap half specifically because it composes safely with
|
|
69
|
+
* another suite's pre-existing mock, which spyOn's reuse rule does not.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
import allowlistData from './console-allowlist.json';
|
|
73
|
+
import * as path from 'path';
|
|
74
|
+
|
|
75
|
+
interface AllowlistEntry {
|
|
76
|
+
suite: string;
|
|
77
|
+
pattern: string;
|
|
78
|
+
reason: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const allowlist = allowlistData as AllowlistEntry[];
|
|
82
|
+
|
|
83
|
+
function isAllowed(suite: string, message: string): boolean {
|
|
84
|
+
return allowlist.some((entry) => {
|
|
85
|
+
if (entry.suite !== suite) return false;
|
|
86
|
+
try {
|
|
87
|
+
return new RegExp(entry.pattern).test(message);
|
|
88
|
+
} catch {
|
|
89
|
+
// A malformed pattern never silently passes output through.
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function stringifyArg(value: unknown): string {
|
|
96
|
+
if (value instanceof Error) return value.stack || value.message;
|
|
97
|
+
if (typeof value === 'string') return value;
|
|
98
|
+
try {
|
|
99
|
+
return JSON.stringify(value);
|
|
100
|
+
} catch {
|
|
101
|
+
return String(value);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatArgs(args: unknown[]): string {
|
|
106
|
+
return args.map(stringifyArg).join(' ');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function currentSuitePath(): string {
|
|
110
|
+
// expect.getState().testPath is the absolute path to the currently
|
|
111
|
+
// running test file. Repo root is resolved relative to THIS file
|
|
112
|
+
// (src/__tests__/), so it's correct regardless of process cwd.
|
|
113
|
+
const testPath = expect.getState().testPath;
|
|
114
|
+
if (!testPath) return '';
|
|
115
|
+
const repoRoot = path.resolve(__dirname, '..', '..');
|
|
116
|
+
return path.relative(repoRoot, testPath).split(path.sep).join('/');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface CapturedOutput {
|
|
120
|
+
method: 'error' | 'warn';
|
|
121
|
+
message: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let captured: CapturedOutput[] = [];
|
|
125
|
+
let originalError: typeof console.error | undefined;
|
|
126
|
+
let originalWarn: typeof console.warn | undefined;
|
|
127
|
+
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
captured = [];
|
|
130
|
+
|
|
131
|
+
// Capture whatever is CURRENTLY installed (native console, or another
|
|
132
|
+
// suite's own persistent mock) immediately before swapping — see the
|
|
133
|
+
// "Chain-through, not shadow" / "Plain property swap" header notes.
|
|
134
|
+
originalError = console.error;
|
|
135
|
+
originalWarn = console.warn;
|
|
136
|
+
|
|
137
|
+
console.error = (...args: unknown[]) => {
|
|
138
|
+
captured.push({ method: 'error', message: formatArgs(args) });
|
|
139
|
+
originalError!.apply(console, args);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
console.warn = (...args: unknown[]) => {
|
|
143
|
+
captured.push({ method: 'warn', message: formatArgs(args) });
|
|
144
|
+
originalWarn!.apply(console, args);
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
afterEach(() => {
|
|
149
|
+
const suite = currentSuitePath();
|
|
150
|
+
|
|
151
|
+
// Targeted restore only — put back exactly what was there before this
|
|
152
|
+
// test's beforeEach ran (native console, or another suite's own mock).
|
|
153
|
+
if (originalError) console.error = originalError;
|
|
154
|
+
if (originalWarn) console.warn = originalWarn;
|
|
155
|
+
|
|
156
|
+
const unallowed = captured.filter((entry) => !isAllowed(suite, entry.message));
|
|
157
|
+
captured = [];
|
|
158
|
+
|
|
159
|
+
if (unallowed.length > 0) {
|
|
160
|
+
const details = unallowed
|
|
161
|
+
.map((entry) => ` [console.${entry.method}] ${entry.message}`)
|
|
162
|
+
.join('\n');
|
|
163
|
+
throw new Error(
|
|
164
|
+
`console-fail: unallowlisted console output in ${suite}:\n${details}\n\n` +
|
|
165
|
+
'Legitimate expected noise: add a { suite, pattern, reason } entry to ' +
|
|
166
|
+
'src/__tests__/console-allowlist.json. Genuine defect: fix the source.'
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
@@ -16,6 +16,22 @@ const PHILOSOPHY_PATH = path.resolve(__dirname, '../../../design-language/design
|
|
|
16
16
|
const PRODUCT_FIXTURES = path.resolve(__dirname, '../../../product-mcp-server/src/__tests__/fixtures');
|
|
17
17
|
|
|
18
18
|
describe('Spec 107 Integration: Design Language Context', () => {
|
|
19
|
+
let consoleErrorSpy: jest.SpyInstance;
|
|
20
|
+
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
// ProductIndexer logs indexing summaries via console.error (informational, not a failure).
|
|
23
|
+
// Installed in beforeAll (not beforeEach) so it's active before nested beforeAll hooks
|
|
24
|
+
// that construct/index a ProductIndexer run.
|
|
25
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterAll(() => {
|
|
29
|
+
consoleErrorSpy.mockRestore();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
consoleErrorSpy.mockClear();
|
|
34
|
+
});
|
|
19
35
|
|
|
20
36
|
describe('Application MCP — Design Philosophy', () => {
|
|
21
37
|
let indexer: any;
|