@afokapu/atdd-bun 0.1.1 → 0.1.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 CHANGED
@@ -137,16 +137,18 @@ bun run atdd-bun init
137
137
  ```
138
138
 
139
139
  It creates `.githooks/` dispatchers, sets a worktree-local `core.hooksPath`,
140
- and generates `.github/workflows/atdd-bun.yml` when it is absent. It never
141
- overwrites another hook path or an existing generated workflow unless you
140
+ generates `.github/workflows/atdd-bun.yml`, and writes the coding-agent skill
141
+ `.claude/skills/atdd/SKILL.md` when they are absent. It never overwrites another
142
+ hook path, an existing generated workflow, or an existing skill unless you
142
143
  explicitly pass `--replace`. Installing the dependency alone deliberately does
143
144
  neither: package installation must not mutate a repository through postinstall.
144
145
 
145
- Use `hooks install` or `ci init` when only one surface is wanted:
146
+ Use `hooks install`, `ci init`, or `agent init` when only one surface is wanted:
146
147
 
147
148
  ```sh
148
149
  bun run atdd-bun hooks install
149
150
  bun run atdd-bun ci init
151
+ bun run atdd-bun agent init
150
152
  ```
151
153
 
152
154
  The hooks enforce protected-branch blocking, micro-commit limits, mass-delete
@@ -161,6 +163,16 @@ bun run atdd-bun hooks uninstall
161
163
  Hooks can be bypassed by Git and therefore are never the merge gate. The CI
162
164
  workflow and GitHub branch ruleset are the authority for merging.
163
165
 
166
+ ## Agent skill: the lifecycle in the agent's context
167
+
168
+ `agent init` writes `.claude/skills/atdd/SKILL.md`, a short skill that coding
169
+ agents load before changing code, tests, or `plan/`. It names the lifecycle
170
+ PLAN → RED → GREEN → SMOKE → REFACTOR → TRACE, the conventions each stage
171
+ follows, and the `atdd-bun` profile that gates it. It points at the conventions
172
+ shipped in this package instead of restating them, so it stays correct as they
173
+ change; after upgrading, refresh it with `bun run atdd-bun agent init --replace`.
174
+ The skill steers the agent; the profiles, hooks, and CI remain the enforcement.
175
+
164
176
  ## CI: the merge gate
165
177
 
166
178
  Generate the repository-owned workflow with:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-bun",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/afokapu/atdd-bun.git"
package/src/agent.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+
5
+ const root = resolve(import.meta.dir, "..");
6
+ const template = join(root, "templates/claude/atdd/SKILL.md");
7
+ const version = (await Bun.file(join(root, "package.json")).json() as { version: string }).version;
8
+ const skillDir = (repo: string) => join(repo, ".claude/skills/atdd");
9
+ export async function agentInit(repo = process.cwd(), replace = false) { const output = join(skillDir(repo), "SKILL.md"); if (existsSync(output) && !replace) return { ok: false, message: `${output} exists; use --replace` }; await mkdir(skillDir(repo), { recursive: true }); await writeFile(output, (await readFile(template, "utf8")).replace("{{VERSION}}", version)); return { ok: true, message: output }; }
10
+ export async function agentStatus(repo = process.cwd()) { const output = join(skillDir(repo), "SKILL.md"); return { ok: existsSync(output), message: output }; }
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 two opt-in local surfaces without touching unrelated
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
- return { ok: true, message: `hooks: ${hooks.message}\nCI workflow: ${ci.message}` };
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,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.