@memorax/memorax-code 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -12
- package/bin/memorax-code-npm-preinstall.mjs +7 -2
- package/bin/memorax-code-opencode.mjs +4 -0
- package/bin/memorax-code-plugin-postinstall.mjs +342 -93
- package/docs/configuration.md +144 -35
- package/docs/troubleshooting.md +73 -13
- package/lib/dsh-plugin-install.mjs +22 -0
- package/lib/memorax-code-adapter-common/src/hooks/capture-cwd-hook.mjs +21 -14
- package/lib/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +26 -7
- package/lib/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +86 -95
- package/lib/memorax-code-adapter-common/src/hooks/memory-skill-reminder-policy.mjs +58 -0
- package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-auto-build.mjs +2 -2
- package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-job-supervisor.mjs +1 -1
- package/lib/memorax-code-adapter-common/src/windows-cli-invocation.mjs +13 -1
- package/lib/memorax-code-backend/dist/app/backend-server.js +210 -0
- package/lib/memorax-code-backend/dist/{server-observability.js → app/memory-observability.js} +4 -4
- package/lib/memorax-code-backend/dist/{backend-state.js → app/state.js} +1 -1
- package/lib/memorax-code-backend/dist/{claude-adapter-lifecycle.js → clients/claude/lifecycle.js} +2 -2
- package/lib/memorax-code-backend/dist/{claude-memory-hook-runtime.js → clients/claude/memory-hook-runtime.js} +7 -7
- package/lib/memorax-code-backend/dist/clients/codex/lifecycle.js +131 -0
- package/lib/memorax-code-backend/dist/{codex-memory-hook-runtime.js → clients/codex/memory-hook-runtime.js} +9 -9
- package/lib/memorax-code-backend/dist/{codex-plugin-hooks.js → clients/codex/plugin-hooks.js} +1 -1
- package/lib/memorax-code-backend/dist/{codex-plugin-install.js → clients/codex/plugin-install.js} +6 -6
- package/lib/memorax-code-backend/dist/clients/dsh/lifecycle.js +75 -0
- package/lib/memorax-code-backend/dist/clients/dsh/memory-hook-runtime.js +258 -0
- package/lib/memorax-code-backend/dist/clients/dsh/session-turn.js +249 -0
- package/lib/memorax-code-backend/dist/clients/opencode/lifecycle.js +54 -0
- package/lib/memorax-code-backend/dist/clients/opencode/memory-hook-runtime.js +277 -0
- package/lib/memorax-code-backend/dist/clients/opencode/message-turn.js +78 -0
- package/lib/memorax-code-backend/dist/codex-adapter-lifecycle.js +1 -131
- package/lib/memorax-code-backend/dist/{memorax-code-config.js → config/memorax-code.js} +32 -4
- package/lib/memorax-code-backend/dist/{server-cli.js → entrypoints/backend-cli.js} +137 -37
- package/lib/memorax-code-backend/dist/jsonl-append.js +1 -75
- package/lib/memorax-code-backend/dist/{active-client-selection.js → lifecycle/active-clients.js} +12 -1
- package/lib/memorax-code-backend/dist/{backend-service-cleanup.js → lifecycle/backend/cleanup.js} +1 -1
- package/lib/memorax-code-backend/dist/{backend-service-record.js → lifecycle/backend/record.js} +1 -1
- package/lib/memorax-code-backend/dist/{backend-service-result.js → lifecycle/backend/result.js} +2 -2
- package/lib/memorax-code-backend/dist/{service.js → lifecycle/backend/service.js} +16 -16
- package/lib/memorax-code-backend/dist/{backend-token-record.js → lifecycle/backend/token-record.js} +1 -1
- package/lib/memorax-code-backend/dist/{backend-service-token.js → lifecycle/backend/token.js} +2 -2
- package/lib/memorax-code-backend/dist/lifecycle/client-plugin-removal.js +109 -0
- package/lib/memorax-code-backend/dist/{client-selection.js → lifecycle/client-selection.js} +11 -5
- package/lib/memorax-code-backend/dist/{backend-lifecycle-lock.js → lifecycle/lock.js} +1 -1
- package/lib/memorax-code-backend/dist/{memorax-code-lifecycle.js → lifecycle/orchestrator.js} +325 -39
- package/lib/memorax-code-backend/dist/memorax-cli.js +1 -1
- package/lib/memorax-code-backend/dist/memorax-code.js +1 -1
- package/lib/memorax-code-backend/dist/{automatic-memory-retrieval.js → memory/automatic-retrieval.js} +3 -3
- package/lib/memorax-code-backend/dist/{automatic-memory-writeback.js → memory/automatic-writeback.js} +75 -21
- package/lib/memorax-code-backend/dist/{memory-cli.js → memory/cli.js} +23 -10
- package/lib/memorax-code-backend/dist/{memory-hook-command.js → memory/hook-command.js} +156 -9
- package/lib/memorax-code-backend/dist/memory/observability.js +1 -0
- package/lib/memorax-code-backend/dist/memory/payload-redaction.js +212 -0
- package/lib/memorax-code-backend/dist/{memory-reminder-trace-recorder.js → memory/reminder-trace-recorder.js} +20 -7
- package/lib/memorax-code-backend/dist/{repository-memory-context.js → memory/repository-session.js} +2 -2
- package/lib/memorax-code-backend/dist/{memory-service.js → memory/service.js} +27 -5
- package/lib/memorax-code-backend/dist/{memory-turn-coordinator.js → memory/turn-coordinator.js} +2 -1
- package/lib/memorax-code-backend/dist/{memory-writeback-buffer.js → memory/writeback-buffer.js} +3 -3
- package/lib/memorax-code-backend/dist/{memory-writeback-chunk.js → memory/writeback-chunk.js} +1 -1
- package/lib/memorax-code-backend/dist/{memory-writeback-reconciler.js → memory/writeback-reconciler.js} +6 -6
- package/lib/memorax-code-backend/dist/{memory-writeback-task-projection.js → memory/writeback-task-projection.js} +4 -4
- package/lib/memorax-code-backend/dist/{memorax-adapter.js → provider/memorax/adapter.js} +5 -5
- package/lib/memorax-code-backend/dist/{memorax-config.js → provider/memorax/config.js} +4 -4
- package/lib/memorax-code-backend/dist/{memorax-http.js → provider/memorax/http.js} +1 -1
- package/lib/memorax-code-backend/dist/{repo-memory-readiness.js → repository/readiness.js} +3 -3
- package/lib/memorax-code-backend/dist/server.js +3 -212
- package/lib/memorax-code-backend/dist/service-entrypoint.js +1 -1
- package/lib/memorax-code-backend/dist/{debug-log.js → shared/debug-log.js} +1 -1
- package/lib/memorax-code-backend/dist/shared/jsonl-append.js +75 -0
- package/lib/memorax-code-backend/dist/shared/windows-cli-invocation.js +148 -0
- package/lib/memorax-code-backend/dist/{trace-config.js → trace/config.js} +43 -5
- package/lib/memorax-code-backend/dist/{trace-context.js → trace/context.js} +53 -2
- package/lib/memorax-code-backend/dist/{trace-store.js → trace/store.js} +3 -3
- package/lib/memorax-code-backend/dist/{server-health.js → transport/http/health.js} +1 -1
- package/lib/memorax-code-backend/dist/{server-memory-hook.js → transport/http/memory-hook.js} +3 -3
- package/lib/memorax-code-backend/dist/{server-http.js → transport/http/request.js} +1 -1
- package/lib/memorax-code-backend/dist/{claude-local-transcript.js → viewer/history/claude-transcript.js} +3 -3
- package/lib/memorax-code-backend/dist/{memory-viewer-session-title.js → viewer/history/session-title.js} +1 -1
- package/lib/memorax-code-backend/dist/{server-memory-viewer.js → viewer/http/public-routes.js} +15 -15
- package/lib/memorax-code-backend/dist/viewer/model.js +1 -0
- package/lib/memorax-code-backend/dist/{memory-viewer-activity.js → viewer/projection/activity.js} +2 -2
- package/lib/memorax-code-backend/dist/{memory-viewer-history-projection.js → viewer/projection/history.js} +1 -1
- package/lib/memorax-code-backend/dist/{memory-viewer-observability.js → viewer/projection/observability.js} +1 -1
- package/lib/memorax-code-backend/dist/{memory-viewer-user-projection.js → viewer/projection/user.js} +1 -1
- package/lib/memorax-code-backend/dist/{memory-viewer-writeback-status.js → viewer/projection/writeback-status.js} +1 -1
- package/lib/memorax-code-backend/dist/{memory-viewer-store.js → viewer/store.js} +59 -32
- package/lib/memorax-code-backend/dist/{memory-viewer-user-html.js → viewer/ui/user-html.js} +11 -10
- package/lib/memorax-code-backend/dist/windows-cli-invocation.js +1 -148
- package/lib/memorax-code-backend/package.json +3 -3
- package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +11 -2
- package/lib/memorax-code-claude-adapter/skills/memorax-code/defaults.json +8 -5
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +37 -9
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/personal-read.md +1 -1
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/personal-write.md +33 -13
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/repo-build.md +62 -13
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/repo-read.md +11 -4
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/repo-templates.md +179 -59
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/repo-update.md +18 -7
- package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/collect_all.py +122 -39
- package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/detect_updates.py +73 -10
- package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/github_resource_facets.py +9 -1
- package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +9 -1
- package/lib/memorax-code-claude-adapter/skills/memorax-code/scripts/validate_memory.py +23 -1
- package/lib/memorax-code-claude-adapter/src/plugin-install.mjs +1 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/capture-cwd-hook.mjs +21 -14
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +26 -7
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +86 -95
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/memory-skill-reminder-policy.mjs +58 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-auto-build.mjs +2 -2
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-job-supervisor.mjs +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/windows-cli-invocation.mjs +13 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +11 -2
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/defaults.json +8 -5
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +37 -9
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/personal-read.md +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/personal-write.md +33 -13
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/repo-build.md +62 -13
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/repo-read.md +11 -4
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/repo-templates.md +179 -59
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/repo-update.md +18 -7
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/collect_all.py +122 -39
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/detect_updates.py +73 -10
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/github_resource_facets.py +9 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +9 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/scripts/validate_memory.py +23 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/plugin-install.mjs +1 -0
- package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
- package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-codex-adapter/package.json +1 -1
- package/lib/memorax-code-codex-adapter/runtime-hooks/memory-skill-reminder.mjs +10 -2
- package/lib/memorax-code-codex-adapter/skills/memorax-code/SKILL.md +11 -2
- package/lib/memorax-code-codex-adapter/skills/memorax-code/defaults.json +8 -5
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +37 -9
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/personal-read.md +1 -1
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/personal-write.md +33 -13
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/repo-build.md +62 -13
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/repo-read.md +11 -4
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/repo-templates.md +179 -59
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/repo-update.md +18 -7
- package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/collect_all.py +122 -39
- package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/detect_updates.py +73 -10
- package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/github_resource_facets.py +9 -1
- package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +9 -1
- package/lib/memorax-code-codex-adapter/skills/memorax-code/scripts/validate_memory.py +23 -1
- package/lib/memorax-code-dsh-adapter/cordis.patch.yml +10 -0
- package/lib/memorax-code-dsh-adapter/hooks/repo-memory-job.mjs +68 -0
- package/lib/memorax-code-dsh-adapter/package.json +69 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/SKILL.md +91 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/agents/claude.yaml +10 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/agents/openai.yaml +7 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/defaults.json +12 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-add.md +86 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-search.md +91 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/personal-read.md +46 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/personal-write.md +120 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/repo-build.md +319 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/repo-read.md +103 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/repo-templates.md +390 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/repo-update.md +127 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/scripts/collect_all.py +579 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/scripts/detect_updates.py +919 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/scripts/git_commit_facets.py +222 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/scripts/github_resource_facets.py +512 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +517 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/scripts/prepare_repo_memory.py +411 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/scripts/user_profile_memory.py +528 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/scripts/validate_memory.py +248 -0
- package/lib/memorax-code-dsh-adapter/src/backend-client.mjs +119 -0
- package/lib/memorax-code-dsh-adapter/src/dsh-message.mjs +32 -0
- package/lib/memorax-code-dsh-adapter/src/dsh-version.mjs +18 -0
- package/lib/memorax-code-dsh-adapter/src/http-client.mjs +55 -0
- package/lib/memorax-code-dsh-adapter/src/index.mjs +72 -0
- package/lib/memorax-code-dsh-adapter/src/personal-context-worker.mjs +48 -0
- package/lib/memorax-code-dsh-adapter/src/personal-context.mjs +114 -0
- package/lib/memorax-code-dsh-adapter/src/plugin.mjs +724 -0
- package/lib/memorax-code-dsh-adapter/src/profile-lifecycle.mjs +1289 -0
- package/lib/memorax-code-dsh-adapter/src/protocol.mjs +145 -0
- package/lib/memorax-code-dsh-adapter/src/runtime-state.mjs +132 -0
- package/lib/memorax-code-opencode-adapter/hooks/repo-memory-job.mjs +37 -0
- package/lib/memorax-code-opencode-adapter/package.json +10 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/SKILL.md +91 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/agents/claude.yaml +10 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/agents/openai.yaml +7 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/defaults.json +12 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-add.md +86 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-search.md +91 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/personal-read.md +46 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/personal-write.md +120 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/repo-build.md +319 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/repo-read.md +103 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/repo-templates.md +390 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/repo-update.md +127 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/scripts/collect_all.py +579 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/scripts/detect_updates.py +919 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/scripts/git_commit_facets.py +222 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/scripts/github_resource_facets.py +512 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +517 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/scripts/prepare_repo_memory.py +411 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/scripts/user_profile_memory.py +528 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/scripts/validate_memory.py +248 -0
- package/lib/memorax-code-opencode-adapter/src/adapter-paths.mjs +37 -0
- package/lib/memorax-code-opencode-adapter/src/cli.mjs +260 -0
- package/lib/memorax-code-opencode-adapter/src/diagnostics.mjs +31 -0
- package/lib/memorax-code-opencode-adapter/src/plugin-install.mjs +673 -0
- package/lib/memorax-code-opencode-adapter/src/plugin.mjs +536 -0
- package/lib/memorax-code-opencode-adapter/src/repo-memory-server-runner.mjs +207 -0
- package/lib/run-entrypoint.mjs +6 -0
- package/package.json +7 -3
- package/lib/memorax-code-backend/dist/client-plugin-removal.js +0 -67
- /package/lib/memorax-code-backend/dist/{claude-transcript-turn.js → clients/claude/transcript-turn.js} +0 -0
- /package/lib/memorax-code-backend/dist/{codex-effective-prompt.js → clients/codex/effective-prompt.js} +0 -0
- /package/lib/memorax-code-backend/dist/{codex-rollout-turn.js → clients/codex/rollout-turn.js} +0 -0
- /package/lib/memorax-code-backend/dist/{codex-session-turn-index.js → clients/codex/session-turn-index.js} +0 -0
- /package/lib/memorax-code-backend/dist/{codex-workspace-links.js → clients/codex/workspace-links.js} +0 -0
- /package/lib/memorax-code-backend/dist/{backend-env.js → config/backend-env.js} +0 -0
- /package/lib/memorax-code-backend/dist/{proxy-env.js → config/proxy-env.js} +0 -0
- /package/lib/memorax-code-backend/dist/{entrypoint.js → entrypoints/main.js} +0 -0
- /package/lib/memorax-code-backend/dist/{backend-process.js → lifecycle/backend/process.js} +0 -0
- /package/lib/memorax-code-backend/dist/{backend-shutdown-request.js → lifecycle/backend/shutdown-request.js} +0 -0
- /package/lib/memorax-code-backend/dist/{backend-status.js → lifecycle/backend/status.js} +0 -0
- /package/lib/memorax-code-backend/dist/{adapter-lifecycle-participant.js → lifecycle/contracts.js} +0 -0
- /package/lib/memorax-code-backend/dist/{install-watchdog.js → lifecycle/install-watchdog.js} +0 -0
- /package/lib/memorax-code-backend/dist/{memory-observability.js → lifecycle/participant.js} +0 -0
- /package/lib/memorax-code-backend/dist/{memory-project.js → memory/project.js} +0 -0
- /package/lib/memorax-code-backend/dist/{repository-memory-scope.js → repository/scope.js} +0 -0
- /package/lib/memorax-code-backend/dist/{incremental-jsonl-projection.js → shared/incremental-jsonl-projection.js} +0 -0
- /package/lib/memorax-code-backend/dist/{utils.js → shared/record.js} +0 -0
- /package/lib/memorax-code-backend/dist/{server-json.js → transport/http/json.js} +0 -0
- /package/lib/memorax-code-backend/dist/{memory-viewer-data-projection-cache.js → viewer/projection/cache.js} +0 -0
- /package/lib/memorax-code-backend/dist/{memory-viewer-event-identity.js → viewer/projection/event-identity.js} +0 -0
- /package/lib/memorax-code-backend/dist/{memory-viewer-redaction.js → viewer/projection/redaction.js} +0 -0
- /package/lib/memorax-code-backend/dist/{memory-viewer-turn-reference.js → viewer/projection/turn-reference.js} +0 -0
- /package/lib/memorax-code-backend/dist/{memory-viewer-icon.js → viewer/ui/icon.js} +0 -0
- /package/lib/memorax-code-backend/dist/{memory-viewer-logo.js → viewer/ui/logo.js} +0 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Manage repo-scoped user profile preferences under .repo_memory/user-profile."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import tempfile
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
from dataclasses import dataclass, replace
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Iterator
|
|
16
|
+
|
|
17
|
+
ALLOWED_TYPES = {"communication", "workflow", "environment", "profile"}
|
|
18
|
+
ALLOWED_STATUSES = {"active", "superseded", "deleted"}
|
|
19
|
+
GITIGNORE_RULE = ".repo_memory/"
|
|
20
|
+
MAX_PREFERENCES_BYTES = 64 * 1024
|
|
21
|
+
PREFERENCES_SCHEMA = "repo_user_profile_memory.v0.1"
|
|
22
|
+
OWNER = "repo-user-profile-memory"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class Preference:
|
|
27
|
+
id: str
|
|
28
|
+
type: str
|
|
29
|
+
description: str
|
|
30
|
+
applies_when: str
|
|
31
|
+
do_not_apply_when: str
|
|
32
|
+
created: str
|
|
33
|
+
updated: str
|
|
34
|
+
confidence: str = "user_stated"
|
|
35
|
+
status: str = "active"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class StorageError(RuntimeError):
|
|
39
|
+
"""Preference storage is malformed or unsafe to mutate."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def run(cmd: list[str], cwd: Path) -> tuple[int, str, str]:
|
|
43
|
+
proc = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True)
|
|
44
|
+
return proc.returncode, proc.stdout.strip(), proc.stderr.strip()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def now_iso() -> str:
|
|
48
|
+
return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def resolve_repo(path: str) -> Path:
|
|
52
|
+
start = Path(path).resolve()
|
|
53
|
+
if not start.exists():
|
|
54
|
+
raise SystemExit(f"Repository path does not exist: {start}")
|
|
55
|
+
code, out, err = run(["git", "rev-parse", "--show-toplevel"], start)
|
|
56
|
+
if code == 0 and out:
|
|
57
|
+
return Path(out).resolve()
|
|
58
|
+
if start.is_file():
|
|
59
|
+
return start.parent
|
|
60
|
+
return start
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def gitignore_has_repo_memory_rule(text: str) -> bool:
|
|
64
|
+
for line in text.splitlines():
|
|
65
|
+
stripped = line.strip()
|
|
66
|
+
if not stripped or stripped.startswith("#") or stripped.startswith("!"):
|
|
67
|
+
continue
|
|
68
|
+
rule = stripped.split("#", 1)[0].strip().lstrip("/")
|
|
69
|
+
if rule in {".repo_memory", ".repo_memory/"}:
|
|
70
|
+
return True
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def ensure_repo_memory_gitignore(repo: Path) -> bool:
|
|
75
|
+
gitignore = repo / ".gitignore"
|
|
76
|
+
existing = gitignore.read_text(encoding="utf-8") if gitignore.exists() else ""
|
|
77
|
+
if gitignore_has_repo_memory_rule(existing):
|
|
78
|
+
return False
|
|
79
|
+
separator = "" if not existing or existing.endswith("\n") else "\n"
|
|
80
|
+
gitignore.write_text(f"{existing}{separator}{GITIGNORE_RULE}\n", encoding="utf-8")
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def normalize_field(value: str) -> str:
|
|
85
|
+
# Keep field values one-line so user text cannot create headings/frontmatter.
|
|
86
|
+
return re.sub(r"\s+", " ", value.replace("\r", " ").replace("\n", " ")).strip()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def normalize_key(value: str) -> str:
|
|
90
|
+
return normalize_field(value).casefold()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def slugify(value: str, fallback: str) -> str:
|
|
94
|
+
ascii_value = value.encode("ascii", "ignore").decode("ascii")
|
|
95
|
+
words = re.findall(r"[a-zA-Z0-9]+", ascii_value.lower())
|
|
96
|
+
slug = "-".join(words[:5]).strip("-")
|
|
97
|
+
return slug or fallback
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def preferences_path(repo: Path) -> Path:
|
|
101
|
+
return repo / ".repo_memory" / "user-profile" / "preferences.md"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def lock_path(repo: Path) -> Path:
|
|
105
|
+
return repo / ".repo_memory" / "user-profile" / ".preferences.lock"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@contextmanager
|
|
109
|
+
def preference_lock(repo: Path) -> Iterator[None]:
|
|
110
|
+
path = lock_path(repo)
|
|
111
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
with path.open("a+b") as lock_file:
|
|
113
|
+
if os.name == "nt":
|
|
114
|
+
import msvcrt
|
|
115
|
+
|
|
116
|
+
lock_file.seek(0, os.SEEK_END)
|
|
117
|
+
if lock_file.tell() == 0:
|
|
118
|
+
lock_file.write(b"\0")
|
|
119
|
+
lock_file.flush()
|
|
120
|
+
lock_file.seek(0)
|
|
121
|
+
msvcrt.locking(lock_file.fileno(), msvcrt.LK_LOCK, 1)
|
|
122
|
+
try:
|
|
123
|
+
yield
|
|
124
|
+
finally:
|
|
125
|
+
lock_file.seek(0)
|
|
126
|
+
msvcrt.locking(lock_file.fileno(), msvcrt.LK_UNLCK, 1)
|
|
127
|
+
else:
|
|
128
|
+
import fcntl
|
|
129
|
+
|
|
130
|
+
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
|
|
131
|
+
try:
|
|
132
|
+
yield
|
|
133
|
+
finally:
|
|
134
|
+
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def ensure_storage(repo: Path) -> tuple[Path, bool]:
|
|
138
|
+
gitignore_updated = ensure_repo_memory_gitignore(repo)
|
|
139
|
+
path = preferences_path(repo)
|
|
140
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
if path.is_symlink():
|
|
142
|
+
raise StorageError("Invalid repo user profile preferences: preferences.md must not be a symlink")
|
|
143
|
+
if not path.exists():
|
|
144
|
+
write_preferences(path, [], now_iso())
|
|
145
|
+
return path, gitignore_updated
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def strip_ticks(value: str) -> str:
|
|
149
|
+
value = value.strip()
|
|
150
|
+
if value.startswith("`") and value.endswith("`") and len(value) >= 2:
|
|
151
|
+
return value[1:-1]
|
|
152
|
+
return value
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def field(block: str, name: str) -> str:
|
|
156
|
+
match = re.search(rf"^- {re.escape(name)}: (.*)$", block, flags=re.MULTILINE)
|
|
157
|
+
return match.group(1).strip() if match else ""
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def strip_yaml_scalar(value: str) -> str:
|
|
161
|
+
trimmed = value.strip()
|
|
162
|
+
if len(trimmed) >= 2 and trimmed[0] == trimmed[-1] and trimmed[0] in {'"', "'"}:
|
|
163
|
+
return trimmed[1:-1]
|
|
164
|
+
return trimmed
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def parse_frontmatter(text: str) -> dict[str, str]:
|
|
168
|
+
match = re.match(r"^---\r?\n(?P<body>.*?)\r?\n---(?:\r?\n|$)", text, flags=re.DOTALL)
|
|
169
|
+
if not match:
|
|
170
|
+
raise StorageError("Invalid repo user profile preferences: missing frontmatter")
|
|
171
|
+
metadata: dict[str, str] = {}
|
|
172
|
+
for raw_line in match.group("body").splitlines():
|
|
173
|
+
line = raw_line.strip()
|
|
174
|
+
if not line or line.startswith("#"):
|
|
175
|
+
continue
|
|
176
|
+
field_match = re.match(r"^([A-Za-z0-9_-]+):\s*(.*)$", line)
|
|
177
|
+
if not field_match:
|
|
178
|
+
raise StorageError("Invalid repo user profile preferences: malformed frontmatter")
|
|
179
|
+
metadata[field_match.group(1)] = strip_yaml_scalar(field_match.group(2))
|
|
180
|
+
return metadata
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def parse_non_negative_int(value: str, name: str) -> int:
|
|
184
|
+
normalized = value.strip().replace("_", "")
|
|
185
|
+
if not re.match(r"^\d+$", normalized):
|
|
186
|
+
raise StorageError(f"Invalid repo user profile preferences: {name} must be a non-negative integer")
|
|
187
|
+
return int(normalized)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def validate_frontmatter(text: str) -> tuple[int, int]:
|
|
191
|
+
metadata = parse_frontmatter(text)
|
|
192
|
+
expected = {
|
|
193
|
+
"schema": PREFERENCES_SCHEMA,
|
|
194
|
+
"scope": "repo",
|
|
195
|
+
"owner": OWNER,
|
|
196
|
+
"trust_state": "user_stated",
|
|
197
|
+
}
|
|
198
|
+
for key, value in expected.items():
|
|
199
|
+
if metadata.get(key) != value:
|
|
200
|
+
raise StorageError(f"Invalid repo user profile preferences: {key} mismatch")
|
|
201
|
+
if "active_count" not in metadata or "total_count" not in metadata:
|
|
202
|
+
raise StorageError("Invalid repo user profile preferences: missing counts")
|
|
203
|
+
return (
|
|
204
|
+
parse_non_negative_int(metadata["active_count"], "active_count"),
|
|
205
|
+
parse_non_negative_int(metadata["total_count"], "total_count"),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def read_preferences(path: Path) -> list[Preference]:
|
|
210
|
+
if path.is_symlink():
|
|
211
|
+
raise StorageError("Invalid repo user profile preferences: preferences.md must not be a symlink")
|
|
212
|
+
if not path.exists():
|
|
213
|
+
return []
|
|
214
|
+
if path.stat().st_size > MAX_PREFERENCES_BYTES:
|
|
215
|
+
raise StorageError("Invalid repo user profile preferences: preferences.md is too large")
|
|
216
|
+
text = path.read_text(encoding="utf-8")
|
|
217
|
+
declared_active, declared_total = validate_frontmatter(text)
|
|
218
|
+
entries: list[Preference] = []
|
|
219
|
+
matches = list(re.finditer(r"^## Preference (?P<id>pref_[^\s]+)\s*$", text, flags=re.MULTILINE))
|
|
220
|
+
if len(matches) != declared_total:
|
|
221
|
+
raise StorageError("Invalid repo user profile preferences: total_count mismatch")
|
|
222
|
+
for index, match in enumerate(matches):
|
|
223
|
+
start = match.end()
|
|
224
|
+
end = matches[index + 1].start() if index + 1 < len(matches) else len(text)
|
|
225
|
+
block = text[start:end]
|
|
226
|
+
status = strip_ticks(field(block, "Status"))
|
|
227
|
+
if status not in ALLOWED_STATUSES:
|
|
228
|
+
raise StorageError("Invalid repo user profile preferences: unknown status")
|
|
229
|
+
pref_type = strip_ticks(field(block, "Type"))
|
|
230
|
+
if pref_type not in ALLOWED_TYPES:
|
|
231
|
+
raise StorageError("Invalid repo user profile preferences: unknown type")
|
|
232
|
+
confidence = strip_ticks(field(block, "Confidence"))
|
|
233
|
+
created = strip_ticks(field(block, "Created"))
|
|
234
|
+
updated = strip_ticks(field(block, "Updated"))
|
|
235
|
+
if not confidence or not created or not updated:
|
|
236
|
+
raise StorageError("Invalid repo user profile preferences: missing required metadata")
|
|
237
|
+
description = normalize_field(field(block, "Description"))
|
|
238
|
+
if status != "active":
|
|
239
|
+
continue
|
|
240
|
+
if not description:
|
|
241
|
+
raise StorageError("Invalid repo user profile preferences: active preference missing description")
|
|
242
|
+
if pref_type not in ALLOWED_TYPES:
|
|
243
|
+
continue
|
|
244
|
+
entries.append(
|
|
245
|
+
Preference(
|
|
246
|
+
id=match.group("id"),
|
|
247
|
+
type=pref_type,
|
|
248
|
+
status=status,
|
|
249
|
+
confidence=confidence,
|
|
250
|
+
created=created,
|
|
251
|
+
updated=updated,
|
|
252
|
+
description=description,
|
|
253
|
+
applies_when=normalize_field(field(block, "Applies when")),
|
|
254
|
+
do_not_apply_when=normalize_field(field(block, "Do not apply when")),
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
if len(entries) != declared_active:
|
|
258
|
+
raise StorageError("Invalid repo user profile preferences: active_count mismatch")
|
|
259
|
+
return entries
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def entry_markdown(pref: Preference) -> str:
|
|
263
|
+
return "\n".join(
|
|
264
|
+
[
|
|
265
|
+
f"## Preference {pref.id}",
|
|
266
|
+
"",
|
|
267
|
+
f"- Type: `{pref.type}`",
|
|
268
|
+
"- Status: `active`",
|
|
269
|
+
f"- Confidence: `{pref.confidence}`",
|
|
270
|
+
f"- Created: `{pref.created}`",
|
|
271
|
+
f"- Updated: `{pref.updated}`",
|
|
272
|
+
f"- Description: {pref.description}",
|
|
273
|
+
f"- Applies when: {pref.applies_when or '-'}",
|
|
274
|
+
f"- Do not apply when: {pref.do_not_apply_when or '-'}",
|
|
275
|
+
f"- Raw lookup: `preferenceId={pref.id}`",
|
|
276
|
+
]
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def render_preferences(entries: list[Preference], updated_at: str) -> str:
|
|
281
|
+
sorted_entries = sorted(entries, key=lambda pref: pref.updated, reverse=True)
|
|
282
|
+
parts = [
|
|
283
|
+
"---",
|
|
284
|
+
f'schema: "{PREFERENCES_SCHEMA}"',
|
|
285
|
+
'scope: "repo"',
|
|
286
|
+
f'owner: "{OWNER}"',
|
|
287
|
+
'trust_state: "user_stated"',
|
|
288
|
+
f'updated_at: "{updated_at}"',
|
|
289
|
+
f"active_count: {len(sorted_entries)}",
|
|
290
|
+
f"total_count: {len(sorted_entries)}",
|
|
291
|
+
"---",
|
|
292
|
+
"",
|
|
293
|
+
"# Repo-Scoped User Profile And Preferences",
|
|
294
|
+
"",
|
|
295
|
+
"These memories are local to this repository. System, developer, and AGENTS.md instructions override current user instructions, and current user instructions override stored preferences. Do not treat these preferences as evidence about current code behavior.",
|
|
296
|
+
"",
|
|
297
|
+
"## Active Preferences",
|
|
298
|
+
"",
|
|
299
|
+
]
|
|
300
|
+
if sorted_entries:
|
|
301
|
+
parts.append("\n\n---\n\n".join(entry_markdown(pref) for pref in sorted_entries))
|
|
302
|
+
parts.append("")
|
|
303
|
+
return "\n".join(parts).rstrip() + "\n"
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def fsync_directory(path: Path) -> None:
|
|
307
|
+
if os.name == "nt":
|
|
308
|
+
return
|
|
309
|
+
try:
|
|
310
|
+
fd = os.open(path, os.O_RDONLY)
|
|
311
|
+
except OSError:
|
|
312
|
+
return
|
|
313
|
+
try:
|
|
314
|
+
os.fsync(fd)
|
|
315
|
+
finally:
|
|
316
|
+
os.close(fd)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def write_preferences(path: Path, entries: list[Preference], updated_at: str) -> None:
|
|
320
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
321
|
+
text = render_preferences(entries, updated_at)
|
|
322
|
+
if len(text.encode("utf-8")) > MAX_PREFERENCES_BYTES:
|
|
323
|
+
raise StorageError(
|
|
324
|
+
f"Invalid repo user profile preferences: rendered preferences.md exceeds {MAX_PREFERENCES_BYTES} bytes"
|
|
325
|
+
)
|
|
326
|
+
fd, tmp_name = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent)
|
|
327
|
+
tmp_path = Path(tmp_name)
|
|
328
|
+
try:
|
|
329
|
+
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as tmp_file:
|
|
330
|
+
tmp_file.write(text)
|
|
331
|
+
tmp_file.flush()
|
|
332
|
+
os.fsync(tmp_file.fileno())
|
|
333
|
+
os.replace(tmp_path, path)
|
|
334
|
+
fsync_directory(path.parent)
|
|
335
|
+
except Exception:
|
|
336
|
+
try:
|
|
337
|
+
tmp_path.unlink()
|
|
338
|
+
except FileNotFoundError:
|
|
339
|
+
pass
|
|
340
|
+
raise
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def make_id(entries: list[Preference], pref_type: str, description: str, timestamp: str) -> str:
|
|
344
|
+
day = timestamp[:10].replace("-", "")
|
|
345
|
+
base = f"pref_{day}_{slugify(description, pref_type + '-preference')}"
|
|
346
|
+
existing = {pref.id for pref in entries}
|
|
347
|
+
if base not in existing:
|
|
348
|
+
return base
|
|
349
|
+
counter = 2
|
|
350
|
+
while f"{base}-{counter}" in existing:
|
|
351
|
+
counter += 1
|
|
352
|
+
return f"{base}-{counter}"
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def preference_contains_add_content(pref: Preference, description: str) -> bool:
|
|
356
|
+
requested = normalize_key(description)
|
|
357
|
+
existing_text = normalize_key(" ".join([pref.description, pref.applies_when, pref.do_not_apply_when]))
|
|
358
|
+
return bool(requested) and requested in existing_text
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def add_preference(args: argparse.Namespace) -> dict[str, Any]:
|
|
362
|
+
repo = resolve_repo(args.repo)
|
|
363
|
+
with preference_lock(repo):
|
|
364
|
+
path, gitignore_updated = ensure_storage(repo)
|
|
365
|
+
entries = read_preferences(path)
|
|
366
|
+
pref_type = args.type
|
|
367
|
+
description = normalize_field(args.description)
|
|
368
|
+
applies_when = normalize_field(args.applies_when)
|
|
369
|
+
do_not_apply_when = normalize_field(args.do_not_apply_when or "")
|
|
370
|
+
for pref in entries:
|
|
371
|
+
if preference_contains_add_content(pref, description):
|
|
372
|
+
return {
|
|
373
|
+
"ok": True,
|
|
374
|
+
"op": "add",
|
|
375
|
+
"status": "duplicate",
|
|
376
|
+
"id": pref.id,
|
|
377
|
+
"active_count": len(entries),
|
|
378
|
+
"total_count": len(entries),
|
|
379
|
+
"preferences_path": str(path),
|
|
380
|
+
"gitignore_updated": gitignore_updated,
|
|
381
|
+
}
|
|
382
|
+
timestamp = now_iso()
|
|
383
|
+
pref = Preference(
|
|
384
|
+
id=make_id(entries, pref_type, description, timestamp),
|
|
385
|
+
type=pref_type,
|
|
386
|
+
description=description,
|
|
387
|
+
applies_when=applies_when,
|
|
388
|
+
do_not_apply_when=do_not_apply_when,
|
|
389
|
+
created=timestamp,
|
|
390
|
+
updated=timestamp,
|
|
391
|
+
)
|
|
392
|
+
entries.append(pref)
|
|
393
|
+
write_preferences(path, entries, timestamp)
|
|
394
|
+
return {
|
|
395
|
+
"ok": True,
|
|
396
|
+
"op": "add",
|
|
397
|
+
"status": "added",
|
|
398
|
+
"id": pref.id,
|
|
399
|
+
"active_count": len(entries),
|
|
400
|
+
"total_count": len(entries),
|
|
401
|
+
"preferences_path": str(path),
|
|
402
|
+
"gitignore_updated": gitignore_updated,
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def update_preference(args: argparse.Namespace) -> dict[str, Any]:
|
|
407
|
+
repo = resolve_repo(args.repo)
|
|
408
|
+
with preference_lock(repo):
|
|
409
|
+
path, gitignore_updated = ensure_storage(repo)
|
|
410
|
+
entries = read_preferences(path)
|
|
411
|
+
timestamp = now_iso()
|
|
412
|
+
updated_entries: list[Preference] = []
|
|
413
|
+
found = False
|
|
414
|
+
for pref in entries:
|
|
415
|
+
if pref.id != args.id:
|
|
416
|
+
updated_entries.append(pref)
|
|
417
|
+
continue
|
|
418
|
+
found = True
|
|
419
|
+
updated_entries.append(
|
|
420
|
+
replace(
|
|
421
|
+
pref,
|
|
422
|
+
description=normalize_field(args.description),
|
|
423
|
+
applies_when=normalize_field(args.applies_when) if args.applies_when is not None else pref.applies_when,
|
|
424
|
+
do_not_apply_when=normalize_field(args.do_not_apply_when) if args.do_not_apply_when is not None else pref.do_not_apply_when,
|
|
425
|
+
updated=timestamp,
|
|
426
|
+
)
|
|
427
|
+
)
|
|
428
|
+
if not found:
|
|
429
|
+
raise SystemExit(f"Preference id not found: {args.id}")
|
|
430
|
+
write_preferences(path, updated_entries, timestamp)
|
|
431
|
+
return {
|
|
432
|
+
"ok": True,
|
|
433
|
+
"op": "update",
|
|
434
|
+
"status": "updated",
|
|
435
|
+
"id": args.id,
|
|
436
|
+
"active_count": len(updated_entries),
|
|
437
|
+
"total_count": len(updated_entries),
|
|
438
|
+
"preferences_path": str(path),
|
|
439
|
+
"gitignore_updated": gitignore_updated,
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def delete_preference(args: argparse.Namespace) -> dict[str, Any]:
|
|
444
|
+
repo = resolve_repo(args.repo)
|
|
445
|
+
with preference_lock(repo):
|
|
446
|
+
path, gitignore_updated = ensure_storage(repo)
|
|
447
|
+
entries = read_preferences(path)
|
|
448
|
+
remaining = [pref for pref in entries if pref.id != args.id]
|
|
449
|
+
if len(remaining) == len(entries):
|
|
450
|
+
raise SystemExit(f"Preference id not found: {args.id}")
|
|
451
|
+
timestamp = now_iso()
|
|
452
|
+
write_preferences(path, remaining, timestamp)
|
|
453
|
+
return {
|
|
454
|
+
"ok": True,
|
|
455
|
+
"op": "delete",
|
|
456
|
+
"status": "deleted",
|
|
457
|
+
"id": args.id,
|
|
458
|
+
"active_count": len(remaining),
|
|
459
|
+
"total_count": len(remaining),
|
|
460
|
+
"preferences_path": str(path),
|
|
461
|
+
"gitignore_updated": gitignore_updated,
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def list_preferences(args: argparse.Namespace) -> dict[str, Any]:
|
|
466
|
+
repo = resolve_repo(args.repo)
|
|
467
|
+
path = preferences_path(repo)
|
|
468
|
+
entries = read_preferences(path)
|
|
469
|
+
return {
|
|
470
|
+
"ok": True,
|
|
471
|
+
"op": "list",
|
|
472
|
+
"active_count": len(entries),
|
|
473
|
+
"total_count": len(entries),
|
|
474
|
+
"preferences_path": str(path),
|
|
475
|
+
"preferences": [pref.__dict__ for pref in entries],
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def add_common_repo_arg(parser: argparse.ArgumentParser) -> None:
|
|
480
|
+
parser.add_argument("--repo", default=".", help="Path inside the target git repository")
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
484
|
+
parser = argparse.ArgumentParser(description="Manage repo-scoped user profile preferences.")
|
|
485
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
486
|
+
|
|
487
|
+
add = subparsers.add_parser("add", help="Add a repo-scoped user preference")
|
|
488
|
+
add_common_repo_arg(add)
|
|
489
|
+
add.add_argument("--type", required=True, choices=sorted(ALLOWED_TYPES))
|
|
490
|
+
add.add_argument("--description", required=True)
|
|
491
|
+
add.add_argument("--applies-when", required=True)
|
|
492
|
+
add.add_argument("--do-not-apply-when", default="")
|
|
493
|
+
add.set_defaults(func=add_preference)
|
|
494
|
+
|
|
495
|
+
update = subparsers.add_parser("update", help="Update an existing preference by id")
|
|
496
|
+
add_common_repo_arg(update)
|
|
497
|
+
update.add_argument("--id", required=True)
|
|
498
|
+
update.add_argument("--description", required=True)
|
|
499
|
+
update.add_argument("--applies-when")
|
|
500
|
+
update.add_argument("--do-not-apply-when")
|
|
501
|
+
update.set_defaults(func=update_preference)
|
|
502
|
+
|
|
503
|
+
delete = subparsers.add_parser("delete", help="Delete an existing preference by id")
|
|
504
|
+
add_common_repo_arg(delete)
|
|
505
|
+
delete.add_argument("--id", required=True)
|
|
506
|
+
delete.set_defaults(func=delete_preference)
|
|
507
|
+
|
|
508
|
+
list_cmd = subparsers.add_parser("list", help="List active preferences")
|
|
509
|
+
add_common_repo_arg(list_cmd)
|
|
510
|
+
list_cmd.set_defaults(func=list_preferences)
|
|
511
|
+
|
|
512
|
+
return parser
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def main(argv: list[str]) -> int:
|
|
516
|
+
parser = build_parser()
|
|
517
|
+
args = parser.parse_args(argv)
|
|
518
|
+
try:
|
|
519
|
+
result = args.func(args)
|
|
520
|
+
except StorageError as exc:
|
|
521
|
+
print(json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False), file=sys.stderr)
|
|
522
|
+
return 1
|
|
523
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
524
|
+
return 0
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
if __name__ == "__main__":
|
|
528
|
+
raise SystemExit(main(sys.argv[1:]))
|