@particle-academy/fancy-conformance 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "../../../schema/suite-manifest.schema.json",
3
+ "suite": "shared/satisfies-range",
4
+ "title": "Minimal semver range matching",
5
+ "since": "0.1.0",
6
+ "caseFormat": "table",
7
+ "cases": "cases.json",
8
+ "contract": {
9
+ "function": "satisfiesRange(version: string, range: string): boolean",
10
+ "summary": "Does a concrete version satisfy a manifest's declared range?",
11
+ "implementations": [
12
+ { "language": "node", "package": "@particle-academy/fancy-ui-cli", "symbol": "satisfiesRange" },
13
+ { "language": "node", "package": "@particle-academy/fancy-flow", "symbol": "satisfiesRange" },
14
+ { "language": "php", "package": "particle-academy/fancy-flow-php", "symbol": "NodeManifest::satisfiesRange" }
15
+ ]
16
+ },
17
+ "notes": [
18
+ "This suite is the one promoted verbatim from code that already existed, because it is the proof the method works. Three independent implementations of this function have NOT drifted, and the only thing they do differently from the suite's other triplicated contracts is that each carries this identical table in its own CI.",
19
+ "Two rows deliberately disagree with standard semver. They are tagged `non-standard` and a fourth implementation reaching for a stock semver library will fail exactly those two. That is the intended outcome: the disagreement should be a red build, not a discovery."
20
+ ]
21
+ }
@@ -0,0 +1,293 @@
1
+ {
2
+ "$schema": "../../../schema/case-table.schema.json",
3
+ "suite": "shared/strings",
4
+ "cases": [
5
+ {
6
+ "id": "0001-ascii-baseline",
7
+ "title": "ASCII baseline — markers segment as expected",
8
+ "since": "0.1.0",
9
+ "tags": [
10
+ "tokenize",
11
+ "regression"
12
+ ],
13
+ "fn": "tokenizeInlineMarkdown",
14
+ "input": {
15
+ "text": "plain **bold** and `code` end"
16
+ },
17
+ "expected": [
18
+ {
19
+ "text": "plain ",
20
+ "b": false,
21
+ "i": false,
22
+ "code": false
23
+ },
24
+ {
25
+ "text": "bold",
26
+ "b": true,
27
+ "i": false,
28
+ "code": false
29
+ },
30
+ {
31
+ "text": " and ",
32
+ "b": false,
33
+ "i": false,
34
+ "code": false
35
+ },
36
+ {
37
+ "text": "code",
38
+ "b": false,
39
+ "i": false,
40
+ "code": true
41
+ },
42
+ {
43
+ "text": " end",
44
+ "b": false,
45
+ "i": false,
46
+ "code": false
47
+ }
48
+ ],
49
+ "notes": "The control. If this row fails, the implementation is broken generally rather than on Unicode specifically, and the rows below tell you nothing."
50
+ },
51
+ {
52
+ "id": "0002-cjk-heading-text",
53
+ "title": "CJK text around bold markers",
54
+ "since": "0.1.0",
55
+ "tags": [
56
+ "tokenize",
57
+ "cjk"
58
+ ],
59
+ "fn": "tokenizeInlineMarkdown",
60
+ "input": {
61
+ "text": "CJK 日本語の見出し **強調** です"
62
+ },
63
+ "expected": [
64
+ {
65
+ "text": "CJK 日本語の見出し ",
66
+ "b": false,
67
+ "i": false,
68
+ "code": false
69
+ },
70
+ {
71
+ "text": "強調",
72
+ "b": true,
73
+ "i": false,
74
+ "code": false
75
+ },
76
+ {
77
+ "text": " です",
78
+ "b": false,
79
+ "i": false,
80
+ "code": false
81
+ }
82
+ ],
83
+ "notes": "Multi-byte in UTF-8, single code unit in UTF-16. A byte-indexing implementation is safe here only because UTF-8 is self-synchronising — no continuation byte can collide with an ASCII marker."
84
+ },
85
+ {
86
+ "id": "0003-accented-latin",
87
+ "title": "Accented Latin around bold and code",
88
+ "since": "0.1.0",
89
+ "tags": [
90
+ "tokenize",
91
+ "latin1-supplement"
92
+ ],
93
+ "fn": "tokenizeInlineMarkdown",
94
+ "input": {
95
+ "text": "café **bold** and naïve `code`"
96
+ },
97
+ "expected": [
98
+ {
99
+ "text": "café ",
100
+ "b": false,
101
+ "i": false,
102
+ "code": false
103
+ },
104
+ {
105
+ "text": "bold",
106
+ "b": true,
107
+ "i": false,
108
+ "code": false
109
+ },
110
+ {
111
+ "text": " and naïve ",
112
+ "b": false,
113
+ "i": false,
114
+ "code": false
115
+ },
116
+ {
117
+ "text": "code",
118
+ "b": false,
119
+ "i": false,
120
+ "code": true
121
+ }
122
+ ]
123
+ },
124
+ {
125
+ "id": "0004-emoji-surrogate",
126
+ "title": "Emoji (astral plane) around bold markers",
127
+ "since": "0.1.0",
128
+ "tags": [
129
+ "tokenize",
130
+ "emoji",
131
+ "surrogate"
132
+ ],
133
+ "fn": "tokenizeInlineMarkdown",
134
+ "input": {
135
+ "text": "emoji 🎉 **bold** end"
136
+ },
137
+ "expected": [
138
+ {
139
+ "text": "emoji 🎉 ",
140
+ "b": false,
141
+ "i": false,
142
+ "code": false
143
+ },
144
+ {
145
+ "text": "bold",
146
+ "b": true,
147
+ "i": false,
148
+ "code": false
149
+ },
150
+ {
151
+ "text": " end",
152
+ "b": false,
153
+ "i": false,
154
+ "code": false
155
+ }
156
+ ],
157
+ "notes": "A surrogate PAIR in UTF-16, so the JS loop visits it as two separate iterations and reassembles it by appending both halves in order. Rust cannot inherit this: slicing &str at a non-char boundary panics rather than producing half a character."
158
+ },
159
+ {
160
+ "id": "0005-combining-mark",
161
+ "title": "Combining mark is not split from its base",
162
+ "since": "0.1.0",
163
+ "tags": [
164
+ "tokenize",
165
+ "combining"
166
+ ],
167
+ "fn": "tokenizeInlineMarkdown",
168
+ "input": {
169
+ "text": "á combining **mark** here"
170
+ },
171
+ "expected": [
172
+ {
173
+ "text": "á combining ",
174
+ "b": false,
175
+ "i": false,
176
+ "code": false
177
+ },
178
+ {
179
+ "text": "mark",
180
+ "b": true,
181
+ "i": false,
182
+ "code": false
183
+ },
184
+ {
185
+ "text": " here",
186
+ "b": false,
187
+ "i": false,
188
+ "code": false
189
+ }
190
+ ],
191
+ "notes": "U+0301 is a separate scalar value from its base letter. An implementation that segments by grapheme cluster rather than scalar will produce the same TEXT here but must not reorder or drop the mark."
192
+ },
193
+ {
194
+ "id": "0006-marker-adjacent-cjk",
195
+ "title": "Bold markers directly adjacent to CJK, no spaces",
196
+ "since": "0.1.0",
197
+ "tags": [
198
+ "tokenize",
199
+ "cjk",
200
+ "edge"
201
+ ],
202
+ "fn": "tokenizeInlineMarkdown",
203
+ "input": {
204
+ "text": "日本語**強調**日本語"
205
+ },
206
+ "expected": [
207
+ {
208
+ "text": "日本語",
209
+ "b": false,
210
+ "i": false,
211
+ "code": false
212
+ },
213
+ {
214
+ "text": "強調",
215
+ "b": true,
216
+ "i": false,
217
+ "code": false
218
+ },
219
+ {
220
+ "text": "日本語",
221
+ "b": false,
222
+ "i": false,
223
+ "code": false
224
+ }
225
+ ],
226
+ "notes": "No ASCII whitespace anywhere near the markers. This is the row that fails first if an implementation looks backward or forward one BYTE to decide whether a marker is word-adjacent."
227
+ },
228
+ {
229
+ "id": "0007-emoji-inside-code",
230
+ "title": "Emoji and accents inside a code span",
231
+ "since": "0.1.0",
232
+ "tags": [
233
+ "tokenize",
234
+ "emoji",
235
+ "code-span"
236
+ ],
237
+ "fn": "tokenizeInlineMarkdown",
238
+ "input": {
239
+ "text": "x `🎉 café` y"
240
+ },
241
+ "expected": [
242
+ {
243
+ "text": "x ",
244
+ "b": false,
245
+ "i": false,
246
+ "code": false
247
+ },
248
+ {
249
+ "text": "🎉 café",
250
+ "b": false,
251
+ "i": false,
252
+ "code": true
253
+ },
254
+ {
255
+ "text": " y",
256
+ "b": false,
257
+ "i": false,
258
+ "code": false
259
+ }
260
+ ],
261
+ "notes": "Code spans are copied out by index rather than scanned character by character, so this exercises the slice path instead of the append path."
262
+ },
263
+ {
264
+ "id": "0008-unclosed-code",
265
+ "title": "Unclosed code span swallows the rest, including non-ASCII",
266
+ "since": "0.1.0",
267
+ "tags": [
268
+ "tokenize",
269
+ "edge",
270
+ "fail-open"
271
+ ],
272
+ "fn": "tokenizeInlineMarkdown",
273
+ "input": {
274
+ "text": "trailing `unclosed 日本語"
275
+ },
276
+ "expected": [
277
+ {
278
+ "text": "trailing ",
279
+ "b": false,
280
+ "i": false,
281
+ "code": false
282
+ },
283
+ {
284
+ "text": "`unclosed 日本語",
285
+ "b": false,
286
+ "i": false,
287
+ "code": false
288
+ }
289
+ ],
290
+ "notes": "An unterminated backtick falls through as literal text rather than erroring. The remainder is taken as one slice to end-of-string, so a byte-indexed implementation must land on a char boundary."
291
+ }
292
+ ]
293
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "../../../schema/suite-manifest.schema.json",
3
+ "suite": "shared/strings",
4
+ "title": "String indexing across scripts — inline markdown segmentation",
5
+ "since": "0.1.0",
6
+ "caseFormat": "table",
7
+ "cases": "cases.json",
8
+ "contract": {
9
+ "function": "tokenizeInlineMarkdown(text: string) -> InlineRun[]",
10
+ "summary": "Split one paragraph of inline markdown into styled runs. Chosen as this suite's probe because it is the smallest shipped function in the suite that indexes into a string character by character AND slices it by offset — the two operations that behave differently in every language.",
11
+ "runShape": { "text": "string", "b": "bool", "i": "bool", "code": "bool" },
12
+ "reference": "php",
13
+ "referenceNote": "particle-academy/dark-slide's Helpers\\MarkdownInline::tokenize. Goldens were generated by running it, then cross-checked against the TypeScript port before being written down.",
14
+ "implementations": [
15
+ { "language": "php", "package": "particle-academy/dark-slide", "symbol": "Helpers\\MarkdownInline::tokenize" },
16
+ { "language": "node", "package": "@particle-academy/dark-slide-js", "symbol": "MarkdownInline.tokenize" }
17
+ ]
18
+ },
19
+ "notes": [
20
+ "PHP indexes these strings by BYTE and the TypeScript port by UTF-16 CODE UNIT, and they do not disagree — verified across CJK, emoji, combining marks and accented Latin. Both loops only ever compare ASCII markers and cut at those positions, and neither a UTF-8 continuation byte nor a UTF-16 surrogate can collide with ASCII. UTF-8 is self-synchronising, which is exactly the property that makes byte-indexing safe here.",
21
+ "The agreement is INCIDENTAL, not designed, which is the entire reason this suite exists. Rust cannot inherit it — slicing a &str at a non-char boundary panics — and a well-meant tidy to mb_substr or Array.from on either existing side would change the output with nothing to catch it.",
22
+ "Recommendation for a new implementation: iterate Unicode SCALAR VALUES (Rust `chars()`, Go `range` over a string, Python's native str). Do not iterate bytes, and do not assume one index step is one visible character."
23
+ ]
24
+ }