@afokapu/atdd-bun 0.1.1 → 0.1.3
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 +23 -4
- package/package.json +1 -1
- package/src/agent.ts +31 -0
- package/src/cli.ts +6 -0
- package/src/setup.ts +7 -2
- package/templates/agents/AGENTS.block.md +5 -0
- package/templates/agents/atdd/SKILL.md +16 -0
package/README.md
CHANGED
|
@@ -137,16 +137,17 @@ bun run atdd-bun init
|
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
It creates `.githooks/` dispatchers, sets a worktree-local `core.hooksPath`,
|
|
140
|
-
|
|
141
|
-
overwrites another hook path
|
|
142
|
-
explicitly pass `--replace`. Installing the dependency alone deliberately does
|
|
140
|
+
generates `.github/workflows/atdd-bun.yml`, and installs the coding-agent skill
|
|
141
|
+
when they are absent. It never overwrites another hook path, an existing
|
|
142
|
+
generated workflow, or an existing skill unless you explicitly pass `--replace`. Installing the dependency alone deliberately does
|
|
143
143
|
neither: package installation must not mutate a repository through postinstall.
|
|
144
144
|
|
|
145
|
-
Use `hooks install` or `
|
|
145
|
+
Use `hooks install`, `ci init`, or `agent init` when only one surface is wanted:
|
|
146
146
|
|
|
147
147
|
```sh
|
|
148
148
|
bun run atdd-bun hooks install
|
|
149
149
|
bun run atdd-bun ci init
|
|
150
|
+
bun run atdd-bun agent init
|
|
150
151
|
```
|
|
151
152
|
|
|
152
153
|
The hooks enforce protected-branch blocking, micro-commit limits, mass-delete
|
|
@@ -161,6 +162,24 @@ bun run atdd-bun hooks uninstall
|
|
|
161
162
|
Hooks can be bypassed by Git and therefore are never the merge gate. The CI
|
|
162
163
|
workflow and GitHub branch ruleset are the authority for merging.
|
|
163
164
|
|
|
165
|
+
## Agent skill: the lifecycle in the agent's context
|
|
166
|
+
|
|
167
|
+
`agent init` gives every coding agent the same short ATDD skill:
|
|
168
|
+
|
|
169
|
+
- `.agents/skills/atdd/SKILL.md`: the vendor-neutral Agent Skills path (Codex,
|
|
170
|
+
GitHub Copilot, Cursor, Gemini CLI, and others);
|
|
171
|
+
- `.claude/skills/atdd/SKILL.md`: Claude Code;
|
|
172
|
+
- a managed `<!-- atdd-bun:start -->` block in `AGENTS.md` pointing at the skill,
|
|
173
|
+
for agents that read `AGENTS.md` but not skills. The rest of `AGENTS.md` is
|
|
174
|
+
never touched.
|
|
175
|
+
|
|
176
|
+
The skill names the lifecycle PLAN → RED → GREEN → SMOKE → REFACTOR → TRACE, the
|
|
177
|
+
conventions each stage follows, and the `atdd-bun` profile that gates it. It
|
|
178
|
+
points at the conventions shipped in this package instead of restating them, so
|
|
179
|
+
it stays correct as they change; after upgrading, refresh it with
|
|
180
|
+
`bun run atdd-bun agent init --replace`. The skill steers the agent; the
|
|
181
|
+
profiles, hooks, and CI remain the enforcement.
|
|
182
|
+
|
|
164
183
|
## CI: the merge gate
|
|
165
184
|
|
|
166
185
|
Generate the repository-owned workflow with:
|
package/package.json
CHANGED
package/src/agent.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
const root = resolve(import.meta.dir, "..");
|
|
6
|
+
const version = (await Bun.file(join(root, "package.json")).json() as { version: string }).version;
|
|
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"];
|
|
10
|
+
const block = /<!-- atdd-bun:start[\s\S]*?<!-- atdd-bun:end -->\n?/;
|
|
11
|
+
|
|
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. */
|
|
14
|
+
export async function agentInit(repo = process.cwd(), replace = false) {
|
|
15
|
+
const written: string[] = [], kept: string[] = [];
|
|
16
|
+
for (const path of skillPaths) {
|
|
17
|
+
const output = join(repo, path);
|
|
18
|
+
if (existsSync(output) && !replace) { kept.push(output); continue; }
|
|
19
|
+
await mkdir(dirname(output), { recursive: true }); await writeFile(output, await render("atdd/SKILL.md")); written.push(output);
|
|
20
|
+
}
|
|
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); }
|
|
24
|
+
if (!written.length) return { ok: false, message: `${kept.join(", ")} exist; use --replace` };
|
|
25
|
+
return { ok: true, message: written.join("\n") };
|
|
26
|
+
}
|
|
27
|
+
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") };
|
|
31
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { enforce, profileNames, type Profile } from "./enforce";
|
|
3
3
|
import { finishWorktree, hookEvents, hooksStatus, installHooks, runHook, startWorktree, uninstallHooks, worktreeStatus } from "./hooks";
|
|
4
4
|
import { ciInit, ciStatus } from "./ci";
|
|
5
|
+
import { agentInit, agentStatus } from "./agent";
|
|
5
6
|
import { releaseCheck } from "./release";
|
|
6
7
|
import { initializeRepository } from "./setup";
|
|
7
8
|
|
|
@@ -14,6 +15,7 @@ const usage = {
|
|
|
14
15
|
"atdd-bun hooks <install|uninstall|status> [--replace]",
|
|
15
16
|
"atdd-bun worktree <start|finish|status>",
|
|
16
17
|
"atdd-bun ci <init|status> [--replace]",
|
|
18
|
+
"atdd-bun agent <init|status> [--replace]",
|
|
17
19
|
"atdd-bun release check",
|
|
18
20
|
],
|
|
19
21
|
profiles: profileNames,
|
|
@@ -64,6 +66,10 @@ if (args[0] === "ci") {
|
|
|
64
66
|
const result = args[1] === "init" ? await ciInit(process.cwd(), args.includes("--replace")) : args[1] === "status" ? await ciStatus() : fail("ci requires init or status");
|
|
65
67
|
console[result.ok ? "log" : "error"](result.message); process.exit(result.ok ? 0 : 1);
|
|
66
68
|
}
|
|
69
|
+
if (args[0] === "agent") {
|
|
70
|
+
const result = args[1] === "init" ? await agentInit(process.cwd(), args.includes("--replace")) : args[1] === "status" ? await agentStatus() : fail("agent requires init or status");
|
|
71
|
+
console[result.ok ? "log" : "error"](result.message); process.exit(result.ok ? 0 : 1);
|
|
72
|
+
}
|
|
67
73
|
if (args[0] === "release") {
|
|
68
74
|
if (args[1] !== "check") fail("release requires check");
|
|
69
75
|
const result = await releaseCheck(); console[result.ok ? "log" : "error"](result.message); process.exit(result.ok ? 0 : 1);
|
package/src/setup.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ciInit, ciStatus } from "./ci";
|
|
2
2
|
import { hooksStatus, installHooks } from "./hooks";
|
|
3
|
+
import { agentInit, agentStatus } from "./agent";
|
|
3
4
|
|
|
4
|
-
/** Install the package's
|
|
5
|
+
/** Install the package's three opt-in local surfaces without touching unrelated
|
|
5
6
|
* workflows or hook paths. Dependency installation itself never calls this. */
|
|
6
7
|
export async function initializeRepository(root = process.cwd(), replace = false) {
|
|
7
8
|
const existingHooks = await hooksStatus(root);
|
|
@@ -11,5 +12,9 @@ export async function initializeRepository(root = process.cwd(), replace = false
|
|
|
11
12
|
const existingCi = await ciStatus(root);
|
|
12
13
|
const ci = replace || !existingCi.ok ? await ciInit(root, replace) : existingCi;
|
|
13
14
|
if (!ci.ok) return { ok: false, message: `CI workflow: ${ci.message}` };
|
|
14
|
-
|
|
15
|
+
|
|
16
|
+
const existingAgent = await agentStatus(root);
|
|
17
|
+
const agent = replace || !existingAgent.ok ? await agentInit(root, replace) : existingAgent;
|
|
18
|
+
if (!agent.ok) return { ok: false, message: `agent skill: ${agent.message}` };
|
|
19
|
+
return { ok: true, message: `hooks: ${hooks.message}\nCI workflow: ${ci.message}\nagent skill: ${agent.message}` };
|
|
15
20
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- atdd-bun:start — generated by @afokapu/atdd-bun {{VERSION}}; run `atdd-bun agent init --replace` to refresh -->
|
|
2
|
+
## ATDD lifecycle
|
|
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.
|
|
5
|
+
<!-- atdd-bun:end -->
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: atdd
|
|
3
|
+
description: Use before writing or changing code, tests, or plan/ files in this repository. New intent is decomposed and delivered through the ATDD lifecycle PLAN → RED → GREEN → SMOKE → REFACTOR → TRACE, each stage gated by atdd-bun. Skip PLAN only when the change fits an acceptance that already exists.
|
|
4
|
+
---
|
|
5
|
+
<!-- Generated by @afokapu/atdd-bun {{VERSION}}. Do not edit; run `atdd-bun agent init --replace`. -->
|
|
6
|
+
|
|
7
|
+
Conventions live in `node_modules/@afokapu/atdd-bun/` (`planner-nodes/nodes/`, `conventions/`). Read the ones a stage names; do not restate them. Finish each stage by passing its gate before starting the next.
|
|
8
|
+
|
|
9
|
+
1. PLAN — Decompose the intent into wagon → WMBT → acceptance → train/interlocking → journey → contract under `plan/`, following `planner.decomposition.*`; every WMBT declares a SMOKE acceptance. Gate: `bun run atdd-bun planner`.
|
|
10
|
+
2. RED — For each acceptance, write a test headed `// URN: test:{wagon}:{feature}:{ACC-ID}` and `// Phase: RED` that fails for the missing behaviour (`tester.bun.red-*`). Gate: `bun run atdd-bun tester`.
|
|
11
|
+
3. GREEN — Write the least code that passes; each source file carries `URN: component:{wagon}:{feature}:{Name}:{side}:{layer}` and a `Tested-By:` block (`coder.bun.green-*`). Gate: `bun test`.
|
|
12
|
+
4. SMOKE — Prove the SMOKE acceptance through the real entry point with no mocks or spies, asserting only on observable output: HTTP, markup, stdout, exit code (`tester.bun.smoke-*`, `planner.smoke.*`). Gate: `bun run atdd-bun tester`.
|
|
13
|
+
5. REFACTOR — With tests green, reduce complexity, fix layering, and remove security faults until the rules pass, without changing behaviour (`coder.bun.complexity-*`, `quality-*`, `composition-*`, `security-*`). Gate: `bun run atdd-bun coder security`.
|
|
14
|
+
6. TRACE — Every acceptance has a test, every test resolves to a declared acceptance, every source file resolves to its tests. Gate: `bun run atdd-bun traceability`, then `bun run atdd-bun all`.
|
|
15
|
+
|
|
16
|
+
When a gate fails, open `<rule_id>.convention.yaml` for the reported rule ID and fix the artifact. Never skip, suppress, or edit a convention to get green.
|