@afokapu/atdd-bun 0.10.99 → 0.10.100

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/integrity.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.10.99",
2
+ "version": "0.10.100",
3
3
  "files": {
4
4
  "HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
5
5
  "PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
@@ -1261,7 +1261,7 @@
1261
1261
  "src/cli.ts": "d93c3aebb78e752fd96347a5b177c0ed0697010c6e2533e116de93e772cd65a8",
1262
1262
  "src/docs-capability.ts": "bdeeb414649049b1cd4fa8008cada3a576318be51c43bec91bfa46a8e250c270",
1263
1263
  "src/enforce.ts": "e0c10519aeecd7080f2900ea230fad2f09482af47c1cee885d5de71573e29732",
1264
- "src/hooks.ts": "b96b5fee3a03c2f4c317d6725d6d21ed3133db18d0cdcdda72183cf05c7b6639",
1264
+ "src/hooks.ts": "8194c2dec14afc307b5ce9e90f64d8659210c579d8f427492823293f302e2876",
1265
1265
  "src/index.ts": "4081f8b08d1d3ee9e15a9964143e53d9cfb94a5616ab3e58a180d24cc16dc381",
1266
1266
  "src/integrity.ts": "f1ede7cb04924ae88e11917047277e264af6596b9759e381bd9c497cd34a797d",
1267
1267
  "src/journey-docs.ts": "994d229376244c84212cff951032823068a76565dd2fd427b9d44e341bd14853",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-bun",
3
- "version": "0.10.99",
3
+ "version": "0.10.100",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/afokapu/atdd-bun.git"
package/src/hooks.ts CHANGED
@@ -61,44 +61,49 @@ async function ratchetCommitGate(root: string, staged: string[], registryChanged
61
61
  try { candidateConfig = Bun.YAML.parse(stagedConfig.out) as Record<string, unknown>; policy = parseRatchetPolicy(candidateConfig); }
62
62
  catch (error) { return bad(`ratchet activation commit gate configuration is invalid: ${String(error)}`); }
63
63
  if (policy?.mode !== "reject-new" || !Array.isArray(candidateConfig.profiles) || !candidateConfig.profiles.every(profile => typeof profile === "string")) return null;
64
- if ((await git(root, ["diff", "--quiet"])).code !== 0 || (await git(root, ["ls-files", "--others", "--exclude-standard"])).out) return bad("ratchet activation commit gate requires no unstaged or untracked candidate state");
65
64
  const head = await git(root, ["rev-parse", "--verify", "HEAD^{commit}"]);
66
65
  if (head.code || !/^[0-9a-f]{40}$/i.test(head.out)) return bad("ratchet activation commit gate requires an exact committed HEAD base");
67
- const scratch = await mkdtemp(join(tmpdir(), "atdd-ratchet-commit-gate-"));
66
+ let scratch: string | undefined;
68
67
  try {
68
+ scratch = await mkdtemp(join(tmpdir(), "atdd-ratchet-commit-gate-"));
69
69
  const added = await git(root, ["worktree", "add", "--detach", "--no-checkout", scratch, head.out]);
70
- if (added.code) return bad(`could not materialize ratchet activation base: ${added.err || added.out}`);
70
+ if (added.code) throw new Error(`could not materialize ratchet activation base: ${added.err || added.out}`);
71
71
  const checkedOut = await git(scratch, ["checkout", "--detach", head.out]);
72
- if (checkedOut.code) return bad(`could not checkout ratchet activation base: ${checkedOut.err || checkedOut.out}`);
72
+ if (checkedOut.code) throw new Error(`could not checkout ratchet activation base: ${checkedOut.err || checkedOut.out}`);
73
73
  const baseConfig = await configAt(scratch), candidateProfiles = stable(candidateConfig.profiles.map(normalizeProfileName));
74
74
  const baseProfiles = stable(await enabledProfiles(scratch));
75
75
  const policyMatches = stable(policy.profiles.map(normalizeProfileName)).join(",") === candidateProfiles.join(",");
76
76
  const activation = isStrictProfileExpansion(baseProfiles, candidateProfiles) && activationContextDigest(baseConfig) === activationContextDigest(candidateConfig);
77
77
  const unchanged = baseProfiles.join(",") === candidateProfiles.join(",") && contextDigest(baseConfig, baseProfiles) === contextDigest(candidateConfig, candidateProfiles);
78
78
  if (candidateProfiles.some(profile => !concreteProfiles.includes(profile as typeof concreteProfiles[number])) || !policyMatches || (!activation && !unchanged)) return bad("ratchet reject-new requires either unchanged explicit profiles/context or an explicit monotonic profile expansion with identical non-profile context");
79
- const selectedProfiles = activation || staged.some(needsFullCommitRatchetProfiles) ? candidateProfiles : (await affectedProfiles(root, staged, registryChanged)).filter(profile => candidateProfiles.includes(profile));
80
79
  const tree = await git(root, ["write-tree"]);
81
80
  if (tree.code || !/^[0-9a-f]{40}$/i.test(tree.out)) return bad(`could not identify staged ratchet activation candidate: ${tree.err || tree.out}`);
82
81
  const baseTree = await git(root, ["rev-parse", "--verify", `${head.out}^{tree}`]);
83
82
  if (baseTree.code || !/^[0-9a-f]{40}$/i.test(baseTree.out)) return bad(`could not identify ratchet activation base tree: ${baseTree.err || baseTree.out}`);
84
- const normalize = (findings: Awaited<ReturnType<typeof enforce>>) => findings.map(finding => ({ ...finding, file: canonicalFindingPath(scratch, finding.file) }));
85
- // Scan and cache the unpatched HEAD before materializing the staged candidate; both keys remain exact Git trees.
86
- const cacheDirectory = await findingsCacheDirectory(root), toolkit = await toolkitIdentity().catch(() => null);
87
- const findingsAt = async (treeId: string) => cachedFindings(cacheDirectory, treeId, toolkit, selectedProfiles, async () => normalize(await enforce({ root: scratch, profiles: selectedProfiles as any })));
88
- const baseFindings = selectedProfiles.length ? await findingsAt(baseTree.out) : [];
89
- const patch = await git(root, ["diff", "--cached", "--binary"], undefined, true);
90
- if (patch.code) return bad(`could not read staged ratchet activation candidate: ${patch.err || patch.out}`);
91
- const applied = await git(scratch, ["apply", "--whitespace=nowarn"], patch.out);
92
- if (applied.code) return bad(`could not materialize staged ratchet activation candidate: ${applied.err || applied.out}`);
83
+ // Scan only exact Git trees in private scratch: live unstaged/untracked bytes never enter, and cache keys stay tree IDs.
84
+ const materialize = async (treeId: string, label: string) => {
85
+ const reset = await git(scratch!, ["read-tree", "--reset", "-u", treeId]);
86
+ if (reset.code) throw new Error(`could not materialize ${label}: ${reset.err || reset.out}`);
87
+ const exact = await git(scratch!, ["write-tree"]);
88
+ if (exact.code || exact.out !== treeId) throw new Error(`${label} did not materialize exactly: ${exact.err || exact.out}`);
89
+ };
90
+ await materialize(tree.out, "staged ratchet activation candidate");
93
91
  const materialized = await configAt(scratch), materializedProfiles = stable(await enabledProfiles(scratch));
94
92
  const materializedContext = activation ? activationContextDigest(materialized) === activationContextDigest(candidateConfig) : materializedProfiles.join(",") === candidateProfiles.join(",") && contextDigest(materialized, materializedProfiles) === contextDigest(candidateConfig, candidateProfiles);
95
93
  if (!materializedContext) return bad("staged ratchet candidate did not materialize exactly");
94
+ const selectedProfiles = activation || staged.some(needsFullCommitRatchetProfiles) ? candidateProfiles : (await affectedProfiles(scratch, staged, registryChanged)).filter(profile => candidateProfiles.includes(profile));
95
+ const normalize = (findings: Awaited<ReturnType<typeof enforce>>) => findings.map(finding => ({ ...finding, file: canonicalFindingPath(scratch!, finding.file) }));
96
+ const cacheDirectory = await findingsCacheDirectory(root), toolkit = await toolkitIdentity().catch(() => null);
97
+ const findingsAt = async (treeId: string) => cachedFindings(cacheDirectory, treeId, toolkit, selectedProfiles, async () => normalize(await enforce({ root: scratch, profiles: selectedProfiles as any })));
98
+ await materialize(baseTree.out, "ratchet activation base tree");
99
+ const baseFindings = selectedProfiles.length ? await findingsAt(baseTree.out) : [];
100
+ await materialize(tree.out, "staged ratchet activation candidate");
96
101
  const candidateFindings = selectedProfiles.length ? await findingsAt(tree.out) : [];
97
102
  const delta = compareFindings(baseFindings, candidateFindings);
98
103
  const report = JSON.stringify({ schema: "atdd-bun.ratchet-report/v1", mode: policy.mode, base: head.out, candidate: tree.out, profiles: selectedProfiles, context: contextDigest(candidateConfig, candidateProfiles), ...delta });
99
104
  return { ok: delta.new.length === 0, message: report };
100
105
  } catch (error) { return bad(`ratchet activation commit gate failed closed: ${String(error)}`); }
101
- finally { await git(root, ["worktree", "remove", "--force", scratch]); await rm(scratch, { recursive: true, force: true }); }
106
+ finally { if (scratch) { await git(root, ["worktree", "remove", "--force", scratch]); await rm(scratch, { recursive: true, force: true }); } }
102
107
  }
103
108
 
104
109
  /** A pushed reject-new candidate is judged from its immutable object IDs, never the hook's checkout or index. */