@memorax/memorax-code 0.1.8 → 0.1.10

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 (102) hide show
  1. package/README.md +10 -0
  2. package/bin/memorax-code-codebuddy.mjs +4 -0
  3. package/bin/memorax-code-setup.mjs +182 -65
  4. package/bin/memorax-code.mjs +74 -45
  5. package/docs/configuration.md +82 -22
  6. package/docs/troubleshooting.md +69 -5
  7. package/lib/automatic-update.mjs +233 -0
  8. package/lib/memorax-code-adapter-common/src/automatic-update-state.d.mts +24 -0
  9. package/lib/memorax-code-adapter-common/src/automatic-update-state.mjs +73 -0
  10. package/lib/memorax-code-adapter-common/src/clients/codebuddy-command.mjs +82 -0
  11. package/lib/memorax-code-adapter-common/src/config-utils.d.mts +2 -0
  12. package/lib/memorax-code-adapter-common/src/config-utils.mjs +11 -0
  13. package/lib/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +3 -0
  14. package/lib/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +45 -10
  15. package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-job-worker.mjs +88 -3
  16. package/lib/memorax-code-adapter-common/src/windows-cli-invocation.mjs +11 -1
  17. package/lib/memorax-code-backend/dist/app/backend-server.js +2 -2
  18. package/lib/memorax-code-backend/dist/app/memory-observability.js +2 -2
  19. package/lib/memorax-code-backend/dist/clients/codebuddy/jsonl-history.js +208 -0
  20. package/lib/memorax-code-backend/dist/clients/codebuddy/lifecycle.js +44 -0
  21. package/lib/memorax-code-backend/dist/clients/codebuddy/memory-hook-runtime.js +250 -0
  22. package/lib/memorax-code-backend/dist/clients/codebuddy/turn-id.js +16 -0
  23. package/lib/memorax-code-backend/dist/clients/codex/plugin-install.js +129 -7
  24. package/lib/memorax-code-backend/dist/clients/codex/rollout-turn.js +52 -2
  25. package/lib/memorax-code-backend/dist/config/memorax-code.js +14 -1
  26. package/lib/memorax-code-backend/dist/entrypoints/backend-cli.js +43 -6
  27. package/lib/memorax-code-backend/dist/lifecycle/active-clients.js +3 -0
  28. package/lib/memorax-code-backend/dist/lifecycle/automatic-update-scheduler.js +133 -0
  29. package/lib/memorax-code-backend/dist/lifecycle/client-plugin-removal.js +14 -2
  30. package/lib/memorax-code-backend/dist/lifecycle/client-selection.js +6 -4
  31. package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +48 -9
  32. package/lib/memorax-code-backend/dist/memory/cli.js +4 -2
  33. package/lib/memorax-code-backend/dist/memory/hook-command.js +31 -1
  34. package/lib/memorax-code-backend/dist/memory/reminder-trace-recorder.js +5 -1
  35. package/lib/memorax-code-backend/dist/memory/service.js +12 -0
  36. package/lib/memorax-code-backend/dist/shared/windows-cli-invocation.js +10 -1
  37. package/lib/memorax-code-backend/dist/trace/config.js +12 -0
  38. package/lib/memorax-code-backend/dist/trace/context.js +21 -1
  39. package/lib/memorax-code-backend/dist/trace/store.js +12 -1
  40. package/lib/memorax-code-backend/package.json +1 -1
  41. package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  42. package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  43. package/lib/memorax-code-claude-adapter/package.json +1 -1
  44. package/lib/memorax-code-claude-adapter/runtime-hooks/ensure-backend.mjs +2 -0
  45. package/lib/memorax-code-claude-adapter/src/plugin-install.mjs +3 -0
  46. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  47. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  48. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/automatic-update-state.d.mts +24 -0
  49. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/automatic-update-state.mjs +73 -0
  50. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/clients/codebuddy-command.mjs +82 -0
  51. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.d.mts +2 -0
  52. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.mjs +11 -0
  53. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +3 -0
  54. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +45 -10
  55. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-job-worker.mjs +88 -3
  56. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/windows-cli-invocation.mjs +11 -1
  57. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
  58. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/ensure-backend.mjs +2 -0
  59. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/plugin-install.mjs +3 -0
  60. package/lib/memorax-code-codebuddy-adapter/.codebuddy-plugin/plugin.json +7 -0
  61. package/lib/memorax-code-codebuddy-adapter/hooks/common-runtime.mjs +13 -0
  62. package/lib/memorax-code-codebuddy-adapter/hooks/hooks.json +38 -0
  63. package/lib/memorax-code-codebuddy-adapter/hooks/repo-memory-job.mjs +40 -0
  64. package/lib/memorax-code-codebuddy-adapter/hooks/runtime-hook.mjs +279 -0
  65. package/lib/memorax-code-codebuddy-adapter/package.json +9 -0
  66. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/SKILL.md +85 -0
  67. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/agents/claude.yaml +10 -0
  68. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/agents/openai.yaml +7 -0
  69. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/defaults.json +12 -0
  70. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-add.md +88 -0
  71. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-search.md +93 -0
  72. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/personal-read.md +46 -0
  73. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/personal-write.md +120 -0
  74. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-build.md +319 -0
  75. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-read.md +103 -0
  76. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-templates.md +390 -0
  77. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-update.md +127 -0
  78. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/collect_all.py +579 -0
  79. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/detect_updates.py +919 -0
  80. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/git_commit_facets.py +222 -0
  81. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/github_resource_facets.py +512 -0
  82. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +517 -0
  83. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/prepare_repo_memory.py +411 -0
  84. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/user_profile_memory.py +528 -0
  85. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/validate_memory.py +248 -0
  86. package/lib/memorax-code-codebuddy-adapter/src/cli.mjs +58 -0
  87. package/lib/memorax-code-codebuddy-adapter/src/config.mjs +277 -0
  88. package/lib/memorax-code-codebuddy-adapter/src/hook-manifest.mjs +47 -0
  89. package/lib/memorax-code-codebuddy-adapter/src/runtime-observation.mjs +65 -0
  90. package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
  91. package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
  92. package/lib/memorax-code-codex-adapter/package.json +1 -1
  93. package/lib/memorax-code-codex-adapter/runtime-hooks/ensure-backend.mjs +4 -1
  94. package/lib/memorax-code-dsh-adapter/package.json +2 -1
  95. package/lib/memorax-code-dsh-adapter/src/profile-lifecycle.mjs +1 -0
  96. package/lib/memorax-code-opencode-adapter/package.json +1 -1
  97. package/lib/npm-invocation.mjs +51 -8
  98. package/lib/resolve-codebuddy-command.mjs +112 -0
  99. package/lib/run-entrypoint.mjs +25 -1
  100. package/lib/windows-cli-invocation.mjs +11 -1
  101. package/lib/windows-user-path.mjs +218 -0
  102. package/package.json +5 -3
@@ -0,0 +1,411 @@
1
+ #!/usr/bin/env python3
2
+ """Prepare an empty .repo_memory workspace; do not generate memory content."""
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import re
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from datetime import datetime, timezone
12
+ from pathlib import Path
13
+ from typing import Any
14
+ from urllib.parse import urlparse
15
+
16
+
17
+ REPO_MEMORY_GITIGNORE_RULE = ".repo_memory/"
18
+ PREFERRED_REMOTE_NAMES = ("upstream", "origin")
19
+
20
+
21
+ def run(cmd: list[str], cwd: Path) -> tuple[int, str, str]:
22
+ proc = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True)
23
+ return proc.returncode, proc.stdout.strip(), proc.stderr.strip()
24
+
25
+
26
+ def now_iso() -> str:
27
+ return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
28
+
29
+
30
+ def is_git_repo(repo: Path) -> bool:
31
+ code, out, _ = run(["git", "rev-parse", "--is-inside-work-tree"], repo)
32
+ return code == 0 and out == "true"
33
+
34
+
35
+ def git_value(repo: Path, *args: str) -> str:
36
+ code, out, _ = run(["git", *args], repo)
37
+ return out if code == 0 else ""
38
+
39
+
40
+ def remote_url_from_line(line: str) -> str:
41
+ parts = line.split()
42
+ return parts[1] if len(parts) >= 2 else ""
43
+
44
+
45
+ def remote_name_from_line(line: str) -> str:
46
+ parts = line.split()
47
+ return parts[0] if parts else ""
48
+
49
+
50
+ def normalize_remote_path(path: str) -> str:
51
+ repo_path = path.strip().lstrip("/").rstrip("/")
52
+ if repo_path.endswith(".git"):
53
+ repo_path = repo_path[:-4]
54
+ return repo_path
55
+
56
+
57
+ def provider_from_host(host: str) -> str:
58
+ lower = host.lower()
59
+ if lower == "github.com" or "github" in lower:
60
+ return "github"
61
+ if lower == "gitlab.com" or "gitlab" in lower:
62
+ return "gitlab"
63
+ return ""
64
+
65
+
66
+ def parse_remote_url(url: str) -> dict[str, str]:
67
+ if not url:
68
+ return {"provider": "", "repo": "", "host": "", "url": ""}
69
+
70
+ if "://" not in url:
71
+ scp_match = re.match(r"^(?:[^@/\s]+@)?(?P<host>[^:/\s]+):(?P<path>.+)$", url)
72
+ if scp_match:
73
+ host = scp_match.group("host")
74
+ repo_path = normalize_remote_path(scp_match.group("path"))
75
+ return {"provider": provider_from_host(host), "repo": repo_path, "host": host, "url": url}
76
+
77
+ parsed = urlparse(url)
78
+ host = parsed.hostname or ""
79
+ repo_path = normalize_remote_path(parsed.path)
80
+ return {"provider": provider_from_host(host), "repo": repo_path, "host": host, "url": url}
81
+
82
+
83
+ def parse_code_host_repo(remotes: str) -> dict[str, str]:
84
+ candidates: list[dict[str, str]] = []
85
+ for line in remotes.splitlines():
86
+ if "(push)" in line:
87
+ continue
88
+ parsed = parse_remote_url(remote_url_from_line(line))
89
+ if parsed["provider"] and parsed["repo"]:
90
+ parsed["remote_name"] = remote_name_from_line(line)
91
+ candidates.append(parsed)
92
+ for preferred in PREFERRED_REMOTE_NAMES:
93
+ for candidate in candidates:
94
+ if candidate.get("remote_name") == preferred:
95
+ candidate["selection_reason"] = f"preferred_{preferred}_fetch_remote"
96
+ return candidate
97
+ if candidates:
98
+ candidates[0]["selection_reason"] = "first_supported_fetch_remote"
99
+ return candidates[0]
100
+ return {"provider": "", "repo": "", "host": "", "url": "", "remote_name": "", "selection_reason": "none"}
101
+
102
+
103
+ def auth_args(command: str, host: str) -> list[str]:
104
+ args = ["auth", "status"]
105
+ if host and command in {"gh", "glab"}:
106
+ args.extend(["--hostname", host])
107
+ return args
108
+
109
+
110
+ def login_hint(command: str, host: str) -> str:
111
+ default_host = "github.com" if command == "gh" else "gitlab.com" if command == "glab" else ""
112
+ if host and host != default_host:
113
+ return f"{command} auth login --hostname {host}"
114
+ return f"{command} auth login"
115
+
116
+
117
+ def gitignore_has_repo_memory_rule(text: str) -> bool:
118
+ for line in text.splitlines():
119
+ stripped = line.strip()
120
+ if not stripped or stripped.startswith("#") or stripped.startswith("!"):
121
+ continue
122
+ rule = stripped.split("#", 1)[0].strip().lstrip("/")
123
+ if rule in {".repo_memory", ".repo_memory/"}:
124
+ return True
125
+ return False
126
+
127
+
128
+ def ensure_repo_memory_gitignore(repo: Path) -> bool:
129
+ gitignore = repo / ".gitignore"
130
+ existing = gitignore.read_text(encoding="utf-8") if gitignore.exists() else ""
131
+ if gitignore_has_repo_memory_rule(existing):
132
+ return False
133
+ separator = "" if not existing or existing.endswith("\n") else "\n"
134
+ gitignore.write_text(f"{existing}{separator}{REPO_MEMORY_GITIGNORE_RULE}\n", encoding="utf-8")
135
+ return True
136
+
137
+
138
+ def is_sidecar_only_memory(memory: Path) -> bool:
139
+ if not memory.exists() or not memory.is_dir() or memory.is_symlink():
140
+ return False
141
+ children = list(memory.iterdir())
142
+ return bool(children) and all(
143
+ (
144
+ child.name == "user-profile"
145
+ and child.is_dir()
146
+ and not child.is_symlink()
147
+ )
148
+ or (
149
+ child.name == "procedure-memory"
150
+ and child.is_dir()
151
+ and not child.is_symlink()
152
+ )
153
+ for child in children
154
+ )
155
+
156
+
157
+ def cli_auth_state(command: str, auth_args: list[str], login_hint: str) -> dict[str, Any]:
158
+ if shutil.which(command) is None:
159
+ return {
160
+ "available": False,
161
+ "authenticated": False,
162
+ "auth_status": "cli_missing",
163
+ "auth_error": "",
164
+ "login_hint": login_hint,
165
+ }
166
+ try:
167
+ proc = subprocess.run([command, *auth_args], text=True, capture_output=True, timeout=8)
168
+ except subprocess.TimeoutExpired:
169
+ return {
170
+ "available": True,
171
+ "authenticated": False,
172
+ "auth_status": "auth_check_timeout",
173
+ "auth_error": f"{command} auth check timed out",
174
+ "login_hint": login_hint,
175
+ }
176
+ if proc.returncode == 0:
177
+ return {
178
+ "available": True,
179
+ "authenticated": True,
180
+ "auth_status": "authenticated",
181
+ "auth_error": "",
182
+ "login_hint": login_hint,
183
+ }
184
+ error = (proc.stderr.strip() or proc.stdout.strip()).splitlines()
185
+ return {
186
+ "available": True,
187
+ "authenticated": False,
188
+ "auth_status": "auth_required",
189
+ "auth_error": error[0][:500] if error else "",
190
+ "login_hint": login_hint,
191
+ }
192
+
193
+
194
+ def provider_label(provider: str) -> str:
195
+ if provider == "github":
196
+ return "GitHub"
197
+ if provider == "gitlab":
198
+ return "GitLab"
199
+ return "Git provider"
200
+
201
+
202
+ def provider_evidence_label(provider: str) -> str:
203
+ if provider == "github":
204
+ return "GitHub PR/issue"
205
+ if provider == "gitlab":
206
+ return "GitLab MR/issue"
207
+ return "provider"
208
+
209
+
210
+ def notice_markdown(body: str, command: str, rerun_line: str) -> str:
211
+ command_block = f"\n```bash\n{command}\n```\n" if command else ""
212
+ return "\n".join(
213
+ [
214
+ "**Provider Evidence Unavailable**",
215
+ "",
216
+ f"> {body}",
217
+ "> Continuing with local-only repo memory now.",
218
+ "",
219
+ "**Next steps for provider evidence**",
220
+ command_block.rstrip(),
221
+ rerun_line,
222
+ ]
223
+ ).strip()
224
+
225
+
226
+ def non_git_repo_notice(repo: Path) -> str:
227
+ return "\n".join(
228
+ [
229
+ "**Repo Memory Cannot Be Built Yet**",
230
+ "",
231
+ f"> This folder is not a git repository: {repo}",
232
+ "> The $memorax-code repo-build operation cannot collect local commit history or provider-linked PR/MR/issue evidence.",
233
+ "",
234
+ "**Next steps**",
235
+ "- If this is an existing project, open the real cloned repo directory.",
236
+ "- If this is a new project, run `git init` first, make at least one commit, then rerun $memorax-code repo-build.",
237
+ "- If you only want file inspection, continue by inspecting files without repo memory.",
238
+ ]
239
+ )
240
+
241
+
242
+ def provider_notice(provider: str, cli: str, evidence_state: str, login_hint: str) -> dict[str, Any]:
243
+ label = provider_label(provider)
244
+ evidence_label = provider_evidence_label(provider)
245
+ if evidence_state == "ready":
246
+ return {
247
+ "provider_notice_level": "info",
248
+ "provider_user_notice": f"{label} provider evidence is ready; {evidence_label} evidence can be collected.",
249
+ "provider_notice_markdown": (
250
+ f"**Provider Evidence Ready**\n\n"
251
+ f"> {label} provider evidence is ready.\n\n"
252
+ f"`{evidence_label}` evidence can be collected."
253
+ ),
254
+ "provider_next_steps": [],
255
+ }
256
+ if evidence_state == "auth_required":
257
+ body = f"{label} provider evidence is unavailable because `{cli}` is not logged in."
258
+ rerun_line = f"Rerun $memorax-code repo-build to collect {evidence_label} evidence."
259
+ return {
260
+ "provider_notice_level": "warning",
261
+ "provider_user_notice": (
262
+ f"{body} "
263
+ f"I am continuing with local-only repo memory now. To include {evidence_label} evidence, "
264
+ f"run `{login_hint}`, then rerun $memorax-code repo-build."
265
+ ),
266
+ "provider_notice_markdown": notice_markdown(body, login_hint, rerun_line),
267
+ "provider_next_steps": [
268
+ f"Run: {login_hint}",
269
+ rerun_line,
270
+ ],
271
+ }
272
+ if evidence_state == "cli_missing":
273
+ body = f"{label} provider evidence is unavailable because `{cli}` is not installed."
274
+ rerun_line = f"Rerun $memorax-code repo-build to collect {evidence_label} evidence."
275
+ return {
276
+ "provider_notice_level": "warning",
277
+ "provider_user_notice": (
278
+ f"{body} "
279
+ f"I am continuing with local-only repo memory now. To include {evidence_label} evidence, "
280
+ f"install `{cli}`, run `{login_hint}`, then rerun $memorax-code repo-build."
281
+ ),
282
+ "provider_notice_markdown": notice_markdown(body, login_hint, rerun_line),
283
+ "provider_next_steps": [
284
+ f"Install: {cli}",
285
+ f"Run: {login_hint}",
286
+ rerun_line,
287
+ ],
288
+ }
289
+ return {
290
+ "provider_notice_level": "info",
291
+ "provider_user_notice": "No supported GitHub/GitLab remote was detected; continuing with local-only repo memory.",
292
+ "provider_notice_markdown": (
293
+ "**Provider Evidence Unavailable**\n\n"
294
+ "> No supported GitHub/GitLab remote was detected.\n"
295
+ "> Continuing with local-only repo memory."
296
+ ),
297
+ "provider_next_steps": [],
298
+ }
299
+
300
+
301
+ def provider_cli_report(provider: str, gh: dict[str, Any], glab: dict[str, Any]) -> dict[str, Any]:
302
+ if provider == "github":
303
+ state = gh
304
+ cli = "gh"
305
+ elif provider == "gitlab":
306
+ state = glab
307
+ cli = "glab"
308
+ else:
309
+ return {
310
+ "provider_cli": "",
311
+ "provider_cli_available": False,
312
+ "provider_authenticated": False,
313
+ "provider_auth_status": "no_supported_remote",
314
+ "provider_evidence_state": "unavailable",
315
+ "provider_login_hint": "",
316
+ "provider_fallback": "No supported GitHub/GitLab remote was detected; build local-only repo memory.",
317
+ **provider_notice(provider, "", "unavailable", ""),
318
+ }
319
+
320
+ if not state["available"]:
321
+ evidence_state = "cli_missing"
322
+ fallback = f"{cli} is not installed; build local-only repo memory unless the user installs the provider CLI."
323
+ elif not state["authenticated"]:
324
+ evidence_state = "auth_required"
325
+ fallback = f"{cli} is not authenticated; build local-only repo memory unless the user logs in and reruns provider collection."
326
+ else:
327
+ evidence_state = "ready"
328
+ fallback = "Provider evidence can be collected."
329
+
330
+ return {
331
+ "provider_cli": cli,
332
+ "provider_cli_available": state["available"],
333
+ "provider_authenticated": state["authenticated"],
334
+ "provider_auth_status": state["auth_status"],
335
+ "provider_evidence_state": evidence_state,
336
+ "provider_login_hint": state["login_hint"],
337
+ "provider_fallback": fallback,
338
+ **provider_notice(provider, cli, evidence_state, state["login_hint"]),
339
+ }
340
+
341
+
342
+ def main(argv: list[str]) -> int:
343
+ parser = argparse.ArgumentParser(description="Check environment and create .repo_memory directories only.")
344
+ parser.add_argument("repo", nargs="?", default=".", help="Local git repository path; defaults to current directory")
345
+ parser.add_argument("--reuse", action="store_true", help="Allow using an existing .repo_memory directory without deleting it")
346
+ args = parser.parse_args(argv)
347
+
348
+ repo = Path(args.repo).resolve()
349
+ if not repo.exists():
350
+ raise SystemExit(f"Repository path does not exist: {repo}")
351
+ if shutil.which("git") is None:
352
+ raise SystemExit("git is required but was not found on PATH")
353
+ if not is_git_repo(repo):
354
+ raise SystemExit(non_git_repo_notice(repo))
355
+
356
+ memory = repo / ".repo_memory"
357
+ if memory.exists() and not args.reuse and not is_sidecar_only_memory(memory):
358
+ raise SystemExit(f"{memory} already exists. Stop by default; rerun with --reuse to update it.")
359
+
360
+ for subdir in [memory, memory / "raw", memory / "resources"]:
361
+ subdir.mkdir(parents=True, exist_ok=True)
362
+
363
+ remotes = git_value(repo, "remote", "-v")
364
+ remote = parse_code_host_repo(remotes)
365
+ gitignore_updated = ensure_repo_memory_gitignore(repo)
366
+ gh_host = remote["host"] if remote["provider"] == "github" else ""
367
+ glab_host = remote["host"] if remote["provider"] == "gitlab" else ""
368
+ gh_state = cli_auth_state("gh", auth_args("gh", gh_host), login_hint("gh", gh_host))
369
+ glab_state = cli_auth_state("glab", auth_args("glab", glab_host), login_hint("glab", glab_host))
370
+ provider_report = provider_cli_report(remote["provider"], gh_state, glab_state)
371
+ report: dict[str, Any] = {
372
+ "prepared_at": now_iso(),
373
+ "repo_path": str(repo),
374
+ "memory_path": str(memory),
375
+ "is_git_repo": True,
376
+ "local_head": git_value(repo, "rev-parse", "HEAD"),
377
+ "local_branch": git_value(repo, "branch", "--show-current") or "(detached HEAD)",
378
+ "working_tree_state": "dirty" if git_value(repo, "status", "--short") else "clean",
379
+ "git_remotes": remotes.splitlines(),
380
+ "git_provider": remote["provider"],
381
+ "git_remote_repo": remote["repo"],
382
+ "git_remote_host": remote["host"],
383
+ "git_remote_url": remote["url"],
384
+ "git_remote_name": remote.get("remote_name", ""),
385
+ "git_remote_selection_reason": remote.get("selection_reason", ""),
386
+ "github_repo": remote["repo"] if remote["provider"] == "github" else "",
387
+ "gitlab_repo": remote["repo"] if remote["provider"] == "gitlab" else "",
388
+ "gh_available": gh_state["available"],
389
+ "gh_authenticated": gh_state["authenticated"],
390
+ "gh_auth_status": gh_state["auth_status"],
391
+ "gh_auth_error": gh_state["auth_error"],
392
+ "gh_login_hint": gh_state["login_hint"],
393
+ "glab_available": glab_state["available"],
394
+ "glab_authenticated": glab_state["authenticated"],
395
+ "glab_auth_status": glab_state["auth_status"],
396
+ "glab_auth_error": glab_state["auth_error"],
397
+ "glab_login_hint": glab_state["login_hint"],
398
+ **provider_report,
399
+ "gitignore_path": str(repo / ".gitignore"),
400
+ "gitignore_rule": REPO_MEMORY_GITIGNORE_RULE,
401
+ "gitignore_updated": gitignore_updated,
402
+ "created_directories": [".repo_memory", ".repo_memory/raw", ".repo_memory/resources"],
403
+ "next_step": "Agent must inspect local code/docs and author PROFILE.md and resources. This script intentionally does not generate memory content.",
404
+ }
405
+ (memory / "raw" / "prepare-report.json").write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
406
+ print(json.dumps(report, ensure_ascii=False, indent=2))
407
+ return 0
408
+
409
+
410
+ if __name__ == "__main__":
411
+ raise SystemExit(main(sys.argv[1:]))