@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
package/src/integrity.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { dirname, join, relative, resolve, sep } from "node:path";
4
+ import { instructionPaths } from "./agent";
5
+ import { concreteProfiles } from "./enforce";
4
6
  import { defaultHookPolicy, type HookPolicy } from "./hooks";
5
7
 
6
8
  /**
@@ -95,15 +97,17 @@ async function checkGenerated(root: string, packageRoot: string): Promise<Integr
95
97
  await same(WORKFLOW, "templates/github/atdd-bun.yml", "bun run atdd-bun ci init --replace");
96
98
  for (const skill of SKILLS) await same(skill, "templates/agents/atdd/SKILL.md", "bun run atdd-bun agent init --replace");
97
99
  await same(relative(root, await testFilePath(root)), "templates/agents/atdd-bun.integrity.test.ts", "bun run atdd-bun integrity init --replace");
98
- const agents = existsSync(join(root, "AGENTS.md")) ? await readFile(join(root, "AGENTS.md"), "utf8") : "";
99
- const block = agents.match(BLOCK)?.[0], canonical = (await readFile(join(packageRoot, "templates/agents/AGENTS.block.md"), "utf8")).match(BLOCK)![0];
100
- if (!block) findings.push({ file: "AGENTS.md", detail: "is missing the atdd-bun block", restore: "bun run atdd-bun agent init --replace" });
101
- else if (unstamp(block) !== unstamp(canonical)) findings.push({ file: "AGENTS.md", detail: "atdd-bun block was edited", restore: "bun run atdd-bun agent init --replace" });
100
+ const canonical = (await readFile(join(packageRoot, "templates/agents/AGENTS.block.md"), "utf8")).match(BLOCK)![0];
101
+ for (const file of instructionPaths) {
102
+ const agents = existsSync(join(root, file)) ? await readFile(join(root, file), "utf8") : "", block = agents.match(BLOCK)?.[0];
103
+ if (!block) findings.push({ file, detail: "is missing the atdd-bun block", restore: "bun run atdd-bun agent init --replace" });
104
+ else if (unstamp(block) !== unstamp(canonical)) findings.push({ file, detail: "atdd-bun block was edited", restore: "bun run atdd-bun agent init --replace" });
105
+ }
102
106
  return findings;
103
107
  }
104
108
 
105
109
  /** Names of the policy fields in `current` that are looser than in `base`. */
106
- export function loosenedPolicy(base: Partial<HookPolicy>, current: Partial<HookPolicy>): string[] {
110
+ export function loosenedPolicy(base: Partial<HookPolicy> & { profiles?: unknown }, current: Partial<HookPolicy> & { profiles?: unknown }): string[] {
107
111
  const b = { ...defaultHookPolicy, ...base, worktrees: { ...defaultHookPolicy.worktrees, ...base.worktrees } }, c = { ...defaultHookPolicy, ...current, worktrees: { ...defaultHookPolicy.worktrees, ...current.worktrees } };
108
112
  const out: string[] = [];
109
113
  for (const key of ["max_staged_files", "max_staged_changed_lines", "max_uncommitted_files", "max_commits_per_push", "max_registry_removed_lines"] as const) if (Number(c[key]) > Number(b[key])) out.push(`${key} ${b[key]} → ${c[key]}`);
@@ -112,6 +116,10 @@ export function loosenedPolicy(base: Partial<HookPolicy>, current: Partial<HookP
112
116
  const removed = b.protected_branches.filter(x => !c.protected_branches.includes(x)), added = c.registry_paths.filter(x => !b.registry_paths.includes(x));
113
117
  if (removed.length) out.push(`protected_branches drops ${removed.join(", ")}`);
114
118
  if (added.length) out.push(`registry_paths adds ${added.join(", ")}`);
119
+ // Deactivating a profile stops enforcing it; the operator may do it, as a change a human approves.
120
+ const active = (config: { profiles?: unknown }): string[] => Array.isArray(config.profiles) ? config.profiles.map(String) : concreteProfiles;
121
+ const dropped = active(base).filter(name => !active(current).includes(name));
122
+ if (dropped.length) out.push(`profiles drops ${dropped.join(", ")}`);
115
123
  return out;
116
124
  }
117
125
 
@@ -17,6 +17,7 @@ async function walk(root: string): Promise<string[]> {
17
17
  const entries = await readdir(root, { withFileTypes: true });
18
18
  return (await Promise.all(entries.map(async entry => {
19
19
  const path = join(root, entry.name);
20
+ if (entry.isDirectory() && entry.name === "_generated") return [];
20
21
  return entry.isDirectory() ? walk(path) : entry.isFile() && /\.(?:yaml|yml|json)$/.test(entry.name) ? [path] : [];
21
22
  }))).flat().sort();
22
23
  }
@@ -97,7 +98,7 @@ export async function validatePlan(root = process.cwd()): Promise<PlanGraph> {
97
98
  const references = structuralRefs(artifact);
98
99
  for (const reference of new Set(references)) if (!known.has(reference) && !reference.startsWith("contract:")) graph.findings.push(finding("planner.kernel.reference-resolves", artifact.file, `${artifact.id} references undeclared ${reference}`));
99
100
  if (artifact.kind === "train") for (const wagon of values(artifact.data.wagons)) { const reference = `wagon:${id(wagon)}`; if (id(wagon) && !known.has(reference)) graph.findings.push(finding("planner.kernel.train-wagon-resolves", artifact.file, `${artifact.id} lists undeclared ${reference}`)); }
100
- if (artifact.kind === "interlocking") for (const participant of [...values(artifact.data.participants), ...values(artifact.data.lifelines).map(value => value && typeof value === "object" ? (value as Record<string, unknown>).ref : value)]) { const reference = id(participant); if (reference.startsWith("wagon:") && !known.has(reference)) graph.findings.push(finding("planner.kernel.interlocking-participant-resolves", artifact.file, `${artifact.id} lists undeclared ${reference}`)); }
101
+ if (artifact.kind === "interlocking") for (const participant of [...values(artifact.data.participants), ...values(artifact.data.lifelines).map(value => value && typeof value === "object" ? (value as Record<string, unknown>).ref : value)]) { const reference = id(participant); if (reference && !/^(user|system):/.test(reference) && !known.has(reference)) graph.findings.push(finding("planner.kernel.interlocking-participant-resolves", artifact.file, `${artifact.id} lists undeclared ${reference}`)); }
101
102
  }
102
103
  return graph;
103
104
  }
@@ -1,5 +1,9 @@
1
1
  <!-- atdd-bun:start — generated by @afokapu/atdd-bun {{VERSION}}; run `atdd-bun agent init --replace` to refresh -->
2
2
  ## ATDD lifecycle
3
3
 
4
- Before changing code, tests, or `plan/`, follow `.agents/skills/atdd/SKILL.md`: PLAN → RED → GREEN → SMOKE → REFACTOR → TRACE, passing each stage's `atdd-bun` gate before starting the next. Never edit `node_modules/@afokapu/atdd-bun` or atdd-bun's generated files, and never loosen `atdd-bun.yaml`; the integrity test and CI fail if you do.
4
+ Before changing code, tests, or `plan/`, follow `.agents/skills/atdd/SKILL.md`: PLAN → RED → GREEN → SMOKE → REFACTOR → TRACE, passing each stage's `atdd-bun` gate before starting the next.
5
+
6
+ Capabilities can be enabled gradually: a greenfield repository runs every profile; a brownfield one lists the profiles it enforces in `atdd-bun.yaml` (`profiles:`), chosen by the operator.
7
+
8
+ Never modify the toolkit itself (`node_modules/@afokapu/atdd-bun`, or the files atdd-bun generates); change only the configuration it offers, and never to loosen it. The integrity test and CI fail if you do.
5
9
  <!-- atdd-bun:end -->