@afokapu/atdd-bun 0.10.13 → 0.10.15

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.
@@ -18,9 +18,10 @@
18
18
  //
19
19
  // CONTRACT (atdd.workspace.bun v1.1): reads ATDD_SCAN_ROOTS, writes RAW
20
20
  // {rule_id,file,line,col,evidence,source_line} violations to ATDD_VIOLATIONS_REPORT, exits 0.
21
- // Zero third-party deps — node builtins only. The plan/_trains interlocking YAML route space is
21
+ // Zero third-party deps: Node and Bun builtins only. The plan/_trains interlocking YAML route space is
22
22
  // stack-neutral planner data (snake_case, core #1248); the runtime + JOURNEY_MAP + trace are Bun/TS.
23
23
 
24
+ import { parseInterlocking as parseInterlockingDoc } from "../../lib/interlocking.mjs";
24
25
  import { readFileSync, writeFileSync, statSync, readdirSync } from "node:fs";
25
26
  import { scanExecution } from "./checks/interlocking_runtime_executes.mjs";
26
27
  import { join, sep, resolve } from "node:path";
@@ -217,8 +218,10 @@ function appFile(croot) {
217
218
  /* absent */
218
219
  }
219
220
  }
221
+ // Beyond the named files, only the module that declares the map counts: a test or wagon that imports it only
222
+ // mentions it, and taking the first mention made a route test the Station Master (C1, #MjeoAs7SZUHk).
220
223
  for (const f of walkFiles(croot, isTs)) {
221
- if (/\bJOURNEY_MAP\b/.test(readText(f))) return f;
224
+ if (!/\.(test|spec)\.[cm]?[jt]sx?$/.test(f) && /\b(?:const|let|var)\s+JOURNEY_MAP\b/.test(readText(f))) return f;
222
225
  }
223
226
  return null;
224
227
  }
@@ -231,68 +234,10 @@ function rel(path, root) {
231
234
  // ── interlocking YAML parse (CONSUMES core #1248 fields; snake_case planner data) ─────────────
232
235
 
233
236
  function parseInterlocking(text) {
234
- const lines = text.split(/\r?\n/);
235
- const idM = text.match(/^interlocking_id:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/m);
236
- if (!idM) return null;
237
- const interlockingId = idM[1].trim();
238
-
239
- const routes = [];
240
- const ri = lines.findIndex((l) => /^routes:\s*(?:#.*)?$/.test(l));
241
- if (ri >= 0) {
242
- for (let i = ri + 1; i < lines.length; i++) {
243
- const l = lines[i];
244
- if (/^\S/.test(l)) break; // dedent to a new top-level key ends the routes block
245
- const rm = l.match(/^\s*-\s*route_id:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
246
- if (rm) {
247
- routes.push({ routeId: rm[1].trim(), line: i + 1, sourceLine: l, trainId: null, trainPath: null });
248
- } else if (routes.length) {
249
- const cur = routes[routes.length - 1];
250
- const tm = l.match(/^\s*train_id:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
251
- if (tm) cur.trainId = tm[1].trim();
252
- const pm = l.match(/^\s*train_path:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
253
- if (pm) cur.trainPath = pm[1].trim();
254
- }
255
- }
256
- }
257
- if (!routes.length) return null;
258
-
259
- let exposed = false;
260
- const actions = [];
261
- const ei = lines.findIndex((l) => /^entrypoint:\s*(?:#.*)?$/.test(l));
262
- if (ei >= 0) {
263
- for (let i = ei + 1; i < lines.length; i++) {
264
- const l = lines[i];
265
- if (/^\S/.test(l)) break; // dedent ends the entrypoint block
266
- if (/^\s*exposed:\s*true\b/.test(l)) exposed = true;
267
- // FLOW STYLE TOO: `actions: [resolve_match, other]`.
268
- //
269
- // The interlocking YAML is stack-neutral planner data and flow sequences are
270
- // ordinary YAML. Python reads it with a real parser and accepts both; this
271
- // hand-rolled line scanner accepted only block style, so the SAME plan file
272
- // was judged differently depending on which stack read it — and the verdict
273
- // was "exposed interlocking is not Station-Master-reachable", a strict
274
- // blocking finding, against a consumer whose YAML was perfectly valid.
275
- // Found by running this provider against a Bun consumer written from scratch.
276
- const flow = l.match(/^\s*actions:\s*\[([^\]]*)\]\s*(?:#.*)?$/);
277
- if (flow) {
278
- for (const raw of flow[1].split(",")) {
279
- const a = raw.trim().replace(/^["']|["']$/g, "");
280
- if (a) actions.push(a);
281
- }
282
- continue;
283
- }
284
- if (/^\s*actions:\s*(?:#.*)?$/.test(l)) {
285
- for (let j = i + 1; j < lines.length; j++) {
286
- const am = lines[j].match(/^\s*-\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
287
- if (am) actions.push(am[1].trim());
288
- else break;
289
- }
290
- }
291
- }
292
- }
293
-
237
+ const rec = parseInterlockingDoc(text);
238
+ if (!rec) return null;
294
239
  const parallelFields = PARALLEL_FIELDS.filter((f) => new RegExp("^\\s*" + f + "\\s*:", "m").test(text));
295
- return { interlockingId, routes, exposed, actions, parallelFields, rawText: text };
240
+ return { ...rec, parallelFields };
296
241
  }
297
242
 
298
243
  // ── Station Master JOURNEY_MAP parse (server.ts) ─────────────────────────
@@ -270,78 +270,9 @@ export function e2eFiles(croot) {
270
270
  return [...walkFiles(join(croot, "e2e"), isTs)].sort();
271
271
  }
272
272
 
273
- // Interlocking YAML parse — declares interlocking_id + a non-empty routes list (core #1248,
274
- // snake_case planner data). Registry/projection docs with no routes yield null.
275
- export function parseInterlocking(text) {
276
- const lines = text.split(/\r?\n/);
277
- const idM = text.match(/^interlocking_id:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/m);
278
- if (!idM) return null;
279
- const interlockingId = idM[1].trim();
280
-
281
- const routes = [];
282
- const ri = lines.findIndex((l) => /^routes:\s*(?:#.*)?$/.test(l));
283
- if (ri >= 0) {
284
- for (let i = ri + 1; i < lines.length; i++) {
285
- const l = lines[i];
286
- if (/^\S/.test(l)) break;
287
- const rm = l.match(/^\s*-\s*route_id:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
288
- if (rm) {
289
- routes.push({
290
- routeId: rm[1].trim(),
291
- line: i + 1,
292
- sourceLine: l,
293
- trainId: null,
294
- category: null,
295
- });
296
- } else if (routes.length) {
297
- const cur = routes[routes.length - 1];
298
- const tm = l.match(/^\s*train_id:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
299
- if (tm) cur.trainId = tm[1].trim();
300
- // `category:` only — `category_digit:` is retired (#1421 / #1440) and rejected by
301
- // core's schema, so there is nothing left to read off a conforming artifact.
302
- const cm = l.match(/^\s*category:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
303
- if (cm) cur.category = cm[1].trim();
304
- }
305
- }
306
- }
307
- if (!routes.length) return null;
308
-
309
- let exposed = false;
310
- const actions = [];
311
- const ei = lines.findIndex((l) => /^entrypoint:\s*(?:#.*)?$/.test(l));
312
- if (ei >= 0) {
313
- for (let i = ei + 1; i < lines.length; i++) {
314
- const l = lines[i];
315
- if (/^\S/.test(l)) break;
316
- if (/^\s*exposed:\s*true\b/.test(l)) exposed = true;
317
- // FLOW STYLE TOO: `actions: [resolve_match, other]`.
318
- //
319
- // The interlocking YAML is stack-neutral planner data and flow sequences are
320
- // ordinary YAML. Python reads it with a real parser and accepts both; this
321
- // hand-rolled line scanner accepted only block style, so the SAME plan file
322
- // was judged differently depending on which stack read it — and the verdict
323
- // was "exposed interlocking is not Station-Master-reachable", a strict
324
- // blocking finding, against a consumer whose YAML was perfectly valid.
325
- // Found by running this provider against a Bun consumer written from scratch.
326
- const flow = l.match(/^\s*actions:\s*\[([^\]]*)\]\s*(?:#.*)?$/);
327
- if (flow) {
328
- for (const raw of flow[1].split(",")) {
329
- const a = raw.trim().replace(/^["']|["']$/g, "");
330
- if (a) actions.push(a);
331
- }
332
- continue;
333
- }
334
- if (/^\s*actions:\s*(?:#.*)?$/.test(l)) {
335
- for (let j = i + 1; j < lines.length; j++) {
336
- const am = lines[j].match(/^\s*-\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
337
- if (am) actions.push(am[1].trim());
338
- else break;
339
- }
340
- }
341
- }
342
- }
343
- return { interlockingId, routes, exposed, actions, rawText: text };
344
- }
273
+ // Interlocking YAML parse: interlocking_id + a non-empty routes list (core #1248, snake_case planner data).
274
+ // Registry/projection docs with no routes yield null. One parser for every detector, in lib/.
275
+ export { parseInterlocking } from "../../../lib/interlocking.mjs";
345
276
 
346
277
  export function lineOf(text, pattern, def = 1) {
347
278
  const lines = text.split(/\r?\n/);
package/integrity.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.10.13",
2
+ "version": "0.10.15",
3
3
  "files": {
4
4
  "HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
5
5
  "PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
@@ -329,7 +329,7 @@
329
329
  "detectors/bun_green_traceability_detector/urn_header.mjs": "004b0ed1f71ec476fb57a959d302da4b5869ff91be049865a806bedca40f55b9",
330
330
  "detectors/bun_interlocking_binding/atdd.implementation.yaml": "8df233a7c337d38ab4fc92a309273b91fe713b27507a4b2cfa66ffc7a948d520",
331
331
  "detectors/bun_interlocking_binding/checks/interlocking_runtime_executes.mjs": "ea4c32914a007962012e9062cc64063fa939ff0a59c07960c34872d058720955",
332
- "detectors/bun_interlocking_binding/detect.mjs": "42767b7d329b5b2623de37f450c416fe6feb4a7ae737201376e93aa74d493dea",
332
+ "detectors/bun_interlocking_binding/detect.mjs": "ec48b90b14e9c8a4e15bb50cf196af2fc673532f449e884e3c454dc57a2b06b1",
333
333
  "detectors/bun_interlocking_binding/fixtures/clean/e2e/interlockings/match-resolution/trace.test.ts": "5dadc041ca9825f404340ca428e14bbff5e00824ca6c25464f21cd2ce56044d8",
334
334
  "detectors/bun_interlocking_binding/fixtures/clean/plan/_trains/_interlockings/match-resolution.yaml": "11884309beb9bcceccfeda7917ea73d51a530366db5eb60772b03cf5eeb239cd",
335
335
  "detectors/bun_interlocking_binding/fixtures/clean/plan/_trains/train:match:match-resolution-standard.yaml": "a7067661a723e02e0e844e6b90ad187f620dd328e1b9be45163c04db1919ce77",
@@ -379,7 +379,7 @@
379
379
  "detectors/bun_interlocking_binding/fixtures/dirty_transcribed_runtime/server.ts": "d5bc34c5b1b2b0a511e0c4604ce6f9c77491dca9aee994be720540f3052babc1",
380
380
  "detectors/bun_interlocking_binding/fixtures/dirty_transcribed_runtime/src/trains/interlocking.ts": "9bcfdb4ecff6efd1f658b9fd587342615179924e25468c75daa07c2626d4d8ab",
381
381
  "detectors/bun_interlocking_binding/fixtures/dirty_transcribed_runtime/src/trains/runner.ts": "0e4b885e50f12cc2a8c8f6740bf83e7b2e7137a28e1e143586e9a8550ea5bd45",
382
- "detectors/bun_interlocking_coverage/_shared/interlocking.mjs": "dd484cec27919f342aa5a3ba631cf197acab4f359e7fe0ffd1123f968954fc52",
382
+ "detectors/bun_interlocking_coverage/_shared/interlocking.mjs": "6a3440e53ba13948edfbb1ef8de4c01e4fd022ad6f9d44739f814220cc583417",
383
383
  "detectors/bun_interlocking_coverage/atdd.implementation.yaml": "f160517a8138ba3ca05e3d130e6fbfb796e643376f25743fdef4fc210196525b",
384
384
  "detectors/bun_interlocking_coverage/checks/_map.json": "0a218ea319cc6258af7bd721c2154cb9561fe67e80d80ef32d08fc77bc9403c3",
385
385
  "detectors/bun_interlocking_coverage/checks/interlocking_production_runner.mjs": "136eb0955052f2485740adfb888f5ba6f1414dcdb53d505034b8db40420a611a",
@@ -968,6 +968,7 @@
968
968
  "detectors/planner_telemetry_plan/fixtures/dirty/telemetry/commons/response-invocation-accepted/notes.json": "ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356",
969
969
  "lib/frontend.mjs": "d6d9b29c3d7f2d162cda03bb34b20a3224a82887b6be3db7cab375510913c9a3",
970
970
  "lib/imports.mjs": "3db4979269488fa26f6e7aefded6ece9c646be6587a10e249821a5a208109962",
971
+ "lib/interlocking.mjs": "d0803d080ac7d7477c51be70112df1e85213d339fd3c6b71f75f26145fa8accd",
971
972
  "lib/scan.mjs": "8aa21e400ec415e23d49e0cc90fba2744822b8855261c85614e89c7cf35c4700",
972
973
  "planner-nodes/ENFORCEMENT_SCOPE.yaml": "d8ec7039c219857f3d77af5d4d84612b533b69c1a5fcef935fbf46470adafed4",
973
974
  "planner-nodes/nodes/planner.acceptance.abstract-fields-required.convention.yaml": "bd565693f0ae861f0561ad2f7a91a8c55f9a209c3acd59c5750cfcffd96a85d8",
@@ -0,0 +1,34 @@
1
+ // The interlocking document (plan/_trains/_interlockings/<id>.yaml), read with a YAML parser. Detectors once scanned
2
+ // it line by line and stopped at the first line in column 0 after `routes:`, so a compact list (`routes:` then
3
+ // `- route_id:` at column 0, the same YAML) parsed to no routes and every route rule passed without judging anything
4
+ // (FWS, atdd-maintainer #G3aXIPPW1Mz5). Line numbers, which findings cite, are looked up in the text afterwards.
5
+
6
+ const scalar = (value) => (typeof value === "string" || typeof value === "number" ? String(value).trim() : null);
7
+ const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8
+
9
+ /** { interlockingId, routes: [{ routeId, line, sourceLine, trainId, trainPath, category }], exposed, actions, rawText },
10
+ * or null for a document with no interlocking_id or no routes (registries and projections). */
11
+ export function parseInterlocking(text) {
12
+ let doc;
13
+ try { doc = Bun.YAML.parse(text); } catch { return null; }
14
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null;
15
+ const interlockingId = scalar(doc.interlocking_id);
16
+ if (!interlockingId) return null;
17
+ const lines = text.split(/\r?\n/), taken = new Set();
18
+ const lineOf = (routeId) => {
19
+ const pattern = new RegExp(`^\\s*(?:-\\s*)?route_id:\\s*["']?${escape(routeId)}["']?\\s*(?:#.*)?$`);
20
+ const index = lines.findIndex((line, i) => !taken.has(i) && pattern.test(line));
21
+ if (index >= 0) taken.add(index);
22
+ return index;
23
+ };
24
+ const routes = (Array.isArray(doc.routes) ? doc.routes : []).flatMap((route) => {
25
+ const routeId = route && typeof route === "object" ? scalar(route.route_id) : null;
26
+ if (!routeId) return [];
27
+ const index = lineOf(routeId);
28
+ return [{ routeId, line: index >= 0 ? index + 1 : 1, sourceLine: index >= 0 ? lines[index] : "", trainId: scalar(route.train_id), trainPath: scalar(route.train_path), category: scalar(route.category) }];
29
+ });
30
+ if (!routes.length) return null;
31
+ const entrypoint = doc.entrypoint && typeof doc.entrypoint === "object" ? doc.entrypoint : {};
32
+ const actions = (Array.isArray(entrypoint.actions) ? entrypoint.actions : []).map(scalar).filter(Boolean);
33
+ return { interlockingId, routes, exposed: entrypoint.exposed === true, actions, rawText: text };
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-bun",
3
- "version": "0.10.13",
3
+ "version": "0.10.15",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/afokapu/atdd-bun.git"