light-plan 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3181 -2
- package/assets/README.md +111 -0
- package/assets/agents/lpm-developer.md +468 -0
- package/assets/agents/lpm-planner.md +532 -0
- package/assets/harnesses/claude.yml +43 -0
- package/assets/harnesses/copilot.yml +54 -0
- package/assets/harnesses/reasonix.yml +49 -0
- package/assets/hcm/light-plan.yml +45 -0
- package/assets/skills/lpm-board-health.md +243 -0
- package/assets/skills/lpm-board-setup.md +354 -0
- package/assets/skills/lpm-delivery.md +344 -0
- package/assets/skills/lpm-planning.md +411 -0
- package/assets/skills/lpm-templates.md +144 -0
- package/assets/skills/lpm.md +267 -0
- package/dist/cli/agent-view.d.ts +27 -0
- package/dist/cli/agent-view.js +247 -0
- package/dist/cli/agent-view.js.map +1 -0
- package/dist/cli/commands/agent/assets.d.ts +64 -0
- package/dist/cli/commands/agent/assets.js +106 -0
- package/dist/cli/commands/agent/assets.js.map +1 -0
- package/dist/cli/commands/agent/glob.d.ts +25 -0
- package/dist/cli/commands/agent/glob.js +82 -0
- package/dist/cli/commands/agent/glob.js.map +1 -0
- package/dist/cli/commands/agent/index.d.ts +11 -0
- package/dist/cli/commands/agent/index.js +284 -0
- package/dist/cli/commands/agent/index.js.map +1 -0
- package/dist/cli/commands/agent/install.d.ts +59 -0
- package/dist/cli/commands/agent/install.js +131 -0
- package/dist/cli/commands/agent/install.js.map +1 -0
- package/dist/cli/commands/agent/mapping.d.ts +175 -0
- package/dist/cli/commands/agent/mapping.js +256 -0
- package/dist/cli/commands/agent/mapping.js.map +1 -0
- package/dist/cli/commands/check.d.ts +2 -0
- package/dist/cli/commands/check.js +81 -0
- package/dist/cli/commands/check.js.map +1 -0
- package/dist/cli/commands/comment.d.ts +2 -0
- package/dist/cli/commands/comment.js +104 -0
- package/dist/cli/commands/comment.js.map +1 -0
- package/dist/cli/commands/convert.d.ts +2 -0
- package/dist/cli/commands/convert.js +115 -0
- package/dist/cli/commands/convert.js.map +1 -0
- package/dist/cli/commands/copy.d.ts +2 -0
- package/dist/cli/commands/copy.js +69 -0
- package/dist/cli/commands/copy.js.map +1 -0
- package/dist/cli/commands/export.d.ts +11 -0
- package/dist/cli/commands/export.js +226 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/flag.d.ts +2 -0
- package/dist/cli/commands/flag.js +172 -0
- package/dist/cli/commands/flag.js.map +1 -0
- package/dist/cli/commands/git.d.ts +11 -0
- package/dist/cli/commands/git.js +357 -0
- package/dist/cli/commands/git.js.map +1 -0
- package/dist/cli/commands/hcm/bundle.d.ts +60 -0
- package/dist/cli/commands/hcm/bundle.js +166 -0
- package/dist/cli/commands/hcm/bundle.js.map +1 -0
- package/dist/cli/commands/hcm/index.d.ts +11 -0
- package/dist/cli/commands/hcm/index.js +162 -0
- package/dist/cli/commands/hcm/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +81 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/insert.d.ts +2 -0
- package/dist/cli/commands/insert.js +70 -0
- package/dist/cli/commands/insert.js.map +1 -0
- package/dist/cli/commands/instructions.d.ts +7 -0
- package/dist/cli/commands/instructions.js +256 -0
- package/dist/cli/commands/instructions.js.map +1 -0
- package/dist/cli/commands/link.d.ts +2 -0
- package/dist/cli/commands/link.js +121 -0
- package/dist/cli/commands/link.js.map +1 -0
- package/dist/cli/commands/mcp/config.d.ts +71 -0
- package/dist/cli/commands/mcp/config.js +119 -0
- package/dist/cli/commands/mcp/config.js.map +1 -0
- package/dist/cli/commands/mcp/index.d.ts +9 -0
- package/dist/cli/commands/mcp/index.js +92 -0
- package/dist/cli/commands/mcp/index.js.map +1 -0
- package/dist/cli/commands/mcp/serve.d.ts +1 -0
- package/dist/cli/commands/mcp/serve.js +76 -0
- package/dist/cli/commands/mcp/serve.js.map +1 -0
- package/dist/cli/commands/mcp/setup.d.ts +1 -0
- package/dist/cli/commands/mcp/setup.js +83 -0
- package/dist/cli/commands/mcp/setup.js.map +1 -0
- package/dist/cli/commands/me.d.ts +2 -0
- package/dist/cli/commands/me.js +85 -0
- package/dist/cli/commands/me.js.map +1 -0
- package/dist/cli/commands/move.d.ts +2 -0
- package/dist/cli/commands/move.js +81 -0
- package/dist/cli/commands/move.js.map +1 -0
- package/dist/cli/commands/new.d.ts +2 -0
- package/dist/cli/commands/new.js +222 -0
- package/dist/cli/commands/new.js.map +1 -0
- package/dist/cli/commands/open.d.ts +2 -0
- package/dist/cli/commands/open.js +43 -0
- package/dist/cli/commands/open.js.map +1 -0
- package/dist/cli/commands/period.d.ts +10 -0
- package/dist/cli/commands/period.js +165 -0
- package/dist/cli/commands/period.js.map +1 -0
- package/dist/cli/commands/profile.d.ts +2 -0
- package/dist/cli/commands/profile.js +133 -0
- package/dist/cli/commands/profile.js.map +1 -0
- package/dist/cli/commands/queue.d.ts +2 -0
- package/dist/cli/commands/queue.js +582 -0
- package/dist/cli/commands/queue.js.map +1 -0
- package/dist/cli/commands/remote.d.ts +10 -0
- package/dist/cli/commands/remote.js +3142 -0
- package/dist/cli/commands/remote.js.map +1 -0
- package/dist/cli/commands/rm.d.ts +2 -0
- package/dist/cli/commands/rm.js +63 -0
- package/dist/cli/commands/rm.js.map +1 -0
- package/dist/cli/commands/set.d.ts +2 -0
- package/dist/cli/commands/set.js +139 -0
- package/dist/cli/commands/set.js.map +1 -0
- package/dist/cli/commands/split.d.ts +2 -0
- package/dist/cli/commands/split.js +90 -0
- package/dist/cli/commands/split.js.map +1 -0
- package/dist/cli/commands/task.d.ts +2 -0
- package/dist/cli/commands/task.js +298 -0
- package/dist/cli/commands/task.js.map +1 -0
- package/dist/cli/commands/team.d.ts +2 -0
- package/dist/cli/commands/team.js +114 -0
- package/dist/cli/commands/team.js.map +1 -0
- package/dist/cli/commands/template.d.ts +2 -0
- package/dist/cli/commands/template.js +338 -0
- package/dist/cli/commands/template.js.map +1 -0
- package/dist/cli/commands/ui.d.ts +2 -0
- package/dist/cli/commands/ui.js +98 -0
- package/dist/cli/commands/ui.js.map +1 -0
- package/dist/cli/commands/upstream.d.ts +2 -0
- package/dist/cli/commands/upstream.js +218 -0
- package/dist/cli/commands/upstream.js.map +1 -0
- package/dist/cli/context.d.ts +24 -0
- package/dist/cli/context.js +52 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +208 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/live.d.ts +71 -0
- package/dist/cli/live.js +182 -0
- package/dist/cli/live.js.map +1 -0
- package/dist/cli/plan.d.ts +28 -0
- package/dist/cli/plan.js +76 -0
- package/dist/cli/plan.js.map +1 -0
- package/dist/cli/prompt.d.ts +60 -0
- package/dist/cli/prompt.js +185 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/ui.d.ts +65 -0
- package/dist/cli/ui.js +79 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/core/board/dependency-rollup.d.ts +54 -0
- package/dist/core/board/dependency-rollup.js +72 -0
- package/dist/core/board/dependency-rollup.js.map +1 -0
- package/dist/core/board/flag-rollup.d.ts +33 -0
- package/dist/core/board/flag-rollup.js +56 -0
- package/dist/core/board/flag-rollup.js.map +1 -0
- package/dist/core/board/index.d.ts +21 -0
- package/dist/core/board/index.js +22 -0
- package/dist/core/board/index.js.map +1 -0
- package/dist/core/board/load/cache.d.ts +52 -0
- package/dist/core/board/load/cache.js +105 -0
- package/dist/core/board/load/cache.js.map +1 -0
- package/dist/core/board/load/fields.d.ts +33 -0
- package/dist/core/board/load/fields.js +154 -0
- package/dist/core/board/load/fields.js.map +1 -0
- package/dist/core/board/load/scan.d.ts +39 -0
- package/dist/core/board/load/scan.js +88 -0
- package/dist/core/board/load/scan.js.map +1 -0
- package/dist/core/board/load/tree.d.ts +10 -0
- package/dist/core/board/load/tree.js +114 -0
- package/dist/core/board/load/tree.js.map +1 -0
- package/dist/core/board/load.d.ts +69 -0
- package/dist/core/board/load.js +204 -0
- package/dist/core/board/load.js.map +1 -0
- package/dist/core/board/query.d.ts +126 -0
- package/dist/core/board/query.js +291 -0
- package/dist/core/board/query.js.map +1 -0
- package/dist/core/board/registry.d.ts +51 -0
- package/dist/core/board/registry.js +98 -0
- package/dist/core/board/registry.js.map +1 -0
- package/dist/core/board/remote-scopes.d.ts +71 -0
- package/dist/core/board/remote-scopes.js +103 -0
- package/dist/core/board/remote-scopes.js.map +1 -0
- package/dist/core/board/rollup.d.ts +32 -0
- package/dist/core/board/rollup.js +67 -0
- package/dist/core/board/rollup.js.map +1 -0
- package/dist/core/board/scope.d.ts +81 -0
- package/dist/core/board/scope.js +134 -0
- package/dist/core/board/scope.js.map +1 -0
- package/dist/core/board/simulate.d.ts +138 -0
- package/dist/core/board/simulate.js +141 -0
- package/dist/core/board/simulate.js.map +1 -0
- package/dist/core/board/tasks/index.d.ts +25 -0
- package/dist/core/board/tasks/index.js +26 -0
- package/dist/core/board/tasks/index.js.map +1 -0
- package/dist/core/board/tasks/ranking.d.ts +155 -0
- package/dist/core/board/tasks/ranking.js +374 -0
- package/dist/core/board/tasks/ranking.js.map +1 -0
- package/dist/core/board/tasks/roster.d.ts +42 -0
- package/dist/core/board/tasks/roster.js +82 -0
- package/dist/core/board/tasks/roster.js.map +1 -0
- package/dist/core/board/tasks.d.ts +1 -0
- package/dist/core/board/tasks.js +2 -0
- package/dist/core/board/tasks.js.map +1 -0
- package/dist/core/config/index.d.ts +9 -0
- package/dist/core/config/index.js +10 -0
- package/dist/core/config/index.js.map +1 -0
- package/dist/core/config/lookup.d.ts +114 -0
- package/dist/core/config/lookup.js +221 -0
- package/dist/core/config/lookup.js.map +1 -0
- package/dist/core/config/remote-blocks.d.ts +17 -0
- package/dist/core/config/remote-blocks.js +47 -0
- package/dist/core/config/remote-blocks.js.map +1 -0
- package/dist/core/config/schema.d.ts +10 -0
- package/dist/core/config/schema.js +451 -0
- package/dist/core/config/schema.js.map +1 -0
- package/dist/core/errors.d.ts +23 -0
- package/dist/core/errors.js +31 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/gitsync/hosts.d.ts +40 -0
- package/dist/core/gitsync/hosts.js +115 -0
- package/dist/core/gitsync/hosts.js.map +1 -0
- package/dist/core/gitsync/index.d.ts +25 -0
- package/dist/core/gitsync/index.js +25 -0
- package/dist/core/gitsync/index.js.map +1 -0
- package/dist/core/gitsync/integrate.d.ts +72 -0
- package/dist/core/gitsync/integrate.js +115 -0
- package/dist/core/gitsync/integrate.js.map +1 -0
- package/dist/core/gitsync/repo.d.ts +61 -0
- package/dist/core/gitsync/repo.js +213 -0
- package/dist/core/gitsync/repo.js.map +1 -0
- package/dist/core/gitsync/run.d.ts +58 -0
- package/dist/core/gitsync/run.js +102 -0
- package/dist/core/gitsync/run.js.map +1 -0
- package/dist/core/gitsync/status.d.ts +59 -0
- package/dist/core/gitsync/status.js +81 -0
- package/dist/core/gitsync/status.js.map +1 -0
- package/dist/core/gitsync/sync.d.ts +111 -0
- package/dist/core/gitsync/sync.js +275 -0
- package/dist/core/gitsync/sync.js.map +1 -0
- package/dist/core/index.d.ts +31 -0
- package/dist/core/index.js +32 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/instructions/analyze.d.ts +70 -0
- package/dist/core/instructions/analyze.js +275 -0
- package/dist/core/instructions/analyze.js.map +1 -0
- package/dist/core/instructions/builtin.d.ts +21 -0
- package/dist/core/instructions/builtin.js +133 -0
- package/dist/core/instructions/builtin.js.map +1 -0
- package/dist/core/instructions/context.d.ts +155 -0
- package/dist/core/instructions/context.js +209 -0
- package/dist/core/instructions/context.js.map +1 -0
- package/dist/core/instructions/index.d.ts +29 -0
- package/dist/core/instructions/index.js +30 -0
- package/dist/core/instructions/index.js.map +1 -0
- package/dist/core/instructions/instructions.d.ts +90 -0
- package/dist/core/instructions/instructions.js +179 -0
- package/dist/core/instructions/instructions.js.map +1 -0
- package/dist/core/instructions/template.d.ts +108 -0
- package/dist/core/instructions/template.js +252 -0
- package/dist/core/instructions/template.js.map +1 -0
- package/dist/core/model/attributes.d.ts +16 -0
- package/dist/core/model/attributes.js +119 -0
- package/dist/core/model/attributes.js.map +1 -0
- package/dist/core/model/index.d.ts +10 -0
- package/dist/core/model/index.js +11 -0
- package/dist/core/model/index.js.map +1 -0
- package/dist/core/model/links.d.ts +34 -0
- package/dist/core/model/links.js +114 -0
- package/dist/core/model/links.js.map +1 -0
- package/dist/core/model/profile.d.ts +31 -0
- package/dist/core/model/profile.js +8 -0
- package/dist/core/model/profile.js.map +1 -0
- package/dist/core/model/types.d.ts +441 -0
- package/dist/core/model/types.js +167 -0
- package/dist/core/model/types.js.map +1 -0
- package/dist/core/model/zod.d.ts +6 -0
- package/dist/core/model/zod.js +11 -0
- package/dist/core/model/zod.js.map +1 -0
- package/dist/core/operations/board-index.d.ts +45 -0
- package/dist/core/operations/board-index.js +147 -0
- package/dist/core/operations/board-index.js.map +1 -0
- package/dist/core/operations/claim.d.ts +69 -0
- package/dist/core/operations/claim.js +115 -0
- package/dist/core/operations/claim.js.map +1 -0
- package/dist/core/operations/comment.d.ts +30 -0
- package/dist/core/operations/comment.js +46 -0
- package/dist/core/operations/comment.js.map +1 -0
- package/dist/core/operations/create.d.ts +82 -0
- package/dist/core/operations/create.js +306 -0
- package/dist/core/operations/create.js.map +1 -0
- package/dist/core/operations/flag.d.ts +58 -0
- package/dist/core/operations/flag.js +95 -0
- package/dist/core/operations/flag.js.map +1 -0
- package/dist/core/operations/git-sync.d.ts +148 -0
- package/dist/core/operations/git-sync.js +341 -0
- package/dist/core/operations/git-sync.js.map +1 -0
- package/dist/core/operations/index.d.ts +44 -0
- package/dist/core/operations/index.js +45 -0
- package/dist/core/operations/index.js.map +1 -0
- package/dist/core/operations/init.d.ts +25 -0
- package/dist/core/operations/init.js +122 -0
- package/dist/core/operations/init.js.map +1 -0
- package/dist/core/operations/link.d.ts +40 -0
- package/dist/core/operations/link.js +131 -0
- package/dist/core/operations/link.js.map +1 -0
- package/dist/core/operations/move.d.ts +43 -0
- package/dist/core/operations/move.js +103 -0
- package/dist/core/operations/move.js.map +1 -0
- package/dist/core/operations/profile.d.ts +23 -0
- package/dist/core/operations/profile.js +26 -0
- package/dist/core/operations/profile.js.map +1 -0
- package/dist/core/operations/remotes-off.d.ts +12 -0
- package/dist/core/operations/remotes-off.js +67 -0
- package/dist/core/operations/remotes-off.js.map +1 -0
- package/dist/core/operations/remove.d.ts +18 -0
- package/dist/core/operations/remove.js +114 -0
- package/dist/core/operations/remove.js.map +1 -0
- package/dist/core/operations/retype.d.ts +25 -0
- package/dist/core/operations/retype.js +59 -0
- package/dist/core/operations/retype.js.map +1 -0
- package/dist/core/operations/rollup.d.ts +74 -0
- package/dist/core/operations/rollup.js +121 -0
- package/dist/core/operations/rollup.js.map +1 -0
- package/dist/core/operations/shared.d.ts +133 -0
- package/dist/core/operations/shared.js +383 -0
- package/dist/core/operations/shared.js.map +1 -0
- package/dist/core/operations/update.d.ts +43 -0
- package/dist/core/operations/update.js +161 -0
- package/dist/core/operations/update.js.map +1 -0
- package/dist/core/operations/user.d.ts +27 -0
- package/dist/core/operations/user.js +68 -0
- package/dist/core/operations/user.js.map +1 -0
- package/dist/core/profile/current.d.ts +35 -0
- package/dist/core/profile/current.js +31 -0
- package/dist/core/profile/current.js.map +1 -0
- package/dist/core/profile/index.d.ts +14 -0
- package/dist/core/profile/index.js +15 -0
- package/dist/core/profile/index.js.map +1 -0
- package/dist/core/profile/schema.d.ts +9 -0
- package/dist/core/profile/schema.js +100 -0
- package/dist/core/profile/schema.js.map +1 -0
- package/dist/core/storage/activity.d.ts +78 -0
- package/dist/core/storage/activity.js +131 -0
- package/dist/core/storage/activity.js.map +1 -0
- package/dist/core/storage/atomic.d.ts +34 -0
- package/dist/core/storage/atomic.js +117 -0
- package/dist/core/storage/atomic.js.map +1 -0
- package/dist/core/storage/comments.d.ts +39 -0
- package/dist/core/storage/comments.js +88 -0
- package/dist/core/storage/comments.js.map +1 -0
- package/dist/core/storage/document.d.ts +16 -0
- package/dist/core/storage/document.js +98 -0
- package/dist/core/storage/document.js.map +1 -0
- package/dist/core/storage/frontmatter.d.ts +16 -0
- package/dist/core/storage/frontmatter.js +33 -0
- package/dist/core/storage/frontmatter.js.map +1 -0
- package/dist/core/storage/git.d.ts +8 -0
- package/dist/core/storage/git.js +48 -0
- package/dist/core/storage/git.js.map +1 -0
- package/dist/core/storage/index.d.ts +27 -0
- package/dist/core/storage/index.js +28 -0
- package/dist/core/storage/index.js.map +1 -0
- package/dist/core/storage/local.d.ts +57 -0
- package/dist/core/storage/local.js +133 -0
- package/dist/core/storage/local.js.map +1 -0
- package/dist/core/storage/lock.d.ts +85 -0
- package/dist/core/storage/lock.js +364 -0
- package/dist/core/storage/lock.js.map +1 -0
- package/dist/core/storage/paths.d.ts +125 -0
- package/dist/core/storage/paths.js +202 -0
- package/dist/core/storage/paths.js.map +1 -0
- package/dist/core/storage/state.d.ts +32 -0
- package/dist/core/storage/state.js +71 -0
- package/dist/core/storage/state.js.map +1 -0
- package/dist/core/storage/templates.d.ts +29 -0
- package/dist/core/storage/templates.js +62 -0
- package/dist/core/storage/templates.js.map +1 -0
- package/dist/core/storage/views.d.ts +18 -0
- package/dist/core/storage/views.js +62 -0
- package/dist/core/storage/views.js.map +1 -0
- package/dist/core/validation/check.d.ts +4 -0
- package/dist/core/validation/check.js +22 -0
- package/dist/core/validation/check.js.map +1 -0
- package/dist/core/validation/checks/collection.d.ts +4 -0
- package/dist/core/validation/checks/collection.js +152 -0
- package/dist/core/validation/checks/collection.js.map +1 -0
- package/dist/core/validation/checks/counters.d.ts +3 -0
- package/dist/core/validation/checks/counters.js +23 -0
- package/dist/core/validation/checks/counters.js.map +1 -0
- package/dist/core/validation/checks/dependencies.d.ts +3 -0
- package/dist/core/validation/checks/dependencies.js +58 -0
- package/dist/core/validation/checks/dependencies.js.map +1 -0
- package/dist/core/validation/checks/gitignore.d.ts +10 -0
- package/dist/core/validation/checks/gitignore.js +22 -0
- package/dist/core/validation/checks/gitignore.js.map +1 -0
- package/dist/core/validation/checks/index-file.d.ts +8 -0
- package/dist/core/validation/checks/index-file.js +26 -0
- package/dist/core/validation/checks/index-file.js.map +1 -0
- package/dist/core/validation/checks/index.d.ts +20 -0
- package/dist/core/validation/checks/index.js +21 -0
- package/dist/core/validation/checks/index.js.map +1 -0
- package/dist/core/validation/checks/issues.d.ts +3 -0
- package/dist/core/validation/checks/issues.js +152 -0
- package/dist/core/validation/checks/issues.js.map +1 -0
- package/dist/core/validation/checks/periods.d.ts +3 -0
- package/dist/core/validation/checks/periods.js +89 -0
- package/dist/core/validation/checks/periods.js.map +1 -0
- package/dist/core/validation/checks/remotes.d.ts +20 -0
- package/dist/core/validation/checks/remotes.js +42 -0
- package/dist/core/validation/checks/remotes.js.map +1 -0
- package/dist/core/validation/checks/resources.d.ts +3 -0
- package/dist/core/validation/checks/resources.js +77 -0
- package/dist/core/validation/checks/resources.js.map +1 -0
- package/dist/core/validation/checks/rollup.d.ts +27 -0
- package/dist/core/validation/checks/rollup.js +53 -0
- package/dist/core/validation/checks/rollup.js.map +1 -0
- package/dist/core/validation/checks/squads.d.ts +3 -0
- package/dist/core/validation/checks/squads.js +42 -0
- package/dist/core/validation/checks/squads.js.map +1 -0
- package/dist/core/validation/checks/templates.d.ts +14 -0
- package/dist/core/validation/checks/templates.js +119 -0
- package/dist/core/validation/checks/templates.js.map +1 -0
- package/dist/core/validation/fix.d.ts +7 -0
- package/dist/core/validation/fix.js +273 -0
- package/dist/core/validation/fix.js.map +1 -0
- package/dist/core/validation/index.d.ts +7 -0
- package/dist/core/validation/index.js +8 -0
- package/dist/core/validation/index.js.map +1 -0
- package/dist/core/validation/shared.d.ts +47 -0
- package/dist/core/validation/shared.js +66 -0
- package/dist/core/validation/shared.js.map +1 -0
- package/dist/mcp/context.d.ts +70 -0
- package/dist/mcp/context.js +110 -0
- package/dist/mcp/context.js.map +1 -0
- package/dist/mcp/index.d.ts +49 -0
- package/dist/mcp/index.js +90 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/reply.d.ts +28 -0
- package/dist/mcp/reply.js +34 -0
- package/dist/mcp/reply.js.map +1 -0
- package/dist/mcp/tools/plan/create.d.ts +3 -0
- package/dist/mcp/tools/plan/create.js +128 -0
- package/dist/mcp/tools/plan/create.js.map +1 -0
- package/dist/mcp/tools/plan/delete.d.ts +3 -0
- package/dist/mcp/tools/plan/delete.js +35 -0
- package/dist/mcp/tools/plan/delete.js.map +1 -0
- package/dist/mcp/tools/plan/graph.d.ts +3 -0
- package/dist/mcp/tools/plan/graph.js +53 -0
- package/dist/mcp/tools/plan/graph.js.map +1 -0
- package/dist/mcp/tools/plan/index.d.ts +15 -0
- package/dist/mcp/tools/plan/index.js +16 -0
- package/dist/mcp/tools/plan/index.js.map +1 -0
- package/dist/mcp/tools/plan/link.d.ts +3 -0
- package/dist/mcp/tools/plan/link.js +46 -0
- package/dist/mcp/tools/plan/link.js.map +1 -0
- package/dist/mcp/tools/plan/registrar.d.ts +10 -0
- package/dist/mcp/tools/plan/registrar.js +24 -0
- package/dist/mcp/tools/plan/registrar.js.map +1 -0
- package/dist/mcp/tools/plan/reshape.d.ts +3 -0
- package/dist/mcp/tools/plan/reshape.js +95 -0
- package/dist/mcp/tools/plan/reshape.js.map +1 -0
- package/dist/mcp/tools/plan/timeline.d.ts +3 -0
- package/dist/mcp/tools/plan/timeline.js +68 -0
- package/dist/mcp/tools/plan/timeline.js.map +1 -0
- package/dist/mcp/tools/plan/upstream.d.ts +14 -0
- package/dist/mcp/tools/plan/upstream.js +69 -0
- package/dist/mcp/tools/plan/upstream.js.map +1 -0
- package/dist/mcp/tools/plan.d.ts +1 -0
- package/dist/mcp/tools/plan.js +2 -0
- package/dist/mcp/tools/plan.js.map +1 -0
- package/dist/mcp/tools/read.d.ts +3 -0
- package/dist/mcp/tools/read.js +339 -0
- package/dist/mcp/tools/read.js.map +1 -0
- package/dist/mcp/tools/remote.d.ts +3 -0
- package/dist/mcp/tools/remote.js +254 -0
- package/dist/mcp/tools/remote.js.map +1 -0
- package/dist/mcp/tools/templates.d.ts +12 -0
- package/dist/mcp/tools/templates.js +165 -0
- package/dist/mcp/tools/templates.js.map +1 -0
- package/dist/mcp/tools/work.d.ts +16 -0
- package/dist/mcp/tools/work.js +359 -0
- package/dist/mcp/tools/work.js.map +1 -0
- package/dist/remote/accounts.d.ts +158 -0
- package/dist/remote/accounts.js +207 -0
- package/dist/remote/accounts.js.map +1 -0
- package/dist/remote/adopt.d.ts +139 -0
- package/dist/remote/adopt.js +181 -0
- package/dist/remote/adopt.js.map +1 -0
- package/dist/remote/anchor.d.ts +35 -0
- package/dist/remote/anchor.js +53 -0
- package/dist/remote/anchor.js.map +1 -0
- package/dist/remote/attributes.d.ts +103 -0
- package/dist/remote/attributes.js +245 -0
- package/dist/remote/attributes.js.map +1 -0
- package/dist/remote/audit.d.ts +112 -0
- package/dist/remote/audit.js +215 -0
- package/dist/remote/audit.js.map +1 -0
- package/dist/remote/capabilities.d.ts +267 -0
- package/dist/remote/capabilities.js +225 -0
- package/dist/remote/capabilities.js.map +1 -0
- package/dist/remote/check.d.ts +49 -0
- package/dist/remote/check.js +356 -0
- package/dist/remote/check.js.map +1 -0
- package/dist/remote/comments.d.ts +86 -0
- package/dist/remote/comments.js +87 -0
- package/dist/remote/comments.js.map +1 -0
- package/dist/remote/config-file.d.ts +237 -0
- package/dist/remote/config-file.js +640 -0
- package/dist/remote/config-file.js.map +1 -0
- package/dist/remote/conflicts.d.ts +110 -0
- package/dist/remote/conflicts.js +188 -0
- package/dist/remote/conflicts.js.map +1 -0
- package/dist/remote/connection-catalogue.d.ts +65 -0
- package/dist/remote/connection-catalogue.js +66 -0
- package/dist/remote/connection-catalogue.js.map +1 -0
- package/dist/remote/connection.d.ts +84 -0
- package/dist/remote/connection.js +229 -0
- package/dist/remote/connection.js.map +1 -0
- package/dist/remote/coverage.d.ts +65 -0
- package/dist/remote/coverage.js +244 -0
- package/dist/remote/coverage.js.map +1 -0
- package/dist/remote/credentials.d.ts +95 -0
- package/dist/remote/credentials.js +210 -0
- package/dist/remote/credentials.js.map +1 -0
- package/dist/remote/execute.d.ts +178 -0
- package/dist/remote/execute.js +1245 -0
- package/dist/remote/execute.js.map +1 -0
- package/dist/remote/fingerprint.d.ts +172 -0
- package/dist/remote/fingerprint.js +337 -0
- package/dist/remote/fingerprint.js.map +1 -0
- package/dist/remote/fixtures.d.ts +176 -0
- package/dist/remote/fixtures.js +432 -0
- package/dist/remote/fixtures.js.map +1 -0
- package/dist/remote/guard.d.ts +90 -0
- package/dist/remote/guard.js +83 -0
- package/dist/remote/guard.js.map +1 -0
- package/dist/remote/hierarchy.d.ts +141 -0
- package/dist/remote/hierarchy.js +129 -0
- package/dist/remote/hierarchy.js.map +1 -0
- package/dist/remote/index.d.ts +145 -0
- package/dist/remote/index.js +146 -0
- package/dist/remote/index.js.map +1 -0
- package/dist/remote/inspect.d.ts +103 -0
- package/dist/remote/inspect.js +152 -0
- package/dist/remote/inspect.js.map +1 -0
- package/dist/remote/labels.d.ts +75 -0
- package/dist/remote/labels.js +137 -0
- package/dist/remote/labels.js.map +1 -0
- package/dist/remote/ladder.d.ts +145 -0
- package/dist/remote/ladder.js +387 -0
- package/dist/remote/ladder.js.map +1 -0
- package/dist/remote/ledger.d.ts +97 -0
- package/dist/remote/ledger.js +148 -0
- package/dist/remote/ledger.js.map +1 -0
- package/dist/remote/lifecycle.d.ts +175 -0
- package/dist/remote/lifecycle.js +274 -0
- package/dist/remote/lifecycle.js.map +1 -0
- package/dist/remote/links.d.ts +411 -0
- package/dist/remote/links.js +748 -0
- package/dist/remote/links.js.map +1 -0
- package/dist/remote/managed-block.d.ts +182 -0
- package/dist/remote/managed-block.js +406 -0
- package/dist/remote/managed-block.js.map +1 -0
- package/dist/remote/managed-comment.d.ts +104 -0
- package/dist/remote/managed-comment.js +92 -0
- package/dist/remote/managed-comment.js.map +1 -0
- package/dist/remote/mapping.d.ts +282 -0
- package/dist/remote/mapping.js +336 -0
- package/dist/remote/mapping.js.map +1 -0
- package/dist/remote/merge.d.ts +172 -0
- package/dist/remote/merge.js +221 -0
- package/dist/remote/merge.js.map +1 -0
- package/dist/remote/periods.d.ts +209 -0
- package/dist/remote/periods.js +224 -0
- package/dist/remote/periods.js.map +1 -0
- package/dist/remote/plan.d.ts +671 -0
- package/dist/remote/plan.js +1210 -0
- package/dist/remote/plan.js.map +1 -0
- package/dist/remote/policy.d.ts +67 -0
- package/dist/remote/policy.js +67 -0
- package/dist/remote/policy.js.map +1 -0
- package/dist/remote/preflight.d.ts +112 -0
- package/dist/remote/preflight.js +342 -0
- package/dist/remote/preflight.js.map +1 -0
- package/dist/remote/prerequisites.d.ts +113 -0
- package/dist/remote/prerequisites.js +161 -0
- package/dist/remote/prerequisites.js.map +1 -0
- package/dist/remote/provider.d.ts +802 -0
- package/dist/remote/provider.js +27 -0
- package/dist/remote/provider.js.map +1 -0
- package/dist/remote/providers/github/config.d.ts +84 -0
- package/dist/remote/providers/github/config.js +161 -0
- package/dist/remote/providers/github/config.js.map +1 -0
- package/dist/remote/providers/github/connector.d.ts +33 -0
- package/dist/remote/providers/github/connector.js +774 -0
- package/dist/remote/providers/github/connector.js.map +1 -0
- package/dist/remote/providers/github/edges.d.ts +29 -0
- package/dist/remote/providers/github/edges.js +56 -0
- package/dist/remote/providers/github/edges.js.map +1 -0
- package/dist/remote/providers/github/hierarchy.d.ts +21 -0
- package/dist/remote/providers/github/hierarchy.js +46 -0
- package/dist/remote/providers/github/hierarchy.js.map +1 -0
- package/dist/remote/providers/github/index.d.ts +26 -0
- package/dist/remote/providers/github/index.js +69 -0
- package/dist/remote/providers/github/index.js.map +1 -0
- package/dist/remote/providers/github/project-iteration.d.ts +71 -0
- package/dist/remote/providers/github/project-iteration.js +203 -0
- package/dist/remote/providers/github/project-iteration.js.map +1 -0
- package/dist/remote/providers/github/project-provision.d.ts +130 -0
- package/dist/remote/providers/github/project-provision.js +247 -0
- package/dist/remote/providers/github/project-provision.js.map +1 -0
- package/dist/remote/providers/github/project-status.d.ts +115 -0
- package/dist/remote/providers/github/project-status.js +317 -0
- package/dist/remote/providers/github/project-status.js.map +1 -0
- package/dist/remote/providers/github/projects.d.ts +181 -0
- package/dist/remote/providers/github/projects.js +481 -0
- package/dist/remote/providers/github/projects.js.map +1 -0
- package/dist/remote/providers/github/translator.d.ts +103 -0
- package/dist/remote/providers/github/translator.js +400 -0
- package/dist/remote/providers/github/translator.js.map +1 -0
- package/dist/remote/providers/jira/config.d.ts +96 -0
- package/dist/remote/providers/jira/config.js +200 -0
- package/dist/remote/providers/jira/config.js.map +1 -0
- package/dist/remote/providers/jira/connector.d.ts +42 -0
- package/dist/remote/providers/jira/connector.js +1688 -0
- package/dist/remote/providers/jira/connector.js.map +1 -0
- package/dist/remote/providers/jira/fields.d.ts +292 -0
- package/dist/remote/providers/jira/fields.js +463 -0
- package/dist/remote/providers/jira/fields.js.map +1 -0
- package/dist/remote/providers/jira/hierarchy.d.ts +28 -0
- package/dist/remote/providers/jira/hierarchy.js +60 -0
- package/dist/remote/providers/jira/hierarchy.js.map +1 -0
- package/dist/remote/providers/jira/index.d.ts +29 -0
- package/dist/remote/providers/jira/index.js +100 -0
- package/dist/remote/providers/jira/index.js.map +1 -0
- package/dist/remote/providers/jira/links.d.ts +41 -0
- package/dist/remote/providers/jira/links.js +102 -0
- package/dist/remote/providers/jira/links.js.map +1 -0
- package/dist/remote/providers/jira/sprints.d.ts +80 -0
- package/dist/remote/providers/jira/sprints.js +107 -0
- package/dist/remote/providers/jira/sprints.js.map +1 -0
- package/dist/remote/providers/jira/translator.d.ts +61 -0
- package/dist/remote/providers/jira/translator.js +382 -0
- package/dist/remote/providers/jira/translator.js.map +1 -0
- package/dist/remote/providers/jira/types.d.ts +112 -0
- package/dist/remote/providers/jira/types.js +160 -0
- package/dist/remote/providers/jira/types.js.map +1 -0
- package/dist/remote/providers/jira/vocabulary.d.ts +55 -0
- package/dist/remote/providers/jira/vocabulary.js +91 -0
- package/dist/remote/providers/jira/vocabulary.js.map +1 -0
- package/dist/remote/providers/jsonfile/config.d.ts +61 -0
- package/dist/remote/providers/jsonfile/config.js +83 -0
- package/dist/remote/providers/jsonfile/config.js.map +1 -0
- package/dist/remote/providers/jsonfile/connector.d.ts +34 -0
- package/dist/remote/providers/jsonfile/connector.js +251 -0
- package/dist/remote/providers/jsonfile/connector.js.map +1 -0
- package/dist/remote/providers/jsonfile/index.d.ts +38 -0
- package/dist/remote/providers/jsonfile/index.js +90 -0
- package/dist/remote/providers/jsonfile/index.js.map +1 -0
- package/dist/remote/providers/jsonfile/store.d.ts +59 -0
- package/dist/remote/providers/jsonfile/store.js +48 -0
- package/dist/remote/providers/jsonfile/store.js.map +1 -0
- package/dist/remote/providers/jsonfile/translator.d.ts +54 -0
- package/dist/remote/providers/jsonfile/translator.js +199 -0
- package/dist/remote/providers/jsonfile/translator.js.map +1 -0
- package/dist/remote/providers/linear/config.d.ts +83 -0
- package/dist/remote/providers/linear/config.js +140 -0
- package/dist/remote/providers/linear/config.js.map +1 -0
- package/dist/remote/providers/linear/connector.d.ts +36 -0
- package/dist/remote/providers/linear/connector.js +438 -0
- package/dist/remote/providers/linear/connector.js.map +1 -0
- package/dist/remote/providers/linear/estimate.d.ts +63 -0
- package/dist/remote/providers/linear/estimate.js +112 -0
- package/dist/remote/providers/linear/estimate.js.map +1 -0
- package/dist/remote/providers/linear/index.d.ts +31 -0
- package/dist/remote/providers/linear/index.js +75 -0
- package/dist/remote/providers/linear/index.js.map +1 -0
- package/dist/remote/providers/linear/labels.d.ts +51 -0
- package/dist/remote/providers/linear/labels.js +129 -0
- package/dist/remote/providers/linear/labels.js.map +1 -0
- package/dist/remote/providers/linear/states.d.ts +97 -0
- package/dist/remote/providers/linear/states.js +124 -0
- package/dist/remote/providers/linear/states.js.map +1 -0
- package/dist/remote/providers/linear/translator.d.ts +48 -0
- package/dist/remote/providers/linear/translator.js +343 -0
- package/dist/remote/providers/linear/translator.js.map +1 -0
- package/dist/remote/providers/linear/vocabulary.d.ts +27 -0
- package/dist/remote/providers/linear/vocabulary.js +42 -0
- package/dist/remote/providers/linear/vocabulary.js.map +1 -0
- package/dist/remote/provision.d.ts +45 -0
- package/dist/remote/provision.js +81 -0
- package/dist/remote/provision.js.map +1 -0
- package/dist/remote/pull.d.ts +52 -0
- package/dist/remote/pull.js +116 -0
- package/dist/remote/pull.js.map +1 -0
- package/dist/remote/readiness.d.ts +67 -0
- package/dist/remote/readiness.js +336 -0
- package/dist/remote/readiness.js.map +1 -0
- package/dist/remote/rebase.d.ts +157 -0
- package/dist/remote/rebase.js +255 -0
- package/dist/remote/rebase.js.map +1 -0
- package/dist/remote/reconcile.d.ts +115 -0
- package/dist/remote/reconcile.js +140 -0
- package/dist/remote/reconcile.js.map +1 -0
- package/dist/remote/redact.d.ts +70 -0
- package/dist/remote/redact.js +132 -0
- package/dist/remote/redact.js.map +1 -0
- package/dist/remote/registry.d.ts +32 -0
- package/dist/remote/registry.js +54 -0
- package/dist/remote/registry.js.map +1 -0
- package/dist/remote/remotes.d.ts +94 -0
- package/dist/remote/remotes.js +182 -0
- package/dist/remote/remotes.js.map +1 -0
- package/dist/remote/render.d.ts +147 -0
- package/dist/remote/render.js +683 -0
- package/dist/remote/render.js.map +1 -0
- package/dist/remote/report.d.ts +63 -0
- package/dist/remote/report.js +345 -0
- package/dist/remote/report.js.map +1 -0
- package/dist/remote/resolutions.d.ts +115 -0
- package/dist/remote/resolutions.js +217 -0
- package/dist/remote/resolutions.js.map +1 -0
- package/dist/remote/scaffold.d.ts +134 -0
- package/dist/remote/scaffold.js +370 -0
- package/dist/remote/scaffold.js.map +1 -0
- package/dist/remote/scope.d.ts +57 -0
- package/dist/remote/scope.js +91 -0
- package/dist/remote/scope.js.map +1 -0
- package/dist/remote/selection.d.ts +38 -0
- package/dist/remote/selection.js +50 -0
- package/dist/remote/selection.js.map +1 -0
- package/dist/remote/shape.d.ts +141 -0
- package/dist/remote/shape.js +220 -0
- package/dist/remote/shape.js.map +1 -0
- package/dist/remote/status.d.ts +104 -0
- package/dist/remote/status.js +238 -0
- package/dist/remote/status.js.map +1 -0
- package/dist/remote/sync.d.ts +182 -0
- package/dist/remote/sync.js +523 -0
- package/dist/remote/sync.js.map +1 -0
- package/dist/remote/transport/budget.d.ts +48 -0
- package/dist/remote/transport/budget.js +68 -0
- package/dist/remote/transport/budget.js.map +1 -0
- package/dist/remote/transport/connector.d.ts +115 -0
- package/dist/remote/transport/connector.js +21 -0
- package/dist/remote/transport/connector.js.map +1 -0
- package/dist/remote/transport/error.d.ts +88 -0
- package/dist/remote/transport/error.js +196 -0
- package/dist/remote/transport/error.js.map +1 -0
- package/dist/remote/transport/gh.d.ts +20 -0
- package/dist/remote/transport/gh.js +41 -0
- package/dist/remote/transport/gh.js.map +1 -0
- package/dist/remote/transport/graphql.d.ts +25 -0
- package/dist/remote/transport/graphql.js +81 -0
- package/dist/remote/transport/graphql.js.map +1 -0
- package/dist/remote/transport/http.d.ts +47 -0
- package/dist/remote/transport/http.js +195 -0
- package/dist/remote/transport/http.js.map +1 -0
- package/dist/remote/transport/index.d.ts +18 -0
- package/dist/remote/transport/index.js +19 -0
- package/dist/remote/transport/index.js.map +1 -0
- package/dist/remote/transport/process.d.ts +39 -0
- package/dist/remote/transport/process.js +121 -0
- package/dist/remote/transport/process.js.map +1 -0
- package/dist/remote/transport/rest.d.ts +22 -0
- package/dist/remote/transport/rest.js +57 -0
- package/dist/remote/transport/rest.js.map +1 -0
- package/dist/remote/transport/retry.d.ts +91 -0
- package/dist/remote/transport/retry.js +138 -0
- package/dist/remote/transport/retry.js.map +1 -0
- package/dist/remote/transport-error.d.ts +19 -0
- package/dist/remote/transport-error.js +22 -0
- package/dist/remote/transport-error.js.map +1 -0
- package/dist/remote/vocabulary.d.ts +126 -0
- package/dist/remote/vocabulary.js +57 -0
- package/dist/remote/vocabulary.js.map +1 -0
- package/dist/runner/config.d.ts +7 -0
- package/dist/runner/config.js +61 -0
- package/dist/runner/config.js.map +1 -0
- package/dist/runner/git.d.ts +19 -0
- package/dist/runner/git.js +26 -0
- package/dist/runner/git.js.map +1 -0
- package/dist/runner/index.d.ts +20 -0
- package/dist/runner/index.js +21 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/runner/loop.d.ts +46 -0
- package/dist/runner/loop.js +243 -0
- package/dist/runner/loop.js.map +1 -0
- package/dist/runner/pi.d.ts +39 -0
- package/dist/runner/pi.js +276 -0
- package/dist/runner/pi.js.map +1 -0
- package/dist/runner/shell.d.ts +44 -0
- package/dist/runner/shell.js +174 -0
- package/dist/runner/shell.js.map +1 -0
- package/dist/runner/stats.d.ts +19 -0
- package/dist/runner/stats.js +58 -0
- package/dist/runner/stats.js.map +1 -0
- package/dist/runner/types.d.ts +218 -0
- package/dist/runner/types.js +37 -0
- package/dist/runner/types.js.map +1 -0
- package/dist/server/git.d.ts +8 -0
- package/dist/server/git.js +50 -0
- package/dist/server/git.js.map +1 -0
- package/dist/server/http/respond.d.ts +13 -0
- package/dist/server/http/respond.js +59 -0
- package/dist/server/http/respond.js.map +1 -0
- package/dist/server/http/router.d.ts +27 -0
- package/dist/server/http/router.js +48 -0
- package/dist/server/http/router.js.map +1 -0
- package/dist/server/http/static.d.ts +14 -0
- package/dist/server/http/static.js +55 -0
- package/dist/server/http/static.js.map +1 -0
- package/dist/server/index.d.ts +58 -0
- package/dist/server/index.js +124 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes/board.d.ts +8 -0
- package/dist/server/routes/board.js +19 -0
- package/dist/server/routes/board.js.map +1 -0
- package/dist/server/routes/comments.d.ts +13 -0
- package/dist/server/routes/comments.js +36 -0
- package/dist/server/routes/comments.js.map +1 -0
- package/dist/server/routes/flags.d.ts +16 -0
- package/dist/server/routes/flags.js +37 -0
- package/dist/server/routes/flags.js.map +1 -0
- package/dist/server/routes/git.d.ts +13 -0
- package/dist/server/routes/git.js +84 -0
- package/dist/server/routes/git.js.map +1 -0
- package/dist/server/routes/remotes.d.ts +36 -0
- package/dist/server/routes/remotes.js +749 -0
- package/dist/server/routes/remotes.js.map +1 -0
- package/dist/server/routes/views.d.ts +10 -0
- package/dist/server/routes/views.js +63 -0
- package/dist/server/routes/views.js.map +1 -0
- package/dist/server/views/schema.d.ts +2 -0
- package/dist/server/views/schema.js +121 -0
- package/dist/server/views/schema.js.map +1 -0
- package/dist/server/views/store.d.ts +17 -0
- package/dist/server/views/store.js +67 -0
- package/dist/server/views/store.js.map +1 -0
- package/dist/shared/adf.d.ts +83 -0
- package/dist/shared/adf.js +795 -0
- package/dist/shared/adf.js.map +1 -0
- package/dist/shared/blocking.d.ts +130 -0
- package/dist/shared/blocking.js +179 -0
- package/dist/shared/blocking.js.map +1 -0
- package/dist/shared/changes.d.ts +100 -0
- package/dist/shared/changes.js +112 -0
- package/dist/shared/changes.js.map +1 -0
- package/dist/shared/cohesion.d.ts +75 -0
- package/dist/shared/cohesion.js +132 -0
- package/dist/shared/cohesion.js.map +1 -0
- package/dist/shared/dependency-rollup.d.ts +91 -0
- package/dist/shared/dependency-rollup.js +132 -0
- package/dist/shared/dependency-rollup.js.map +1 -0
- package/dist/shared/errors.d.ts +23 -0
- package/dist/shared/errors.js +24 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/flag-rollup.d.ts +88 -0
- package/dist/shared/flag-rollup.js +119 -0
- package/dist/shared/flag-rollup.js.map +1 -0
- package/dist/shared/git-sync.d.ts +90 -0
- package/dist/shared/git-sync.js +9 -0
- package/dist/shared/git-sync.js.map +1 -0
- package/dist/shared/index.d.ts +40 -0
- package/dist/shared/index.js +41 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/model.d.ts +218 -0
- package/dist/shared/model.js +95 -0
- package/dist/shared/model.js.map +1 -0
- package/dist/shared/period-query.d.ts +21 -0
- package/dist/shared/period-query.js +26 -0
- package/dist/shared/period-query.js.map +1 -0
- package/dist/shared/period-stance.d.ts +45 -0
- package/dist/shared/period-stance.js +50 -0
- package/dist/shared/period-stance.js.map +1 -0
- package/dist/shared/plans/breakdown.d.ts +66 -0
- package/dist/shared/plans/breakdown.js +186 -0
- package/dist/shared/plans/breakdown.js.map +1 -0
- package/dist/shared/plans/index.d.ts +21 -0
- package/dist/shared/plans/index.js +22 -0
- package/dist/shared/plans/index.js.map +1 -0
- package/dist/shared/plans/instantiate.d.ts +61 -0
- package/dist/shared/plans/instantiate.js +184 -0
- package/dist/shared/plans/instantiate.js.map +1 -0
- package/dist/shared/plans/reading.d.ts +79 -0
- package/dist/shared/plans/reading.js +149 -0
- package/dist/shared/plans/reading.js.map +1 -0
- package/dist/shared/plans/reparent.d.ts +54 -0
- package/dist/shared/plans/reparent.js +217 -0
- package/dist/shared/plans/reparent.js.map +1 -0
- package/dist/shared/plans/timeline.d.ts +93 -0
- package/dist/shared/plans/timeline.js +224 -0
- package/dist/shared/plans/timeline.js.map +1 -0
- package/dist/shared/plans/upstream.d.ts +67 -0
- package/dist/shared/plans/upstream.js +73 -0
- package/dist/shared/plans/upstream.js.map +1 -0
- package/dist/shared/remote-api.d.ts +413 -0
- package/dist/shared/remote-api.js +11 -0
- package/dist/shared/remote-api.js.map +1 -0
- package/dist/shared/remote-coverage.d.ts +120 -0
- package/dist/shared/remote-coverage.js +98 -0
- package/dist/shared/remote-coverage.js.map +1 -0
- package/dist/shared/remote-readiness.d.ts +162 -0
- package/dist/shared/remote-readiness.js +49 -0
- package/dist/shared/remote-readiness.js.map +1 -0
- package/dist/shared/remote-status.d.ts +268 -0
- package/dist/shared/remote-status.js +79 -0
- package/dist/shared/remote-status.js.map +1 -0
- package/dist/shared/rollup.d.ts +90 -0
- package/dist/shared/rollup.js +119 -0
- package/dist/shared/rollup.js.map +1 -0
- package/dist/shared/static.d.ts +65 -0
- package/dist/shared/static.js +107 -0
- package/dist/shared/static.js.map +1 -0
- package/dist/shared/template-params.d.ts +80 -0
- package/dist/shared/template-params.js +141 -0
- package/dist/shared/template-params.js.map +1 -0
- package/dist/shared/view.d.ts +105 -0
- package/dist/shared/view.js +44 -0
- package/dist/shared/view.js.map +1 -0
- package/dist/shared/work-unit.d.ts +34 -0
- package/dist/shared/work-unit.js +47 -0
- package/dist/shared/work-unit.js.map +1 -0
- package/dist/sync/apply.d.ts +21 -0
- package/dist/sync/apply.js +149 -0
- package/dist/sync/apply.js.map +1 -0
- package/dist/sync/dto.d.ts +9 -0
- package/dist/sync/dto.js +162 -0
- package/dist/sync/dto.js.map +1 -0
- package/dist/sync/index.d.ts +24 -0
- package/dist/sync/index.js +25 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/patch.d.ts +13 -0
- package/dist/sync/patch.js +212 -0
- package/dist/sync/patch.js.map +1 -0
- package/dist/sync/session.d.ts +31 -0
- package/dist/sync/session.js +55 -0
- package/dist/sync/session.js.map +1 -0
- package/dist/sync/static.d.ts +11 -0
- package/dist/sync/static.js +33 -0
- package/dist/sync/static.js.map +1 -0
- package/package.json +95 -4
- package/templates/blank.yml +71 -0
- package/templates/context/bug.md +107 -0
- package/templates/context/default.md +119 -0
- package/templates/context/epic.md +81 -0
- package/templates/context/feature.md +91 -0
- package/templates/context/research.md +87 -0
- package/templates/context/review.md +81 -0
- package/templates/context/story.md +94 -0
- package/templates/context/sub_task.md +108 -0
- package/templates/context/task.md +86 -0
- package/templates/context/test.md +73 -0
- package/templates/context/user_story.md +147 -0
- package/templates/kanban.yml +226 -0
- package/templates/scrum.yml +567 -0
- package/web/dist/assets/index-D0j8OnHq.js +35 -0
- package/web/dist/assets/index-DFL7v8aP.css +1 -0
- package/web/dist/index.html +13 -0
- package/web/dist-viewer/assets/index-Bcns6OuC.css +1 -0
- package/web/dist-viewer/assets/index-vVXzeiFo.js +7 -0
- package/web/dist-viewer/index.html +14 -0
|
@@ -0,0 +1,1210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The push and pull planners — pure functions from a board to a plan.
|
|
3
|
+
*
|
|
4
|
+
* planPush(board, links, remote) → PushPlan (LP-280, this file)
|
|
5
|
+
* planPull(board, links, remote, options) → PullPlan (LP-281, this file)
|
|
6
|
+
*
|
|
7
|
+
* Both take a `BoardView` DTO rather than a `LoadedBoard`, so neither needs a
|
|
8
|
+
* filesystem handle and the plan is a plain data structure a dry-run can render
|
|
9
|
+
* and an executor can walk. Neither touches the disk or the network: the push
|
|
10
|
+
* plan is handed to the executor (`LP-490`), the pull plan to `applyChanges`
|
|
11
|
+
* from `src/sync`.
|
|
12
|
+
*
|
|
13
|
+
* ## Why a push plan exists at all
|
|
14
|
+
*
|
|
15
|
+
* A push is a list of *remote operations*, not a batch write. The remote is a
|
|
16
|
+
* separate system with its own ids, its own workflow and its own idea of a
|
|
17
|
+
* parent, so the plan has to say, in order:
|
|
18
|
+
*
|
|
19
|
+
* 1. what to create, parents before children (a parent must exist before its
|
|
20
|
+
* children are filed under it);
|
|
21
|
+
* 2. what to update / transition / close, for documents that already have a
|
|
22
|
+
* twin;
|
|
23
|
+
* 3. what to link and unlink, after every endpoint exists.
|
|
24
|
+
*
|
|
25
|
+
* Two rules make the order load-bearing. A `create` op carries no remote id —
|
|
26
|
+
* it is referenced by a **placeholder** (`new:1`) until the executor resolves
|
|
27
|
+
* it against the response — and an edge (`link` / `unlink`) is emitted only
|
|
28
|
+
* once both endpoints exist, so a document created in this same push can be an
|
|
29
|
+
* endpoint. That is the held-back-link trick `src/sync/patch.ts` uses for
|
|
30
|
+
* `depends_on`, expressed here as a rule about op order rather than a deferred
|
|
31
|
+
* patch: every edge is placed after every create and update, and it references
|
|
32
|
+
* its endpoints through a `RemoteRef` that is either a recorded twin or a
|
|
33
|
+
* placeholder.
|
|
34
|
+
*
|
|
35
|
+
* ## What this file deliberately does not do
|
|
36
|
+
*
|
|
37
|
+
* - **Conflict detection on the push side** (both sides edited a field) is
|
|
38
|
+
* `LP-257`'s; `planPush` diffs a document against its base and pushes what
|
|
39
|
+
* changed locally — it never consults the remote side for a pushed field.
|
|
40
|
+
* The pull planner *does* detect a both-sides edit: a field both sides
|
|
41
|
+
* changed is reported in `PullPlan.fieldConflicts` rather than written.
|
|
42
|
+
* - **Existence and shape** (the twin was deleted, moved or converted) is
|
|
43
|
+
* `LP-361`'s; `remote.issues` is part of the input so that story and the pull
|
|
44
|
+
* planner can read it, but this planner does not yet act on it.
|
|
45
|
+
* - **The mapping** is the translator's job at execution time. The planner
|
|
46
|
+
* decides *what* changed against the base snapshot and *in what order* it is
|
|
47
|
+
* pushed; it never turns a board status into a remote state.
|
|
48
|
+
* - **`relates_to`** is a non-gating edge: it is linked / unlinked natively
|
|
49
|
+
* only where the provider declares a native relate edge (Jira, LP-325), and
|
|
50
|
+
* rides the managed block everywhere else (LP-314).
|
|
51
|
+
*/
|
|
52
|
+
import { tempId } from '../shared/changes.js';
|
|
53
|
+
import { counterFactory } from '../shared/plans/reading.js';
|
|
54
|
+
import { subtreeIds } from '../shared/plans/reading.js';
|
|
55
|
+
import { anchorResolver } from './anchor.js';
|
|
56
|
+
import { parentIsNative, carrierChanged } from './hierarchy.js';
|
|
57
|
+
import { getSyncedCommentIndexes, getSyncedRemoteCommentIds, getTombstone, hashBody, isManagedComment } from './links.js';
|
|
58
|
+
import { mergeValues, NO_BASE, valuesEqual } from './merge.js';
|
|
59
|
+
import { planCommentPull, planCommentPush, renderCommentForRemote } from './comments.js';
|
|
60
|
+
/** Fields `computeBase` treats as a list, in either of its two spellings. */
|
|
61
|
+
const BASE_LIST_KEYS = ['dependsOn', 'depends_on'];
|
|
62
|
+
const BASE_RELATES_KEYS = ['relatesTo', 'relates_to'];
|
|
63
|
+
/** Read a base's dependency list, whichever spelling it was stored under. */
|
|
64
|
+
function baseDepends(base) {
|
|
65
|
+
for (const key of BASE_LIST_KEYS) {
|
|
66
|
+
const value = base[key];
|
|
67
|
+
if (Array.isArray(value)) {
|
|
68
|
+
return value.filter((entry) => typeof entry === 'string').sort();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
/** Read a base's relates list, whichever spelling it was stored under. */
|
|
74
|
+
function baseRelates(base) {
|
|
75
|
+
for (const key of BASE_RELATES_KEYS) {
|
|
76
|
+
const value = base[key];
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
return value.filter((entry) => typeof entry === 'string').sort();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
/** The canonical, non-attribute fields a base snapshot may record. */
|
|
84
|
+
const BASE_FIELD_KEYS = new Set([
|
|
85
|
+
'title',
|
|
86
|
+
'body',
|
|
87
|
+
'status',
|
|
88
|
+
'assignee',
|
|
89
|
+
'period',
|
|
90
|
+
'dependsOn',
|
|
91
|
+
'depends_on',
|
|
92
|
+
'relatesTo',
|
|
93
|
+
'relates_to',
|
|
94
|
+
'relatedFiles',
|
|
95
|
+
'related_files',
|
|
96
|
+
// Shape fields (LP-368): recorded in the base so a later sync can tell who
|
|
97
|
+
// moved a document. They are never pushed as `update` fields — reparenting
|
|
98
|
+
// is planned by the shared planners, not by the field diff — so `diffIssue`
|
|
99
|
+
// must ignore them rather than treat them as phantom attributes.
|
|
100
|
+
'parent',
|
|
101
|
+
'type',
|
|
102
|
+
]);
|
|
103
|
+
/**
|
|
104
|
+
* Compare one issue against its base snapshot.
|
|
105
|
+
*
|
|
106
|
+
* With a base, a field is "changed" only when the base records it and the
|
|
107
|
+
* local value differs — a field the base does not record (a mapping change, a
|
|
108
|
+
* new attribute) is not tracked, so it cannot manufacture a phantom conflict.
|
|
109
|
+
* Without a base (a twin recorded but never confirmed), every field visible
|
|
110
|
+
* locally counts as changed, so the first push converges the twin onto the
|
|
111
|
+
* board's current state.
|
|
112
|
+
*/
|
|
113
|
+
function diffIssue(issue, base, normalizeBody) {
|
|
114
|
+
if (base === undefined) {
|
|
115
|
+
// No base: push the whole document so the twin converges.
|
|
116
|
+
const fields = {
|
|
117
|
+
title: issue.title,
|
|
118
|
+
body: issue.body,
|
|
119
|
+
assignee: issue.assignee ?? null,
|
|
120
|
+
period: issue.period ?? null,
|
|
121
|
+
};
|
|
122
|
+
const attributes = {};
|
|
123
|
+
for (const [key, value] of Object.entries(issue.attributes)) {
|
|
124
|
+
if (value !== null && value !== undefined)
|
|
125
|
+
attributes[key] = value;
|
|
126
|
+
}
|
|
127
|
+
if (Object.keys(attributes).length > 0)
|
|
128
|
+
fields.attributes = attributes;
|
|
129
|
+
return {
|
|
130
|
+
fields,
|
|
131
|
+
statusChanged: true,
|
|
132
|
+
status: issue.status,
|
|
133
|
+
dependsAdded: [...issue.dependsOn],
|
|
134
|
+
dependsRemoved: [],
|
|
135
|
+
relatesAdded: [...issue.relatesTo],
|
|
136
|
+
relatesRemoved: [],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const fields = {};
|
|
140
|
+
if ('title' in base && issue.title !== base.title)
|
|
141
|
+
fields.title = issue.title;
|
|
142
|
+
// The base stores the body as a hash (`hashBody`), so a local edit is a hash
|
|
143
|
+
// mismatch, never a text comparison.
|
|
144
|
+
if ('body' in base && hashBody(issue.body, normalizeBody) !== base.body)
|
|
145
|
+
fields.body = issue.body;
|
|
146
|
+
if ('assignee' in base && (issue.assignee ?? null) !== base.assignee) {
|
|
147
|
+
fields.assignee = issue.assignee ?? null;
|
|
148
|
+
}
|
|
149
|
+
if ('period' in base && (issue.period ?? null) !== base.period) {
|
|
150
|
+
fields.period = issue.period ?? null;
|
|
151
|
+
}
|
|
152
|
+
// Attributes are the base keys that are not canonical fields — only those
|
|
153
|
+
// are mapped, so only those are compared. The comparison is canonical
|
|
154
|
+
// (`valuesEqual`), not reference equality: an `array` attribute reloaded
|
|
155
|
+
// from YAML is a fresh array each time, and `!==` would re-push it forever.
|
|
156
|
+
const attributes = {};
|
|
157
|
+
for (const [key, stored] of Object.entries(base)) {
|
|
158
|
+
if (BASE_FIELD_KEYS.has(key))
|
|
159
|
+
continue;
|
|
160
|
+
const local = issue.attributes[key] ?? null;
|
|
161
|
+
if (!valuesEqual(local, stored))
|
|
162
|
+
attributes[key] = local;
|
|
163
|
+
}
|
|
164
|
+
if (Object.keys(attributes).length > 0)
|
|
165
|
+
fields.attributes = attributes;
|
|
166
|
+
const statusChanged = 'status' in base && issue.status !== base.status;
|
|
167
|
+
const localDepends = [...issue.dependsOn].sort();
|
|
168
|
+
const remoteDepends = baseDepends(base);
|
|
169
|
+
const dependsAdded = localDepends.filter((id) => !remoteDepends.includes(id));
|
|
170
|
+
const dependsRemoved = remoteDepends.filter((id) => !localDepends.includes(id));
|
|
171
|
+
const localRelates = [...issue.relatesTo].sort();
|
|
172
|
+
const remoteRelates = baseRelates(base);
|
|
173
|
+
const relatesAdded = localRelates.filter((id) => !remoteRelates.includes(id));
|
|
174
|
+
const relatesRemoved = remoteRelates.filter((id) => !localRelates.includes(id));
|
|
175
|
+
return {
|
|
176
|
+
fields,
|
|
177
|
+
statusChanged,
|
|
178
|
+
status: issue.status,
|
|
179
|
+
dependsAdded,
|
|
180
|
+
dependsRemoved,
|
|
181
|
+
relatesAdded,
|
|
182
|
+
relatesRemoved,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** The issue fields a create carries, in board vocabulary. */
|
|
186
|
+
function boardFields(issue) {
|
|
187
|
+
return {
|
|
188
|
+
title: issue.title,
|
|
189
|
+
body: issue.body,
|
|
190
|
+
type: issue.type,
|
|
191
|
+
status: issue.status,
|
|
192
|
+
assignee: issue.assignee,
|
|
193
|
+
period: issue.period,
|
|
194
|
+
attributes: issue.attributes,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/** The number of ancestors above an issue — the depth creates are ordered by. */
|
|
198
|
+
function issueDepth(board, issue) {
|
|
199
|
+
let depth = 0;
|
|
200
|
+
const seen = new Set();
|
|
201
|
+
let parent = issue.parentId;
|
|
202
|
+
while (parent && !seen.has(parent)) {
|
|
203
|
+
seen.add(parent);
|
|
204
|
+
depth += 1;
|
|
205
|
+
parent = board.nodes[parent]?.parentId ?? null;
|
|
206
|
+
}
|
|
207
|
+
return depth;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The parent reference a create / restore writes, split by the hierarchy
|
|
211
|
+
* encoding (LP-309): native `parent` while the document sits within the
|
|
212
|
+
* remote's native depth, `blockParent` beyond it. With no resolved encoding
|
|
213
|
+
* the parent stays native, the pre-encoding behaviour.
|
|
214
|
+
*/
|
|
215
|
+
function parentRefFor(remote, depth, parent) {
|
|
216
|
+
if (parent === undefined)
|
|
217
|
+
return {};
|
|
218
|
+
const resolved = remote.hierarchy;
|
|
219
|
+
if (resolved === undefined)
|
|
220
|
+
return { parent };
|
|
221
|
+
return parentIsNative(depth, resolved) ? { parent } : { blockParent: parent };
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* The comment a policy close posts on the twin before closing it (LP-351): who
|
|
225
|
+
* closed it, why, and the local document that went away. Written for the
|
|
226
|
+
* person reading the remote tracker, who may never have seen the `.lpm` folder.
|
|
227
|
+
*/
|
|
228
|
+
function pushDeleteNote(localId, remoteKey, deleted) {
|
|
229
|
+
const key = remoteKey !== '' ? ` (${remoteKey})` : '';
|
|
230
|
+
const why = deleted
|
|
231
|
+
? `the local document ${localId} that tracked it was deleted`
|
|
232
|
+
: `the local document ${localId} that tracked it moved out of this remote's scope`;
|
|
233
|
+
return `Closed by light-plan: ${why}${key}. See the board's \`on_delete\` policy.`;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Plan a push: the remote operations needed to bring the remote's in-scope
|
|
237
|
+
* issues onto the board's current state, ordered so every reference resolves.
|
|
238
|
+
*
|
|
239
|
+
* Returns an empty `{ ops: [], skipped: [] }` when nothing changed, when the
|
|
240
|
+
* remote is pull-only, or when the scope holds no documents. The order inside
|
|
241
|
+
* a non-empty plan is:
|
|
242
|
+
*
|
|
243
|
+
* 1. `create` ops, parents before children (by depth);
|
|
244
|
+
* 2. `update` / `transition` / `close` ops for changed twins;
|
|
245
|
+
* 3. `link` / `unlink` ops, after every endpoint exists;
|
|
246
|
+
* 4. the `on_delete` resolution for linked twins whose local document went
|
|
247
|
+
* away (deleted, or left the scope subtree): `unlink` / `close` / `delete`.
|
|
248
|
+
*
|
|
249
|
+
* In-scope documents carrying a tombstone are never created; they are
|
|
250
|
+
* reported in `skipped` rather than silently omitted (LP-366).
|
|
251
|
+
*
|
|
252
|
+
* A `create` carries no remote id: it is referenced by a `new:` placeholder,
|
|
253
|
+
* which the executor resolves against each create's response before it reaches
|
|
254
|
+
* any op that points at it.
|
|
255
|
+
*/
|
|
256
|
+
export function planPush(board, links, remote, localComments) {
|
|
257
|
+
if (remote.direction === 'pull')
|
|
258
|
+
return { ops: [], skipped: [] };
|
|
259
|
+
const issues = Object.values(board.nodes)
|
|
260
|
+
.filter((node) => node.kind === 'issue')
|
|
261
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
262
|
+
const scope = remote.scope ? new Set(subtreeIds(board, remote.scope)) : null;
|
|
263
|
+
const inScope = (id) => scope === null || scope.has(id);
|
|
264
|
+
// What this run is about, which is not the same question as what the remote
|
|
265
|
+
// owns — see `only` on RemoteSnapshot. Everything that *writes* asks
|
|
266
|
+
// `acting`; the gone pass and `refOf` ask `inScope`.
|
|
267
|
+
const only = remote.only ?? null;
|
|
268
|
+
const acting = (id) => inScope(id) && (only === null || only.has(id));
|
|
269
|
+
const ownedElsewhere = remote.ownedElsewhere ?? null;
|
|
270
|
+
// -- skipped: in-scope documents deliberately decoupled -------------------
|
|
271
|
+
// A tombstoned document is never created, and it is reported rather than
|
|
272
|
+
// silently omitted (LP-366). Its base snapshot is already gone — the link
|
|
273
|
+
// was dropped when the tombstone was written.
|
|
274
|
+
const skipped = [];
|
|
275
|
+
// -- creates: in-scope documents with no twin, parents before children ----
|
|
276
|
+
const toCreate = issues.filter((issue) => {
|
|
277
|
+
if (!acting(issue.id))
|
|
278
|
+
return false;
|
|
279
|
+
if (links.links.has(issue.id))
|
|
280
|
+
return false;
|
|
281
|
+
const claimed = ownedElsewhere?.get(issue.id);
|
|
282
|
+
if (claimed !== undefined) {
|
|
283
|
+
// One document, one remote. Reported rather than refused: a whole-board
|
|
284
|
+
// push to a second tracker is a normal thing to run on a board whose
|
|
285
|
+
// remotes divide the work between them.
|
|
286
|
+
skipped.push({ localId: issue.id, reason: 'owned_elsewhere', remoteKey: claimed });
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
const tombstone = getTombstone(links, issue.id);
|
|
290
|
+
if (tombstone !== undefined) {
|
|
291
|
+
skipped.push({
|
|
292
|
+
localId: issue.id,
|
|
293
|
+
reason: tombstone.reason || 'manual',
|
|
294
|
+
remoteKey: tombstone.remoteKey || '',
|
|
295
|
+
});
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
return true;
|
|
299
|
+
});
|
|
300
|
+
toCreate.sort((a, b) => issueDepth(board, a) - issueDepth(board, b) || a.id.localeCompare(b.id));
|
|
301
|
+
const placeholderOf = new Map();
|
|
302
|
+
toCreate.forEach((issue, index) => placeholderOf.set(issue.id, tempId(index + 1)));
|
|
303
|
+
/** The remote reference for a local id, when it is a resolvable endpoint. */
|
|
304
|
+
const refOf = (id) => {
|
|
305
|
+
const placeholder = placeholderOf.get(id);
|
|
306
|
+
if (placeholder !== undefined)
|
|
307
|
+
return { kind: 'created', localId: id, placeholder };
|
|
308
|
+
const link = links.links.get(id);
|
|
309
|
+
if (link && inScope(id))
|
|
310
|
+
return { kind: 'linked', localId: id, remoteId: link.remoteId };
|
|
311
|
+
return undefined;
|
|
312
|
+
};
|
|
313
|
+
const ops = [];
|
|
314
|
+
for (const issue of toCreate) {
|
|
315
|
+
const parent = issue.parentId ? refOf(issue.parentId) : undefined;
|
|
316
|
+
ops.push({
|
|
317
|
+
kind: 'create',
|
|
318
|
+
placeholder: placeholderOf.get(issue.id),
|
|
319
|
+
localId: issue.id,
|
|
320
|
+
fields: boardFields(issue),
|
|
321
|
+
...parentRefFor(remote, issueDepth(board, issue), parent),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
// The diff for every in-scope twin, computed once; absent for a create.
|
|
325
|
+
const diffOf = new Map();
|
|
326
|
+
for (const issue of issues) {
|
|
327
|
+
if (!inScope(issue.id))
|
|
328
|
+
continue;
|
|
329
|
+
const link = links.links.get(issue.id);
|
|
330
|
+
if (link)
|
|
331
|
+
diffOf.set(issue.id, diffIssue(issue, link.base, remote.normalizeBody));
|
|
332
|
+
}
|
|
333
|
+
// -- updates / transitions / closes for changed twins ---------------------
|
|
334
|
+
for (const issue of issues) {
|
|
335
|
+
if (!acting(issue.id))
|
|
336
|
+
continue;
|
|
337
|
+
const link = links.links.get(issue.id);
|
|
338
|
+
if (!link)
|
|
339
|
+
continue; // already planned as a create
|
|
340
|
+
const diff = diffOf.get(issue.id);
|
|
341
|
+
const ref = { kind: 'linked', localId: issue.id, remoteId: link.remoteId };
|
|
342
|
+
const parent = issue.parentId ? refOf(issue.parentId) : undefined;
|
|
343
|
+
const { blockParent } = parentRefFor(remote, issueDepth(board, issue), parent);
|
|
344
|
+
if (Object.keys(diff.fields).length > 0) {
|
|
345
|
+
ops.push({
|
|
346
|
+
kind: 'update',
|
|
347
|
+
localId: issue.id,
|
|
348
|
+
ref,
|
|
349
|
+
fields: diff.fields,
|
|
350
|
+
...(blockParent !== undefined ? { blockParent } : {}),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
if (diff.statusChanged) {
|
|
354
|
+
const terminal = board.config.statuses.find((status) => status.id === diff.status)?.terminal === true;
|
|
355
|
+
if (terminal) {
|
|
356
|
+
ops.push({ kind: 'close', localId: issue.id, ref });
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
ops.push({ kind: 'transition', localId: issue.id, ref, status: diff.status });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// -- reparent: linked twins whose parent moved, switched carrier, or has
|
|
364
|
+
// only now arrived upstream -------------------------------------------
|
|
365
|
+
// A local reparent (LP-493 gap 2) is detected against the base snapshot's
|
|
366
|
+
// recorded `parent`; a hierarchy-encoding switch (gap 1) against the
|
|
367
|
+
// encoding the link store last recorded. Both emit a `reparent` op that
|
|
368
|
+
// re-writes the twin's parent on whichever carrier the *current* encoding
|
|
369
|
+
// uses — in place, never a re-file. A twin with no parent in either the
|
|
370
|
+
// base or the board is left alone, whatever the encoding.
|
|
371
|
+
//
|
|
372
|
+
// The third case is what makes a plan pushable a piece at a time. A story
|
|
373
|
+
// filed before its feature had a twin was filed at the remote's root, and
|
|
374
|
+
// neither of the first two cases ever fires for it: the local parent never
|
|
375
|
+
// moved and the encoding never changed, so pushing the feature afterwards
|
|
376
|
+
// left the story dangling. `parentRemoteId` on the link is the record of
|
|
377
|
+
// what was actually filed, so "the board says it has a parent, the parent
|
|
378
|
+
// now has a twin, and that is not the twin we filed it under" is a question
|
|
379
|
+
// that can be asked at all. An older link that never recorded one answers
|
|
380
|
+
// `undefined` — unknown, and unknown changes nothing.
|
|
381
|
+
const recordedHierarchy = links.hierarchy;
|
|
382
|
+
for (const issue of issues) {
|
|
383
|
+
if (!acting(issue.id))
|
|
384
|
+
continue;
|
|
385
|
+
const link = links.links.get(issue.id);
|
|
386
|
+
if (!link)
|
|
387
|
+
continue; // planned as a create, whose parent is already placed
|
|
388
|
+
const depth = issueDepth(board, issue);
|
|
389
|
+
const carrierSwitched = carrierChanged(recordedHierarchy, remote.hierarchy, depth);
|
|
390
|
+
const baseRecordsParent = link.base !== undefined && 'parent' in link.base;
|
|
391
|
+
const parentChanged = baseRecordsParent &&
|
|
392
|
+
(issue.parentId ?? null) !== (link.base['parent'] ?? null);
|
|
393
|
+
// The twin's filed parent, against the parent it should have now. Only a
|
|
394
|
+
// link that recorded what it filed takes part; `undefined` is "we do not
|
|
395
|
+
// know", never "none".
|
|
396
|
+
const filedParent = link.parentRemoteId;
|
|
397
|
+
const wantedRef = issue.parentId ? refOf(issue.parentId) : undefined;
|
|
398
|
+
const parentArrived = filedParent !== undefined &&
|
|
399
|
+
wantedRef !== undefined &&
|
|
400
|
+
// A parent this same push is creating has no remote id to compare yet,
|
|
401
|
+
// so the question is whether this twin was filed at the root — which is
|
|
402
|
+
// exactly the case pushing the parent is meant to repair, and it is
|
|
403
|
+
// repaired in the same run rather than on the one after.
|
|
404
|
+
(wantedRef.kind === 'created' ? filedParent === null : filedParent !== wantedRef.remoteId);
|
|
405
|
+
if (!carrierSwitched && !parentChanged && !parentArrived)
|
|
406
|
+
continue;
|
|
407
|
+
const parent = issue.parentId ? refOf(issue.parentId) : undefined;
|
|
408
|
+
ops.push({
|
|
409
|
+
kind: 'reparent',
|
|
410
|
+
localId: issue.id,
|
|
411
|
+
ref: { kind: 'linked', localId: issue.id, remoteId: link.remoteId },
|
|
412
|
+
...parentRefFor(remote, depth, parent),
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
// -- edges, after every endpoint exists -----------------------------------
|
|
416
|
+
// A provider with a native blocking edge links/unlinks it directly; one
|
|
417
|
+
// without carries `depends_on` in the managed block. `relates_to` is native
|
|
418
|
+
// only where the provider declares it (Jira, LP-325); otherwise it rides the
|
|
419
|
+
// block (LP-314). The block is authoritative: its op carries the complete
|
|
420
|
+
// current set, and the executor omits any reference it cannot resolve
|
|
421
|
+
// (AC #2).
|
|
422
|
+
const nativeDepends = remote.edges?.dependsOn ?? true;
|
|
423
|
+
const nativeRelates = remote.edges?.relatesTo ?? false;
|
|
424
|
+
const sameIds = (a, b) => {
|
|
425
|
+
if (a.length !== b.length)
|
|
426
|
+
return false;
|
|
427
|
+
for (let i = 0; i < a.length; i += 1)
|
|
428
|
+
if (a[i] !== b[i])
|
|
429
|
+
return false;
|
|
430
|
+
return true;
|
|
431
|
+
};
|
|
432
|
+
for (const issue of issues) {
|
|
433
|
+
if (!acting(issue.id))
|
|
434
|
+
continue;
|
|
435
|
+
const diff = diffOf.get(issue.id) ?? null;
|
|
436
|
+
// Native link/unlink, for each edge kind the remote holds natively
|
|
437
|
+
// (`depends_on` LP-314, `relates_to` LP-325). A create has no base, so
|
|
438
|
+
// every in-scope edge is a new link; a removal references a twin that
|
|
439
|
+
// must still be linked.
|
|
440
|
+
const nativeKinds = [];
|
|
441
|
+
if (nativeDepends) {
|
|
442
|
+
nativeKinds.push({
|
|
443
|
+
linkKind: 'depends',
|
|
444
|
+
added: diff ? diff.dependsAdded : issue.dependsOn,
|
|
445
|
+
removed: diff ? diff.dependsRemoved : [],
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
if (nativeRelates) {
|
|
449
|
+
nativeKinds.push({
|
|
450
|
+
linkKind: 'relates',
|
|
451
|
+
added: diff ? diff.relatesAdded : issue.relatesTo,
|
|
452
|
+
removed: diff ? diff.relatesRemoved : [],
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
for (const { linkKind, added, removed } of nativeKinds) {
|
|
456
|
+
for (const target of removed) {
|
|
457
|
+
const targetRef = refOf(target);
|
|
458
|
+
if (!targetRef || targetRef.kind !== 'linked')
|
|
459
|
+
continue;
|
|
460
|
+
ops.push({
|
|
461
|
+
kind: 'unlink',
|
|
462
|
+
dependent: { kind: 'linked', localId: issue.id, remoteId: links.links.get(issue.id).remoteId },
|
|
463
|
+
dependency: targetRef,
|
|
464
|
+
linkKind,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
const dependentRef = refOf(issue.id);
|
|
468
|
+
if (dependentRef) {
|
|
469
|
+
for (const target of added) {
|
|
470
|
+
const targetRef = refOf(target);
|
|
471
|
+
if (!targetRef)
|
|
472
|
+
continue;
|
|
473
|
+
ops.push({ kind: 'link', dependent: dependentRef, dependency: targetRef, linkKind });
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// The managed block (LP-314): rewrite the complete edge set when the
|
|
478
|
+
// resolvable set changed. An out-of-scope dependency is not resolvable
|
|
479
|
+
// and never enters the block, exactly as it never enters a native link.
|
|
480
|
+
const currentDepends = nativeDepends
|
|
481
|
+
? []
|
|
482
|
+
: [...issue.dependsOn].filter((id) => refOf(id) !== undefined).sort();
|
|
483
|
+
const currentRelates = nativeRelates
|
|
484
|
+
? []
|
|
485
|
+
: [...issue.relatesTo].filter((id) => refOf(id) !== undefined).sort();
|
|
486
|
+
const base = links.links.get(issue.id)?.base;
|
|
487
|
+
const recordedDepends = nativeDepends ? [] : baseDepends(base ?? {});
|
|
488
|
+
const recordedRelates = nativeRelates ? [] : baseRelates(base ?? {});
|
|
489
|
+
if (sameIds(currentDepends, recordedDepends) && sameIds(currentRelates, recordedRelates)) {
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
const dependentRef = refOf(issue.id);
|
|
493
|
+
if (!dependentRef)
|
|
494
|
+
continue;
|
|
495
|
+
const parent = issue.parentId ? refOf(issue.parentId) : undefined;
|
|
496
|
+
const { blockParent } = parentRefFor(remote, issueDepth(board, issue), parent);
|
|
497
|
+
ops.push({
|
|
498
|
+
kind: 'edges',
|
|
499
|
+
localId: issue.id,
|
|
500
|
+
ref: dependentRef,
|
|
501
|
+
...(blockParent !== undefined ? { parent: blockParent } : {}),
|
|
502
|
+
dependsOn: currentDepends
|
|
503
|
+
.map((id) => refOf(id))
|
|
504
|
+
.filter((r) => r !== undefined),
|
|
505
|
+
relatesTo: currentRelates
|
|
506
|
+
.map((id) => refOf(id))
|
|
507
|
+
.filter((r) => r !== undefined),
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
// -- gone documents: deleted locally, or left the scope subtree -----------
|
|
511
|
+
// A linked twin whose local document no longer exists (removed with `lpm rm`)
|
|
512
|
+
// or that moved out of `scope:` no longer qualifies for a twin. What happens
|
|
513
|
+
// to the remote side is the remote's `on_delete` policy (LP-351):
|
|
514
|
+
//
|
|
515
|
+
// - `unlink` (the default) leaves the twin alone and drops the link — a
|
|
516
|
+
// tombstone for a document that still exists (out of scope), a plain
|
|
517
|
+
// local unlink for one that is gone;
|
|
518
|
+
// - `close` closes the twin and posts a comment saying why;
|
|
519
|
+
// - `delete` deletes the twin (the connector degrades where the platform
|
|
520
|
+
// cannot hard-delete), with confirmation every run from the consent gate.
|
|
521
|
+
//
|
|
522
|
+
// `restore` and `manual` are pull-side answers to the *remote* going away and
|
|
523
|
+
// have no push meaning, so they fall back to `unlink` — a value meant for the
|
|
524
|
+
// opposite direction can never delete or close upstream by surprise.
|
|
525
|
+
const onDelete = remote.onDelete ?? 'unlink';
|
|
526
|
+
const goneLocalIds = [];
|
|
527
|
+
for (const [id] of links.links) {
|
|
528
|
+
if (board.nodes[id] === undefined)
|
|
529
|
+
goneLocalIds.push({ id, deleted: true });
|
|
530
|
+
}
|
|
531
|
+
for (const issue of issues) {
|
|
532
|
+
if (inScope(issue.id))
|
|
533
|
+
continue;
|
|
534
|
+
if (links.links.has(issue.id))
|
|
535
|
+
goneLocalIds.push({ id: issue.id, deleted: false });
|
|
536
|
+
}
|
|
537
|
+
goneLocalIds.sort((a, b) => a.id.localeCompare(b.id));
|
|
538
|
+
for (const { id, deleted } of goneLocalIds) {
|
|
539
|
+
const link = links.links.get(id);
|
|
540
|
+
if (link === undefined)
|
|
541
|
+
continue;
|
|
542
|
+
const ref = { kind: 'linked', localId: id, remoteId: link.remoteId };
|
|
543
|
+
if (onDelete === 'close') {
|
|
544
|
+
ops.push({
|
|
545
|
+
kind: 'close',
|
|
546
|
+
localId: id,
|
|
547
|
+
ref,
|
|
548
|
+
note: pushDeleteNote(id, link.remoteKey, deleted),
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
else if (onDelete === 'delete') {
|
|
552
|
+
ops.push({ kind: 'delete', localId: id, ref });
|
|
553
|
+
}
|
|
554
|
+
else if (deleted) {
|
|
555
|
+
// `unlink` (and the pull-only restore / manual): drop the dangling link.
|
|
556
|
+
ops.push({ kind: 'unlinkLocal', localId: id });
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
ops.push({ kind: 'decouple', localId: id, reason: 'out_of_scope' });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
// -- user comments, after every twin exists (LP-316) ----------------------
|
|
563
|
+
// Post the unsynced work-log entries. A comment references its endpoint the
|
|
564
|
+
// same way an edge does — a `created` placeholder for a document this same
|
|
565
|
+
// push files, a `linked` twin otherwise — so a comment on a brand-new issue
|
|
566
|
+
// is posted once its create lands. `planCommentPush` drops any entry whose
|
|
567
|
+
// index is already recorded (never posted twice) and any body that is a
|
|
568
|
+
// managed block (never re-posted as a user comment).
|
|
569
|
+
if (localComments) {
|
|
570
|
+
for (const issue of issues) {
|
|
571
|
+
if (!acting(issue.id))
|
|
572
|
+
continue;
|
|
573
|
+
const ref = refOf(issue.id);
|
|
574
|
+
if (ref === undefined)
|
|
575
|
+
continue; // decoupled, or outside the scope subtree
|
|
576
|
+
const local = localComments.get(issue.id);
|
|
577
|
+
if (!local || local.length === 0)
|
|
578
|
+
continue;
|
|
579
|
+
for (const item of planCommentPush(local, getSyncedCommentIndexes(links, issue.id))) {
|
|
580
|
+
ops.push({
|
|
581
|
+
kind: 'comment',
|
|
582
|
+
ref,
|
|
583
|
+
index: item.index,
|
|
584
|
+
author: item.author,
|
|
585
|
+
body: renderCommentForRemote(item.author, item.body),
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return { ops, skipped };
|
|
591
|
+
}
|
|
592
|
+
// ---------------------------------------------------------------------------
|
|
593
|
+
// Pull planning
|
|
594
|
+
// ---------------------------------------------------------------------------
|
|
595
|
+
/**
|
|
596
|
+
* The linked descendant of `localId` whose twin would survive a delete of
|
|
597
|
+
* `localId`'s subtree — the child a `delete` must refuse to orphan. A child
|
|
598
|
+
* that is itself being deleted (its twin vanished too, and the policy is
|
|
599
|
+
* `delete`) is not orphaned, so it is skipped. Returns undefined when the
|
|
600
|
+
* delete would orphan nothing.
|
|
601
|
+
*/
|
|
602
|
+
function orphanedLinkedChild(board, links, localId, deleting) {
|
|
603
|
+
const descendants = subtreeIds(board, localId).slice(1);
|
|
604
|
+
for (const id of descendants) {
|
|
605
|
+
if (deleting.has(id))
|
|
606
|
+
continue;
|
|
607
|
+
if (links.links.has(id))
|
|
608
|
+
return id;
|
|
609
|
+
}
|
|
610
|
+
return undefined;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* The field-diff half of a pull for one linked twin that is still present
|
|
614
|
+
* upstream (LP-257 AC #2, built on LP-284).
|
|
615
|
+
*
|
|
616
|
+
* Returns the `update` patch carrying the fields the remote alone changed, and
|
|
617
|
+
* the conflicts for fields both sides changed. Fields the board alone changed
|
|
618
|
+
* are deliberately absent — they are `planPush`'s to carry upstream, never
|
|
619
|
+
* pulled here. A field the base snapshot does not record is skipped, exactly
|
|
620
|
+
* as `diffIssue` skips it on the push side, so a mapping change cannot
|
|
621
|
+
* manufacture a phantom conflict.
|
|
622
|
+
*
|
|
623
|
+
* With no base at all (a twin recorded but never confirmed) every field the
|
|
624
|
+
* remote carries is merged against `NO_BASE`: agreement is `none`, a
|
|
625
|
+
* difference is a conflict — the only honest answer short of clobbering one
|
|
626
|
+
* side.
|
|
627
|
+
*/
|
|
628
|
+
function pullFieldDiff(localId, remoteId, doc, base, patch, normalizeBody) {
|
|
629
|
+
const update = {};
|
|
630
|
+
const conflicts = [];
|
|
631
|
+
/** The base value for a field: `NO_BASE` when there is no snapshot, otherwise the stored value. */
|
|
632
|
+
const baseOf = (field) => (base === undefined ? NO_BASE : base[field]);
|
|
633
|
+
const merge = (field, local, remote, carries, apply) => {
|
|
634
|
+
if (!carries)
|
|
635
|
+
return;
|
|
636
|
+
const baseValue = baseOf(field);
|
|
637
|
+
if (baseValue === undefined)
|
|
638
|
+
return; // base exists but records no such field
|
|
639
|
+
const outcome = mergeValues(local, remote, baseValue);
|
|
640
|
+
if (outcome === 'pull')
|
|
641
|
+
apply(remote);
|
|
642
|
+
else if (outcome === 'conflict')
|
|
643
|
+
conflicts.push({ localId, remoteId, field, local, remote });
|
|
644
|
+
};
|
|
645
|
+
merge('title', doc.title, patch.title, patch.title !== undefined, (value) => {
|
|
646
|
+
update.title = value;
|
|
647
|
+
});
|
|
648
|
+
// The base stores the body as a hash; both sides are hashed so the four-case
|
|
649
|
+
// table compares hash to hash, never raw text. The conflict carries the raw
|
|
650
|
+
// text so a renderer can show what each side wrote.
|
|
651
|
+
if (patch.body !== undefined) {
|
|
652
|
+
const baseValue = baseOf('body');
|
|
653
|
+
if (baseValue !== undefined) {
|
|
654
|
+
const outcome = mergeValues(hashBody(doc.body, normalizeBody), hashBody(patch.body), baseValue);
|
|
655
|
+
if (outcome === 'pull')
|
|
656
|
+
update.body = patch.body;
|
|
657
|
+
else if (outcome === 'conflict') {
|
|
658
|
+
conflicts.push({ localId, remoteId, field: 'body', local: doc.body, remote: patch.body });
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
merge('status', doc.status, patch.status, patch.status !== undefined, (value) => {
|
|
663
|
+
update.status = value;
|
|
664
|
+
});
|
|
665
|
+
merge('assignee', doc.assignee ?? null, patch.assignee ?? null, patch.assignee !== undefined, (value) => {
|
|
666
|
+
update.assignee = value;
|
|
667
|
+
});
|
|
668
|
+
merge('period', doc.period ?? null, patch.period ?? null, patch.period !== undefined, (value) => {
|
|
669
|
+
update.period = value;
|
|
670
|
+
});
|
|
671
|
+
// Attributes: every name the base records, plus any the remote carries, each
|
|
672
|
+
// merged on its own so one attribute's conflict never holds a sibling back.
|
|
673
|
+
const attrNames = new Set();
|
|
674
|
+
for (const key of Object.keys(base ?? {})) {
|
|
675
|
+
if (!BASE_FIELD_KEYS.has(key))
|
|
676
|
+
attrNames.add(key);
|
|
677
|
+
}
|
|
678
|
+
const attrPatch = patch.attributes;
|
|
679
|
+
if (attrPatch !== undefined) {
|
|
680
|
+
for (const key of Object.keys(attrPatch))
|
|
681
|
+
attrNames.add(key);
|
|
682
|
+
}
|
|
683
|
+
for (const name of [...attrNames].sort()) {
|
|
684
|
+
const carries = attrPatch !== undefined && name in attrPatch;
|
|
685
|
+
merge(name, doc.attributes[name] ?? null, carries ? attrPatch[name] : null, carries, (value) => {
|
|
686
|
+
update.attributes = { ...update.attributes, [name]: value };
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
return { update, conflicts };
|
|
690
|
+
}
|
|
691
|
+
/** True when two sorted id lists hold the same members in the same order. */
|
|
692
|
+
function listEquals(a, b) {
|
|
693
|
+
if (a.length !== b.length)
|
|
694
|
+
return false;
|
|
695
|
+
for (let i = 0; i < a.length; i += 1)
|
|
696
|
+
if (a[i] !== b[i])
|
|
697
|
+
return false;
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* The path `start` → … → `target` following `depends_on` edges in `graph`, or
|
|
702
|
+
* null when `target` is unreachable. Used to refuse a pulled edge that would
|
|
703
|
+
* close a cycle: adding `from` depends_on `to` closes a loop exactly when `to`
|
|
704
|
+
* already reaches `from`.
|
|
705
|
+
*/
|
|
706
|
+
function pathFrom(graph, start, target) {
|
|
707
|
+
const previous = new Map();
|
|
708
|
+
const queue = [start];
|
|
709
|
+
const seen = new Set([start]);
|
|
710
|
+
while (queue.length > 0) {
|
|
711
|
+
const node = queue.shift();
|
|
712
|
+
for (const next of graph.get(node) ?? []) {
|
|
713
|
+
if (next === target) {
|
|
714
|
+
const path = [next];
|
|
715
|
+
let cursor = node;
|
|
716
|
+
while (cursor !== start) {
|
|
717
|
+
path.unshift(cursor);
|
|
718
|
+
cursor = previous.get(cursor);
|
|
719
|
+
}
|
|
720
|
+
path.unshift(start);
|
|
721
|
+
return path;
|
|
722
|
+
}
|
|
723
|
+
if (seen.has(next))
|
|
724
|
+
continue;
|
|
725
|
+
seen.add(next);
|
|
726
|
+
previous.set(next, node);
|
|
727
|
+
queue.push(next);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Plan a pull: the `Change[]` needed to bring the board's in-scope issues into
|
|
734
|
+
* agreement with the remote listing, plus the link-store effects that travel
|
|
735
|
+
* with it.
|
|
736
|
+
*
|
|
737
|
+
* The pull is an *existence* reconciliation, plus a field diff. A remote
|
|
738
|
+
* issue with no twin is planned as a `create` — carrying its hierarchy parent
|
|
739
|
+
* and its dependency edges, both referenced by temporary ids when the other
|
|
740
|
+
* endpoint is also new in this pull, so the existing hold-back-and-replay
|
|
741
|
+
* wires them. A linked twin absent from the listing is resolved by the
|
|
742
|
+
* remote's `on_delete` policy (`unlink`, `close`, `delete`, `restore` or
|
|
743
|
+
* `manual`), one document at a time (LP-365). A linked twin still present on
|
|
744
|
+
* both sides is merged field by field against its base snapshot (LP-284): a
|
|
745
|
+
* field the remote alone changed becomes an `update` pulling the remote value
|
|
746
|
+
* onto the board, a field both sides changed is reported in `fieldConflicts`
|
|
747
|
+
* and left untouched on both sides (LP-257 AC #2), and a field the board alone
|
|
748
|
+
* changed is left for the push planner to carry upstream.
|
|
749
|
+
*
|
|
750
|
+
* The plan is applied by `applyChanges` from `src/sync` — the same applier the
|
|
751
|
+
* web app and the CLI push a view through — so the pull inherits partial
|
|
752
|
+
* application, temporary-id remapping and the hold-back-and-replay of a create
|
|
753
|
+
* whose parent is created later in the same push. (Naming note: `PushSession`
|
|
754
|
+
* inside `src/sync` is named for pushing a *view* onto the board, which is the
|
|
755
|
+
* same direction a remote **pull** moves — remote → board.)
|
|
756
|
+
*
|
|
757
|
+
* Returns an empty plan for a `direction: push` remote (it never pulls), for a
|
|
758
|
+
* scope that names no documents, and when the listing agrees with the link
|
|
759
|
+
* store. Pure: no disk, no network.
|
|
760
|
+
*/
|
|
761
|
+
export function planPull(board, links, remote, options) {
|
|
762
|
+
if (remote.direction === 'push')
|
|
763
|
+
return { changes: [], links: [] };
|
|
764
|
+
// An unreachable remote plans nothing: the twin may be deleted or merely
|
|
765
|
+
// hidden by a credential that lost scope, and guessing writes a deletion
|
|
766
|
+
// (LP-364). The caller reports the reason and distinguishes the exit code.
|
|
767
|
+
if (remote.unreachable === true)
|
|
768
|
+
return { changes: [], links: [] };
|
|
769
|
+
const onDelete = remote.onDelete ?? 'unlink';
|
|
770
|
+
const scopeSet = remote.scope ? new Set(subtreeIds(board, remote.scope)) : null;
|
|
771
|
+
const parentOf = options.parentIdOf;
|
|
772
|
+
const dependsOnOf = options.dependsOnOf;
|
|
773
|
+
const relatesToOf = options.relatesToOf;
|
|
774
|
+
const blockEdgesOf = options.blockEdgesOf;
|
|
775
|
+
/** Edge references a managed block carried that could not be resolved (LP-314). */
|
|
776
|
+
const edgeWarnings = [];
|
|
777
|
+
// The local id that anchors a remote issue in the local tree: its own twin
|
|
778
|
+
// when it has one, otherwise the anchor of its remote parent. A remote with
|
|
779
|
+
// no scope pulls everything; a scoped remote pulls only issues whose anchor
|
|
780
|
+
// sits under the scope root. The walk is `anchor.ts` because the drift
|
|
781
|
+
// report asks the same question of the same listing — "where would this land?"
|
|
782
|
+
// — and a report that answered it differently would offer work this planner
|
|
783
|
+
// then declined to adopt.
|
|
784
|
+
const anchorOf = anchorResolver(links, remote.issues, parentOf);
|
|
785
|
+
const inScope = (remoteId) => {
|
|
786
|
+
if (scopeSet === null)
|
|
787
|
+
return true;
|
|
788
|
+
const anchored = anchorOf(remoteId);
|
|
789
|
+
return anchored !== undefined && scopeSet.has(anchored);
|
|
790
|
+
};
|
|
791
|
+
// -- creates: remote issues with no twin ----------------------------------
|
|
792
|
+
const newIssues = [...remote.issues.entries()]
|
|
793
|
+
.filter(([remoteId]) => !links.byRemote.has(remoteId) && inScope(remoteId))
|
|
794
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
795
|
+
// Temporary ids from a fresh counter, one per new remote issue, so a
|
|
796
|
+
// reference to one (a child's parent, a later edge) names the same temp id
|
|
797
|
+
// the create carries. Never derived from a view's pending list.
|
|
798
|
+
const nextTemp = counterFactory();
|
|
799
|
+
const tempIdOf = new Map();
|
|
800
|
+
for (const [remoteId] of newIssues)
|
|
801
|
+
tempIdOf.set(remoteId, nextTemp());
|
|
802
|
+
/** The hierarchy depth of a new issue, from its remote parent chain. */
|
|
803
|
+
const depthMemo = new Map();
|
|
804
|
+
const underParent = options.underParent;
|
|
805
|
+
/** The depth `underParent` puts an otherwise parentless issue at. */
|
|
806
|
+
const adoptedDepth = underParent ? (board.nodes[underParent]?.depth ?? 0) + 1 : 0;
|
|
807
|
+
const depthOf = (remoteId, record) => {
|
|
808
|
+
if (depthMemo.has(remoteId))
|
|
809
|
+
return depthMemo.get(remoteId);
|
|
810
|
+
depthMemo.set(remoteId, 0); // cycle guard
|
|
811
|
+
const parentRemote = parentOf ? parentOf(record) : undefined;
|
|
812
|
+
if (!parentRemote)
|
|
813
|
+
return adoptedDepth;
|
|
814
|
+
const linked = links.byRemote.get(parentRemote);
|
|
815
|
+
if (linked !== undefined) {
|
|
816
|
+
const depth = (board.nodes[linked]?.depth ?? 0) + 1;
|
|
817
|
+
depthMemo.set(remoteId, depth);
|
|
818
|
+
return depth;
|
|
819
|
+
}
|
|
820
|
+
const parentRecord = remote.issues.get(parentRemote);
|
|
821
|
+
if (!parentRecord)
|
|
822
|
+
return adoptedDepth;
|
|
823
|
+
const depth = depthOf(parentRemote, parentRecord) + 1;
|
|
824
|
+
depthMemo.set(remoteId, depth);
|
|
825
|
+
return depth;
|
|
826
|
+
};
|
|
827
|
+
/** The `parentId` a create writes: the parent's temp id, or its twin. */
|
|
828
|
+
const parentRefOf = (record) => {
|
|
829
|
+
const parentRemote = parentOf ? parentOf(record) : undefined;
|
|
830
|
+
if (!parentRemote)
|
|
831
|
+
return underParent ?? null;
|
|
832
|
+
const temp = tempIdOf.get(parentRemote);
|
|
833
|
+
if (temp !== undefined)
|
|
834
|
+
return temp;
|
|
835
|
+
return links.byRemote.get(parentRemote) ?? underParent ?? null;
|
|
836
|
+
};
|
|
837
|
+
/** The `dependsOn` ids a create writes, each resolved to its local document. */
|
|
838
|
+
const dependsRefsOf = (record) => {
|
|
839
|
+
if (!dependsOnOf)
|
|
840
|
+
return [];
|
|
841
|
+
const refs = [];
|
|
842
|
+
for (const remoteId of dependsOnOf(record)) {
|
|
843
|
+
const temp = tempIdOf.get(remoteId);
|
|
844
|
+
if (temp !== undefined)
|
|
845
|
+
refs.push(temp);
|
|
846
|
+
else {
|
|
847
|
+
const linked = links.byRemote.get(remoteId);
|
|
848
|
+
if (linked !== undefined)
|
|
849
|
+
refs.push(linked);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return refs;
|
|
853
|
+
};
|
|
854
|
+
/**
|
|
855
|
+
* Resolve a list of same-repo remote ids to local ids: the dependency's
|
|
856
|
+
* temporary id when it is also new in this pull, its twin otherwise.
|
|
857
|
+
* A remote id with neither is dropped — there is no local document for the
|
|
858
|
+
* edge to point at.
|
|
859
|
+
*/
|
|
860
|
+
const resolveRemoteIds = (remoteIds) => {
|
|
861
|
+
const refs = [];
|
|
862
|
+
for (const remoteId of remoteIds) {
|
|
863
|
+
const temp = tempIdOf.get(remoteId);
|
|
864
|
+
if (temp !== undefined)
|
|
865
|
+
refs.push(temp);
|
|
866
|
+
else {
|
|
867
|
+
const linked = links.byRemote.get(remoteId);
|
|
868
|
+
if (linked !== undefined)
|
|
869
|
+
refs.push(linked);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return refs;
|
|
873
|
+
};
|
|
874
|
+
/** The `relatesTo` ids a create writes, resolved exactly like `dependsRefsOf`. */
|
|
875
|
+
const relatesRefsOf = (record) => {
|
|
876
|
+
if (!relatesToOf)
|
|
877
|
+
return [];
|
|
878
|
+
return resolveRemoteIds(relatesToOf(record));
|
|
879
|
+
};
|
|
880
|
+
/** Temp id → the remote id that created it, for naming a cycle legibly. */
|
|
881
|
+
const tempRemoteOf = new Map();
|
|
882
|
+
for (const [remoteId, temp] of tempIdOf)
|
|
883
|
+
tempRemoteOf.set(temp, remoteId);
|
|
884
|
+
/** A local id named as the remote id it came from, or itself when it has none. */
|
|
885
|
+
const labelOf = (id) => tempRemoteOf.get(id) ?? links.links.get(id)?.remoteId ?? id;
|
|
886
|
+
// The prospective dependency graph: the board's existing edges plus every
|
|
887
|
+
// edge the pull decides to write. A pulled dependency that would close a
|
|
888
|
+
// cycle is refused here and named, rather than handed to `applyChanges` —
|
|
889
|
+
// which would reject the whole change, not the one edge (LP-325 AC #5).
|
|
890
|
+
const prospective = new Map();
|
|
891
|
+
for (const node of Object.values(board.nodes)) {
|
|
892
|
+
if (node.kind === 'issue' || node.kind === 'template') {
|
|
893
|
+
prospective.set(node.id, [...node.dependsOn]);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
/** The cycle adding `from` depends_on `to` would close, or null when safe.
|
|
897
|
+
* Returned as a closed loop (`[from, …, from]`), ready to join for naming. */
|
|
898
|
+
const cycleIfLinked = (from, to) => {
|
|
899
|
+
if (from === to)
|
|
900
|
+
return [from, from];
|
|
901
|
+
const path = pathFrom(prospective, to, from);
|
|
902
|
+
return path === null ? null : [from, ...path];
|
|
903
|
+
};
|
|
904
|
+
const changes = [];
|
|
905
|
+
const linkOps = [];
|
|
906
|
+
// The type a create falls back to when nothing on the remote says otherwise
|
|
907
|
+
// (LP-315): the deepest declared issue type — `hierarchy.flat().at(-1)`, the
|
|
908
|
+
// same "deepest declared level" the period mapping uses for its container.
|
|
909
|
+
// A triaged GitHub issue with no mapped label still lands as the board's most
|
|
910
|
+
// specific type, never a document with no type (which `applyChanges` refuses).
|
|
911
|
+
const deepestType = (board.config.hierarchy.issue ?? []).flat().at(-1);
|
|
912
|
+
for (const [remoteId, record] of newIssues) {
|
|
913
|
+
const temp = tempIdOf.get(remoteId);
|
|
914
|
+
const fields = options.toPatch(record, depthOf(remoteId, record));
|
|
915
|
+
const patch = {};
|
|
916
|
+
if (fields.type !== undefined)
|
|
917
|
+
patch.type = fields.type;
|
|
918
|
+
else if (deepestType !== undefined)
|
|
919
|
+
patch.type = deepestType;
|
|
920
|
+
if (fields.title !== undefined)
|
|
921
|
+
patch.title = fields.title;
|
|
922
|
+
if (fields.body !== undefined)
|
|
923
|
+
patch.body = fields.body;
|
|
924
|
+
if (fields.status !== undefined)
|
|
925
|
+
patch.status = fields.status;
|
|
926
|
+
if (fields.assignee !== undefined)
|
|
927
|
+
patch.assignee = fields.assignee;
|
|
928
|
+
if (fields.period !== undefined)
|
|
929
|
+
patch.period = fields.period;
|
|
930
|
+
if (fields.attributes !== undefined)
|
|
931
|
+
patch.attributes = fields.attributes;
|
|
932
|
+
const parentRef = parentRefOf(record);
|
|
933
|
+
if (parentRef !== null)
|
|
934
|
+
patch.parentId = parentRef;
|
|
935
|
+
const dependsRefs = dependsRefsOf(record);
|
|
936
|
+
const relatesRefs = relatesRefsOf(record);
|
|
937
|
+
// Managed-block edges (LP-314): a provider with no native edge reads the
|
|
938
|
+
// `depends_on` / `relates_to` rows back out of the block. Same-repo
|
|
939
|
+
// references resolve through the link store (or the pending-create map)
|
|
940
|
+
// exactly like a native edge; cross-repo and unknown references are
|
|
941
|
+
// reported in `edgeWarnings`, never resolved.
|
|
942
|
+
const blockEdges = blockEdgesOf ? blockEdgesOf(record) : undefined;
|
|
943
|
+
if (blockEdges !== undefined) {
|
|
944
|
+
dependsRefs.push(...resolveRemoteIds(blockEdges.dependsOn));
|
|
945
|
+
relatesRefs.push(...resolveRemoteIds(blockEdges.relatesTo));
|
|
946
|
+
for (const warning of blockEdges.crossRepo) {
|
|
947
|
+
edgeWarnings.push({ remoteId, ...warning, kind: 'cross_repo' });
|
|
948
|
+
}
|
|
949
|
+
for (const warning of blockEdges.unknown) {
|
|
950
|
+
edgeWarnings.push({ remoteId, ...warning, kind: 'unknown' });
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
// A pulled dependency that would close a cycle is refused and the cycle
|
|
954
|
+
// named, the rest of the pull continuing (LP-325 AC #5). Only the gating
|
|
955
|
+
// `depends_on` edge is checked — a loop of `relates_to` is not a stall.
|
|
956
|
+
const safeDepends = [];
|
|
957
|
+
for (const dependency of [...new Set(dependsRefs)].sort()) {
|
|
958
|
+
const cycle = cycleIfLinked(temp, dependency);
|
|
959
|
+
if (cycle !== null) {
|
|
960
|
+
edgeWarnings.push({
|
|
961
|
+
remoteId,
|
|
962
|
+
field: 'depends_on',
|
|
963
|
+
reference: labelOf(dependency),
|
|
964
|
+
kind: 'cycle',
|
|
965
|
+
cycle: cycle.map(labelOf).join(' -> '),
|
|
966
|
+
});
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
safeDepends.push(dependency);
|
|
970
|
+
}
|
|
971
|
+
if (safeDepends.length > 0)
|
|
972
|
+
patch.dependsOn = safeDepends;
|
|
973
|
+
if (relatesRefs.length > 0)
|
|
974
|
+
patch.relatesTo = [...new Set(relatesRefs)].sort();
|
|
975
|
+
// Record the create's edges so a later edge is checked against them.
|
|
976
|
+
prospective.set(temp, safeDepends);
|
|
977
|
+
changes.push({ kind: 'create', id: temp, nodeKind: 'issue', patch });
|
|
978
|
+
linkOps.push({ kind: 'record', tempId: temp, remoteId });
|
|
979
|
+
}
|
|
980
|
+
// -- gone twins: linked, in scope, and absent from the listing -------------
|
|
981
|
+
// A partial listing proves nothing about absence — see `partial` on the
|
|
982
|
+
// snapshot — so a targeted pull reconciles no existence at all.
|
|
983
|
+
const goneTwins = remote.partial === true
|
|
984
|
+
? []
|
|
985
|
+
: [...links.links.entries()]
|
|
986
|
+
.filter(([localId, entry]) => (scopeSet === null || scopeSet.has(localId)) && !remote.issues.has(entry.remoteId))
|
|
987
|
+
.map(([localId]) => localId)
|
|
988
|
+
.sort();
|
|
989
|
+
const restore = [];
|
|
990
|
+
const conflicts = [];
|
|
991
|
+
// `delete` removes the document *and its subtree* — the ordinary `remove`
|
|
992
|
+
// operation — so a linked descendant whose twin still exists would be
|
|
993
|
+
// deleted locally while its twin survives, an orphan the board would only
|
|
994
|
+
// notice on the next pull. Refuse the delete and name the child; the
|
|
995
|
+
// document is left for a human rather than silently reshaped (LP-365).
|
|
996
|
+
const deleting = onDelete === 'delete' ? new Set(goneTwins) : new Set();
|
|
997
|
+
const refused = new Set();
|
|
998
|
+
if (onDelete === 'delete') {
|
|
999
|
+
for (const localId of goneTwins) {
|
|
1000
|
+
const child = orphanedLinkedChild(board, links, localId, deleting);
|
|
1001
|
+
if (child !== undefined) {
|
|
1002
|
+
refused.add(localId);
|
|
1003
|
+
conflicts.push({
|
|
1004
|
+
localId,
|
|
1005
|
+
remoteId: links.links.get(localId).remoteId,
|
|
1006
|
+
reason: `delete refused — would orphan linked child ${child}`,
|
|
1007
|
+
childId: child,
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
// `restore` re-files the vanished twin upstream: a re-create with no remote
|
|
1013
|
+
// id, referenced by a placeholder the push executor resolves, after which the
|
|
1014
|
+
// link is repointed at the new id and the base rewritten from the response.
|
|
1015
|
+
// Parents are restored before children, exactly as a push creates them.
|
|
1016
|
+
if (onDelete === 'restore') {
|
|
1017
|
+
const toRestore = goneTwins
|
|
1018
|
+
.filter((localId) => board.nodes[localId]?.kind === 'issue')
|
|
1019
|
+
.sort((a, b) => (board.nodes[a]?.depth ?? 0) - (board.nodes[b]?.depth ?? 0) || a.localeCompare(b));
|
|
1020
|
+
const placeholderOf = new Map();
|
|
1021
|
+
toRestore.forEach((localId, index) => placeholderOf.set(localId, tempId(index + 1)));
|
|
1022
|
+
const refOf = (id) => {
|
|
1023
|
+
const placeholder = placeholderOf.get(id);
|
|
1024
|
+
if (placeholder !== undefined)
|
|
1025
|
+
return { kind: 'created', localId: id, placeholder };
|
|
1026
|
+
const link = links.links.get(id);
|
|
1027
|
+
if (link && (scopeSet === null || scopeSet.has(id))) {
|
|
1028
|
+
return { kind: 'linked', localId: id, remoteId: link.remoteId };
|
|
1029
|
+
}
|
|
1030
|
+
return undefined;
|
|
1031
|
+
};
|
|
1032
|
+
for (const localId of toRestore) {
|
|
1033
|
+
const issue = board.nodes[localId];
|
|
1034
|
+
const parent = issue.parentId ? refOf(issue.parentId) : undefined;
|
|
1035
|
+
restore.push({
|
|
1036
|
+
kind: 'restore',
|
|
1037
|
+
placeholder: placeholderOf.get(localId),
|
|
1038
|
+
localId,
|
|
1039
|
+
oldRemoteId: links.links.get(localId).remoteId,
|
|
1040
|
+
fields: boardFields(issue),
|
|
1041
|
+
...parentRefFor(remote, issueDepth(board, issue), parent),
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
for (const localId of goneTwins) {
|
|
1046
|
+
switch (onDelete) {
|
|
1047
|
+
case 'delete': {
|
|
1048
|
+
if (refused.has(localId))
|
|
1049
|
+
continue; // left for a human, above
|
|
1050
|
+
changes.push({ kind: 'delete', id: localId, nodeKind: 'issue' });
|
|
1051
|
+
// `applyPull` skips the unlink when the `delete` change failed, so the
|
|
1052
|
+
// next pull retries the deletion rather than silently dropping it.
|
|
1053
|
+
linkOps.push({ kind: 'unlink', localId });
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
1056
|
+
case 'close': {
|
|
1057
|
+
// Follow the remote by closing the local document. The status used is
|
|
1058
|
+
// the one the board declares terminal — never a hard-coded name — and
|
|
1059
|
+
// the document is decoupled so a later push does not re-file a closed
|
|
1060
|
+
// document upstream as a brand-new issue.
|
|
1061
|
+
const terminal = board.config.statuses.find((status) => status.terminal)?.id;
|
|
1062
|
+
if (terminal !== undefined) {
|
|
1063
|
+
changes.push({
|
|
1064
|
+
kind: 'update',
|
|
1065
|
+
id: localId,
|
|
1066
|
+
nodeKind: 'issue',
|
|
1067
|
+
patch: { status: terminal },
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
linkOps.push({ kind: 'decouple', localId, reason: 'remote_deleted' });
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
case 'unlink': {
|
|
1074
|
+
// Decouple: the document is untouched, the link is dropped, and a
|
|
1075
|
+
// tombstone records the decision so the next push does not re-file it
|
|
1076
|
+
// (LP-366's tombstone, LP-365's resolution).
|
|
1077
|
+
linkOps.push({ kind: 'decouple', localId, reason: 'remote_deleted' });
|
|
1078
|
+
break;
|
|
1079
|
+
}
|
|
1080
|
+
case 'restore':
|
|
1081
|
+
// Handled above: the restore op repoints the link when it lands.
|
|
1082
|
+
break;
|
|
1083
|
+
case 'manual': {
|
|
1084
|
+
conflicts.push({
|
|
1085
|
+
localId,
|
|
1086
|
+
remoteId: links.links.get(localId).remoteId,
|
|
1087
|
+
reason: 'on_delete: manual — the twin is gone and a human must choose',
|
|
1088
|
+
});
|
|
1089
|
+
break;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
// -- field diffs: linked twins still present on both sides -----------------
|
|
1094
|
+
// A linked twin still in the listing is merged field by field against its
|
|
1095
|
+
// base snapshot (LP-284). A field the remote alone changed is pulled onto
|
|
1096
|
+
// the board as an `update`; a field both sides changed is a conflict, left
|
|
1097
|
+
// for a human (LP-257 AC #2) — neither side's value is written for it.
|
|
1098
|
+
const fieldConflicts = [];
|
|
1099
|
+
for (const [localId, link] of [...links.links.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
1100
|
+
if (scopeSet !== null && !scopeSet.has(localId))
|
|
1101
|
+
continue;
|
|
1102
|
+
const record = remote.issues.get(link.remoteId);
|
|
1103
|
+
if (record === undefined)
|
|
1104
|
+
continue; // gone twin — the on_delete section handled it
|
|
1105
|
+
const doc = board.nodes[localId];
|
|
1106
|
+
if (!doc || doc.kind !== 'issue')
|
|
1107
|
+
continue;
|
|
1108
|
+
const { update, conflicts: mergedConflicts } = pullFieldDiff(localId, link.remoteId, doc, link.base, options.toPatch(record, doc.depth), remote.normalizeBody);
|
|
1109
|
+
fieldConflicts.push(...mergedConflicts);
|
|
1110
|
+
// -- native edges (LP-325) ----------------------------------------------
|
|
1111
|
+
// An edge changed on the remote alone is pulled onto the board: an edge the
|
|
1112
|
+
// remote removed since the base is removed locally, an edge the remote
|
|
1113
|
+
// added is added — `depends_on` cycle-checked, `relates_to` never (a loop
|
|
1114
|
+
// of associations is not a stall). An edge the board alone changed is left
|
|
1115
|
+
// for the push planner, exactly as a scalar field is.
|
|
1116
|
+
if (dependsOnOf || relatesToOf) {
|
|
1117
|
+
const remoteDepends = dependsOnOf ? new Set(resolveRemoteIds(dependsOnOf(record))) : new Set();
|
|
1118
|
+
const remoteRelates = relatesToOf ? new Set(resolveRemoteIds(relatesToOf(record))) : new Set();
|
|
1119
|
+
const baseDep = new Set(baseDepends(link.base ?? {}));
|
|
1120
|
+
const baseRel = new Set(baseRelates(link.base ?? {}));
|
|
1121
|
+
const nextDepends = new Set(doc.dependsOn);
|
|
1122
|
+
const nextRelates = new Set(doc.relatesTo);
|
|
1123
|
+
for (const id of baseDep)
|
|
1124
|
+
if (!remoteDepends.has(id))
|
|
1125
|
+
nextDepends.delete(id);
|
|
1126
|
+
for (const id of baseRel)
|
|
1127
|
+
if (!remoteRelates.has(id))
|
|
1128
|
+
nextRelates.delete(id);
|
|
1129
|
+
for (const id of remoteDepends) {
|
|
1130
|
+
if (baseDep.has(id) || nextDepends.has(id))
|
|
1131
|
+
continue;
|
|
1132
|
+
const cycle = cycleIfLinked(localId, id);
|
|
1133
|
+
if (cycle !== null) {
|
|
1134
|
+
edgeWarnings.push({
|
|
1135
|
+
remoteId: link.remoteId,
|
|
1136
|
+
field: 'depends_on',
|
|
1137
|
+
reference: labelOf(id),
|
|
1138
|
+
kind: 'cycle',
|
|
1139
|
+
cycle: cycle.map(labelOf).join(' -> '),
|
|
1140
|
+
});
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
nextDepends.add(id);
|
|
1144
|
+
}
|
|
1145
|
+
for (const id of remoteRelates) {
|
|
1146
|
+
if (baseRel.has(id) || nextRelates.has(id))
|
|
1147
|
+
continue;
|
|
1148
|
+
nextRelates.add(id);
|
|
1149
|
+
}
|
|
1150
|
+
const sortedDepends = [...nextDepends].sort();
|
|
1151
|
+
const sortedRelates = [...nextRelates].sort();
|
|
1152
|
+
if (!listEquals(sortedDepends, [...doc.dependsOn].sort()))
|
|
1153
|
+
update.dependsOn = sortedDepends;
|
|
1154
|
+
if (!listEquals(sortedRelates, [...doc.relatesTo].sort()))
|
|
1155
|
+
update.relatesTo = sortedRelates;
|
|
1156
|
+
// The agreed edges become the graph a later twin's edge is checked
|
|
1157
|
+
// against, so a cycle spanning two linked twins is still caught.
|
|
1158
|
+
prospective.set(localId, sortedDepends);
|
|
1159
|
+
}
|
|
1160
|
+
if (Object.keys(update).length > 0) {
|
|
1161
|
+
changes.push({ kind: 'update', id: localId, nodeKind: 'issue', patch: update });
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
// -- user comments (LP-316) -------------------------------------------------
|
|
1165
|
+
// Opt-in (`comments: both`): append remote comments to `_comments.md` for
|
|
1166
|
+
// every remote issue in scope, new and existing alike. A comment already
|
|
1167
|
+
// recorded in the link store is skipped, and the managed comment is excluded
|
|
1168
|
+
// by id (where the store records it) and by body (where the id was lost).
|
|
1169
|
+
// The ops carry a temp id for a new issue, resolved by `applyPull` against
|
|
1170
|
+
// `idMap` exactly like a `record` link op.
|
|
1171
|
+
const comments = [];
|
|
1172
|
+
if (remote.commentsMode === 'both' && remote.remoteComments) {
|
|
1173
|
+
const remoteComments = remote.remoteComments;
|
|
1174
|
+
const appendFor = (remoteId, targetId) => {
|
|
1175
|
+
const list = remoteComments.get(remoteId);
|
|
1176
|
+
if (!list || list.length === 0)
|
|
1177
|
+
return;
|
|
1178
|
+
const synced = getSyncedRemoteCommentIds(links, targetId);
|
|
1179
|
+
const toAppend = planCommentPull(list, synced, (id, body) => isManagedComment(links, targetId, id, body));
|
|
1180
|
+
for (const comment of toAppend) {
|
|
1181
|
+
comments.push({
|
|
1182
|
+
kind: 'append',
|
|
1183
|
+
targetId,
|
|
1184
|
+
remoteId: comment.id,
|
|
1185
|
+
author: comment.author,
|
|
1186
|
+
at: comment.createdAt,
|
|
1187
|
+
body: comment.body,
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
for (const [remoteId] of newIssues) {
|
|
1192
|
+
appendFor(remoteId, tempIdOf.get(remoteId));
|
|
1193
|
+
}
|
|
1194
|
+
for (const [localId, link] of [...links.links.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
1195
|
+
if (scopeSet !== null && !scopeSet.has(localId))
|
|
1196
|
+
continue;
|
|
1197
|
+
appendFor(link.remoteId, localId);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return {
|
|
1201
|
+
changes,
|
|
1202
|
+
links: linkOps,
|
|
1203
|
+
...(comments.length > 0 ? { comments } : {}),
|
|
1204
|
+
...(restore.length > 0 ? { restore } : {}),
|
|
1205
|
+
...(conflicts.length > 0 ? { conflicts } : {}),
|
|
1206
|
+
...(fieldConflicts.length > 0 ? { fieldConflicts } : {}),
|
|
1207
|
+
...(edgeWarnings.length > 0 ? { edgeWarnings } : {}),
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
//# sourceMappingURL=plan.js.map
|