@xopcai/xopc 0.0.123 → 0.0.124
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 +28 -11
- package/README.zh-CN.md +28 -11
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Bicr3ZBg.js → agent-avatar-dicebear-B5uSVAyJ.js} +1 -1
- package/dist/gateway/static/root/assets/agents-BjIxt67Z.js +2 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-CbEy9sR1.js → app-management-settings-panel-CYCONLIL.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-CTHja6n9.js → appearance-settings-Clw3ryKg.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-QICTeGjX.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-Bpy64GjU.js → archive-plugin-CXUGRp2i.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-DMkp4xSb.js → attachment-load-Cmiub3Wg.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-KnYaP7NT.js → automation-suggestion-card-CU0-4nQ-.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-Car-jtiz.js +1 -0
- package/dist/gateway/static/root/assets/{binary-plugins-DA9C5mCw.js → binary-plugins-0Izz-N3p.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-CiSXk_YC.js → block-editor-C1Kv0IkJ.js} +6 -6
- package/dist/gateway/static/root/assets/browser-settings-page-CMeBJzlS.js +8 -0
- package/dist/gateway/static/root/assets/{capability-presets-api-CEJ44WCU.js → capability-presets-api-BmQTM9Il.js} +1 -1
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-DZEJEoCF.js +1 -0
- package/dist/gateway/static/root/assets/{channel-recipient-api-CDhrhfhN.js → channel-recipient-api-BeA2aVor.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-kkW-xFau.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-BvaQvO5F.js +2 -0
- package/dist/gateway/static/root/assets/{directory-picker-path-field-7jKfbH0J.js → directory-picker-path-field-2rjzUu57.js} +1 -1
- package/dist/gateway/static/root/assets/{dreaming-settings-m1-wmz4o.js → dreaming-settings-DnHO686d.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-C9qQQrBS.js → extension-debug-page-BGLRuWVN.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DcBBMEGl.js → extension-page-D978ePp9.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-BQ-jMMP9.js → extension-settings-page-B6Ih4Swx.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-Dtpbbe0s.js → fetch-BU5S0Sn7.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-BN6pyX2c.js → gateway-config-api-BIPGwrl1.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-CMEA3r5u.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-BIET2x3y.js → gateway-startup-retry-Cd0Zz30N.js} +1 -1
- package/dist/gateway/static/root/assets/{goal-create-dialog-BuIMDx6C.js → goal-create-dialog-B-KHKeXN.js} +1 -1
- package/dist/gateway/static/root/assets/goal-detail-page-BxrSoWdj.js +1 -0
- package/dist/gateway/static/root/assets/{goals-config-api-CQm2r1Ty.js → goals-config-api-B95KdGMj.js} +1 -1
- package/dist/gateway/static/root/assets/goals-page-BTJzIl28.js +2 -0
- package/dist/gateway/static/root/assets/{goals-settings-DDrdRMxB.js → goals-settings-BJyEuLLm.js} +1 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-wkOjYNQT.js +1 -0
- package/dist/gateway/static/root/assets/{index-CBDr5xjB.css → index-BLaXr6Ev.css} +1 -1
- package/dist/gateway/static/root/assets/index-c_9TVr_C.js +191 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-C2TuQppb.js → keyboard-shortcuts-settings-BT8T4DyC.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-CrxBJUgI.js +2 -0
- package/dist/gateway/static/root/assets/{markdown-split-DhONQ81k.js → markdown-split-Dv66bO2b.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-DS44pJzr.js → media-plugins-BhIQU5yS.js} +1 -1
- package/dist/gateway/static/root/assets/messages-O6TAtdFC.js +3 -0
- package/dist/gateway/static/root/assets/models-hub-panel-DOOpz6y1.js +2 -0
- package/dist/gateway/static/root/assets/notes-page-Br16pK9v.js +1 -0
- package/dist/gateway/static/root/assets/notes-workbench-PSJAi0ZC.js +2 -0
- package/dist/gateway/static/root/assets/{preview-open-alternatives-Dej6zHnr.js → preview-open-alternatives-DGzet1d7.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-CIGGgKuw.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-B2grhsBt.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-CXMORxBD.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-Zu1FzH6z.js → save-bar-controls-Cqgb-jqa.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-BW78mKc0.js → schedule-field-DVZcI8Yt.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-Cc-IOydE.js → schema-form-Bj7sOxC8.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-Bi2wkBzU.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-B1L3G4IA.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-DIpIYht_.js → settings-collapsible-section-BmXn-8N6.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-BFxBNxOG.js → settings-form-section-BnqYMWHR.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-BfRSHJmO.js +1 -0
- package/dist/gateway/static/root/assets/{settings-page-layout-CLx4o2PN.js → settings-page-layout-CdWZU1hd.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-CWtPM7RK.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-ZswWCrDu.js → share-preview-page-IB1v8i86.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-ngbsBgij.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-BIe442Gv.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-BfchZRVa.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-3ZNcGRhS.js → system-settings-panel-BxQ384RC.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-bpzZjiVF.js → typed-models-lib-BkcpvFYO.js} +1 -1
- package/dist/gateway/static/root/assets/{use-channel-catalog-BCjZLZ3O.js → use-channel-catalog-Cd0FaEQ4.js} +1 -1
- package/dist/gateway/static/root/assets/{user-profile-settings-panel-CIXafITi.js → user-profile-settings-panel-DQ7NnRSY.js} +1 -1
- package/dist/gateway/static/root/assets/{utils-DXMtz5j_.js → utils-YKhVf7Ka.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-C5Ov8Uk_.js +1 -0
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-BYD9BtOq.js +3 -0
- package/dist/gateway/static/root/assets/{workflow-start-dialog-B4sZpTDQ.js → workflow-start-dialog-BNJVCWoS.js} +1 -1
- package/dist/gateway/static/root/assets/workflows-page-BYF29ANm.js +18 -0
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.d.ts +6 -1
- package/dist/src/agent/agent-manager.js +28 -12
- package/dist/src/agent/agent-manager.js.map +1 -1
- package/dist/src/agent/context/workspace-templates/AGENTS.md +1 -1
- package/dist/src/agent/embedded/xopc-tools-bridge.js +11 -7
- package/dist/src/agent/embedded/xopc-tools-bridge.js.map +1 -1
- package/dist/src/agent/lifecycle/progress.js +3 -1
- package/dist/src/agent/lifecycle/progress.js.map +1 -1
- package/dist/src/agent/lifecycle/timeout-wrapper.d.ts +2 -2
- package/dist/src/agent/lifecycle/timeout-wrapper.js +4 -4
- package/dist/src/agent/lifecycle/timeout-wrapper.js.map +1 -1
- package/dist/src/agent/memory/summary-generator.d.ts +1 -1
- package/dist/src/agent/memory/summary-generator.js +1 -2
- package/dist/src/agent/memory/summary-generator.js.map +1 -1
- package/dist/src/agent/middleware/index.d.ts +1 -1
- package/dist/src/agent/middleware/self-verify.d.ts +44 -14
- package/dist/src/agent/middleware/self-verify.js +214 -44
- package/dist/src/agent/middleware/self-verify.js.map +1 -1
- package/dist/src/agent/orchestration/agent-event-handler.js +57 -15
- package/dist/src/agent/orchestration/agent-event-handler.js.map +1 -1
- package/dist/src/agent/prompt/sections/tooling.js +18 -13
- package/dist/src/agent/prompt/sections/tooling.js.map +1 -1
- package/dist/src/agent/prompt/system-reminder.js +2 -2
- package/dist/src/agent/prompt/system-reminder.js.map +1 -1
- package/dist/src/agent/sandbox/sanitize-env-vars.d.ts +2 -2
- package/dist/src/agent/sandbox/sanitize-env-vars.js +2 -2
- package/dist/src/agent/sandbox/sanitize-env-vars.js.map +1 -1
- package/dist/src/agent/sandbox/types.d.ts +2 -2
- package/dist/src/agent/sandbox/types.js.map +1 -1
- package/dist/src/agent/service/btw-query.js +1 -1
- package/dist/src/agent/service/btw-query.js.map +1 -1
- package/dist/src/agent/service/process-direct-one-shot.js +6 -2
- package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +12 -7
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service.js +2 -1
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/skills/skill-tool-gating.js +1 -1
- package/dist/src/agent/skills/skill-tool-gating.js.map +1 -1
- package/dist/src/agent/starter-agents.js +9 -9
- package/dist/src/agent/starter-agents.js.map +1 -1
- package/dist/src/agent/tools/apply-patch.d.ts +25 -0
- package/dist/src/agent/tools/apply-patch.js +256 -0
- package/dist/src/agent/tools/apply-patch.js.map +1 -0
- package/dist/src/agent/tools/delegate-tool.d.ts +1 -1
- package/dist/src/agent/tools/delegate-tool.js +3 -3
- package/dist/src/agent/tools/delegate-tool.js.map +1 -1
- package/dist/src/agent/tools/exec-command.d.ts +30 -0
- package/dist/src/agent/tools/exec-command.js +224 -0
- package/dist/src/agent/tools/exec-command.js.map +1 -0
- package/dist/src/agent/tools/execute-code-tool.js +7 -4
- package/dist/src/agent/tools/execute-code-tool.js.map +1 -1
- package/dist/src/agent/tools/factory.d.ts +3 -3
- package/dist/src/agent/tools/factory.js +7 -8
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/index.d.ts +3 -2
- package/dist/src/agent/tools/index.js +5 -4
- package/dist/src/agent/tools/skills-tools.d.ts +1 -1
- package/dist/src/agent/tools/skills-tools.js.map +1 -1
- package/dist/src/agent/tools/update-plan-tool.d.ts +11 -0
- package/dist/src/agent/tools/update-plan-tool.js +72 -0
- package/dist/src/agent/tools/update-plan-tool.js.map +1 -0
- package/dist/src/agent/workflow/builtins/debug-incident.d.ts +1 -1
- package/dist/src/agent/workflow/builtins/debug-incident.js +1 -1
- package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -1
- package/dist/src/agent/workflow/builtins/pr-review.d.ts +1 -1
- package/dist/src/agent/workflow/builtins/pr-review.js +1 -1
- package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -1
- package/dist/src/agent/workflow/builtins/release-check.d.ts +1 -1
- package/dist/src/agent/workflow/builtins/release-check.js +1 -1
- package/dist/src/agent/workflow/builtins/release-check.js.map +1 -1
- package/dist/src/agent/workflow/step-labels.js +7 -3
- package/dist/src/agent/workflow/step-labels.js.map +1 -1
- package/dist/src/chat-commands/agent-edit.js +1 -1
- package/dist/src/chat-commands/agent-edit.js.map +1 -1
- package/dist/src/chat-commands/builtins/review.d.ts +1 -0
- package/dist/src/chat-commands/builtins/review.js +167 -0
- package/dist/src/chat-commands/builtins/review.js.map +1 -0
- package/dist/src/chat-commands/index.d.ts +1 -0
- package/dist/src/chat-commands/index.js +3 -1
- package/dist/src/chat-commands/index.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
- package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
- package/dist/src/gateway/agent-builtin-tools.js +3 -2
- package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
- package/dist/src/gateway/chat-stream/mapper.d.ts +6 -0
- package/dist/src/gateway/chat-stream/mapper.js +149 -6
- package/dist/src/gateway/chat-stream/mapper.js.map +1 -1
- package/dist/src/gateway/chat-stream/protocol.d.ts +51 -1
- package/dist/src/gateway/hono/routes/commands-skills.js +21 -0
- package/dist/src/gateway/hono/routes/commands-skills.js.map +1 -1
- package/dist/src/gateway/hono/routes/index.js +2 -0
- package/dist/src/gateway/hono/routes/index.js.map +1 -1
- package/dist/src/gateway/hono/routes/lazy-bundles.js +1 -0
- package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
- package/dist/src/gateway/hono/routes/projects.js +40 -2
- package/dist/src/gateway/hono/routes/projects.js.map +1 -1
- package/dist/src/gateway/hono/routes/search.d.ts +3 -0
- package/dist/src/gateway/hono/routes/search.js +47 -0
- package/dist/src/gateway/hono/routes/search.js.map +1 -0
- package/dist/src/gateway/security/dangerous-tools.js +1 -1
- package/dist/src/gateway/security/dangerous-tools.js.map +1 -1
- package/dist/src/infra/update-restart.js +1 -1
- package/dist/src/mcp/channel-bridge.js +1 -1
- package/dist/src/projects/index.d.ts +2 -0
- package/dist/src/projects/index.js +3 -1
- package/dist/src/projects/project-agent-suggestion.d.ts +14 -0
- package/dist/src/projects/project-agent-suggestion.js +19 -0
- package/dist/src/projects/project-agent-suggestion.js.map +1 -0
- package/dist/src/projects/project-kind.d.ts +13 -0
- package/dist/src/projects/project-kind.js +147 -0
- package/dist/src/projects/project-kind.js.map +1 -0
- package/dist/src/projects/project-service.d.ts +1 -0
- package/dist/src/projects/project-service.js +1 -1
- package/dist/src/projects/project-service.js.map +1 -1
- package/dist/src/projects/project-store.js +32 -8
- package/dist/src/projects/project-store.js.map +1 -1
- package/dist/src/projects/types.d.ts +1 -0
- package/dist/src/review/review-git.d.ts +48 -0
- package/dist/src/review/review-git.js +333 -0
- package/dist/src/review/review-git.js.map +1 -0
- package/dist/src/review/review-types.d.ts +22 -0
- package/dist/src/review/review-types.js +29 -0
- package/dist/src/review/review-types.js.map +1 -0
- package/dist/src/session/session-context-for-llm.js +151 -0
- package/dist/src/session/session-context-for-llm.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/021_projects_fts.sql +21 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/tui/backends/embedded-backend.d.ts +1 -0
- package/dist/src/tui/backends/embedded-backend.js +20 -0
- package/dist/src/tui/backends/embedded-backend.js.map +1 -1
- package/dist/src/tui/backends/gateway-sse-backend.d.ts +2 -0
- package/dist/src/tui/backends/gateway-sse-backend.js +7 -0
- package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
- package/dist/src/tui/components/assistant-message.js +35 -0
- package/dist/src/tui/components/assistant-message.js.map +1 -1
- package/dist/src/tui/tui-agent-events.js +25 -0
- package/dist/src/tui/tui-agent-events.js.map +1 -1
- package/dist/src/tui/tui-backend.d.ts +4 -0
- package/dist/src/tui/tui-commands.d.ts +1 -0
- package/dist/src/tui/tui-commands.js +110 -4
- package/dist/src/tui/tui-commands.js.map +1 -1
- package/dist/src/tui/tui-picker-overlay.d.ts +2 -0
- package/dist/src/tui/tui-picker-overlay.js +153 -1
- package/dist/src/tui/tui-picker-overlay.js.map +1 -1
- package/dist/src/tui/tui.js +16 -3
- package/dist/src/tui/tui.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agents-CxDSFHGx.js +0 -2
- package/dist/gateway/static/root/assets/apps-page-CyZdgqvm.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-8VLKM4au.js +0 -1
- package/dist/gateway/static/root/assets/browser-settings-page-5tPg0DcP.js +0 -8
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-BJW3aVST.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-CorUCTrs.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-VKstC3sP.js +0 -2
- package/dist/gateway/static/root/assets/gateway-settings-hQtgVmSR.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-FEypCq74.js +0 -1
- package/dist/gateway/static/root/assets/goals-page-C4-2_MLG.js +0 -2
- package/dist/gateway/static/root/assets/heartbeat-settings-BJVok3Sl.js +0 -1
- package/dist/gateway/static/root/assets/index-T0SaNd5a.js +0 -191
- package/dist/gateway/static/root/assets/logs-page-DwkwPgid.js +0 -2
- package/dist/gateway/static/root/assets/messages-avfvyZuQ.js +0 -3
- package/dist/gateway/static/root/assets/models-hub-panel-huNY4A1G.js +0 -2
- package/dist/gateway/static/root/assets/notes-page-DdfGuEZ1.js +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-B0bT9Hqa.js +0 -2
- package/dist/gateway/static/root/assets/project-detail-page-CjxhPorG.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-C4Z5OJMF.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-CVCOPUVd.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-B4qExXHC.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-Ce8yLTBi.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-C4fivemT.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-Kyc4eUf0.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-Bu76CMiC.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-D-CFg86j.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-CUU4f6Ss.js +0 -2
- package/dist/gateway/static/root/assets/voice-api-key-field-C1MrQD8R.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-DqizIcY5.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-B3sVNPFk.js +0 -18
- package/dist/src/agent/tools/edit.d.ts +0 -14
- package/dist/src/agent/tools/edit.js +0 -132
- package/dist/src/agent/tools/edit.js.map +0 -1
- package/dist/src/agent/tools/shell.d.ts +0 -15
- package/dist/src/agent/tools/shell.js +0 -158
- package/dist/src/agent/tools/shell.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-service.js","names":[],"sources":["../../../src/projects/project-service.ts"],"sourcesContent":["import { getSessionMetadata } from '../storage/sqlite/index.js';\nimport { ProjectStore } from './project-store.js';\nimport { bindGoalToProject, listProjectGoalIds, unbindGoalFromProject } from './goal-bind.js';\nimport { bindSessionToProject, listProjectSessionKeys, unbindSessionFromProject } from './session-bind.js';\nimport type { CreateProjectInput, Project, ProjectListQuery, ProjectListResult, ProjectWithDetails, UpdateProjectInput } from './types.js';\nimport {\n canonicalWorkspacePath,\n ensureWorkspaceDirectory,\n inferProjectNameFromWorkspaceRoot,\n isPathSameOrInsideWorkspace,\n isSafeAutoCreateWorkspaceRoot,\n ProjectWorkspaceConflictError,\n ProjectWorkspaceMissingError,\n resolveWorkspaceProjectRoot,\n workspaceDirectoryExists,\n type WorkspaceProjectMatch,\n} from './workspace-project.js';\n\nexport type ProjectSuggestion = {\n projectId: string;\n projectName: string;\n score: number;\n reason: string;\n};\n\nexport class ProjectService {\n constructor(private readonly store = new ProjectStore()) {}\n\n private listAllProjects(): Project[] {\n const items: Project[] = [];\n let offset = 0;\n const limit = 500;\n while (true) {\n const page = this.list({ limit, offset });\n items.push(...page.items);\n if (!page.hasMore || page.items.length === 0) break;\n offset += page.items.length;\n }\n return items;\n }\n\n create(input: CreateProjectInput): Project {\n const rawWorkspaceRoot = input.workspaceRoot?.trim();\n const workspaceRoot = rawWorkspaceRoot ? canonicalWorkspacePath(rawWorkspaceRoot) : undefined;\n if (rawWorkspaceRoot && !workspaceRoot) {\n throw new Error('Invalid workspace root');\n }\n if (workspaceRoot) {\n const existing = this.findByWorkspaceRoot(workspaceRoot);\n if (existing) throw new ProjectWorkspaceConflictError(existing);\n if (!workspaceDirectoryExists(workspaceRoot)) {\n if (input.createWorkspaceRoot === true) {\n ensureWorkspaceDirectory(workspaceRoot);\n } else {\n throw new ProjectWorkspaceMissingError(workspaceRoot);\n }\n }\n }\n const name = input.name?.trim() || inferProjectNameFromWorkspaceRoot(workspaceRoot) || '';\n const slug = input.slug?.trim() || this.store.generateSlug(name);\n return this.store.create({ ...input, name, slug, workspaceRoot });\n }\n\n get(id: string): Project | null {\n return this.store.get(id);\n }\n\n getBySlug(slug: string): Project | null {\n return this.store.findBySlug(slug);\n }\n\n list(query?: ProjectListQuery): ProjectListResult {\n return this.store.list(query);\n }\n\n findByWorkspaceRoot(workspaceRoot: string, options: { excludeProjectId?: string } = {}): Project | null {\n const target = canonicalWorkspacePath(workspaceRoot);\n if (!target) return null;\n for (const project of this.listAllProjects()) {\n if (options.excludeProjectId && project.id === options.excludeProjectId) continue;\n const projectRoot = canonicalWorkspacePath(project.workspaceRoot);\n if (projectRoot && isPathSameOrInsideWorkspace(projectRoot, target) && isPathSameOrInsideWorkspace(target, projectRoot)) {\n return project;\n }\n }\n return null;\n }\n\n resolveForWorkspacePath(workspacePath: string): { project: Project; reason: 'exact' | 'contained' } | null {\n const target = canonicalWorkspacePath(workspacePath);\n if (!target) return null;\n let best: { project: Project; root: string; reason: 'exact' | 'contained' } | null = null;\n for (const project of this.listAllProjects()) {\n const root = canonicalWorkspacePath(project.workspaceRoot);\n if (!root || !isPathSameOrInsideWorkspace(root, target)) continue;\n const reason = isPathSameOrInsideWorkspace(root, target) && isPathSameOrInsideWorkspace(target, root) ? 'exact' : 'contained';\n if (!best || root.length > best.root.length) {\n best = { project, root, reason };\n }\n }\n return best ? { project: best.project, reason: best.reason } : null;\n }\n\n resolveOrCreateForWorkspacePath(input: {\n workspacePath: string;\n agentId?: string;\n autoCreate?: boolean;\n }): WorkspaceProjectMatch | null {\n const existing = this.resolveForWorkspacePath(input.workspacePath);\n if (existing) return { project: existing.project, reason: existing.reason, created: false };\n if (!input.autoCreate) return null;\n const workspaceRoot = resolveWorkspaceProjectRoot(input.workspacePath) ?? canonicalWorkspacePath(input.workspacePath);\n if (!workspaceRoot || !isSafeAutoCreateWorkspaceRoot(workspaceRoot)) return null;\n const rootExisting = this.resolveForWorkspacePath(workspaceRoot);\n if (rootExisting) return { project: rootExisting.project, reason: rootExisting.reason, created: false };\n const project = this.create({\n name: inferProjectNameFromWorkspaceRoot(workspaceRoot) ?? undefined,\n workspaceRoot,\n defaultAgentId: input.agentId,\n });\n return { project, reason: 'auto_created', created: true };\n }\n\n update(id: string, input: UpdateProjectInput): Project {\n const patch = { ...input };\n if (input.workspaceRoot !== undefined && input.workspaceRoot !== null && input.workspaceRoot.trim()) {\n const workspaceRoot = canonicalWorkspacePath(input.workspaceRoot);\n if (!workspaceRoot) {\n throw new Error('Invalid workspace root');\n }\n const existing = this.findByWorkspaceRoot(workspaceRoot, { excludeProjectId: id });\n if (existing) throw new ProjectWorkspaceConflictError(existing);\n if (!workspaceDirectoryExists(workspaceRoot)) {\n if (input.createWorkspaceRoot === true) {\n ensureWorkspaceDirectory(workspaceRoot);\n } else {\n throw new ProjectWorkspaceMissingError(workspaceRoot);\n }\n }\n patch.workspaceRoot = workspaceRoot;\n }\n return this.store.update(id, patch);\n }\n\n delete(id: string): void {\n this.store.delete(id);\n }\n\n getWithDetails(id: string): ProjectWithDetails | null {\n return this.store.getWithDetails(id);\n }\n\n attachSession(sessionKey: string, projectId: string): void {\n bindSessionToProject(sessionKey, projectId);\n }\n\n detachSession(sessionKey: string): void {\n unbindSessionFromProject(sessionKey);\n }\n\n listSessionKeys(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectSessionKeys(projectId, limit, offset);\n }\n\n attachGoal(goalId: string, projectId: string): void {\n bindGoalToProject(goalId, projectId);\n }\n\n detachGoal(goalId: string): void {\n unbindGoalFromProject(goalId);\n }\n\n listGoalIds(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectGoalIds(projectId, limit, offset);\n }\n\n suggestProjectsForSession(sessionKey: string): ProjectSuggestion[] {\n const session = getSessionMetadata(sessionKey);\n if (!session) return [];\n const projects = this.list({ status: 'active', limit: 500 }).items;\n const haystack = [session.key, session.name, session.routing?.agentId, session.sourceChannel, ...(session.tags ?? [])]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n const suggestions: ProjectSuggestion[] = [];\n for (const project of projects) {\n let score = 0;\n const reasons: string[] = [];\n if (haystack.includes(project.slug.toLowerCase())) {\n score += 10;\n reasons.push('session metadata matches project slug');\n }\n for (const token of project.name.toLowerCase().split(/\\s+/).filter((t) => t.length >= 3)) {\n if (haystack.includes(token)) {\n score += 3;\n reasons.push(`matches ${token}`);\n break;\n }\n }\n if (score > 0) {\n suggestions.push({ projectId: project.id, projectName: project.name, score, reason: reasons.join(', ') });\n }\n }\n return suggestions.sort((a, b) => b.score - a.score).slice(0, 5);\n }\n}\n"],"mappings":";;;;;;;AAyBA,IAAa,iBAAb,MAA4B;CAC1B,YAAY,QAAyB,IAAI,cAAc,EAAE;AAA5B,OAAA,QAAA;;CAE7B,kBAAqC;EACnC,MAAM,QAAmB,EAAE;EAC3B,IAAI,SAAS;EACb,MAAM,QAAQ;AACd,SAAO,MAAM;GACX,MAAM,OAAO,KAAK,KAAK;IAAE;IAAO;IAAQ,CAAC;AACzC,SAAM,KAAK,GAAG,KAAK,MAAM;AACzB,OAAI,CAAC,KAAK,WAAW,KAAK,MAAM,WAAW,EAAG;AAC9C,aAAU,KAAK,MAAM;;AAEvB,SAAO;;CAGT,OAAO,OAAoC;EACzC,MAAM,mBAAmB,MAAM,eAAe,MAAM;EACpD,MAAM,gBAAgB,mBAAmB,uBAAuB,iBAAiB,GAAG,KAAA;AACpF,MAAI,oBAAoB,CAAC,cACvB,OAAM,IAAI,MAAM,yBAAyB;AAE3C,MAAI,eAAe;GACjB,MAAM,WAAW,KAAK,oBAAoB,cAAc;AACxD,OAAI,SAAU,OAAM,IAAI,8BAA8B,SAAS;AAC/D,OAAI,CAAC,yBAAyB,cAAc,CAC1C,KAAI,MAAM,wBAAwB,KAChC,0BAAyB,cAAc;OAEvC,OAAM,IAAI,6BAA6B,cAAc;;EAI3D,MAAM,OAAO,MAAM,MAAM,MAAM,IAAI,kCAAkC,cAAc,IAAI;EACvF,MAAM,OAAO,MAAM,MAAM,MAAM,IAAI,KAAK,MAAM,aAAa,KAAK;AAChE,SAAO,KAAK,MAAM,OAAO;GAAE,GAAG;GAAO;GAAM;GAAM;GAAe,CAAC;;CAGnE,IAAI,IAA4B;AAC9B,SAAO,KAAK,MAAM,IAAI,GAAG;;CAG3B,UAAU,MAA8B;AACtC,SAAO,KAAK,MAAM,WAAW,KAAK;;CAGpC,KAAK,OAA6C;AAChD,SAAO,KAAK,MAAM,KAAK,MAAM;;CAG/B,oBAAoB,eAAuB,UAAyC,EAAE,EAAkB;EACtG,MAAM,SAAS,uBAAuB,cAAc;AACpD,MAAI,CAAC,OAAQ,QAAO;AACpB,OAAK,MAAM,WAAW,KAAK,iBAAiB,EAAE;AAC5C,OAAI,QAAQ,oBAAoB,QAAQ,OAAO,QAAQ,iBAAkB;GACzE,MAAM,cAAc,uBAAuB,QAAQ,cAAc;AACjE,OAAI,eAAe,4BAA4B,aAAa,OAAO,IAAI,4BAA4B,QAAQ,YAAY,CACrH,QAAO;;AAGX,SAAO;;CAGT,wBAAwB,eAAmF;EACzG,MAAM,SAAS,uBAAuB,cAAc;AACpD,MAAI,CAAC,OAAQ,QAAO;EACpB,IAAI,OAAiF;AACrF,OAAK,MAAM,WAAW,KAAK,iBAAiB,EAAE;GAC5C,MAAM,OAAO,uBAAuB,QAAQ,cAAc;AAC1D,OAAI,CAAC,QAAQ,CAAC,4BAA4B,MAAM,OAAO,CAAE;GACzD,MAAM,SAAS,4BAA4B,MAAM,OAAO,IAAI,4BAA4B,QAAQ,KAAK,GAAG,UAAU;AAClH,OAAI,CAAC,QAAQ,KAAK,SAAS,KAAK,KAAK,OACnC,QAAO;IAAE;IAAS;IAAM;IAAQ;;AAGpC,SAAO,OAAO;GAAE,SAAS,KAAK;GAAS,QAAQ,KAAK;GAAQ,GAAG;;CAGjE,gCAAgC,OAIC;EAC/B,MAAM,WAAW,KAAK,wBAAwB,MAAM,cAAc;AAClE,MAAI,SAAU,QAAO;GAAE,SAAS,SAAS;GAAS,QAAQ,SAAS;GAAQ,SAAS;GAAO;AAC3F,MAAI,CAAC,MAAM,WAAY,QAAO;EAC9B,MAAM,gBAAgB,4BAA4B,MAAM,cAAc,IAAI,uBAAuB,MAAM,cAAc;AACrH,MAAI,CAAC,iBAAiB,CAAC,8BAA8B,cAAc,CAAE,QAAO;EAC5E,MAAM,eAAe,KAAK,wBAAwB,cAAc;AAChE,MAAI,aAAc,QAAO;GAAE,SAAS,aAAa;GAAS,QAAQ,aAAa;GAAQ,SAAS;GAAO;AAMvG,SAAO;GAAE,SALO,KAAK,OAAO;IAC1B,MAAM,kCAAkC,cAAc,IAAI,KAAA;IAC1D;IACA,gBAAgB,MAAM;IACvB,CACe;GAAE,QAAQ;GAAgB,SAAS;GAAM;;CAG3D,OAAO,IAAY,OAAoC;EACrD,MAAM,QAAQ,EAAE,GAAG,OAAO;AAC1B,MAAI,MAAM,kBAAkB,KAAA,KAAa,MAAM,kBAAkB,QAAQ,MAAM,cAAc,MAAM,EAAE;GACnG,MAAM,gBAAgB,uBAAuB,MAAM,cAAc;AACjE,OAAI,CAAC,cACH,OAAM,IAAI,MAAM,yBAAyB;GAE3C,MAAM,WAAW,KAAK,oBAAoB,eAAe,EAAE,kBAAkB,IAAI,CAAC;AAClF,OAAI,SAAU,OAAM,IAAI,8BAA8B,SAAS;AAC/D,OAAI,CAAC,yBAAyB,cAAc,CAC1C,KAAI,MAAM,wBAAwB,KAChC,0BAAyB,cAAc;OAEvC,OAAM,IAAI,6BAA6B,cAAc;AAGzD,SAAM,gBAAgB;;AAExB,SAAO,KAAK,MAAM,OAAO,IAAI,MAAM;;CAGrC,OAAO,IAAkB;AACvB,OAAK,MAAM,OAAO,GAAG;;CAGvB,eAAe,IAAuC;AACpD,SAAO,KAAK,MAAM,eAAe,GAAG;;CAGtC,cAAc,YAAoB,WAAyB;AACzD,uBAAqB,YAAY,UAAU;;CAG7C,cAAc,YAA0B;AACtC,2BAAyB,WAAW;;CAGtC,gBAAgB,WAAmB,OAAgB,QAA2B;AAC5E,SAAO,uBAAuB,WAAW,OAAO,OAAO;;CAGzD,WAAW,QAAgB,WAAyB;AAClD,oBAAkB,QAAQ,UAAU;;CAGtC,WAAW,QAAsB;AAC/B,wBAAsB,OAAO;;CAG/B,YAAY,WAAmB,OAAgB,QAA2B;AACxE,SAAO,mBAAmB,WAAW,OAAO,OAAO;;CAGrD,0BAA0B,YAAyC;EACjE,MAAM,UAAU,mBAAmB,WAAW;AAC9C,MAAI,CAAC,QAAS,QAAO,EAAE;EACvB,MAAM,WAAW,KAAK,KAAK;GAAE,QAAQ;GAAU,OAAO;GAAK,CAAC,CAAC;EAC7D,MAAM,WAAW;GAAC,QAAQ;GAAK,QAAQ;GAAM,QAAQ,SAAS;GAAS,QAAQ;GAAe,GAAI,QAAQ,QAAQ,EAAE;GAAE,CACnH,OAAO,QAAQ,CACf,KAAK,IAAI,CACT,aAAa;EAChB,MAAM,cAAmC,EAAE;AAC3C,OAAK,MAAM,WAAW,UAAU;GAC9B,IAAI,QAAQ;GACZ,MAAM,UAAoB,EAAE;AAC5B,OAAI,SAAS,SAAS,QAAQ,KAAK,aAAa,CAAC,EAAE;AACjD,aAAS;AACT,YAAQ,KAAK,wCAAwC;;AAEvD,QAAK,MAAM,SAAS,QAAQ,KAAK,aAAa,CAAC,MAAM,MAAM,CAAC,QAAQ,MAAM,EAAE,UAAU,EAAE,CACtF,KAAI,SAAS,SAAS,MAAM,EAAE;AAC5B,aAAS;AACT,YAAQ,KAAK,WAAW,QAAQ;AAChC;;AAGJ,OAAI,QAAQ,EACV,aAAY,KAAK;IAAE,WAAW,QAAQ;IAAI,aAAa,QAAQ;IAAM;IAAO,QAAQ,QAAQ,KAAK,KAAK;IAAE,CAAC;;AAG7G,SAAO,YAAY,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,GAAG,EAAE"}
|
|
1
|
+
{"version":3,"file":"project-service.js","names":[],"sources":["../../../src/projects/project-service.ts"],"sourcesContent":["import { getSessionMetadata } from '../storage/sqlite/index.js';\nimport { ProjectStore } from './project-store.js';\nimport { bindGoalToProject, listProjectGoalIds, unbindGoalFromProject } from './goal-bind.js';\nimport { bindSessionToProject, listProjectSessionKeys, unbindSessionFromProject } from './session-bind.js';\nimport type { CreateProjectInput, Project, ProjectListQuery, ProjectListResult, ProjectWithDetails, UpdateProjectInput } from './types.js';\nimport {\n canonicalWorkspacePath,\n ensureWorkspaceDirectory,\n inferProjectNameFromWorkspaceRoot,\n isPathSameOrInsideWorkspace,\n isSafeAutoCreateWorkspaceRoot,\n ProjectWorkspaceConflictError,\n ProjectWorkspaceMissingError,\n resolveWorkspaceProjectRoot,\n workspaceDirectoryExists,\n type WorkspaceProjectMatch,\n} from './workspace-project.js';\n\nexport type ProjectSuggestion = {\n projectId: string;\n projectName: string;\n score: number;\n reason: string;\n};\n\nexport class ProjectService {\n constructor(private readonly store = new ProjectStore()) {}\n\n private listAllProjects(): Project[] {\n const items: Project[] = [];\n let offset = 0;\n const limit = 500;\n while (true) {\n const page = this.list({ limit, offset });\n items.push(...page.items);\n if (!page.hasMore || page.items.length === 0) break;\n offset += page.items.length;\n }\n return items;\n }\n\n create(input: CreateProjectInput): Project {\n const rawWorkspaceRoot = input.workspaceRoot?.trim();\n const workspaceRoot = rawWorkspaceRoot ? canonicalWorkspacePath(rawWorkspaceRoot) : undefined;\n if (rawWorkspaceRoot && !workspaceRoot) {\n throw new Error('Invalid workspace root');\n }\n if (workspaceRoot) {\n const existing = this.findByWorkspaceRoot(workspaceRoot);\n if (existing) throw new ProjectWorkspaceConflictError(existing);\n if (!workspaceDirectoryExists(workspaceRoot)) {\n if (input.createWorkspaceRoot === true) {\n ensureWorkspaceDirectory(workspaceRoot);\n } else {\n throw new ProjectWorkspaceMissingError(workspaceRoot);\n }\n }\n }\n const name = input.name?.trim() || inferProjectNameFromWorkspaceRoot(workspaceRoot) || '';\n const slug = input.slug?.trim() || this.store.generateSlug(name);\n return this.store.create({ ...input, name, slug, workspaceRoot });\n }\n\n get(id: string): Project | null {\n return this.store.get(id);\n }\n\n getBySlug(slug: string): Project | null {\n return this.store.findBySlug(slug);\n }\n\n list(query?: ProjectListQuery): ProjectListResult {\n return this.store.list(query);\n }\n\n findByWorkspaceRoot(workspaceRoot: string, options: { excludeProjectId?: string } = {}): Project | null {\n const target = canonicalWorkspacePath(workspaceRoot);\n if (!target) return null;\n for (const project of this.listAllProjects()) {\n if (options.excludeProjectId && project.id === options.excludeProjectId) continue;\n const projectRoot = canonicalWorkspacePath(project.workspaceRoot);\n if (projectRoot && isPathSameOrInsideWorkspace(projectRoot, target) && isPathSameOrInsideWorkspace(target, projectRoot)) {\n return project;\n }\n }\n return null;\n }\n\n resolveForWorkspacePath(workspacePath: string): { project: Project; reason: 'exact' | 'contained' } | null {\n const target = canonicalWorkspacePath(workspacePath);\n if (!target) return null;\n let best: { project: Project; root: string; reason: 'exact' | 'contained' } | null = null;\n for (const project of this.listAllProjects()) {\n const root = canonicalWorkspacePath(project.workspaceRoot);\n if (!root || !isPathSameOrInsideWorkspace(root, target)) continue;\n const reason = isPathSameOrInsideWorkspace(root, target) && isPathSameOrInsideWorkspace(target, root) ? 'exact' : 'contained';\n if (!best || root.length > best.root.length) {\n best = { project, root, reason };\n }\n }\n return best ? { project: best.project, reason: best.reason } : null;\n }\n\n resolveOrCreateForWorkspacePath(input: {\n workspacePath: string;\n agentId?: string;\n defaultAgentId?: string;\n autoCreate?: boolean;\n }): WorkspaceProjectMatch | null {\n const existing = this.resolveForWorkspacePath(input.workspacePath);\n if (existing) return { project: existing.project, reason: existing.reason, created: false };\n if (!input.autoCreate) return null;\n const workspaceRoot = resolveWorkspaceProjectRoot(input.workspacePath) ?? canonicalWorkspacePath(input.workspacePath);\n if (!workspaceRoot || !isSafeAutoCreateWorkspaceRoot(workspaceRoot)) return null;\n const rootExisting = this.resolveForWorkspacePath(workspaceRoot);\n if (rootExisting) return { project: rootExisting.project, reason: rootExisting.reason, created: false };\n const project = this.create({\n name: inferProjectNameFromWorkspaceRoot(workspaceRoot) ?? undefined,\n workspaceRoot,\n defaultAgentId: Object.hasOwn(input, 'defaultAgentId') ? input.defaultAgentId : input.agentId,\n });\n return { project, reason: 'auto_created', created: true };\n }\n\n update(id: string, input: UpdateProjectInput): Project {\n const patch = { ...input };\n if (input.workspaceRoot !== undefined && input.workspaceRoot !== null && input.workspaceRoot.trim()) {\n const workspaceRoot = canonicalWorkspacePath(input.workspaceRoot);\n if (!workspaceRoot) {\n throw new Error('Invalid workspace root');\n }\n const existing = this.findByWorkspaceRoot(workspaceRoot, { excludeProjectId: id });\n if (existing) throw new ProjectWorkspaceConflictError(existing);\n if (!workspaceDirectoryExists(workspaceRoot)) {\n if (input.createWorkspaceRoot === true) {\n ensureWorkspaceDirectory(workspaceRoot);\n } else {\n throw new ProjectWorkspaceMissingError(workspaceRoot);\n }\n }\n patch.workspaceRoot = workspaceRoot;\n }\n return this.store.update(id, patch);\n }\n\n delete(id: string): void {\n this.store.delete(id);\n }\n\n getWithDetails(id: string): ProjectWithDetails | null {\n return this.store.getWithDetails(id);\n }\n\n attachSession(sessionKey: string, projectId: string): void {\n bindSessionToProject(sessionKey, projectId);\n }\n\n detachSession(sessionKey: string): void {\n unbindSessionFromProject(sessionKey);\n }\n\n listSessionKeys(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectSessionKeys(projectId, limit, offset);\n }\n\n attachGoal(goalId: string, projectId: string): void {\n bindGoalToProject(goalId, projectId);\n }\n\n detachGoal(goalId: string): void {\n unbindGoalFromProject(goalId);\n }\n\n listGoalIds(projectId: string, limit?: number, offset?: number): string[] {\n return listProjectGoalIds(projectId, limit, offset);\n }\n\n suggestProjectsForSession(sessionKey: string): ProjectSuggestion[] {\n const session = getSessionMetadata(sessionKey);\n if (!session) return [];\n const projects = this.list({ status: 'active', limit: 500 }).items;\n const haystack = [session.key, session.name, session.routing?.agentId, session.sourceChannel, ...(session.tags ?? [])]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n const suggestions: ProjectSuggestion[] = [];\n for (const project of projects) {\n let score = 0;\n const reasons: string[] = [];\n if (haystack.includes(project.slug.toLowerCase())) {\n score += 10;\n reasons.push('session metadata matches project slug');\n }\n for (const token of project.name.toLowerCase().split(/\\s+/).filter((t) => t.length >= 3)) {\n if (haystack.includes(token)) {\n score += 3;\n reasons.push(`matches ${token}`);\n break;\n }\n }\n if (score > 0) {\n suggestions.push({ projectId: project.id, projectName: project.name, score, reason: reasons.join(', ') });\n }\n }\n return suggestions.sort((a, b) => b.score - a.score).slice(0, 5);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
|
|
2
|
+
import { escapeFts5Query } from "../storage/sqlite/fts.js";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
//#region src/projects/project-store.ts
|
|
4
5
|
function trimOptional(value) {
|
|
@@ -43,6 +44,25 @@ function projectSortColumn(sortBy) {
|
|
|
43
44
|
default: return "updated_at";
|
|
44
45
|
}
|
|
45
46
|
}
|
|
47
|
+
function projectSortExpression(sortBy) {
|
|
48
|
+
const column = projectSortColumn(sortBy);
|
|
49
|
+
return column === "LOWER(name)" ? "LOWER(p.name)" : `p.${column}`;
|
|
50
|
+
}
|
|
51
|
+
function projectFtsContent(project) {
|
|
52
|
+
return [
|
|
53
|
+
project.name,
|
|
54
|
+
project.slug,
|
|
55
|
+
project.description,
|
|
56
|
+
project.workspaceRoot,
|
|
57
|
+
project.brief,
|
|
58
|
+
project.instructions,
|
|
59
|
+
project.defaultAgentId
|
|
60
|
+
].filter((value) => Boolean(value?.trim())).join("\n");
|
|
61
|
+
}
|
|
62
|
+
function syncProjectFts(db, project) {
|
|
63
|
+
db.prepare(`DELETE FROM projects_fts WHERE project_id = ?`).run(project.id);
|
|
64
|
+
db.prepare(`INSERT INTO projects_fts (content, project_id) VALUES (?, ?)`).run(projectFtsContent(project), project.id);
|
|
65
|
+
}
|
|
46
66
|
var ProjectStore = class {
|
|
47
67
|
create(input) {
|
|
48
68
|
const now = Date.now();
|
|
@@ -67,6 +87,7 @@ var ProjectStore = class {
|
|
|
67
87
|
project_id, name, slug, description, status, default_agent_id, workspace_root,
|
|
68
88
|
brief, instructions, created_at, updated_at, last_active_at
|
|
69
89
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(project.id, project.name, project.slug, project.description ?? null, project.status, project.defaultAgentId ?? null, project.workspaceRoot ?? null, project.brief ?? null, project.instructions ?? null, project.createdAt, project.updatedAt, project.lastActiveAt ?? null);
|
|
90
|
+
syncProjectFts(db, project);
|
|
70
91
|
});
|
|
71
92
|
return project;
|
|
72
93
|
}
|
|
@@ -81,25 +102,26 @@ var ProjectStore = class {
|
|
|
81
102
|
list(query = {}) {
|
|
82
103
|
const conditions = [];
|
|
83
104
|
const params = [];
|
|
105
|
+
const search = query.search?.trim();
|
|
106
|
+
const ftsQuery = search ? escapeFts5Query(search) : "";
|
|
84
107
|
if (query.status) {
|
|
85
108
|
const statuses = Array.isArray(query.status) ? query.status : [query.status];
|
|
86
|
-
conditions.push(`status IN (${statuses.map(() => "?").join(", ")})`);
|
|
109
|
+
conditions.push(`p.status IN (${statuses.map(() => "?").join(", ")})`);
|
|
87
110
|
params.push(...statuses);
|
|
88
111
|
}
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
params.push(like, like, like);
|
|
112
|
+
if (ftsQuery) {
|
|
113
|
+
conditions.push(`p.project_id IN (SELECT project_id FROM projects_fts WHERE projects_fts MATCH ?)`);
|
|
114
|
+
params.push(ftsQuery);
|
|
93
115
|
}
|
|
94
116
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
95
117
|
const limit = clampLimit(query.limit, 50);
|
|
96
118
|
const offset = Math.max(0, Math.floor(query.offset ?? 0));
|
|
97
119
|
const sortOrder = query.sortOrder === "asc" ? "ASC" : "DESC";
|
|
98
|
-
const sortColumn =
|
|
120
|
+
const sortColumn = projectSortExpression(query.sortBy);
|
|
99
121
|
const db = getSqliteDatabase();
|
|
100
|
-
const total = db.prepare(`SELECT COUNT(*) AS total FROM projects ${where}`).get(...params).total;
|
|
122
|
+
const total = db.prepare(`SELECT COUNT(*) AS total FROM projects p ${where}`).get(...params).total;
|
|
101
123
|
return {
|
|
102
|
-
items: db.prepare(`SELECT
|
|
124
|
+
items: db.prepare(`SELECT p.* FROM projects p ${where} ORDER BY ${sortColumn} ${sortOrder} LIMIT ? OFFSET ?`).all(...params, limit, offset).map(projectFromRow),
|
|
103
125
|
total,
|
|
104
126
|
limit,
|
|
105
127
|
offset,
|
|
@@ -124,6 +146,7 @@ var ProjectStore = class {
|
|
|
124
146
|
db.prepare(`UPDATE projects SET
|
|
125
147
|
name = ?, description = ?, status = ?, default_agent_id = ?, workspace_root = ?, brief = ?, instructions = ?, updated_at = ?
|
|
126
148
|
WHERE project_id = ?`).run(next.name, next.description ?? null, next.status, next.defaultAgentId ?? null, next.workspaceRoot ?? null, next.brief ?? null, next.instructions ?? null, next.updatedAt, id);
|
|
149
|
+
syncProjectFts(db, next);
|
|
127
150
|
});
|
|
128
151
|
return this.get(id);
|
|
129
152
|
}
|
|
@@ -134,6 +157,7 @@ var ProjectStore = class {
|
|
|
134
157
|
db.prepare(`UPDATE workflow_runs SET project_id = NULL WHERE project_id = ?`).run(id);
|
|
135
158
|
db.prepare(`UPDATE automations SET project_id = NULL WHERE project_id = ?`).run(id);
|
|
136
159
|
db.prepare(`UPDATE memory_records SET project_id = NULL WHERE project_id = ?`).run(id);
|
|
160
|
+
db.prepare(`DELETE FROM projects_fts WHERE project_id = ?`).run(id);
|
|
137
161
|
db.prepare(`DELETE FROM projects WHERE project_id = ?`).run(id);
|
|
138
162
|
});
|
|
139
163
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-store.js","names":[],"sources":["../../../src/projects/project-store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { getSqliteDatabase, runSqliteWriteTransaction } from '../storage/sqlite/transaction.js';\nimport type {\n CreateProjectInput,\n Project,\n ProjectListQuery,\n ProjectListResult,\n ProjectStatus,\n ProjectWithDetails,\n UpdateProjectInput,\n} from './types.js';\n\nexport type ProjectRow = {\n project_id: string;\n name: string;\n slug: string;\n description: string | null;\n status: string;\n default_agent_id: string | null;\n workspace_root: string | null;\n brief: string | null;\n instructions: string | null;\n created_at: number;\n updated_at: number;\n last_active_at: number | null;\n};\n\nfunction trimOptional(value: string | null | undefined): string | undefined {\n if (value == null) return undefined;\n const trimmed = value.trim();\n return trimmed || undefined;\n}\n\nfunction nullableText(value: string | null | undefined): string | null {\n if (value == null) return null;\n const trimmed = value.trim();\n return trimmed || null;\n}\n\nfunction projectFromRow(row: ProjectRow): Project {\n return {\n id: row.project_id,\n name: row.name,\n slug: row.slug,\n description: row.description ?? undefined,\n status: row.status as ProjectStatus,\n defaultAgentId: row.default_agent_id ?? undefined,\n workspaceRoot: row.workspace_root ?? undefined,\n brief: row.brief ?? undefined,\n instructions: row.instructions ?? undefined,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n lastActiveAt: row.last_active_at ?? undefined,\n };\n}\n\nfunction clampLimit(limit: number | undefined, fallback = 50): number {\n return Math.min(500, Math.max(1, Math.floor(limit ?? fallback)));\n}\n\nfunction normalizeName(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) throw new Error('Project name is required');\n return trimmed;\n}\n\nexport function slugifyProjectName(name: string): string {\n const slug = name\n .trim()\n .toLowerCase()\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^\\p{L}\\p{N}]+/gu, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80);\n return slug || `project-${Date.now()}`;\n}\n\nfunction projectSortColumn(sortBy: ProjectListQuery['sortBy']): string {\n switch (sortBy) {\n case 'createdAt':\n return 'created_at';\n case 'name':\n return 'LOWER(name)';\n case 'updatedAt':\n default:\n return 'updated_at';\n }\n}\n\nexport class ProjectStore {\n create(input: CreateProjectInput): Project {\n const now = Date.now();\n const name = normalizeName(input.name);\n const slug = input.slug?.trim() || slugifyProjectName(name);\n const project: Project = {\n id: randomUUID(),\n name,\n slug,\n description: trimOptional(input.description),\n status: 'active',\n defaultAgentId: trimOptional(input.defaultAgentId),\n workspaceRoot: trimOptional(input.workspaceRoot),\n brief: trimOptional(input.brief),\n instructions: trimOptional(input.instructions),\n createdAt: now,\n updatedAt: now,\n lastActiveAt: now,\n };\n\n runSqliteWriteTransaction((db) => {\n db.prepare(\n `INSERT INTO projects (\n project_id, name, slug, description, status, default_agent_id, workspace_root,\n brief, instructions, created_at, updated_at, last_active_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n project.id,\n project.name,\n project.slug,\n project.description ?? null,\n project.status,\n project.defaultAgentId ?? null,\n project.workspaceRoot ?? null,\n project.brief ?? null,\n project.instructions ?? null,\n project.createdAt,\n project.updatedAt,\n project.lastActiveAt ?? null,\n );\n });\n return project;\n }\n\n get(id: string): Project | null {\n const row = getSqliteDatabase()\n .prepare(`SELECT * FROM projects WHERE project_id = ?`)\n .get(id) as ProjectRow | undefined;\n return row ? projectFromRow(row) : null;\n }\n\n findBySlug(slug: string): Project | null {\n const row = getSqliteDatabase()\n .prepare(`SELECT * FROM projects WHERE slug = ?`)\n .get(slug.trim()) as ProjectRow | undefined;\n return row ? projectFromRow(row) : null;\n }\n\n list(query: ProjectListQuery = {}): ProjectListResult {\n const conditions: string[] = [];\n const params: Array<string | number> = [];\n if (query.status) {\n const statuses = Array.isArray(query.status) ? query.status : [query.status];\n conditions.push(`status IN (${statuses.map(() => '?').join(', ')})`);\n params.push(...statuses);\n }\n if (query.search?.trim()) {\n const like = `%${query.search.trim().toLowerCase()}%`;\n conditions.push(`(LOWER(name) LIKE ? OR LOWER(slug) LIKE ? OR LOWER(COALESCE(description, '')) LIKE ?)`);\n params.push(like, like, like);\n }\n const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';\n const limit = clampLimit(query.limit, 50);\n const offset = Math.max(0, Math.floor(query.offset ?? 0));\n const sortOrder = query.sortOrder === 'asc' ? 'ASC' : 'DESC';\n const sortColumn = projectSortColumn(query.sortBy);\n const db = getSqliteDatabase();\n const total = (db.prepare(`SELECT COUNT(*) AS total FROM projects ${where}`).get(...params) as { total: number }).total;\n const rows = db\n .prepare(`SELECT * FROM projects ${where} ORDER BY ${sortColumn} ${sortOrder} LIMIT ? OFFSET ?`)\n .all(...params, limit, offset) as ProjectRow[];\n return { items: rows.map(projectFromRow), total, limit, offset, hasMore: offset + limit < total };\n }\n\n update(id: string, input: UpdateProjectInput): Project {\n const before = this.get(id);\n if (!before) throw new Error(`Project not found: ${id}`);\n const next = {\n ...before,\n ...(input.name !== undefined ? { name: normalizeName(input.name) } : {}),\n ...(input.description !== undefined ? { description: nullableText(input.description) ?? undefined } : {}),\n ...(input.status !== undefined ? { status: input.status } : {}),\n ...(input.defaultAgentId !== undefined ? { defaultAgentId: nullableText(input.defaultAgentId) ?? undefined } : {}),\n ...(input.workspaceRoot !== undefined ? { workspaceRoot: nullableText(input.workspaceRoot) ?? undefined } : {}),\n ...(input.brief !== undefined ? { brief: nullableText(input.brief) ?? undefined } : {}),\n ...(input.instructions !== undefined ? { instructions: nullableText(input.instructions) ?? undefined } : {}),\n updatedAt: Date.now(),\n } satisfies Project;\n\n runSqliteWriteTransaction((db) => {\n db.prepare(\n `UPDATE projects SET\n name = ?, description = ?, status = ?, default_agent_id = ?, workspace_root = ?, brief = ?, instructions = ?, updated_at = ?\n WHERE project_id = ?`,\n ).run(\n next.name,\n next.description ?? null,\n next.status,\n next.defaultAgentId ?? null,\n next.workspaceRoot ?? null,\n next.brief ?? null,\n next.instructions ?? null,\n next.updatedAt,\n id,\n );\n });\n return this.get(id)!;\n }\n\n delete(id: string): void {\n runSqliteWriteTransaction((db) => {\n db.prepare(`UPDATE sessions SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE goals SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE workflow_runs SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE automations SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE memory_records SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`DELETE FROM projects WHERE project_id = ?`).run(id);\n });\n }\n\n getSessionCount(id: string): number {\n return (getSqliteDatabase()\n .prepare(`SELECT COUNT(*) AS total FROM sessions WHERE project_id = ? AND hidden_from_session_list = 0`)\n .get(id) as { total: number }).total;\n }\n\n getGoalCount(id: string): number {\n return (getSqliteDatabase().prepare(`SELECT COUNT(*) AS total FROM goals WHERE project_id = ?`).get(id) as { total: number }).total;\n }\n\n getActiveGoalCount(id: string): number {\n return (getSqliteDatabase()\n .prepare(`SELECT COUNT(*) AS total FROM goals WHERE project_id = ? AND status IN ('active', 'paused', 'blocked', 'needs_input')`)\n .get(id) as { total: number }).total;\n }\n\n getRecentSessions(id: string, limit = 5): ProjectWithDetails['recentSessions'] {\n const rows = getSqliteDatabase()\n .prepare(\n `SELECT session_key, name, updated_at, agent_id FROM sessions\n WHERE project_id = ? AND hidden_from_session_list = 0 ORDER BY updated_at DESC LIMIT ?`,\n )\n .all(id, clampLimit(limit, 5)) as Array<{ session_key: string; name: string | null; updated_at: number; agent_id: string }>;\n return rows.map((row) => ({\n key: row.session_key,\n name: row.name ?? undefined,\n updatedAt: new Date(row.updated_at).toISOString(),\n agentId: row.agent_id,\n }));\n }\n\n getRecentWorkflowRuns(id: string, limit = 5): ProjectWithDetails['recentWorkflowRuns'] {\n const rows = getSqliteDatabase()\n .prepare(\n `SELECT run_id, definition_id, status, created_at_ms FROM workflow_runs\n WHERE project_id = ? ORDER BY created_at_ms DESC LIMIT ?`,\n )\n .all(id, clampLimit(limit, 5)) as Array<{ run_id: string; definition_id: string; status: string; created_at_ms: number }>;\n return rows.map((row) => ({ runId: row.run_id, definitionId: row.definition_id, status: row.status, createdAt: row.created_at_ms }));\n }\n\n getWithDetails(id: string): ProjectWithDetails | null {\n const project = this.get(id);\n if (!project) return null;\n return {\n ...project,\n sessionCount: this.getSessionCount(id),\n goalCount: this.getGoalCount(id),\n activeGoalCount: this.getActiveGoalCount(id),\n recentSessions: this.getRecentSessions(id),\n recentWorkflowRuns: this.getRecentWorkflowRuns(id),\n };\n }\n\n generateSlug(name: string): string {\n const base = slugifyProjectName(name);\n let candidate = base;\n let counter = 2;\n while (this.findBySlug(candidate)) {\n candidate = `${base}-${counter}`;\n counter += 1;\n }\n return candidate;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"project-store.js","names":[],"sources":["../../../src/projects/project-store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { escapeFts5Query } from '../storage/sqlite/fts.js';\nimport { getSqliteDatabase, runSqliteWriteTransaction } from '../storage/sqlite/transaction.js';\nimport type {\n CreateProjectInput,\n Project,\n ProjectListQuery,\n ProjectListResult,\n ProjectStatus,\n ProjectWithDetails,\n UpdateProjectInput,\n} from './types.js';\n\nexport type ProjectRow = {\n project_id: string;\n name: string;\n slug: string;\n description: string | null;\n status: string;\n default_agent_id: string | null;\n workspace_root: string | null;\n brief: string | null;\n instructions: string | null;\n created_at: number;\n updated_at: number;\n last_active_at: number | null;\n};\n\nfunction trimOptional(value: string | null | undefined): string | undefined {\n if (value == null) return undefined;\n const trimmed = value.trim();\n return trimmed || undefined;\n}\n\nfunction nullableText(value: string | null | undefined): string | null {\n if (value == null) return null;\n const trimmed = value.trim();\n return trimmed || null;\n}\n\nfunction projectFromRow(row: ProjectRow): Project {\n return {\n id: row.project_id,\n name: row.name,\n slug: row.slug,\n description: row.description ?? undefined,\n status: row.status as ProjectStatus,\n defaultAgentId: row.default_agent_id ?? undefined,\n workspaceRoot: row.workspace_root ?? undefined,\n brief: row.brief ?? undefined,\n instructions: row.instructions ?? undefined,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n lastActiveAt: row.last_active_at ?? undefined,\n };\n}\n\nfunction clampLimit(limit: number | undefined, fallback = 50): number {\n return Math.min(500, Math.max(1, Math.floor(limit ?? fallback)));\n}\n\nfunction normalizeName(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) throw new Error('Project name is required');\n return trimmed;\n}\n\nexport function slugifyProjectName(name: string): string {\n const slug = name\n .trim()\n .toLowerCase()\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^\\p{L}\\p{N}]+/gu, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80);\n return slug || `project-${Date.now()}`;\n}\n\nfunction projectSortColumn(sortBy: ProjectListQuery['sortBy']): string {\n switch (sortBy) {\n case 'createdAt':\n return 'created_at';\n case 'name':\n return 'LOWER(name)';\n case 'updatedAt':\n default:\n return 'updated_at';\n }\n}\n\nfunction projectSortExpression(sortBy: ProjectListQuery['sortBy']): string {\n const column = projectSortColumn(sortBy);\n return column === 'LOWER(name)' ? 'LOWER(p.name)' : `p.${column}`;\n}\n\nfunction projectFtsContent(project: Project): string {\n return [\n project.name,\n project.slug,\n project.description,\n project.workspaceRoot,\n project.brief,\n project.instructions,\n project.defaultAgentId,\n ]\n .filter((value): value is string => Boolean(value?.trim()))\n .join('\\n');\n}\n\nfunction syncProjectFts(\n db: ReturnType<typeof getSqliteDatabase>,\n project: Project,\n): void {\n db.prepare(`DELETE FROM projects_fts WHERE project_id = ?`).run(project.id);\n db.prepare(`INSERT INTO projects_fts (content, project_id) VALUES (?, ?)`).run(\n projectFtsContent(project),\n project.id,\n );\n}\n\nexport class ProjectStore {\n create(input: CreateProjectInput): Project {\n const now = Date.now();\n const name = normalizeName(input.name);\n const slug = input.slug?.trim() || slugifyProjectName(name);\n const project: Project = {\n id: randomUUID(),\n name,\n slug,\n description: trimOptional(input.description),\n status: 'active',\n defaultAgentId: trimOptional(input.defaultAgentId),\n workspaceRoot: trimOptional(input.workspaceRoot),\n brief: trimOptional(input.brief),\n instructions: trimOptional(input.instructions),\n createdAt: now,\n updatedAt: now,\n lastActiveAt: now,\n };\n\n runSqliteWriteTransaction((db) => {\n db.prepare(\n `INSERT INTO projects (\n project_id, name, slug, description, status, default_agent_id, workspace_root,\n brief, instructions, created_at, updated_at, last_active_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n project.id,\n project.name,\n project.slug,\n project.description ?? null,\n project.status,\n project.defaultAgentId ?? null,\n project.workspaceRoot ?? null,\n project.brief ?? null,\n project.instructions ?? null,\n project.createdAt,\n project.updatedAt,\n project.lastActiveAt ?? null,\n );\n syncProjectFts(db, project);\n });\n return project;\n }\n\n get(id: string): Project | null {\n const row = getSqliteDatabase()\n .prepare(`SELECT * FROM projects WHERE project_id = ?`)\n .get(id) as ProjectRow | undefined;\n return row ? projectFromRow(row) : null;\n }\n\n findBySlug(slug: string): Project | null {\n const row = getSqliteDatabase()\n .prepare(`SELECT * FROM projects WHERE slug = ?`)\n .get(slug.trim()) as ProjectRow | undefined;\n return row ? projectFromRow(row) : null;\n }\n\n list(query: ProjectListQuery = {}): ProjectListResult {\n const conditions: string[] = [];\n const params: Array<string | number> = [];\n const search = query.search?.trim();\n const ftsQuery = search ? escapeFts5Query(search) : '';\n if (query.status) {\n const statuses = Array.isArray(query.status) ? query.status : [query.status];\n conditions.push(`p.status IN (${statuses.map(() => '?').join(', ')})`);\n params.push(...statuses);\n }\n if (ftsQuery) {\n conditions.push(`p.project_id IN (SELECT project_id FROM projects_fts WHERE projects_fts MATCH ?)`);\n params.push(ftsQuery);\n }\n const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';\n const limit = clampLimit(query.limit, 50);\n const offset = Math.max(0, Math.floor(query.offset ?? 0));\n const sortOrder = query.sortOrder === 'asc' ? 'ASC' : 'DESC';\n const sortColumn = projectSortExpression(query.sortBy);\n const db = getSqliteDatabase();\n const total = (db.prepare(`SELECT COUNT(*) AS total FROM projects p ${where}`).get(...params) as { total: number }).total;\n const rows = db\n .prepare(`SELECT p.* FROM projects p ${where} ORDER BY ${sortColumn} ${sortOrder} LIMIT ? OFFSET ?`)\n .all(...params, limit, offset) as ProjectRow[];\n return { items: rows.map(projectFromRow), total, limit, offset, hasMore: offset + limit < total };\n }\n\n update(id: string, input: UpdateProjectInput): Project {\n const before = this.get(id);\n if (!before) throw new Error(`Project not found: ${id}`);\n const next = {\n ...before,\n ...(input.name !== undefined ? { name: normalizeName(input.name) } : {}),\n ...(input.description !== undefined ? { description: nullableText(input.description) ?? undefined } : {}),\n ...(input.status !== undefined ? { status: input.status } : {}),\n ...(input.defaultAgentId !== undefined ? { defaultAgentId: nullableText(input.defaultAgentId) ?? undefined } : {}),\n ...(input.workspaceRoot !== undefined ? { workspaceRoot: nullableText(input.workspaceRoot) ?? undefined } : {}),\n ...(input.brief !== undefined ? { brief: nullableText(input.brief) ?? undefined } : {}),\n ...(input.instructions !== undefined ? { instructions: nullableText(input.instructions) ?? undefined } : {}),\n updatedAt: Date.now(),\n } satisfies Project;\n\n runSqliteWriteTransaction((db) => {\n db.prepare(\n `UPDATE projects SET\n name = ?, description = ?, status = ?, default_agent_id = ?, workspace_root = ?, brief = ?, instructions = ?, updated_at = ?\n WHERE project_id = ?`,\n ).run(\n next.name,\n next.description ?? null,\n next.status,\n next.defaultAgentId ?? null,\n next.workspaceRoot ?? null,\n next.brief ?? null,\n next.instructions ?? null,\n next.updatedAt,\n id,\n );\n syncProjectFts(db, next);\n });\n return this.get(id)!;\n }\n\n delete(id: string): void {\n runSqliteWriteTransaction((db) => {\n db.prepare(`UPDATE sessions SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE goals SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE workflow_runs SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE automations SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`UPDATE memory_records SET project_id = NULL WHERE project_id = ?`).run(id);\n db.prepare(`DELETE FROM projects_fts WHERE project_id = ?`).run(id);\n db.prepare(`DELETE FROM projects WHERE project_id = ?`).run(id);\n });\n }\n\n getSessionCount(id: string): number {\n return (getSqliteDatabase()\n .prepare(`SELECT COUNT(*) AS total FROM sessions WHERE project_id = ? AND hidden_from_session_list = 0`)\n .get(id) as { total: number }).total;\n }\n\n getGoalCount(id: string): number {\n return (getSqliteDatabase().prepare(`SELECT COUNT(*) AS total FROM goals WHERE project_id = ?`).get(id) as { total: number }).total;\n }\n\n getActiveGoalCount(id: string): number {\n return (getSqliteDatabase()\n .prepare(`SELECT COUNT(*) AS total FROM goals WHERE project_id = ? AND status IN ('active', 'paused', 'blocked', 'needs_input')`)\n .get(id) as { total: number }).total;\n }\n\n getRecentSessions(id: string, limit = 5): ProjectWithDetails['recentSessions'] {\n const rows = getSqliteDatabase()\n .prepare(\n `SELECT session_key, name, updated_at, agent_id FROM sessions\n WHERE project_id = ? AND hidden_from_session_list = 0 ORDER BY updated_at DESC LIMIT ?`,\n )\n .all(id, clampLimit(limit, 5)) as Array<{ session_key: string; name: string | null; updated_at: number; agent_id: string }>;\n return rows.map((row) => ({\n key: row.session_key,\n name: row.name ?? undefined,\n updatedAt: new Date(row.updated_at).toISOString(),\n agentId: row.agent_id,\n }));\n }\n\n getRecentWorkflowRuns(id: string, limit = 5): ProjectWithDetails['recentWorkflowRuns'] {\n const rows = getSqliteDatabase()\n .prepare(\n `SELECT run_id, definition_id, status, created_at_ms FROM workflow_runs\n WHERE project_id = ? ORDER BY created_at_ms DESC LIMIT ?`,\n )\n .all(id, clampLimit(limit, 5)) as Array<{ run_id: string; definition_id: string; status: string; created_at_ms: number }>;\n return rows.map((row) => ({ runId: row.run_id, definitionId: row.definition_id, status: row.status, createdAt: row.created_at_ms }));\n }\n\n getWithDetails(id: string): ProjectWithDetails | null {\n const project = this.get(id);\n if (!project) return null;\n return {\n ...project,\n sessionCount: this.getSessionCount(id),\n goalCount: this.getGoalCount(id),\n activeGoalCount: this.getActiveGoalCount(id),\n recentSessions: this.getRecentSessions(id),\n recentWorkflowRuns: this.getRecentWorkflowRuns(id),\n };\n }\n\n generateSlug(name: string): string {\n const base = slugifyProjectName(name);\n let candidate = base;\n let counter = 2;\n while (this.findBySlug(candidate)) {\n candidate = `${base}-${counter}`;\n counter += 1;\n }\n return candidate;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type ReviewTarget = {
|
|
2
|
+
kind: 'uncommitted';
|
|
3
|
+
instructions?: string;
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'base_branch';
|
|
6
|
+
baseBranch: string;
|
|
7
|
+
instructions?: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'commit';
|
|
10
|
+
sha: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
instructions?: string;
|
|
13
|
+
};
|
|
14
|
+
export interface ReviewDiffBundle {
|
|
15
|
+
cwd: string;
|
|
16
|
+
target: ReviewTarget;
|
|
17
|
+
targetLabel: string;
|
|
18
|
+
status: string;
|
|
19
|
+
stat: string;
|
|
20
|
+
diff: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ReviewContextBranch {
|
|
23
|
+
name: string;
|
|
24
|
+
current?: boolean;
|
|
25
|
+
remote?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface ReviewContextCommit {
|
|
28
|
+
sha: string;
|
|
29
|
+
shortSha: string;
|
|
30
|
+
subject: string;
|
|
31
|
+
date?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ReviewContext {
|
|
34
|
+
cwd: string;
|
|
35
|
+
defaultBaseBranch?: string;
|
|
36
|
+
status: {
|
|
37
|
+
changedFiles: number;
|
|
38
|
+
untrackedFiles: number;
|
|
39
|
+
isClean: boolean;
|
|
40
|
+
};
|
|
41
|
+
branches: ReviewContextBranch[];
|
|
42
|
+
commits: ReviewContextCommit[];
|
|
43
|
+
}
|
|
44
|
+
export declare function git(args: string[], cwd: string): Promise<string>;
|
|
45
|
+
export declare function resolveGitRoot(workspace: string): Promise<string>;
|
|
46
|
+
export declare function parseReviewTargetArgs(args: string): ReviewTarget;
|
|
47
|
+
export declare function buildReviewDiffBundle(cwd: string, target: ReviewTarget): Promise<ReviewDiffBundle>;
|
|
48
|
+
export declare function buildReviewContext(cwd: string): Promise<ReviewContext>;
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
//#region src/review/review-git.ts
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
async function git(args, cwd) {
|
|
6
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
7
|
+
cwd,
|
|
8
|
+
maxBuffer: 8 * 1024 * 1024
|
|
9
|
+
});
|
|
10
|
+
return stdout;
|
|
11
|
+
}
|
|
12
|
+
async function gitDiffNoIndex(args, cwd) {
|
|
13
|
+
try {
|
|
14
|
+
return await git(args, cwd);
|
|
15
|
+
} catch (err) {
|
|
16
|
+
const rec = err && typeof err === "object" ? err : {};
|
|
17
|
+
if (typeof rec.stdout === "string") return rec.stdout;
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function resolveGitRoot(workspace) {
|
|
22
|
+
return (await git(["rev-parse", "--show-toplevel"], workspace)).trim() || workspace;
|
|
23
|
+
}
|
|
24
|
+
async function untrackedFileDiff(cwd) {
|
|
25
|
+
const files = (await git([
|
|
26
|
+
"ls-files",
|
|
27
|
+
"--others",
|
|
28
|
+
"--exclude-standard",
|
|
29
|
+
"-z"
|
|
30
|
+
], cwd).catch(() => "")).split("\0").map((item) => item.trim()).filter(Boolean);
|
|
31
|
+
if (files.length === 0) return "";
|
|
32
|
+
const chunks = [];
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
const diff = await gitDiffNoIndex([
|
|
35
|
+
"diff",
|
|
36
|
+
"--no-index",
|
|
37
|
+
"--",
|
|
38
|
+
"/dev/null",
|
|
39
|
+
file
|
|
40
|
+
], cwd).catch(() => "");
|
|
41
|
+
if (diff.trim()) chunks.push(diff);
|
|
42
|
+
}
|
|
43
|
+
return chunks.join("\n");
|
|
44
|
+
}
|
|
45
|
+
function splitArgs(input) {
|
|
46
|
+
const out = [];
|
|
47
|
+
let cur = "";
|
|
48
|
+
let quote = null;
|
|
49
|
+
let escaped = false;
|
|
50
|
+
for (const ch of input) {
|
|
51
|
+
if (escaped) {
|
|
52
|
+
cur += ch;
|
|
53
|
+
escaped = false;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (ch === "\\") {
|
|
57
|
+
escaped = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (quote) {
|
|
61
|
+
if (ch === quote) quote = null;
|
|
62
|
+
else cur += ch;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (ch === "\"" || ch === "'") {
|
|
66
|
+
quote = ch;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (/\s/.test(ch)) {
|
|
70
|
+
if (cur) {
|
|
71
|
+
out.push(cur);
|
|
72
|
+
cur = "";
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
cur += ch;
|
|
77
|
+
}
|
|
78
|
+
if (escaped) cur += "\\";
|
|
79
|
+
if (cur) out.push(cur);
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function readOptionValue(tokens, index, flag) {
|
|
83
|
+
const token = tokens[index] ?? "";
|
|
84
|
+
const eq = token.indexOf("=");
|
|
85
|
+
if (eq > -1) return {
|
|
86
|
+
value: token.slice(eq + 1),
|
|
87
|
+
next: index + 1
|
|
88
|
+
};
|
|
89
|
+
const value = tokens[index + 1] ?? "";
|
|
90
|
+
if (!value || value.startsWith("--")) throw new Error(`Missing value for ${flag}`);
|
|
91
|
+
return {
|
|
92
|
+
value,
|
|
93
|
+
next: index + 2
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function parseReviewTargetArgs(args) {
|
|
97
|
+
const tokens = splitArgs(args.trim());
|
|
98
|
+
let kind;
|
|
99
|
+
let baseBranch = "";
|
|
100
|
+
let sha = "";
|
|
101
|
+
let instructions = "";
|
|
102
|
+
const free = [];
|
|
103
|
+
for (let i = 0; i < tokens.length;) {
|
|
104
|
+
const token = tokens[i];
|
|
105
|
+
if (token === "--uncommitted") {
|
|
106
|
+
kind = "uncommitted";
|
|
107
|
+
i += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (token === "--base" || token.startsWith("--base=")) {
|
|
111
|
+
const value = readOptionValue(tokens, i, "--base");
|
|
112
|
+
kind = "base_branch";
|
|
113
|
+
baseBranch = value.value.trim();
|
|
114
|
+
i = value.next;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (token === "--commit" || token.startsWith("--commit=")) {
|
|
118
|
+
const value = readOptionValue(tokens, i, "--commit");
|
|
119
|
+
kind = "commit";
|
|
120
|
+
sha = value.value.trim();
|
|
121
|
+
i = value.next;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (token === "--custom" || token === "--instructions") {
|
|
125
|
+
instructions = tokens.slice(i + 1).join(" ").trim();
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
if (token.startsWith("--custom=") || token.startsWith("--instructions=")) {
|
|
129
|
+
instructions = token.slice(token.indexOf("=") + 1).trim();
|
|
130
|
+
i += 1;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
free.push(token);
|
|
134
|
+
i += 1;
|
|
135
|
+
}
|
|
136
|
+
const extraInstructions = instructions || free.join(" ").trim();
|
|
137
|
+
if (!kind) kind = "uncommitted";
|
|
138
|
+
if (kind === "base_branch") {
|
|
139
|
+
if (!baseBranch) throw new Error("Missing base branch for review.");
|
|
140
|
+
return {
|
|
141
|
+
kind,
|
|
142
|
+
baseBranch,
|
|
143
|
+
...extraInstructions ? { instructions: extraInstructions } : {}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (kind === "commit") {
|
|
147
|
+
if (!sha) throw new Error("Missing commit SHA for review.");
|
|
148
|
+
return {
|
|
149
|
+
kind,
|
|
150
|
+
sha,
|
|
151
|
+
...extraInstructions ? { instructions: extraInstructions } : {}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
kind: "uncommitted",
|
|
156
|
+
...extraInstructions ? { instructions: extraInstructions } : {}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async function buildUncommittedReview(cwd, target) {
|
|
160
|
+
return {
|
|
161
|
+
cwd,
|
|
162
|
+
target,
|
|
163
|
+
targetLabel: "uncommitted changes",
|
|
164
|
+
status: await git(["status", "--short"], cwd).catch(() => ""),
|
|
165
|
+
stat: await git([
|
|
166
|
+
"diff",
|
|
167
|
+
"--stat",
|
|
168
|
+
"HEAD",
|
|
169
|
+
"--"
|
|
170
|
+
], cwd).catch(() => ""),
|
|
171
|
+
diff: [await git([
|
|
172
|
+
"diff",
|
|
173
|
+
"--find-renames",
|
|
174
|
+
"HEAD",
|
|
175
|
+
"--"
|
|
176
|
+
], cwd).catch(() => ""), await untrackedFileDiff(cwd)].filter((part) => part.trim()).join("\n")
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
async function buildBaseBranchReview(cwd, target) {
|
|
180
|
+
const mergeBase = (await git([
|
|
181
|
+
"merge-base",
|
|
182
|
+
"HEAD",
|
|
183
|
+
target.baseBranch
|
|
184
|
+
], cwd)).trim();
|
|
185
|
+
const status = await git(["status", "--short"], cwd).catch(() => "");
|
|
186
|
+
const stat = await git([
|
|
187
|
+
"diff",
|
|
188
|
+
"--stat",
|
|
189
|
+
mergeBase,
|
|
190
|
+
"HEAD",
|
|
191
|
+
"--"
|
|
192
|
+
], cwd).catch(() => "");
|
|
193
|
+
const diff = await git([
|
|
194
|
+
"diff",
|
|
195
|
+
"--find-renames",
|
|
196
|
+
mergeBase,
|
|
197
|
+
"HEAD",
|
|
198
|
+
"--"
|
|
199
|
+
], cwd).catch(() => "");
|
|
200
|
+
return {
|
|
201
|
+
cwd,
|
|
202
|
+
target,
|
|
203
|
+
targetLabel: `changes against ${target.baseBranch} (merge-base ${mergeBase.slice(0, 12)})`,
|
|
204
|
+
status,
|
|
205
|
+
stat,
|
|
206
|
+
diff
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function buildCommitReview(cwd, target) {
|
|
210
|
+
const title = (await git([
|
|
211
|
+
"log",
|
|
212
|
+
"-1",
|
|
213
|
+
"--format=%s",
|
|
214
|
+
target.sha
|
|
215
|
+
], cwd)).trim();
|
|
216
|
+
const nextTarget = {
|
|
217
|
+
...target,
|
|
218
|
+
...title ? { title } : {}
|
|
219
|
+
};
|
|
220
|
+
const stat = await git([
|
|
221
|
+
"show",
|
|
222
|
+
"--stat",
|
|
223
|
+
"--format=",
|
|
224
|
+
"--find-renames",
|
|
225
|
+
target.sha,
|
|
226
|
+
"--"
|
|
227
|
+
], cwd);
|
|
228
|
+
const diff = await git([
|
|
229
|
+
"show",
|
|
230
|
+
"--format=fuller",
|
|
231
|
+
"--find-renames",
|
|
232
|
+
"--patch",
|
|
233
|
+
target.sha,
|
|
234
|
+
"--"
|
|
235
|
+
], cwd);
|
|
236
|
+
return {
|
|
237
|
+
cwd,
|
|
238
|
+
target: nextTarget,
|
|
239
|
+
targetLabel: `commit ${target.sha}${title ? ` (${title})` : ""}`,
|
|
240
|
+
status: "",
|
|
241
|
+
stat,
|
|
242
|
+
diff
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async function buildReviewDiffBundle(cwd, target) {
|
|
246
|
+
if (target.kind === "base_branch") return buildBaseBranchReview(cwd, target);
|
|
247
|
+
if (target.kind === "commit") return buildCommitReview(cwd, target);
|
|
248
|
+
return buildUncommittedReview(cwd, target);
|
|
249
|
+
}
|
|
250
|
+
function parseStatusCounts(status) {
|
|
251
|
+
let changedFiles = 0;
|
|
252
|
+
let untrackedFiles = 0;
|
|
253
|
+
for (const line of status.split("\n")) {
|
|
254
|
+
if (!line.trim()) continue;
|
|
255
|
+
if (line.startsWith("??")) untrackedFiles += 1;
|
|
256
|
+
else changedFiles += 1;
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
changedFiles,
|
|
260
|
+
untrackedFiles,
|
|
261
|
+
isClean: changedFiles === 0 && untrackedFiles === 0
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function normalizeBranchName(raw) {
|
|
265
|
+
return raw.replace(/^remotes\//, "").trim();
|
|
266
|
+
}
|
|
267
|
+
async function listBranches(cwd) {
|
|
268
|
+
const raw = await git([
|
|
269
|
+
"branch",
|
|
270
|
+
"--all",
|
|
271
|
+
"--format=%(refname:short)|%(HEAD)"
|
|
272
|
+
], cwd).catch(() => "");
|
|
273
|
+
const seen = /* @__PURE__ */ new Set();
|
|
274
|
+
const out = [];
|
|
275
|
+
for (const line of raw.split("\n")) {
|
|
276
|
+
const [nameRaw, headRaw] = line.split("|");
|
|
277
|
+
const name = normalizeBranchName(nameRaw ?? "");
|
|
278
|
+
if (!name || name.includes("HEAD ->") || seen.has(name)) continue;
|
|
279
|
+
seen.add(name);
|
|
280
|
+
const item = { name };
|
|
281
|
+
if (headRaw?.trim() === "*") item.current = true;
|
|
282
|
+
if (name.includes("/")) item.remote = true;
|
|
283
|
+
out.push(item);
|
|
284
|
+
}
|
|
285
|
+
return out;
|
|
286
|
+
}
|
|
287
|
+
async function listCommits(cwd) {
|
|
288
|
+
return (await git([
|
|
289
|
+
"log",
|
|
290
|
+
"--date=iso-strict",
|
|
291
|
+
"--format=%H%x00%h%x00%s%x00%aI",
|
|
292
|
+
"-n",
|
|
293
|
+
"100"
|
|
294
|
+
], cwd).catch(() => "")).split("\n").map((line) => {
|
|
295
|
+
const [sha, shortSha, subject, date] = line.split("\0");
|
|
296
|
+
if (!sha || !shortSha || !subject) return null;
|
|
297
|
+
const item = {
|
|
298
|
+
sha,
|
|
299
|
+
shortSha,
|
|
300
|
+
subject
|
|
301
|
+
};
|
|
302
|
+
if (date) item.date = date;
|
|
303
|
+
return item;
|
|
304
|
+
}).filter((item) => item != null);
|
|
305
|
+
}
|
|
306
|
+
function defaultBaseBranch(branches) {
|
|
307
|
+
for (const name of [
|
|
308
|
+
"origin/main",
|
|
309
|
+
"main",
|
|
310
|
+
"origin/master",
|
|
311
|
+
"master",
|
|
312
|
+
"develop"
|
|
313
|
+
]) if (branches.some((branch) => branch.name === name)) return name;
|
|
314
|
+
return branches.find((branch) => branch.remote && !branch.current)?.name ?? branches.find((branch) => !branch.current)?.name;
|
|
315
|
+
}
|
|
316
|
+
async function buildReviewContext(cwd) {
|
|
317
|
+
const statusRaw = await git(["status", "--short"], cwd).catch(() => "");
|
|
318
|
+
const branches = await listBranches(cwd);
|
|
319
|
+
const commits = await listCommits(cwd);
|
|
320
|
+
const context = {
|
|
321
|
+
cwd,
|
|
322
|
+
status: parseStatusCounts(statusRaw),
|
|
323
|
+
branches,
|
|
324
|
+
commits
|
|
325
|
+
};
|
|
326
|
+
const base = defaultBaseBranch(branches);
|
|
327
|
+
if (base) context.defaultBaseBranch = base;
|
|
328
|
+
return context;
|
|
329
|
+
}
|
|
330
|
+
//#endregion
|
|
331
|
+
export { buildReviewContext, buildReviewDiffBundle, git, parseReviewTargetArgs, resolveGitRoot };
|
|
332
|
+
|
|
333
|
+
//# sourceMappingURL=review-git.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-git.js","names":[],"sources":["../../../src/review/review-git.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\nexport type ReviewTarget =\n | { kind: 'uncommitted'; instructions?: string }\n | { kind: 'base_branch'; baseBranch: string; instructions?: string }\n | { kind: 'commit'; sha: string; title?: string; instructions?: string };\n\nexport interface ReviewDiffBundle {\n cwd: string;\n target: ReviewTarget;\n targetLabel: string;\n status: string;\n stat: string;\n diff: string;\n}\n\nexport interface ReviewContextBranch {\n name: string;\n current?: boolean;\n remote?: boolean;\n}\n\nexport interface ReviewContextCommit {\n sha: string;\n shortSha: string;\n subject: string;\n date?: string;\n}\n\nexport interface ReviewContext {\n cwd: string;\n defaultBaseBranch?: string;\n status: {\n changedFiles: number;\n untrackedFiles: number;\n isClean: boolean;\n };\n branches: ReviewContextBranch[];\n commits: ReviewContextCommit[];\n}\n\nexport async function git(args: string[], cwd: string): Promise<string> {\n const { stdout } = await execFileAsync('git', args, {\n cwd,\n maxBuffer: 8 * 1024 * 1024,\n });\n return stdout;\n}\n\nasync function gitDiffNoIndex(args: string[], cwd: string): Promise<string> {\n try {\n return await git(args, cwd);\n } catch (err) {\n const rec = err && typeof err === 'object' ? err as Record<string, unknown> : {};\n if (typeof rec.stdout === 'string') return rec.stdout;\n throw err;\n }\n}\n\nexport async function resolveGitRoot(workspace: string): Promise<string> {\n const root = (await git(['rev-parse', '--show-toplevel'], workspace)).trim();\n return root || workspace;\n}\n\nasync function untrackedFileDiff(cwd: string): Promise<string> {\n const raw = await git(['ls-files', '--others', '--exclude-standard', '-z'], cwd).catch(() => '');\n const files = raw.split('\\0').map((item) => item.trim()).filter(Boolean);\n if (files.length === 0) return '';\n\n const chunks: string[] = [];\n for (const file of files) {\n const diff = await gitDiffNoIndex(['diff', '--no-index', '--', '/dev/null', file], cwd).catch(() => '');\n if (diff.trim()) chunks.push(diff);\n }\n return chunks.join('\\n');\n}\n\nfunction splitArgs(input: string): string[] {\n const out: string[] = [];\n let cur = '';\n let quote: '\"' | \"'\" | null = null;\n let escaped = false;\n for (const ch of input) {\n if (escaped) {\n cur += ch;\n escaped = false;\n continue;\n }\n if (ch === '\\\\') {\n escaped = true;\n continue;\n }\n if (quote) {\n if (ch === quote) quote = null;\n else cur += ch;\n continue;\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch;\n continue;\n }\n if (/\\s/.test(ch)) {\n if (cur) {\n out.push(cur);\n cur = '';\n }\n continue;\n }\n cur += ch;\n }\n if (escaped) cur += '\\\\';\n if (cur) out.push(cur);\n return out;\n}\n\nfunction readOptionValue(tokens: string[], index: number, flag: string): { value: string; next: number } {\n const token = tokens[index] ?? '';\n const eq = token.indexOf('=');\n if (eq > -1) {\n return { value: token.slice(eq + 1), next: index + 1 };\n }\n const value = tokens[index + 1] ?? '';\n if (!value || value.startsWith('--')) {\n throw new Error(`Missing value for ${flag}`);\n }\n return { value, next: index + 2 };\n}\n\nexport function parseReviewTargetArgs(args: string): ReviewTarget {\n const tokens = splitArgs(args.trim());\n let kind: ReviewTarget['kind'] | undefined;\n let baseBranch = '';\n let sha = '';\n let instructions = '';\n const free: string[] = [];\n\n for (let i = 0; i < tokens.length;) {\n const token = tokens[i]!;\n if (token === '--uncommitted') {\n kind = 'uncommitted';\n i += 1;\n continue;\n }\n if (token === '--base' || token.startsWith('--base=')) {\n const value = readOptionValue(tokens, i, '--base');\n kind = 'base_branch';\n baseBranch = value.value.trim();\n i = value.next;\n continue;\n }\n if (token === '--commit' || token.startsWith('--commit=')) {\n const value = readOptionValue(tokens, i, '--commit');\n kind = 'commit';\n sha = value.value.trim();\n i = value.next;\n continue;\n }\n if (token === '--custom' || token === '--instructions') {\n instructions = tokens.slice(i + 1).join(' ').trim();\n break;\n }\n if (token.startsWith('--custom=') || token.startsWith('--instructions=')) {\n instructions = token.slice(token.indexOf('=') + 1).trim();\n i += 1;\n continue;\n }\n free.push(token);\n i += 1;\n }\n\n const extraInstructions = instructions || free.join(' ').trim();\n if (!kind) kind = 'uncommitted';\n if (kind === 'base_branch') {\n if (!baseBranch) throw new Error('Missing base branch for review.');\n return { kind, baseBranch, ...(extraInstructions ? { instructions: extraInstructions } : {}) };\n }\n if (kind === 'commit') {\n if (!sha) throw new Error('Missing commit SHA for review.');\n return { kind, sha, ...(extraInstructions ? { instructions: extraInstructions } : {}) };\n }\n return { kind: 'uncommitted', ...(extraInstructions ? { instructions: extraInstructions } : {}) };\n}\n\nasync function buildUncommittedReview(cwd: string, target: ReviewTarget & { kind: 'uncommitted' }): Promise<ReviewDiffBundle> {\n const status = await git(['status', '--short'], cwd).catch(() => '');\n const stat = await git(['diff', '--stat', 'HEAD', '--'], cwd).catch(() => '');\n const trackedDiff = await git(['diff', '--find-renames', 'HEAD', '--'], cwd).catch(() => '');\n const untrackedDiff = await untrackedFileDiff(cwd);\n return {\n cwd,\n target,\n targetLabel: 'uncommitted changes',\n status,\n stat,\n diff: [trackedDiff, untrackedDiff].filter((part) => part.trim()).join('\\n'),\n };\n}\n\nasync function buildBaseBranchReview(cwd: string, target: ReviewTarget & { kind: 'base_branch' }): Promise<ReviewDiffBundle> {\n const mergeBase = (await git(['merge-base', 'HEAD', target.baseBranch], cwd)).trim();\n const status = await git(['status', '--short'], cwd).catch(() => '');\n const stat = await git(['diff', '--stat', mergeBase, 'HEAD', '--'], cwd).catch(() => '');\n const diff = await git(['diff', '--find-renames', mergeBase, 'HEAD', '--'], cwd).catch(() => '');\n return {\n cwd,\n target,\n targetLabel: `changes against ${target.baseBranch} (merge-base ${mergeBase.slice(0, 12)})`,\n status,\n stat,\n diff,\n };\n}\n\nasync function buildCommitReview(cwd: string, target: ReviewTarget & { kind: 'commit' }): Promise<ReviewDiffBundle> {\n const title = (await git(['log', '-1', '--format=%s', target.sha], cwd)).trim();\n const nextTarget: ReviewTarget = {\n ...target,\n ...(title ? { title } : {}),\n };\n const stat = await git(['show', '--stat', '--format=', '--find-renames', target.sha, '--'], cwd);\n const diff = await git(['show', '--format=fuller', '--find-renames', '--patch', target.sha, '--'], cwd);\n return {\n cwd,\n target: nextTarget,\n targetLabel: `commit ${target.sha}${title ? ` (${title})` : ''}`,\n status: '',\n stat,\n diff,\n };\n}\n\nexport async function buildReviewDiffBundle(cwd: string, target: ReviewTarget): Promise<ReviewDiffBundle> {\n if (target.kind === 'base_branch') return buildBaseBranchReview(cwd, target);\n if (target.kind === 'commit') return buildCommitReview(cwd, target);\n return buildUncommittedReview(cwd, target);\n}\n\nfunction parseStatusCounts(status: string): ReviewContext['status'] {\n let changedFiles = 0;\n let untrackedFiles = 0;\n for (const line of status.split('\\n')) {\n if (!line.trim()) continue;\n if (line.startsWith('??')) untrackedFiles += 1;\n else changedFiles += 1;\n }\n return {\n changedFiles,\n untrackedFiles,\n isClean: changedFiles === 0 && untrackedFiles === 0,\n };\n}\n\nfunction normalizeBranchName(raw: string): string {\n return raw.replace(/^remotes\\//, '').trim();\n}\n\nasync function listBranches(cwd: string): Promise<ReviewContextBranch[]> {\n const raw = await git(['branch', '--all', '--format=%(refname:short)|%(HEAD)'], cwd).catch(() => '');\n const seen = new Set<string>();\n const out: ReviewContextBranch[] = [];\n for (const line of raw.split('\\n')) {\n const [nameRaw, headRaw] = line.split('|');\n const name = normalizeBranchName(nameRaw ?? '');\n if (!name || name.includes('HEAD ->') || seen.has(name)) continue;\n seen.add(name);\n const item: ReviewContextBranch = { name };\n if (headRaw?.trim() === '*') item.current = true;\n if (name.includes('/')) item.remote = true;\n out.push(item);\n }\n return out;\n}\n\nasync function listCommits(cwd: string): Promise<ReviewContextCommit[]> {\n const raw = await git(['log', '--date=iso-strict', '--format=%H%x00%h%x00%s%x00%aI', '-n', '100'], cwd).catch(() => '');\n return raw\n .split('\\n')\n .map((line): ReviewContextCommit | null => {\n const [sha, shortSha, subject, date] = line.split('\\0');\n if (!sha || !shortSha || !subject) return null;\n const item: ReviewContextCommit = { sha, shortSha, subject };\n if (date) item.date = date;\n return item;\n })\n .filter((item): item is ReviewContextCommit => item != null);\n}\n\nfunction defaultBaseBranch(branches: ReviewContextBranch[]): string | undefined {\n const preferred = ['origin/main', 'main', 'origin/master', 'master', 'develop'];\n for (const name of preferred) {\n if (branches.some((branch) => branch.name === name)) return name;\n }\n return branches.find((branch) => branch.remote && !branch.current)?.name\n ?? branches.find((branch) => !branch.current)?.name;\n}\n\nexport async function buildReviewContext(cwd: string): Promise<ReviewContext> {\n const statusRaw = await git(['status', '--short'], cwd).catch(() => '');\n const branches = await listBranches(cwd);\n const commits = await listCommits(cwd);\n const context: ReviewContext = {\n cwd,\n status: parseStatusCounts(statusRaw),\n branches,\n commits,\n };\n const base = defaultBaseBranch(branches);\n if (base) context.defaultBaseBranch = base;\n return context;\n}\n"],"mappings":"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"}
|