@afokapu/atdd-bun 0.10.7 → 0.10.9

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,35 @@
1
+ schema_version: 1.1.0
2
+ rule_id: atdd-bun.profiles.presets
3
+ kind: policy
4
+ status: active
5
+ name: The agent proposes the profiles the work at hand needs, one preset at a time
6
+ statement: >-
7
+ The agent proposes profiles without being asked: when the work reaches a stage whose preset is not active yet (RED starts while only planner is on, or no profiles: list is set), it asks the user what they want to do and recommends the matching preset. It explains that presets are adopted gradually, each the set most consistent with that work, so nothing needs activating all at once. The user decides; the agent writes only what they choose. Presets accumulate: moving on adds profiles, it never swaps them, because removing one loosens the gate.
8
+ terms:
9
+ - term_id: presets
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. Delivering through reviewed tranches: add delivery.
12
+ values:
13
+ planning: [planner]
14
+ building: [coder, tester, traceability, security]
15
+ instrumenting: [telemetry]
16
+ delivering: [delivery]
17
+ content:
18
+ summary: >-
19
+ 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.
20
+ normative_text: |
21
+ 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.
22
+ security joins with the first code, not at release: a secret committed during GREEN stays in history.
23
+ coder already bundles the architecture, metrics, runtime, design and responsive detectors, so those need no later preset.
24
+ fix_hint: |
25
+ At a stage whose preset is missing, ask what the user wants to do (planning, building, instrumenting, delivering), recommend that preset and say it can be adopted step by step, then write the chosen list to profiles: in atdd-bun.yaml.
26
+ exceptions:
27
+ - >-
28
+ 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.
29
+ metadata:
30
+ aliases:
31
+ - ATDD-BUN-PROFILES-PRESETS
32
+ introduced_in: 0.10.9
33
+ # A policy, not a rule: which work a user is doing cannot be validated from the repository.
34
+ implementation:
35
+ type: none
@@ -18,7 +18,7 @@ content:
18
18
  Record the full or abbreviated SHA the final review named. After a rebase, run the final review again on the new head.
19
19
  exceptions:
20
20
  - >-
21
- At the gate, a record the change does not touch is not judged again: it was judged when it merged, and its tranche branch may be gone. Outside the gate every ready record is judged.
21
+ At the gate, a record the change does not touch is not judged again: it was judged when it merged, and its tranche branch may be gone. The pre-push hook scopes the same way, from where the branch left its base branch; with no base found it judges every record. Everywhere else every ready record is judged.
22
22
  - >-
23
23
  A shallow clone may lack the commit: CI must fetch full history (the generated workflow uses fetch-depth 0).
24
24
  metadata:
package/integrity.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.10.7",
2
+ "version": "0.10.9",
3
3
  "files": {
4
4
  "HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
5
5
  "PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
@@ -13,6 +13,7 @@
13
13
  "conventions/atdd-bun.planner/atdd-bun.planner.reference-resolves.convention.yaml": "b96927d269692c29c02fee38ecbeb999480791f8dc7486c4072a9ff30183d7d6",
14
14
  "conventions/atdd-bun.planner/atdd-bun.planner.schema.convention.yaml": "16d0a26451758b759e6ca1f5b4f38ae1feff129688306ffcc2f70f6d3974b591",
15
15
  "conventions/atdd-bun.planner/atdd-bun.planner.train-wagon-resolves.convention.yaml": "6410e248351e701dcba38c671c07c4d8f6d67927984ed4d18e567abd1eb1f3f0",
16
+ "conventions/atdd-bun.profiles/atdd-bun.profiles.presets.convention.yaml": "0db36d84018fb8d799db246c6e324575c586a575f28d76d891ca5054d558e5cd",
16
17
  "conventions/atdd-bun.topology/atdd-bun.topology.e2e-location.convention.yaml": "3a3b991bc851b86d597636200f5cb10a0defda3920348860cdca28955ab91f4c",
17
18
  "conventions/atdd-bun.topology/atdd-bun.topology.feature-source-coverage.convention.yaml": "2b64093f1e7f1fe0e2edcadf12e560fa80cecec19536abaa5bf09af362c6694e",
18
19
  "conventions/atdd-bun.topology/atdd-bun.topology.feature-test-coverage.convention.yaml": "279be57399c9b79a1bd758414076e3053d67b7d4a7fcbbb950ea12d1201effeb",
@@ -91,7 +92,7 @@
91
92
  "conventions/coder.htmx/coder.htmx.verb-endpoint-is-routed.convention.yaml": "d6aa58d35df4b7ebbcf675ca79f0177d64e56f095802b9a3eaae3e4befb5c270",
92
93
  "conventions/coder.htmx/coder.htmx.verb-endpoint-same-origin.convention.yaml": "2128d26153e2d1f19c65a1e1665d4f82c78395daedb336d6ef1256e82b74aedb",
93
94
  "conventions/coder.htmx/coder.htmx.verb-mutation-signals-progress.convention.yaml": "ae85c5ea01a09a3c41b939988088a8e5da91848d2710a1f0a1e0c86308638ac9",
94
- "conventions/delivery/delivery.approved-sha-resolves.convention.yaml": "12eadbc75b081e9469b9a0a7dbf3fdd30c8b34f66e647efa8bf2528119e61547",
95
+ "conventions/delivery/delivery.approved-sha-resolves.convention.yaml": "68f2ecc10870e780abb05bb31d211b1923d47b56672df4b3950f437aa9cf8ca8",
95
96
  "conventions/delivery/delivery.board.convention.yaml": "51ff4c16dddf20504f34accc5ddcd150108fab70e4e6f5bdb36cec71d0c905a8",
96
97
  "conventions/delivery/delivery.config-schema.convention.yaml": "4c7b449074a8bd9f89b70c702ab3e0df229105ddb4458772f894f0bf37813e9f",
97
98
  "conventions/delivery/delivery.evidence-schema.convention.yaml": "9c6a22495dfb3e2fcbca700392866c3b5660084b1f5594230eb320b499d5e5cb",
@@ -1189,16 +1190,16 @@
1189
1190
  "planner-schemas/train.schema.json": "d6fbac9067c1bffeb84eb79a71228441055af6e6e7ba141dd4e0bcb079345ec0",
1190
1191
  "planner-schemas/wagon.schema.json": "57b98f1ade998d745538eb3d72fb3014a50042006870f90bce47028eea336eb4",
1191
1192
  "planner-schemas/wmbt.schema.json": "7a3a5693bfcf01340667c2be4a3c48e74f34b1af7e8dc13372c0de06b7f4fe12",
1192
- "relationships.yaml": "97da2db334e3c8cd4f634ee8d12fe426aabc0f5c37508d02450ce8beca100f04",
1193
+ "relationships.yaml": "1cb713364aff54fb6e299e0737fe75a5d7b2e1dbd0a79b16b793aa1d269207b5",
1193
1194
  "src/agent.ts": "65e4579adb5a54977f557f8c783aba0e2a2a1d5cf3cea9f017b67ffe598b2f65",
1194
1195
  "src/board-ui.ts": "16a843705552f8f18e0e328e90e4a2a9148d771ca69f2708fe4a91302947dfad",
1195
1196
  "src/board.ts": "115c6cff12fb2b20e6471013161aef020a65331ecb97fa323e9b08f55053149f",
1196
1197
  "src/ci.ts": "67e54d2cbfc44a9e42837d5e750af526d6255c539d3c452a4d5fca3226c0d9ac",
1197
1198
  "src/cli.ts": "e45f2666334503a91eb65b1b5ee4dd8acab6b99f62d333e2ee2b65963db6358b",
1198
- "src/delivery.ts": "9a4500ae949faeef7bcfda114b7b09d4ac2b2cbb1218e68016829e0b6892847b",
1199
+ "src/delivery.ts": "5688356e26bf5aa49ad940d1bcd42e41b9f3dc1c21dc833b328fcb38a3f04fb8",
1199
1200
  "src/docs-capability.ts": "115cf27049a5cf19c133bac5ec237072be6f969a59a2ef6767e054108a21dc5c",
1200
1201
  "src/enforce.ts": "41a6f8c058a2a034996c817f224191403cc66ccc4ed4e04bbefc430014c783d8",
1201
- "src/hooks.ts": "9779eb48f9d0cb6234c33f6616f98dde6583d2ee03ac3c19f3ace2d156af26d0",
1202
+ "src/hooks.ts": "2feeec2674c1462c94256de02ce59ccc6e3ff671ec4dca1bf9903c32888b600b",
1202
1203
  "src/index.ts": "8ee4d7716794f6990ae8580e471b98256a0d0223dad063cd3b4542e598b6e744",
1203
1204
  "src/integrity.ts": "ba3de29e12e62a1b3e0149e243183fdfbce33d88efc9d39605b54996c01426cd",
1204
1205
  "src/journey-docs.ts": "994d229376244c84212cff951032823068a76565dd2fd427b9d44e341bd14853",
@@ -1210,7 +1211,7 @@
1210
1211
  "src/setup.ts": "04fe6dc4618c14b93580f36d9775b7deca2082b8cc4efab7668795053b7d8dd0",
1211
1212
  "src/telemetry-plan.ts": "cdb1e8bfff414f3ea81977388973da2cf6d89567d9e060dc9d5989a33600f153",
1212
1213
  "src/topology.ts": "9779ee48e1c453abb76363163d4ab363463fa14e359571139f6d1f1fcc51ae2c",
1213
- "templates/agents/AGENTS.block.md": "7e9687b1eff245b66da4127b336a9e20af2f5fa273e08895188c0a0dffebd77d",
1214
+ "templates/agents/AGENTS.block.md": "e3e49cb31294bff3d9fbfab9b0b600e690f80e11eccf96a1925e6b4ac21401ba",
1214
1215
  "templates/agents/atdd-bun.integrity.test.ts": "dec6f6e5f65a08d9512fc703b367c163f8c9da1f6aa7fd466835c1ec30348a41",
1215
1216
  "templates/agents/atdd/SKILL.md": "8f36d7d571aa17beb03df5e620ff837be73eb21a4e73dabd2c12188d2b378022",
1216
1217
  "templates/agents/delivery/SKILL.md": "0c4fa25303ada52db9c6b67d51a4011c15039e92c4f2947994ec9cba4d0c5efa",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-bun",
3
- "version": "0.10.7",
3
+ "version": "0.10.9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/afokapu/atdd-bun.git"
@@ -16,6 +16,7 @@ nodes:
16
16
  - atdd-bun.planner.reference-resolves
17
17
  - atdd-bun.planner.schema
18
18
  - atdd-bun.planner.train-wagon-resolves
19
+ - atdd-bun.profiles.presets
19
20
  - atdd-bun.topology.e2e-location
20
21
  - atdd-bun.topology.feature-source-coverage
21
22
  - atdd-bun.topology.feature-test-coverage
@@ -363,6 +364,16 @@ nodes:
363
364
  - traceability.source.tested-by-resolves
364
365
  - traceability.test.binding-resolves
365
366
  edges:
367
+ - confidence: 1
368
+ constraint: discretionary
369
+ control: internal
370
+ foundation: finish_to_start
371
+ reason: traceability joins with the building preset because executable-acceptance-has-test reports every acceptance until tests exist
372
+ source_ref: atdd-bun.profiles.presets
373
+ strength: minor
374
+ target_ref: traceability.plan.executable-acceptance-has-test
375
+ type: follows
376
+ origin: "@afokapu/atdd-bun"
366
377
  - confidence: 1
367
378
  constraint: mandatory
368
379
  control: internal
package/src/delivery.ts CHANGED
@@ -394,8 +394,10 @@ async function againstRef(root: string, ref: string): Promise<{ base: string; he
394
394
  return since && head ? { base: since, head } : null;
395
395
  }
396
396
 
397
- /** `gate: true` is the pre-merge gate; `false` disables it; unset reads ATDD_DELIVERY_GATE. */
398
- export type DeliveryOptions = { gate?: boolean | GateMode; base?: string };
397
+ /** `gate: true` is the pre-merge gate; `false` disables it; unset reads ATDD_DELIVERY_GATE. `since` (unset: reads
398
+ * ATDD_DELIVERY_SINCE, which only the pre-push hook sets) scopes a run outside the gate as the gate is scoped: a record
399
+ * unchanged since that commit is not judged again. An unresolvable commit scopes nothing, so every record is judged. */
400
+ export type DeliveryOptions = { gate?: boolean | GateMode; base?: string; since?: string };
399
401
 
400
402
  export async function validateDelivery(root = process.cwd(), options: DeliveryOptions = {}): Promise<PlanFinding[]> {
401
403
  const absolute = resolve(root), config = await readConfig(absolute);
@@ -434,9 +436,11 @@ export async function validateDelivery(root = process.cwd(), options: DeliveryOp
434
436
  const mode = options.gate === undefined ? gateMode() : options.gate === true ? "merge" : options.gate || null;
435
437
  // At the gate, a record the change does not touch was judged when it merged. It is not judged again, against a
436
438
  // later policy, schema or history: records are append-only and could never be repaired, so one tightening would
437
- // fail every later change. Outside the gate every record is judged. The gate itself still rejects any change to
438
- // an untouched record's folder (mergeGate).
439
- const onBase = mode ? (await gateRange(absolute, mode, options.base))?.base ?? null : null;
439
+ // fail every later change. Outside the gate every record is judged, unless a `since` commit scopes the run the same
440
+ // way (the pre-push hook). The gate itself still rejects any change to an untouched record's folder (mergeGate).
441
+ const since = options.since ?? process.env.ATDD_DELIVERY_SINCE;
442
+ const onBase = mode ? (await gateRange(absolute, mode, options.base))?.base ?? null
443
+ : since ? (await git(absolute, ["rev-parse", "--verify", "--quiet", `${since}^{commit}`])).out || null : null;
440
444
  // At the gate, like records, a stray the change does not touch was there when it merged and is not judged again.
441
445
  // A data file belongs only as a report some record in its tranche names; an unnamed one is authored content by another name.
442
446
  // Per tranche: a data file is a report only when a record in its own tranche names it.
package/src/hooks.ts CHANGED
@@ -36,13 +36,17 @@ export type WorktreeLayout = { primary: string; worktreeRoot: string; current: s
36
36
  async function layout(root: string, cfg: WorktreePolicy): Promise<WorktreeLayout | { error: string }> { const current = await realpath(resolve(root)), commonRoot = await primaryRoot(current); if (!commonRoot) return { error: "not inside a Git worktree" }; const primary = await realpath(commonRoot), expectedPrimary = join(dirname(primary), cfg.primary_directory); if (primary !== expectedPrimary) return { error: `primary checkout must be ${expectedPrimary}, not ${primary}` }; const branch = (await git(primary, ["symbolic-ref", "--quiet", "--short", "HEAD"])).out; if (branch !== cfg.primary_branch) return { error: `primary checkout ${primary} must be on ${cfg.primary_branch}, not ${branch || "a detached HEAD"}` }; return { primary, worktreeRoot: resolve(primary, cfg.root), current, branch: (await git(current, ["symbolic-ref", "--quiet", "--short", "HEAD"])).out }; }
37
37
  async function worktreeCommitPolicy(root: string, cfg: WorktreePolicy): Promise<string | null> { if (!cfg.enabled || !cfg.require_linked_worktree) return null; const state = await layout(root, cfg); if ("error" in state) return state.error; if (state.current === state.primary) return `commits must use a linked worktree beneath ${state.worktreeRoot}; run: atdd-bun worktree start <branch>`; if (!inside(state.worktreeRoot, state.current)) return `linked worktree ${state.current} is outside required root ${state.worktreeRoot}`; if (!state.branch) return "commits require a named branch, not a detached HEAD"; return null; }
38
38
 
39
+ /** Where the pushed branch left its base branch. A delivery record unchanged since then was judged when it merged, so
40
+ * pre-push does not judge it again, exactly as the merge gate does not; with no base found, every record is judged. */
41
+ async function pushBase(root: string, remote: string, cfg: HookPolicy): Promise<string | null> { for (const ref of [`${remote}/HEAD`, ...cfg.protected_branches.map(name => `${remote}/${name}`)]) { const found = await git(root, ["merge-base", "HEAD", ref]); if (!found.code && found.out) return found.out; } return null; }
42
+
39
43
  export async function runHook(event: HookEvent, root = process.cwd(), args: string[] = [], stdin = "") {
40
44
  const cfg = await policy(root), branch = (await git(root, ["symbolic-ref", "--quiet", "--short", "HEAD"])).out;
41
45
  if (["pre-commit", "pre-merge-commit"].includes(event) && cfg.protected_branches.includes(branch)) return bad(`protected branch ${branch} is blocked`);
42
46
  if (["pre-commit", "pre-merge-commit"].includes(event)) { const violation = await worktreeCommitPolicy(root, cfg.worktrees); if (violation) return bad(violation); }
43
47
  if (event === "pre-commit") { const staged = await files(root, ["diff", "--cached", "--name-only"]), registries = staged.filter(path => isRegistry(cfg, path)), generated = staged.filter(isGeneratedView), counted = staged.length - registries.length - generated.length, lines = (await stagedLineStats(root)).filter(row => !isRegistry(cfg, row.path) && !isGeneratedView(row.path)).reduce((n, row) => n + row.added + row.removed, 0); if (counted > cfg.max_staged_files) return bad(`staged files ${counted} exceed ${cfg.max_staged_files}`); if (lines > cfg.max_staged_changed_lines) return bad(`staged changed lines ${lines} exceed ${cfg.max_staged_changed_lines}`); return cfg.require_traceability || registries.length || generated.length ? validation(root, staged, false, registries.length > 0) : { ok: true, message: "ok" }; }
44
48
  if (event === "commit-msg") { const deleted = (await files(root, ["diff", "--cached", "--name-only", "--diff-filter=D"])).length, stats = await stagedStats(root), lines = stats.reduce((n, row) => n + row.removed, 0), registryRemoved = stats.filter(row => isRegistry(cfg, row.path)).reduce((n, row) => n + row.removed - row.added, 0), message = args[0] && existsSync(args[0]) ? await readFile(args[0], "utf8") : "", approved = message.includes("[mass-delete-approved]"); if ((deleted > 50 || lines > 10_000) && !approved) return bad("mass delete requires [mass-delete-approved]"); return registryRemoved > cfg.max_registry_removed_lines && !approved ? bad(`registry removal of ${registryRemoved} net lines exceeds ${cfg.max_registry_removed_lines}; requires [mass-delete-approved]`) : { ok: true, message: "ok" }; }
45
- if (event === "pre-push") { for (const row of stdin.split("\n").filter(Boolean).map(row => row.split(/\s+/))) { const [,,remote, remoteSha] = row, target = remote?.replace("refs/heads/", ""); if (target && cfg.protected_branches.includes(target)) return bad(`protected branch ${target} is blocked`); const local = row[1]; if (local && !/^0+$/.test(local)) { const range = !remoteSha || /^0+$/.test(remoteSha) ? `${local}^..${local}` : `${remoteSha}..${local}`, count = Number((await git(root, ["rev-list", "--count", range])).out); if (count > cfg.max_commits_per_push) return bad(`commits per push ${count} exceed ${cfg.max_commits_per_push}`); } } return validation(root, await files(root, ["diff", "--name-only", "HEAD~1..HEAD"]), true); }
49
+ if (event === "pre-push") { for (const row of stdin.split("\n").filter(Boolean).map(row => row.split(/\s+/))) { const [,,remote, remoteSha] = row, target = remote?.replace("refs/heads/", ""); if (target && cfg.protected_branches.includes(target)) return bad(`protected branch ${target} is blocked`); const local = row[1]; if (local && !/^0+$/.test(local)) { const range = !remoteSha || /^0+$/.test(remoteSha) ? `${local}^..${local}` : `${remoteSha}..${local}`, count = Number((await git(root, ["rev-list", "--count", range])).out); if (count > cfg.max_commits_per_push) return bad(`commits per push ${count} exceed ${cfg.max_commits_per_push}`); } } const base = await pushBase(root, args[0] || "origin", cfg), previous = process.env.ATDD_DELIVERY_SINCE; if (base) process.env.ATDD_DELIVERY_SINCE = base; else delete process.env.ATDD_DELIVERY_SINCE; try { return await validation(root, await files(root, ["diff", "--name-only", "HEAD~1..HEAD"]), true); } finally { if (previous === undefined) delete process.env.ATDD_DELIVERY_SINCE; else process.env.ATDD_DELIVERY_SINCE = previous; } }
46
50
  if (event === "post-commit") { const result = await validation(root, await files(root, ["show", "--pretty=format:", "--name-only", "HEAD"]), false); return { ok: true, message: result.ok ? result.message : `advisory: ${result.message}` }; }
47
51
  return { ok: true, message: "ok" };
48
52
  }
@@ -5,6 +5,8 @@ Before changing code, tests, or `plan/`, follow `.agents/skills/atdd/SKILL.md`:
5
5
 
6
6
  `atdd-bun.yaml` controls which profiles are active (`profiles:`; absent, every profile runs) and their settings. Turn a profile on or off only when the user asks. Adding one is always allowed; removing one loosens the gate, so the integrity check reports it until a human approves it on the base branch.
7
7
 
8
+ When the work reaches a stage whose profiles are not active yet, ask the user what they want to do and propose the matching preset from the package convention `atdd-bun.profiles.presets`.
9
+
8
10
  When `delivery` is active, deliver tranches through `.agents/skills/delivery/SKILL.md`: the reviews it requires are recorded under the delivery root and checked in CI.
9
11
 
10
12
  Never modify the toolkit itself (`node_modules/@afokapu/atdd-bun`, or the files atdd-bun generates); change only the configuration it offers, and loosen it only when the user asks. The integrity test and CI fail if the toolkit is modified.