@afokapu/atdd-bun 0.10.25 → 0.10.27
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/conventions/planner.docs/planner.docs.adr-registry-derived.convention.yaml +5 -3
- package/detectors/bun_green_traceability_detector/urn_header.mjs +2 -2
- package/detectors/bun_interlocking_binding/detect.mjs +2 -2
- package/detectors/bun_interlocking_coverage/_shared/interlocking.mjs +2 -2
- package/detectors/bun_interlocking_infrastructure/_shared/interlocking.mjs +2 -2
- package/detectors/bun_telemetry_test/_shared.mjs +2 -2
- package/detectors/bun_tester_discipline_detector/test_header.mjs +2 -2
- package/integrity.json +12 -12
- package/lib/scan.mjs +23 -3
- package/package.json +1 -1
- package/src/board.ts +13 -3
- package/src/cli.ts +7 -1
- package/src/docs-capability.ts +23 -0
|
@@ -51,10 +51,12 @@ content:
|
|
|
51
51
|
fix_hint: |
|
|
52
52
|
Regenerate the registry rather than editing it:
|
|
53
53
|
|
|
54
|
-
atdd docs
|
|
54
|
+
atdd-bun docs adr-register # rewrites the adr-register region
|
|
55
|
+
atdd-bun docs adr-register --check # reports drift without writing
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
It writes only between `// BEGIN GENERATED: adr-register` and `// END GENERATED: adr-register`
|
|
58
|
+
in decisions/index.adoc (add the two markers where the list belongs), one line per ADR by
|
|
59
|
+
`:adr-id:`, from the same documents the check reads. If you find yourself typing an entry into
|
|
58
60
|
`decisions/index.adoc`, the entry belongs in an ADR file with a `:decides:` attribute instead.
|
|
59
61
|
exceptions:
|
|
60
62
|
- Prose surrounding the derived entries in `index.adoc` (a title, an introductory paragraph) is not
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
2
|
+
"version": "0.10.27",
|
|
3
3
|
"files": {
|
|
4
4
|
"HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
|
|
5
5
|
"PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"conventions/delivery/delivery.review.convention.yaml": "b033579c2d90fcb098d325dcc07c7b084e048cca43fbe8e28159ec08a412018a",
|
|
104
104
|
"conventions/delivery/delivery.reviewer-independent.convention.yaml": "8c436d66ef5b1f6d6bc5dd55a235caca2490020e88665eecadb2ca41cd4149c5",
|
|
105
105
|
"conventions/delivery/delivery.stages-complete.convention.yaml": "0e5dd8057ad51eff7906e3424a9686a81be1814447afb9a30acab43d39e49b4c",
|
|
106
|
-
"conventions/planner.docs/planner.docs.adr-registry-derived.convention.yaml": "
|
|
106
|
+
"conventions/planner.docs/planner.docs.adr-registry-derived.convention.yaml": "152cfb05c4a0e2053b09c05186d48ef330f4a94c5faddb8fbb322a81341a60a5",
|
|
107
107
|
"conventions/planner.docs/planner.docs.area-index-required.convention.yaml": "fd931e9f225bfbb5e78436d0d8a2a89cc0f5114493579962c72dd971301becce",
|
|
108
108
|
"conventions/planner.docs/planner.docs.artifact-path-shape.convention.yaml": "a9f6178d29abe9385342a46a2c57470a3631aaf5e26f01e3660b7088b042418e",
|
|
109
109
|
"conventions/planner.docs/planner.docs.asciidoc-only.convention.yaml": "9e6fadbbe9aaf6015afc479f5b392e4c4324bfc47e1c59e315d26c2e5180a5f8",
|
|
@@ -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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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,11 +1196,11 @@
|
|
|
1196
1196
|
"relationships.yaml": "1cb713364aff54fb6e299e0737fe75a5d7b2e1dbd0a79b16b793aa1d269207b5",
|
|
1197
1197
|
"src/agent.ts": "65e4579adb5a54977f557f8c783aba0e2a2a1d5cf3cea9f017b67ffe598b2f65",
|
|
1198
1198
|
"src/board-ui.ts": "16a843705552f8f18e0e328e90e4a2a9148d771ca69f2708fe4a91302947dfad",
|
|
1199
|
-
"src/board.ts": "
|
|
1199
|
+
"src/board.ts": "bc70e479874f2f05a44293226e53eb9e763b661bdcebade4f8948b0dd2ee2ead",
|
|
1200
1200
|
"src/ci.ts": "67e54d2cbfc44a9e42837d5e750af526d6255c539d3c452a4d5fca3226c0d9ac",
|
|
1201
|
-
"src/cli.ts": "
|
|
1201
|
+
"src/cli.ts": "e5ab4d2435fdcbebf462b4c2b67b495bd975d5deb668b088d8cba04a9d8b78a5",
|
|
1202
1202
|
"src/delivery.ts": "f6bb6a7c42693d795b964c77b6186e1ca3a1556fd43932911a0ad42e72e7e441",
|
|
1203
|
-
"src/docs-capability.ts": "
|
|
1203
|
+
"src/docs-capability.ts": "8a7a4360976773fe462b1860eaf26182b016bcb1fbcd6a6d5423bd81379dbe67",
|
|
1204
1204
|
"src/enforce.ts": "41a6f8c058a2a034996c817f224191403cc66ccc4ed4e04bbefc430014c783d8",
|
|
1205
1205
|
"src/hooks.ts": "2feeec2674c1462c94256de02ce59ccc6e3ff671ec4dca1bf9903c32888b600b",
|
|
1206
1206
|
"src/index.ts": "8ee4d7716794f6990ae8580e471b98256a0d0223dad063cd3b4542e598b6e744",
|
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
|
-
|
|
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
package/src/board.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -132,6 +132,16 @@ function cursorFile(agent: string, topics: string[], env: Record<string, string
|
|
|
132
132
|
return join(env.ATDD_BOARD_STATE || join(homedir(), ".atdd-board", "cursors"), key);
|
|
133
133
|
}
|
|
134
134
|
const loadCursor = (file: string) => { try { return readFileSync(file, "utf8").trim() || undefined; } catch { return undefined; } };
|
|
135
|
+
/** The place to start from on a topic set this identity has never listened to: its OLDEST saved place on any other set,
|
|
136
|
+
* so nothing unread is skipped and nothing older is replayed. Without one, the start of the board, as before. */
|
|
137
|
+
function seedCursor(agent: string, env: Record<string, string | undefined>): string | undefined {
|
|
138
|
+
const dir = env.ATDD_BOARD_STATE || join(homedir(), ".atdd-board", "cursors"), prefix = `${agent}__`.replace(/[^A-Za-z0-9@+_.-]/g, "_");
|
|
139
|
+
try {
|
|
140
|
+
const saved = readdirSync(dir).filter(name => name.startsWith(prefix)).map(name => ({ file: join(dir, name), at: statSync(join(dir, name)).mtimeMs })).sort((a, b) => a.at - b.at);
|
|
141
|
+
for (const { file } of saved) { const id = loadCursor(file); if (id) return id; }
|
|
142
|
+
} catch { /* no saved places */ }
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
135
145
|
const saveCursor = (file: string, id: string) => { try { mkdirSync(join(file, ".."), { recursive: true }); writeFileSync(file, `${id}\n`); } catch { /* best effort */ } };
|
|
136
146
|
|
|
137
147
|
export function format(message: Message, withTopic = false): string {
|
|
@@ -159,7 +169,7 @@ export async function chat(args: string[], env: Record<string, string | undefine
|
|
|
159
169
|
if (command === "wait") {
|
|
160
170
|
// Without --since, continue from where this identity last stopped on these topics; either way, remember the place.
|
|
161
171
|
const file = cursorFile(me, topics, env);
|
|
162
|
-
const message = await waitFor(url, topics, me, flag("since") || loadCursor(file) || "all", Number(flag("timeout") ?? 0), list(flag("skip")), 500, id => saveCursor(file, id));
|
|
172
|
+
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));
|
|
163
173
|
if (!message) { console.error(`no message for ${me} on ${topics.join(", ")} yet; run the same wait again to continue`); return 2; }
|
|
164
174
|
console.log(format(message, topics.length > 1));
|
|
165
175
|
return 0;
|
|
@@ -169,7 +179,7 @@ export async function chat(args: string[], env: Record<string, string | undefine
|
|
|
169
179
|
// this identity, as it arrives, until --timeout (seconds; none = until stopped). Same place-keeping as wait.
|
|
170
180
|
const file = cursorFile(me, topics, env), skip = list(flag("skip")), total = Number(flag("timeout") ?? 0);
|
|
171
181
|
const deadline = total > 0 ? Date.now() + total * 1000 : Infinity;
|
|
172
|
-
let since = flag("since") || loadCursor(file) || "all";
|
|
182
|
+
let since = flag("since") || loadCursor(file) || seedCursor(me, env) || "all";
|
|
173
183
|
for (;;) {
|
|
174
184
|
const left = Math.ceil((deadline - Date.now()) / 1000);
|
|
175
185
|
if (left <= 0) return 0;
|
package/src/cli.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { ciInit, ciStatus } from "./ci";
|
|
|
5
5
|
import { agentInit, agentStatus } from "./agent";
|
|
6
6
|
import { checkIntegrity, formatIntegrity, integrityInit, integrityStatus } from "./integrity";
|
|
7
7
|
import { journeyDocs } from "./journey-docs";
|
|
8
|
+
import { adrRegister } from "./docs-capability";
|
|
8
9
|
import { releaseCheck } from "./release";
|
|
9
10
|
import { initializeRepository } from "./setup";
|
|
10
11
|
import { chat } from "./board";
|
|
@@ -21,6 +22,7 @@ const usage = {
|
|
|
21
22
|
"atdd-bun agent <init|status> [--replace]",
|
|
22
23
|
"atdd-bun integrity [init|status] [--replace]",
|
|
23
24
|
"atdd-bun docs journeys [--out <dir>] [--check] [--force]",
|
|
25
|
+
"atdd-bun docs adr-register [--check]",
|
|
24
26
|
"atdd-bun release check",
|
|
25
27
|
"atdd-bun chat topic <program> [<tranche> [<stage> <round>]]",
|
|
26
28
|
"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)",
|
|
@@ -77,8 +79,12 @@ if (args[0] === "ci") {
|
|
|
77
79
|
const result = args[1] === "init" ? await ciInit(process.cwd(), args.includes("--replace")) : args[1] === "status" ? await ciStatus() : fail("ci requires init or status");
|
|
78
80
|
console[result.ok ? "log" : "error"](result.message); process.exit(result.ok ? 0 : 1);
|
|
79
81
|
}
|
|
82
|
+
if (args[0] === "docs" && args[1] === "adr-register") {
|
|
83
|
+
const result = await adrRegister(process.cwd(), args.includes("--check"));
|
|
84
|
+
console[result.ok ? "log" : "error"](result.message); process.exit(result.ok ? 0 : 1);
|
|
85
|
+
}
|
|
80
86
|
if (args[0] === "docs") {
|
|
81
|
-
if (args[1] !== "journeys") fail("docs requires journeys");
|
|
87
|
+
if (args[1] !== "journeys") fail("docs requires journeys or adr-register");
|
|
82
88
|
const at = args.indexOf("--out"), out = at === -1 ? undefined : args[at + 1];
|
|
83
89
|
if (at !== -1 && !out) fail("--out requires a directory");
|
|
84
90
|
const result = await journeyDocs({ out, check: args.includes("--check"), force: args.includes("--force") });
|
package/src/docs-capability.ts
CHANGED
|
@@ -184,3 +184,26 @@ export async function checkDocumentation(input: { root: string; declaration: Doc
|
|
|
184
184
|
try { return await checkDocumentationInner(input); }
|
|
185
185
|
catch (error) { return { verdict: "FAIL", findings: [{ rule_id: null, where: input.root, message: `the documentation capability raised ${error instanceof Error ? error.name : "Error"}: ${String(error)}. A capability that crashes has not discharged the obligation.` }], checked: [] }; }
|
|
186
186
|
}
|
|
187
|
+
|
|
188
|
+
const ADR_REGION = /^(\/\/\s*BEGIN GENERATED:\s*adr-register\b[^\n]*\n)[\s\S]*?(^\/\/\s*END GENERATED:\s*adr-register\b[^\n]*$)/m;
|
|
189
|
+
|
|
190
|
+
/** Regenerates the ADR register: the `// BEGIN GENERATED: adr-register` … `// END GENERATED: adr-register` region of
|
|
191
|
+
* docs/architecture/decisions/index.adoc, one line per ADR by :adr-id:, which planner.docs.adr-registry-derived
|
|
192
|
+
* requires to be projected, never typed. `check` reports drift without writing. Without the markers it writes nothing
|
|
193
|
+
* and says where they go: where the list belongs in the index is the corpus's choice. */
|
|
194
|
+
export async function adrRegister(root = process.cwd(), check = false): Promise<{ ok: boolean; message: string }> {
|
|
195
|
+
const index = join(root, ADR_INDEX);
|
|
196
|
+
if (!existsSync(index)) return { ok: false, message: `${ADR_INDEX} does not exist; create it with the adr-register markers where the list belongs` };
|
|
197
|
+
const text = await readFile(index, "utf8");
|
|
198
|
+
if (!ADR_REGION.test(text)) return { ok: false, message: `${ADR_INDEX} has no adr-register region; add "// BEGIN GENERATED: adr-register" and "// END GENERATED: adr-register" on their own lines where the list belongs` };
|
|
199
|
+
const adr = (await documents(root)).filter(d => d.path.startsWith(`${ADR_DIR}/`) && /^adr-\d{8}-\d{3}-[a-z0-9][a-z0-9-]*\.adoc$/.test(basename(d.path)) && d.attrs["adr-id"]);
|
|
200
|
+
const lines = adr.sort((a, b) => a.attrs["adr-id"].localeCompare(b.attrs["adr-id"])).map(d => {
|
|
201
|
+
const title = d.text.match(/^=\s+(.+)$/m)?.[1]?.trim() ?? d.attrs["adr-id"], status = d.attrs.status ? ` (${d.attrs.status})` : "";
|
|
202
|
+
return `* xref:${basename(d.path)}[${d.attrs["adr-id"]}] ${title}${status}`;
|
|
203
|
+
});
|
|
204
|
+
const next = text.replace(ADR_REGION, (_m, begin: string, end: string) => `${begin}${lines.join("\n")}${lines.length ? "\n" : ""}${end}`);
|
|
205
|
+
if (next === text) return { ok: true, message: `ADR register is current (${lines.length} ADRs)` };
|
|
206
|
+
if (check) return { ok: false, message: `ADR register has drifted; run atdd-bun docs adr-register` };
|
|
207
|
+
await Bun.write(index, next);
|
|
208
|
+
return { ok: true, message: `ADR register regenerated (${lines.length} ADRs)` };
|
|
209
|
+
}
|