@rmyndharis/aimhooman 0.1.2 → 0.1.3

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.
@@ -95,15 +95,15 @@
95
95
  },
96
96
  {
97
97
  "id": "attribution.generated-with",
98
- "version": 2,
98
+ "version": 3,
99
99
  "provider": "generic",
100
100
  "category": "ai-attribution",
101
101
  "confidence": "high",
102
102
  "kind": "message",
103
103
  "match": {
104
104
  "content": [
105
- "(?i)^\\s*generated\\s+(?:with|by)\\s+(?:claude(?:\\s+code)?|(?:github\\s+)?copilot|chatgpt|gpt(?:-[0-9]+(?:\\.[0-9]+)?)?|(?:openai\\s+)?codex|cursor)[.!]?\\s*$",
106
- "(?i)^\\s*(?:🤖\\s*)?generated\\s+with\\s+\\[(?:claude(?:\\s+code)?|(?:github\\s+)?copilot|chatgpt|gpt(?:-[0-9]+(?:\\.[0-9]+)?)?|(?:openai\\s+)?codex|cursor)\\]\\([^)]*\\)\\s*$"
105
+ "(?i)^\\s*(?:🤖\\s*)?generated\\s+(?:with|by)\\s+(?:claude|(?:github\\s+)?copilot|chatgpt|gpt(?:-[0-9]+(?:\\.[0-9]+)?)?|(?:openai\\s+)?codex|cursor)(?:\\s+[A-Za-z0-9.]+){0,3}[.!]?\\s*(?:🤖\\s*)?$",
106
+ "(?i)^\\s*(?:🤖\\s*)?generated\\s+with\\s+\\[(?:claude|(?:github\\s+)?copilot|chatgpt|gpt(?:-[0-9]+(?:\\.[0-9]+)?)?|(?:openai\\s+)?codex|cursor)(?:\\s+[A-Za-z0-9.]+){0,3}\\]\\([^)]*\\)\\s*(?:🤖\\s*)?$"
107
107
  ]
108
108
  },
109
109
  "actions": {
@@ -1,7 +1,7 @@
1
1
  [
2
2
  {
3
3
  "id": "marker.corner-cut",
4
- "version": 2,
4
+ "version": 3,
5
5
  "provider": "generic",
6
6
  "category": "ai-marker",
7
7
  "confidence": "medium",
@@ -14,19 +14,32 @@
14
14
  "**/docs/**",
15
15
  "**/test/**",
16
16
  "**/tests/**",
17
+ "**/__tests__/**",
18
+ "**/testdata/**",
17
19
  "**/fixtures/**",
18
20
  "**/__fixtures__/**",
19
21
  "**/vendor/**",
22
+ "**/vendored/**",
20
23
  "**/third_party/**",
21
24
  "**/node_modules/**",
22
25
  "**/dist/**",
23
26
  "**/build/**",
27
+ "out/**",
28
+ "target/**",
29
+ "**/.next/**",
24
30
  "**/coverage/**",
25
31
  "**/generated/**",
26
32
  "**/*.generated.*",
27
33
  "**/*.min.js",
28
34
  "**/*.map",
29
- "**/*.md"
35
+ "**/*.md",
36
+ "**/*.mdx",
37
+ "**/*.markdown",
38
+ "**/*.rst",
39
+ "**/*.test.*",
40
+ "**/*.spec.*",
41
+ "**/*_test.*",
42
+ "**/*_spec.*"
30
43
  ],
31
44
  "content": [
32
45
  "(?i)\\b(ponytail|caveman|yagni-oneliner):"
@@ -44,7 +57,7 @@
44
57
  },
45
58
  {
46
59
  "id": "marker.ai-authored",
47
- "version": 1,
60
+ "version": 2,
48
61
  "provider": "generic",
49
62
  "category": "ai-marker",
50
63
  "confidence": "low",
@@ -57,22 +70,35 @@
57
70
  "**/docs/**",
58
71
  "**/test/**",
59
72
  "**/tests/**",
73
+ "**/__tests__/**",
74
+ "**/testdata/**",
60
75
  "**/fixtures/**",
61
76
  "**/__fixtures__/**",
62
77
  "**/vendor/**",
78
+ "**/vendored/**",
63
79
  "**/third_party/**",
64
80
  "**/node_modules/**",
65
81
  "**/dist/**",
66
82
  "**/build/**",
83
+ "out/**",
84
+ "target/**",
85
+ "**/.next/**",
67
86
  "**/coverage/**",
68
87
  "**/generated/**",
69
88
  "**/*.generated.*",
70
89
  "**/*.min.js",
71
90
  "**/*.map",
72
- "**/*.md"
91
+ "**/*.md",
92
+ "**/*.mdx",
93
+ "**/*.markdown",
94
+ "**/*.rst",
95
+ "**/*.test.*",
96
+ "**/*.spec.*",
97
+ "**/*_test.*",
98
+ "**/*_spec.*"
73
99
  ],
74
100
  "content": [
75
- "(?i)\\b(ai[- ]?generated|generated by (ai|copilot|claude|chatgpt|codex))\\b"
101
+ "(?i)\\b(generated by (ai|copilot|claude|chatgpt|codex))\\b"
76
102
  ]
77
103
  },
78
104
  "actions": {
package/rules/paths.json CHANGED
@@ -1,12 +1,13 @@
1
1
  [
2
2
  {
3
3
  "id": "claude.local-settings",
4
- "version": 1,
4
+ "version": 2,
5
5
  "provider": "claude-code",
6
6
  "category": "local-settings",
7
7
  "confidence": "high",
8
8
  "kind": "path",
9
9
  "match": {
10
+ "path_case": "insensitive",
10
11
  "paths": [
11
12
  ".claude/settings.local.json",
12
13
  "**/.claude/settings.local.json"
@@ -24,17 +25,16 @@
24
25
  },
25
26
  {
26
27
  "id": "claude.session-state",
27
- "version": 1,
28
+ "version": 2,
28
29
  "provider": "claude-code",
29
30
  "category": "ephemeral-state",
30
31
  "confidence": "high",
31
32
  "kind": "path",
32
33
  "match": {
34
+ "path_case": "insensitive",
33
35
  "paths": [
34
36
  ".claude.json",
35
37
  "**/.claude.json",
36
- ".claude/.credentials.json",
37
- "**/.claude/.credentials.json",
38
38
  ".claude/session*.json",
39
39
  "**/.claude/session*.json",
40
40
  ".claude/history*",
@@ -64,12 +64,13 @@
64
64
  },
65
65
  {
66
66
  "id": "codex.session-state",
67
- "version": 1,
67
+ "version": 2,
68
68
  "provider": "codex",
69
69
  "category": "ephemeral-state",
70
70
  "confidence": "high",
71
71
  "kind": "path",
72
72
  "match": {
73
+ "path_case": "insensitive",
73
74
  "paths": [
74
75
  ".codex/sessions/**",
75
76
  "**/.codex/sessions/**",
@@ -93,12 +94,13 @@
93
94
  },
94
95
  {
95
96
  "id": "copilot.session-state",
96
- "version": 2,
97
+ "version": 3,
97
98
  "provider": "copilot",
98
99
  "category": "ephemeral-state",
99
100
  "confidence": "high",
100
101
  "kind": "path",
101
102
  "match": {
103
+ "path_case": "insensitive",
102
104
  "paths": [
103
105
  ".copilot/**",
104
106
  "**/.copilot/**"
@@ -116,12 +118,13 @@
116
118
  },
117
119
  {
118
120
  "id": "cursor.session-state",
119
- "version": 1,
121
+ "version": 2,
120
122
  "provider": "cursor",
121
123
  "category": "ephemeral-state",
122
124
  "confidence": "medium",
123
125
  "kind": "path",
124
126
  "match": {
127
+ "path_case": "insensitive",
125
128
  "paths": [
126
129
  ".cursor/session*",
127
130
  "**/.cursor/session*",
@@ -143,12 +146,13 @@
143
146
  },
144
147
  {
145
148
  "id": "aider.history",
146
- "version": 2,
149
+ "version": 3,
147
150
  "provider": "aider",
148
151
  "category": "ephemeral-state",
149
152
  "confidence": "high",
150
153
  "kind": "path",
151
154
  "match": {
155
+ "path_case": "insensitive",
152
156
  "paths": [
153
157
  ".aider.*",
154
158
  "**/.aider.*"
@@ -166,12 +170,13 @@
166
170
  },
167
171
  {
168
172
  "id": "specstory.history",
169
- "version": 1,
173
+ "version": 2,
170
174
  "provider": "specstory",
171
175
  "category": "ephemeral-state",
172
176
  "confidence": "medium",
173
177
  "kind": "path",
174
178
  "match": {
179
+ "path_case": "insensitive",
175
180
  "paths": [
176
181
  ".specstory/**",
177
182
  "**/.specstory/**"
@@ -189,12 +194,13 @@
189
194
  },
190
195
  {
191
196
  "id": "continue.sessions",
192
- "version": 1,
197
+ "version": 2,
193
198
  "provider": "continue",
194
199
  "category": "ephemeral-state",
195
200
  "confidence": "medium",
196
201
  "kind": "path",
197
202
  "match": {
203
+ "path_case": "insensitive",
198
204
  "paths": [
199
205
  ".continue/sessions/**",
200
206
  "**/.continue/sessions/**"
@@ -212,7 +218,7 @@
212
218
  },
213
219
  {
214
220
  "id": "secret.dotenv",
215
- "version": 2,
221
+ "version": 3,
216
222
  "provider": "generic",
217
223
  "category": "secret",
218
224
  "confidence": "high",
@@ -226,16 +232,11 @@
226
232
  "**/.env.*"
227
233
  ],
228
234
  "except": [
229
- ".env.example",
230
- "**/.env.example",
231
- ".env.sample",
232
- "**/.env.sample",
233
- ".env.template",
234
- "**/.env.template",
235
- ".env.dist",
236
- "**/.env.dist",
237
- ".env.defaults",
238
- "**/.env.defaults"
235
+ "**/*.example",
236
+ "**/*.sample",
237
+ "**/*.template",
238
+ "**/*.dist",
239
+ "**/*.defaults"
239
240
  ]
240
241
  },
241
242
  "actions": {
@@ -303,8 +304,33 @@
303
304
  ]
304
305
  },
305
306
  {
306
- "id": "generic.agent-instructions",
307
+ "id": "secret.claude-credentials",
307
308
  "version": 1,
309
+ "provider": "claude-code",
310
+ "category": "secret",
311
+ "confidence": "high",
312
+ "kind": "path",
313
+ "match": {
314
+ "path_case": "insensitive",
315
+ "paths": [
316
+ ".claude/.credentials.json",
317
+ "**/.claude/.credentials.json"
318
+ ]
319
+ },
320
+ "actions": {
321
+ "clean": "block",
322
+ "strict": "block",
323
+ "compliance": "block"
324
+ },
325
+ "reason": "This file holds an OAuth access token and must never be committed.",
326
+ "remediation": [
327
+ "git restore --staged <path>",
328
+ "Revoke the token if it already reached a commit."
329
+ ]
330
+ },
331
+ {
332
+ "id": "generic.agent-instructions",
333
+ "version": 2,
308
334
  "provider": "generic",
309
335
  "category": "ambiguous-instructions",
310
336
  "confidence": "medium",
@@ -328,18 +354,19 @@
328
354
  },
329
355
  "reason": "Agent instruction files may be intentional team config. Review before committing.",
330
356
  "remediation": [
331
- "aimhooman allow <path> --reason \"shared team config\"",
357
+ "aimhooman review <path> --head <commit> --reason \"shared team config\"",
332
358
  "or unstage if it is personal"
333
359
  ]
334
360
  },
335
361
  {
336
362
  "id": "generic.project-policy",
337
- "version": 1,
363
+ "version": 2,
338
364
  "provider": "aimhooman",
339
365
  "category": "policy-config",
340
366
  "confidence": "high",
341
367
  "kind": "path",
342
368
  "match": {
369
+ "path_case": "insensitive",
343
370
  "paths": [
344
371
  ".aimhooman.json",
345
372
  "**/.aimhooman.json"
@@ -352,54 +379,54 @@
352
379
  },
353
380
  "reason": "Versioned enforcement policy changes require explicit human review.",
354
381
  "remediation": [
355
- "aimhooman allow .aimhooman.json --reason \"reviewed team policy change\"",
382
+ "aimhooman review .aimhooman.json --head <commit> --reason \"reviewed team policy change\"",
356
383
  "or unstage the policy change"
357
384
  ]
358
385
  },
359
386
  {
360
387
  "id": "playwright-mcp.state",
361
- "version": 1,
388
+ "version": 2,
362
389
  "provider": "playwright-mcp",
363
390
  "category": "ephemeral-state",
364
391
  "confidence": "high",
365
392
  "kind": "path",
366
- "match": { "paths": [".playwright-mcp/**", "**/.playwright-mcp/**"] },
393
+ "match": { "path_case": "insensitive", "paths": [".playwright-mcp/**", "**/.playwright-mcp/**"] },
367
394
  "actions": { "clean": "block", "strict": "block", "compliance": "block" },
368
395
  "reason": "Playwright MCP session artifacts are local, not repository content.",
369
396
  "remediation": ["git restore --staged <path>"]
370
397
  },
371
398
  {
372
399
  "id": "remember.state",
373
- "version": 1,
400
+ "version": 2,
374
401
  "provider": "remember",
375
402
  "category": "ephemeral-state",
376
403
  "confidence": "high",
377
404
  "kind": "path",
378
- "match": { "paths": [".remember/**", "**/.remember/**"] },
405
+ "match": { "path_case": "insensitive", "paths": [".remember/**", "**/.remember/**"] },
379
406
  "actions": { "clean": "block", "strict": "block", "compliance": "block" },
380
407
  "reason": "Remember second-brain data is local, not repository content.",
381
408
  "remediation": ["git restore --staged <path>"]
382
409
  },
383
410
  {
384
411
  "id": "superpowers.state",
385
- "version": 1,
412
+ "version": 2,
386
413
  "provider": "superpowers",
387
414
  "category": "ephemeral-state",
388
415
  "confidence": "high",
389
416
  "kind": "path",
390
- "match": { "paths": [".superpowers/**", "**/.superpowers/**"] },
417
+ "match": { "path_case": "insensitive", "paths": [".superpowers/**", "**/.superpowers/**"] },
391
418
  "actions": { "clean": "block", "strict": "block", "compliance": "block" },
392
419
  "reason": "Superpowers plugin state is local, not repository content.",
393
420
  "remediation": ["git restore --staged <path>"]
394
421
  },
395
422
  {
396
423
  "id": "agent.state",
397
- "version": 2,
424
+ "version": 3,
398
425
  "provider": "generic",
399
426
  "category": "ephemeral-state",
400
427
  "confidence": "high",
401
428
  "kind": "path",
402
- "match": { "paths": [".agent/**", "**/.agent/**"] },
429
+ "match": { "path_case": "insensitive", "paths": [".agent/**", "**/.agent/**"] },
403
430
  "actions": { "clean": "block", "strict": "block", "compliance": "block" },
404
431
  "reason": "Generic agent state is local, not repository content.",
405
432
  "remediation": ["git restore --staged <path>"]
@@ -74,7 +74,8 @@
74
74
  "kind": "code",
75
75
  "match": {
76
76
  "content": [
77
- "\\b(?:gh[pousr]_[A-Za-z0-9]{36,255}|github_pat_[A-Za-z0-9_]{60,255}|glpat-[A-Za-z0-9_-]{20,255}|npm_[A-Za-z0-9]{36,255}|xox[baprs]-[A-Za-z0-9-]{20,255})\\b"
77
+ "\\b(?:gh[pousr]_[A-Za-z0-9]{36,255}|github_pat_[A-Za-z0-9_]{60,255}|glpat-[A-Za-z0-9_-]{20,255}|npm_[A-Za-z0-9]{36,255}|xox[baprs]-[A-Za-z0-9-]{20,255})\\b",
78
+ "\\b(?:sk-ant-api[0-9]{2}-[A-Za-z0-9_-]{80,255}|sk-proj-[A-Za-z0-9_-]{40,255}|AIza[A-Za-z0-9_-]{35}|(?:sk|rk)_live_[A-Za-z0-9]{20,255}|hf_[A-Za-z0-9]{34}|SG\\.[A-Za-z0-9_-]{20,255}\\.[A-Za-z0-9_-]{40,255})\\b"
78
79
  ]
79
80
  },
80
81
  "actions": {
@@ -17,7 +17,7 @@ This repository uses aimhooman to keep AI tooling artifacts out of Git history.
17
17
  Its policy:
18
18
 
19
19
  - Never stage or commit local AI session/state files. Examples include
20
- `.claude.json`, `.claude/.credentials.json`, `.claude/session*.json`,
20
+ `.claude.json`, `.claude/session*.json`,
21
21
  `.claude/history*`, `.claude/todos/*`, `.claude/shell-snapshots/*`,
22
22
  `.claude/statsig/*`, `.claude/projects/*`, `.claude/logs/*`,
23
23
  `.codex/sessions/*`, `.codex/history*`, `.codex/log/*`, `.codex/logs/*`,
@@ -27,8 +27,10 @@ Its policy:
27
27
  exhaustive; the packaged `rules/paths.json` is the detection source of truth.
28
28
  If one is staged, unstage it and keep it out of Git instead.
29
29
  - Never commit secrets: a real `.env` (not `.env.example`), private keys
30
- (`id_rsa` and files containing a private-key header), `.aws/credentials`, or
31
- service-account keys. Public certificates are allowed.
30
+ (`id_rsa` and files containing a private-key header), `.aws/credentials`,
31
+ `.claude/.credentials.json`, service-account keys, or a provider API key
32
+ (GitHub, GitLab, npm, Slack, Anthropic, OpenAI, Google, Stripe, Hugging Face,
33
+ SendGrid). Public certificates are allowed.
32
34
  - Never add AI attribution to commit messages: no `Co-authored-by` trailer naming
33
35
  an AI (Claude, Copilot, Codex), no "Generated with/by <AI>" lines, no AI-service
34
36
  noreply emails. A commit message reads as if a human wrote it.
@@ -290,7 +290,17 @@ export function withLock(lockPath, fn, options = {}) {
290
290
  if (attempt + 1 < retries) waitForLock(retryDelayMs);
291
291
  }
292
292
  if (!held) {
293
- throw new Error(`cannot acquire state lock "${lockPath}" after ${retries} attempts`);
293
+ // Name the queue, not just the lock. This scheme writes no file at lockPath;
294
+ // only the legacy holder checked above would, so a reader of this error
295
+ // usually goes looking for a path that is not there. What blocks is a
296
+ // candidate in the queue directory, retained because its owner cannot be
297
+ // disproved, and a holder killed outright never reaches the finally below
298
+ // that would have removed its own.
299
+ throw new Error(
300
+ `cannot acquire state lock "${lockPath}" after ${retries} attempts. Contenders `
301
+ + `queue in "${queueDir}"; if no other aimhooman command is running, a candidate `
302
+ + 'there outlived the process that published it, and removing that directory clears it',
303
+ );
294
304
  }
295
305
  return fn();
296
306
  } catch (error) {
package/src/gitx.mjs CHANGED
@@ -200,7 +200,7 @@ export function unmergedPaths(repo) {
200
200
  export function stagedRenameSources(repo, destinations) {
201
201
  const selected = new Set(destinations);
202
202
  if (!selected.size) return [];
203
- const entries = diffEntries(repo, ['--cached'], 1);
203
+ const entries = diffEntries(repo, ['--cached'], '1%');
204
204
  const sources = new Set(entries
205
205
  .filter((entry) => entry.status === 'R' && selected.has(entry.path) && entry.sourcePath)
206
206
  .map((entry) => entry.sourcePath));
@@ -210,7 +210,9 @@ export function stagedRenameSources(repo, destinations) {
210
210
  // there is no exact source to recover. When a blocked destination remains an
211
211
  // add even at the lowest similarity threshold, conservatively restore every
212
212
  // staged deletion. This may unstage an unrelated deletion, but it never lets
213
- // automatic repair silently commit half of a possible rename.
213
+ // automatic repair silently commit half of a possible rename. That threshold
214
+ // carries a % sign because git reads a bare 1 as the fraction 0.1, so -M1
215
+ // asks for 10% and leaves a 3%-similar rename here as an add plus a delete.
214
216
  if (entries.some((entry) => entry.status === 'A' && selected.has(entry.path))) {
215
217
  for (const entry of entries) {
216
218
  if (entry.status === 'D') sources.add(entry.path);
@@ -226,8 +228,13 @@ export function readStagedPath(repo, path) {
226
228
  const target = 'staged';
227
229
  let records;
228
230
  try {
231
+ // The --literal-pathspecs flag rather than :(top,literal) magic: magic
232
+ // parsing is disabled by GIT_LITERAL_PATHSPECS in the environment, which
233
+ // would leave the pathspec a literal filename no repository contains, so
234
+ // git matched nothing and the read reported the policy absent. cwd is
235
+ // already repo.root, which is what the top magic was for.
229
236
  records = nulStrings(gitBuf([
230
- 'ls-files', '--stage', '-z', '--', `:(top,literal)${path}`,
237
+ '--literal-pathspecs', 'ls-files', '--stage', '-z', '--', path,
231
238
  ], repo.root));
232
239
  } catch (error) {
233
240
  throw new GitTargetReadError(target, path, gitErrorDetail(error), error);
@@ -274,7 +281,7 @@ export function readCommitPath(repo, revision, path) {
274
281
  let records;
275
282
  try {
276
283
  records = nulStrings(gitBuf([
277
- 'ls-tree', '--full-tree', '-z', oid, '--', `:(top,literal)${path}`,
284
+ '--literal-pathspecs', 'ls-tree', '--full-tree', '-z', oid, '--', path,
278
285
  ], repo.root));
279
286
  } catch (error) {
280
287
  throw new GitTargetReadError(target, path, gitErrorDetail(error), error);
@@ -483,8 +490,13 @@ export function unstagePaths(repo, paths) {
483
490
  '--pathspec-from-file=-', '--pathspec-file-nul',
484
491
  ], opts);
485
492
  } else {
493
+ // -f waives only the check that the staged blob still matches the file
494
+ // on disk. With no HEAD that check has nothing else to pass against, so
495
+ // an artifact appended to between `git add` and `git commit` could never
496
+ // be unstaged. --cached leaves the worktree alone either way, and the
497
+ // sibling `restore --staged` branch enforces no equivalent check.
486
498
  execFileSync('git', [
487
- '--literal-pathspecs', 'rm', '--cached', '--quiet', '--ignore-unmatch',
499
+ '--literal-pathspecs', 'rm', '--cached', '-f', '--quiet', '--ignore-unmatch',
488
500
  '--pathspec-from-file=-', '--pathspec-file-nul',
489
501
  ], opts);
490
502
  }
@@ -13,6 +13,10 @@ function gitBuffer(repo, args, input) {
13
13
  encoding: 'buffer',
14
14
  maxBuffer: 128 * 1024 * 1024,
15
15
  timeout: GIT_TIMEOUT_MS,
16
+ // Same reason as gitBuf in gitx.mjs: without an explicit stdio,
17
+ // execFileSync echoes the child's stderr before it checks the exit
18
+ // status, so git's raw output reaches the terminal even on success.
19
+ stdio: [input === undefined ? 'ignore' : 'pipe', 'pipe', 'pipe'],
16
20
  ...(input === undefined ? {} : { input }),
17
21
  });
18
22
  }