@memorax/memorax-code 0.1.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 (282) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/bin/memorax-cli.mjs +28 -0
  4. package/bin/memorax-code-backend.mjs +4 -0
  5. package/bin/memorax-code-claude.mjs +4 -0
  6. package/bin/memorax-code-codex.mjs +4 -0
  7. package/bin/memorax-code-npm-preinstall.mjs +55 -0
  8. package/bin/memorax-code-plugin-postinstall.mjs +1236 -0
  9. package/bin/memorax-code.mjs +204 -0
  10. package/docs/configuration.md +232 -0
  11. package/docs/troubleshooting.md +179 -0
  12. package/lib/client-hook-runtime.mjs +32 -0
  13. package/lib/memorax-code-adapter-common/src/backend-connection.d.mts +111 -0
  14. package/lib/memorax-code-adapter-common/src/backend-connection.mjs +304 -0
  15. package/lib/memorax-code-adapter-common/src/clients/claude-command.mjs +38 -0
  16. package/lib/memorax-code-adapter-common/src/clients/codex-command.mjs +41 -0
  17. package/lib/memorax-code-adapter-common/src/clients/codex-plugin-artifact.d.mts +3 -0
  18. package/lib/memorax-code-adapter-common/src/clients/codex-plugin-artifact.mjs +38 -0
  19. package/lib/memorax-code-adapter-common/src/config-utils.d.mts +27 -0
  20. package/lib/memorax-code-adapter-common/src/config-utils.mjs +391 -0
  21. package/lib/memorax-code-adapter-common/src/hooks/capture-cwd-hook.mjs +137 -0
  22. package/lib/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +324 -0
  23. package/lib/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +142 -0
  24. package/lib/memorax-code-adapter-common/src/hooks/hook-runtime-generation.d.mts +38 -0
  25. package/lib/memorax-code-adapter-common/src/hooks/hook-runtime-generation.mjs +501 -0
  26. package/lib/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +277 -0
  27. package/lib/memorax-code-adapter-common/src/memorax-code-config-file.d.mts +28 -0
  28. package/lib/memorax-code-adapter-common/src/memorax-code-config-file.mjs +296 -0
  29. package/lib/memorax-code-adapter-common/src/memorax-defaults.d.mts +10 -0
  30. package/lib/memorax-code-adapter-common/src/memorax-defaults.mjs +12 -0
  31. package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-job-context.mjs +28 -0
  32. package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-job-marker.mjs +252 -0
  33. package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-job-supervisor.mjs +559 -0
  34. package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-job-worker.mjs +411 -0
  35. package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-update-policy-evaluator.mjs +185 -0
  36. package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-update-policy.mjs +165 -0
  37. package/lib/memorax-code-adapter-common/src/repo-memory/repo-procedure-memory-context.mjs +181 -0
  38. package/lib/memorax-code-adapter-common/src/repo-memory/repo-user-profile-context.mjs +251 -0
  39. package/lib/memorax-code-adapter-common/src/runtime-record.d.mts +55 -0
  40. package/lib/memorax-code-adapter-common/src/runtime-record.mjs +152 -0
  41. package/lib/memorax-code-adapter-common/src/windows-cli-invocation.mjs +100 -0
  42. package/lib/memorax-code-backend/dist/active-client-selection.js +35 -0
  43. package/lib/memorax-code-backend/dist/adapter-lifecycle-participant.js +1 -0
  44. package/lib/memorax-code-backend/dist/automatic-memory-retrieval.js +113 -0
  45. package/lib/memorax-code-backend/dist/automatic-memory-writeback.js +297 -0
  46. package/lib/memorax-code-backend/dist/backend-env.js +13 -0
  47. package/lib/memorax-code-backend/dist/backend-lifecycle-lock.js +42 -0
  48. package/lib/memorax-code-backend/dist/backend-process.js +147 -0
  49. package/lib/memorax-code-backend/dist/backend-service-cleanup.js +18 -0
  50. package/lib/memorax-code-backend/dist/backend-service-record.js +206 -0
  51. package/lib/memorax-code-backend/dist/backend-service-result.js +37 -0
  52. package/lib/memorax-code-backend/dist/backend-service-token.js +11 -0
  53. package/lib/memorax-code-backend/dist/backend-shutdown-request.js +133 -0
  54. package/lib/memorax-code-backend/dist/backend-state.js +58 -0
  55. package/lib/memorax-code-backend/dist/backend-status.js +53 -0
  56. package/lib/memorax-code-backend/dist/backend-token-record.js +79 -0
  57. package/lib/memorax-code-backend/dist/claude-adapter-lifecycle.js +134 -0
  58. package/lib/memorax-code-backend/dist/claude-local-transcript.js +648 -0
  59. package/lib/memorax-code-backend/dist/claude-memory-hook-runtime.js +472 -0
  60. package/lib/memorax-code-backend/dist/claude-transcript-turn.js +876 -0
  61. package/lib/memorax-code-backend/dist/client-plugin-removal.js +67 -0
  62. package/lib/memorax-code-backend/dist/client-selection.js +36 -0
  63. package/lib/memorax-code-backend/dist/codex-adapter-lifecycle.js +131 -0
  64. package/lib/memorax-code-backend/dist/codex-effective-prompt.js +54 -0
  65. package/lib/memorax-code-backend/dist/codex-memory-hook-runtime.js +541 -0
  66. package/lib/memorax-code-backend/dist/codex-plugin-hooks.js +421 -0
  67. package/lib/memorax-code-backend/dist/codex-plugin-install.js +523 -0
  68. package/lib/memorax-code-backend/dist/codex-rollout-turn.js +402 -0
  69. package/lib/memorax-code-backend/dist/codex-session-turn-index.js +88 -0
  70. package/lib/memorax-code-backend/dist/codex-workspace-links.js +55 -0
  71. package/lib/memorax-code-backend/dist/debug-log.js +21 -0
  72. package/lib/memorax-code-backend/dist/entrypoint.js +14 -0
  73. package/lib/memorax-code-backend/dist/incremental-jsonl-projection.js +271 -0
  74. package/lib/memorax-code-backend/dist/install-watchdog.js +71 -0
  75. package/lib/memorax-code-backend/dist/jsonl-append.js +75 -0
  76. package/lib/memorax-code-backend/dist/memorax-adapter.js +451 -0
  77. package/lib/memorax-code-backend/dist/memorax-cli.js +14 -0
  78. package/lib/memorax-code-backend/dist/memorax-code-config.js +279 -0
  79. package/lib/memorax-code-backend/dist/memorax-code-lifecycle.js +523 -0
  80. package/lib/memorax-code-backend/dist/memorax-code.js +3 -0
  81. package/lib/memorax-code-backend/dist/memorax-config.js +283 -0
  82. package/lib/memorax-code-backend/dist/memorax-http.js +142 -0
  83. package/lib/memorax-code-backend/dist/memory-cli.js +345 -0
  84. package/lib/memorax-code-backend/dist/memory-hook-command.js +200 -0
  85. package/lib/memorax-code-backend/dist/memory-observability.js +1 -0
  86. package/lib/memorax-code-backend/dist/memory-project.js +89 -0
  87. package/lib/memorax-code-backend/dist/memory-reminder-trace-recorder.js +58 -0
  88. package/lib/memorax-code-backend/dist/memory-service.js +66 -0
  89. package/lib/memorax-code-backend/dist/memory-turn-coordinator.js +139 -0
  90. package/lib/memorax-code-backend/dist/memory-viewer-activity.js +113 -0
  91. package/lib/memorax-code-backend/dist/memory-viewer-data-projection-cache.js +69 -0
  92. package/lib/memorax-code-backend/dist/memory-viewer-event-identity.js +3 -0
  93. package/lib/memorax-code-backend/dist/memory-viewer-history-projection.js +79 -0
  94. package/lib/memorax-code-backend/dist/memory-viewer-logo.js +1 -0
  95. package/lib/memorax-code-backend/dist/memory-viewer-observability.js +8 -0
  96. package/lib/memorax-code-backend/dist/memory-viewer-redaction.js +52 -0
  97. package/lib/memorax-code-backend/dist/memory-viewer-session-title.js +93 -0
  98. package/lib/memorax-code-backend/dist/memory-viewer-store.js +964 -0
  99. package/lib/memorax-code-backend/dist/memory-viewer-turn-reference.js +12 -0
  100. package/lib/memorax-code-backend/dist/memory-viewer-user-html.js +115 -0
  101. package/lib/memorax-code-backend/dist/memory-viewer-user-projection.js +168 -0
  102. package/lib/memorax-code-backend/dist/memory-viewer-writeback-status.js +97 -0
  103. package/lib/memorax-code-backend/dist/memory-writeback-buffer.js +217 -0
  104. package/lib/memorax-code-backend/dist/memory-writeback-chunk.js +120 -0
  105. package/lib/memorax-code-backend/dist/memory-writeback-reconciler.js +244 -0
  106. package/lib/memorax-code-backend/dist/memory-writeback-task-projection.js +377 -0
  107. package/lib/memorax-code-backend/dist/proxy-env.js +54 -0
  108. package/lib/memorax-code-backend/dist/repo-memory-readiness.js +188 -0
  109. package/lib/memorax-code-backend/dist/repository-memory-context.js +134 -0
  110. package/lib/memorax-code-backend/dist/repository-memory-scope.js +490 -0
  111. package/lib/memorax-code-backend/dist/server-cli.js +787 -0
  112. package/lib/memorax-code-backend/dist/server-health.js +16 -0
  113. package/lib/memorax-code-backend/dist/server-http.js +110 -0
  114. package/lib/memorax-code-backend/dist/server-json.js +4 -0
  115. package/lib/memorax-code-backend/dist/server-memory-hook.js +32 -0
  116. package/lib/memorax-code-backend/dist/server-memory-viewer.js +135 -0
  117. package/lib/memorax-code-backend/dist/server-observability.js +102 -0
  118. package/lib/memorax-code-backend/dist/server.js +220 -0
  119. package/lib/memorax-code-backend/dist/service-entrypoint.js +10 -0
  120. package/lib/memorax-code-backend/dist/service.js +567 -0
  121. package/lib/memorax-code-backend/dist/trace-config.js +112 -0
  122. package/lib/memorax-code-backend/dist/trace-context.js +111 -0
  123. package/lib/memorax-code-backend/dist/trace-store.js +571 -0
  124. package/lib/memorax-code-backend/dist/utils.js +3 -0
  125. package/lib/memorax-code-backend/dist/windows-cli-invocation.js +148 -0
  126. package/lib/memorax-code-backend/package.json +26 -0
  127. package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +15 -0
  128. package/lib/memorax-code-claude-adapter/hooks/hook-launcher.mjs +27 -0
  129. package/lib/memorax-code-claude-adapter/hooks/hooks.json +66 -0
  130. package/lib/memorax-code-claude-adapter/hooks/repo-memory-job.mjs +37 -0
  131. package/lib/memorax-code-claude-adapter/hooks/runtime-hook.mjs +4 -0
  132. package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +5 -0
  133. package/lib/memorax-code-claude-adapter/package.json +13 -0
  134. package/lib/memorax-code-claude-adapter/runtime-hooks/capture-cwd.mjs +17 -0
  135. package/lib/memorax-code-claude-adapter/runtime-hooks/ensure-backend.mjs +59 -0
  136. package/lib/memorax-code-claude-adapter/runtime-hooks/memory-cli-session.mjs +33 -0
  137. package/lib/memorax-code-claude-adapter/runtime-hooks/memory-skill-reminder.mjs +73 -0
  138. package/lib/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +91 -0
  139. package/lib/memorax-code-claude-adapter/scripts/build-marketplace.mjs +99 -0
  140. package/lib/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +83 -0
  141. package/lib/memorax-code-claude-adapter/skills/memorax-code/agents/claude.yaml +10 -0
  142. package/lib/memorax-code-claude-adapter/skills/memorax-code/agents/openai.yaml +7 -0
  143. package/lib/memorax-code-claude-adapter/skills/memorax-code/defaults.json +9 -0
  144. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +82 -0
  145. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +61 -0
  146. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/personal-read.md +46 -0
  147. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/personal-write.md +100 -0
  148. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/repo-build.md +270 -0
  149. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/repo-read.md +96 -0
  150. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/repo-templates.md +270 -0
  151. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/repo-update.md +116 -0
  152. package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/collect_all.py +496 -0
  153. package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/detect_updates.py +856 -0
  154. package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/git_commit_facets.py +222 -0
  155. package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/github_resource_facets.py +504 -0
  156. package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +509 -0
  157. package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/prepare_repo_memory.py +411 -0
  158. package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/user_profile_memory.py +528 -0
  159. package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/validate_memory.py +226 -0
  160. package/lib/memorax-code-claude-adapter/src/adapter-paths.mjs +46 -0
  161. package/lib/memorax-code-claude-adapter/src/cli.mjs +363 -0
  162. package/lib/memorax-code-claude-adapter/src/config-utils.mjs +1 -0
  163. package/lib/memorax-code-claude-adapter/src/config.mjs +544 -0
  164. package/lib/memorax-code-claude-adapter/src/plugin-artifact-contract.mjs +148 -0
  165. package/lib/memorax-code-claude-adapter/src/plugin-install.mjs +566 -0
  166. package/lib/memorax-code-claude-marketplace/.claude-plugin/marketplace.json +15 -0
  167. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +15 -0
  168. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/hook-launcher.mjs +27 -0
  169. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/hooks.json +66 -0
  170. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/repo-memory-job.mjs +37 -0
  171. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-hook.mjs +4 -0
  172. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +5 -0
  173. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/backend-connection.d.mts +111 -0
  174. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/backend-connection.mjs +304 -0
  175. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/clients/claude-command.mjs +38 -0
  176. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/clients/codex-command.mjs +41 -0
  177. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/clients/codex-plugin-artifact.d.mts +3 -0
  178. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/clients/codex-plugin-artifact.mjs +38 -0
  179. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.d.mts +27 -0
  180. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.mjs +391 -0
  181. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/capture-cwd-hook.mjs +137 -0
  182. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +324 -0
  183. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +142 -0
  184. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/hook-runtime-generation.d.mts +38 -0
  185. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/hook-runtime-generation.mjs +501 -0
  186. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +277 -0
  187. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/memorax-code-config-file.d.mts +28 -0
  188. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/memorax-code-config-file.mjs +296 -0
  189. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/memorax-defaults.d.mts +10 -0
  190. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/memorax-defaults.mjs +12 -0
  191. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-job-context.mjs +28 -0
  192. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-job-marker.mjs +252 -0
  193. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-job-supervisor.mjs +559 -0
  194. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-job-worker.mjs +411 -0
  195. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-update-policy-evaluator.mjs +185 -0
  196. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-update-policy.mjs +165 -0
  197. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-procedure-memory-context.mjs +181 -0
  198. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-user-profile-context.mjs +251 -0
  199. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/runtime-record.d.mts +55 -0
  200. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/runtime-record.mjs +152 -0
  201. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/windows-cli-invocation.mjs +100 -0
  202. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +13 -0
  203. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/capture-cwd.mjs +17 -0
  204. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/ensure-backend.mjs +59 -0
  205. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/memory-cli-session.mjs +33 -0
  206. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/memory-skill-reminder.mjs +73 -0
  207. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +91 -0
  208. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/scripts/build-marketplace.mjs +99 -0
  209. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +83 -0
  210. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/agents/claude.yaml +10 -0
  211. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/agents/openai.yaml +7 -0
  212. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/defaults.json +9 -0
  213. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +82 -0
  214. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +61 -0
  215. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/personal-read.md +46 -0
  216. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/personal-write.md +100 -0
  217. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/repo-build.md +270 -0
  218. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/repo-read.md +96 -0
  219. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/repo-templates.md +270 -0
  220. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/repo-update.md +116 -0
  221. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/collect_all.py +496 -0
  222. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/detect_updates.py +856 -0
  223. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/git_commit_facets.py +222 -0
  224. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/github_resource_facets.py +504 -0
  225. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +509 -0
  226. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/prepare_repo_memory.py +411 -0
  227. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/user_profile_memory.py +528 -0
  228. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/validate_memory.py +226 -0
  229. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/adapter-paths.mjs +46 -0
  230. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/cli.mjs +363 -0
  231. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/config-utils.mjs +1 -0
  232. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/config.mjs +544 -0
  233. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/plugin-artifact-contract.mjs +148 -0
  234. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/plugin-install.mjs +566 -0
  235. package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +33 -0
  236. package/lib/memorax-code-codex-adapter/assets/composer-icon.png +0 -0
  237. package/lib/memorax-code-codex-adapter/assets/logo.png +0 -0
  238. package/lib/memorax-code-codex-adapter/hooks/hook-launcher.mjs +26 -0
  239. package/lib/memorax-code-codex-adapter/hooks/hooks.json +61 -0
  240. package/lib/memorax-code-codex-adapter/hooks/repo-memory-job.mjs +38 -0
  241. package/lib/memorax-code-codex-adapter/hooks/repo-memory-update-policy.mjs +10 -0
  242. package/lib/memorax-code-codex-adapter/hooks/runtime-hook.mjs +4 -0
  243. package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +5 -0
  244. package/lib/memorax-code-codex-adapter/package.json +13 -0
  245. package/lib/memorax-code-codex-adapter/runtime-hooks/capture-cwd.mjs +18 -0
  246. package/lib/memorax-code-codex-adapter/runtime-hooks/ensure-backend.mjs +82 -0
  247. package/lib/memorax-code-codex-adapter/runtime-hooks/memory-skill-reminder.mjs +125 -0
  248. package/lib/memorax-code-codex-adapter/runtime-hooks/memory-writeback.mjs +52 -0
  249. package/lib/memorax-code-codex-adapter/skills/memorax-code/SKILL.md +83 -0
  250. package/lib/memorax-code-codex-adapter/skills/memorax-code/agents/claude.yaml +10 -0
  251. package/lib/memorax-code-codex-adapter/skills/memorax-code/agents/openai.yaml +7 -0
  252. package/lib/memorax-code-codex-adapter/skills/memorax-code/defaults.json +9 -0
  253. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +82 -0
  254. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +61 -0
  255. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/personal-read.md +46 -0
  256. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/personal-write.md +100 -0
  257. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/repo-build.md +270 -0
  258. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/repo-read.md +96 -0
  259. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/repo-templates.md +270 -0
  260. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/repo-update.md +116 -0
  261. package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/collect_all.py +496 -0
  262. package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/detect_updates.py +856 -0
  263. package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/git_commit_facets.py +222 -0
  264. package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/github_resource_facets.py +504 -0
  265. package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +509 -0
  266. package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/prepare_repo_memory.py +411 -0
  267. package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/user_profile_memory.py +528 -0
  268. package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/validate_memory.py +226 -0
  269. package/lib/memorax-code-codex-adapter/src/adapter-paths.mjs +29 -0
  270. package/lib/memorax-code-codex-adapter/src/cli.mjs +368 -0
  271. package/lib/memorax-code-codex-adapter/src/config-utils.mjs +1 -0
  272. package/lib/memorax-code-codex-adapter/src/config.mjs +226 -0
  273. package/lib/memorax-code-codex-adapter/src/session-registry.mjs +331 -0
  274. package/lib/memorax-code-codex-adapter/src/workspace-kind.mjs +68 -0
  275. package/lib/node-version.mjs +11 -0
  276. package/lib/npm-invocation.mjs +31 -0
  277. package/lib/resolve-claude-command.mjs +47 -0
  278. package/lib/resolve-codex-command.mjs +94 -0
  279. package/lib/run-entrypoint.mjs +83 -0
  280. package/lib/vscode-extension-command.mjs +119 -0
  281. package/lib/windows-cli-invocation.mjs +100 -0
  282. package/package.json +45 -0
@@ -0,0 +1,856 @@
1
+ #!/usr/bin/env python3
2
+ """Detect incremental repo-memory updates from git and optional provider evidence."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import re
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ import tempfile
13
+ from pathlib import Path
14
+ from typing import Any
15
+ from urllib.parse import urlparse
16
+
17
+
18
+ SCRIPT_DIR = Path(__file__).resolve().parent
19
+ SKILL_DIR = SCRIPT_DIR.parent
20
+ BUILDER_SCRIPTS_DIR = SCRIPT_DIR
21
+ BUILDER_DEFAULTS_PATH = SKILL_DIR / "defaults.json"
22
+ BUILDER_SKILL_DIR = SKILL_DIR
23
+ BUILDER_HELPER_FILES = {
24
+ "defaults": BUILDER_DEFAULTS_PATH,
25
+ "validate_memory": BUILDER_SCRIPTS_DIR / "validate_memory.py",
26
+ "github_resource_facets": BUILDER_SCRIPTS_DIR / "github_resource_facets.py",
27
+ "gitlab_resource_facets": BUILDER_SCRIPTS_DIR / "gitlab_resource_facets.py",
28
+ }
29
+ PREFERRED_REMOTE_NAMES = ("upstream", "origin")
30
+ FALLBACK_DEFAULTS = {
31
+ "limits": {
32
+ "prs": 30,
33
+ "issues": 30,
34
+ },
35
+ "summaryChars": 4000,
36
+ }
37
+
38
+
39
+ def run(cmd: list[str], cwd: Path) -> subprocess.CompletedProcess[str]:
40
+ return subprocess.run(cmd, cwd=cwd, text=True, capture_output=True)
41
+
42
+
43
+ def git(repo: Path, args: list[str], message: str) -> str:
44
+ if shutil.which("git") is None:
45
+ raise SystemExit("git is required for repo-memory incremental update detection")
46
+ result = run(["git", "-C", str(repo), *args], repo)
47
+ if result.returncode != 0:
48
+ raise SystemExit(f"{message}:\n{result.stderr.strip()}")
49
+ return result.stdout.strip()
50
+
51
+
52
+ def load_json(path: Path, fallback: Any) -> Any:
53
+ if not path.exists():
54
+ return fallback
55
+ try:
56
+ return json.loads(path.read_text(encoding="utf-8"))
57
+ except json.JSONDecodeError as exc:
58
+ raise SystemExit(f"{path}: invalid JSON at line {exc.lineno}, column {exc.colno}: {exc.msg}") from exc
59
+
60
+
61
+ def default_int(settings: dict[str, Any], path: list[str], fallback: int) -> int:
62
+ value: Any = settings
63
+ for key in path:
64
+ value = value.get(key) if isinstance(value, dict) else None
65
+ if value is None:
66
+ return fallback
67
+ if isinstance(value, bool) or not isinstance(value, int):
68
+ raise ValueError(f"{BUILDER_DEFAULTS_PATH}: {'.'.join(path)} must be an integer")
69
+ return value
70
+
71
+
72
+ def load_default_settings() -> tuple[dict[str, Any], str]:
73
+ if not BUILDER_DEFAULTS_PATH.exists():
74
+ return FALLBACK_DEFAULTS, "hardcoded_fallback"
75
+ data = load_json(BUILDER_DEFAULTS_PATH, FALLBACK_DEFAULTS)
76
+ if not isinstance(data, dict):
77
+ raise ValueError(f"{BUILDER_DEFAULTS_PATH}: expected a JSON object")
78
+ return data, str(BUILDER_DEFAULTS_PATH)
79
+
80
+
81
+ def validate_range(parser: argparse.ArgumentParser, name: str, value: int, minimum: int, maximum: int) -> None:
82
+ if value < minimum or value > maximum:
83
+ parser.error(f"{name} must be from {minimum} to {maximum}")
84
+
85
+
86
+ def apply_effective_settings(args: argparse.Namespace, parser: argparse.ArgumentParser) -> argparse.Namespace:
87
+ try:
88
+ defaults, source = load_default_settings()
89
+ default_pr_limit = default_int(defaults, ["limits", "prs"], FALLBACK_DEFAULTS["limits"]["prs"])
90
+ default_issue_limit = default_int(defaults, ["limits", "issues"], FALLBACK_DEFAULTS["limits"]["issues"])
91
+ default_summary_chars = default_int(defaults, ["summaryChars"], FALLBACK_DEFAULTS["summaryChars"])
92
+ except ValueError as exc:
93
+ parser.error(str(exc))
94
+
95
+ pr_limit = args.pr_limit if args.pr_limit is not None else default_pr_limit
96
+ issue_limit = args.issue_limit if args.issue_limit is not None else default_issue_limit
97
+ summary_chars = args.summary_chars if args.summary_chars is not None else default_summary_chars
98
+
99
+ validate_range(parser, "--pr-limit", pr_limit, 1, 100)
100
+ validate_range(parser, "--issue-limit", issue_limit, 1, 100)
101
+ if summary_chars < 100:
102
+ parser.error("--summary-chars must be at least 100")
103
+
104
+ overrides: dict[str, int] = {}
105
+ if args.pr_limit is not None:
106
+ overrides["pr_limit"] = args.pr_limit
107
+ if args.issue_limit is not None:
108
+ overrides["issue_limit"] = args.issue_limit
109
+ if args.summary_chars is not None:
110
+ overrides["summary_chars"] = args.summary_chars
111
+
112
+ args.pr_limit = pr_limit
113
+ args.issue_limit = issue_limit
114
+ args.summary_chars = summary_chars
115
+ args.effective_settings = {
116
+ "limits": {
117
+ "prs": pr_limit,
118
+ "issues": issue_limit,
119
+ },
120
+ "summary_chars": summary_chars,
121
+ "source": source,
122
+ "overrides": overrides,
123
+ }
124
+ return args
125
+
126
+
127
+ def file_fingerprint(path: Path) -> dict[str, Any]:
128
+ item: dict[str, Any] = {
129
+ "path": str(path),
130
+ "exists": path.exists(),
131
+ }
132
+ if path.exists():
133
+ stat = path.stat()
134
+ item["mtime_ns"] = stat.st_mtime_ns
135
+ item["size_bytes"] = stat.st_size
136
+ return item
137
+
138
+
139
+ def builder_helper_report() -> dict[str, Any]:
140
+ return {
141
+ "skill_dir": str(BUILDER_SKILL_DIR),
142
+ "scripts_dir": str(BUILDER_SCRIPTS_DIR),
143
+ "files": {
144
+ name: file_fingerprint(path)
145
+ for name, path in BUILDER_HELPER_FILES.items()
146
+ },
147
+ }
148
+
149
+
150
+ def parse_frontmatter(path: Path) -> dict[str, str]:
151
+ if not path.exists():
152
+ return {}
153
+ text = path.read_text(encoding="utf-8")
154
+ if not text.startswith("---\n"):
155
+ return {}
156
+ end = text.find("\n---", 4)
157
+ if end == -1:
158
+ return {}
159
+ fields: dict[str, str] = {}
160
+ for line in text[4:end].splitlines():
161
+ if ":" not in line:
162
+ continue
163
+ key, value = line.split(":", 1)
164
+ key = key.strip()
165
+ value = value.strip().strip('"').strip("'")
166
+ if key:
167
+ fields[key] = value
168
+ return fields
169
+
170
+
171
+ def resolve_memory(repo: Path, memory_arg: str | None) -> Path:
172
+ return Path(memory_arg).expanduser().resolve() if memory_arg else repo / ".repo_memory"
173
+
174
+
175
+ def body_after_frontmatter(text: str) -> str:
176
+ if not text.startswith("---\n"):
177
+ return text
178
+ end = text.find("\n---", 4)
179
+ if end == -1:
180
+ return text
181
+ return text[end + 4 :].lstrip("\n")
182
+
183
+
184
+ def commit_baseline(memory: Path, repo: Path | None = None, head_sha: str = "") -> str:
185
+ profile_head = parse_frontmatter(memory / "PROFILE.md").get("local_head", "")
186
+ if profile_head:
187
+ return profile_head
188
+ text_path = memory / "resources" / "commits.md"
189
+ if text_path.exists():
190
+ text = body_after_frontmatter(text_path.read_text(encoding="utf-8"))
191
+ shas = re.findall(r"(?im)^-\s*SHA:\s*`?([0-9a-f]{7,40})`?", text)
192
+ if repo is not None and head_sha and shas:
193
+ nearest = nearest_ancestor(repo, shas, head_sha)
194
+ if nearest:
195
+ return nearest
196
+ if shas:
197
+ return shas[0]
198
+ return ""
199
+
200
+
201
+ def is_ancestor(repo: Path, ancestor: str, descendant: str) -> bool:
202
+ if not ancestor:
203
+ return False
204
+ result = run(["git", "-C", str(repo), "merge-base", "--is-ancestor", ancestor, descendant], repo)
205
+ return result.returncode == 0
206
+
207
+
208
+ def nearest_ancestor(repo: Path, candidates: list[str], descendant: str) -> str:
209
+ unique_candidates = list(dict.fromkeys(candidate for candidate in candidates if candidate))
210
+ if not unique_candidates:
211
+ return ""
212
+ result = run(["git", "-C", str(repo), "rev-list", descendant], repo)
213
+ if result.returncode != 0:
214
+ return ""
215
+ for sha in [line.strip() for line in result.stdout.splitlines() if line.strip()]:
216
+ for candidate in unique_candidates:
217
+ if sha.startswith(candidate):
218
+ return candidate
219
+ return ""
220
+
221
+
222
+ def commit_delta(repo: Path, baseline_sha: str, head_sha: str) -> tuple[list[dict[str, Any]], dict[str, Any]]:
223
+ if not baseline_sha:
224
+ return [], {
225
+ "status": "skipped",
226
+ "reason": "missing_baseline_commit",
227
+ "message": "No stored local commit baseline was found in .repo_memory.",
228
+ }
229
+ if baseline_sha == head_sha:
230
+ return [], {"status": "current", "reason": ""}
231
+ if not is_ancestor(repo, baseline_sha, head_sha):
232
+ return [], {
233
+ "status": "skipped",
234
+ "reason": "baseline_not_ancestor_of_head",
235
+ "message": "Stored repo-memory commit baseline is not an ancestor of the current HEAD; history was probably rebased or force-pushed.",
236
+ }
237
+ shas = git(repo, ["rev-list", "--reverse", f"{baseline_sha}..{head_sha}"], "git could not list new commits")
238
+ out: list[dict[str, Any]] = []
239
+ for sha in [line.strip() for line in shas.splitlines() if line.strip()]:
240
+ fields = git(
241
+ repo,
242
+ ["show", "-s", "--date=iso-strict", "--format=%H%x1f%h%x1f%an%x1f%ad%x1f%s", sha],
243
+ f"git could not read commit {sha}",
244
+ ).split("\x1f")
245
+ full_sha, short_sha, author, authored_at, title = (fields + [""] * 5)[:5]
246
+ files = git(repo, ["show", "--format=", "--name-only", "--no-renames", sha], f"git could not list files for {sha}")
247
+ out.append({
248
+ "sha": full_sha,
249
+ "short_sha": short_sha,
250
+ "title": title,
251
+ "author": author,
252
+ "authored_at": authored_at,
253
+ "files": [line for line in files.splitlines() if line],
254
+ })
255
+ return out, {"status": "ok", "reason": "", "count": len(out)}
256
+
257
+
258
+ def markdown_sections(path: Path) -> list[tuple[str, str]]:
259
+ if not path.exists():
260
+ return []
261
+ text = body_after_frontmatter(path.read_text(encoding="utf-8"))
262
+ matches = list(re.finditer(r"(?m)^##\s+(.+?)\s*$", text))
263
+ sections: list[tuple[str, str]] = []
264
+ for index, match in enumerate(matches):
265
+ start = match.end()
266
+ end = matches[index + 1].start() if index + 1 < len(matches) else len(text)
267
+ sections.append((match.group(1).strip(), text[start:end]))
268
+ return sections
269
+
270
+
271
+ def markdown_field(body: str, label: str) -> str:
272
+ pattern = rf"(?im)^-\s*{re.escape(label)}:\s*(.+?)\s*$"
273
+ match = re.search(pattern, body)
274
+ if not match:
275
+ return ""
276
+ value = match.group(1).strip()
277
+ return value.strip().strip("`").strip()
278
+
279
+
280
+ def first_code_value(text: str) -> str:
281
+ match = re.search(r"`([^`]+)`", text)
282
+ return match.group(1).strip() if match else text.strip()
283
+
284
+
285
+ def section_number(title: str) -> int | None:
286
+ match = re.search(r"[#!#]\s*(\d+)", title)
287
+ return int(match.group(1)) if match else None
288
+
289
+
290
+ def section_title(title: str) -> str:
291
+ return title.split(":", 1)[1].strip() if ":" in title else title.strip()
292
+
293
+
294
+ def branch_parts(value: str) -> tuple[str, str]:
295
+ if "<-" not in value:
296
+ return "", ""
297
+ base, head = value.split("<-", 1)
298
+ return base.strip(), head.strip()
299
+
300
+
301
+ def resource_items(memory: Path, resource_name: str) -> list[dict[str, Any]]:
302
+ path = memory / "resources" / resource_name
303
+ kind = "issue" if resource_name == "issues.md" else "pr"
304
+ out: list[dict[str, Any]] = []
305
+ for title, body in markdown_sections(path):
306
+ number = section_number(title)
307
+ if number is None:
308
+ continue
309
+ item: dict[str, Any] = {
310
+ "number": number,
311
+ "title": section_title(title),
312
+ "source": "resource",
313
+ }
314
+ state = first_code_value(markdown_field(body, "State"))
315
+ if state:
316
+ item["state"] = state
317
+ url = markdown_field(body, "URL")
318
+ if url:
319
+ item["url"] = url
320
+ if kind == "pr":
321
+ base_ref, head_ref = branch_parts(first_code_value(markdown_field(body, "Branch")))
322
+ if base_ref:
323
+ item["base_ref"] = base_ref
324
+ if head_ref:
325
+ item["head_ref"] = head_ref
326
+ out.append(item)
327
+ return out
328
+
329
+
330
+ def first_int(values: Any) -> int | None:
331
+ if isinstance(values, int):
332
+ return values
333
+ if isinstance(values, list):
334
+ for value in values:
335
+ if isinstance(value, int):
336
+ return value
337
+ return None
338
+
339
+
340
+ def facet_number(facet: dict[str, Any], source_type: str) -> int | None:
341
+ values = facet.get("issues") if source_type == "issue" else facet.get("prs")
342
+ number = first_int(values)
343
+ if number is not None:
344
+ return number
345
+ match = re.search(r"\.(\d+)$", str(facet.get("facetId", "")))
346
+ return int(match.group(1)) if match else None
347
+
348
+
349
+ AUTHORING_FACET_KEYS = [
350
+ "summary",
351
+ "labels",
352
+ "commits",
353
+ "commit_headlines",
354
+ "review_decision",
355
+ "review_states",
356
+ "files",
357
+ "symbols",
358
+ "evidence",
359
+ "changed_files",
360
+ "additions",
361
+ "deletions",
362
+ ]
363
+
364
+
365
+ def keep_value(value: Any) -> bool:
366
+ return value not in (None, "", [], {})
367
+
368
+
369
+ def facet_items(facets: Any, source_type: str) -> list[dict[str, Any]]:
370
+ if not isinstance(facets, list):
371
+ return []
372
+ out: list[dict[str, Any]] = []
373
+ for facet in facets:
374
+ if not isinstance(facet, dict) or facet.get("sourceType") != source_type:
375
+ continue
376
+ number = facet_number(facet, source_type)
377
+ if number is None:
378
+ continue
379
+ item: dict[str, Any] = {
380
+ "number": number,
381
+ "title": str(facet.get("title") or ""),
382
+ "state": str(facet.get("state") or ""),
383
+ "updated_at": str(facet.get("updatedAt") or facet.get("updated_at") or ""),
384
+ "url": str(facet.get("url") or ""),
385
+ "source": "provider_facet",
386
+ }
387
+ facet_id = str(facet.get("facetId") or "")
388
+ if facet_id:
389
+ item["raw_lookup"] = f"facetId={facet_id}"
390
+ for key in AUTHORING_FACET_KEYS:
391
+ if key in facet:
392
+ item[key] = facet[key]
393
+ if source_type == "pr":
394
+ item["base_ref"] = str(facet.get("base_ref") or "")
395
+ item["head_ref"] = str(facet.get("head_ref") or "")
396
+ item["merged_at"] = str(facet.get("mergedAt") or facet.get("merged_at") or "")
397
+ item["closed_at"] = str(facet.get("closedAt") or facet.get("closed_at") or "")
398
+ linked_issues = facet.get("issues")
399
+ if keep_value(linked_issues):
400
+ item["linked_issues"] = linked_issues
401
+ out.append({key: value for key, value in item.items() if keep_value(value)})
402
+ return out
403
+
404
+
405
+ def raw_provider_items(memory: Path, source_type: str) -> list[dict[str, Any]]:
406
+ out: list[dict[str, Any]] = []
407
+ for raw_name in ["github-facets.json", "gitlab-facets.json"]:
408
+ raw_path = memory / "raw" / raw_name
409
+ if raw_path.exists():
410
+ out.extend(facet_items(load_json(raw_path, []), source_type))
411
+ return out
412
+
413
+
414
+ def baseline_items(memory: Path, resource_name: str, source_type: str) -> list[dict[str, Any]]:
415
+ resource = resource_items(memory, resource_name)
416
+ raw_by_no = by_number(raw_provider_items(memory, source_type))
417
+ out: list[dict[str, Any]] = []
418
+ seen: set[int] = set()
419
+ for item in resource:
420
+ number = item_number(item)
421
+ if number is None:
422
+ continue
423
+ merged = dict(item)
424
+ merged.update(raw_by_no.get(number, {}))
425
+ if number in raw_by_no:
426
+ merged["source"] = "resource_with_raw_provider_facet"
427
+ out.append(merged)
428
+ seen.add(number)
429
+ for number, item in sorted(raw_by_no.items()):
430
+ if number not in seen:
431
+ out.append(item)
432
+ return out
433
+
434
+
435
+ def item_number(item: dict[str, Any]) -> int | None:
436
+ number = item.get("number")
437
+ return number if isinstance(number, int) else None
438
+
439
+
440
+ def by_number(items: list[dict[str, Any]]) -> dict[int, dict[str, Any]]:
441
+ out: dict[int, dict[str, Any]] = {}
442
+ for item in items:
443
+ number = item_number(item)
444
+ if number is not None:
445
+ out[number] = item
446
+ return out
447
+
448
+
449
+ def comparable_item(item: dict[str, Any]) -> str:
450
+ ignored_keys = {"raw_lookup", "source", "updated_at", "merged_at", "closed_at"}
451
+ normalized = {key: value for key, value in item.items() if key not in ignored_keys}
452
+ return json.dumps(normalized, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
453
+
454
+
455
+ def changed_numbers(existing: dict[int, dict[str, Any]], current: dict[int, dict[str, Any]]) -> list[int]:
456
+ changed: list[int] = []
457
+ for number, item in current.items():
458
+ old = existing.get(number)
459
+ if old is None:
460
+ continue
461
+ for key in ["updated_at", "state", "title", "head_ref", "base_ref", "merged_at", "closed_at"]:
462
+ if old.get(key) not in (None, "") and old.get(key) != item.get(key):
463
+ changed.append(number)
464
+ break
465
+ else:
466
+ common_keys = {
467
+ key
468
+ for key, value in old.items()
469
+ if key not in {"number", "raw_lookup", "source"} and value not in (None, "")
470
+ }
471
+ old_common = {key: old.get(key) for key in common_keys}
472
+ current_common = {key: item.get(key) for key in common_keys}
473
+ if old_common != current_common and comparable_item(old) != comparable_item(item):
474
+ changed.append(number)
475
+ return sorted(changed)
476
+
477
+
478
+ def remote_url_from_line(line: str) -> str:
479
+ parts = line.split()
480
+ return parts[1] if len(parts) >= 2 else ""
481
+
482
+
483
+ def remote_name_from_line(line: str) -> str:
484
+ parts = line.split()
485
+ return parts[0] if parts else ""
486
+
487
+
488
+ def normalize_remote_path(path: str) -> str:
489
+ repo_path = path.strip().lstrip("/").rstrip("/")
490
+ return repo_path[:-4] if repo_path.endswith(".git") else repo_path
491
+
492
+
493
+ def provider_from_host(host: str) -> str:
494
+ lower = host.lower()
495
+ if lower == "github.com" or "github" in lower:
496
+ return "github"
497
+ if lower == "gitlab.com" or "gitlab" in lower:
498
+ return "gitlab"
499
+ return ""
500
+
501
+
502
+ def parse_remote_url(url: str) -> dict[str, str]:
503
+ if not url:
504
+ return {"provider": "", "repo": "", "host": "", "url": ""}
505
+ if "://" not in url:
506
+ match = re.match(r"^(?:[^@/\s]+@)?(?P<host>[^:/\s]+):(?P<path>.+)$", url)
507
+ if match:
508
+ host = match.group("host")
509
+ return {
510
+ "provider": provider_from_host(host),
511
+ "repo": normalize_remote_path(match.group("path")),
512
+ "host": host,
513
+ "url": url,
514
+ }
515
+ parsed = urlparse(url)
516
+ host = parsed.hostname or ""
517
+ return {
518
+ "provider": provider_from_host(host),
519
+ "repo": normalize_remote_path(parsed.path),
520
+ "host": host,
521
+ "url": url,
522
+ }
523
+
524
+
525
+ def parse_code_host_repo(remotes: str) -> dict[str, str]:
526
+ candidates: list[dict[str, str]] = []
527
+ for line in remotes.splitlines():
528
+ if "(push)" in line:
529
+ continue
530
+ parsed = parse_remote_url(remote_url_from_line(line))
531
+ if parsed["provider"] and parsed["repo"]:
532
+ parsed["remote_name"] = remote_name_from_line(line)
533
+ candidates.append(parsed)
534
+ for preferred in PREFERRED_REMOTE_NAMES:
535
+ for candidate in candidates:
536
+ if candidate.get("remote_name") == preferred:
537
+ candidate["selection_reason"] = f"preferred_{preferred}_fetch_remote"
538
+ return candidate
539
+ if candidates:
540
+ candidates[0]["selection_reason"] = "first_supported_fetch_remote"
541
+ return candidates[0]
542
+ return {"provider": "", "repo": "", "host": "", "url": "", "remote_name": "", "selection_reason": "none"}
543
+
544
+
545
+ def provider_host(provider: str) -> str:
546
+ if provider == "github":
547
+ return "github.com"
548
+ if provider == "gitlab":
549
+ return "gitlab.com"
550
+ return ""
551
+
552
+
553
+ def provider_from_profile(memory: Path) -> dict[str, str]:
554
+ profile = parse_frontmatter(memory / "PROFILE.md")
555
+ provider = profile.get("code_host_provider", "")
556
+ repo = profile.get("repo_full_name", "")
557
+ if provider in {"github", "gitlab"} and repo and "/" in repo:
558
+ parsed_profile_url = parse_remote_url(str(profile.get("repo_url", "")))
559
+ host = parsed_profile_url["host"] if parsed_profile_url["provider"] == provider else provider_host(provider)
560
+ return {
561
+ "provider": provider,
562
+ "repo": repo,
563
+ "host": host,
564
+ "url": profile.get("repo_url", ""),
565
+ "remote_name": "",
566
+ "selection_reason": "profile_frontmatter",
567
+ }
568
+ return {}
569
+
570
+
571
+ def auth_args(command: str, host: str) -> list[str]:
572
+ args = ["auth", "status"]
573
+ if host and command in {"gh", "glab"}:
574
+ args.extend(["--hostname", host])
575
+ return args
576
+
577
+
578
+ def login_hint(command: str, host: str) -> str:
579
+ default_host = "github.com" if command == "gh" else "gitlab.com" if command == "glab" else ""
580
+ if host and host != default_host:
581
+ return f"{command} auth login --hostname {host}"
582
+ return f"{command} auth login"
583
+
584
+
585
+ def cli_evidence_state(command: str, host: str) -> tuple[str, bool]:
586
+ if shutil.which(command) is None:
587
+ return "cli_missing", False
588
+ try:
589
+ result = subprocess.run([command, *auth_args(command, host)], text=True, capture_output=True, timeout=8)
590
+ except subprocess.TimeoutExpired:
591
+ return "auth_check_timeout", True
592
+ return ("ready", True) if result.returncode == 0 else ("auth_required", True)
593
+
594
+
595
+ def provider_report(repo: Path, memory: Path) -> dict[str, Any]:
596
+ remote = provider_from_profile(memory) or parse_code_host_repo(git(repo, ["remote", "-v"], "git could not list remotes"))
597
+ provider = remote["provider"]
598
+ cli = "gh" if provider == "github" else "glab" if provider == "gitlab" else ""
599
+ evidence_state = "unavailable"
600
+ cli_available = False
601
+ if cli:
602
+ evidence_state, cli_available = cli_evidence_state(cli, remote["host"])
603
+ return {
604
+ "name": provider,
605
+ "repo": remote["repo"],
606
+ "host": remote["host"],
607
+ "remote_url": remote["url"],
608
+ "remote_name": remote.get("remote_name", ""),
609
+ "selection_reason": remote.get("selection_reason", ""),
610
+ "evidence_state": evidence_state,
611
+ "cli": cli,
612
+ "cli_available": cli_available,
613
+ "login_hint": login_hint(cli, remote["host"]) if cli else "",
614
+ }
615
+
616
+
617
+ def provider_script(provider: str) -> str:
618
+ if provider == "github":
619
+ return "github_resource_facets.py"
620
+ if provider == "gitlab":
621
+ return "gitlab_resource_facets.py"
622
+ return ""
623
+
624
+
625
+ def fetch_provider_items(repo: Path, provider: dict[str, Any], snapshot_ref: str, pr_limit: int, issue_limit: int, summary_chars: int) -> tuple[dict[str, Any], list[dict[str, Any]], list[dict[str, Any]]]:
626
+ provider_name = str(provider.get("name") or "")
627
+ provider_repo = str(provider.get("repo") or "")
628
+ script_name = provider_script(provider_name)
629
+ if not script_name or not provider_repo or provider.get("evidence_state") != "ready":
630
+ return {
631
+ "attempted": False,
632
+ "reason": f"provider_evidence_state={provider.get('evidence_state', '') or 'unavailable'}",
633
+ }, [], []
634
+
635
+ script_path = BUILDER_SCRIPTS_DIR / script_name
636
+ if not script_path.exists():
637
+ return {"attempted": False, "reason": "memorax-code repo-build helpers are missing"}, [], []
638
+
639
+ with tempfile.TemporaryDirectory(prefix="memorax-code-repo-update.") as tmp:
640
+ tmp_root = Path(tmp)
641
+ raw = tmp_root / f"{provider_name}-facets.json"
642
+ facets = run(
643
+ [
644
+ sys.executable,
645
+ str(script_path),
646
+ "--repo",
647
+ provider_repo,
648
+ "--repo-path",
649
+ str(repo),
650
+ "--snapshot-ref",
651
+ snapshot_ref,
652
+ "--include",
653
+ "prs,issues",
654
+ "--pr-limit",
655
+ str(pr_limit),
656
+ "--issue-limit",
657
+ str(issue_limit),
658
+ "--state",
659
+ "all",
660
+ "--summary-chars",
661
+ str(summary_chars),
662
+ "--out",
663
+ str(raw),
664
+ *(["--hostname", str(provider.get("host"))] if provider.get("host") else []),
665
+ ],
666
+ repo,
667
+ )
668
+ if facets.returncode != 0:
669
+ return {
670
+ "attempted": True,
671
+ "ok": False,
672
+ "exit_code": facets.returncode,
673
+ "stderr": facets.stderr.strip(),
674
+ }, [], []
675
+ raw_facets = load_json(raw, [])
676
+ prs = facet_items(raw_facets, "pr")
677
+ issues = facet_items(raw_facets, "issue")
678
+ return {
679
+ "attempted": True,
680
+ "ok": True,
681
+ "provider": provider_name,
682
+ "repo": provider_repo,
683
+ "pr_count": len(prs) if isinstance(prs, list) else 0,
684
+ "issue_count": len(issues) if isinstance(issues, list) else 0,
685
+ }, prs, issues
686
+
687
+
688
+ def delta_summary(existing: list[dict[str, Any]], current: list[dict[str, Any]]) -> dict[str, Any]:
689
+ existing_by_no = by_number(existing)
690
+ current_by_no = by_number(current)
691
+ added = sorted(number for number in current_by_no if number not in existing_by_no)
692
+ updated = changed_numbers(existing_by_no, current_by_no)
693
+ baseline_only = sorted(number for number in existing_by_no if number not in current_by_no)
694
+ return {
695
+ "baseline_numbers": sorted(existing_by_no),
696
+ "current_numbers": sorted(current_by_no),
697
+ "added_numbers": added,
698
+ "updated_numbers": updated,
699
+ "upsert_numbers": sorted([*added, *updated]),
700
+ "baseline_only_numbers": baseline_only,
701
+ "delete_numbers": [],
702
+ }
703
+
704
+
705
+ def actions_for(deltas: dict[str, Any], provider_fetch: dict[str, Any]) -> list[str]:
706
+ actions: list[str] = []
707
+ local_commit_status = deltas.get("local_commit_status", {})
708
+ if local_commit_status.get("reason") == "missing_baseline_commit":
709
+ actions.append("Local commit delta was skipped because no stored commit baseline was found. Use $memorax-code repo-build for a full rebuild before doing incremental commit updates.")
710
+ if local_commit_status.get("reason") == "baseline_not_ancestor_of_head":
711
+ actions.append("Local commit delta was skipped because the stored baseline is not an ancestor of HEAD. Use $memorax-code repo-build for a full rebuild, or explicitly choose a new baseline before doing incremental commit updates.")
712
+ if deltas["local_commits"]:
713
+ actions.append("Update .repo_memory/resources/commits.md with sections for the new local commits; update PROFILE.md local_head and commit snapshot notes only where needed.")
714
+ if deltas["pull_requests"]["added_numbers"] or deltas["pull_requests"]["updated_numbers"]:
715
+ actions.append("Upsert .repo_memory/resources/prs.md sections by PR/MR number for only the added or changed numbers. Preserve baseline-only and unaffected PR/MR sections; do not delete missing numbers by default.")
716
+ if deltas["issues"]["added_numbers"] or deltas["issues"]["updated_numbers"]:
717
+ actions.append("Upsert .repo_memory/resources/issues.md sections by issue number for only the added or changed numbers. Preserve baseline-only and unaffected issue sections; do not delete missing numbers by default.")
718
+ if provider_fetch.get("attempted") is True and provider_fetch.get("ok") is False:
719
+ actions.append("Provider delta fetch failed; keeping existing PR/issue resources unchanged. Fix provider access and rerun $memorax-code repo-update, or use repo-build for a full provider refresh if needed.")
720
+ if provider_fetch.get("attempted") is False:
721
+ actions.append("Provider delta fetch was skipped; keep PR/issue resources unchanged unless the user asks to login/install provider CLI or force provider refresh.")
722
+ if not actions:
723
+ actions.append("No repo-memory content update is needed.")
724
+ return actions
725
+
726
+
727
+ def notices_for(deltas: dict[str, Any], provider_fetch: dict[str, Any]) -> list[dict[str, Any]]:
728
+ notices: list[dict[str, Any]] = []
729
+ local_commit_status = deltas.get("local_commit_status", {})
730
+ if local_commit_status.get("reason") == "missing_baseline_commit":
731
+ notices.append({
732
+ "level": "warning",
733
+ "title": "Repo Memory Commit Baseline Missing",
734
+ "message": "No stored local commit baseline was found in .repo_memory, so incremental commit detection was skipped.",
735
+ "next_steps": [
736
+ "Run $memorax-code repo-build for a full rebuild before incremental commit updates.",
737
+ ],
738
+ "render_as": "assistant_message",
739
+ })
740
+ if local_commit_status.get("reason") == "baseline_not_ancestor_of_head":
741
+ notices.append({
742
+ "level": "warning",
743
+ "title": "Repo Memory Baseline Rewritten",
744
+ "message": "Stored repo-memory commit baseline is not an ancestor of the current HEAD. History was probably rebased or force-pushed, so incremental commit detection was skipped.",
745
+ "next_steps": [
746
+ "Run $memorax-code repo-build for a full rebuild, or explicitly choose a new baseline before incremental commit updates.",
747
+ ],
748
+ "render_as": "assistant_message",
749
+ })
750
+ if provider_fetch.get("attempted") is True and provider_fetch.get("ok") is False:
751
+ notices.append({
752
+ "level": "warning",
753
+ "title": "Provider Delta Fetch Failed",
754
+ "message": "Provider delta fetch failed; keeping existing PR/issue resources unchanged. Fix provider access before rerunning provider deltas.",
755
+ "next_steps": [
756
+ "Fix GitHub/GitLab CLI access and rerun $memorax-code repo-update.",
757
+ "Use $memorax-code repo-build for a full provider refresh if a broader rebuild is needed.",
758
+ ],
759
+ "render_as": "assistant_message",
760
+ })
761
+ return notices
762
+
763
+
764
+ def parse_args(argv: list[str]) -> argparse.Namespace:
765
+ parser = argparse.ArgumentParser(description="Detect incremental updates for an existing .repo_memory bundle.")
766
+ parser.add_argument("--repo-path", default=".", help="Local git repository path")
767
+ parser.add_argument("--memory-path", help="Existing .repo_memory path; defaults to <repo>/.repo_memory")
768
+ parser.add_argument("--snapshot-ref", default="HEAD", help="Local git ref used for current detection")
769
+ parser.add_argument("--provider-mode", choices=["auto", "off"], default="auto", help="Whether to fetch latest provider PR/issue facets when provider evidence is ready")
770
+ parser.add_argument("--pr-limit", type=int, default=None, help="Maximum latest PR/MR candidates to compare")
771
+ parser.add_argument("--issue-limit", type=int, default=None, help="Maximum latest issue candidates to compare")
772
+ parser.add_argument("--summary-chars", type=int, default=None, help="Maximum raw provider summary characters when provider facets are fetched")
773
+ parser.add_argument("--pretty", action="store_true", help="Pretty-print JSON")
774
+ args = parser.parse_args(argv)
775
+ args.repo_path = Path(args.repo_path).expanduser().resolve()
776
+ return apply_effective_settings(args, parser)
777
+
778
+
779
+ def main(argv: list[str]) -> int:
780
+ args = parse_args(argv)
781
+ repo = args.repo_path
782
+ memory = resolve_memory(repo, args.memory_path)
783
+ if not memory.exists() or not (memory / "PROFILE.md").exists():
784
+ report = {
785
+ "ok": False,
786
+ "repo_path": str(repo),
787
+ "memory_path": str(memory),
788
+ "builder_helpers": builder_helper_report(),
789
+ "error": "Existing .repo_memory/PROFILE.md is required; run $memorax-code repo-build before repo-update.",
790
+ }
791
+ print(json.dumps(report, ensure_ascii=False, indent=2 if args.pretty else None))
792
+ return 1
793
+
794
+ head = git(repo, ["rev-parse", "--verify", f"{args.snapshot_ref}^{{commit}}"], f"git could not resolve {args.snapshot_ref!r}")
795
+ baseline_sha = commit_baseline(memory, repo, head)
796
+ local_commits, local_commit_status = commit_delta(repo, baseline_sha, head)
797
+
798
+ existing_prs = baseline_items(memory, "prs.md", "pr")
799
+ existing_issues = baseline_items(memory, "issues.md", "issue")
800
+ provider = provider_report(repo, memory)
801
+ if args.provider_mode == "auto":
802
+ provider_fetch, current_prs, current_issues = fetch_provider_items(
803
+ repo,
804
+ provider,
805
+ args.snapshot_ref,
806
+ args.pr_limit,
807
+ args.issue_limit,
808
+ args.summary_chars,
809
+ )
810
+ else:
811
+ provider_fetch, current_prs, current_issues = {"attempted": False, "reason": "provider-mode=off"}, [], []
812
+ fetched_prs = current_prs if provider_fetch.get("ok") is True else []
813
+ fetched_issues = current_issues if provider_fetch.get("ok") is True else []
814
+ if not current_prs and provider_fetch.get("ok") is not True:
815
+ current_prs = existing_prs
816
+ if not current_issues and provider_fetch.get("ok") is not True:
817
+ current_issues = existing_issues
818
+
819
+ deltas = {
820
+ "local_commits": local_commits,
821
+ "local_commit_status": local_commit_status,
822
+ "pull_requests": delta_summary(existing_prs, current_prs),
823
+ "issues": delta_summary(existing_issues, current_issues),
824
+ }
825
+ if local_commit_status.get("status") == "skipped" and local_commit_status.get("reason"):
826
+ deltas["commit_delta_skipped"] = local_commit_status["reason"]
827
+ report = {
828
+ "ok": True,
829
+ "repo_path": str(repo),
830
+ "memory_path": str(memory),
831
+ "effective_settings": args.effective_settings,
832
+ "builder_helpers": builder_helper_report(),
833
+ "baseline": {
834
+ "local_commit_sha": baseline_sha,
835
+ "pull_request_numbers": deltas["pull_requests"]["baseline_numbers"],
836
+ "issue_numbers": deltas["issues"]["baseline_numbers"],
837
+ },
838
+ "current": {
839
+ "local_head": head,
840
+ "provider": provider,
841
+ "provider_fetch": provider_fetch,
842
+ "provider_items": {
843
+ "pull_requests": fetched_prs,
844
+ "issues": fetched_issues,
845
+ },
846
+ },
847
+ "deltas": deltas,
848
+ "notices": notices_for(deltas, provider_fetch),
849
+ "actions": actions_for(deltas, provider_fetch),
850
+ }
851
+ print(json.dumps(report, ensure_ascii=False, indent=2 if args.pretty else None))
852
+ return 0
853
+
854
+
855
+ if __name__ == "__main__":
856
+ raise SystemExit(main(sys.argv[1:]))