@afokapu/atdd-bun 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -484
- package/conventions/atdd-bun.planner/atdd-bun.planner.acceptance-identity.convention.yaml +27 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.identity-required.convention.yaml +28 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.identity-unique.convention.yaml +27 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.interlocking-participant-resolves.convention.yaml +28 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.parse.convention.yaml +27 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.reference-resolves.convention.yaml +28 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.schema.convention.yaml +30 -0
- package/conventions/atdd-bun.planner/atdd-bun.planner.train-wagon-resolves.convention.yaml +26 -0
- package/conventions/atdd-bun.topology/atdd-bun.topology.e2e-location.convention.yaml +38 -0
- package/conventions/atdd-bun.topology/atdd-bun.topology.feature-source-coverage.convention.yaml +30 -0
- package/conventions/atdd-bun.topology/atdd-bun.topology.feature-test-coverage.convention.yaml +29 -0
- package/conventions/atdd-bun.topology/atdd-bun.topology.plan-location.convention.yaml +31 -0
- package/conventions/atdd-bun.topology/atdd-bun.topology.source-location.convention.yaml +33 -0
- package/conventions/atdd-bun.topology/atdd-bun.topology.test-location.convention.yaml +34 -0
- package/detectors/atdd_topology/atdd.implementation.yaml +3 -0
- package/detectors/atdd_topology/detect.mjs +14 -3
- package/detectors/atdd_topology/fixtures/clean/e2e/journeys/checkout.journey.test.ts +1 -0
- package/detectors/atdd_topology/fixtures/clean/plan/orders/E001.yaml +2 -0
- package/detectors/atdd_traceability_closure/detect.mjs +31 -16
- package/detectors/planner_docs_capability/atdd.implementation.yaml +7 -0
- package/detectors/planner_plan_integrity/atdd.implementation.yaml +1 -0
- package/detectors/planner_plan_integrity/detect.mjs +4 -1
- package/detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/_interlockings/checkout.yaml +6 -0
- package/detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/_interlockings/unnamed.yaml +2 -0
- package/detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/dup-a.yaml +7 -0
- package/detectors/planner_plan_integrity/fixtures/dirty/plan/_trains/dup-b.yaml +6 -0
- package/detectors/planner_plan_integrity/fixtures/dirty/plan/orders/E001.yaml +5 -0
- package/detectors/planner_schema_validation/fixtures/dirty/plan/_trains/missing-description.yaml +5 -0
- package/integrity.json +39 -19
- package/package.json +1 -1
- package/planner-nodes/ENFORCEMENT_SCOPE.yaml +11 -1
- package/relationships.yaml +224 -0
- package/src/agent.ts +15 -8
- package/src/docs-capability.ts +3 -0
- package/src/enforce.ts +48 -2
- package/src/hooks.ts +2 -2
- package/src/integrity.ts +13 -5
- package/src/planner-kernel.ts +2 -1
- package/templates/agents/AGENTS.block.md +5 -1
package/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
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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
|
|
package/src/planner-kernel.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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 -->
|