@afokapu/atdd-bun 0.6.0 → 0.6.2
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/README.md +87 -484
- package/conventions/atdd-bun.planner/atdd-bun.planner.acceptance-identity.convention.yaml +27 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.identity-required.convention.yaml +28 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.identity-unique.convention.yaml +27 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.interlocking-participant-resolves.convention.yaml +28 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.parse.convention.yaml +27 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.reference-resolves.convention.yaml +28 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.schema.convention.yaml +30 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.train-wagon-resolves.convention.yaml +26 -0
- package/conventions/atdd-bun.topology/atdd-bun.topology.e2e-location.convention.yaml +38 -0
- package/conventions/atdd-bun.topology/atdd-bun.topology.feature-source-coverage.convention.yaml +30 -0
- package/conventions/atdd-bun.topology/atdd-bun.topology.feature-test-coverage.convention.yaml +29 -0
- package/conventions/atdd-bun.topology/atdd-bun.topology.plan-location.convention.yaml +31 -0
- package/conventions/atdd-bun.topology/atdd-bun.topology.source-location.convention.yaml +33 -0
- package/conventions/atdd-bun.topology/atdd-bun.topology.test-location.convention.yaml +34 -0
- package/detectors/atdd_topology/atdd.implementation.yaml +3 -0
- package/detectors/atdd_topology/detect.mjs +14 -3
- package/detectors/atdd_topology/fixtures/clean/e2e/journeys/checkout.journey.test.ts +1 -0
- package/detectors/atdd_topology/fixtures/clean/plan/orders/E001.yaml +2 -0
- package/detectors/atdd_traceability_closure/detect.mjs +31 -16
- package/detectors/planner_docs_capability/atdd.implementation.yaml +7 -0
- package/detectors/planner_plan_integrity/atdd.implementation.yaml +1 -0
- package/detectors/planner_plan_integrity/detect.mjs +4 -1
- package/detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/_interlockings/checkout.yaml +6 -0
- package/detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/_interlockings/unnamed.yaml +2 -0
- package/detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/dup-a.yaml +7 -0
- package/detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/dup-b.yaml +6 -0
- package/detectors/planner_plan_integrity/fixtures/dirty/plan/orders/E001.yaml +5 -0
- package/detectors/planner_schema_validation/fixtures/dirty/plan/_trains/missing-description.yaml +5 -0
- package/integrity.json +39 -19
- package/package.json +1 -1
- package/planner-nodes/ENFORCEMENT_SCOPE.yaml +11 -1
- package/relationships.yaml +224 -0
- package/src/agent.ts +15 -8
- package/src/docs-capability.ts +3 -0
- package/src/enforce.ts +48 -2
- package/src/hooks.ts +2 -2
- package/src/integrity.ts +13 -5
- package/src/planner-kernel.ts +2 -1
- package/templates/agents/AGENTS.block.md +5 -1
package/integrity.json
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.6.
|
|
2
|
+
"version": "0.6.2",
|
|
3
3
|
"files": {
|
|
4
4
|
"HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
|
|
5
5
|
"PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
|
|
6
|
-
"README.md": "
|
|
6
|
+
"README.md": "2d2518482d9213dc3a6b3ad815b21dee889312ea2bcf22fbc767a5cfed6dc7b3",
|
|
7
7
|
"bunfig.toml": "b9fc65eca9014c5179380259d70e76385a6a80788fa9a2df5fb3eaa5554fd2fe",
|
|
8
|
+
"conventions/atdd-bun.planner/atdd-bun.planner.acceptance-identity.convention.yaml": "81c5c773d5ee15e8d99a2c237aa9846b110b2533df45ebf407d0e3ec6ed3dd03",
|
|
9
|
+
"conventions/atdd-bun.planner/atdd-bun.planner.identity-required.convention.yaml": "e96d7c1455d0c221072d82e7f2b55da1c4f9ceb17718eb6ed96affdeef37675b",
|
|
10
|
+
"conventions/atdd-bun.planner/atdd-bun.planner.identity-unique.convention.yaml": "409ba1d035ed46bc45f3627b62fd3a6a5967d984cc4c071c41be3f3fa887c6d6",
|
|
11
|
+
"conventions/atdd-bun.planner/atdd-bun.planner.interlocking-participant-resolves.convention.yaml": "fef62f58ad2cd6b036556cfbe17d32fedcd13656eb04c0747e955af7f954ff02",
|
|
12
|
+
"conventions/atdd-bun.planner/atdd-bun.planner.parse.convention.yaml": "953ccd7de6d129f1bd17ce680a1a4819bff6d57b379aaa03323bb22cf255ae08",
|
|
13
|
+
"conventions/atdd-bun.planner/atdd-bun.planner.reference-resolves.convention.yaml": "b96927d269692c29c02fee38ecbeb999480791f8dc7486c4072a9ff30183d7d6",
|
|
14
|
+
"conventions/atdd-bun.planner/atdd-bun.planner.schema.convention.yaml": "16d0a26451758b759e6ca1f5b4f38ae1feff129688306ffcc2f70f6d3974b591",
|
|
15
|
+
"conventions/atdd-bun.planner/atdd-bun.planner.train-wagon-resolves.convention.yaml": "6410e248351e701dcba38c671c07c4d8f6d67927984ed4d18e567abd1eb1f3f0",
|
|
16
|
+
"conventions/atdd-bun.topology/atdd-bun.topology.e2e-location.convention.yaml": "3a3b991bc851b86d597636200f5cb10a0defda3920348860cdca28955ab91f4c",
|
|
17
|
+
"conventions/atdd-bun.topology/atdd-bun.topology.feature-source-coverage.convention.yaml": "2b64093f1e7f1fe0e2edcadf12e560fa80cecec19536abaa5bf09af362c6694e",
|
|
18
|
+
"conventions/atdd-bun.topology/atdd-bun.topology.feature-test-coverage.convention.yaml": "279be57399c9b79a1bd758414076e3053d67b7d4a7fcbbb950ea12d1201effeb",
|
|
19
|
+
"conventions/atdd-bun.topology/atdd-bun.topology.plan-location.convention.yaml": "82149598eb329a0f5db0feb345482a99fef65bef90d3fe34768db66406c30e24",
|
|
20
|
+
"conventions/atdd-bun.topology/atdd-bun.topology.source-location.convention.yaml": "7e7bb788eb1ff9cbe6f67dfddc78a20b3800e733ab1036b70adb67e404b77113",
|
|
21
|
+
"conventions/atdd-bun.topology/atdd-bun.topology.test-location.convention.yaml": "a1f7f772a889607d4b6c0f20e9924b88838199a3e763b6328dbe09b8f29f2e50",
|
|
8
22
|
"conventions/coder.bun/coder.bun.boundaries-http-client.convention.yaml": "b846bb6ced2c4c967466520d52b00ff1ca802dafcdbb2ec2adbd0ce19e752a28",
|
|
9
23
|
"conventions/coder.bun/coder.bun.commons-application-no-integration.convention.yaml": "f63e7f6442bcdf5cf79f58a091547a1a990d0ee7c006b797fd5187d6f8bf8712",
|
|
10
24
|
"conventions/coder.bun/coder.bun.commons-cross-feature-imports-in.convention.yaml": "2d66bd3c04186fcef67a89c2b6d81a697f8f43f978ff27284a3b8a3848097182",
|
|
@@ -124,16 +138,16 @@
|
|
|
124
138
|
"conventions/traceability/traceability.source.tested-by-present.convention.yaml": "27a2340a9dae9eee67a3fbdcea0d2f8f35f222f22dbce7f84b2a0c6ab6321018",
|
|
125
139
|
"conventions/traceability/traceability.source.tested-by-resolves.convention.yaml": "ad4516670934dc82f1ebf9dc057110313059110302f88b4747dde89dd060870b",
|
|
126
140
|
"conventions/traceability/traceability.test.binding-resolves.convention.yaml": "03c118f498b1e824d457d25c060c8f9689ccbcca49c58d8bcd158e3cd3144ee9",
|
|
127
|
-
"detectors/atdd_topology/atdd.implementation.yaml": "
|
|
128
|
-
"detectors/atdd_topology/detect.mjs": "
|
|
141
|
+
"detectors/atdd_topology/atdd.implementation.yaml": "7879f441a62691bf041b0fd8220feb12996d4a036d74d6c79d0985ee20992efb",
|
|
142
|
+
"detectors/atdd_topology/detect.mjs": "33cb5584ce334352324e4a8331f75697cc9c09f7ebc07e59c5f917ad9879f447",
|
|
129
143
|
"detectors/atdd_topology/fixtures/clean/atdd-bun.yaml": "85049c05fb624037e1e2200a117b07d6c8d089d4e81fd784ade15a860a444f59",
|
|
130
144
|
"detectors/atdd_topology/fixtures/clean/e2e/interlockings/checkout/nominal.routes.test.ts": "be769e192e8165d4d36be743e8df578c1790b900c6b89acc7d2046ba40105135",
|
|
131
|
-
"detectors/atdd_topology/fixtures/clean/e2e/journeys/checkout.journey.test.ts": "
|
|
145
|
+
"detectors/atdd_topology/fixtures/clean/e2e/journeys/checkout.journey.test.ts": "345c391888e3e8c93d0437428dc1a3358968c7871ef54d313d6e083717254bbe",
|
|
132
146
|
"detectors/atdd_topology/fixtures/clean/implementation/orders/features/place-order/domain/order.ts": "b81c0630a2c518749ed6d81741ba4e1503ffdbc64f232747518a17e8e8a46927",
|
|
133
147
|
"detectors/atdd_topology/fixtures/clean/plan/_journeys/checkout.yaml": "c61021d0bf2f7f3bfe306b18c4b34563292de02acce47326db71ac4f8d67bc8d",
|
|
134
148
|
"detectors/atdd_topology/fixtures/clean/plan/_trains/_interlockings/checkout.yaml": "99ee3ef93e3098943878c947a584d93fec1407f0a5bd85c5383129229a98d0bc",
|
|
135
149
|
"detectors/atdd_topology/fixtures/clean/plan/_trains/checkout.yaml": "d022cd946cced5961d16535f93cc875b76e2cad872625e5e040242810f8cfad1",
|
|
136
|
-
"detectors/atdd_topology/fixtures/clean/plan/orders/E001.yaml": "
|
|
150
|
+
"detectors/atdd_topology/fixtures/clean/plan/orders/E001.yaml": "97cec4c393561e40252c0f72bed3a471831006e4cf231653506c601df0ed9172",
|
|
137
151
|
"detectors/atdd_topology/fixtures/clean/plan/orders/_orders.yaml": "33f0f30b31e3fb1bedf1ba8d2071dc7cea02749b48d18196038c9d2321aaf628",
|
|
138
152
|
"detectors/atdd_topology/fixtures/clean/plan/orders/place-order.yaml": "cccbad80a40da7bbb52c475fe73f5cd6e523994b428dc133aefc37225b04a80c",
|
|
139
153
|
"detectors/atdd_topology/fixtures/clean/tests/wagons/orders/features/place-order/unit/order.test.ts": "01a9a560d3d977346e39b88a88e7112d9f47f02e8dc8d995ba5fb45f77842153",
|
|
@@ -145,7 +159,7 @@
|
|
|
145
159
|
"detectors/atdd_topology/fixtures/dirty/src/wagons/orders/features/place-order/domain/order.ts": "6fdaad52671ef25b47d93f9b4e97e12c62eb78f5b4cde727435c3ac135a1c0e5",
|
|
146
160
|
"detectors/atdd_topology/fixtures/dirty/tests/wagons/orders/features/place-order/unit/order.test.ts": "f117e6c8b034700e1f00889bea48cb1528f0003d699cfd3e279cd9164b264a38",
|
|
147
161
|
"detectors/atdd_traceability_closure/atdd.implementation.yaml": "82e56cc85539476e42fc8d58e718bc998f1eff6c325a0cd240c0284625834d68",
|
|
148
|
-
"detectors/atdd_traceability_closure/detect.mjs": "
|
|
162
|
+
"detectors/atdd_traceability_closure/detect.mjs": "b3f5afbfa4d4970f56a2a361d3392652dd4f536d5018ac0f54aa7fc97c3ead11",
|
|
149
163
|
"detectors/atdd_traceability_closure/fixtures/clean/plan/orders/E001.yaml": "0ddad928dde10c2ab690558259a6975125af540a1f30eca9aa0c615bf0ad69fb",
|
|
150
164
|
"detectors/atdd_traceability_closure/fixtures/clean/src/order.ts": "efe6db7e3f723c7b6096abdf58dec9c88b27ec8978d434a10135b3e77bfce321",
|
|
151
165
|
"detectors/atdd_traceability_closure/fixtures/clean/test/order.test.ts": "bee451198d472749616f85c1f1e97c0a06ad28255b9a6a2cc8701bd2e8d56562",
|
|
@@ -683,7 +697,7 @@
|
|
|
683
697
|
"detectors/htmx_tester_detector/fixtures/dirty/statusonly.test.ts": "a2daf2874bf68c6221374ac05619642a42d56b5d1f76e30fa3d2a1826adaf51b",
|
|
684
698
|
"detectors/htmx_tester_detector/test_header.mjs": "a22221fe9ef6d10297ec777c5d0c6e7b84010ccf41e90db30c036206790ea198",
|
|
685
699
|
"detectors/planner_docs_capability/README.md": "1e1f8ee258242c05293447fcde124543447ba375306e8a7e5f665881b038bcd1",
|
|
686
|
-
"detectors/planner_docs_capability/atdd.implementation.yaml": "
|
|
700
|
+
"detectors/planner_docs_capability/atdd.implementation.yaml": "0bc4c27c61faa15147ad8ed55a866d7830875efb6222aa463d49f1163a8b6121",
|
|
687
701
|
"detectors/planner_docs_capability/detect.mjs": "d10773fa5b2d8f65ce351804694171a6eb75e05eb4c80fa1edce4a5d56b72c5d",
|
|
688
702
|
"detectors/planner_docs_capability/fixtures/clean/docs/architecture/decisions/adr-20260906-001-shared-control-root.adoc": "6d30e5545e7b051c80605c3dbee50286f71e344ab2e71a987db945941de42523",
|
|
689
703
|
"detectors/planner_docs_capability/fixtures/clean/docs/architecture/decisions/index.adoc": "4a6cb87e249d587c85bb6389ff5fecae7a96194af8677b588eed0c791099123b",
|
|
@@ -810,10 +824,15 @@
|
|
|
810
824
|
"detectors/planner_docs_capability/fixtures/dirty_unresolved_edge/docs/index.adoc": "8aabcd7a4ecfc6ef565a604c1172364f761bef7d3fc4f2c96c07dce4e0ebe19b",
|
|
811
825
|
"detectors/planner_docs_capability/fixtures/dirty_unresolved_edge/docs/purpose/index.adoc": "e10193ea48f3c77b67ca8ed7221c53f23ce699e63f651cc340cea9ef6b44cffb",
|
|
812
826
|
"detectors/planner_docs_capability/fixtures/dirty_unresolved_edge/docs/purpose/worktrees.adoc": "de957bd8aec411c930fa9785d793c3345c82f5d7cb7f28f40f73f1c53a436440",
|
|
813
|
-
"detectors/planner_plan_integrity/atdd.implementation.yaml": "
|
|
814
|
-
"detectors/planner_plan_integrity/detect.mjs": "
|
|
827
|
+
"detectors/planner_plan_integrity/atdd.implementation.yaml": "11e6c1a12c9abe5d4c169fd3ba181e13021e4c58b951757dddd4794417db4367",
|
|
828
|
+
"detectors/planner_plan_integrity/detect.mjs": "ee21ec706c48182250dbcb6045521185b2c91cc8dbcdbbad6e417f2dbfe641b8",
|
|
815
829
|
"detectors/planner_plan_integrity/fixtures/clean/README.md": "d64b0a6ee983757a62b634bb5f64e19f4e5c1eb2e6b23dfd33cf8cb8f3ec0b4a",
|
|
830
|
+
"detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/_interlockings/checkout.yaml": "4d994aae6517b6d8fc2398ca3c37e4a87d999fc559027ae9f2bab0b036e09870",
|
|
831
|
+
"detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/_interlockings/unnamed.yaml": "1e4844007b6679c5d0555e56e088eab3a6d35d0b929261dda3033897e39eace8",
|
|
832
|
+
"detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/dup-a.yaml": "1879b918b34af2a6bb929ae5010619002ece1698bf71ecbcc0414617f88da481",
|
|
833
|
+
"detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/dup-b.yaml": "36bf938cde33a57a0316ffde20fe632b185d9f43a9429df2aa55bdaef987ebf9",
|
|
816
834
|
"detectors/planner_plan_integrity/fixtures/dirty/plan/broken.yaml": "81865b7554ea4a1eb4598de81265abe0760d9ea722945f5bfff5415bc272e95a",
|
|
835
|
+
"detectors/planner_plan_integrity/fixtures/dirty/plan/orders/E001.yaml": "cc800a145aa136328d0e28dfc46d3911e841b47cf69e2f785cacd1f54c2ed3f6",
|
|
817
836
|
"detectors/planner_schema_validation/atdd.implementation.yaml": "2daeff9f85558ad6d246ecdd3d7eb7a172dd1bde646b3cf3b64f21a162da721f",
|
|
818
837
|
"detectors/planner_schema_validation/detect.mjs": "b208e1483e9bae74d3dcff12a824e6dfd0dcb8009affa3c3f58b2a55fe7ae8bf",
|
|
819
838
|
"detectors/planner_schema_validation/fixtures/clean/contracts/_contracts.yaml": "967f4f0aac0be000cb6107dc735ca21b73a3a21870e9ada1396df445584cad41",
|
|
@@ -823,6 +842,7 @@
|
|
|
823
842
|
"detectors/planner_schema_validation/fixtures/clean/plan/orders/_orders.yaml": "77d92ee0c9abae5ef7a407d5e0ed5baabf38391d75b91d47b598777ecc6b87ec",
|
|
824
843
|
"detectors/planner_schema_validation/fixtures/dirty/contracts/_contracts.yaml": "6460d5b8c73bb5f91ae356b29935c0a377cfef2d79ad6180e8cbb8a31fe2e882",
|
|
825
844
|
"detectors/planner_schema_validation/fixtures/dirty/plan/_themes.yaml": "c3e8fb1939d83fdde56405ddfee68e266a16c4a7ed8439b6f7906d47372df473",
|
|
845
|
+
"detectors/planner_schema_validation/fixtures/dirty/plan/_trains/missing-description.yaml": "34052c4d46487751814cdb65e90beb4964493a3a64331b27e1927a5efb8949c5",
|
|
826
846
|
"detectors/planner_schema_validation/fixtures/dirty/plan/_trains/orders.yaml": "ae2fdb750fd5a78c0baddd54fada48522671ae2a9a6538c50ab41ca32a90f682",
|
|
827
847
|
"detectors/planner_static_validators/atdd.implementation.yaml": "030d9999473bd488590a608b6f60f97f2b2c484a183574581edaa262a2e24a10",
|
|
828
848
|
"detectors/planner_static_validators/detect.mjs": "efbff2e8cd3f1b7d8e3240f132e90f09cc081b9352511f3ade67ec6fe06d124b",
|
|
@@ -847,7 +867,7 @@
|
|
|
847
867
|
"lib/frontend.mjs": "d6d9b29c3d7f2d162cda03bb34b20a3224a82887b6be3db7cab375510913c9a3",
|
|
848
868
|
"lib/imports.mjs": "3db4979269488fa26f6e7aefded6ece9c646be6587a10e249821a5a208109962",
|
|
849
869
|
"lib/scan.mjs": "f232a6178f9b6d3d63ba4056709664b1700d4292c49bad1dec18e9560a7c0461",
|
|
850
|
-
"planner-nodes/ENFORCEMENT_SCOPE.yaml": "
|
|
870
|
+
"planner-nodes/ENFORCEMENT_SCOPE.yaml": "d8ec7039c219857f3d77af5d4d84612b533b69c1a5fcef935fbf46470adafed4",
|
|
851
871
|
"planner-nodes/nodes/planner.acceptance.abstract-fields-required.convention.yaml": "bd565693f0ae861f0561ad2f7a91a8c55f9a209c3acd59c5750cfcffd96a85d8",
|
|
852
872
|
"planner-nodes/nodes/planner.acceptance.authoring-guidelines.convention.yaml": "16348dceed5868041b832bde766e9c8d4a950d4032f1f3066b357644081a3b17",
|
|
853
873
|
"planner-nodes/nodes/planner.acceptance.boundary-kind-vocabulary.convention.yaml": "c5645225399568c2d4fe632442f9c597b2c59e4f4239880df7db2cbdc0353c9c",
|
|
@@ -1065,24 +1085,24 @@
|
|
|
1065
1085
|
"planner-schemas/train.schema.json": "d6fbac9067c1bffeb84eb79a71228441055af6e6e7ba141dd4e0bcb079345ec0",
|
|
1066
1086
|
"planner-schemas/wagon.schema.json": "57b98f1ade998d745538eb3d72fb3014a50042006870f90bce47028eea336eb4",
|
|
1067
1087
|
"planner-schemas/wmbt.schema.json": "7a3a5693bfcf01340667c2be4a3c48e74f34b1af7e8dc13372c0de06b7f4fe12",
|
|
1068
|
-
"relationships.yaml": "
|
|
1069
|
-
"src/agent.ts": "
|
|
1088
|
+
"relationships.yaml": "3a3b4987dacbc6ae566e607183f60503c934bf8c60925943566e6887726d8bbf",
|
|
1089
|
+
"src/agent.ts": "dfbd20cbd5958cc59aca94bd06d21ff5062f71820226230fac1c1d3ff34f29d5",
|
|
1070
1090
|
"src/ci.ts": "899125ec76d14f7d0126a5db2249913e373e3a325cbc98d7f053e1351bdf47af",
|
|
1071
1091
|
"src/cli.ts": "bb2047cee60f48bfe3eaee4c1b3dcd3538a0e1edc170cf58e87686117bea07a7",
|
|
1072
|
-
"src/docs-capability.ts": "
|
|
1073
|
-
"src/enforce.ts": "
|
|
1074
|
-
"src/hooks.ts": "
|
|
1092
|
+
"src/docs-capability.ts": "0feae94b7697dd53d8a457b24765e81fcf804c774f0cc4b6035cb3da822e486a",
|
|
1093
|
+
"src/enforce.ts": "1c884830937eb6a097a1dad6a979419b60472c39d928833e4414a020b82b1693",
|
|
1094
|
+
"src/hooks.ts": "adbb72f7a83f53d596fd217f01f4ce32c5a59a23ab81993fed226fb96fd1cbd1",
|
|
1075
1095
|
"src/index.ts": "2743b7969526d0aaf21179048e9896141e97d89c45c4a503b795f41fed57f6cc",
|
|
1076
|
-
"src/integrity.ts": "
|
|
1096
|
+
"src/integrity.ts": "009b5f23ec3240cdc2cb133f942995e48f494edaa0259053bca15283134be3ea",
|
|
1077
1097
|
"src/journey-docs.ts": "994d229376244c84212cff951032823068a76565dd2fd427b9d44e341bd14853",
|
|
1078
|
-
"src/planner-kernel.ts": "
|
|
1098
|
+
"src/planner-kernel.ts": "5d3f5305fb59becb2cc97d8a03aca6b104f385805d91cc0410856e289dd6fbb1",
|
|
1079
1099
|
"src/planner-schema-validator.ts": "ce529c0936171075e93c7539b3655fe895a98dd2973ebda5775958e6ebc316d9",
|
|
1080
1100
|
"src/planner-validators.ts": "99adeb65f579af923c11f21a8f55cfb9993b007fd8df59c5f764696b55379cfc",
|
|
1081
1101
|
"src/register.ts": "6a2f1c6b8df0dc9c8f45df8ec9fac2be29044e048b08c8ffe423410e94dadedd",
|
|
1082
1102
|
"src/release.ts": "7b0fe444ee81f476f53b9c21b6c347ef588ee616c60211419cf984132be768ba",
|
|
1083
1103
|
"src/setup.ts": "0a6f1a9aeff31dcf17f6bd036168b909c1841c11bbbb452105d8debf64a6bc78",
|
|
1084
1104
|
"src/topology.ts": "ff86fc7923793c727196e6f8459db4f071fcaa6844268c2e3b232bb18801c43d",
|
|
1085
|
-
"templates/agents/AGENTS.block.md": "
|
|
1105
|
+
"templates/agents/AGENTS.block.md": "c45d831e57976b874d2a6dc6e5e929b6e9ea3f928fe8c6683c2bab71676817c7",
|
|
1086
1106
|
"templates/agents/atdd-bun.integrity.test.ts": "dec6f6e5f65a08d9512fc703b367c163f8c9da1f6aa7fd466835c1ec30348a41",
|
|
1087
1107
|
"templates/agents/atdd/SKILL.md": "b621abe22851a75b30fc0e6c339a2c0a3ed6bbf78b8c17000a4ce301e05df96d",
|
|
1088
1108
|
"templates/github/atdd-bun-release.yml": "d9547e9e6ef3ae010d53d55314f6a54567f88bd900f7e50d994cd308601d2684",
|
package/package.json
CHANGED
|
@@ -117,8 +117,18 @@ canonical_bun_enforcement:
|
|
|
117
117
|
coverage: partial
|
|
118
118
|
predicates:
|
|
119
119
|
- duplicate feature references are reported
|
|
120
|
+
- a wagon with WMBTs declares a feature decomposition (atdd_topology)
|
|
121
|
+
- every feature a wagon lists is declared by a feature document (atdd_topology)
|
|
122
|
+
- every WMBT belongs to exactly one feature (atdd_topology)
|
|
120
123
|
unported:
|
|
121
124
|
- complete feature-manifest coverage
|
|
125
|
+
- rule_id: planner.feature.wagon-link
|
|
126
|
+
disposition: strict
|
|
127
|
+
origin: bun-custom-validator
|
|
128
|
+
coverage: partial
|
|
129
|
+
predicates:
|
|
130
|
+
- every feature is listed by exactly one wagon and belongs to it
|
|
131
|
+
- source and test URNs name a declared feature
|
|
122
132
|
- rule_id: planner.wagon.telemetry-filesystem
|
|
123
133
|
disposition: strict
|
|
124
134
|
origin: bun-custom-validator
|
|
@@ -130,7 +140,7 @@ canonical_bun_enforcement:
|
|
|
130
140
|
|
|
131
141
|
reference_only:
|
|
132
142
|
selection: "every canonical planner node not named in canonical_bun_enforcement"
|
|
133
|
-
count:
|
|
143
|
+
count: 180
|
|
134
144
|
|
|
135
145
|
package_integrity_guard:
|
|
136
146
|
implementation_id: planner.plan-integrity.bun
|
package/relationships.yaml
CHANGED
|
@@ -8,6 +8,20 @@
|
|
|
8
8
|
schema_version: 1.0.0
|
|
9
9
|
graph_id: "@afokapu/atdd-bun"
|
|
10
10
|
nodes:
|
|
11
|
+
- atdd-bun.planner.acceptance-identity
|
|
12
|
+
- atdd-bun.planner.identity-required
|
|
13
|
+
- atdd-bun.planner.identity-unique
|
|
14
|
+
- atdd-bun.planner.interlocking-participant-resolves
|
|
15
|
+
- atdd-bun.planner.parse
|
|
16
|
+
- atdd-bun.planner.reference-resolves
|
|
17
|
+
- atdd-bun.planner.schema
|
|
18
|
+
- atdd-bun.planner.train-wagon-resolves
|
|
19
|
+
- atdd-bun.topology.e2e-location
|
|
20
|
+
- atdd-bun.topology.feature-source-coverage
|
|
21
|
+
- atdd-bun.topology.feature-test-coverage
|
|
22
|
+
- atdd-bun.topology.plan-location
|
|
23
|
+
- atdd-bun.topology.source-location
|
|
24
|
+
- atdd-bun.topology.test-location
|
|
11
25
|
- coder.bun.boundaries-http-client
|
|
12
26
|
- coder.bun.commons-application-no-integration
|
|
13
27
|
- coder.bun.commons-cross-feature-imports-in
|
|
@@ -4788,3 +4802,213 @@ edges:
|
|
|
4788
4802
|
target_ref: tester.htmx.a11y-harness
|
|
4789
4803
|
type: runs_alongside
|
|
4790
4804
|
origin: "@afokapu/atdd-bun"
|
|
4805
|
+
- confidence: 1
|
|
4806
|
+
constraint: mandatory
|
|
4807
|
+
control: internal
|
|
4808
|
+
foundation: finish_to_start
|
|
4809
|
+
reason: an artifact is only identified once it parses
|
|
4810
|
+
source_ref: atdd-bun.planner.parse
|
|
4811
|
+
strength: critical
|
|
4812
|
+
target_ref: atdd-bun.planner.identity-unique
|
|
4813
|
+
type: enables
|
|
4814
|
+
origin: "@afokapu/atdd-bun"
|
|
4815
|
+
- confidence: 1
|
|
4816
|
+
constraint: mandatory
|
|
4817
|
+
control: internal
|
|
4818
|
+
foundation: finish_to_start
|
|
4819
|
+
reason: a reference resolves to exactly one target only when identities are unique
|
|
4820
|
+
source_ref: atdd-bun.planner.identity-unique
|
|
4821
|
+
strength: critical
|
|
4822
|
+
target_ref: atdd-bun.planner.reference-resolves
|
|
4823
|
+
type: enables
|
|
4824
|
+
origin: "@afokapu/atdd-bun"
|
|
4825
|
+
- confidence: 1
|
|
4826
|
+
constraint: mandatory
|
|
4827
|
+
control: internal
|
|
4828
|
+
foundation: start_to_start
|
|
4829
|
+
reason: wagon membership of a train is the train-specific case of reference resolution
|
|
4830
|
+
source_ref: atdd-bun.planner.train-wagon-resolves
|
|
4831
|
+
strength: important
|
|
4832
|
+
target_ref: atdd-bun.planner.reference-resolves
|
|
4833
|
+
type: runs_alongside
|
|
4834
|
+
origin: "@afokapu/atdd-bun"
|
|
4835
|
+
- confidence: 1
|
|
4836
|
+
constraint: mandatory
|
|
4837
|
+
control: internal
|
|
4838
|
+
foundation: start_to_start
|
|
4839
|
+
reason: interlocking participants are the interlocking-specific case of reference resolution
|
|
4840
|
+
source_ref: atdd-bun.planner.interlocking-participant-resolves
|
|
4841
|
+
strength: important
|
|
4842
|
+
target_ref: atdd-bun.planner.reference-resolves
|
|
4843
|
+
type: runs_alongside
|
|
4844
|
+
origin: "@afokapu/atdd-bun"
|
|
4845
|
+
- confidence: 1
|
|
4846
|
+
constraint: mandatory
|
|
4847
|
+
control: internal
|
|
4848
|
+
foundation: finish_to_start
|
|
4849
|
+
reason: "a test can bind an acceptance only through the acc: identity the WMBT gives it"
|
|
4850
|
+
source_ref: atdd-bun.planner.acceptance-identity
|
|
4851
|
+
strength: critical
|
|
4852
|
+
target_ref: traceability.test.binding-resolves
|
|
4853
|
+
type: enables
|
|
4854
|
+
origin: "@afokapu/atdd-bun"
|
|
4855
|
+
- confidence: 1
|
|
4856
|
+
constraint: mandatory
|
|
4857
|
+
control: internal
|
|
4858
|
+
foundation: finish_to_start
|
|
4859
|
+
reason: a document is validated against its schema only after it parses
|
|
4860
|
+
source_ref: atdd-bun.planner.schema
|
|
4861
|
+
strength: critical
|
|
4862
|
+
target_ref: atdd-bun.planner.parse
|
|
4863
|
+
type: requires
|
|
4864
|
+
origin: "@afokapu/atdd-bun"
|
|
4865
|
+
- confidence: 1
|
|
4866
|
+
constraint: mandatory
|
|
4867
|
+
control: internal
|
|
4868
|
+
foundation: start_to_start
|
|
4869
|
+
reason: the schema catch-all beside the canonical rules its violations are routed to
|
|
4870
|
+
source_ref: atdd-bun.planner.schema
|
|
4871
|
+
strength: important
|
|
4872
|
+
target_ref: planner.train.naming
|
|
4873
|
+
type: runs_alongside
|
|
4874
|
+
origin: "@afokapu/atdd-bun"
|
|
4875
|
+
- confidence: 1
|
|
4876
|
+
constraint: mandatory
|
|
4877
|
+
control: internal
|
|
4878
|
+
foundation: finish_to_start
|
|
4879
|
+
reason: "features are placed by path once the wagon's feature decomposition is declared"
|
|
4880
|
+
source_ref: atdd-bun.topology.plan-location
|
|
4881
|
+
strength: critical
|
|
4882
|
+
target_ref: planner.wagon.features
|
|
4883
|
+
type: requires
|
|
4884
|
+
origin: "@afokapu/atdd-bun"
|
|
4885
|
+
- confidence: 1
|
|
4886
|
+
constraint: mandatory
|
|
4887
|
+
control: internal
|
|
4888
|
+
foundation: start_to_start
|
|
4889
|
+
reason: "where a feature lives and which wagon owns it: the same ownership, by path and by link"
|
|
4890
|
+
source_ref: atdd-bun.topology.plan-location
|
|
4891
|
+
strength: important
|
|
4892
|
+
target_ref: planner.feature.wagon-link
|
|
4893
|
+
type: runs_alongside
|
|
4894
|
+
origin: "@afokapu/atdd-bun"
|
|
4895
|
+
- confidence: 1
|
|
4896
|
+
constraint: mandatory
|
|
4897
|
+
control: internal
|
|
4898
|
+
foundation: finish_to_start
|
|
4899
|
+
reason: source is placed beneath a feature the plan declares at its canonical path
|
|
4900
|
+
source_ref: atdd-bun.topology.source-location
|
|
4901
|
+
strength: critical
|
|
4902
|
+
target_ref: atdd-bun.topology.plan-location
|
|
4903
|
+
type: requires
|
|
4904
|
+
origin: "@afokapu/atdd-bun"
|
|
4905
|
+
- confidence: 1
|
|
4906
|
+
constraint: mandatory
|
|
4907
|
+
control: internal
|
|
4908
|
+
foundation: start_to_start
|
|
4909
|
+
reason: "the same component URN, read for its grammar and for where the file must live"
|
|
4910
|
+
source_ref: atdd-bun.topology.source-location
|
|
4911
|
+
strength: important
|
|
4912
|
+
target_ref: coder.bun.green-urn-pattern
|
|
4913
|
+
type: runs_alongside
|
|
4914
|
+
origin: "@afokapu/atdd-bun"
|
|
4915
|
+
- confidence: 1
|
|
4916
|
+
constraint: mandatory
|
|
4917
|
+
control: internal
|
|
4918
|
+
foundation: finish_to_start
|
|
4919
|
+
reason: tests are placed beneath a feature the plan declares
|
|
4920
|
+
source_ref: atdd-bun.topology.test-location
|
|
4921
|
+
strength: critical
|
|
4922
|
+
target_ref: atdd-bun.topology.plan-location
|
|
4923
|
+
type: requires
|
|
4924
|
+
origin: "@afokapu/atdd-bun"
|
|
4925
|
+
- confidence: 1
|
|
4926
|
+
constraint: mandatory
|
|
4927
|
+
control: internal
|
|
4928
|
+
foundation: start_to_start
|
|
4929
|
+
reason: "the acceptance a test binds, read for presence and for whether a WMBT declares it"
|
|
4930
|
+
source_ref: atdd-bun.topology.test-location
|
|
4931
|
+
strength: important
|
|
4932
|
+
target_ref: tester.bun.acceptance-binding-declared
|
|
4933
|
+
type: runs_alongside
|
|
4934
|
+
origin: "@afokapu/atdd-bun"
|
|
4935
|
+
- confidence: 1
|
|
4936
|
+
constraint: mandatory
|
|
4937
|
+
control: internal
|
|
4938
|
+
foundation: finish_to_start
|
|
4939
|
+
reason: journey E2E paths are derived from the journeys the plan declares
|
|
4940
|
+
source_ref: atdd-bun.topology.e2e-location
|
|
4941
|
+
strength: critical
|
|
4942
|
+
target_ref: planner.journey.definition
|
|
4943
|
+
type: requires
|
|
4944
|
+
origin: "@afokapu/atdd-bun"
|
|
4945
|
+
- confidence: 1
|
|
4946
|
+
constraint: mandatory
|
|
4947
|
+
control: internal
|
|
4948
|
+
foundation: start_to_start
|
|
4949
|
+
reason: "every route has an end-to-end proof: at its canonical path, and exercising the production runner"
|
|
4950
|
+
source_ref: atdd-bun.topology.e2e-location
|
|
4951
|
+
strength: important
|
|
4952
|
+
target_ref: tester.bun.interlocking-route-coverage
|
|
4953
|
+
type: runs_alongside
|
|
4954
|
+
origin: "@afokapu/atdd-bun"
|
|
4955
|
+
- confidence: 1
|
|
4956
|
+
constraint: mandatory
|
|
4957
|
+
control: internal
|
|
4958
|
+
foundation: start_to_start
|
|
4959
|
+
reason: "on a frontend journey a browser spec is the route's end-to-end proof"
|
|
4960
|
+
source_ref: atdd-bun.topology.e2e-location
|
|
4961
|
+
strength: important
|
|
4962
|
+
target_ref: tester.htmx.train-e2e-coverage
|
|
4963
|
+
type: runs_alongside
|
|
4964
|
+
origin: "@afokapu/atdd-bun"
|
|
4965
|
+
- confidence: 1
|
|
4966
|
+
constraint: mandatory
|
|
4967
|
+
control: internal
|
|
4968
|
+
foundation: finish_to_start
|
|
4969
|
+
reason: coverage counts only source placed beneath its feature
|
|
4970
|
+
source_ref: atdd-bun.topology.feature-source-coverage
|
|
4971
|
+
strength: critical
|
|
4972
|
+
target_ref: atdd-bun.topology.source-location
|
|
4973
|
+
type: requires
|
|
4974
|
+
origin: "@afokapu/atdd-bun"
|
|
4975
|
+
- confidence: 1
|
|
4976
|
+
constraint: mandatory
|
|
4977
|
+
control: internal
|
|
4978
|
+
foundation: finish_to_start
|
|
4979
|
+
reason: coverage counts only tests placed beneath their feature and bound to its acceptances
|
|
4980
|
+
source_ref: atdd-bun.topology.feature-test-coverage
|
|
4981
|
+
strength: critical
|
|
4982
|
+
target_ref: atdd-bun.topology.test-location
|
|
4983
|
+
type: requires
|
|
4984
|
+
origin: "@afokapu/atdd-bun"
|
|
4985
|
+
- confidence: 1
|
|
4986
|
+
constraint: mandatory
|
|
4987
|
+
control: internal
|
|
4988
|
+
foundation: start_to_start
|
|
4989
|
+
reason: a feature with WMBTs is implemented and tested
|
|
4990
|
+
source_ref: atdd-bun.topology.feature-test-coverage
|
|
4991
|
+
strength: important
|
|
4992
|
+
target_ref: atdd-bun.topology.feature-source-coverage
|
|
4993
|
+
type: runs_alongside
|
|
4994
|
+
origin: "@afokapu/atdd-bun"
|
|
4995
|
+
- confidence: 1
|
|
4996
|
+
constraint: mandatory
|
|
4997
|
+
control: internal
|
|
4998
|
+
foundation: finish_to_start
|
|
4999
|
+
reason: identities can only be compared once every artifact carries one
|
|
5000
|
+
source_ref: atdd-bun.planner.identity-required
|
|
5001
|
+
strength: critical
|
|
5002
|
+
target_ref: atdd-bun.planner.identity-unique
|
|
5003
|
+
type: enables
|
|
5004
|
+
origin: "@afokapu/atdd-bun"
|
|
5005
|
+
- confidence: 1
|
|
5006
|
+
constraint: mandatory
|
|
5007
|
+
control: internal
|
|
5008
|
+
foundation: finish_to_start
|
|
5009
|
+
reason: an artifact is read for its identity only once it parses
|
|
5010
|
+
source_ref: atdd-bun.planner.parse
|
|
5011
|
+
strength: critical
|
|
5012
|
+
target_ref: atdd-bun.planner.identity-required
|
|
5013
|
+
type: enables
|
|
5014
|
+
origin: "@afokapu/atdd-bun"
|
package/src/agent.ts
CHANGED
|
@@ -7,10 +7,14 @@ const version = (await Bun.file(join(root, "package.json")).json() as { version:
|
|
|
7
7
|
const render = async (path: string) => (await readFile(join(root, "templates/agents", path), "utf8")).replace("{{VERSION}}", version);
|
|
8
8
|
// .agents/skills is the vendor-neutral Agent Skills path (Codex, Copilot, Cursor, Gemini CLI, …); .claude/skills is Claude Code's.
|
|
9
9
|
const skillPaths = [".agents/skills/atdd/SKILL.md", ".claude/skills/atdd/SKILL.md"];
|
|
10
|
+
// AGENTS.md is read by Codex, Cursor and most agents; CLAUDE.md by Claude Code. Both carry the same block.
|
|
11
|
+
export const instructionPaths = ["AGENTS.md", "CLAUDE.md"];
|
|
10
12
|
const block = /<!-- atdd-bun:start[\s\S]*?<!-- atdd-bun:end -->\n?/;
|
|
13
|
+
/** `current` with the managed block replaced, or appended after at least one blank line; `current` itself is kept. */
|
|
14
|
+
const withBlock = (current: string, managed: string) => block.test(current) ? current.replace(block, managed) : `${current}${current ? "\n\n".slice(current.match(/\n{0,2}$/)![0].length) : ""}${managed}`;
|
|
11
15
|
|
|
12
|
-
/** Write the ATDD skill for every agent and a managed pointer block in AGENTS.md. Existing files
|
|
13
|
-
* existing block are kept unless `replace`; the rest of
|
|
16
|
+
/** Write the ATDD skill for every agent and a managed pointer block in AGENTS.md and CLAUDE.md. Existing files
|
|
17
|
+
* and an existing block are kept unless `replace`; the rest of each instruction file is never touched. */
|
|
14
18
|
export async function agentInit(repo = process.cwd(), replace = false) {
|
|
15
19
|
const written: string[] = [], kept: string[] = [];
|
|
16
20
|
for (const path of skillPaths) {
|
|
@@ -18,14 +22,17 @@ export async function agentInit(repo = process.cwd(), replace = false) {
|
|
|
18
22
|
if (existsSync(output) && !replace) { kept.push(output); continue; }
|
|
19
23
|
await mkdir(dirname(output), { recursive: true }); await writeFile(output, await render("atdd/SKILL.md")); written.push(output);
|
|
20
24
|
}
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
const managed = await render("AGENTS.block.md");
|
|
26
|
+
for (const path of instructionPaths) {
|
|
27
|
+
const file = join(repo, path), current = existsSync(file) ? await readFile(file, "utf8") : "";
|
|
28
|
+
if (block.test(current) && !replace) { kept.push(file); continue; }
|
|
29
|
+
await writeFile(file, withBlock(current, managed)); written.push(file);
|
|
30
|
+
}
|
|
24
31
|
if (!written.length) return { ok: false, message: `${kept.join(", ")} exist; use --replace` };
|
|
25
32
|
return { ok: true, message: written.join("\n") };
|
|
26
33
|
}
|
|
27
34
|
export async function agentStatus(repo = process.cwd()) {
|
|
28
|
-
const
|
|
29
|
-
if (!existsSync(agents) || !block.test(await readFile(agents, "utf8"))) missing.push(`${agents} (atdd-bun block)`);
|
|
30
|
-
return { ok: !missing.length, message: missing.length ? `missing: ${missing.join(", ")}` : [...skillPaths,
|
|
35
|
+
const missing = skillPaths.map(path => join(repo, path)).filter(path => !existsSync(path));
|
|
36
|
+
for (const path of instructionPaths) { const agents = join(repo, path); if (!existsSync(agents) || !block.test(await readFile(agents, "utf8"))) missing.push(`${agents} (atdd-bun block)`); }
|
|
37
|
+
return { ok: !missing.length, message: missing.length ? `missing: ${missing.join(", ")}` : [...skillPaths, ...instructionPaths].map(path => join(repo, path)).join("\n") };
|
|
31
38
|
}
|
package/src/docs-capability.ts
CHANGED
|
@@ -155,6 +155,9 @@ async function checkDocumentationInner(input: { root: string; declaration: Docum
|
|
|
155
155
|
if (input.declaration) { const declarationFindings = declarationViolations(input.declaration, input.changeSet ?? undefined); findings.push(...declarationFindings); definite ||= declarationFindings.length > 0; checked.push("<declaration>"); if (input.declaration.impact === "change") for (const artifact of input.declaration.artifacts ?? []) if (["create", "modify"].includes(artifact.action ?? "") && artifact.path && !existsSync(join(input.root, artifact.path))) { findings.push(violation("planner.docs.artifact-path-shape", artifact.path, 1, `declared artifact ${JSON.stringify(artifact.path)} (action: ${artifact.action}) is not in the tree, so it was never examined; a declared document that was never written has not discharged the obligation.`)); definite = true; } } else findings.push({ rule_id: null, where: "<declaration>", message: "core supplied no documentation declaration, so no declaration-dependent rule could be evaluated. This is COULD_NOT_CHECK and it BLOCKS." });
|
|
156
156
|
if (input.declaration && input.changeSet === null) findings.push({ rule_id: null, where: "<change_set>", message: "core supplied no change set, so whether this diff touches docs/ without declaring it could not be established. This is COULD_NOT_CHECK and it BLOCKS." });
|
|
157
157
|
const rendered = await (input.render ?? (() => renderDocumentation(input.root)))(); findings.push(...rendered.findings); definite ||= rendered.findings.length > 0; if (rendered.couldNotCheck) findings.push({ rule_id: "planner.docs.reference-integrity", file: "docs/", line: 1, col: 1, evidence: rendered.couldNotCheck, source_line: "" }); else checked.push("<render:asciidoctor>");
|
|
158
|
+
// Fail closed on any rule id this capability does not declare, however it was built (a renderer can return anything).
|
|
159
|
+
const undeclared = [...new Set(findings.map(f => f.rule_id).filter((id): id is string => id !== null && !(DOC_RULE_IDS as readonly string[]).includes(id)))];
|
|
160
|
+
if (undeclared.length) throw new Error(`documentation capability produced undeclared rule id(s): ${undeclared.join(", ")}. Declare them in DOC_RULE_IDS and the manifest, each with a convention.`);
|
|
158
161
|
return { verdict: definite ? "FAIL" : input.declaration === null || input.changeSet === null || Boolean(rendered.couldNotCheck) ? "COULD_NOT_CHECK" : "PASS", findings, checked };
|
|
159
162
|
}
|
|
160
163
|
|
package/src/enforce.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
2
3
|
import { tmpdir } from "node:os";
|
|
3
4
|
import { join, resolve } from "node:path";
|
|
@@ -37,8 +38,27 @@ const profiles: Record<Exclude<Profile, "all">, string[]> = {
|
|
|
37
38
|
design: ["bun_design_system_detector", "bun_responsive_detector"],
|
|
38
39
|
};
|
|
39
40
|
|
|
41
|
+
type ConcreteProfile = Exclude<Profile, "all">;
|
|
42
|
+
/** Every profile but `all`. */
|
|
43
|
+
export const concreteProfiles = Object.keys(profiles) as ConcreteProfile[];
|
|
40
44
|
/** Profile names accepted by the CLI and public integrations. */
|
|
41
|
-
export const profileNames = [...
|
|
45
|
+
export const profileNames = [...concreteProfiles, "all"] as Profile[];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The profiles the operator has activated: `profiles:` in atdd-bun.yaml, or every profile when absent. A legacy
|
|
49
|
+
* repository can adopt enforcement gradually by listing only what it is ready for. `all`, the hooks and the
|
|
50
|
+
* generated CI run exactly these; naming a profile explicitly still runs it. An unknown name or an empty list is
|
|
51
|
+
* a configuration error, never a silent "run nothing".
|
|
52
|
+
*/
|
|
53
|
+
export async function enabledProfiles(root = process.cwd()): Promise<ConcreteProfile[]> {
|
|
54
|
+
const file = join(resolve(root), "atdd-bun.yaml");
|
|
55
|
+
const listed = existsSync(file) ? (Bun.YAML.parse(await readFile(file, "utf8")) as { profiles?: unknown } | null)?.profiles : undefined;
|
|
56
|
+
if (listed === undefined) return concreteProfiles;
|
|
57
|
+
if (!Array.isArray(listed) || !listed.length) throw new Error("atdd-bun.yaml profiles must be a non-empty list of profile names");
|
|
58
|
+
const unknown = listed.filter(name => !concreteProfiles.includes(name));
|
|
59
|
+
if (unknown.length) throw new Error(`atdd-bun.yaml profiles lists unknown profile(s): ${unknown.join(", ")}; known: ${concreteProfiles.join(", ")}`);
|
|
60
|
+
return [...new Set(listed as ConcreteProfile[])];
|
|
61
|
+
}
|
|
42
62
|
|
|
43
63
|
const packageRoot = resolve(import.meta.dir, "..");
|
|
44
64
|
const detectorRoot = join(packageRoot, "detectors");
|
|
@@ -57,6 +77,26 @@ export function implementationsFor(requested: Profile[] = ["all"]): string[] {
|
|
|
57
77
|
return [...selected].sort();
|
|
58
78
|
}
|
|
59
79
|
|
|
80
|
+
/** The rule ids a detector's manifest declares it emits (emits_rule_ids and api_emits_rule_ids). */
|
|
81
|
+
export async function declaredRuleIds(implementation: string): Promise<Set<string>> {
|
|
82
|
+
const ids = new Set<string>();
|
|
83
|
+
let inList = false;
|
|
84
|
+
for (const line of (await readFile(join(detectorRoot, implementation, "atdd.implementation.yaml"), "utf8")).split("\n")) {
|
|
85
|
+
if (line === "emits_rule_ids:" || line === "api_emits_rule_ids:") { inList = true; continue; }
|
|
86
|
+
if (/^[A-Za-z_][\w-]*:/.test(line)) { inList = false; continue; }
|
|
87
|
+
const match = inList && line.match(/^\s*-\s+([^#\s]+)/);
|
|
88
|
+
if (match) ids.add(match[1]);
|
|
89
|
+
}
|
|
90
|
+
return ids;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Rule ids a detector actually emitted without declaring them. Checked at run time, so an id built at run
|
|
94
|
+
* time (a template string, a concatenation, a value read from YAML) cannot escape the per-rule checks. */
|
|
95
|
+
export async function undeclaredEmissions(implementation: string, violations: Pick<Violation, "rule_id">[]): Promise<string[]> {
|
|
96
|
+
const declared = await declaredRuleIds(implementation);
|
|
97
|
+
return [...new Set(violations.map(v => v.rule_id).filter(id => !declared.has(id)))].sort();
|
|
98
|
+
}
|
|
99
|
+
|
|
60
100
|
export async function runImplementation(
|
|
61
101
|
implementation: string,
|
|
62
102
|
config: Required<Pick<EnforcementConfig, "scanRoots" | "excludes">>,
|
|
@@ -88,6 +128,8 @@ export async function runImplementation(
|
|
|
88
128
|
if (!raw || !Array.isArray(raw.violations)) {
|
|
89
129
|
throw new Error(`${implementation} emitted no structured violation report`);
|
|
90
130
|
}
|
|
131
|
+
const undeclared = await undeclaredEmissions(implementation, raw.violations as Violation[]);
|
|
132
|
+
if (undeclared.length) throw new Error(`${implementation} emitted rule id(s) its manifest does not declare: ${undeclared.join(", ")}. Declare them in emits_rule_ids, each with a convention.`);
|
|
91
133
|
return raw.violations as Violation[];
|
|
92
134
|
} finally {
|
|
93
135
|
await rm(scratch, { recursive: true, force: true });
|
|
@@ -98,8 +140,12 @@ export async function enforce(config: EnforcementConfig = {}): Promise<Violation
|
|
|
98
140
|
const root = resolve(config.root ?? process.cwd());
|
|
99
141
|
const scanRoots = (config.scanRoots?.length ? config.scanRoots : [root]).map((path) => resolve(root, path));
|
|
100
142
|
const excludes = ["node_modules", ".git", ".atdd", ...(config.excludes ?? [])];
|
|
143
|
+
// `all` means every profile the operator activated; explicitly named profiles run as asked. The operator's list
|
|
144
|
+
// is validated on every run, so a misspelled atdd-bun.yaml fails whichever profile is requested.
|
|
145
|
+
const requested = config.profiles ?? ["all"], enabled = await enabledProfiles(root);
|
|
146
|
+
const selected = requested.includes("all") ? [...new Set([...requested.filter(p => p !== "all"), ...enabled])] : requested;
|
|
101
147
|
const results = await Promise.all(
|
|
102
|
-
implementationsFor(
|
|
148
|
+
implementationsFor(selected).map((implementation) => runImplementation(implementation, { scanRoots, excludes })),
|
|
103
149
|
);
|
|
104
150
|
return results.flat().sort((left, right) =>
|
|
105
151
|
left.rule_id.localeCompare(right.rule_id) || left.file.localeCompare(right.file) || left.line - right.line,
|
package/src/hooks.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { chmod, mkdir, readdir, readFile, realpath, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
-
import { enforce } from "./enforce";
|
|
4
|
+
import { enabledProfiles, enforce } from "./enforce";
|
|
5
5
|
import { topologyFor } from "./topology";
|
|
6
6
|
|
|
7
7
|
export type WorktreePolicy = { enabled: boolean; root: string; primary_directory: string; primary_branch: string; require_linked_worktree: boolean };
|
|
@@ -19,7 +19,7 @@ const isRegistry = (cfg: HookPolicy, path: string) => cfg.registry_paths.some(pa
|
|
|
19
19
|
const stagedStats = async (root: string) => (await git(root, ["diff", "--cached", "--numstat", "--no-renames"])).out.split("\n").filter(Boolean).map(row => { const [added, removed, ...path] = row.split("\t"); return { added: Number(added) || 0, removed: Number(removed) || 0, path: path.join("\t") }; });
|
|
20
20
|
|
|
21
21
|
export async function policy(root: string): Promise<HookPolicy> { const file = join(root, "atdd-bun.yaml"); if (!existsSync(file)) return defaultHookPolicy; const data = Bun.YAML.parse(await readFile(file, "utf8")) as Record<string, unknown>; const configuredWorktrees = data?.worktrees && typeof data.worktrees === "object" && !Array.isArray(data.worktrees) ? data.worktrees as Record<string, unknown> : {}; return { ...defaultHookPolicy, ...Object.fromEntries(Object.entries(data ?? {}).filter(([key, value]) => key in defaultHookPolicy && key !== "worktrees" && typeof value === typeof (defaultHookPolicy as any)[key])), protected_branches: strings(data?.protected_branches) ?? defaultHookPolicy.protected_branches, registry_paths: strings(data?.registry_paths) ?? defaultHookPolicy.registry_paths, worktrees: { ...defaultWorktreePolicy, ...Object.fromEntries(Object.entries(configuredWorktrees).filter(([key, value]) => key in defaultWorktreePolicy && typeof value === typeof (defaultWorktreePolicy as any)[key])) } }; }
|
|
22
|
-
async function validation(root: string, changed: string[], full: boolean, registryChanged = false) { const profiles: any[] = [], topology = await topologyFor(root); if (registryChanged || changed.some(path => path.startsWith(`${topology.planRoot}/`)) || changed.includes("atdd-bun.yaml")) profiles.push("planner", "traceability"); if (changed.some(path => /\.(?:[cm]?[jt]sx?|html)$/.test(path))) profiles.push("coder", "tester"); if (changed.includes("atdd-bun.yaml") || changed.some(path => /\.ya?ml$/.test(path))) profiles.push("topology"); if (!profiles.length) return { ok: true, message: "no affected area" }; try { const
|
|
22
|
+
async function validation(root: string, changed: string[], full: boolean, registryChanged = false) { const profiles: any[] = [], topology = await topologyFor(root); if (registryChanged || changed.some(path => path.startsWith(`${topology.planRoot}/`)) || changed.includes("atdd-bun.yaml")) profiles.push("planner", "traceability"); if (changed.some(path => /\.(?:[cm]?[jt]sx?|html)$/.test(path))) profiles.push("coder", "tester"); if (changed.includes("atdd-bun.yaml") || changed.some(path => /\.ya?ml$/.test(path))) profiles.push("topology"); if (!profiles.length) return { ok: true, message: "no affected area" }; try { const enabled = await enabledProfiles(root), selected = full ? ["all" as const] : [...new Set(profiles)].filter(profile => enabled.includes(profile)); if (!selected.length) return { ok: true, message: "no activated profile covers the affected area" }; const findings = await enforce({ root, profiles: selected }); return findings.length ? bad(findings.map(f => `${f.rule_id}: ${f.file}`).join("\n")) : { ok: true, message: "validation clean" }; } catch (error) { return bad(`Bun enforcer resolution failed: ${String(error)}`); } }
|
|
23
23
|
|
|
24
24
|
const inside = (parent: string, child: string) => { const path = relative(parent, child); return path === "" || (path !== ".." && !path.startsWith("../") && !path.startsWith("..\\")); };
|
|
25
25
|
async function primaryRoot(root: string): Promise<string | null> { const result = await git(root, ["rev-parse", "--git-common-dir"]); if (result.code) return null; return dirname(resolve(root, result.out)); }
|