@afokapu/atdd-bun 0.10.24 → 0.10.26

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.
@@ -27,11 +27,12 @@ terms:
27
27
  atdd-bun chat post writes the header: from, to, participants and conversation always; kind (task, result, review-request, verdict, rebuttal, status, blocked, heartbeat), stage, reply-to, repo, branch, worktree, goal, base and sha when they apply. The body carries the whole instruction or answer, however long: the thread is the history. A heartbeat carries nothing else; anything that needs an answer goes as blocked, never inside a heartbeat.
28
28
  - term_id: commands
29
29
  text: >-
30
- Post with the body on standard input; read what is on a topic; wait for the next message addressed to you on one or several topics (comma-separated), skipping kinds you do not act on (--skip heartbeat), bounded by --timeout so a tool call ends (exit 2: nothing yet). wait remembers where each identity stopped on each set of topics (~/.atdd-board/cursors), so re-arming a watcher is always the same command; --since still overrides it; and, for a human, atdd-bun chat alone opens the board: every topic on the left, the selected conversation on the right, live.
30
+ Post with the body on standard input; read what is on a topic; wait for the next message addressed to you on one or several topics (comma-separated), skipping kinds you do not act on (--skip heartbeat), bounded by --timeout so a tool call ends (exit 2: nothing yet). wait remembers where each identity stopped on each set of topics (~/.atdd-board/cursors), so re-arming a watcher is always the same command; --since still overrides it; follow is the listener for a host that wakes its agent on each line a running process prints (Claude Code's Monitor tool): it streams every message addressed to the identity as it arrives and keeps running, keeping the same saved place; a host that wakes its agent only when a process exits runs wait and re-arms it after each wake-up. One listener per identity. And, for a human, atdd-bun chat alone opens the board: every topic on the left, the selected conversation on the right, live.
31
31
  values:
32
32
  post: atdd-bun chat post <topic> --to <agent> --kind <kind> [--reply-to <id>] < message.md
33
33
  read: atdd-bun chat read <topic> [--mine]
34
34
  wait: atdd-bun chat wait <topic>[,<topic>...] [--since <id>] [--skip heartbeat] --timeout 540
35
+ follow: atdd-bun chat follow <topic>[,<topic>...] [--skip heartbeat,progress] [--timeout <seconds>]
35
36
  board: atdd-bun chat
36
37
  - term_id: server
37
38
  text: >-
@@ -30,6 +30,7 @@
30
30
  // is content, so neither may displace the URN from "first real line" — the
31
31
  // Vite parser counts a shebang as the first non-empty line and fails such a
32
32
  // file for a header it actually has. `firstMeaningfulNo` skips both.
33
+ import { excludedPath } from "../../lib/scan.mjs";
33
34
  import { readFileSync, statSync, readdirSync, writeFileSync } from "node:fs";
34
35
  import { join, extname, sep } from "node:path";
35
36
 
@@ -50,8 +51,7 @@ export function parseJsonEnv(name, fallback) {
50
51
  }
51
52
 
52
53
  export function isExcluded(path, excludes) {
53
- const segs = path.split(sep);
54
- return excludes.some((ex) => segs.includes(ex) || path.includes(ex));
54
+ return excludedPath(path, excludes);
55
55
  }
56
56
 
57
57
  function isScannable(file) {
@@ -21,6 +21,7 @@
21
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 { excludedPath } from "../../lib/scan.mjs";
24
25
  import { parseInterlocking as parseInterlockingDoc } from "../../lib/interlocking.mjs";
25
26
  import { readFileSync, writeFileSync, statSync, readdirSync } from "node:fs";
26
27
  import { scanExecution } from "./checks/interlocking_runtime_executes.mjs";
@@ -112,8 +113,7 @@ const SCAN_EXCLUDES = (() => {
112
113
  })();
113
114
 
114
115
  function isExcluded(path) {
115
- const segments = path.split(sep);
116
- return SCAN_EXCLUDES.some((ex) => segments.includes(ex) || path.includes(ex));
116
+ return excludedPath(path, SCAN_EXCLUDES);
117
117
  }
118
118
 
119
119
  function hasChildDir(dir, name) {
@@ -6,6 +6,7 @@
6
6
  // ZERO third-party deps — node builtins only. The interlocking route space is stack-neutral planner
7
7
  // data (snake_case, plan/_trains/_interlockings/**); the e2e tests are Bun/TS under e2e/**.
8
8
 
9
+ import { excludedPath } from "../../../lib/scan.mjs";
9
10
  import { readFileSync, statSync, readdirSync, writeFileSync } from "node:fs";
10
11
  import { join, sep } from "node:path";
11
12
 
@@ -184,8 +185,7 @@ const SCAN_EXCLUDES = (() => {
184
185
  })();
185
186
 
186
187
  function isExcluded(path) {
187
- const segments = path.split(sep);
188
- return SCAN_EXCLUDES.some((ex) => segments.includes(ex) || path.includes(ex));
188
+ return excludedPath(path, SCAN_EXCLUDES);
189
189
  }
190
190
 
191
191
  function hasChildDir(dir, name) {
@@ -21,6 +21,7 @@
21
21
  // Mirrors python/<wagon>/wagon.py and convex/<wagon>/wagon.ts;
22
22
  // `wagon.ts` is already vocabulary in coder.bun.composition-root.
23
23
 
24
+ import { excludedPath } from "../../../lib/scan.mjs";
24
25
  import { readFileSync, statSync, readdirSync, writeFileSync } from "node:fs";
25
26
  import { join, sep, basename } from "node:path";
26
27
 
@@ -88,8 +89,7 @@ const SCAN_EXCLUDES = (() => {
88
89
  })();
89
90
 
90
91
  function isExcluded(path) {
91
- const segments = path.split(sep);
92
- return SCAN_EXCLUDES.some((ex) => segments.includes(ex) || path.includes(ex));
92
+ return excludedPath(path, SCAN_EXCLUDES);
93
93
  }
94
94
 
95
95
  function hasChildDir(dir, name) {
@@ -6,6 +6,7 @@
6
6
  // kind segment (-TELEMETRY- / -EVENT- / -METRIC-), or by *.telemetry.test.*
7
7
  // colocation — the same identification tester.bun.telemetry-emit uses, plus the
8
8
  // binding header this profile adds.
9
+ import { excludedPath } from "../../lib/scan.mjs";
9
10
  import { readFileSync, statSync, readdirSync } from "node:fs";
10
11
  import { join, extname, sep } from "node:path";
11
12
  import { loadTelemetryFiles, CONCRETE_URN } from "../../src/telemetry-plan.ts";
@@ -14,8 +15,7 @@ const TEST_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
14
15
  const DEFAULT_EXCLUDES = ["node_modules", "dist", "build", ".next", ".git", "_generated"];
15
16
 
16
17
  function isExcluded(path, excludes) {
17
- const segs = path.split(sep);
18
- return excludes.some((ex) => segs.includes(ex) || path.includes(ex));
18
+ return excludedPath(path, excludes);
19
19
  }
20
20
 
21
21
  export function* walkTests(root, excludes) {
@@ -23,6 +23,7 @@
23
23
  // per-file rename: one spec file is wagon-level and legitimately covers many
24
24
  // acceptances, so a one-acceptance-per-file naming rule would fragment a real
25
25
  // suite. Both levels are parsed here.
26
+ import { excludedPath } from "../../lib/scan.mjs";
26
27
  import { readFileSync, statSync, readdirSync, writeFileSync } from "node:fs";
27
28
  import { dirname, join, extname, sep } from "node:path";
28
29
  import { fileURLToPath } from "node:url";
@@ -50,8 +51,7 @@ export function parseJsonEnv(name, fallback) {
50
51
  }
51
52
 
52
53
  function isExcluded(path, excludes) {
53
- const segs = path.split(sep);
54
- return excludes.some((ex) => segs.includes(ex) || path.includes(ex));
54
+ return excludedPath(path, excludes);
55
55
  }
56
56
 
57
57
  // Walks TEST FILES ONLY. The tester persona governs the suite; source files are
package/integrity.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.10.24",
2
+ "version": "0.10.26",
3
3
  "files": {
4
4
  "HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
5
5
  "PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
@@ -93,7 +93,7 @@
93
93
  "conventions/coder.htmx/coder.htmx.verb-endpoint-same-origin.convention.yaml": "2128d26153e2d1f19c65a1e1665d4f82c78395daedb336d6ef1256e82b74aedb",
94
94
  "conventions/coder.htmx/coder.htmx.verb-mutation-signals-progress.convention.yaml": "ae85c5ea01a09a3c41b939988088a8e5da91848d2710a1f0a1e0c86308638ac9",
95
95
  "conventions/delivery/delivery.approved-sha-resolves.convention.yaml": "68f2ecc10870e780abb05bb31d211b1923d47b56672df4b3950f437aa9cf8ca8",
96
- "conventions/delivery/delivery.board.convention.yaml": "51ff4c16dddf20504f34accc5ddcd150108fab70e4e6f5bdb36cec71d0c905a8",
96
+ "conventions/delivery/delivery.board.convention.yaml": "4c544f51de80b09961a8df41028867023ab802aa4b31dcb0a1ce29e890552e6d",
97
97
  "conventions/delivery/delivery.config-schema.convention.yaml": "4c7b449074a8bd9f89b70c702ab3e0df229105ddb4458772f894f0bf37813e9f",
98
98
  "conventions/delivery/delivery.evidence-schema.convention.yaml": "9c6a22495dfb3e2fcbca700392866c3b5660084b1f5594230eb320b499d5e5cb",
99
99
  "conventions/delivery/delivery.findings-resolved.convention.yaml": "f6574e93a9a2d4bdd6d6f5645720b1a647fc0048d95f7675fe783d2ddb62c05b",
@@ -326,10 +326,10 @@
326
326
  "detectors/bun_green_traceability_detector/fixtures/dirty/green_urn_pattern/OrderRow.ts": "841349cf639b8322558c5e004c38f81cb82180af9a69cc5c3ed9ea35d39249fb",
327
327
  "detectors/bun_green_traceability_detector/fixtures/dirty/green_urn_side/OrderRow.ts": "e08bc0fba889b2008bb528ee86f3bfaf6c6756fefc17780da843bbaf5d00ac06",
328
328
  "detectors/bun_green_traceability_detector/fixtures/dirty/green_urn_wagon_feature/OrderRow.ts": "eed3e99b3a582a41e7179228ca8c33987fdf0ca1c06d78710951057a2355d902",
329
- "detectors/bun_green_traceability_detector/urn_header.mjs": "004b0ed1f71ec476fb57a959d302da4b5869ff91be049865a806bedca40f55b9",
329
+ "detectors/bun_green_traceability_detector/urn_header.mjs": "ec43ef492e405d31b30617646e41617aaef7de8eebc2f153ce8d4e8f6c2de1b2",
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": "ec48b90b14e9c8a4e15bb50cf196af2fc673532f449e884e3c454dc57a2b06b1",
332
+ "detectors/bun_interlocking_binding/detect.mjs": "7e381c8c875b4cd3693a5388b9aeba6d0fc0d89435f3a480765458c6b9497e8b",
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": "4946e582f1ddee7346a6d546bd8309f622ffeb383f9f8ac1a02de9d20a72723d",
382
+ "detectors/bun_interlocking_coverage/_shared/interlocking.mjs": "99983d159528c7ef4e0a72f39f0dcc9977ffdcce728d52b83c4ef5bc70f2a3ec",
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",
@@ -427,7 +427,7 @@
427
427
  "detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_train_sequence/plan/_trains/_interlockings/match-resolution.yaml": "9e0f02cb0889826f2ac2fb88cd8c118c97d4321dcc5fccc0b767a511606841e6",
428
428
  "detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_train_sequence/plan/_trains/train:match:match-resolution-standard.yaml": "9ea90f674e5fc565d8893e74ad2e47f273c971ca29284dbce58a366a3e832428",
429
429
  "detectors/bun_interlocking_coverage/fixtures/dirty/interlocking_train_sequence/plan/_trains/train:match:match-resolution-timeout.yaml": "aa1b7682f99f3a9a25ec0d3d03f3798ddda8c74eb728e148b38197ddab292493",
430
- "detectors/bun_interlocking_infrastructure/_shared/interlocking.mjs": "433977b3b8242c0f061924c53e9f7488cd5cc7368edacbe007c14d749cc805f4",
430
+ "detectors/bun_interlocking_infrastructure/_shared/interlocking.mjs": "fa99c71ce8431e99af87efd31db9196516c44b0a42211764b421bf9b73c68ea6",
431
431
  "detectors/bun_interlocking_infrastructure/atdd.implementation.yaml": "fa7a1de97b19dff6515100131a3b6fc2dacf2766cd2bd44bc9dd792f6b01590d",
432
432
  "detectors/bun_interlocking_infrastructure/checks/_map.json": "5f86408f98de8f1562e679e2bd3a2194a8eb297fecf677ca774f19905942ec71",
433
433
  "detectors/bun_interlocking_infrastructure/checks/interlocking_delegates.mjs": "1aa4c87e234643e99ba5a2ff1fab3db8bfc7cbc9618c8e1a014eef2c11cce9c7",
@@ -558,7 +558,7 @@
558
558
  "detectors/bun_telemetry_code/fixtures/dirty/telemetry/commons/response-invocation-accepted/event.be.json": "cbf0ca07f717745edd88876a1c75880d7ae5755292cae4af10e1f9a2b8fccc9a",
559
559
  "detectors/bun_telemetry_code/fixtures/dirty/tests/wagons/commons/features/ingress/unit/probe.test.ts": "4dca357fbeb14c6423975e422a047b6bec3e352b2b687a809fb6d59b8f66ee0c",
560
560
  "detectors/bun_telemetry_code/registry.mjs": "ccb169a31f02d7a6af379a8c1d6a548ef688a7885e925826a43c1e0bcf670097",
561
- "detectors/bun_telemetry_test/_shared.mjs": "425340b41f958c4164f4e808633d8f5b27533280f516b7b176f58f1531da9e6c",
561
+ "detectors/bun_telemetry_test/_shared.mjs": "bb2ad89de8ff8e683efbdc5dc43bab4214cc469ad998f3477abc0a28c999f746",
562
562
  "detectors/bun_telemetry_test/atdd.implementation.yaml": "0bbe065a0cdebdf581b4a9a17b258421cb3619070d21fe44f0dbc7e765f47681",
563
563
  "detectors/bun_telemetry_test/checks/t_captured_sink.mjs": "bc616b5fbf76cde18dc6a70256a8293a2cfad98bd6a656b11608ebb1befe0d0f",
564
564
  "detectors/bun_telemetry_test/checks/t_identity_assertion.mjs": "10f089ac696cc1bb6024c6dea831d928843037395458f5a6882ecd72041b7f2d",
@@ -643,7 +643,7 @@
643
643
  "detectors/bun_tester_discipline_detector/fixtures/dirty/unresolvable_binding.test.ts": "fd878356127cd66c05a1ab2c461982366f3a757895decf19cf24634f2c476d44",
644
644
  "detectors/bun_tester_discipline_detector/fixtures/dirty/vacuous.red.test.ts": "deea6befc7f87aa54b52df40f1032e4b547cb1336da3aec985c76aeb5097364a",
645
645
  "detectors/bun_tester_discipline_detector/fixtures/dirty/wrongruntime.test.ts": "041e421ef2b2810545020b5945733822dcae416d3360657217d3bab901cc2cdd",
646
- "detectors/bun_tester_discipline_detector/test_header.mjs": "f4b5934bb08cfc3b3c0b4fc33b4136662c6643f1cad9e47d270214b5b2b48cd4",
646
+ "detectors/bun_tester_discipline_detector/test_header.mjs": "f66c371c6428eccc88b2d336e5960b44dbe3b3e81bc3eb48d3ba78d889ba6a70",
647
647
  "detectors/bun_ts_metrics_detector/atdd.implementation.yaml": "6ced804a5fdc82b6cd52be21ebacefa5d3261fca03a391178b2bf21186776364",
648
648
  "detectors/bun_ts_metrics_detector/checks/_map.json": "ed7c198564a80252f92e8ba2f50352daa1476ea88a23bfc0b3630e58c688f95d",
649
649
  "detectors/bun_ts_metrics_detector/checks/ts_complexity_cyclomatic.mjs": "56640b3a0f565c45a73b7c174adb1d6b4b6ac24d286a907bc1426f013cd6039e",
@@ -971,7 +971,7 @@
971
971
  "lib/frontend.mjs": "d6d9b29c3d7f2d162cda03bb34b20a3224a82887b6be3db7cab375510913c9a3",
972
972
  "lib/imports.mjs": "3db4979269488fa26f6e7aefded6ece9c646be6587a10e249821a5a208109962",
973
973
  "lib/interlocking.mjs": "d0803d080ac7d7477c51be70112df1e85213d339fd3c6b71f75f26145fa8accd",
974
- "lib/scan.mjs": "8aa21e400ec415e23d49e0cc90fba2744822b8855261c85614e89c7cf35c4700",
974
+ "lib/scan.mjs": "925847f91a46c8ca1bdcfd44a047e67c68441a047c51ff496b253b422baddb97",
975
975
  "planner-nodes/ENFORCEMENT_SCOPE.yaml": "d8ec7039c219857f3d77af5d4d84612b533b69c1a5fcef935fbf46470adafed4",
976
976
  "planner-nodes/nodes/planner.acceptance.abstract-fields-required.convention.yaml": "bd565693f0ae861f0561ad2f7a91a8c55f9a209c3acd59c5750cfcffd96a85d8",
977
977
  "planner-nodes/nodes/planner.acceptance.authoring-guidelines.convention.yaml": "16348dceed5868041b832bde766e9c8d4a950d4032f1f3066b357644081a3b17",
@@ -1196,9 +1196,9 @@
1196
1196
  "relationships.yaml": "1cb713364aff54fb6e299e0737fe75a5d7b2e1dbd0a79b16b793aa1d269207b5",
1197
1197
  "src/agent.ts": "65e4579adb5a54977f557f8c783aba0e2a2a1d5cf3cea9f017b67ffe598b2f65",
1198
1198
  "src/board-ui.ts": "16a843705552f8f18e0e328e90e4a2a9148d771ca69f2708fe4a91302947dfad",
1199
- "src/board.ts": "a3a8efe923858b9b5cf5ca3682dacfceb5557f8303d818687eb1325a15fa1782",
1199
+ "src/board.ts": "72893289118461fbcee460d43f91e93fe4b078cabf171c03152c447eb8b71bb9",
1200
1200
  "src/ci.ts": "67e54d2cbfc44a9e42837d5e750af526d6255c539d3c452a4d5fca3226c0d9ac",
1201
- "src/cli.ts": "e45f2666334503a91eb65b1b5ee4dd8acab6b99f62d333e2ee2b65963db6358b",
1201
+ "src/cli.ts": "c1a7becd490ef9bf024569a869abc3b62ef48c0382897455e40fac9142d5ce46",
1202
1202
  "src/delivery.ts": "f6bb6a7c42693d795b964c77b6186e1ca3a1556fd43932911a0ad42e72e7e441",
1203
1203
  "src/docs-capability.ts": "115cf27049a5cf19c133bac5ec237072be6f969a59a2ef6767e054108a21dc5c",
1204
1204
  "src/enforce.ts": "41a6f8c058a2a034996c817f224191403cc66ccc4ed4e04bbefc430014c783d8",
package/lib/scan.mjs CHANGED
@@ -13,7 +13,7 @@
13
13
  // their actual detection logic — and keeps them under the duplication convention
14
14
  // the coder extension itself enforces.
15
15
  import { readFileSync, writeFileSync, statSync, readdirSync } from "node:fs";
16
- import { join, extname, sep } from "node:path";
16
+ import { join, extname, sep, resolve, relative } from "node:path";
17
17
 
18
18
  // Build output and vendored trees are never source-under-inspection. `.atdd` is
19
19
  // excluded by the adapter, not here, because a consumer's `.atdd/config.yaml` is
@@ -41,9 +41,29 @@ export function parseJsonEnv(name, fallback) {
41
41
  }
42
42
  }
43
43
 
44
+ // Excludes are matched against the path RELATIVE to the scan root that contains it, as whole consecutive segments:
45
+ // `build` excludes a folder named build, never `building`, and `.atdd/workspaces` still matches its two segments.
46
+ // Matching the absolute path by substring excluded every file of a checkout living under, say,
47
+ // ~/Github/frg-workstation-building-profile-activation, so every profile passed blind (FWS #U3vqdzo2RwFa).
48
+ const SCAN_ROOT_PATHS = (() => {
49
+ try {
50
+ const v = JSON.parse(process.env.ATDD_SCAN_ROOTS || "[]");
51
+ return Array.isArray(v) ? v.filter((x) => typeof x === "string").map((r) => resolve(r)) : [];
52
+ } catch { return []; }
53
+ })();
54
+ export function excludedPath(path, excludes) {
55
+ const abs = resolve(path);
56
+ const root = SCAN_ROOT_PATHS.filter((r) => abs === r || abs.startsWith(r + sep)).sort((a, b) => b.length - a.length)[0];
57
+ const segs = (root ? relative(root, abs) : abs).split(/[\\/]+/).filter(Boolean);
58
+ return excludes.some((ex) => {
59
+ const parts = String(ex).split(/[\\/]+/).filter(Boolean);
60
+ for (let i = 0; parts.length && i + parts.length <= segs.length; i++) if (parts.every((part, j) => segs[i + j] === part)) return true;
61
+ return false;
62
+ });
63
+ }
64
+
44
65
  function isExcluded(path, excludes) {
45
- const segments = path.split(sep);
46
- return excludes.some((ex) => segments.includes(ex) || path.includes(ex));
66
+ return excludedPath(path, excludes);
47
67
  }
48
68
 
49
69
  // Yield every file under `root` whose extension is in `exts`. `includeTests`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-bun",
3
- "version": "0.10.24",
3
+ "version": "0.10.26",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/afokapu/atdd-bun.git"
package/src/board.ts CHANGED
@@ -164,6 +164,19 @@ export async function chat(args: string[], env: Record<string, string | undefine
164
164
  console.log(format(message, topics.length > 1));
165
165
  return 0;
166
166
  }
167
+ if (command === "follow") {
168
+ // The listener for a host that wakes its agent on each line a running process prints: every message addressed to
169
+ // this identity, as it arrives, until --timeout (seconds; none = until stopped). Same place-keeping as wait.
170
+ const file = cursorFile(me, topics, env), skip = list(flag("skip")), total = Number(flag("timeout") ?? 0);
171
+ const deadline = total > 0 ? Date.now() + total * 1000 : Infinity;
172
+ let since = flag("since") || loadCursor(file) || "all";
173
+ for (;;) {
174
+ const left = Math.ceil((deadline - Date.now()) / 1000);
175
+ if (left <= 0) return 0;
176
+ const message = await waitFor(url, topics, me, since, Math.min(60, left), skip, 500, id => { since = id; saveCursor(file, id); });
177
+ if (message) console.log(format(message, topics.length > 1));
178
+ }
179
+ }
167
180
  if (topics.length !== 1) throw new Error(`${command} takes one topic`);
168
181
  const topic = topics[0];
169
182
  if (command === "post") {
package/src/cli.ts CHANGED
@@ -26,6 +26,7 @@ const usage = {
26
26
  "atdd-bun chat post <topic> --to <agent,...> [--kind K] [--stage S] [--reply-to ID] [--repo R] [--branch B] [--worktree W] [--goal G] (body on stdin)",
27
27
  "atdd-bun chat read <topic> [--mine] [--since ID]",
28
28
  "atdd-bun chat wait <topic[,topic...]> [--since ID] [--skip KIND,...] [--timeout SECONDS]",
29
+ "atdd-bun chat follow <topic[,topic...]> [--since ID] [--skip KIND,...] [--timeout SECONDS]",
29
30
  "atdd-bun chat (the board: every topic on the left, the conversation on the right)",
30
31
  ],
31
32
  profiles: profileNames,