@rmyndharis/aimhooman 0.1.4 → 0.1.5

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.
@@ -9,7 +9,7 @@
9
9
  "name": "aimhooman",
10
10
  "source": "./",
11
11
  "description": "Keeps AI session files, secrets, and unwanted attribution out at the agent and Git boundary.",
12
- "version": "0.1.4",
12
+ "version": "0.1.5",
13
13
  "license": "MIT",
14
14
  "keywords": [
15
15
  "git",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aimhooman",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "AI works. Hoomans ship. Keeps AI session files, secrets, and unwanted attribution out at the agent and Git boundary.",
5
5
  "author": {
6
6
  "name": "aimhooman"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aimhooman",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "AI works. Hoomans ship. Keeps AI session files, secrets, and unwanted attribution out at the agent and Git boundary.",
5
5
  "author": {
6
6
  "name": "aimhooman maintainers",
package/CHANGELOG.md CHANGED
@@ -5,6 +5,51 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.5] - 2026-07-18
9
+
10
+ ### Fixed
11
+
12
+ - AWS's own published example key no longer blocks a commit. 0.1.4 began matching bare
13
+ `AKIA…`/`ASIA…` access key IDs, which also matched `AKIAIOSFODNN7EXAMPLE` — the value AWS
14
+ prints in its own documentation, and one that lands in READMEs, Terraform samples, and test
15
+ fixtures. Because the rule's category is `secret`, neither a rule allow nor a plain path
16
+ allow is accepted, so a documentation example became an unconditional block on every profile
17
+ with only a per-file escape. Every AWS example key ends in `EXAMPLE`, so the pattern now
18
+ excludes that suffix and keeps blocking real keys. Path scoping was deliberately not used:
19
+ excluding `docs/` or `tests/` from secret scanning would hide genuine keys in the places they
20
+ most often leak.
21
+ - `uninstall --purge-state` no longer leaves a backup behind in a linked worktree. Git writes
22
+ the commit message file into the per-worktree Git directory, so 0.1.4's sweep — which looked
23
+ only in the common directory — printed "state purged" with `COMMIT_EDITMSG.aimhooman-bak`
24
+ still on disk. Uninstall disarms every worktree at once, so it now sweeps the main Git
25
+ directory and each linked one, and matches the `.aimhooman-bak` suffix so a backup left by a
26
+ merge goes with it.
27
+ - A plain `uninstall` keeps the attribution backup. It printed "state kept" and then deleted
28
+ the one file holding the lines stripped from the last commit message. Only `--purge-state`,
29
+ which promises to remove everything, takes it now.
30
+ - Sweeping an empty lock queue can no longer stop a concurrent `aimhooman init`. Between its
31
+ `mkdir` and its first publication a lock contender owns no file in the queue, so the sweep
32
+ saw an empty directory and removed it, and the contender then failed on a bare `ENOENT`. The
33
+ window is wide enough to lose because building the candidate probes the process identity,
34
+ which spawns `ps` on macOS and BSD. Publication now recreates the directory and retries once.
35
+ - The unstage summary no longer claims the files were "kept in your working tree". That is
36
+ false when a path was staged and then deleted before the commit, and when a staged deletion
37
+ is unstaged — the file is absent either way, though aimhooman never removed it. Unstaging
38
+ only ever touches the index, so the note says that instead of guessing where the file is.
39
+
40
+ ### Changed
41
+
42
+ - An ordinary commit no longer pays a Node cold start for a phase that does nothing. Git fires
43
+ `reference-transaction` twice per commit, and `refcheck` returns immediately for `committed`
44
+ and `aborted`, so the dispatcher now short-circuits those phases in the shell — after the
45
+ chained-hook call, so a chained hook still sees every phase. Measured locally, an ordinary
46
+ commit drops from about 870ms to about 710ms. The `prepared` scan, and the `--no-verify`
47
+ backstop that rides on it, are unchanged. A repository installed with an earlier release
48
+ keeps a valid dispatcher; re-run `aimhooman init` to pick up the faster one.
49
+ - The attribution backup no longer lingers indefinitely. `commit-msg` clears the previous run's
50
+ backup before it may write its own, so at most one exists at a time and a later clean commit
51
+ leaves none. The recovery window is now until your next commit rather than unbounded.
52
+
8
53
  ## [0.1.4] - 2026-07-18
9
54
 
10
55
  ### Fixed
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  <p align="center">
11
11
  <img src="https://img.shields.io/github/actions/workflow/status/rmyndharis/aimhooman/test.yml?branch=main&label=CI" alt="CI">
12
- <img src="https://img.shields.io/badge/version-v0.1.4-blue" alt="v0.1.4">
12
+ <img src="https://img.shields.io/badge/version-v0.1.5-blue" alt="v0.1.5">
13
13
  <img src="https://img.shields.io/badge/node-%E2%89%A522.8-339933?logo=node.js&logoColor=white" alt="Node 22.8+">
14
14
  <img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero dependencies">
15
15
  <img src="https://img.shields.io/badge/license-MIT-111111" alt="MIT">
package/bin/aimhooman.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { execFileSync } from 'node:child_process';
3
- import { chmodSync, lstatSync, readFileSync, rmdirSync, rmSync } from 'node:fs';
3
+ import { chmodSync, lstatSync, readdirSync, readFileSync, rmdirSync, rmSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { GIT_TIMEOUT_MS } from '../src/git-environment.mjs';
@@ -372,7 +372,7 @@ function cmdPrecommit(args) {
372
372
  emptied = stagedBefore !== null
373
373
  && stagedBefore.every((path) => unstageTargets.has(path));
374
374
  process.stderr.write(
375
- `aimhooman: unstaged ${paths.length} file(s) from this commit: ${paths.map(visible).join(', ')}${emptied ? ' — nothing else was staged, so the commit is stopped rather than left empty' : ''}\n`
375
+ `aimhooman: unstaged ${paths.length} file(s) from this commit: ${paths.map(visible).join(', ')} (index only; nothing on disk was deleted)${emptied ? ' — nothing else was staged, so the commit is stopped rather than left empty' : ''}\n`
376
376
  );
377
377
  } catch (e) {
378
378
  process.stderr.write(
@@ -409,6 +409,10 @@ function cmdCommitmsg(args) {
409
409
  const file = positionals[0];
410
410
  const repo = tryRepo();
411
411
  if (!repo) { console.error('aimhooman: not a git repository'); return 30; }
412
+ // A .aimhooman-bak from an earlier commit went stale the moment that commit
413
+ // finished, and git reuses this message path. Clear the previous run's backup
414
+ // now, before this run may write its own, so at most one ever lingers.
415
+ try { rmSync(`${file}.aimhooman-bak`, { force: true }); } catch { /* best effort */ }
412
416
  // Frictionless profiles (clean/compliance) never cancel a commit merely
413
417
  // because the message file cannot be read; only strict fails closed.
414
418
  let hookProfile = 'clean';
@@ -1696,9 +1700,29 @@ function cmdUninstall(args) {
1696
1700
  ]) {
1697
1701
  try { rmdirSync(queue); } catch { /* held by another aimhooman, or already gone */ }
1698
1702
  }
1699
- // A one-commit-ago attribution backup that git's next COMMIT_EDITMSG makes stale.
1700
- try { rmSync(join(repo.commonDir, 'COMMIT_EDITMSG.aimhooman-bak'), { force: true }); }
1701
- catch { /* nothing to remove */ }
1703
+ // The attribution backup is the user's only copy of the lines stripped from
1704
+ // their last message — commit-msg already clears the previous one, so what
1705
+ // survives here is current, not stale. A plain uninstall says "state kept"
1706
+ // and must not delete it; only --purge-state, which promises to remove
1707
+ // everything, sweeps it. Git names the message file per operation
1708
+ // (COMMIT_EDITMSG, MERGE_MSG, ...) and the backup inherits that name, so
1709
+ // match the suffix rather than one filename. It lands beside that file, in
1710
+ // the per-worktree git directory rather than the common one, and uninstall
1711
+ // disarms every worktree at once — so cover the main one and each linked.
1712
+ if (purge) {
1713
+ const messageDirs = [repo.commonDir];
1714
+ try {
1715
+ const linked = join(repo.commonDir, 'worktrees');
1716
+ for (const name of readdirSync(linked)) messageDirs.push(join(linked, name));
1717
+ } catch { /* no linked worktrees */ }
1718
+ for (const dir of messageDirs) {
1719
+ try {
1720
+ for (const name of readdirSync(dir)) {
1721
+ if (name.endsWith('.aimhooman-bak')) rmSync(join(dir, name), { force: true });
1722
+ }
1723
+ } catch { /* directory missing or unreadable */ }
1724
+ }
1725
+ }
1702
1726
  return exitStatus;
1703
1727
  }
1704
1728
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmyndharis/aimhooman",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "AI works. Hoomans ship. Keep AI session files, secrets, and attribution out of your commits.",
5
5
  "homepage": "https://github.com/rmyndharis/aimhooman#readme",
6
6
  "repository": {
@@ -53,7 +53,7 @@
53
53
  "match": {
54
54
  "content": [
55
55
  "(?i)\\baws_(?:secret_access_key|session_token)\\b\\s*[:=]\\s*[\\\"']?[A-Za-z0-9/+=]{32,}(?![A-Za-z0-9/+=])",
56
- "\\b(?:AKIA|ASIA)[A-Z0-9]{16}\\b"
56
+ "\\b(?:AKIA|ASIA)(?![A-Z0-9]{9}EXAMPLE\\b)[A-Z0-9]{16}\\b"
57
57
  ]
58
58
  },
59
59
  "actions": {
@@ -259,18 +259,34 @@ export function withLock(lockPath, fn, options = {}) {
259
259
  let held = false;
260
260
  let published = false;
261
261
  let primaryError = null;
262
+ // Between the mkdir above and the first publication below this contender owns
263
+ // no file in the queue, so a concurrent cleanup that removes empty queue
264
+ // directories — `aimhooman uninstall` sweeps them — can delete it out from
265
+ // under us. The gap is wide enough to lose: building the candidate probes the
266
+ // process identity, which spawns `ps` on macOS and BSD. Recreate and retry
267
+ // once rather than failing the caller with a bare ENOENT. Once a candidate is
268
+ // published the directory is no longer empty, so no cleanup can remove it.
269
+ const publish = () => {
270
+ try {
271
+ publishCandidate(candidatePath, candidate, options.candidateOperations);
272
+ } catch (error) {
273
+ if (error?.code !== 'ENOENT') throw error;
274
+ mkdirSync(queueDir, { recursive: true });
275
+ publishCandidate(candidatePath, candidate, options.candidateOperations);
276
+ }
277
+ };
262
278
  try {
263
279
  // Own the UUID pathname before publication begins. If the rename lands
264
280
  // but its directory fsync fails, finally still removes the candidate;
265
281
  // unlinking ENOENT is harmless when publication failed earlier.
266
282
  published = true;
267
- publishCandidate(candidatePath, candidate, options.candidateOperations);
283
+ publish();
268
284
  const observed = queueCandidates(queueDir, staleMs);
269
285
  candidate.ticket = observed.reduce((maximum, peer) => (
270
286
  peer.ticket === null ? maximum : Math.max(maximum, peer.ticket)
271
287
  ), 0) + 1;
272
288
  candidate.choosing = false;
273
- publishCandidate(candidatePath, candidate, options.candidateOperations);
289
+ publish();
274
290
 
275
291
  for (let attempt = 0; attempt < retries; attempt += 1) {
276
292
  // A file at the pre-queue lock path may belong to a concurrently
package/src/githooks.mjs CHANGED
@@ -642,6 +642,14 @@ function hookScript(name, cmd, cliPath, chainedPath) {
642
642
  const aimInvocation = name === 'reference-transaction'
643
643
  ? `printf '%s\\n' "$AIMHOOMAN_REF_UPDATES" | run_aimhooman ${aimCommand} || exit $?`
644
644
  : `run_aimhooman ${aimCommand} || exit $?`;
645
+ // committed/aborted fire only after refs are locked in, and refcheck can do
646
+ // nothing but return 0 for them (see cmdRefcheck). Short-circuit in the shell
647
+ // so an ordinary commit no longer pays a Node cold start for the committed
648
+ // phase. Placed after the chained-hook call, so a chained hook still sees
649
+ // every phase.
650
+ const phaseShortCircuit = name === 'reference-transaction'
651
+ ? 'case "$1" in committed|aborted) exit 0 ;; esac\n'
652
+ : '';
645
653
  const template = `#!/bin/sh -p
646
654
  ${MARKER} (${name})
647
655
  # aimhooman-hook-version: ${HOOK_FORMAT_VERSION}
@@ -691,7 +699,7 @@ fi
691
699
  if [ -x "$CHAINED" ]; then
692
700
  ${chainedInvocation}
693
701
  fi
694
- ${aimInvocation}
702
+ ${phaseShortCircuit}${aimInvocation}
695
703
  `;
696
704
  const fingerprint = hookFingerprint(template);
697
705
  return template.replace(FINGERPRINT_PLACEHOLDER, fingerprint);