@afokapu/atdd-bun 0.10.32 → 0.10.34

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.
@@ -0,0 +1,192 @@
1
+ schema_version: "1.0.0"
2
+ profile: "topology"
3
+ implementations:
4
+ - "atdd_topology"
5
+ conventions:
6
+ - rule_id: "atdd-bun.topology.e2e-location"
7
+ path: "conventions/atdd-bun.topology/atdd-bun.topology.e2e-location.convention.yaml"
8
+ - rule_id: "atdd-bun.topology.feature-source-coverage"
9
+ path: "conventions/atdd-bun.topology/atdd-bun.topology.feature-source-coverage.convention.yaml"
10
+ - rule_id: "atdd-bun.topology.feature-test-coverage"
11
+ path: "conventions/atdd-bun.topology/atdd-bun.topology.feature-test-coverage.convention.yaml"
12
+ - rule_id: "atdd-bun.topology.plan-location"
13
+ path: "conventions/atdd-bun.topology/atdd-bun.topology.plan-location.convention.yaml"
14
+ - rule_id: "atdd-bun.topology.source-location"
15
+ path: "conventions/atdd-bun.topology/atdd-bun.topology.source-location.convention.yaml"
16
+ - rule_id: "atdd-bun.topology.test-location"
17
+ path: "conventions/atdd-bun.topology/atdd-bun.topology.test-location.convention.yaml"
18
+ - rule_id: "planner.feature.wagon-link"
19
+ path: "planner-nodes/nodes/planner.feature.wagon-link.convention.yaml"
20
+ - rule_id: "planner.wagon.features"
21
+ path: "planner-nodes/nodes/planner.wagon.features.convention.yaml"
22
+ relationships:
23
+ - confidence: 1
24
+ constraint: "mandatory"
25
+ control: "internal"
26
+ foundation: "finish_to_start"
27
+ origin: "@afokapu/atdd-bun"
28
+ reason: "journey E2E paths are derived from the journeys the plan declares"
29
+ source_ref: "atdd-bun.topology.e2e-location"
30
+ strength: "critical"
31
+ target_ref: "planner.journey.definition"
32
+ type: "requires"
33
+ - confidence: 1
34
+ constraint: "mandatory"
35
+ control: "internal"
36
+ foundation: "start_to_start"
37
+ origin: "@afokapu/atdd-bun"
38
+ reason: "every route has an end-to-end proof: at its canonical path, and exercising the production runner"
39
+ source_ref: "atdd-bun.topology.e2e-location"
40
+ strength: "important"
41
+ target_ref: "tester.bun.interlocking-route-coverage"
42
+ type: "runs_alongside"
43
+ - confidence: 1
44
+ constraint: "mandatory"
45
+ control: "internal"
46
+ foundation: "start_to_start"
47
+ origin: "@afokapu/atdd-bun"
48
+ reason: "on a frontend journey a browser spec is the route's end-to-end proof"
49
+ source_ref: "atdd-bun.topology.e2e-location"
50
+ strength: "important"
51
+ target_ref: "tester.htmx.train-e2e-coverage"
52
+ type: "runs_alongside"
53
+ - confidence: 1
54
+ constraint: "mandatory"
55
+ control: "internal"
56
+ foundation: "finish_to_start"
57
+ origin: "@afokapu/atdd-bun"
58
+ reason: "coverage counts only source placed beneath its feature"
59
+ source_ref: "atdd-bun.topology.feature-source-coverage"
60
+ strength: "critical"
61
+ target_ref: "atdd-bun.topology.source-location"
62
+ type: "requires"
63
+ - confidence: 1
64
+ constraint: "mandatory"
65
+ control: "internal"
66
+ foundation: "start_to_start"
67
+ origin: "@afokapu/atdd-bun"
68
+ reason: "a feature with WMBTs is implemented and tested"
69
+ source_ref: "atdd-bun.topology.feature-test-coverage"
70
+ strength: "important"
71
+ target_ref: "atdd-bun.topology.feature-source-coverage"
72
+ type: "runs_alongside"
73
+ - confidence: 1
74
+ constraint: "mandatory"
75
+ control: "internal"
76
+ foundation: "finish_to_start"
77
+ origin: "@afokapu/atdd-bun"
78
+ reason: "coverage counts only tests placed beneath their feature and bound to its acceptances"
79
+ source_ref: "atdd-bun.topology.feature-test-coverage"
80
+ strength: "critical"
81
+ target_ref: "atdd-bun.topology.test-location"
82
+ type: "requires"
83
+ - confidence: 1
84
+ constraint: "mandatory"
85
+ control: "internal"
86
+ foundation: "start_to_start"
87
+ origin: "@afokapu/atdd-bun"
88
+ reason: "where a feature lives and which wagon owns it: the same ownership, by path and by link"
89
+ source_ref: "atdd-bun.topology.plan-location"
90
+ strength: "important"
91
+ target_ref: "planner.feature.wagon-link"
92
+ type: "runs_alongside"
93
+ - confidence: 1
94
+ constraint: "mandatory"
95
+ control: "internal"
96
+ foundation: "finish_to_start"
97
+ origin: "@afokapu/atdd-bun"
98
+ reason: "features are placed by path once the wagon's feature decomposition is declared"
99
+ source_ref: "atdd-bun.topology.plan-location"
100
+ strength: "critical"
101
+ target_ref: "planner.wagon.features"
102
+ type: "requires"
103
+ - confidence: 1
104
+ constraint: "mandatory"
105
+ control: "internal"
106
+ foundation: "finish_to_start"
107
+ origin: "@afokapu/atdd-bun"
108
+ reason: "source is placed beneath a feature the plan declares at its canonical path"
109
+ source_ref: "atdd-bun.topology.source-location"
110
+ strength: "critical"
111
+ target_ref: "atdd-bun.topology.plan-location"
112
+ type: "requires"
113
+ - confidence: 1
114
+ constraint: "mandatory"
115
+ control: "internal"
116
+ foundation: "start_to_start"
117
+ origin: "@afokapu/atdd-bun"
118
+ reason: "the same component URN, read for its grammar and for where the file must live"
119
+ source_ref: "atdd-bun.topology.source-location"
120
+ strength: "important"
121
+ target_ref: "coder.bun.green-urn-pattern"
122
+ type: "runs_alongside"
123
+ - confidence: 1
124
+ constraint: "mandatory"
125
+ control: "internal"
126
+ foundation: "finish_to_start"
127
+ origin: "@afokapu/atdd-bun"
128
+ reason: "tests are placed beneath a feature the plan declares"
129
+ source_ref: "atdd-bun.topology.test-location"
130
+ strength: "critical"
131
+ target_ref: "atdd-bun.topology.plan-location"
132
+ type: "requires"
133
+ - confidence: 1
134
+ constraint: "mandatory"
135
+ control: "internal"
136
+ foundation: "start_to_start"
137
+ origin: "@afokapu/atdd-bun"
138
+ reason: "the acceptance a test binds, read for presence and for whether a WMBT declares it"
139
+ source_ref: "atdd-bun.topology.test-location"
140
+ strength: "important"
141
+ target_ref: "tester.bun.acceptance-binding-declared"
142
+ type: "runs_alongside"
143
+ - confidence: 1
144
+ constraint: "mandatory"
145
+ control: "external"
146
+ foundation: "finish_to_start"
147
+ origin: "atdd@36ce055a:src/atdd/coach/graph/relationships.yaml"
148
+ reason: "wagon-feature coverage reads the wagon features manifest"
149
+ source_ref: "planner.coverage.bidirectional-coverage-between-wagons"
150
+ strength: "important"
151
+ target_ref: "planner.wagon.features"
152
+ type: "requires"
153
+ - confidence: 1
154
+ constraint: "mandatory"
155
+ control: "internal"
156
+ foundation: "finish_to_finish"
157
+ origin: "atdd@36ce055a:src/atdd/coach/graph/relationships.yaml"
158
+ reason: "a feature belongs to exactly one wagon"
159
+ source_ref: "planner.feature.definition"
160
+ strength: "critical"
161
+ target_ref: "planner.feature.wagon-link"
162
+ type: "requires"
163
+ - confidence: 1
164
+ constraint: "mandatory"
165
+ control: "external"
166
+ foundation: "finish_to_start"
167
+ origin: "atdd@36ce055a:src/atdd/coach/graph/relationships.yaml"
168
+ reason: "a feature is registered in its wagon _features.yaml"
169
+ source_ref: "planner.feature.wagon-link"
170
+ strength: "important"
171
+ target_ref: "planner.wagon.features"
172
+ type: "requires"
173
+ - confidence: 1
174
+ constraint: "mandatory"
175
+ control: "internal"
176
+ foundation: "finish_to_finish"
177
+ origin: "atdd@36ce055a:src/atdd/coach/graph/relationships.yaml"
178
+ reason: "a wagon groups the features that decompose it"
179
+ source_ref: "planner.wagon.definition"
180
+ strength: "minor"
181
+ target_ref: "planner.wagon.features"
182
+ type: "requires"
183
+ - confidence: 1
184
+ constraint: "mandatory"
185
+ control: "internal"
186
+ foundation: "start_to_start"
187
+ origin: "atdd@36ce055a:src/atdd/coach/graph/relationships.yaml"
188
+ reason: "the features-manifest rule uses the urn-array features format"
189
+ source_ref: "planner.wagon.features-format"
190
+ strength: "important"
191
+ target_ref: "planner.wagon.features"
192
+ type: "runs_alongside"
@@ -0,0 +1,94 @@
1
+ schema_version: "1.0.0"
2
+ profile: "traceability"
3
+ implementations:
4
+ - "atdd_traceability_closure"
5
+ conventions:
6
+ - rule_id: "traceability.plan.executable-acceptance-has-test"
7
+ path: "conventions/traceability/traceability.plan.executable-acceptance-has-test.convention.yaml"
8
+ - rule_id: "traceability.source.tested-by-present"
9
+ path: "conventions/traceability/traceability.source.tested-by-present.convention.yaml"
10
+ - rule_id: "traceability.source.tested-by-resolves"
11
+ path: "conventions/traceability/traceability.source.tested-by-resolves.convention.yaml"
12
+ - rule_id: "traceability.test.binding-resolves"
13
+ path: "conventions/traceability/traceability.test.binding-resolves.convention.yaml"
14
+ relationships:
15
+ - confidence: 1
16
+ constraint: "mandatory"
17
+ control: "internal"
18
+ foundation: "finish_to_start"
19
+ origin: "@afokapu/atdd-bun"
20
+ reason: "a test can bind an acceptance only through the acc: identity the WMBT gives it"
21
+ source_ref: "atdd-bun.planner.acceptance-identity"
22
+ strength: "critical"
23
+ target_ref: "traceability.test.binding-resolves"
24
+ type: "enables"
25
+ - confidence: 1
26
+ constraint: "discretionary"
27
+ control: "internal"
28
+ foundation: "finish_to_start"
29
+ origin: "@afokapu/atdd-bun"
30
+ reason: "traceability joins with the building preset because executable-acceptance-has-test reports every acceptance until tests exist"
31
+ source_ref: "atdd-bun.profiles.presets"
32
+ strength: "minor"
33
+ target_ref: "traceability.plan.executable-acceptance-has-test"
34
+ type: "follows"
35
+ - confidence: 1
36
+ constraint: "mandatory"
37
+ control: "internal"
38
+ foundation: "finish_to_start"
39
+ origin: "@afokapu/atdd-bun"
40
+ reason: "telemetry items follow the acceptance-has-test discipline"
41
+ source_ref: "tester.bun.telemetry-required-item-coverage"
42
+ strength: "important"
43
+ target_ref: "traceability.plan.executable-acceptance-has-test"
44
+ type: "requires"
45
+ - confidence: 1
46
+ constraint: "mandatory"
47
+ control: "internal"
48
+ foundation: "start_to_start"
49
+ origin: "@afokapu/atdd-bun"
50
+ reason: "the two directions of plan-test closure: every acceptance has a test, and every test names an acceptance that exists"
51
+ source_ref: "traceability.plan.executable-acceptance-has-test"
52
+ strength: "critical"
53
+ target_ref: "traceability.test.binding-resolves"
54
+ type: "runs_alongside"
55
+ - confidence: 1
56
+ constraint: "mandatory"
57
+ control: "internal"
58
+ foundation: "start_to_start"
59
+ origin: "@afokapu/atdd-bun"
60
+ reason: "the same Tested-By header, read by the closure check and by the coder header rule"
61
+ source_ref: "traceability.source.tested-by-present"
62
+ strength: "important"
63
+ target_ref: "coder.bun.green-header-tested-by"
64
+ type: "runs_alongside"
65
+ - confidence: 1
66
+ constraint: "mandatory"
67
+ control: "internal"
68
+ foundation: "finish_to_start"
69
+ origin: "@afokapu/atdd-bun"
70
+ reason: "a testing witness can only resolve once one is declared"
71
+ source_ref: "traceability.source.tested-by-resolves"
72
+ strength: "critical"
73
+ target_ref: "traceability.source.tested-by-present"
74
+ type: "requires"
75
+ - confidence: 1
76
+ constraint: "mandatory"
77
+ control: "internal"
78
+ foundation: "start_to_start"
79
+ origin: "@afokapu/atdd-bun"
80
+ reason: "source-to-test and test-to-plan links chain into plan to test to implementation closure"
81
+ source_ref: "traceability.source.tested-by-resolves"
82
+ strength: "important"
83
+ target_ref: "traceability.test.binding-resolves"
84
+ type: "runs_alongside"
85
+ - confidence: 1
86
+ constraint: "mandatory"
87
+ control: "internal"
88
+ foundation: "finish_to_start"
89
+ origin: "@afokapu/atdd-bun"
90
+ reason: "a test's binding must be declared before it can be resolved against the plan"
91
+ source_ref: "traceability.test.binding-resolves"
92
+ strength: "critical"
93
+ target_ref: "tester.bun.acceptance-binding-declared"
94
+ type: "requires"
@@ -0,0 +1,7 @@
1
+ schema_version: "1.0.0"
2
+ profile: "workflow"
3
+ implementations: []
4
+ conventions:
5
+ - rule_id: "atdd-workflow.workflow.lifecycle"
6
+ path: "../atdd-workflow/conventions/atdd-workflow.workflow/atdd-workflow.workflow.lifecycle.convention.yaml"
7
+ relationships: []
@@ -8,11 +8,32 @@ statement: >-
8
8
  terms:
9
9
  - term_id: presets
10
10
  text: >-
11
- Planning: planner (plus docs when the repository keeps documentation under docs/). Building, once RED starts: add coder, tester, traceability, security; add htmx and design for a web UI, and interlocking once trains have a runtime. Instrumenting: add telemetry.
11
+ Planning: planner (plus docs when the repository keeps documentation under docs/). Building, once RED starts: add coder, tester, traceability, security; add htmx and design for a web UI, and interlocking once trains have a runtime. Instrumenting: add telemetry. Coordinating durable multi-agent work: after @afokapu/atdd-workflow is installed locally, add workflow.
12
12
  values:
13
13
  planning: [planner]
14
14
  building: [coder, tester, traceability, security]
15
15
  instrumenting: [telemetry]
16
+ coordinating: [workflow]
17
+ - term_id: profile_registries
18
+ text: >-
19
+ A deterministic, profile-scoped projection of the detector manifests, convention paths, and
20
+ direct relationship edges. Read the selected profile's registry instead of the complete graph.
21
+ values:
22
+ architecture: conventions/_profiles/architecture.yaml
23
+ coder: conventions/_profiles/coder.yaml
24
+ design: conventions/_profiles/design.yaml
25
+ docs: conventions/_profiles/docs.yaml
26
+ htmx: conventions/_profiles/htmx.yaml
27
+ interlocking: conventions/_profiles/interlocking.yaml
28
+ metrics: conventions/_profiles/metrics.yaml
29
+ planner: conventions/_profiles/planner.yaml
30
+ runtime: conventions/_profiles/runtime.yaml
31
+ security: conventions/_profiles/security.yaml
32
+ telemetry: conventions/_profiles/telemetry.yaml
33
+ tester: conventions/_profiles/tester.yaml
34
+ topology: conventions/_profiles/topology.yaml
35
+ traceability: conventions/_profiles/traceability.yaml
36
+ workflow: conventions/_profiles/workflow.yaml
16
37
  content:
17
38
  summary: >-
18
39
  A goal is easier to name than a profile. The presets map what the user is doing to the profiles that judge it, in the order the work creates what they check.
@@ -20,8 +41,9 @@ content:
20
41
  planner alone keeps the plan consistent: it parses, validates and resolves the plan with nothing else present. traceability judges the links from the plan to tests and source, so before tests exist it can only report that every acceptance is untested; it joins with building, and from then on it also catches a plan change that orphans a test.
21
42
  security joins with the first code, not at release: a secret committed during GREEN stays in history.
22
43
  coder already bundles the architecture, metrics, runtime, design and responsive detectors, so those need no later preset.
44
+ workflow is optional: it governs durable seats, tasks and threads only when the repository has the locally installed @afokapu/atdd-workflow companion. It is deliberately not selected by a default profile list.
23
45
  fix_hint: |
24
- At a stage whose preset is missing, ask what the user wants to do (planning, building, or instrumenting), recommend that preset and say it can be adopted step by step, then write the chosen list to profiles: in atdd-bun.yaml.
46
+ At a stage whose preset is missing, ask what the user wants to do (planning, building, instrumenting, or coordinating), recommend that preset and say it can be adopted step by step, then write the chosen list to profiles: in atdd-bun.yaml.
25
47
  exceptions:
26
48
  - >-
27
49
  A profile can always be run by name to see what it would report (atdd-bun coder); only profiles: decides what the repository is governed by.
package/integrity.json CHANGED
@@ -1,10 +1,25 @@
1
1
  {
2
- "version": "0.10.32",
2
+ "version": "0.10.34",
3
3
  "files": {
4
4
  "HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
5
5
  "PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
6
- "README.md": "78a1acc1f18f84a567448051bec90f676e3cab7956d971abc97a6201c96fc409",
6
+ "README.md": "edcd36ebed5853c412c89ed998cd198f7a58237a68dcf37c14be5f7f5c0e9ff4",
7
7
  "bunfig.toml": "b9fc65eca9014c5179380259d70e76385a6a80788fa9a2df5fb3eaa5554fd2fe",
8
+ "conventions/_profiles/architecture.yaml": "f1808768b720a4e476047e4a0912002ca673946562cea9aeef5d0c214828d93a",
9
+ "conventions/_profiles/coder.yaml": "c38e46405ddd7e82fd7679246d027821db19db86c307a23ce5d14f18165d0699",
10
+ "conventions/_profiles/design.yaml": "25a02a67152d49728ad3517f6510da7285116fa69c56cbda55613a7cfcfd3b89",
11
+ "conventions/_profiles/docs.yaml": "05485c3b32f1757e77c42e331eec1db651cb52ef57fc4e8607bb2f73b056cbb3",
12
+ "conventions/_profiles/htmx.yaml": "38fc8b3e1a53429e16977ed63e83e751b203deffe9b338ea171050fb64641559",
13
+ "conventions/_profiles/interlocking.yaml": "46a2a4fb2eb537941c991ef4178d009a30d14f84279e525ca0d0887d3239047d",
14
+ "conventions/_profiles/metrics.yaml": "6028a227ccdef6d97ff10602f1a146eb73fd02b0a03ebce6726540fa30540f3c",
15
+ "conventions/_profiles/planner.yaml": "b3938783793c34f832968b7293b7c301f3e1deff5f06536a078168c911d0d089",
16
+ "conventions/_profiles/runtime.yaml": "48fae0d31fb9228b8819243c264721037720acb1763bf27f74325c14ffef46d4",
17
+ "conventions/_profiles/security.yaml": "38eb6b17e35e1439fe4f19c8ccf3ba1b2f18759a984dd1d33171ec3a29128876",
18
+ "conventions/_profiles/telemetry.yaml": "4c07c693357fad420df0df82a3ad196cd159df017fec6b98244841530516dfd9",
19
+ "conventions/_profiles/tester.yaml": "348183eee5298cbb843031f6c41c3f9d0a725b11b3c66e154b47c9b340a72666",
20
+ "conventions/_profiles/topology.yaml": "08df698c2e5419e8f7faa83f5f15c3ba9c085cc2ce72579624eb24962a6b8612",
21
+ "conventions/_profiles/traceability.yaml": "adce93fb8392307685a01bec85e60534c59a9f4f0e4f8ea4767d79d22cf80878",
22
+ "conventions/_profiles/workflow.yaml": "162404ac861c77d1d4f4f2a8b731eb80f75677197636f96d198909661247b229",
8
23
  "conventions/atdd-bun.planner/atdd-bun.planner.acceptance-identity.convention.yaml": "81c5c773d5ee15e8d99a2c237aa9846b110b2533df45ebf407d0e3ec6ed3dd03",
9
24
  "conventions/atdd-bun.planner/atdd-bun.planner.identity-required.convention.yaml": "e96d7c1455d0c221072d82e7f2b55da1c4f9ceb17718eb6ed96affdeef37675b",
10
25
  "conventions/atdd-bun.planner/atdd-bun.planner.identity-unique.convention.yaml": "409ba1d035ed46bc45f3627b62fd3a6a5967d984cc4c071c41be3f3fa887c6d6",
@@ -13,7 +28,7 @@
13
28
  "conventions/atdd-bun.planner/atdd-bun.planner.reference-resolves.convention.yaml": "b96927d269692c29c02fee38ecbeb999480791f8dc7486c4072a9ff30183d7d6",
14
29
  "conventions/atdd-bun.planner/atdd-bun.planner.schema.convention.yaml": "16d0a26451758b759e6ca1f5b4f38ae1feff129688306ffcc2f70f6d3974b591",
15
30
  "conventions/atdd-bun.planner/atdd-bun.planner.train-wagon-resolves.convention.yaml": "6410e248351e701dcba38c671c07c4d8f6d67927984ed4d18e567abd1eb1f3f0",
16
- "conventions/atdd-bun.profiles/atdd-bun.profiles.presets.convention.yaml": "a5885bfa387d0abbc4694ea2d0cf1f3639d5b45588707d4f70f2f2be4e12e847",
31
+ "conventions/atdd-bun.profiles/atdd-bun.profiles.presets.convention.yaml": "7ef8f1bc1a9c148f93a1929dbb854c467a508a51a7c7b2b3f49c245fd1c0c734",
17
32
  "conventions/atdd-bun.topology/atdd-bun.topology.e2e-location.convention.yaml": "3a3b991bc851b86d597636200f5cb10a0defda3920348860cdca28955ab91f4c",
18
33
  "conventions/atdd-bun.topology/atdd-bun.topology.feature-source-coverage.convention.yaml": "2b64093f1e7f1fe0e2edcadf12e560fa80cecec19536abaa5bf09af362c6694e",
19
34
  "conventions/atdd-bun.topology/atdd-bun.topology.feature-test-coverage.convention.yaml": "279be57399c9b79a1bd758414076e3053d67b7d4a7fcbbb950ea12d1201effeb",
@@ -1176,26 +1191,26 @@
1176
1191
  "planner-schemas/wagon.schema.json": "57b98f1ade998d745538eb3d72fb3014a50042006870f90bce47028eea336eb4",
1177
1192
  "planner-schemas/wmbt.schema.json": "7a3a5693bfcf01340667c2be4a3c48e74f34b1af7e8dc13372c0de06b7f4fe12",
1178
1193
  "relationships.yaml": "26dd50489d7cbd45400937a8949d6ff77cdd4400bfa828d6b7a17e90c64c879b",
1179
- "src/agent.ts": "009d3170752ded12e9e3d4f7f07fd68d4bab821046714ad68cea428581343c67",
1194
+ "src/agent.ts": "39ca8b3183063c60e00eb96e5629d0397aeb83cc82d4c4bc32c01715c906649b",
1180
1195
  "src/ci.ts": "67e54d2cbfc44a9e42837d5e750af526d6255c539d3c452a4d5fca3226c0d9ac",
1181
- "src/cli.ts": "09e6b4bdebef32ff482a12182e2efbc2e42424ff854e813a10efd3764e45d4fc",
1196
+ "src/cli.ts": "cbc50f1e7fc7d0935f78230b7da6e232ed7ee6f04828e015c599fb6f6edcbfa2",
1182
1197
  "src/docs-capability.ts": "bdeeb414649049b1cd4fa8008cada3a576318be51c43bec91bfa46a8e250c270",
1183
- "src/enforce.ts": "45c473ffec9f565fba24df2a7db6564d76ee885cd4b0ab8b34bc14b7f2bcc7b2",
1184
- "src/hooks.ts": "9779eb48f9d0cb6234c33f6616f98dde6583d2ee03ac3c19f3ace2d156af26d0",
1185
- "src/index.ts": "b3cc97f9867bd605aefa95650a6b8b773e32e6e52e58792d0dbf6736dd3406be",
1186
- "src/integrity.ts": "3a74cc03bbdebf83d607073a4a13ab4afd4a163c2307062ed137d7d21e38b988",
1198
+ "src/enforce.ts": "8b22a3ac30fdd35c9caa4c23f064498293a628a342d590fca1f2040ee3a8ea05",
1199
+ "src/hooks.ts": "0b4ca8c857e637bbd144e737e78eda924e3cba7b038c0abe5e6916d200d73068",
1200
+ "src/index.ts": "4081f8b08d1d3ee9e15a9964143e53d9cfb94a5616ab3e58a180d24cc16dc381",
1201
+ "src/integrity.ts": "44ebe101933e8b1a42c563acdffcaf7a24baa4861eb8731a914bc51500d0a93d",
1187
1202
  "src/journey-docs.ts": "994d229376244c84212cff951032823068a76565dd2fd427b9d44e341bd14853",
1188
1203
  "src/planner-kernel.ts": "5d3f5305fb59becb2cc97d8a03aca6b104f385805d91cc0410856e289dd6fbb1",
1189
1204
  "src/planner-schema-validator.ts": "ce529c0936171075e93c7539b3655fe895a98dd2973ebda5775958e6ebc316d9",
1190
1205
  "src/planner-validators.ts": "99adeb65f579af923c11f21a8f55cfb9993b007fd8df59c5f764696b55379cfc",
1206
+ "src/profile-registry.ts": "716ef6e75e8e6eb14ffee37095b4fc9b38ddba2170fb84e7f06476863c3c02a5",
1191
1207
  "src/register.ts": "6a2f1c6b8df0dc9c8f45df8ec9fac2be29044e048b08c8ffe423410e94dadedd",
1192
1208
  "src/release.ts": "7b0fe444ee81f476f53b9c21b6c347ef588ee616c60211419cf984132be768ba",
1193
- "src/setup.ts": "2dbdddec898e9a3779889f3491eaa1423c4427c9ec63167663245b3ae4db1e6f",
1209
+ "src/setup.ts": "38233265afedc63499bbef3b5403889651add335a70efd3813254ba53f4fc4fe",
1194
1210
  "src/telemetry-plan.ts": "cdb1e8bfff414f3ea81977388973da2cf6d89567d9e060dc9d5989a33600f153",
1195
1211
  "src/topology.ts": "9779ee48e1c453abb76363163d4ab363463fa14e359571139f6d1f1fcc51ae2c",
1196
- "templates/agents/AGENTS.block.md": "d7f6013ef7b1793f236b610f78f2898f6c10c60751f671ff2c4b72d49b4f6011",
1212
+ "templates/agents/AGENTS.block.md": "978ea89e1469f0a89a6ef6e4ac40bdc11ae856bb2754069743758ba204c395a1",
1197
1213
  "templates/agents/atdd-bun.integrity.test.ts": "dec6f6e5f65a08d9512fc703b367c163f8c9da1f6aa7fd466835c1ec30348a41",
1198
- "templates/agents/atdd/SKILL.md": "8f36d7d571aa17beb03df5e620ff837be73eb21a4e73dabd2c12188d2b378022",
1199
1214
  "templates/docs/site.css": "482b3427e3a3cb02a85dc85cfbeac80592dfd53abf42fc92d6ea7071f22afc95",
1200
1215
  "templates/github/atdd-bun-release.yml": "d9547e9e6ef3ae010d53d55314f6a54567f88bd900f7e50d994cd308601d2684",
1201
1216
  "templates/github/atdd-bun.yml": "404dc06b6ff4faf5eab909f9f77bac0ae58163d810c3928c4353b1f4b3529048"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-bun",
3
- "version": "0.10.32",
3
+ "version": "0.10.34",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/afokapu/atdd-bun.git"
@@ -39,6 +39,7 @@
39
39
  "ajv-formats": "^3.0.1"
40
40
  },
41
41
  "devDependencies": {
42
+ "@afokapu/atdd-workflow": "^0.1.7",
42
43
  "@axe-core/playwright": "^4.13.0",
43
44
  "@playwright/test": "^1.63.0"
44
45
  }
package/src/agent.ts CHANGED
@@ -1,26 +1,26 @@
1
- import { mkdir, readFile, writeFile } from "node:fs/promises";
1
+ import { readFile, rm, writeFile } from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
- import { dirname, join, resolve } from "node:path";
3
+ import { join, resolve } from "node:path";
4
4
 
5
5
  const root = resolve(import.meta.dir, "..");
6
6
  const version = (await Bun.file(join(root, "package.json")).json() as { version: string }).version;
7
7
  const render = async (path: string) => (await readFile(join(root, "templates/agents", path), "utf8")).replace("{{VERSION}}", version);
8
- // .agents/skills is the vendor-neutral Agent Skills path (Codex, Copilot, Cursor, Gemini CLI, …); .claude/skills is Claude Code's.
9
- const skillPaths = [".agents/skills/atdd/SKILL.md", ".claude/skills/atdd/SKILL.md"];
8
+ const legacySkillPaths = [".agents/skills/atdd/SKILL.md", ".claude/skills/atdd/SKILL.md"];
10
9
  // AGENTS.md is read by Codex, Cursor and most agents; CLAUDE.md by Claude Code. Both carry the same block.
11
10
  export const instructionPaths = ["AGENTS.md", "CLAUDE.md"];
12
11
  const block = /<!-- atdd-bun:start[\s\S]*?<!-- atdd-bun:end -->\n?/;
12
+ const generatedSkill = /<!-- Generated by @afokapu\/atdd-bun /;
13
13
  /** `current` with the managed block replaced, or appended after at least one blank line; `current` itself is kept. */
14
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}`;
15
15
 
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. */
16
+ /** Write the managed ATDD block in AGENTS.md and CLAUDE.md. Existing files and an existing block are kept unless
17
+ * `replace`; the rest of each instruction file is never touched. Generated legacy skills are retired automatically. */
18
18
  export async function agentInit(repo = process.cwd(), replace = false) {
19
- const written: string[] = [], kept: string[] = [];
20
- for (const path of skillPaths) {
19
+ const written: string[] = [], kept: string[] = [], retired: string[] = [];
20
+ for (const path of legacySkillPaths) {
21
21
  const output = join(repo, path);
22
- if (existsSync(output) && !replace) { kept.push(output); continue; }
23
- await mkdir(dirname(output), { recursive: true }); await writeFile(output, await render("atdd/SKILL.md")); written.push(output);
22
+ if (!existsSync(output) || (!replace && !generatedSkill.test(await readFile(output, "utf8")))) continue;
23
+ await rm(output); retired.push(output);
24
24
  }
25
25
  const managed = await render("AGENTS.block.md");
26
26
  for (const path of instructionPaths) {
@@ -28,12 +28,12 @@ export async function agentInit(repo = process.cwd(), replace = false) {
28
28
  if (block.test(current) && !replace) { kept.push(file); continue; }
29
29
  await writeFile(file, withBlock(current, managed)); written.push(file);
30
30
  }
31
- if (!written.length) return { ok: false, message: `${kept.join(", ")} exist; use --replace` };
32
- return { ok: true, message: written.join("\n") };
31
+ if (!written.length && !retired.length) return { ok: false, message: `${kept.join(", ")} exist; use --replace` };
32
+ return { ok: true, message: [...written, ...retired.map(path => `${path} removed`)].join("\n") };
33
33
  }
34
34
  export async function agentStatus(repo = process.cwd()) {
35
- const expected = skillPaths;
36
- const missing = expected.map(path => join(repo, path)).filter(path => !existsSync(path));
35
+ const missing: string[] = [];
37
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)`); }
38
- return { ok: !missing.length, message: missing.length ? `missing: ${missing.join(", ")}` : [...expected, ...instructionPaths].map(path => join(repo, path)).join("\n") };
37
+ for (const path of legacySkillPaths) { const skill = join(repo, path); if (existsSync(skill) && generatedSkill.test(await readFile(skill, "utf8"))) missing.push(`${skill} (retired generated skill)`); }
38
+ return { ok: !missing.length, message: missing.length ? `missing: ${missing.join(", ")}` : instructionPaths.map(path => join(repo, path)).join("\n") };
39
39
  }
package/src/cli.ts CHANGED
@@ -8,6 +8,7 @@ import { journeyDocs } from "./journey-docs";
8
8
  import { adrRegister } from "./docs-capability";
9
9
  import { releaseCheck } from "./release";
10
10
  import { initializeRepository } from "./setup";
11
+ import { profileRegistries } from "./profile-registry";
11
12
 
12
13
  const args = process.argv.slice(2);
13
14
  const usage = {
@@ -19,6 +20,7 @@ const usage = {
19
20
  "atdd-bun worktree <start|finish|status>",
20
21
  "atdd-bun ci <init|status> [--replace]",
21
22
  "atdd-bun agent <init|status> [--replace]",
23
+ "atdd-bun profiles registry [--check]",
22
24
  "atdd-bun integrity [init|status] [--replace]",
23
25
  "atdd-bun docs journeys [--out <dir>] [--check] [--force]",
24
26
  "atdd-bun docs adr-register [--check]",
@@ -94,6 +96,11 @@ if (args[0] === "agent") {
94
96
  const result = args[1] === "init" ? await agentInit(process.cwd(), args.includes("--replace")) : args[1] === "status" ? await agentStatus() : fail("agent requires init or status");
95
97
  console[result.ok ? "log" : "error"](result.message); process.exit(result.ok ? 0 : 1);
96
98
  }
99
+ if (args[0] === "profiles") {
100
+ if (args[1] !== "registry") fail("profiles requires registry");
101
+ const result = await profileRegistries({ check: args.includes("--check") });
102
+ console[result.ok ? "log" : "error"](result.message); process.exit(result.ok ? 0 : 1);
103
+ }
97
104
  if (args[0] === "release") {
98
105
  if (args[1] !== "check") fail("release requires check");
99
106
  const result = await releaseCheck(); console[result.ok ? "log" : "error"](result.message); process.exit(result.ok ? 0 : 1);
package/src/enforce.ts CHANGED
@@ -4,7 +4,8 @@ import { tmpdir } from "node:os";
4
4
  import { join, resolve } from "node:path";
5
5
  import { topologyFor } from "./topology";
6
6
 
7
- export type Profile = "traceability" | "topology" | "docs" | "planner" | "telemetry" | "coder" | "tester" | "security" | "architecture" | "metrics" | "runtime" | "interlocking" | "htmx" | "design" | "all";
7
+ export type Profile = "traceability" | "topology" | "docs" | "planner" | "telemetry" | "coder" | "tester" | "security" | "architecture" | "metrics" | "runtime" | "interlocking" | "htmx" | "design" | "workflow" | "all";
8
+ export type ConcreteProfile = Exclude<Profile, "all">;
8
9
 
9
10
  export type Violation = {
10
11
  rule_id: string;
@@ -22,7 +23,7 @@ export type EnforcementConfig = {
22
23
  profiles?: Profile[];
23
24
  };
24
25
 
25
- const profiles: Record<Exclude<Profile, "all">, string[]> = {
26
+ export const profileImplementations: Record<ConcreteProfile, string[]> = {
26
27
  traceability: ["atdd_traceability_closure"],
27
28
  topology: ["atdd_topology"],
28
29
  docs: ["planner_docs_capability"],
@@ -37,16 +38,28 @@ const profiles: Record<Exclude<Profile, "all">, string[]> = {
37
38
  interlocking: ["bun_interlocking_binding", "bun_interlocking_coverage", "bun_interlocking_infrastructure"],
38
39
  htmx: ["htmx_hypermedia_detector", "htmx_tester_detector", "htmx_e2e_detector"],
39
40
  design: ["bun_design_system_detector", "bun_responsive_detector"],
41
+ // Operational policy only: its convention lives in the optional @afokapu/atdd-workflow package.
42
+ workflow: [],
43
+ };
44
+
45
+ /** Conventions which an installed companion package contributes to an otherwise policy-only profile. */
46
+ export const profileConventions: Record<ConcreteProfile, Array<{ rule_id: string; path: string }>> = {
47
+ traceability: [], topology: [], docs: [], planner: [], telemetry: [], coder: [], tester: [], security: [],
48
+ architecture: [], metrics: [], runtime: [], interlocking: [], htmx: [], design: [],
49
+ workflow: [{ rule_id: "atdd-workflow.workflow.lifecycle", path: "../atdd-workflow/conventions/atdd-workflow.workflow/atdd-workflow.workflow.lifecycle.convention.yaml" }],
40
50
  };
41
51
 
42
- type ConcreteProfile = Exclude<Profile, "all">;
43
52
  /** Every profile but `all`. */
44
- export const concreteProfiles = Object.keys(profiles) as ConcreteProfile[];
53
+ export const concreteProfiles = Object.keys(profileImplementations) as ConcreteProfile[];
54
+ /** Profiles a repository adopts only when it explicitly uses the optional companion package. */
55
+ export const optionalProfiles: ConcreteProfile[] = ["workflow"];
56
+ /** The safe default set for repositories that have not opted into an optional companion profile. */
57
+ export const defaultProfiles = concreteProfiles.filter(profile => !optionalProfiles.includes(profile));
45
58
  /** Profile names accepted by the CLI and public integrations. */
46
59
  export const profileNames = [...concreteProfiles, "all"] as Profile[];
47
60
 
48
61
  /**
49
- * The profiles the operator has activated: `profiles:` in atdd-bun.yaml, or every profile when absent. A legacy
62
+ * The profiles the operator has activated: `profiles:` in atdd-bun.yaml, or every built-in profile when absent. A legacy
50
63
  * repository can adopt enforcement gradually by listing only what it is ready for. `all`, the hooks and the
51
64
  * generated CI run exactly these; naming a profile explicitly still runs it. An unknown name or an empty list is
52
65
  * a configuration error, never a silent "run nothing".
@@ -54,7 +67,7 @@ export const profileNames = [...concreteProfiles, "all"] as Profile[];
54
67
  export async function enabledProfiles(root = process.cwd()): Promise<ConcreteProfile[]> {
55
68
  const file = join(resolve(root), "atdd-bun.yaml");
56
69
  const listed = existsSync(file) ? (Bun.YAML.parse(await readFile(file, "utf8")) as { profiles?: unknown } | null)?.profiles : undefined;
57
- if (listed === undefined) return concreteProfiles;
70
+ if (listed === undefined) return defaultProfiles;
58
71
  if (!Array.isArray(listed) || !listed.length) throw new Error("atdd-bun.yaml profiles must be a non-empty list of profile names");
59
72
  const unknown = listed.filter(name => !concreteProfiles.includes(name));
60
73
  if (unknown.length) throw new Error(`atdd-bun.yaml profiles lists unknown profile(s): ${unknown.join(", ")}; known: ${concreteProfiles.join(", ")}`);
@@ -68,10 +81,10 @@ export function implementationsFor(requested: Profile[] = ["all"]): string[] {
68
81
  const selected = new Set<string>();
69
82
  for (const profile of requested) {
70
83
  if (profile === "all") {
71
- for (const ids of Object.values(profiles)) ids.forEach((id) => selected.add(id));
84
+ for (const ids of Object.values(profileImplementations)) ids.forEach((id) => selected.add(id));
72
85
  continue;
73
86
  }
74
- const ids = profiles[profile];
87
+ const ids = profileImplementations[profile];
75
88
  if (!ids) throw new Error(`unknown enforcement profile: ${profile}`);
76
89
  ids.forEach((id) => selected.add(id));
77
90
  }
@@ -79,10 +92,10 @@ export function implementationsFor(requested: Profile[] = ["all"]): string[] {
79
92
  }
80
93
 
81
94
  /** The rule ids a detector's manifest declares it emits (emits_rule_ids and api_emits_rule_ids). */
82
- export async function declaredRuleIds(implementation: string): Promise<Set<string>> {
95
+ export async function declaredRuleIds(implementation: string, detectors = detectorRoot): Promise<Set<string>> {
83
96
  const ids = new Set<string>();
84
97
  let inList = false;
85
- for (const line of (await readFile(join(detectorRoot, implementation, "atdd.implementation.yaml"), "utf8")).split("\n")) {
98
+ for (const line of (await readFile(join(detectors, implementation, "atdd.implementation.yaml"), "utf8")).split("\n")) {
86
99
  if (line === "emits_rule_ids:" || line === "api_emits_rule_ids:") { inList = true; continue; }
87
100
  if (/^[A-Za-z_][\w-]*:/.test(line)) { inList = false; continue; }
88
101
  const match = inList && line.match(/^\s*-\s+([^#\s]+)/);
package/src/hooks.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import { chmod, mkdir, readdir, readFile, realpath, rm, writeFile } from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
- import { dirname, join, relative, resolve } from "node:path";
3
+ import { basename, dirname, join, relative, resolve } from "node:path";
4
4
  import { enabledProfiles, enforce } from "./enforce";
5
5
  import { topologyFor } from "./topology";
6
6
  import { JOURNEY_DOCS_DIR } from "./journey-docs";
7
7
 
8
8
  export type WorktreePolicy = { enabled: boolean; root: string; primary_directory: string; primary_branch: string; require_linked_worktree: boolean };
9
9
  export type HookPolicy = { max_staged_files: number; max_staged_changed_lines: number; max_commits_per_push: number; max_registry_removed_lines: number; registry_paths: string[]; protected_branches: string[]; require_plan_reference: boolean; require_traceability: boolean; worktrees: WorktreePolicy };
10
- export const defaultWorktreePolicy: WorktreePolicy = { enabled: false, root: "../worktrees", primary_directory: "main", primary_branch: "main", require_linked_worktree: true };
10
+ export const defaultWorktreePolicy: WorktreePolicy = { enabled: false, root: "../worktrees/{repo}", primary_directory: ".", primary_branch: "main", require_linked_worktree: true };
11
11
  export const defaultHookPolicy: HookPolicy = { max_staged_files: 20, max_staged_changed_lines: 350, max_commits_per_push: 10, max_registry_removed_lines: 350, registry_paths: ["plan/_*.yaml", "plan/_*.yml", "contracts/_*.yaml", "contracts/_*.yml"], protected_branches: ["main", "master"], require_plan_reference: true, require_traceability: true, worktrees: defaultWorktreePolicy };
12
12
  export const hookEvents = ["pre-commit", "commit-msg", "pre-push", "pre-merge-commit", "post-commit", "post-merge"] as const;
13
13
  export type HookEvent = typeof hookEvents[number];
@@ -33,7 +33,7 @@ async function validation(root: string, changed: string[], full: boolean, regist
33
33
  const inside = (parent: string, child: string) => { const path = relative(parent, child); return path === "" || (path !== ".." && !path.startsWith("../") && !path.startsWith("..\\")); };
34
34
  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)); }
35
35
  export type WorktreeLayout = { primary: string; worktreeRoot: string; current: string; branch: string };
36
- async function layout(root: string, cfg: WorktreePolicy): Promise<WorktreeLayout | { error: string }> { const current = await realpath(resolve(root)), commonRoot = await primaryRoot(current); if (!commonRoot) return { error: "not inside a Git worktree" }; const primary = await realpath(commonRoot), expectedPrimary = join(dirname(primary), cfg.primary_directory); if (primary !== expectedPrimary) return { error: `primary checkout must be ${expectedPrimary}, not ${primary}` }; const branch = (await git(primary, ["symbolic-ref", "--quiet", "--short", "HEAD"])).out; if (branch !== cfg.primary_branch) return { error: `primary checkout ${primary} must be on ${cfg.primary_branch}, not ${branch || "a detached HEAD"}` }; return { primary, worktreeRoot: resolve(primary, cfg.root), current, branch: (await git(current, ["symbolic-ref", "--quiet", "--short", "HEAD"])).out }; }
36
+ async function layout(root: string, cfg: WorktreePolicy): Promise<WorktreeLayout | { error: string }> { const current = await realpath(resolve(root)), commonRoot = await primaryRoot(current); if (!commonRoot) return { error: "not inside a Git worktree" }; const primary = await realpath(commonRoot), expectedPrimary = cfg.primary_directory === "." ? primary : join(dirname(primary), cfg.primary_directory); if (primary !== expectedPrimary) return { error: `primary checkout must be ${expectedPrimary}, not ${primary}` }; const branch = (await git(primary, ["symbolic-ref", "--quiet", "--short", "HEAD"])).out; if (branch !== cfg.primary_branch) return { error: `primary checkout ${primary} must be on ${cfg.primary_branch}, not ${branch || "a detached HEAD"}` }; return { primary, worktreeRoot: resolve(primary, cfg.root.replaceAll("{repo}", basename(primary))), current, branch: (await git(current, ["symbolic-ref", "--quiet", "--short", "HEAD"])).out }; }
37
37
  async function worktreeCommitPolicy(root: string, cfg: WorktreePolicy): Promise<string | null> { if (!cfg.enabled || !cfg.require_linked_worktree) return null; const state = await layout(root, cfg); if ("error" in state) return state.error; if (state.current === state.primary) return `commits must use a linked worktree beneath ${state.worktreeRoot}; run: atdd-bun worktree start <branch>`; if (!inside(state.worktreeRoot, state.current)) return `linked worktree ${state.current} is outside required root ${state.worktreeRoot}`; if (!state.branch) return "commits require a named branch, not a detached HEAD"; return null; }
38
38
 
39
39
  export async function runHook(event: HookEvent, root = process.cwd(), args: string[] = [], stdin = "") {