@afokapu/atdd-bun 0.10.33 → 0.10.35

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,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,12 @@ 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]
16
17
  - term_id: profile_registries
17
18
  text: >-
18
19
  A deterministic, profile-scoped projection of the detector manifests, convention paths, and
@@ -32,6 +33,7 @@ terms:
32
33
  tester: conventions/_profiles/tester.yaml
33
34
  topology: conventions/_profiles/topology.yaml
34
35
  traceability: conventions/_profiles/traceability.yaml
36
+ workflow: conventions/_profiles/workflow.yaml
35
37
  content:
36
38
  summary: >-
37
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.
@@ -39,8 +41,9 @@ content:
39
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.
40
42
  security joins with the first code, not at release: a secret committed during GREEN stays in history.
41
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. A legacy explicit delivery profile migrates by replacing delivery with workflow in the same profile list; that one-for-one replacement is not a policy loosening.
42
45
  fix_hint: |
43
- 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.
44
47
  exceptions:
45
48
  - >-
46
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,5 +1,5 @@
1
1
  {
2
- "version": "0.10.33",
2
+ "version": "0.10.35",
3
3
  "files": {
4
4
  "HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
5
5
  "PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
@@ -19,6 +19,7 @@
19
19
  "conventions/_profiles/tester.yaml": "348183eee5298cbb843031f6c41c3f9d0a725b11b3c66e154b47c9b340a72666",
20
20
  "conventions/_profiles/topology.yaml": "08df698c2e5419e8f7faa83f5f15c3ba9c085cc2ce72579624eb24962a6b8612",
21
21
  "conventions/_profiles/traceability.yaml": "adce93fb8392307685a01bec85e60534c59a9f4f0e4f8ea4767d79d22cf80878",
22
+ "conventions/_profiles/workflow.yaml": "162404ac861c77d1d4f4f2a8b731eb80f75677197636f96d198909661247b229",
22
23
  "conventions/atdd-bun.planner/atdd-bun.planner.acceptance-identity.convention.yaml": "81c5c773d5ee15e8d99a2c237aa9846b110b2533df45ebf407d0e3ec6ed3dd03",
23
24
  "conventions/atdd-bun.planner/atdd-bun.planner.identity-required.convention.yaml": "e96d7c1455d0c221072d82e7f2b55da1c4f9ceb17718eb6ed96affdeef37675b",
24
25
  "conventions/atdd-bun.planner/atdd-bun.planner.identity-unique.convention.yaml": "409ba1d035ed46bc45f3627b62fd3a6a5967d984cc4c071c41be3f3fa887c6d6",
@@ -27,7 +28,7 @@
27
28
  "conventions/atdd-bun.planner/atdd-bun.planner.reference-resolves.convention.yaml": "b96927d269692c29c02fee38ecbeb999480791f8dc7486c4072a9ff30183d7d6",
28
29
  "conventions/atdd-bun.planner/atdd-bun.planner.schema.convention.yaml": "16d0a26451758b759e6ca1f5b4f38ae1feff129688306ffcc2f70f6d3974b591",
29
30
  "conventions/atdd-bun.planner/atdd-bun.planner.train-wagon-resolves.convention.yaml": "6410e248351e701dcba38c671c07c4d8f6d67927984ed4d18e567abd1eb1f3f0",
30
- "conventions/atdd-bun.profiles/atdd-bun.profiles.presets.convention.yaml": "4f6f6367d6baa1c5f2eac8c686a7a0e309b3e6335fdaa5e6641f3cc43dbb97d0",
31
+ "conventions/atdd-bun.profiles/atdd-bun.profiles.presets.convention.yaml": "822c91fe1ec15ec0715a8ca2339c9379326f6a3e216829539b72ce5def272585",
31
32
  "conventions/atdd-bun.topology/atdd-bun.topology.e2e-location.convention.yaml": "3a3b991bc851b86d597636200f5cb10a0defda3920348860cdca28955ab91f4c",
32
33
  "conventions/atdd-bun.topology/atdd-bun.topology.feature-source-coverage.convention.yaml": "2b64093f1e7f1fe0e2edcadf12e560fa80cecec19536abaa5bf09af362c6694e",
33
34
  "conventions/atdd-bun.topology/atdd-bun.topology.feature-test-coverage.convention.yaml": "279be57399c9b79a1bd758414076e3053d67b7d4a7fcbbb950ea12d1201effeb",
@@ -1194,18 +1195,18 @@
1194
1195
  "src/ci.ts": "67e54d2cbfc44a9e42837d5e750af526d6255c539d3c452a4d5fca3226c0d9ac",
1195
1196
  "src/cli.ts": "cbc50f1e7fc7d0935f78230b7da6e232ed7ee6f04828e015c599fb6f6edcbfa2",
1196
1197
  "src/docs-capability.ts": "bdeeb414649049b1cd4fa8008cada3a576318be51c43bec91bfa46a8e250c270",
1197
- "src/enforce.ts": "736da6a864785137f3c2ef5b4f25060d2f4a1eba5d2872b7f4e7cd7395eb46de",
1198
+ "src/enforce.ts": "8b22a3ac30fdd35c9caa4c23f064498293a628a342d590fca1f2040ee3a8ea05",
1198
1199
  "src/hooks.ts": "0b4ca8c857e637bbd144e737e78eda924e3cba7b038c0abe5e6916d200d73068",
1199
1200
  "src/index.ts": "4081f8b08d1d3ee9e15a9964143e53d9cfb94a5616ab3e58a180d24cc16dc381",
1200
- "src/integrity.ts": "44ebe101933e8b1a42c563acdffcaf7a24baa4861eb8731a914bc51500d0a93d",
1201
+ "src/integrity.ts": "556976a42bae298df14b73675102d83b6bf4df0674d8e7211aad5315548f005f",
1201
1202
  "src/journey-docs.ts": "994d229376244c84212cff951032823068a76565dd2fd427b9d44e341bd14853",
1202
1203
  "src/planner-kernel.ts": "5d3f5305fb59becb2cc97d8a03aca6b104f385805d91cc0410856e289dd6fbb1",
1203
1204
  "src/planner-schema-validator.ts": "ce529c0936171075e93c7539b3655fe895a98dd2973ebda5775958e6ebc316d9",
1204
1205
  "src/planner-validators.ts": "99adeb65f579af923c11f21a8f55cfb9993b007fd8df59c5f764696b55379cfc",
1205
- "src/profile-registry.ts": "1c0d64172d2006a8d865bffbb47506684eb5f42b75df6308d28564dbb942088c",
1206
+ "src/profile-registry.ts": "716ef6e75e8e6eb14ffee37095b4fc9b38ddba2170fb84e7f06476863c3c02a5",
1206
1207
  "src/register.ts": "6a2f1c6b8df0dc9c8f45df8ec9fac2be29044e048b08c8ffe423410e94dadedd",
1207
1208
  "src/release.ts": "7b0fe444ee81f476f53b9c21b6c347ef588ee616c60211419cf984132be768ba",
1208
- "src/setup.ts": "19629b3c03e6d35b3e0e8b8a697a3c796d0e8cc4f9bac65875a26421573f3447",
1209
+ "src/setup.ts": "38233265afedc63499bbef3b5403889651add335a70efd3813254ba53f4fc4fe",
1209
1210
  "src/telemetry-plan.ts": "cdb1e8bfff414f3ea81977388973da2cf6d89567d9e060dc9d5989a33600f153",
1210
1211
  "src/topology.ts": "9779ee48e1c453abb76363163d4ab363463fa14e359571139f6d1f1fcc51ae2c",
1211
1212
  "templates/agents/AGENTS.block.md": "978ea89e1469f0a89a6ef6e4ac40bdc11ae856bb2754069743758ba204c395a1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-bun",
3
- "version": "0.10.33",
3
+ "version": "0.10.35",
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/enforce.ts CHANGED
@@ -4,7 +4,7 @@ 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
8
  export type ConcreteProfile = Exclude<Profile, "all">;
9
9
 
10
10
  export type Violation = {
@@ -38,15 +38,28 @@ export const profileImplementations: Record<ConcreteProfile, string[]> = {
38
38
  interlocking: ["bun_interlocking_binding", "bun_interlocking_coverage", "bun_interlocking_infrastructure"],
39
39
  htmx: ["htmx_hypermedia_detector", "htmx_tester_detector", "htmx_e2e_detector"],
40
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" }],
41
50
  };
42
51
 
43
52
  /** Every profile but `all`. */
44
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(", ")}`);
package/src/integrity.ts CHANGED
@@ -156,7 +156,10 @@ export function loosenedPolicy(base: Partial<HookPolicy> & { profiles?: unknown
156
156
  // (explicit → implicit → narrower would otherwise be a two-step bypass) is loosening a human approves.
157
157
  const before = explicitProfiles(base), after = explicitProfiles(current);
158
158
  if (before && !after) out.push(`profiles becomes implicit: the explicit list [${before.join(", ")}] was removed`);
159
- const dropped = before && after ? before.filter(name => !after.includes(name)) : [];
159
+ // delivery was retired in favour of the workflow companion. This is a one-way package migration,
160
+ // not a general escape hatch for removing profiles: its replacement must be explicit in the same
161
+ // policy change, and every other removed profile remains a loosening.
162
+ const dropped = before && after ? before.filter(name => !after.includes(name) && !(name === "delivery" && after.includes("workflow"))) : [];
160
163
  if (dropped.length) out.push(`profiles drops ${dropped.join(", ")}`);
161
164
  return out;
162
165
  }
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { join, relative, resolve } from "node:path";
4
- import { concreteProfiles, declaredRuleIds, profileImplementations } from "./enforce";
4
+ import { concreteProfiles, declaredRuleIds, profileConventions, profileImplementations } from "./enforce";
5
5
 
6
6
  export const PROFILE_REGISTRY_DIR = "conventions/_profiles";
7
7
  type Convention = { rule_id: string; path: string };
@@ -36,9 +36,9 @@ const scalar = (value: unknown) => value === null ? "null" : typeof value === "s
36
36
  const fields = (value: Record<string, unknown>, indent: string) => sorted(Object.entries(value), ([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${indent}${key}: ${scalar(entry)}`);
37
37
  const render = (profile: string, implementations: string[], conventions: Convention[], relationships: Relationship[]) => [
38
38
  'schema_version: "1.0.0"', `profile: ${scalar(profile)}`,
39
- "implementations:", ...implementations.map(implementation => ` - ${scalar(implementation)}`),
40
- "conventions:", ...conventions.flatMap(convention => [` - rule_id: ${scalar(convention.rule_id)}`, ` path: ${scalar(convention.path)}`]),
41
- "relationships:", ...relationships.flatMap(edge => { const entries = fields(edge, " "); return [` - ${entries[0]!.slice(4)}`, ...entries.slice(1)]; }), "",
39
+ implementations.length ? "implementations:" : "implementations: []", ...implementations.map(implementation => ` - ${scalar(implementation)}`),
40
+ conventions.length ? "conventions:" : "conventions: []", ...conventions.flatMap(convention => [` - rule_id: ${scalar(convention.rule_id)}`, ` path: ${scalar(convention.path)}`]),
41
+ relationships.length ? "relationships:" : "relationships: []", ...relationships.flatMap(edge => { const entries = fields(edge, " "); return [` - ${entries[0]!.slice(4)}`, ...entries.slice(1)]; }), "",
42
42
  ].join("\n");
43
43
 
44
44
  /** Deterministically project profiles, detector manifests, conventions, and direct graph edges into small registries. */
@@ -53,9 +53,13 @@ export async function renderProfileRegistries(root = resolve(import.meta.dir, ".
53
53
  const ruleIds = sorted(new Set(declared.flatMap(ids => [...ids])));
54
54
  const missing = ruleIds.filter(id => !conventions.has(id));
55
55
  if (missing.length) throw new Error(`${profile} declares rule IDs without exactly one convention: ${missing.join(", ")}`);
56
- const rules = new Set(ruleIds);
56
+ const supplementary = profileConventions[profile];
57
+ const duplicate = supplementary.find(convention => conventions.has(convention.rule_id) || ruleIds.includes(convention.rule_id));
58
+ if (duplicate) throw new Error(`${profile} duplicates convention ${duplicate.rule_id}`);
59
+ const selectedConventions = [...ruleIds.map(id => conventions.get(id)!), ...supplementary];
60
+ const rules = new Set(selectedConventions.map(convention => convention.rule_id));
57
61
  const relationships = sorted(graph.edges.filter(edge => rules.has(edge.source_ref) || rules.has(edge.target_ref)), (left, right) => relationshipKey(left).localeCompare(relationshipKey(right)));
58
- rendered.set(`${PROFILE_REGISTRY_DIR}/${profile}.yaml`, render(profile, implementations, ruleIds.map(id => conventions.get(id)!), relationships));
62
+ rendered.set(`${PROFILE_REGISTRY_DIR}/${profile}.yaml`, render(profile, implementations, selectedConventions, relationships));
59
63
  }
60
64
  return rendered;
61
65
  }
package/src/setup.ts CHANGED
@@ -2,7 +2,7 @@ import { ciInit, ciStatus } from "./ci";
2
2
  import { hooksStatus, installHooks } from "./hooks";
3
3
  import { agentInit, agentStatus } from "./agent";
4
4
  import { integrityInit, integrityStatus } from "./integrity";
5
- import { concreteProfiles } from "./enforce";
5
+ import { defaultProfiles } from "./enforce";
6
6
  import { existsSync } from "node:fs";
7
7
  import { writeFile } from "node:fs/promises";
8
8
  import { join } from "node:path";
@@ -14,7 +14,7 @@ import { join } from "node:path";
14
14
  export async function policyInit(root = process.cwd()) {
15
15
  const file = join(root, "atdd-bun.yaml");
16
16
  if (existsSync(file)) return { ok: true, message: `${file} kept` };
17
- await writeFile(file, `# Generated by atdd-bun init. The profiles this repository enforces; trim the list before the first commit to\n# adopt gradually. Once committed, removing a profile or the list is reported by the integrity check.\nprofiles: [${concreteProfiles.join(", ")}]\n`);
17
+ await writeFile(file, `# Generated by atdd-bun init. The profiles this repository enforces; trim the list before the first commit to\n# adopt gradually. Once committed, removing a profile or the list is reported by the integrity check.\nprofiles: [${defaultProfiles.join(", ")}]\n`);
18
18
  return { ok: true, message: file };
19
19
  }
20
20