@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.
Files changed (40) hide show
  1. package/README.md +87 -484
  2. package/conventions/atdd-bun.planner/atdd-bun.planner.acceptance-identity.convention.yaml +27 -0
  3. package/conventions/atdd-bun.planner/atdd-bun.planner.identity-required.convention.yaml +28 -0
  4. package/conventions/atdd-bun.planner/atdd-bun.planner.identity-unique.convention.yaml +27 -0
  5. package/conventions/atdd-bun.planner/atdd-bun.planner.interlocking-participant-resolves.convention.yaml +28 -0
  6. package/conventions/atdd-bun.planner/atdd-bun.planner.parse.convention.yaml +27 -0
  7. package/conventions/atdd-bun.planner/atdd-bun.planner.reference-resolves.convention.yaml +28 -0
  8. package/conventions/atdd-bun.planner/atdd-bun.planner.schema.convention.yaml +30 -0
  9. package/conventions/atdd-bun.planner/atdd-bun.planner.train-wagon-resolves.convention.yaml +26 -0
  10. package/conventions/atdd-bun.topology/atdd-bun.topology.e2e-location.convention.yaml +38 -0
  11. package/conventions/atdd-bun.topology/atdd-bun.topology.feature-source-coverage.convention.yaml +30 -0
  12. package/conventions/atdd-bun.topology/atdd-bun.topology.feature-test-coverage.convention.yaml +29 -0
  13. package/conventions/atdd-bun.topology/atdd-bun.topology.plan-location.convention.yaml +31 -0
  14. package/conventions/atdd-bun.topology/atdd-bun.topology.source-location.convention.yaml +33 -0
  15. package/conventions/atdd-bun.topology/atdd-bun.topology.test-location.convention.yaml +34 -0
  16. package/detectors/atdd_topology/atdd.implementation.yaml +3 -0
  17. package/detectors/atdd_topology/detect.mjs +14 -3
  18. package/detectors/atdd_topology/fixtures/clean/e2e/journeys/checkout.journey.test.ts +1 -0
  19. package/detectors/atdd_topology/fixtures/clean/plan/orders/E001.yaml +2 -0
  20. package/detectors/atdd_traceability_closure/detect.mjs +31 -16
  21. package/detectors/planner_docs_capability/atdd.implementation.yaml +7 -0
  22. package/detectors/planner_plan_integrity/atdd.implementation.yaml +1 -0
  23. package/detectors/planner_plan_integrity/detect.mjs +4 -1
  24. package/detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/_interlockings/checkout.yaml +6 -0
  25. package/detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/_interlockings/unnamed.yaml +2 -0
  26. package/detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/dup-a.yaml +7 -0
  27. package/detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/dup-b.yaml +6 -0
  28. package/detectors/planner_plan_integrity/fixtures/dirty/plan/orders/E001.yaml +5 -0
  29. package/detectors/planner_schema_validation/fixtures/dirty/plan/_trains/missing-description.yaml +5 -0
  30. package/integrity.json +39 -19
  31. package/package.json +1 -1
  32. package/planner-nodes/ENFORCEMENT_SCOPE.yaml +11 -1
  33. package/relationships.yaml +224 -0
  34. package/src/agent.ts +15 -8
  35. package/src/docs-capability.ts +3 -0
  36. package/src/enforce.ts +48 -2
  37. package/src/hooks.ts +2 -2
  38. package/src/integrity.ts +13 -5
  39. package/src/planner-kernel.ts +2 -1
  40. package/templates/agents/AGENTS.block.md +5 -1
@@ -0,0 +1,7 @@
1
+ train_id: train:orders:duplicate
2
+ title: First declaration
3
+ description: Declares an identity a second file also declares.
4
+ themes: [orders]
5
+ participants: [wagon:ghost]
6
+ wagons: [ghost]
7
+ sequence: []
@@ -0,0 +1,6 @@
1
+ train_id: train:orders:duplicate
2
+ title: Second declaration
3
+ description: The duplicate identity.
4
+ themes: [orders]
5
+ participants: []
6
+ sequence: []
@@ -0,0 +1,5 @@
1
+ urn: wmbt:orders:E001
2
+ statement: Place an order.
3
+ acceptances:
4
+ - identity:
5
+ purpose: An acceptance with no acc identity.
@@ -0,0 +1,5 @@
1
+ train_id: train:orders:missing-description
2
+ title: A train without its required description
3
+ themes: [orders]
4
+ participants: [wagon:orders]
5
+ sequence: []
package/integrity.json CHANGED
@@ -1,10 +1,24 @@
1
1
  {
2
- "version": "0.6.0",
2
+ "version": "0.6.2",
3
3
  "files": {
4
4
  "HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
5
5
  "PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
6
- "README.md": "622e7768f567099b0e30ac82f2053c165b8023e571966fc327f77f985ae1be0d",
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": "e5121dfd24c8dff4a3d471c90946e995c1efb3d56433d5739c289e77c7dd13ec",
128
- "detectors/atdd_topology/detect.mjs": "ab79df467d12423c8c851fae198b97adaef6fc5b66681b2460b8aeb84a210b36",
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": "330dd13694d4b53b1ee261dd8d37e0610ef0cf4e92bc70ccf3039878bfee90a7",
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": "94797b2afa85962a16673a92d15bf4796fd00cf2223c9d4106e06ba28c89599e",
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": "b8c454d7e136cdcc82524fea407490eefc8c2b8c9448831d94d51253f00a38a9",
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": "7e88a43ee43f5131d25debcc8f02f7381c3b356b6d8ea14357a89ead8e83342e",
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": "8ba6742949d5c72ba66a0eb5f2ebec94ae7a82270de10f985b45944e38742c18",
814
- "detectors/planner_plan_integrity/detect.mjs": "0172b6edaf66cc71cb9f5506ac25e679a90aeea1d0e0e2138689ec5079f87d16",
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": "b9429fb63a31799589461df342bf5fc0bfce5d1739e6ed54fff6b21db04bf775",
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": "157fd93f9cb9d900944aff7f7f0d3f6e96b2c5b9c5359c4dc9bae75d721ae238",
1069
- "src/agent.ts": "38a14f0f2ccce357efb1b2927e59bf55c20ff0050374367f15da36db20c46e77",
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": "65ef91658664b41b78184b395e57e5d28bda16128caa9e314f26e63568abc6cd",
1073
- "src/enforce.ts": "43ef276e1c4465ec32e5a431d9a5b1825c6168180e695280fd6d208fd583bac9",
1074
- "src/hooks.ts": "54d70d7d6923f1e39b259e3b9f377c43609aecdc64ff4d1232da595950d7b1aa",
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": "48d7adce486ece1885a95aac6867d286bb972d7a97bdbc32222fd6d75c2e2b49",
1096
+ "src/integrity.ts": "009b5f23ec3240cdc2cb133f942995e48f494edaa0259053bca15283134be3ea",
1077
1097
  "src/journey-docs.ts": "994d229376244c84212cff951032823068a76565dd2fd427b9d44e341bd14853",
1078
- "src/planner-kernel.ts": "87ffbabc60d7868df52e6e060fcb36b41e90c9d6a47fa9a99f16aa263d61a528",
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": "bfd8769bc42b8c4c91a70777e056624e37acb1037c7fd1497fcb59f007567336",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-bun",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/afokapu/atdd-bun.git"
@@ -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: 181
143
+ count: 180
134
144
 
135
145
  package_integrity_guard:
136
146
  implementation_id: planner.plan-integrity.bun
@@ -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 and an
13
- * existing block are kept unless `replace`; the rest of AGENTS.md is never touched. */
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 agents = join(repo, "AGENTS.md"), current = existsSync(agents) ? await readFile(agents, "utf8") : "", managed = await render("AGENTS.block.md");
22
- if (block.test(current) && !replace) kept.push(agents);
23
- else { await writeFile(agents, block.test(current) ? current.replace(block, managed) : current + (current && !current.endsWith("\n\n") ? (current.endsWith("\n") ? "\n" : "\n\n") : "") + managed); written.push(agents); }
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 agents = join(repo, "AGENTS.md"), missing = skillPaths.map(path => join(repo, path)).filter(path => !existsSync(path));
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, "AGENTS.md"].map(path => join(repo, path)).join("\n") };
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
  }
@@ -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 = [...Object.keys(profiles), "all"] as Profile[];
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(config.profiles).map((implementation) => runImplementation(implementation, { scanRoots, excludes })),
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 findings = await enforce({ root, profiles: full ? ["all"] : [...new Set(profiles)] }); 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)}`); } }
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)); }