@afokapu/atdd-bun 0.10.29 → 0.10.31

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.
@@ -337,6 +337,7 @@ export function unbuiltWagons(croot, trainId) {
337
337
  let sourceRoot = "src/wagons";
338
338
  try { sourceRoot = Bun.YAML.parse(readText(join(croot, "atdd-bun.yaml")))?.topology?.source_root || sourceRoot; } catch { /* default layout */ }
339
339
  const hasSource = (wagon) => [...walkFiles(join(croot, sourceRoot, wagon), isTs)].some((f) => !/\.(test|spec)\.[cm]?[jt]sx?$/.test(f));
340
+ const produces = new Map();
340
341
  (function rec(dir) {
341
342
  let entries;
342
343
  try { entries = readdirSync(dir).sort(); } catch { return; }
@@ -348,6 +349,8 @@ export function unbuiltWagons(croot, trainId) {
348
349
  if (!/\.ya?ml$/.test(name)) continue;
349
350
  let doc;
350
351
  try { doc = Bun.YAML.parse(readText(full)); } catch { continue; }
352
+ if (doc && typeof doc.wagon === "string" && Array.isArray(doc.produce))
353
+ produces.set(doc.wagon.trim(), doc.produce.map((p) => p?.name).filter((name) => typeof name === "string" && name));
351
354
  if (!doc || typeof doc.train_id !== "string" || !Array.isArray(doc.sequence)) continue;
352
355
  const wagons = new Set(doc.sequence.flatMap((step) => [step?.from, step?.to]).filter((end) => typeof end === "string" && end.startsWith("wagon:")).map((end) => end.slice(6).trim()));
353
356
  byTrain.set(doc.train_id, [...wagons]);
@@ -355,7 +358,18 @@ export function unbuiltWagons(croot, trainId) {
355
358
  })(join(croot, PLAN_ROOT));
356
359
  // Only a repository that keeps wagon code under the source root can show a wagon as not built yet. Where no wagon has
357
360
  // source there (the code lives elsewhere, as in decision-os), nothing is pending and every route is judged.
358
- const built = new Map([...new Set([...byTrain.values()].flat())].map((wagon) => [wagon, hasSource(wagon)]));
361
+ // A wagon coded outside the source root is built all the same when Cargo-moving code names an artifact it produces:
362
+ // the wagon-contract rule's own evidence (C1 #lVuJXXe3mM9E). Comments are masked, tests are not read.
363
+ let cargoCode = null;
364
+ const named = (wagon) => {
365
+ const names = produces.get(wagon) ?? [];
366
+ if (!names.length) return false;
367
+ cargoCode ??= [...walkFiles(croot, isTs)].filter((f) => !/\.(test|spec)\.[cm]?[jt]sx?$/.test(f)).map((f) => readText(f) ?? "")
368
+ .filter((text) => /\bCargo\b|\bcargo\s*\.\s*(?:put|get)\s*\(/.test(text))
369
+ .map((text) => text.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:\\])\/\/[^\n]*/g, "$1")).join("\n");
370
+ return names.some((name) => cargoCode.includes(name));
371
+ };
372
+ const built = new Map([...new Set([...byTrain.values()].flat())].map((wagon) => [wagon, hasSource(wagon) || named(wagon)]));
359
373
  const layoutInUse = [...built.values()].some(Boolean);
360
374
  for (const [trainId, wagons] of byTrain) byTrain.set(trainId, layoutInUse ? wagons.filter((wagon) => !built.get(wagon)) : []);
361
375
  pendingCache.set(croot, byTrain);
package/integrity.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.10.29",
2
+ "version": "0.10.31",
3
3
  "files": {
4
4
  "HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
5
5
  "PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
@@ -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": "99983d159528c7ef4e0a72f39f0dcc9977ffdcce728d52b83c4ef5bc70f2a3ec",
382
+ "detectors/bun_interlocking_coverage/_shared/interlocking.mjs": "448ba5f8561d225eef0e9fd8cc8c7a0bcfc755ad0214ff5758a7aefbbf35283c",
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": "a1edd5aa15892564aa8d46a93dca2d17001901e90085cd660be192a2ec747c1f",
@@ -977,7 +977,7 @@
977
977
  "lib/frontend.mjs": "d6d9b29c3d7f2d162cda03bb34b20a3224a82887b6be3db7cab375510913c9a3",
978
978
  "lib/imports.mjs": "3db4979269488fa26f6e7aefded6ece9c646be6587a10e249821a5a208109962",
979
979
  "lib/interlocking.mjs": "d0803d080ac7d7477c51be70112df1e85213d339fd3c6b71f75f26145fa8accd",
980
- "lib/scan.mjs": "7a4410f3bc2e2ebbdddb4f3dd6268987a01b9685e94c8a18ea05b4056daf4ea8",
980
+ "lib/scan.mjs": "02b0fa864cab24157a8f27b50a87a2bb66a875f90cf6632c753c4627d61456ef",
981
981
  "planner-nodes/ENFORCEMENT_SCOPE.yaml": "d8ec7039c219857f3d77af5d4d84612b533b69c1a5fcef935fbf46470adafed4",
982
982
  "planner-nodes/nodes/planner.acceptance.abstract-fields-required.convention.yaml": "bd565693f0ae861f0561ad2f7a91a8c55f9a209c3acd59c5750cfcffd96a85d8",
983
983
  "planner-nodes/nodes/planner.acceptance.authoring-guidelines.convention.yaml": "16348dceed5868041b832bde766e9c8d4a950d4032f1f3066b357644081a3b17",
@@ -1202,7 +1202,7 @@
1202
1202
  "relationships.yaml": "1cb713364aff54fb6e299e0737fe75a5d7b2e1dbd0a79b16b793aa1d269207b5",
1203
1203
  "src/agent.ts": "65e4579adb5a54977f557f8c783aba0e2a2a1d5cf3cea9f017b67ffe598b2f65",
1204
1204
  "src/board-ui.ts": "16a843705552f8f18e0e328e90e4a2a9148d771ca69f2708fe4a91302947dfad",
1205
- "src/board.ts": "6f46cd890e0dfeb4a928d969e902474fb059844561a111ca8bbfeb4ff3e2d83b",
1205
+ "src/board.ts": "4b9aa5b356d67c1b4ab4018796f9d09afba6d443dbebbb733b5d6c6a8d5a1c87",
1206
1206
  "src/ci.ts": "67e54d2cbfc44a9e42837d5e750af526d6255c539d3c452a4d5fca3226c0d9ac",
1207
1207
  "src/cli.ts": "e5ab4d2435fdcbebf462b4c2b67b495bd975d5deb668b088d8cba04a9d8b78a5",
1208
1208
  "src/delivery.ts": "2eaaf4fd87aea26dce70b4c88a4e0a2f25b9de165bc2e38330fd7eb449587901",
package/lib/scan.mjs CHANGED
@@ -132,6 +132,38 @@ function regexMayStart(text, i) {
132
132
  return false;
133
133
  }
134
134
 
135
+ // The index just past the template literal opening at `start`. A `${ … }` substitution is code: it may hold strings,
136
+ // braces and templates of its own (escaped backticks included), so the template ends only at a backtick outside them.
137
+ function templateEnd(text, start) {
138
+ const n = text.length;
139
+ let j = start + 1;
140
+ while (j < n) {
141
+ const c = text[j];
142
+ if (c === "\\") { j += 2; continue; }
143
+ if (c === "`") return j + 1;
144
+ if (c === "$" && text[j + 1] === "{") {
145
+ let depth = 1;
146
+ j += 2;
147
+ while (j < n && depth > 0) {
148
+ const d = text[j];
149
+ if (d === "`") { j = templateEnd(text, j); continue; }
150
+ if (d === '"' || d === "'") {
151
+ let k = j + 1;
152
+ while (k < n && text[k] !== d && text[k] !== "\n") k += text[k] === "\\" ? 2 : 1;
153
+ j = k + 1;
154
+ continue;
155
+ }
156
+ if (d === "{") depth++;
157
+ else if (d === "}") depth--;
158
+ j++;
159
+ }
160
+ continue;
161
+ }
162
+ j++;
163
+ }
164
+ return n;
165
+ }
166
+
135
167
  export function maskLiteralsAndComments(text) {
136
168
  const out = text.split("");
137
169
  let i = 0;
@@ -165,7 +197,11 @@ export function maskLiteralsAndComments(text) {
165
197
  if (j >= n || text[j] !== "/") { i++; continue; } // no closing slash on the line: not a regex
166
198
  blank(i + 1, j);
167
199
  i = j + 1;
168
- } else if (ch === '"' || ch === "'" || ch === "`") {
200
+ } else if (ch === "`") {
201
+ const j = templateEnd(text, i);
202
+ blank(i + 1, j - 1 >= i + 1 ? j - 1 : i + 1);
203
+ i = j;
204
+ } else if (ch === '"' || ch === "'") {
169
205
  let j = i + 1;
170
206
  while (j < n) {
171
207
  if (text[j] === "\\") { j += 2; continue; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-bun",
3
- "version": "0.10.29",
3
+ "version": "0.10.31",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/afokapu/atdd-bun.git"
package/src/board.ts CHANGED
@@ -113,9 +113,9 @@ const POLL_LIMIT_MS = 10_000;
113
113
 
114
114
  /** What a listener prints when nothing has arrived for a while: the owner's check-in, for the agent to act on (move
115
115
  * the work, or tell whoever it waits on), never to answer with a "still here" post. */
116
- export function checkIn(agent: string, quietSeconds: number): string {
116
+ export function checkIn(agent: string, quietSeconds: number, rearm = false): string {
117
117
  const quiet = quietSeconds <= 0 ? "" : quietSeconds < 60 ? ` for ${quietSeconds} s` : ` for ${Math.round(quietSeconds / 60)} min`;
118
- return `CHECK-IN for ${agent}: no message${quiet}. Where are you? Are you moving forward, or blocked on someone, and do they know? Is your stakeholder informed? Are you avoiding ceremony and rabbit holes, effectiveness first, then efficiency? Act on it: move the work, or post once to whoever you wait on; never post just to say you are still here. Then re-arm this listener.`;
118
+ return `CHECK-IN for ${agent}: no message${quiet}. Where are you? Are you moving forward, or blocked on someone, and do they know? Is your stakeholder informed? Are you avoiding ceremony and rabbit holes, effectiveness first, then efficiency? Act on it: move the work, or post once to whoever you wait on; never post just to say you are still here.${rearm ? " Then re-arm this listener." : ""}`;
119
119
  }
120
120
 
121
121
  export async function waitFor(url: string, topics: string[], agent: string, since = "all", timeoutSeconds = 0, skip: string[] = [], intervalMs = 500, seen: (id: string) => void = () => {}): Promise<Message | null> {
@@ -177,7 +177,7 @@ export async function chat(args: string[], env: Record<string, string | undefine
177
177
  // Without --since, continue from where this identity last stopped on these topics; either way, remember the place.
178
178
  const file = cursorFile(me, topics, env);
179
179
  const message = await waitFor(url, topics, me, flag("since") || loadCursor(file) || seedCursor(me, env) || "all", Number(flag("timeout") ?? 0), list(flag("skip")), 500, id => saveCursor(file, id));
180
- if (!message) { console.log(checkIn(me, Number(flag("timeout") ?? 0))); return 2; }
180
+ if (!message) { console.log(checkIn(me, Number(flag("timeout") ?? 0), true)); return 2; }
181
181
  console.log(format(message, topics.length > 1));
182
182
  return 0;
183
183
  }