claude-dev-env 4.0.0 → 4.2.0

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.
Files changed (32) hide show
  1. package/.agents/skills/everything-search/SKILL.md +18 -3
  2. package/.agents/skills/everything-search/scripts/everything_search.py +204 -0
  3. package/.agents/skills/everything-search/scripts/everything_search_command_constants/__init__.py +1 -0
  4. package/.agents/skills/everything-search/scripts/everything_search_command_constants/config/__init__.py +1 -0
  5. package/.agents/skills/everything-search/scripts/everything_search_command_constants/config/constants.py +11 -0
  6. package/.agents/skills/everything-search/scripts/test_everything_search.py +172 -0
  7. package/.agents/skills/syncing-submodules/SKILL.md +101 -0
  8. package/.agents/skills/syncing-submodules/reference/run-tasks.md +9 -0
  9. package/.agents/skills/syncing-submodules/scripts/submodule_sync.py +383 -0
  10. package/.agents/skills/syncing-submodules/scripts/submodule_sync_constants/__init__.py +1 -0
  11. package/.agents/skills/syncing-submodules/scripts/submodule_sync_constants/config/__init__.py +1 -0
  12. package/.agents/skills/syncing-submodules/scripts/submodule_sync_constants/config/constants.py +66 -0
  13. package/.agents/skills/syncing-submodules/scripts/sync_parent_pointer.py +88 -0
  14. package/.agents/skills/syncing-submodules/scripts/test_submodule_sync.py +303 -0
  15. package/.agents/skills/syncing-submodules/scripts/test_sync_parent_pointer.py +83 -0
  16. package/bin/ever-shipped-skills.mjs +1 -0
  17. package/bin/ever-shipped-skills.test.mjs +30 -0
  18. package/bin/git_hooks_installer.mjs +0 -1
  19. package/bin/git_hooks_installer.test.mjs +16 -1
  20. package/bin/install.mjs +5 -2
  21. package/bin/install.prune.test.mjs +95 -24
  22. package/bin/install.test.mjs +7 -0
  23. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +5 -8
  24. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +7 -13
  25. package/package.json +1 -1
  26. package/rules/filesystem-search.md +5 -5
  27. package/hooks/blocking/es_exe_path_rewriter.py +0 -159
  28. package/hooks/blocking/test_es_exe_path_rewriter.py +0 -447
  29. package/hooks/git-hooks/post_commit.py +0 -241
  30. package/hooks/git-hooks/test_post_commit.py +0 -298
  31. package/hooks/hooks_constants/path_rewriter_constants.py +0 -13
  32. package/hooks/hooks_constants/test_path_rewriter_constants.py +0 -57
@@ -0,0 +1,383 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import subprocess
5
+ from dataclasses import dataclass
6
+ from enum import StrEnum
7
+ from pathlib import Path
8
+
9
+ from submodule_sync_constants.config.constants import (
10
+ ALL_GH_PULL_REQUEST_ARGUMENTS,
11
+ DECODE_ERRORS_POLICY,
12
+ EXIT_CODE_FAILURE,
13
+ EXIT_CODE_SUCCESS,
14
+ GH_COMMAND_TIMEOUT_SECONDS,
15
+ GH_EXECUTABLE_NAME,
16
+ GIT_ADD_SUBCOMMAND,
17
+ GIT_ARGUMENT_SEPARATOR,
18
+ GIT_CACHED_ARGUMENT,
19
+ GIT_COMMAND_TIMEOUT_SECONDS,
20
+ GIT_COMMIT_SUBCOMMAND,
21
+ GIT_DIFF_SUBCOMMAND,
22
+ GIT_EXECUTABLE_NAME,
23
+ GIT_EXIT_FAILURE_MESSAGE_TEMPLATE,
24
+ GIT_HEAD_REFERENCE,
25
+ GIT_LATEST_COMMIT_ARGUMENT,
26
+ GIT_LAUNCH_FAILURE_MESSAGE_TEMPLATE,
27
+ GIT_LITERAL_PATHSPEC_PREFIX,
28
+ GIT_LOG_SUBCOMMAND,
29
+ GIT_MESSAGE_ARGUMENT,
30
+ GIT_ONLY_ARGUMENT,
31
+ GIT_QUIET_ARGUMENT,
32
+ GIT_REV_PARSE_SUBCOMMAND,
33
+ GIT_SHOW_SUPERPROJECT_ARGUMENT,
34
+ GIT_SHOW_TOP_LEVEL_ARGUMENT,
35
+ GIT_SUBJECT_FORMAT_ARGUMENT,
36
+ GIT_TIMEOUT_MESSAGE_TEMPLATE,
37
+ PARENT_COMMIT_MESSAGE_TEMPLATE,
38
+ REPOSITORY_NOT_DIRECTORY_MESSAGE_TEMPLATE,
39
+ REPOSITORY_RESOLUTION_FAILURE_MESSAGE,
40
+ SUBMODULE_COMMIT_MESSAGE_TEMPLATE,
41
+ SUBMODULE_PATH_FAILURE_MESSAGE,
42
+ SYNC_STATUS_ERROR,
43
+ SYNC_STATUS_NOT_SUBMODULE,
44
+ SYNC_STATUS_UNCHANGED,
45
+ SYNC_STATUS_UPDATED,
46
+ UTF8_ENCODING,
47
+ )
48
+
49
+
50
+ class SyncStatus(StrEnum):
51
+ """States reported by one parent-pointer sync."""
52
+
53
+ UPDATED = SYNC_STATUS_UPDATED
54
+ UNCHANGED = SYNC_STATUS_UNCHANGED
55
+ NOT_SUBMODULE = SYNC_STATUS_NOT_SUBMODULE
56
+ ERROR = SYNC_STATUS_ERROR
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class SyncReport:
61
+ """Serializable facts from one parent-pointer sync."""
62
+
63
+ status: SyncStatus
64
+ repository: str
65
+ parent_repository: str | None = None
66
+ submodule_path: str | None = None
67
+ commit: str | None = None
68
+ parent_commit: str | None = None
69
+ pull_request_url: str | None = None
70
+ diagnostic: str | None = None
71
+
72
+ def as_record(self) -> dict[str, str | None]:
73
+ """Build the JSON-ready record.
74
+
75
+ Returns:
76
+ All report fields with nullable values preserved.
77
+ """
78
+ return {
79
+ "status": self.status.value,
80
+ "repository": self.repository,
81
+ "parent_repository": self.parent_repository,
82
+ "submodule_path": self.submodule_path,
83
+ "commit": self.commit,
84
+ "parent_commit": self.parent_commit,
85
+ "pull_request_url": self.pull_request_url,
86
+ "diagnostic": self.diagnostic,
87
+ }
88
+
89
+
90
+ @dataclass(frozen=True)
91
+ class _SyncContext:
92
+ repository: Path
93
+ parent_repository: Path
94
+ submodule_path: str
95
+ literal_pathspec: str
96
+ commit: str
97
+ commit_subject: str
98
+
99
+
100
+ class _GitCommandFailure(RuntimeError):
101
+ pass
102
+
103
+
104
+ def _build_child_environment() -> dict[str, str]:
105
+ return {
106
+ each_name: each_content
107
+ for each_name, each_content in os.environ.items()
108
+ if not each_name.upper().startswith("GIT_")
109
+ }
110
+
111
+
112
+ def _execute_git(
113
+ all_arguments: tuple[str, ...],
114
+ cwd: Path,
115
+ ) -> subprocess.CompletedProcess[str]:
116
+ try:
117
+ return subprocess.run(
118
+ [GIT_EXECUTABLE_NAME, *all_arguments],
119
+ cwd=cwd,
120
+ check=False,
121
+ capture_output=True,
122
+ encoding=UTF8_ENCODING,
123
+ errors=DECODE_ERRORS_POLICY,
124
+ env=_build_child_environment(),
125
+ timeout=GIT_COMMAND_TIMEOUT_SECONDS,
126
+ )
127
+ except subprocess.TimeoutExpired as error:
128
+ raise _GitCommandFailure(
129
+ GIT_TIMEOUT_MESSAGE_TEMPLATE.format(seconds=GIT_COMMAND_TIMEOUT_SECONDS)
130
+ ) from error
131
+ except OSError as error:
132
+ raise _GitCommandFailure(
133
+ GIT_LAUNCH_FAILURE_MESSAGE_TEMPLATE.format(error=error)
134
+ ) from error
135
+
136
+
137
+ def _git_failure_diagnostic(
138
+ completed_process: subprocess.CompletedProcess[str],
139
+ ) -> str:
140
+ diagnostic = completed_process.stderr.strip() or completed_process.stdout.strip()
141
+ if diagnostic:
142
+ return diagnostic
143
+ return GIT_EXIT_FAILURE_MESSAGE_TEMPLATE.format(
144
+ status=completed_process.returncode,
145
+ command=GIT_EXECUTABLE_NAME,
146
+ )
147
+
148
+
149
+ def _read_required_git_text(
150
+ all_arguments: tuple[str, ...],
151
+ cwd: Path,
152
+ empty_text_diagnostic: str,
153
+ ) -> str:
154
+ completed_process = _execute_git(all_arguments, cwd)
155
+ if completed_process.returncode != EXIT_CODE_SUCCESS:
156
+ raise _GitCommandFailure(_git_failure_diagnostic(completed_process))
157
+ git_text = completed_process.stdout.strip()
158
+ if not git_text:
159
+ raise _GitCommandFailure(empty_text_diagnostic)
160
+ return git_text
161
+
162
+
163
+ def lookup_pull_request_url(repository: Path) -> str | None:
164
+ """Read the open pull request URL when GitHub CLI can provide one.
165
+
166
+ Args:
167
+ repository: Child repository to query.
168
+
169
+ Returns:
170
+ The pull request URL, or None when the lookup is unavailable.
171
+ """
172
+ try:
173
+ completed_process = subprocess.run(
174
+ [GH_EXECUTABLE_NAME, *ALL_GH_PULL_REQUEST_ARGUMENTS],
175
+ cwd=repository,
176
+ check=False,
177
+ capture_output=True,
178
+ encoding=UTF8_ENCODING,
179
+ errors=DECODE_ERRORS_POLICY,
180
+ env=_build_child_environment(),
181
+ timeout=GH_COMMAND_TIMEOUT_SECONDS,
182
+ )
183
+ except (OSError, subprocess.SubprocessError):
184
+ return None
185
+ if completed_process.returncode != EXIT_CODE_SUCCESS:
186
+ return None
187
+ return completed_process.stdout.strip() or None
188
+
189
+
190
+ def _build_error_report(repository: Path, diagnostic: str) -> SyncReport:
191
+ return SyncReport(
192
+ status=SyncStatus.ERROR,
193
+ repository=repository.resolve().as_posix(),
194
+ diagnostic=diagnostic,
195
+ )
196
+
197
+
198
+ def _resolve_repository_root(repository: Path) -> Path:
199
+ requested_repository = repository.resolve()
200
+ if not requested_repository.is_dir():
201
+ raise _GitCommandFailure(
202
+ REPOSITORY_NOT_DIRECTORY_MESSAGE_TEMPLATE.format(
203
+ path=requested_repository,
204
+ )
205
+ )
206
+ repository_root_text = _read_required_git_text(
207
+ (GIT_REV_PARSE_SUBCOMMAND, GIT_SHOW_TOP_LEVEL_ARGUMENT),
208
+ requested_repository,
209
+ REPOSITORY_RESOLUTION_FAILURE_MESSAGE,
210
+ )
211
+ return Path(repository_root_text).resolve()
212
+
213
+
214
+ def _find_parent_repository(repository: Path) -> Path | None:
215
+ completed_process = _execute_git(
216
+ (GIT_REV_PARSE_SUBCOMMAND, GIT_SHOW_SUPERPROJECT_ARGUMENT),
217
+ repository,
218
+ )
219
+ if completed_process.returncode != EXIT_CODE_SUCCESS:
220
+ raise _GitCommandFailure(_git_failure_diagnostic(completed_process))
221
+ parent_repository_text = completed_process.stdout.strip()
222
+ if not parent_repository_text:
223
+ return None
224
+ return Path(parent_repository_text).resolve()
225
+
226
+
227
+ def _read_child_commit_subject(repository: Path) -> str:
228
+ completed_process = _execute_git(
229
+ (
230
+ GIT_LOG_SUBCOMMAND,
231
+ GIT_LATEST_COMMIT_ARGUMENT,
232
+ GIT_SUBJECT_FORMAT_ARGUMENT,
233
+ ),
234
+ repository,
235
+ )
236
+ if completed_process.returncode != EXIT_CODE_SUCCESS:
237
+ raise _GitCommandFailure(_git_failure_diagnostic(completed_process))
238
+ return completed_process.stdout.strip()
239
+
240
+
241
+ def _build_sync_context(
242
+ repository: Path,
243
+ parent_repository: Path,
244
+ ) -> _SyncContext:
245
+ try:
246
+ submodule_relative_path = repository.relative_to(parent_repository)
247
+ except ValueError as error:
248
+ raise _GitCommandFailure(SUBMODULE_PATH_FAILURE_MESSAGE) from error
249
+ commit_hash = _read_required_git_text(
250
+ (GIT_REV_PARSE_SUBCOMMAND, GIT_HEAD_REFERENCE),
251
+ repository,
252
+ REPOSITORY_RESOLUTION_FAILURE_MESSAGE,
253
+ )
254
+ submodule_path = submodule_relative_path.as_posix()
255
+ return _SyncContext(
256
+ repository=repository,
257
+ parent_repository=parent_repository,
258
+ submodule_path=submodule_path,
259
+ literal_pathspec=f"{GIT_LITERAL_PATHSPEC_PREFIX}{submodule_path}",
260
+ commit=commit_hash,
261
+ commit_subject=_read_child_commit_subject(repository),
262
+ )
263
+
264
+
265
+ def _stage_parent_pointer(sync_context: _SyncContext) -> None:
266
+ completed_process = _execute_git(
267
+ (GIT_ADD_SUBCOMMAND, GIT_ARGUMENT_SEPARATOR, sync_context.literal_pathspec),
268
+ sync_context.parent_repository,
269
+ )
270
+ if completed_process.returncode != EXIT_CODE_SUCCESS:
271
+ raise _GitCommandFailure(_git_failure_diagnostic(completed_process))
272
+
273
+
274
+ def _parent_pointer_needs_commit(sync_context: _SyncContext) -> bool:
275
+ completed_process = _execute_git(
276
+ (
277
+ GIT_DIFF_SUBCOMMAND,
278
+ GIT_CACHED_ARGUMENT,
279
+ GIT_QUIET_ARGUMENT,
280
+ GIT_ARGUMENT_SEPARATOR,
281
+ sync_context.literal_pathspec,
282
+ ),
283
+ sync_context.parent_repository,
284
+ )
285
+ if completed_process.returncode == EXIT_CODE_SUCCESS:
286
+ return False
287
+ if completed_process.returncode == EXIT_CODE_FAILURE:
288
+ return True
289
+ raise _GitCommandFailure(_git_failure_diagnostic(completed_process))
290
+
291
+
292
+ def _build_parent_commit_message(sync_context: _SyncContext) -> str:
293
+ parent_message = PARENT_COMMIT_MESSAGE_TEMPLATE.format(
294
+ repository_name=sync_context.repository.name,
295
+ commit_hash=sync_context.commit,
296
+ )
297
+ if not sync_context.commit_subject:
298
+ return parent_message
299
+ return SUBMODULE_COMMIT_MESSAGE_TEMPLATE.format(
300
+ parent_message=parent_message,
301
+ subject=sync_context.commit_subject,
302
+ )
303
+
304
+
305
+ def _read_parent_commit(sync_context: _SyncContext) -> str:
306
+ return _read_required_git_text(
307
+ (GIT_REV_PARSE_SUBCOMMAND, GIT_HEAD_REFERENCE),
308
+ sync_context.parent_repository,
309
+ REPOSITORY_RESOLUTION_FAILURE_MESSAGE,
310
+ )
311
+
312
+
313
+ def _commit_parent_pointer(sync_context: _SyncContext) -> str:
314
+ completed_process = _execute_git(
315
+ (
316
+ GIT_COMMIT_SUBCOMMAND,
317
+ GIT_ONLY_ARGUMENT,
318
+ GIT_MESSAGE_ARGUMENT,
319
+ _build_parent_commit_message(sync_context),
320
+ GIT_ARGUMENT_SEPARATOR,
321
+ sync_context.literal_pathspec,
322
+ ),
323
+ sync_context.parent_repository,
324
+ )
325
+ if completed_process.returncode != EXIT_CODE_SUCCESS:
326
+ raise _GitCommandFailure(_git_failure_diagnostic(completed_process))
327
+ return _read_parent_commit(sync_context)
328
+
329
+
330
+ def _report_from_context(
331
+ sync_context: _SyncContext,
332
+ status: SyncStatus,
333
+ parent_commit: str,
334
+ ) -> SyncReport:
335
+ return SyncReport(
336
+ status=status,
337
+ repository=sync_context.repository.as_posix(),
338
+ parent_repository=sync_context.parent_repository.as_posix(),
339
+ submodule_path=sync_context.submodule_path,
340
+ commit=sync_context.commit,
341
+ parent_commit=parent_commit,
342
+ pull_request_url=lookup_pull_request_url(sync_context.repository),
343
+ )
344
+
345
+
346
+ def _sync_parent_pointer(sync_context: _SyncContext) -> SyncReport:
347
+ _stage_parent_pointer(sync_context)
348
+ if not _parent_pointer_needs_commit(sync_context):
349
+ return _report_from_context(
350
+ sync_context,
351
+ SyncStatus.UNCHANGED,
352
+ _read_parent_commit(sync_context),
353
+ )
354
+ return _report_from_context(
355
+ sync_context,
356
+ SyncStatus.UPDATED,
357
+ _commit_parent_pointer(sync_context),
358
+ )
359
+
360
+
361
+ def sync_repository(repository: Path) -> SyncReport:
362
+ """Record the child commit in its parent repository.
363
+
364
+ Args:
365
+ repository: Child repository or a directory inside it.
366
+
367
+ Returns:
368
+ The sync status and every path or commit needed for readback.
369
+ """
370
+ requested_repository = repository.resolve()
371
+ try:
372
+ repository_root = _resolve_repository_root(requested_repository)
373
+ parent_repository = _find_parent_repository(repository_root)
374
+ if parent_repository is None:
375
+ return SyncReport(
376
+ status=SyncStatus.NOT_SUBMODULE,
377
+ repository=repository_root.as_posix(),
378
+ pull_request_url=lookup_pull_request_url(repository_root),
379
+ )
380
+ sync_context = _build_sync_context(repository_root, parent_repository)
381
+ return _sync_parent_pointer(sync_context)
382
+ except (_GitCommandFailure, OSError) as error:
383
+ return _build_error_report(requested_repository, str(error))
@@ -0,0 +1 @@
1
+ """Constants package for the parent-pointer synchronization command."""
@@ -0,0 +1 @@
1
+ """Configuration package for the parent-pointer synchronization command."""
@@ -0,0 +1,66 @@
1
+ """Constants used by the parent-pointer synchronization command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ DECODE_ERRORS_POLICY: str = "replace"
6
+ DEFAULT_REPOSITORY_ARGUMENT: str = "."
7
+ DIAGNOSTIC_PREFIX: str = "syncing-submodules: "
8
+ EXIT_CODE_FAILURE: int = 1
9
+ EXIT_CODE_INVALID_ARGUMENTS: int = 2
10
+ EXIT_CODE_SUCCESS: int = 0
11
+ GIT_COMMAND_TIMEOUT_SECONDS: int = 30
12
+ GIT_EXECUTABLE_NAME: str = "git"
13
+ GIT_HEAD_REFERENCE: str = "HEAD"
14
+ GIT_LATEST_COMMIT_ARGUMENT: str = "-1"
15
+ GIT_LITERAL_PATHSPEC_PREFIX: str = ":(literal)"
16
+ GIT_REV_PARSE_SUBCOMMAND: str = "rev-parse"
17
+ GIT_SHOW_SUPERPROJECT_ARGUMENT: str = "--show-superproject-working-tree"
18
+ GIT_SHOW_TOP_LEVEL_ARGUMENT: str = "--show-toplevel"
19
+ GIT_LOG_SUBCOMMAND: str = "log"
20
+ GIT_SUBJECT_FORMAT_ARGUMENT: str = "--pretty=%s"
21
+ GIT_ADD_SUBCOMMAND: str = "add"
22
+ GIT_ARGUMENT_SEPARATOR: str = "--"
23
+ GIT_DIFF_SUBCOMMAND: str = "diff"
24
+ GIT_CACHED_ARGUMENT: str = "--cached"
25
+ GIT_QUIET_ARGUMENT: str = "--quiet"
26
+ GIT_COMMIT_SUBCOMMAND: str = "commit"
27
+ GIT_ONLY_ARGUMENT: str = "--only"
28
+ GIT_MESSAGE_ARGUMENT: str = "-m"
29
+ GIT_LAUNCH_FAILURE_MESSAGE_TEMPLATE: str = "git command could not run: {error}"
30
+ GIT_TIMEOUT_MESSAGE_TEMPLATE: str = "git command timed out after {seconds} seconds"
31
+ GIT_EXIT_FAILURE_MESSAGE_TEMPLATE: str = (
32
+ "git command exited with status {status}: {command}"
33
+ )
34
+ GH_EXECUTABLE_NAME: str = "gh"
35
+ ALL_GH_PULL_REQUEST_ARGUMENTS: tuple[str, ...] = (
36
+ "pr",
37
+ "view",
38
+ "--json",
39
+ "url",
40
+ "--jq",
41
+ ".url",
42
+ )
43
+ GH_COMMAND_TIMEOUT_SECONDS: int = 5
44
+ PARENT_COMMIT_MESSAGE_TEMPLATE: str = (
45
+ "chore: update {repository_name} submodule to {commit_hash}"
46
+ )
47
+ SUBMODULE_COMMIT_MESSAGE_TEMPLATE: str = (
48
+ "{parent_message}\n\nSubmodule commit: {subject}"
49
+ )
50
+ INVALID_ARGUMENT_MESSAGE_TEMPLATE: str = "invalid command arguments: {error}"
51
+ SUBMODULE_PATH_FAILURE_MESSAGE: str = (
52
+ "could not resolve the submodule path in its parent repository"
53
+ )
54
+ REPOSITORY_RESOLUTION_FAILURE_MESSAGE: str = (
55
+ "git did not return a repository top-level path"
56
+ )
57
+ REPOSITORY_NOT_DIRECTORY_MESSAGE_TEMPLATE: str = (
58
+ "repository path is not a directory: {path}"
59
+ )
60
+ REPOSITORY_ARGUMENT_NAME: str = "--repository"
61
+ SYNC_STATUS_UPDATED: str = "updated"
62
+ SYNC_STATUS_UNCHANGED: str = "unchanged"
63
+ SYNC_STATUS_NOT_SUBMODULE: str = "not_submodule"
64
+ SYNC_STATUS_ERROR: str = "error"
65
+ JSON_LINE_SEPARATOR: str = "\n"
66
+ UTF8_ENCODING: str = "utf-8"
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from collections.abc import Sequence
7
+ from pathlib import Path
8
+ from typing import NoReturn
9
+
10
+ from submodule_sync import (
11
+ SyncReport,
12
+ SyncStatus,
13
+ sync_repository,
14
+ )
15
+ from submodule_sync_constants.config.constants import (
16
+ DEFAULT_REPOSITORY_ARGUMENT,
17
+ DIAGNOSTIC_PREFIX,
18
+ EXIT_CODE_FAILURE,
19
+ EXIT_CODE_INVALID_ARGUMENTS,
20
+ EXIT_CODE_SUCCESS,
21
+ INVALID_ARGUMENT_MESSAGE_TEMPLATE,
22
+ JSON_LINE_SEPARATOR,
23
+ REPOSITORY_ARGUMENT_NAME,
24
+ )
25
+
26
+
27
+ class _InvalidCommandArguments(ValueError):
28
+ pass
29
+
30
+
31
+ class _SyncArgumentParser(argparse.ArgumentParser):
32
+ def error(self, message: str) -> NoReturn:
33
+ """Reject invalid command arguments.
34
+
35
+ Args:
36
+ message: Parser diagnostic.
37
+
38
+ Raises:
39
+ _InvalidCommandArguments: Always raised with the parser diagnostic.
40
+ """
41
+ raise _InvalidCommandArguments(message)
42
+
43
+
44
+ def _parse_arguments(all_arguments: Sequence[str]) -> Path:
45
+ parser = _SyncArgumentParser(add_help=False)
46
+ parser.add_argument(
47
+ REPOSITORY_ARGUMENT_NAME,
48
+ default=DEFAULT_REPOSITORY_ARGUMENT,
49
+ )
50
+ parsed_arguments = parser.parse_args(all_arguments)
51
+ return Path(str(parsed_arguments.repository))
52
+
53
+
54
+ def _emit_report(sync_report: SyncReport) -> None:
55
+ sys.stdout.write(json.dumps(sync_report.as_record()) + JSON_LINE_SEPARATOR)
56
+
57
+
58
+ def main(all_arguments: Sequence[str]) -> int:
59
+ """Run the parent-pointer synchronization command.
60
+
61
+ Args:
62
+ all_arguments: Command arguments without the executable name.
63
+
64
+ Returns:
65
+ Zero for completed states, one for Git failures, or two for bad arguments.
66
+ """
67
+ try:
68
+ repository = _parse_arguments(all_arguments)
69
+ except _InvalidCommandArguments as error:
70
+ diagnostic = INVALID_ARGUMENT_MESSAGE_TEMPLATE.format(error=error)
71
+ sync_report = SyncReport(
72
+ status=SyncStatus.ERROR,
73
+ repository=Path(DEFAULT_REPOSITORY_ARGUMENT).resolve().as_posix(),
74
+ diagnostic=diagnostic,
75
+ )
76
+ _emit_report(sync_report)
77
+ print(f"{DIAGNOSTIC_PREFIX}{diagnostic}", file=sys.stderr)
78
+ return EXIT_CODE_INVALID_ARGUMENTS
79
+ sync_report = sync_repository(repository)
80
+ _emit_report(sync_report)
81
+ if sync_report.status is SyncStatus.ERROR:
82
+ print(f"{DIAGNOSTIC_PREFIX}{sync_report.diagnostic}", file=sys.stderr)
83
+ return EXIT_CODE_FAILURE
84
+ return EXIT_CODE_SUCCESS
85
+
86
+
87
+ if __name__ == "__main__":
88
+ raise SystemExit(main(sys.argv[1:]))