claude-dev-env 8.74.0 → 8.74.1

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.
@@ -25,10 +25,15 @@ if _hooks_directory not in sys.path:
25
25
  from hooks_constants.followup_ledger_constants import (
26
26
  ABSENT_ORIGIN_COMMIT,
27
27
  ALL_FOLLOWUP_LEDGER_PATH_SEGMENTS,
28
- ALL_GIT_HEAD_PATH_SEGMENTS,
29
28
  CHECK_ID_KEY,
30
29
  FILE_PATH_KEY,
30
+ GIT_COMMON_DIRECTORY_FILE_NAME,
31
+ GIT_DIRECTORY_FILE_PREFIX,
31
32
  GIT_DIRECTORY_NAME,
33
+ GIT_HEAD_FILE_NAME,
34
+ GIT_PACKED_REFERENCES_COMMENT_PREFIX,
35
+ GIT_PACKED_REFERENCES_FILE_NAME,
36
+ GIT_PACKED_REFERENCES_PEELED_PREFIX,
32
37
  GIT_REFERENCE_PREFIX,
33
38
  LEDGER_APPEND_MODE,
34
39
  LEDGER_ENCODING,
@@ -267,7 +272,10 @@ def head_commit(repository_root: Path) -> str:
267
272
 
268
273
  .git/HEAD holding "ref: refs/heads/main" -> the sha in refs/heads/main
269
274
  .git/HEAD holding a sha -> that sha
270
- no .git directory -> ""
275
+ .git file holding "gitdir: <path>" -> the same reads in <path>,
276
+ branches from its commondir
277
+ branch only in packed-refs -> the sha packed-refs lists
278
+ no .git entry -> ""
271
279
 
272
280
  The revision comes from the files git writes rather than from a git
273
281
  process, so recording a finding starts no subprocess.
@@ -278,16 +286,73 @@ def head_commit(repository_root: Path) -> str:
278
286
  Returns:
279
287
  The checked-out revision, or an empty string when none can be read.
280
288
  """
281
- head_path = repository_root.joinpath(*ALL_GIT_HEAD_PATH_SEGMENTS)
282
- head_text = _file_text(head_path)
289
+ git_directory = _git_directory(repository_root)
290
+ head_text = _file_text(git_directory / GIT_HEAD_FILE_NAME)
283
291
  if head_text is None:
284
292
  return ABSENT_ORIGIN_COMMIT
285
293
  if not head_text.startswith(GIT_REFERENCE_PREFIX):
286
294
  return head_text
287
- reference_path = repository_root / GIT_DIRECTORY_NAME / head_text[
288
- len(GIT_REFERENCE_PREFIX) :
289
- ]
290
- return _file_text(reference_path) or ABSENT_ORIGIN_COMMIT
295
+ reference_name = head_text[len(GIT_REFERENCE_PREFIX) :]
296
+ for each_reference_directory in (git_directory, _common_directory(git_directory)):
297
+ loose_commit = _file_text(each_reference_directory / reference_name)
298
+ if loose_commit:
299
+ return loose_commit
300
+ return _packed_reference_commit(_common_directory(git_directory), reference_name)
301
+
302
+
303
+ def _git_directory(repository_root: Path) -> Path:
304
+ """Locate the git directory, following a linked worktree's .git file.
305
+
306
+ Args:
307
+ repository_root: The repository whose git directory to locate.
308
+
309
+ Returns:
310
+ The directory git keeps HEAD in for this checkout.
311
+ """
312
+ git_entry = repository_root / GIT_DIRECTORY_NAME
313
+ if not git_entry.is_file():
314
+ return git_entry
315
+ entry_text = _file_text(git_entry) or ""
316
+ if not entry_text.startswith(GIT_DIRECTORY_FILE_PREFIX):
317
+ return git_entry
318
+ return repository_root / entry_text[len(GIT_DIRECTORY_FILE_PREFIX) :]
319
+
320
+
321
+ def _common_directory(git_directory: Path) -> Path:
322
+ """Locate the directory holding the branches shared by every worktree.
323
+
324
+ Args:
325
+ git_directory: The checkout's own git directory.
326
+
327
+ Returns:
328
+ The commondir target, or git_directory itself when none is named.
329
+ """
330
+ common_text = _file_text(git_directory / GIT_COMMON_DIRECTORY_FILE_NAME)
331
+ if not common_text:
332
+ return git_directory
333
+ return git_directory / common_text
334
+
335
+
336
+ def _packed_reference_commit(common_directory: Path, reference_name: str) -> str:
337
+ """Find one reference's sha in packed-refs.
338
+
339
+ Args:
340
+ common_directory: The git directory holding packed-refs.
341
+ reference_name: The full reference name, such as refs/heads/main.
342
+
343
+ Returns:
344
+ The packed sha, or an empty string when packed-refs lacks the reference.
345
+ """
346
+ packed_text = _file_text(common_directory / GIT_PACKED_REFERENCES_FILE_NAME) or ""
347
+ for each_line in packed_text.splitlines():
348
+ if each_line.startswith(
349
+ (GIT_PACKED_REFERENCES_COMMENT_PREFIX, GIT_PACKED_REFERENCES_PEELED_PREFIX)
350
+ ):
351
+ continue
352
+ commit_and_name = each_line.split(maxsplit=1)
353
+ if len(commit_and_name) == 2 and commit_and_name[1] == reference_name:
354
+ return commit_and_name[0]
355
+ return ABSENT_ORIGIN_COMMIT
291
356
 
292
357
 
293
358
  def _file_text(file_path: Path) -> str | None:
@@ -15,7 +15,12 @@ __all__ = [
15
15
  "SEVERITY_KEY",
16
16
  "ORIGIN_COMMIT_KEY",
17
17
  "ABSENT_ORIGIN_COMMIT",
18
- "ALL_GIT_HEAD_PATH_SEGMENTS",
18
+ "GIT_HEAD_FILE_NAME",
19
+ "GIT_DIRECTORY_FILE_PREFIX",
20
+ "GIT_COMMON_DIRECTORY_FILE_NAME",
21
+ "GIT_PACKED_REFERENCES_FILE_NAME",
22
+ "GIT_PACKED_REFERENCES_COMMENT_PREFIX",
23
+ "GIT_PACKED_REFERENCES_PEELED_PREFIX",
19
24
  "GIT_DIRECTORY_NAME",
20
25
  "GIT_REFERENCE_PREFIX",
21
26
  "SEVERITY_BREAKING",
@@ -38,7 +43,12 @@ ORIGIN_COMMIT_KEY: str = "origin_commit"
38
43
 
39
44
  ABSENT_ORIGIN_COMMIT: str = ""
40
45
  GIT_DIRECTORY_NAME: str = ".git"
41
- ALL_GIT_HEAD_PATH_SEGMENTS: tuple[str, ...] = (GIT_DIRECTORY_NAME, "HEAD")
46
+ GIT_HEAD_FILE_NAME: str = "HEAD"
47
+ GIT_DIRECTORY_FILE_PREFIX: str = "gitdir: "
48
+ GIT_COMMON_DIRECTORY_FILE_NAME: str = "commondir"
49
+ GIT_PACKED_REFERENCES_FILE_NAME: str = "packed-refs"
50
+ GIT_PACKED_REFERENCES_COMMENT_PREFIX: str = "#"
51
+ GIT_PACKED_REFERENCES_PEELED_PREFIX: str = "^"
42
52
  GIT_REFERENCE_PREFIX: str = "ref: "
43
53
 
44
54
  SEVERITY_BREAKING: str = "breaking"
@@ -191,6 +191,83 @@ def test_head_commit_is_empty_outside_a_repository(tmp_path: Path) -> None:
191
191
  assert head_commit(tmp_path) == ""
192
192
 
193
193
 
194
+ def _committed_repository(repository_root: Path) -> str:
195
+ subprocess.run(["git", "init", "--quiet", str(repository_root)], check=True)
196
+ subprocess.run(
197
+ [
198
+ "git",
199
+ "-C",
200
+ str(repository_root),
201
+ "-c",
202
+ "user.name=t",
203
+ "-c",
204
+ "user.email=t@t",
205
+ "commit",
206
+ "--quiet",
207
+ "--allow-empty",
208
+ "-m",
209
+ "start",
210
+ ],
211
+ check=True,
212
+ )
213
+ return subprocess.run(
214
+ ["git", "-C", str(repository_root), "rev-parse", "HEAD"],
215
+ check=True,
216
+ capture_output=True,
217
+ text=True,
218
+ ).stdout.strip()
219
+
220
+
221
+ def test_head_commit_reads_a_linked_worktree_branch(tmp_path: Path) -> None:
222
+ main_root = tmp_path / "main"
223
+ worktree_root = tmp_path / "linked"
224
+ expected_commit = _committed_repository(main_root)
225
+ subprocess.run(
226
+ [
227
+ "git",
228
+ "-C",
229
+ str(main_root),
230
+ "worktree",
231
+ "add",
232
+ "--quiet",
233
+ "-b",
234
+ "feature",
235
+ str(worktree_root),
236
+ ],
237
+ check=True,
238
+ )
239
+
240
+ assert head_commit(worktree_root) == expected_commit
241
+
242
+
243
+ def test_head_commit_reads_a_packed_branch_from_a_linked_worktree(
244
+ tmp_path: Path,
245
+ ) -> None:
246
+ main_root = tmp_path / "main"
247
+ worktree_root = tmp_path / "linked"
248
+ expected_commit = _committed_repository(main_root)
249
+ subprocess.run(
250
+ [
251
+ "git",
252
+ "-C",
253
+ str(main_root),
254
+ "worktree",
255
+ "add",
256
+ "--quiet",
257
+ "-b",
258
+ "feature",
259
+ str(worktree_root),
260
+ ],
261
+ check=True,
262
+ )
263
+ subprocess.run(
264
+ ["git", "-C", str(main_root), "pack-refs", "--all"], check=True
265
+ )
266
+
267
+ assert not (main_root / ".git" / "refs" / "heads" / "feature").exists()
268
+ assert head_commit(worktree_root) == expected_commit
269
+
270
+
194
271
  def test_deduplicated_lines_keeps_the_first_of_each_exact_line_in_order() -> None:
195
272
  assert deduplicated_lines(["a", "b", "a", "c", "b"]) == ["a", "b", "c"]
196
273
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.74.0",
3
+ "version": "8.74.1",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {