@afokapu/atdd-bun 0.10.1 → 0.10.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 +4 -0
- package/integrity.json +4 -4
- package/package.json +1 -1
- package/src/hooks.ts +3 -3
- package/src/integrity.ts +2 -2
package/README.md
CHANGED
|
@@ -156,6 +156,10 @@ recorded work. Independence now defaults to `different-model`: a repository that
|
|
|
156
156
|
`fresh-process` default sets it explicitly. A repository with no `stages:` gets the new default
|
|
157
157
|
operating model, without the integrity check reporting the change.
|
|
158
158
|
|
|
159
|
+
From 0.10.2 the pre-commit hook no longer counts uncommitted work: it refused exactly the small
|
|
160
|
+
commits that reduce it. `max_uncommitted_files` is no longer read; commit size stays limited by
|
|
161
|
+
`max_staged_files` and `max_staged_changed_lines`.
|
|
162
|
+
|
|
159
163
|
Every key is optional; these are the defaults:
|
|
160
164
|
|
|
161
165
|
```yaml
|
package/integrity.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.10.
|
|
2
|
+
"version": "0.10.2",
|
|
3
3
|
"files": {
|
|
4
4
|
"HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
|
|
5
5
|
"PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
|
|
6
|
-
"README.md": "
|
|
6
|
+
"README.md": "763767126dd67f0f0ed0a3955cc6c707370f79b60739cab05773f41b1d37fa97",
|
|
7
7
|
"bunfig.toml": "b9fc65eca9014c5179380259d70e76385a6a80788fa9a2df5fb3eaa5554fd2fe",
|
|
8
8
|
"conventions/atdd-bun.planner/atdd-bun.planner.acceptance-identity.convention.yaml": "81c5c773d5ee15e8d99a2c237aa9846b110b2533df45ebf407d0e3ec6ed3dd03",
|
|
9
9
|
"conventions/atdd-bun.planner/atdd-bun.planner.identity-required.convention.yaml": "e96d7c1455d0c221072d82e7f2b55da1c4f9ceb17718eb6ed96affdeef37675b",
|
|
@@ -1198,9 +1198,9 @@
|
|
|
1198
1198
|
"src/delivery.ts": "f78cb15e1eabeca1492bd8f57b2029370d9c2dbc3d3fb7d25ed3a18ea9de87cb",
|
|
1199
1199
|
"src/docs-capability.ts": "115cf27049a5cf19c133bac5ec237072be6f969a59a2ef6767e054108a21dc5c",
|
|
1200
1200
|
"src/enforce.ts": "41a6f8c058a2a034996c817f224191403cc66ccc4ed4e04bbefc430014c783d8",
|
|
1201
|
-
"src/hooks.ts": "
|
|
1201
|
+
"src/hooks.ts": "9779eb48f9d0cb6234c33f6616f98dde6583d2ee03ac3c19f3ace2d156af26d0",
|
|
1202
1202
|
"src/index.ts": "8ee4d7716794f6990ae8580e471b98256a0d0223dad063cd3b4542e598b6e744",
|
|
1203
|
-
"src/integrity.ts": "
|
|
1203
|
+
"src/integrity.ts": "ba3de29e12e62a1b3e0149e243183fdfbce33d88efc9d39605b54996c01426cd",
|
|
1204
1204
|
"src/journey-docs.ts": "994d229376244c84212cff951032823068a76565dd2fd427b9d44e341bd14853",
|
|
1205
1205
|
"src/planner-kernel.ts": "5d3f5305fb59becb2cc97d8a03aca6b104f385805d91cc0410856e289dd6fbb1",
|
|
1206
1206
|
"src/planner-schema-validator.ts": "ce529c0936171075e93c7539b3655fe895a98dd2973ebda5775958e6ebc316d9",
|
package/package.json
CHANGED
package/src/hooks.ts
CHANGED
|
@@ -6,9 +6,9 @@ import { topologyFor } from "./topology";
|
|
|
6
6
|
import { JOURNEY_DOCS_DIR } from "./journey-docs";
|
|
7
7
|
|
|
8
8
|
export type WorktreePolicy = { enabled: boolean; root: string; primary_directory: string; primary_branch: string; require_linked_worktree: boolean };
|
|
9
|
-
export type HookPolicy = { max_staged_files: number; max_staged_changed_lines: number;
|
|
9
|
+
export type HookPolicy = { max_staged_files: number; max_staged_changed_lines: number; max_commits_per_push: number; max_registry_removed_lines: number; registry_paths: string[]; protected_branches: string[]; require_plan_reference: boolean; require_traceability: boolean; worktrees: WorktreePolicy };
|
|
10
10
|
export const defaultWorktreePolicy: WorktreePolicy = { enabled: false, root: "../worktrees", primary_directory: "main", primary_branch: "main", require_linked_worktree: true };
|
|
11
|
-
export const defaultHookPolicy: HookPolicy = { max_staged_files: 20, max_staged_changed_lines: 350,
|
|
11
|
+
export const defaultHookPolicy: HookPolicy = { max_staged_files: 20, max_staged_changed_lines: 350, max_commits_per_push: 10, max_registry_removed_lines: 350, registry_paths: ["plan/_*.yaml", "plan/_*.yml", "contracts/_*.yaml", "contracts/_*.yml"], protected_branches: ["main", "master"], require_plan_reference: true, require_traceability: true, worktrees: defaultWorktreePolicy };
|
|
12
12
|
export const hookEvents = ["pre-commit", "commit-msg", "pre-push", "pre-merge-commit", "post-commit", "post-merge"] as const;
|
|
13
13
|
export type HookEvent = typeof hookEvents[number];
|
|
14
14
|
const git = async (root: string, args: string[], input?: string) => { const child = Bun.spawn({ cmd: ["git", ...args], cwd: root, stdin: input ? new Blob([input]) : undefined, stdout: "pipe", stderr: "pipe" }); return { code: await child.exited, out: (await new Response(child.stdout).text()).trim(), err: (await new Response(child.stderr).text()).trim() }; };
|
|
@@ -40,7 +40,7 @@ export async function runHook(event: HookEvent, root = process.cwd(), args: stri
|
|
|
40
40
|
const cfg = await policy(root), branch = (await git(root, ["symbolic-ref", "--quiet", "--short", "HEAD"])).out;
|
|
41
41
|
if (["pre-commit", "pre-merge-commit"].includes(event) && cfg.protected_branches.includes(branch)) return bad(`protected branch ${branch} is blocked`);
|
|
42
42
|
if (["pre-commit", "pre-merge-commit"].includes(event)) { const violation = await worktreeCommitPolicy(root, cfg.worktrees); if (violation) return bad(violation); }
|
|
43
|
-
if (event === "pre-commit") { const staged = await files(root, ["diff", "--cached", "--name-only"]),
|
|
43
|
+
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
44
|
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
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); }
|
|
46
46
|
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}` }; }
|
package/src/integrity.ts
CHANGED
|
@@ -132,7 +132,7 @@ function withoutNulls(config: Record<string, unknown>): Record<string, unknown>
|
|
|
132
132
|
* where a list is expected) or compare them as the defaults. An empty or null document is the default policy, and fine. */
|
|
133
133
|
export function policyShapeErrors(config: Record<string, unknown>): string[] {
|
|
134
134
|
const out: string[] = [];
|
|
135
|
-
for (const key of ["max_staged_files", "max_staged_changed_lines", "
|
|
135
|
+
for (const key of ["max_staged_files", "max_staged_changed_lines", "max_commits_per_push", "max_registry_removed_lines"])
|
|
136
136
|
if (config[key] !== undefined && !(typeof config[key] === "number" && Number.isFinite(config[key]))) out.push(`${key} must be a finite number`);
|
|
137
137
|
for (const key of ["require_plan_reference", "require_traceability"]) if (config[key] !== undefined && typeof config[key] !== "boolean") out.push(`${key} must be true or false`);
|
|
138
138
|
for (const key of ["protected_branches", "registry_paths"]) if (config[key] !== undefined && !(Array.isArray(config[key]) && (config[key] as unknown[]).every(item => typeof item === "string"))) out.push(`${key} must be a list of strings`);
|
|
@@ -152,7 +152,7 @@ export function policyShapeErrors(config: Record<string, unknown>): string[] {
|
|
|
152
152
|
export function loosenedPolicy(base: Partial<HookPolicy> & { profiles?: unknown; delivery?: unknown }, current: Partial<HookPolicy> & { profiles?: unknown; delivery?: unknown }): string[] {
|
|
153
153
|
const b = { ...defaultHookPolicy, ...base, worktrees: { ...defaultHookPolicy.worktrees, ...base.worktrees } }, c = { ...defaultHookPolicy, ...current, worktrees: { ...defaultHookPolicy.worktrees, ...current.worktrees } };
|
|
154
154
|
const out: string[] = [];
|
|
155
|
-
for (const key of ["max_staged_files", "max_staged_changed_lines", "
|
|
155
|
+
for (const key of ["max_staged_files", "max_staged_changed_lines", "max_commits_per_push", "max_registry_removed_lines"] as const) if (Number(c[key]) > Number(b[key])) out.push(`${key} ${b[key]} → ${c[key]}`);
|
|
156
156
|
for (const key of ["require_plan_reference", "require_traceability"] as const) if (b[key] && !c[key]) out.push(`${key} true → false`);
|
|
157
157
|
for (const key of ["enabled", "require_linked_worktree"] as const) if (b.worktrees[key] && !c.worktrees[key]) out.push(`worktrees.${key} true → false`);
|
|
158
158
|
const removed = b.protected_branches.filter(x => !c.protected_branches.includes(x)), added = c.registry_paths.filter(x => !b.registry_paths.includes(x));
|