@tacuchi/agent-workflow-cli 25.7.0 → 25.9.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/CHANGELOG.md +110 -0
- package/README.md +5 -1
- package/dist/adapters/git-cli.js +311 -24
- package/dist/adapters/git-cli.js.map +1 -1
- package/dist/adapters/node-file-system.js +48 -0
- package/dist/adapters/node-file-system.js.map +1 -1
- package/dist/application/amend-service.js +51 -14
- package/dist/application/amend-service.js.map +1 -1
- package/dist/application/branch-resolver.js +6 -3
- package/dist/application/branch-resolver.js.map +1 -1
- package/dist/application/capability/design-handler.js +124 -43
- package/dist/application/capability/design-handler.js.map +1 -1
- package/dist/application/capability/dispatcher.js +4 -1
- package/dist/application/capability/dispatcher.js.map +1 -1
- package/dist/application/capability/durable-effect.js +3 -0
- package/dist/application/capability/durable-effect.js.map +1 -1
- package/dist/application/capability/readiness.js +26 -10
- package/dist/application/capability/readiness.js.map +1 -1
- package/dist/application/capability/wrapper.js +10 -3
- package/dist/application/capability/wrapper.js.map +1 -1
- package/dist/application/check-branch-service.js +158 -19
- package/dist/application/check-branch-service.js.map +1 -1
- package/dist/application/checkpoint/markdown.js +2 -2
- package/dist/application/checkpoint/markdown.js.map +1 -1
- package/dist/application/checkpoint/state-reader.js +12 -0
- package/dist/application/checkpoint/state-reader.js.map +1 -1
- package/dist/application/checkpoint-service.js +12 -4
- package/dist/application/checkpoint-service.js.map +1 -1
- package/dist/application/checkpoint-write-service.js +91 -17
- package/dist/application/checkpoint-write-service.js.map +1 -1
- package/dist/application/claims-ledger.js +137 -11
- package/dist/application/claims-ledger.js.map +1 -1
- package/dist/application/claims-recovery.js +85 -44
- package/dist/application/claims-recovery.js.map +1 -1
- package/dist/application/database-tool-catalog.js +33 -0
- package/dist/application/database-tool-catalog.js.map +1 -1
- package/dist/application/design/consumer-document.js +2 -2
- package/dist/application/design/consumer-document.js.map +1 -1
- package/dist/application/design/design-content-gate-service.js +20 -2
- package/dist/application/design/design-content-gate-service.js.map +1 -1
- package/dist/application/design/design-gate-service.js +2 -0
- package/dist/application/design/design-gate-service.js.map +1 -1
- package/dist/application/design/design-index-service.js +4 -0
- package/dist/application/design/design-index-service.js.map +1 -1
- package/dist/application/design/design-publish-service.js +1 -4
- package/dist/application/design/design-publish-service.js.map +1 -1
- package/dist/application/design/design-record-service.js +13 -7
- package/dist/application/design/design-record-service.js.map +1 -1
- package/dist/application/design/design-resolver-service.js +22 -3
- package/dist/application/design/design-resolver-service.js.map +1 -1
- package/dist/application/design/design-simple-service.js +24 -5
- package/dist/application/design/design-simple-service.js.map +1 -1
- package/dist/application/design/digest.js +41 -0
- package/dist/application/design/digest.js.map +1 -0
- package/dist/application/dev-only-services.js +171 -27
- package/dist/application/dev-only-services.js.map +1 -1
- package/dist/application/doc-branch-ledger.js +145 -0
- package/dist/application/doc-branch-ledger.js.map +1 -0
- package/dist/application/docs-canon-service.js +1 -1
- package/dist/application/docs-canon-service.js.map +1 -1
- package/dist/application/doctor/dist-integrity.js +113 -0
- package/dist/application/doctor/dist-integrity.js.map +1 -0
- package/dist/application/doctor/hosts.js +5 -2
- package/dist/application/doctor/hosts.js.map +1 -1
- package/dist/application/doctor/prepare.js +4 -1
- package/dist/application/doctor/prepare.js.map +1 -1
- package/dist/application/doctor/provider-installation.js +39 -19
- package/dist/application/doctor/provider-installation.js.map +1 -1
- package/dist/application/doctor/provider-mcps.js +10 -2
- package/dist/application/doctor/provider-mcps.js.map +1 -1
- package/dist/application/doctor/provider-skills.js +25 -0
- package/dist/application/doctor/provider-skills.js.map +1 -1
- package/dist/application/doctor/provider-visibility.js +76 -3
- package/dist/application/doctor/provider-visibility.js.map +1 -1
- package/dist/application/doctor/provider-workspace-block.js +156 -0
- package/dist/application/doctor/provider-workspace-block.js.map +1 -0
- package/dist/application/doctor/repair-runner.js +47 -3
- package/dist/application/doctor/repair-runner.js.map +1 -1
- package/dist/application/doctor/report.js +24 -4
- package/dist/application/doctor/report.js.map +1 -1
- package/dist/application/doctor/skills-toml-migrate.js +76 -0
- package/dist/application/doctor/skills-toml-migrate.js.map +1 -0
- package/dist/application/export-catalog-check.js +200 -0
- package/dist/application/export-catalog-check.js.map +1 -0
- package/dist/application/export-service.js +716 -79
- package/dist/application/export-service.js.map +1 -1
- package/dist/application/fix-git-service.js +285 -64
- package/dist/application/fix-git-service.js.map +1 -1
- package/dist/application/flow/advance.js +241 -30
- package/dist/application/flow/advance.js.map +1 -1
- package/dist/application/flow/annul-service.js +3 -1
- package/dist/application/flow/annul-service.js.map +1 -1
- package/dist/application/flow/batch-commit.js +189 -0
- package/dist/application/flow/batch-commit.js.map +1 -0
- package/dist/application/flow/checkout-observation.js +110 -8
- package/dist/application/flow/checkout-observation.js.map +1 -1
- package/dist/application/flow/close-artifacts.js +105 -0
- package/dist/application/flow/close-artifacts.js.map +1 -0
- package/dist/application/flow/close-at-boundary.js +155 -0
- package/dist/application/flow/close-at-boundary.js.map +1 -0
- package/dist/application/flow/close-document-guidance.js +70 -0
- package/dist/application/flow/close-document-guidance.js.map +1 -0
- package/dist/application/flow/flow-service.js +49 -5
- package/dist/application/flow/flow-service.js.map +1 -1
- package/dist/application/flow/internal-actions.js +361 -80
- package/dist/application/flow/internal-actions.js.map +1 -1
- package/dist/application/flow/internal-drive.js +47 -19
- package/dist/application/flow/internal-drive.js.map +1 -1
- package/dist/application/flow/plan-entry.js +82 -0
- package/dist/application/flow/plan-entry.js.map +1 -0
- package/dist/application/flow/prove.js +32 -1
- package/dist/application/flow/prove.js.map +1 -1
- package/dist/application/flow/quick-checkouts.js +48 -0
- package/dist/application/flow/quick-checkouts.js.map +1 -0
- package/dist/application/flow/reinfer-batch.js +177 -0
- package/dist/application/flow/reinfer-batch.js.map +1 -0
- package/dist/application/flow/reopen-run.js +159 -0
- package/dist/application/flow/reopen-run.js.map +1 -0
- package/dist/application/flow/run-journey.js +11 -8
- package/dist/application/flow/run-journey.js.map +1 -1
- package/dist/application/flow/run-projection.js +16 -5
- package/dist/application/flow/run-projection.js.map +1 -1
- package/dist/application/flow/run-state-service.js +3 -1
- package/dist/application/flow/run-state-service.js.map +1 -1
- package/dist/application/flow/submit.js +731 -154
- package/dist/application/flow/submit.js.map +1 -1
- package/dist/application/generate-launch-service.js +16 -6
- package/dist/application/generate-launch-service.js.map +1 -1
- package/dist/application/git-flow-service.js +34 -3
- package/dist/application/git-flow-service.js.map +1 -1
- package/dist/application/history-publications.js +24 -0
- package/dist/application/history-publications.js.map +1 -1
- package/dist/application/history-reconcile-service.js +45 -0
- package/dist/application/history-reconcile-service.js.map +1 -0
- package/dist/application/history-table.js +61 -12
- package/dist/application/history-table.js.map +1 -1
- package/dist/application/history-update-service.js +25 -4
- package/dist/application/history-update-service.js.map +1 -1
- package/dist/application/hook-branch-check.js +25 -3
- package/dist/application/hook-branch-check.js.map +1 -1
- package/dist/application/hook-git-commit-advisor.js +3 -4
- package/dist/application/hook-git-commit-advisor.js.map +1 -1
- package/dist/application/hook-sql-mutation-guard.js +147 -12
- package/dist/application/hook-sql-mutation-guard.js.map +1 -1
- package/dist/application/hook-turn-start.js +51 -0
- package/dist/application/hook-turn-start.js.map +1 -0
- package/dist/application/host-memory/codex.js +1 -1
- package/dist/application/host-memory/codex.js.map +1 -1
- package/dist/application/hub-registry.js +135 -0
- package/dist/application/hub-registry.js.map +1 -0
- package/dist/application/local-proposal.js +72 -19
- package/dist/application/local-proposal.js.map +1 -1
- package/dist/application/mcp-host-reader.js +1 -1
- package/dist/application/mcp-host-reader.js.map +1 -1
- package/dist/application/mcp-host-writer.js +16 -10
- package/dist/application/mcp-host-writer.js.map +1 -1
- package/dist/application/merge-state-service.js +150 -34
- package/dist/application/merge-state-service.js.map +1 -1
- package/dist/application/multiroot/claude.js +17 -10
- package/dist/application/multiroot/claude.js.map +1 -1
- package/dist/application/multiroot/codex.js +17 -11
- package/dist/application/multiroot/codex.js.map +1 -1
- package/dist/application/multiroot-service.js +52 -28
- package/dist/application/multiroot-service.js.map +1 -1
- package/dist/application/parsers/execution-batches.js +64 -0
- package/dist/application/parsers/execution-batches.js.map +1 -0
- package/dist/application/parsers/phases.js +23 -2
- package/dist/application/parsers/phases.js.map +1 -1
- package/dist/application/parsers/project-block.js +146 -17
- package/dist/application/parsers/project-block.js.map +1 -1
- package/dist/application/parsers/tasks.js +16 -2
- package/dist/application/parsers/tasks.js.map +1 -1
- package/dist/application/parsers/toml.js +19 -0
- package/dist/application/parsers/toml.js.map +1 -0
- package/dist/application/paths-service.js +13 -16
- package/dist/application/paths-service.js.map +1 -1
- package/dist/application/persist-service.js +118 -5
- package/dist/application/persist-service.js.map +1 -1
- package/dist/application/plan-exec-batch-service.js +162 -22
- package/dist/application/plan-exec-batch-service.js.map +1 -1
- package/dist/application/plan-exec-decision-service.js +61 -12
- package/dist/application/plan-exec-decision-service.js.map +1 -1
- package/dist/application/plan-exec-plan-diff.js +105 -0
- package/dist/application/plan-exec-plan-diff.js.map +1 -0
- package/dist/application/plan-lint-service.js +2 -0
- package/dist/application/plan-lint-service.js.map +1 -1
- package/dist/application/plan-passes.js +41 -0
- package/dist/application/plan-passes.js.map +1 -0
- package/dist/application/preparation-receipts.js +58 -0
- package/dist/application/preparation-receipts.js.map +1 -0
- package/dist/application/project-md-upsert-service.js +191 -52
- package/dist/application/project-md-upsert-service.js.map +1 -1
- package/dist/application/project-tab-data.js +40 -10
- package/dist/application/project-tab-data.js.map +1 -1
- package/dist/application/release-data/artifacts.js +15 -7
- package/dist/application/release-data/artifacts.js.map +1 -1
- package/dist/application/release-data/bundles.js +52 -6
- package/dist/application/release-data/bundles.js.map +1 -1
- package/dist/application/release-data/common.js +2 -2
- package/dist/application/release-data/common.js.map +1 -1
- package/dist/application/release-data/rollback.js +7 -0
- package/dist/application/release-data/rollback.js.map +1 -0
- package/dist/application/release-data/sessions.js +2 -2
- package/dist/application/release-data/sessions.js.map +1 -1
- package/dist/application/release-pass-ledger.js +1 -1
- package/dist/application/render/history-row.js +2 -0
- package/dist/application/render/history-row.js.map +1 -1
- package/dist/application/render/project-block.js +28 -11
- package/dist/application/render/project-block.js.map +1 -1
- package/dist/application/reseal-service.js +1 -0
- package/dist/application/reseal-service.js.map +1 -1
- package/dist/application/resume-service.js +55 -8
- package/dist/application/resume-service.js.map +1 -1
- package/dist/application/retirement/apply.js +25 -7
- package/dist/application/retirement/apply.js.map +1 -1
- package/dist/application/retirement/attribution.js +121 -48
- package/dist/application/retirement/attribution.js.map +1 -1
- package/dist/application/retirement/prepare.js +17 -11
- package/dist/application/retirement/prepare.js.map +1 -1
- package/dist/application/retirement/preview.js +2 -1
- package/dist/application/retirement/preview.js.map +1 -1
- package/dist/application/self/codex-plugin.js +1 -1
- package/dist/application/self/codex-plugin.js.map +1 -1
- package/dist/application/self/host-states.js +3 -3
- package/dist/application/self/host-states.js.map +1 -1
- package/dist/application/self/install-hooks.js +1 -1
- package/dist/application/self/install-hooks.js.map +1 -1
- package/dist/application/self/install-skill.js +35 -11
- package/dist/application/self/install-skill.js.map +1 -1
- package/dist/application/self/install-targets.js +73 -2
- package/dist/application/self/install-targets.js.map +1 -1
- package/dist/application/self/mcp-config.js +19 -11
- package/dist/application/self/mcp-config.js.map +1 -1
- package/dist/application/self/opencode-plugin.js +1 -0
- package/dist/application/self/opencode-plugin.js.map +1 -1
- package/dist/application/self/uninstall.js +22 -8
- package/dist/application/self/uninstall.js.map +1 -1
- package/dist/application/semantic-operation/protocol.js +66 -5
- package/dist/application/semantic-operation/protocol.js.map +1 -1
- package/dist/application/session-archive-service.js +113 -0
- package/dist/application/session-archive-service.js.map +1 -0
- package/dist/application/session-binding-service.js +35 -0
- package/dist/application/session-binding-service.js.map +1 -1
- package/dist/application/session-close-service.js +390 -54
- package/dist/application/session-close-service.js.map +1 -1
- package/dist/application/session-create-service.js +97 -5
- package/dist/application/session-create-service.js.map +1 -1
- package/dist/application/session-custody-recorder.js +15 -12
- package/dist/application/session-custody-recorder.js.map +1 -1
- package/dist/application/session-custody-service.js +1 -0
- package/dist/application/session-custody-service.js.map +1 -1
- package/dist/application/session-narrative.js +51 -12
- package/dist/application/session-narrative.js.map +1 -1
- package/dist/application/session-pause-service.js +46 -0
- package/dist/application/session-pause-service.js.map +1 -0
- package/dist/application/session-resolver.js +36 -11
- package/dist/application/session-resolver.js.map +1 -1
- package/dist/application/session-resume-service.js +190 -20
- package/dist/application/session-resume-service.js.map +1 -1
- package/dist/application/sessions-service.js +4 -0
- package/dist/application/sessions-service.js.map +1 -1
- package/dist/application/sessions-sweep-service.js +77 -0
- package/dist/application/sessions-sweep-service.js.map +1 -0
- package/dist/application/settle-service.js +3 -1
- package/dist/application/settle-service.js.map +1 -1
- package/dist/application/skills-resolver-service.js +1 -1
- package/dist/application/skills-resolver-service.js.map +1 -1
- package/dist/application/source-add-service.js +37 -0
- package/dist/application/source-add-service.js.map +1 -0
- package/dist/application/source-boundary-policy.js +97 -8
- package/dist/application/source-boundary-policy.js.map +1 -1
- package/dist/application/source-pipeline.js +68 -0
- package/dist/application/source-pipeline.js.map +1 -0
- package/dist/application/source-remove-service.js +15 -3
- package/dist/application/source-remove-service.js.map +1 -1
- package/dist/application/sources-service.js +71 -16
- package/dist/application/sources-service.js.map +1 -1
- package/dist/application/status-service.js +55 -5
- package/dist/application/status-service.js.map +1 -1
- package/dist/application/unit-dependencies.js +100 -0
- package/dist/application/unit-dependencies.js.map +1 -0
- package/dist/application/unit-membership.js +37 -0
- package/dist/application/unit-membership.js.map +1 -0
- package/dist/application/unit-residue.js +72 -0
- package/dist/application/unit-residue.js.map +1 -0
- package/dist/application/visibility-doctor-service.js +66 -4
- package/dist/application/visibility-doctor-service.js.map +1 -1
- package/dist/application/working-branch-service.js +6 -0
- package/dist/application/working-branch-service.js.map +1 -1
- package/dist/application/workline-graph.js +1 -1
- package/dist/application/workline-graph.js.map +1 -1
- package/dist/application/workline-index-service.js +118 -17
- package/dist/application/workline-index-service.js.map +1 -1
- package/dist/application/workspace-commit-service.js +218 -0
- package/dist/application/workspace-commit-service.js.map +1 -0
- package/dist/application/workspace-init-service.js +64 -34
- package/dist/application/workspace-init-service.js.map +1 -1
- package/dist/application/workspace-local-config.js +90 -0
- package/dist/application/workspace-local-config.js.map +1 -0
- package/dist/application/workspace-materialization-service.js +52 -3
- package/dist/application/workspace-materialization-service.js.map +1 -1
- package/dist/application/workspace-migrate/apply.js +525 -4
- package/dist/application/workspace-migrate/apply.js.map +1 -1
- package/dist/application/workspace-migrate/markers.js +9 -6
- package/dist/application/workspace-migrate/markers.js.map +1 -1
- package/dist/application/workspace-migrate/plan.js +97 -5
- package/dist/application/workspace-migrate/plan.js.map +1 -1
- package/dist/application/workspace-move-service.js +270 -0
- package/dist/application/workspace-move-service.js.map +1 -0
- package/dist/application/workspace-untrack-service.js +28 -0
- package/dist/application/workspace-untrack-service.js.map +1 -0
- package/dist/application/worktree-service.js +654 -105
- package/dist/application/worktree-service.js.map +1 -1
- package/dist/cli/ascii.js +86 -0
- package/dist/cli/ascii.js.map +1 -0
- package/dist/cli/commands/add-source.js +38 -0
- package/dist/cli/commands/add-source.js.map +1 -0
- package/dist/cli/commands/amend.js +14 -2
- package/dist/cli/commands/amend.js.map +1 -1
- package/dist/cli/commands/capability.js +26 -0
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/check-branch.js +1 -0
- package/dist/cli/commands/check-branch.js.map +1 -1
- package/dist/cli/commands/checkpoint-read.js +1 -0
- package/dist/cli/commands/checkpoint-read.js.map +1 -1
- package/dist/cli/commands/checkpoint-write.js +4 -1
- package/dist/cli/commands/checkpoint-write.js.map +1 -1
- package/dist/cli/commands/claims.js +1 -0
- package/dist/cli/commands/claims.js.map +1 -1
- package/dist/cli/commands/code-scan.js +1 -0
- package/dist/cli/commands/code-scan.js.map +1 -1
- package/dist/cli/commands/context-budget.js +1 -0
- package/dist/cli/commands/context-budget.js.map +1 -1
- package/dist/cli/commands/context-plan.js +1 -0
- package/dist/cli/commands/context-plan.js.map +1 -1
- package/dist/cli/commands/cut-intent.js +1 -0
- package/dist/cli/commands/cut-intent.js.map +1 -1
- package/dist/cli/commands/designs.js +15 -5
- package/dist/cli/commands/designs.js.map +1 -1
- package/dist/cli/commands/dev-only.js +22 -5
- package/dist/cli/commands/dev-only.js.map +1 -1
- package/dist/cli/commands/doc-branch.js +184 -0
- package/dist/cli/commands/doc-branch.js.map +1 -0
- package/dist/cli/commands/doctor.js +9 -0
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/export.js +89 -16
- package/dist/cli/commands/export.js.map +1 -1
- package/dist/cli/commands/fix-git.js +89 -13
- package/dist/cli/commands/fix-git.js.map +1 -1
- package/dist/cli/commands/flow.js +129 -13
- package/dist/cli/commands/flow.js.map +1 -1
- package/dist/cli/commands/generate-launch.js +15 -2
- package/dist/cli/commands/generate-launch.js.map +1 -1
- package/dist/cli/commands/git-flow.js +1 -0
- package/dist/cli/commands/git-flow.js.map +1 -1
- package/dist/cli/commands/history-update.js +7 -9
- package/dist/cli/commands/history-update.js.map +1 -1
- package/dist/cli/commands/history.js +14 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/hook.js +26 -5
- package/dist/cli/commands/hook.js.map +1 -1
- package/dist/cli/commands/host-doctor.js +1 -0
- package/dist/cli/commands/host-doctor.js.map +1 -1
- package/dist/cli/commands/host-memory.js +2 -9
- package/dist/cli/commands/host-memory.js.map +1 -1
- package/dist/cli/commands/index.js +18 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/mcp.js +30 -4
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/merge-state.js +3 -2
- package/dist/cli/commands/merge-state.js.map +1 -1
- package/dist/cli/commands/multiroot.js +16 -0
- package/dist/cli/commands/multiroot.js.map +1 -1
- package/dist/cli/commands/persist.js +7 -5
- package/dist/cli/commands/persist.js.map +1 -1
- package/dist/cli/commands/plan.js +1 -0
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/commands/plugin-cache.js +4 -0
- package/dist/cli/commands/plugin-cache.js.map +1 -1
- package/dist/cli/commands/plugin-doctor.js +3 -0
- package/dist/cli/commands/plugin-doctor.js.map +1 -1
- package/dist/cli/commands/project-md-upsert.js +8 -3
- package/dist/cli/commands/project-md-upsert.js.map +1 -1
- package/dist/cli/commands/release-data.js +12 -0
- package/dist/cli/commands/release-data.js.map +1 -1
- package/dist/cli/commands/release-pass.js +13 -3
- package/dist/cli/commands/release-pass.js.map +1 -1
- package/dist/cli/commands/remove-source.js +1 -0
- package/dist/cli/commands/remove-source.js.map +1 -1
- package/dist/cli/commands/reseal.js +1 -0
- package/dist/cli/commands/reseal.js.map +1 -1
- package/dist/cli/commands/resume-summary.js +5 -0
- package/dist/cli/commands/resume-summary.js.map +1 -1
- package/dist/cli/commands/resume.js +13 -3
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/commands/retirement.js +1 -0
- package/dist/cli/commands/retirement.js.map +1 -1
- package/dist/cli/commands/self.js +39 -1
- package/dist/cli/commands/self.js.map +1 -1
- package/dist/cli/commands/session-artifacts.js +1 -0
- package/dist/cli/commands/session-artifacts.js.map +1 -1
- package/dist/cli/commands/session-close.js +185 -31
- package/dist/cli/commands/session-close.js.map +1 -1
- package/dist/cli/commands/session-create.js +4 -1
- package/dist/cli/commands/session-create.js.map +1 -1
- package/dist/cli/commands/session-pause.js +14 -0
- package/dist/cli/commands/session-pause.js.map +1 -0
- package/dist/cli/commands/session-resume.js +1 -0
- package/dist/cli/commands/session-resume.js.map +1 -1
- package/dist/cli/commands/sessions.js +16 -2
- package/dist/cli/commands/sessions.js.map +1 -1
- package/dist/cli/commands/set-branch.js +29 -4
- package/dist/cli/commands/set-branch.js.map +1 -1
- package/dist/cli/commands/set-edit-mode.js +20 -0
- package/dist/cli/commands/set-edit-mode.js.map +1 -0
- package/dist/cli/commands/set-pipeline.js +35 -0
- package/dist/cli/commands/set-pipeline.js.map +1 -0
- package/dist/cli/commands/settle.js +1 -0
- package/dist/cli/commands/settle.js.map +1 -1
- package/dist/cli/commands/skill-index.js +1 -0
- package/dist/cli/commands/skill-index.js.map +1 -1
- package/dist/cli/commands/skills.js +1 -0
- package/dist/cli/commands/skills.js.map +1 -1
- package/dist/cli/commands/sources.js +5 -0
- package/dist/cli/commands/sources.js.map +1 -1
- package/dist/cli/commands/stack.js +1 -0
- package/dist/cli/commands/stack.js.map +1 -1
- package/dist/cli/commands/status.js +36 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/tool.js +5 -21
- package/dist/cli/commands/tool.js.map +1 -1
- package/dist/cli/commands/unknown-flags.js +68 -16
- package/dist/cli/commands/unknown-flags.js.map +1 -1
- package/dist/cli/commands/visibility.js +2 -0
- package/dist/cli/commands/visibility.js.map +1 -1
- package/dist/cli/commands/workspace-commit.js +62 -0
- package/dist/cli/commands/workspace-commit.js.map +1 -0
- package/dist/cli/commands/workspace-init.js +51 -2
- package/dist/cli/commands/workspace-init.js.map +1 -1
- package/dist/cli/commands/workspace-migrate.js +31 -14
- package/dist/cli/commands/workspace-migrate.js.map +1 -1
- package/dist/cli/commands/workspace-move.js +42 -0
- package/dist/cli/commands/workspace-move.js.map +1 -0
- package/dist/cli/commands/worktree.js +5 -4
- package/dist/cli/commands/worktree.js.map +1 -1
- package/dist/cli/context-id.js +6 -0
- package/dist/cli/context-id.js.map +1 -1
- package/dist/cli/full-cli.js +555 -0
- package/dist/cli/full-cli.js.map +1 -0
- package/dist/cli/help-groups.js +108 -14
- package/dist/cli/help-groups.js.map +1 -1
- package/dist/cli/hook-stdin-cache.js +12 -0
- package/dist/cli/hook-stdin-cache.js.map +1 -0
- package/dist/cli/main.js +47 -471
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/output-mode.js +11 -2
- package/dist/cli/output-mode.js.map +1 -1
- package/dist/cli/parser.js +18 -5
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli/render.js +30 -1
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/tui/app.js +1 -1
- package/dist/cli/tui/app.js.map +1 -1
- package/dist/cli/tui/components/workspace-init-form.js +4 -1
- package/dist/cli/tui/components/workspace-init-form.js.map +1 -1
- package/dist/cli/tui/data/recommended-skills.js +2 -2
- package/dist/cli/tui/data/recommended-skills.js.map +1 -1
- package/dist/cli/tui/data/workflow-content.js +6 -1
- package/dist/cli/tui/data/workflow-content.js.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab.js +4 -3
- package/dist/cli/tui/tabs/mcp-tab.js.map +1 -1
- package/dist/cli/tui/tabs/project-tab.js +7 -2
- package/dist/cli/tui/tabs/project-tab.js.map +1 -1
- package/dist/dist-manifest.json +4117 -0
- package/dist/domain/capability/protocol.js.map +1 -1
- package/dist/domain/decision-note.js +13 -0
- package/dist/domain/decision-note.js.map +1 -1
- package/dist/domain/design/artifact.js +22 -3
- package/dist/domain/design/artifact.js.map +1 -1
- package/dist/domain/design/capability.js +1 -0
- package/dist/domain/design/capability.js.map +1 -1
- package/dist/domain/design/expansion.js +1 -1
- package/dist/domain/design/expansion.js.map +1 -1
- package/dist/domain/design/maturity.js +16 -0
- package/dist/domain/design/maturity.js.map +1 -1
- package/dist/domain/design/reference.js +5 -0
- package/dist/domain/design/reference.js.map +1 -1
- package/dist/domain/doctor/operations.js +28 -2
- package/dist/domain/doctor/operations.js.map +1 -1
- package/dist/domain/effective-contract.js +3 -1
- package/dist/domain/effective-contract.js.map +1 -1
- package/dist/domain/elicitation.js +1 -1
- package/dist/domain/flow/answer.js +273 -130
- package/dist/domain/flow/answer.js.map +1 -1
- package/dist/domain/flow/authority.js +330 -44
- package/dist/domain/flow/authority.js.map +1 -1
- package/dist/domain/flow/batch-review.js +65 -0
- package/dist/domain/flow/batch-review.js.map +1 -0
- package/dist/domain/flow/decision-schema.js +47 -0
- package/dist/domain/flow/decision-schema.js.map +1 -0
- package/dist/domain/flow/directive.js +16 -0
- package/dist/domain/flow/directive.js.map +1 -1
- package/dist/domain/flow/execution-result.js +169 -9
- package/dist/domain/flow/execution-result.js.map +1 -1
- package/dist/domain/flow/route.js.map +1 -1
- package/dist/domain/flow/rules.js +5 -6
- package/dist/domain/flow/rules.js.map +1 -1
- package/dist/domain/flow/run-state.js +531 -47
- package/dist/domain/flow/run-state.js.map +1 -1
- package/dist/domain/flow/test-run-evidence.js +285 -0
- package/dist/domain/flow/test-run-evidence.js.map +1 -0
- package/dist/domain/flow/unchanged-phase.js +15 -0
- package/dist/domain/flow/unchanged-phase.js.map +1 -0
- package/dist/domain/harnesses.js +22 -6
- package/dist/domain/harnesses.js.map +1 -1
- package/dist/domain/isolation-unit.js +2 -2
- package/dist/domain/isolation-unit.js.map +1 -1
- package/dist/domain/lineage.js +7 -2
- package/dist/domain/lineage.js.map +1 -1
- package/dist/domain/obligation-correspondence.js +4 -2
- package/dist/domain/obligation-correspondence.js.map +1 -1
- package/dist/domain/plan-handoff.js +5 -0
- package/dist/domain/plan-handoff.js.map +1 -0
- package/dist/domain/proposal.js +18 -7
- package/dist/domain/proposal.js.map +1 -1
- package/dist/domain/reservation.js +5 -3
- package/dist/domain/reservation.js.map +1 -1
- package/dist/domain/retirement/proposal.js +1 -0
- package/dist/domain/retirement/proposal.js.map +1 -1
- package/dist/domain/session/custody.js +10 -6
- package/dist/domain/session/custody.js.map +1 -1
- package/dist/domain/session/narrative.js +4 -1
- package/dist/domain/session/narrative.js.map +1 -1
- package/dist/domain/skills.js +2 -0
- package/dist/domain/skills.js.map +1 -1
- package/dist/domain/sql-lexer.js +15 -2
- package/dist/domain/sql-lexer.js.map +1 -1
- package/dist/runtime/namespace-resolver.js +4 -0
- package/dist/runtime/namespace-resolver.js.map +1 -1
- package/dist/runtime/workline-marker.js +35 -8
- package/dist/runtime/workline-marker.js.map +1 -1
- package/dist/runtime/workspace-resolution.js +123 -0
- package/dist/runtime/workspace-resolution.js.map +1 -0
- package/package.json +5 -4
- package/skills/w/SKILL.md +3 -1
- package/skills/w/artifacts/artifacts-core/CHECKPOINT.md +1 -1
- package/skills/w/artifacts/artifacts-core/SCRIPTS.rollback.sql +8 -0
- package/skills/w/artifacts/artifacts-core/SCRIPTS.sql +4 -8
- package/skills/w/commands/export-diagrams.md +3 -3
- package/skills/w/commands/export-manuals.md +6 -6
- package/skills/w/commands/export-reports.md +3 -3
- package/skills/w/commands/export-scripts.md +5 -5
- package/skills/w/commands/fix-git.md +12 -13
- package/skills/w/commands/persist.md +2 -2
- package/skills/w/commands/plan-exec.md +4 -2
- package/skills/w/commands/spec-new.md +1 -1
- package/skills/w/commands/workspace-init.md +2 -0
- package/skills/w/context/MANIFEST.json +1 -1
- package/skills/w/exports/export-diagrams/EXPORT.md +3 -3
- package/skills/w/exports/export-manuals/EXPORT.md +14 -16
- package/skills/w/exports/export-reports/EXPORT.md +3 -3
- package/skills/w/exports/export-scripts/EXPORT.md +45 -21
- package/skills/w/harness/HARNESS.md +5 -3
- package/skills/w/hooks/hooks.template.json +6 -4
- package/skills/w/loops/CHASSIS.md +12 -6
- package/skills/w/loops/CODE-POLICIES.md +7 -4
- package/skills/w/loops/plan-exec-loop/LOOP.md +23 -22
- package/skills/w/loops/plan-new-loop/LOOP.md +23 -31
- package/skills/w/loops/plan-refine-loop/LOOP.md +8 -2
- package/skills/w/loops/quick-loop/LOOP.md +6 -5
- package/skills/w/modules/DESIGN-REFERENCES.md +9 -11
- package/skills/w/modules/PLAN-EXECUTION-BATCHES.md +4 -4
- package/skills/w/modules/SESSION-NUMBERING.md +2 -0
- package/skills/w/roles/design/CONTRACT.md +16 -7
- package/skills/w/roles/design/ROLE.md +4 -2
- package/skills/w/roles/git/ROLE.md +16 -9
- package/skills/w/roles/research/ROLE.md +1 -1
- package/skills/w/roles/sql/ROLE.md +16 -14
- package/skills/w/schemas/design/ui-screen.v1.schema.json +9 -0
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
2
|
import { parseUnitPath, unitBranch, unitPath, workspaceKey, } from "../domain/isolation-unit.js";
|
|
3
|
-
import { resolveSourceBranches } from "./branch-resolver.js";
|
|
3
|
+
import { isWorkingBranch, resolveSourceBranches } from "./branch-resolver.js";
|
|
4
|
+
import { documentOfSession, resolveDocBranch } from "./doc-branch-ledger.js";
|
|
4
5
|
import { locateRun, readRun } from "./flow/run-state-service.js";
|
|
5
6
|
import { withCwdLock } from "./lock-service.js";
|
|
6
7
|
import { runMultiroot } from "./multiroot-service.js";
|
|
7
8
|
import { normalizePath } from "./multiroot/paths.js";
|
|
8
|
-
import { readWorkspaceBlock } from "./parsers/project-block.js";
|
|
9
|
+
import { readWorkspaceBlock, requireSourcePath, } from "./parsers/project-block.js";
|
|
9
10
|
import { recordIntegration, recordUnitTaken } from "./session-custody-recorder.js";
|
|
10
|
-
import {
|
|
11
|
+
import { readCustody } from "./session-custody-service.js";
|
|
12
|
+
import { listSessionFolders, readSessionState, resolveSessionTarget, sessionFolderMatches, } from "./session-resolver.js";
|
|
13
|
+
import { linkUnitDependencies, removeUnitSafely, } from "./unit-dependencies.js";
|
|
14
|
+
import { hubUnitPaths } from "./unit-membership.js";
|
|
15
|
+
import { finishResidue } from "./unit-residue.js";
|
|
11
16
|
import { ensureWorklineMaterialized } from "./workspace-materialization-service.js";
|
|
12
17
|
/**
|
|
13
18
|
* How long an integration waits for the workspace lock before giving up.
|
|
@@ -16,6 +21,67 @@ import { ensureWorklineMaterialized } from "./workspace-materialization-service.
|
|
|
16
21
|
* a merge: the wait is bounded by another merge finishing, not by a file write.
|
|
17
22
|
*/
|
|
18
23
|
const INTEGRATE_LOCK_WAIT_MS = 10_000;
|
|
24
|
+
/** Read-only close classification: never treats an unreadable tree as empty. */
|
|
25
|
+
export async function classifyListedUnits(deps, units) {
|
|
26
|
+
const block = await readWorkspaceBlock(deps.fs, deps.paths.workspaceDir(), deps.paths.blockMarkers());
|
|
27
|
+
const declared = new Set();
|
|
28
|
+
for (const source of block?.fuentes ?? []) {
|
|
29
|
+
const roles = resolveSourceBranches(source, block);
|
|
30
|
+
for (const name of [
|
|
31
|
+
roles.prod,
|
|
32
|
+
roles.work,
|
|
33
|
+
block?.qa_branches[source.alias] ?? block?.default_branches.qa,
|
|
34
|
+
])
|
|
35
|
+
if (name)
|
|
36
|
+
declared.add(`refs/heads/${name}`);
|
|
37
|
+
}
|
|
38
|
+
return Promise.all(units.map(async (unit) => {
|
|
39
|
+
const classify = (classification, reason, preservedIn) => ({
|
|
40
|
+
...unit,
|
|
41
|
+
classification,
|
|
42
|
+
classification_reason: reason,
|
|
43
|
+
...(preservedIn ? { preserved_in: preservedIn } : {}),
|
|
44
|
+
});
|
|
45
|
+
try {
|
|
46
|
+
if (unit.dirty === null || unit.head === null)
|
|
47
|
+
return classify("retained", "no se pudo leer HEAD o limpieza de la unidad");
|
|
48
|
+
if ((await deps.git.operationState(unit.path)) !== "clean")
|
|
49
|
+
return classify("retained", "operación git a medio resolver");
|
|
50
|
+
if (unit.dirty)
|
|
51
|
+
return classify("retained", "cambios sin commitear");
|
|
52
|
+
const custody = await readCustody(deps.fs, join(deps.paths.cwdSessionsDir(), unit.session));
|
|
53
|
+
if (custody.status === "unreadable")
|
|
54
|
+
return classify("retained", `custodia ilegible: ${custody.reason}`);
|
|
55
|
+
const sealed = custody.status === "present"
|
|
56
|
+
? custody.custody.sources.find((entry) => entry.alias === unit.alias)?.base_branch
|
|
57
|
+
: undefined;
|
|
58
|
+
const source = block?.fuentes.find((entry) => entry.alias === unit.alias);
|
|
59
|
+
const work = source ? resolveSourceBranches(source, block).work : null;
|
|
60
|
+
const base = sealed ?? work;
|
|
61
|
+
if (base === null || base === undefined)
|
|
62
|
+
return classify("pending", "sin base sellada ni rama de trabajo declarada");
|
|
63
|
+
const baseline = sealed !== undefined && custody.status === "present"
|
|
64
|
+
? custody.custody.sources.find((entry) => entry.alias === unit.alias)?.baseline_head
|
|
65
|
+
: null;
|
|
66
|
+
if (baseline !== null && baseline !== undefined && unit.head === baseline)
|
|
67
|
+
return classify("empty", `sin commits sobre ${base}`);
|
|
68
|
+
const refs = await deps.git.refsContaining(unit.source_path, unit.head);
|
|
69
|
+
const baseRef = `refs/heads/${base}`;
|
|
70
|
+
const preserved = refs.includes(baseRef) && declared.has(baseRef)
|
|
71
|
+
? baseRef
|
|
72
|
+
: refs.find((ref) => declared.has(ref) || ref.startsWith("refs/remotes/"));
|
|
73
|
+
if (preserved)
|
|
74
|
+
return {
|
|
75
|
+
...classify("preserved", `commits contenidos en ${preserved}`, preserved),
|
|
76
|
+
integrated: preserved === baseRef,
|
|
77
|
+
};
|
|
78
|
+
return classify("pending", "commits no contenidos en una rama declarada ni remota");
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
return classify("retained", `no se pudo verificar la unidad: ${err.message}`);
|
|
82
|
+
}
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
19
85
|
export async function runWorktree(deps, input) {
|
|
20
86
|
if (input.action === "list")
|
|
21
87
|
return listUnits(deps, input);
|
|
@@ -49,6 +115,7 @@ export async function runWorktree(deps, input) {
|
|
|
49
115
|
async function integrateSession(deps, input) {
|
|
50
116
|
const resolution = await resolveSessionTarget(deps.fs, deps.paths, {
|
|
51
117
|
intent: "write",
|
|
118
|
+
allowClosed: input.sessionCode !== undefined,
|
|
52
119
|
...(input.sessionCode !== undefined ? { code: input.sessionCode } : {}),
|
|
53
120
|
...(input.contextId !== undefined ? { contextId: input.contextId } : {}),
|
|
54
121
|
});
|
|
@@ -66,8 +133,18 @@ async function integrateSession(deps, input) {
|
|
|
66
133
|
// branch — so this cannot take the side of a merge nobody resolved.
|
|
67
134
|
const swept = await reclaimUnits(deps, { action: "reclaim", sessionCode: session });
|
|
68
135
|
const residue = "error" in swept
|
|
69
|
-
? {
|
|
136
|
+
? {
|
|
137
|
+
reclaimed: [],
|
|
138
|
+
retained: [],
|
|
139
|
+
unreadable: [
|
|
140
|
+
{ alias: "workspace", error: swept.message },
|
|
141
|
+
],
|
|
142
|
+
next: null,
|
|
143
|
+
}
|
|
70
144
|
: swept;
|
|
145
|
+
const unreadable = [
|
|
146
|
+
...new Map([...listed.unreadable, ...residue.unreadable].map((item) => [item.alias, item])).values(),
|
|
147
|
+
];
|
|
71
148
|
// An alias whose unit was collected is no longer holding one, so it leaves
|
|
72
149
|
// `pending`. That is not hiding its refusal — the refusal stays in `results` —
|
|
73
150
|
// it is that a unit with nothing the working branch lacks had no merge pending
|
|
@@ -84,7 +161,12 @@ async function integrateSession(deps, input) {
|
|
|
84
161
|
pending: holding,
|
|
85
162
|
reclaimed: residue.reclaimed,
|
|
86
163
|
retained: residue.retained,
|
|
87
|
-
|
|
164
|
+
...(unreadable.length > 0 ? { unreadable } : {}),
|
|
165
|
+
next: (first !== undefined ? (nextOf.get(first) ?? null) : null) ??
|
|
166
|
+
residue.next ??
|
|
167
|
+
(unreadable[0]?.code === "SOURCE_PATH_MISSING"
|
|
168
|
+
? `aw add-source ${unreadable[0].alias}:<ruta>`
|
|
169
|
+
: null),
|
|
88
170
|
};
|
|
89
171
|
}
|
|
90
172
|
/**
|
|
@@ -137,6 +219,8 @@ async function planOf(deps, session) {
|
|
|
137
219
|
*/
|
|
138
220
|
async function integrateUnit(deps, target) {
|
|
139
221
|
const { source, path, branch, base, identity } = target;
|
|
222
|
+
if (base === null)
|
|
223
|
+
return undeclaredWorkBranch(source.alias);
|
|
140
224
|
if (!(await deps.git.isGitRepo(source.path))) {
|
|
141
225
|
return {
|
|
142
226
|
error: "not_a_repo",
|
|
@@ -151,9 +235,13 @@ async function integrateUnit(deps, target) {
|
|
|
151
235
|
hint: `creála con 'aw worktree ensure --source ${source.alias} --code ${identity.session}'`,
|
|
152
236
|
};
|
|
153
237
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
238
|
+
if ((await deps.git.operationState(path)) !== "clean") {
|
|
239
|
+
return {
|
|
240
|
+
error: "unit_operation_in_progress",
|
|
241
|
+
message: `la unidad ${path} tiene una operación git pendiente`,
|
|
242
|
+
hint: `resolvé el merge con aw fix-git --path ${path} antes de integrar otra vez`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
157
245
|
if (await deps.git.isDirty(path)) {
|
|
158
246
|
return {
|
|
159
247
|
error: "unit_not_committed",
|
|
@@ -161,47 +249,18 @@ async function integrateUnit(deps, target) {
|
|
|
161
249
|
hint: "commiteá el trabajo del flujo en su unidad antes de integrarlo",
|
|
162
250
|
};
|
|
163
251
|
}
|
|
164
|
-
|
|
252
|
+
const current = await deps.git.currentBranch(source.path);
|
|
253
|
+
if (current === base && (await deps.git.isDirty(source.path))) {
|
|
165
254
|
return {
|
|
166
255
|
error: "checkout_dirty",
|
|
167
256
|
message: `el checkout principal de ${source.alias} tiene cambios sin commitear`,
|
|
168
257
|
hint: "commiteá o guardá esos cambios: la integración no los va a mezclar con los del flujo",
|
|
169
258
|
};
|
|
170
259
|
}
|
|
171
|
-
// Never switch the user's branch to make the command succeed. The declared
|
|
172
|
-
// working branch is where the checkout is SUPPOSED to be — the git-safe
|
|
173
|
-
// invariant says so — and a checkout that is somewhere else is a state the
|
|
174
|
-
// person has to see, not one to be quietly corrected under a merge.
|
|
175
|
-
const current = await deps.git.currentBranch(source.path);
|
|
176
|
-
if (current !== base) {
|
|
177
|
-
return {
|
|
178
|
-
error: "checkout_off_branch",
|
|
179
|
-
message: `el checkout principal de ${source.alias} está en '${current}' y la integración va a '${base}'`,
|
|
180
|
-
hint: `posicioná el checkout en '${base}' y volvé a integrar; la integración nunca cambia de rama por su cuenta`,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
// The merge goes through GitPort, so it would otherwise bypass the command
|
|
184
|
-
// filesystem guard. All refusal/precondition reads are complete above; from
|
|
185
|
-
// here on this operation may change a source checkout.
|
|
186
260
|
await ensureWorklineMaterialized(deps.fs, deps.paths);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// failing fast for the same reason a correlative claim waits: by the time this
|
|
191
|
-
// lock is taken there is committed work with nowhere else to go, so losing the
|
|
192
|
-
// race would lose real work instead of a retry.
|
|
193
|
-
//
|
|
194
|
-
// The pre-merge HEAD is read INSIDE the lock, next to the merge it describes.
|
|
195
|
-
// Reading it outside would leave a window in which another integration lands
|
|
196
|
-
// between the read and the merge, and the receipt would then attribute this
|
|
197
|
-
// integration over a value the branch no longer had.
|
|
198
|
-
const merged = await withCwdLock(deps.fs, deps.paths, async () => {
|
|
199
|
-
const before = await deps.git.head(source.path).catch(() => null);
|
|
200
|
-
const merge = await deps.git.merge(source.path, branch);
|
|
201
|
-
if (merge.ok)
|
|
202
|
-
return { ok: true, before };
|
|
203
|
-
return { ok: false, conflicted: merge.conflicted };
|
|
204
|
-
}, { waitMs: INTEGRATE_LOCK_WAIT_MS });
|
|
261
|
+
const merged = await withCwdLock(deps.fs, deps.paths, () => integrateLocked(deps, { ...target, base }), {
|
|
262
|
+
waitMs: INTEGRATE_LOCK_WAIT_MS,
|
|
263
|
+
});
|
|
205
264
|
if ("error" in merged) {
|
|
206
265
|
return {
|
|
207
266
|
error: "integration_locked",
|
|
@@ -209,7 +268,9 @@ async function integrateUnit(deps, target) {
|
|
|
209
268
|
hint: "esperá a que la otra integración termine y volvé a integrar; el merge no se empieza a medias",
|
|
210
269
|
};
|
|
211
270
|
}
|
|
212
|
-
if (
|
|
271
|
+
if (merged.state === "refused")
|
|
272
|
+
return merged.refusal;
|
|
273
|
+
if (merged.state === "conflict") {
|
|
213
274
|
return {
|
|
214
275
|
alias: source.alias,
|
|
215
276
|
source_path: source.path,
|
|
@@ -221,15 +282,29 @@ async function integrateUnit(deps, target) {
|
|
|
221
282
|
// The unit SURVIVES a conflict: its commits are the only copy of one side
|
|
222
283
|
// of the merge, and releasing it here would delete them to tidy up.
|
|
223
284
|
released: false,
|
|
224
|
-
next: `aw fix-git --path ${
|
|
285
|
+
next: `aw fix-git --path ${merged.path}`,
|
|
225
286
|
};
|
|
226
287
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
288
|
+
if (merged.before !== null && merged.after !== null && merged.before !== merged.after) {
|
|
289
|
+
const tip = await deps.git.head(path);
|
|
290
|
+
if (tip === null)
|
|
291
|
+
return {
|
|
292
|
+
error: "custody_unreadable",
|
|
293
|
+
message: `no se pudo leer la punta de la unidad ${path} antes de liberarla`,
|
|
294
|
+
};
|
|
295
|
+
const recorded = await recordIntegration(deps, identity.session, {
|
|
296
|
+
alias: source.alias,
|
|
297
|
+
into: base,
|
|
298
|
+
before: merged.before,
|
|
299
|
+
after: merged.after,
|
|
300
|
+
unitTip: tip,
|
|
301
|
+
});
|
|
302
|
+
if (recorded.status === "unreadable")
|
|
303
|
+
return {
|
|
304
|
+
error: "custody_unreadable",
|
|
305
|
+
message: `la integración de ${source.alias} llegó a ${base}, pero no se pudo registrar la punta ${tip} en la custodia: ${recorded.reason}; la unidad conserva su rama`,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
233
308
|
const release = await releaseUnit(deps, target);
|
|
234
309
|
return {
|
|
235
310
|
alias: source.alias,
|
|
@@ -240,9 +315,100 @@ async function integrateUnit(deps, target) {
|
|
|
240
315
|
integrated: true,
|
|
241
316
|
conflicted: [],
|
|
242
317
|
released: "released" in release ? release.released : false,
|
|
243
|
-
|
|
318
|
+
...("branch_kept" in release && release.branch_kept
|
|
319
|
+
? { branch_kept: release.branch_kept }
|
|
320
|
+
: {}),
|
|
321
|
+
...("visibility_error" in release && release.visibility_error
|
|
322
|
+
? { visibility_error: release.visibility_error }
|
|
323
|
+
: {}),
|
|
324
|
+
...("visibility_error" in release && release.visibility_error
|
|
325
|
+
? { visibility_error: release.visibility_error }
|
|
326
|
+
: {}),
|
|
327
|
+
next: "released" in release && release.released
|
|
328
|
+
? null
|
|
329
|
+
: `aw worktree release --source ${source.alias} --code ${identity.session}`,
|
|
244
330
|
};
|
|
245
331
|
}
|
|
332
|
+
/** All ref reads, occupancy checks and the move happen under the integration lock. */
|
|
333
|
+
async function integrateLocked(deps, target) {
|
|
334
|
+
const { source, path, branch, base, roles } = target;
|
|
335
|
+
const current = await deps.git.currentBranch(source.path);
|
|
336
|
+
if (current === base) {
|
|
337
|
+
const before = await deps.git.head(source.path);
|
|
338
|
+
const merge = await deps.git.merge(source.path, branch);
|
|
339
|
+
return merge.ok
|
|
340
|
+
? { state: "done", before, after: await deps.git.head(source.path) }
|
|
341
|
+
: { state: "conflict", conflicted: merge.conflicted, path: source.path };
|
|
342
|
+
}
|
|
343
|
+
const ref = `refs/heads/${base}`;
|
|
344
|
+
const before = await deps.git.refValue(source.path, ref);
|
|
345
|
+
if (before === null)
|
|
346
|
+
return {
|
|
347
|
+
state: "refused",
|
|
348
|
+
refusal: {
|
|
349
|
+
error: "target_missing",
|
|
350
|
+
message: `la rama destino '${base}' no existe en local`,
|
|
351
|
+
hint: `restaurá ${base} antes de integrar; la unidad queda intacta`,
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
const trees = await deps.git.worktreeList(source.path);
|
|
355
|
+
const occupied = trees.find((tree) => tree.branch === base && !samePath(tree.path, source.path));
|
|
356
|
+
if (occupied)
|
|
357
|
+
return {
|
|
358
|
+
state: "refused",
|
|
359
|
+
refusal: {
|
|
360
|
+
error: "target_occupied",
|
|
361
|
+
message: `${base} está puesta en otro árbol: ${occupied.path}`,
|
|
362
|
+
hint: `liberá ese árbol antes de mover la referencia ${ref}`,
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
let head = await deps.git.head(path);
|
|
366
|
+
if (head === null)
|
|
367
|
+
return {
|
|
368
|
+
state: "refused",
|
|
369
|
+
refusal: {
|
|
370
|
+
error: "unit_not_committed",
|
|
371
|
+
message: `la unidad ${path} no tiene HEAD`,
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
if (await deps.git.isAncestor(source.path, head, before)) {
|
|
375
|
+
return { state: "done", before, after: before };
|
|
376
|
+
}
|
|
377
|
+
if (!(await deps.git.isAncestor(source.path, before, head))) {
|
|
378
|
+
if (!isWorkingBranch(base, roles))
|
|
379
|
+
return {
|
|
380
|
+
state: "refused",
|
|
381
|
+
refusal: {
|
|
382
|
+
error: "checkout_off_branch",
|
|
383
|
+
message: `la rama de rol ${base} no admite integración divergente fuera de su checkout`,
|
|
384
|
+
hint: `posicioná el checkout en '${base}' y volvé a integrar`,
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
const merge = await deps.git.merge(path, base);
|
|
388
|
+
if (!merge.ok)
|
|
389
|
+
return { state: "conflict", conflicted: merge.conflicted, path };
|
|
390
|
+
head = await deps.git.head(path);
|
|
391
|
+
if (head === null)
|
|
392
|
+
return {
|
|
393
|
+
state: "refused",
|
|
394
|
+
refusal: {
|
|
395
|
+
error: "unit_not_committed",
|
|
396
|
+
message: `no se pudo leer HEAD de la unidad ${path} después del merge`,
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
const moved = await deps.git.updateRefCas(source.path, ref, head, before);
|
|
401
|
+
if (!moved.ok)
|
|
402
|
+
return {
|
|
403
|
+
state: "refused",
|
|
404
|
+
refusal: {
|
|
405
|
+
error: "integration_raced",
|
|
406
|
+
message: `la referencia ${ref} avanzó durante la integración: ${moved.why}`,
|
|
407
|
+
hint: `la unidad queda intacta: volvé a correr aw worktree integrate --source ${source.alias} --code ${target.identity.session}`,
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
return { state: "done", before, after: head };
|
|
411
|
+
}
|
|
246
412
|
async function resolveTarget(deps, input) {
|
|
247
413
|
const block = await readWorkspaceBlock(deps.fs, deps.paths.workspaceDir(), deps.paths.blockMarkers());
|
|
248
414
|
const sources = block?.fuentes ?? [];
|
|
@@ -250,7 +416,7 @@ async function resolveTarget(deps, input) {
|
|
|
250
416
|
return {
|
|
251
417
|
error: "no_sources_declared",
|
|
252
418
|
message: "el bloque WORKSPACE no declara ninguna fuente",
|
|
253
|
-
hint: "declará la fuente
|
|
419
|
+
hint: "declará la fuente con aw add-source <alias>:<ruta>:<rama> antes de pedir una unidad",
|
|
254
420
|
};
|
|
255
421
|
}
|
|
256
422
|
if (input.alias === undefined) {
|
|
@@ -268,8 +434,16 @@ async function resolveTarget(deps, input) {
|
|
|
268
434
|
hint: `fuentes declaradas: ${sources.map((s) => s.alias).join(", ")}`,
|
|
269
435
|
};
|
|
270
436
|
}
|
|
437
|
+
let sourcePath;
|
|
438
|
+
try {
|
|
439
|
+
sourcePath = await requireSourcePath(deps.fs, source);
|
|
440
|
+
}
|
|
441
|
+
catch (err) {
|
|
442
|
+
return { error: "SOURCE_PATH_MISSING", message: err.message };
|
|
443
|
+
}
|
|
271
444
|
const resolution = await resolveSessionTarget(deps.fs, deps.paths, {
|
|
272
445
|
intent: "write",
|
|
446
|
+
allowClosed: input.action !== "ensure" && input.sessionCode !== undefined,
|
|
273
447
|
...(input.sessionCode !== undefined ? { code: input.sessionCode } : {}),
|
|
274
448
|
...(input.contextId !== undefined ? { contextId: input.contextId } : {}),
|
|
275
449
|
});
|
|
@@ -281,12 +455,58 @@ async function resolveTarget(deps, input) {
|
|
|
281
455
|
alias: source.alias,
|
|
282
456
|
session,
|
|
283
457
|
};
|
|
458
|
+
const roles = resolveSourceBranches(source, block);
|
|
459
|
+
const custody = await readCustody(deps.fs, join(deps.paths.cwdSessionsDir(), session));
|
|
460
|
+
if (custody.status === "unreadable")
|
|
461
|
+
return {
|
|
462
|
+
error: "custody_unreadable",
|
|
463
|
+
message: `la custodia de ${session} no se puede leer: ${custody.reason}`,
|
|
464
|
+
};
|
|
465
|
+
const sealed = custody.status === "present"
|
|
466
|
+
? custody.custody.sources.find((s) => s.alias === source.alias)?.base_branch
|
|
467
|
+
: undefined;
|
|
468
|
+
const recorded = custody.status === "present"
|
|
469
|
+
? custody.custody.sources.find((s) => s.alias === source.alias)
|
|
470
|
+
: undefined;
|
|
471
|
+
if (recorded?.unit_path) {
|
|
472
|
+
const oldIdentity = parseUnitPath(await canonicalUnitsRootForRead(deps), recorded.unit_path);
|
|
473
|
+
if (oldIdentity?.alias === source.alias && oldIdentity.session === session)
|
|
474
|
+
identity.workspaceKey = oldIdentity.workspaceKey;
|
|
475
|
+
}
|
|
476
|
+
let previous;
|
|
477
|
+
if (custody.status === "absent") {
|
|
478
|
+
const root = await canonicalUnitsRootForRead(deps);
|
|
479
|
+
const owns = await hubUnitPaths(deps.fs, deps.paths, root);
|
|
480
|
+
try {
|
|
481
|
+
previous = (await deps.git.worktreeList(sourcePath)).find((tree) => {
|
|
482
|
+
const found = parseUnitPath(root, tree.path);
|
|
483
|
+
return found?.session === session && found.alias === source.alias && owns(tree.path);
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
/* a broken git inventory is reported by ensure/list itself */
|
|
488
|
+
}
|
|
489
|
+
if (previous) {
|
|
490
|
+
const found = parseUnitPath(root, previous.path);
|
|
491
|
+
if (found)
|
|
492
|
+
identity.workspaceKey = found.workspaceKey;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
const effective = sealed === undefined
|
|
496
|
+
? await resolveDocBranch(deps.fs, deps.paths, source, block, await documentOfSession(deps.fs, deps.paths, session))
|
|
497
|
+
: null;
|
|
498
|
+
if (effective?.origin === "unreadable")
|
|
499
|
+
return {
|
|
500
|
+
error: "custody_unreadable",
|
|
501
|
+
message: effective.reason ?? "custodia ilegible",
|
|
502
|
+
};
|
|
284
503
|
return {
|
|
285
|
-
source,
|
|
504
|
+
source: { ...source, path: sourcePath },
|
|
286
505
|
identity,
|
|
287
|
-
path: unitPath(await canonicalUnitsRoot(deps), identity),
|
|
288
|
-
branch: unitBranch(session),
|
|
289
|
-
base:
|
|
506
|
+
path: recorded?.unit_path ?? previous?.path ?? unitPath(await canonicalUnitsRoot(deps), identity),
|
|
507
|
+
branch: recorded?.unit_branch ?? previous?.branch ?? unitBranch(session, deps.paths.workspaceDir()),
|
|
508
|
+
base: sealed ?? effective?.branch ?? null,
|
|
509
|
+
roles,
|
|
290
510
|
};
|
|
291
511
|
}
|
|
292
512
|
/**
|
|
@@ -306,6 +526,13 @@ function sessionRefusal(resolution) {
|
|
|
306
526
|
hint: resolution.action,
|
|
307
527
|
};
|
|
308
528
|
}
|
|
529
|
+
function undeclaredWorkBranch(alias) {
|
|
530
|
+
return {
|
|
531
|
+
error: "working_branch_undeclared",
|
|
532
|
+
message: `rama de trabajo no declarada para ${alias}`,
|
|
533
|
+
hint: `usá 'aw set-working-branch ${alias} <rama>'`,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
309
536
|
/**
|
|
310
537
|
* The units root in the OS's own spelling.
|
|
311
538
|
*
|
|
@@ -346,6 +573,7 @@ async function ensureUnit(deps, target) {
|
|
|
346
573
|
message: `${source.alias} (${source.path}) no es un repositorio git`,
|
|
347
574
|
};
|
|
348
575
|
}
|
|
576
|
+
const longpathsEnabled = await enableLongPaths(deps, source.path);
|
|
349
577
|
// Vanished directories keep holding their branch until git is told, so the
|
|
350
578
|
// prune runs BEFORE the occupancy read — otherwise a unit whose folder the
|
|
351
579
|
// user deleted by hand would look occupied forever.
|
|
@@ -353,17 +581,47 @@ async function ensureUnit(deps, target) {
|
|
|
353
581
|
// root, so materialize explicitly before this first Git mutation rather than
|
|
354
582
|
// relying on a workspace-path filesystem write to notice it.
|
|
355
583
|
await ensureWorklineMaterialized(deps.fs, deps.paths);
|
|
584
|
+
const unitsRoot = await canonicalUnitsRootForRead(deps);
|
|
585
|
+
const owns = await hubUnitPaths(deps.fs, deps.paths, unitsRoot);
|
|
586
|
+
const staleErrors = [];
|
|
587
|
+
for (const tree of await deps.git.worktreeList(source.path)) {
|
|
588
|
+
if (!tree.prunable || !owns(tree.path) || (await deps.fs.lstat(tree.path)) !== null)
|
|
589
|
+
continue;
|
|
590
|
+
const result = await detach(deps, tree.path);
|
|
591
|
+
if (result.error)
|
|
592
|
+
staleErrors.push(`${tree.path}: ${result.error}`);
|
|
593
|
+
}
|
|
356
594
|
await deps.git.worktreePrune(source.path);
|
|
357
595
|
const existing = await deps.git.worktreeList(source.path);
|
|
358
596
|
const mine = existing.find((w) => samePath(w.path, path));
|
|
359
597
|
if (mine !== undefined) {
|
|
598
|
+
const custody = await readCustody(deps.fs, join(deps.paths.cwdSessionsDir(), target.identity.session));
|
|
599
|
+
const recorded = custody.status === "present"
|
|
600
|
+
? custody.custody.sources.find((s) => s.alias === source.alias)
|
|
601
|
+
: undefined;
|
|
602
|
+
if (custody.status === "present" &&
|
|
603
|
+
(recorded?.unit_path !== path || recorded.unit_branch !== branch || mine.branch !== branch)) {
|
|
604
|
+
return {
|
|
605
|
+
error: "unit_foreign",
|
|
606
|
+
message: `La unidad ${path} no está registrada en la custodia de esta sesión.`,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
360
609
|
// Idempotent: the unit is already there, on its own branch. The baseline is
|
|
361
610
|
// sealed here too — it is idempotent by alias, so the FIRST reading wins and a
|
|
362
611
|
// second `ensure` can never overwrite it with a state the session produced.
|
|
363
|
-
const sealed = await sealBaseline(deps, target);
|
|
612
|
+
const sealed = base === null ? null : await sealBaseline(deps, { ...target, base });
|
|
364
613
|
if (sealed !== null)
|
|
365
614
|
return sealed;
|
|
366
|
-
|
|
615
|
+
const visible = await attach(deps, path);
|
|
616
|
+
const visibilityError = [...staleErrors, visible.error].filter(Boolean).join("; ");
|
|
617
|
+
return {
|
|
618
|
+
...unitOf(target, false),
|
|
619
|
+
base,
|
|
620
|
+
visibility: visible.state,
|
|
621
|
+
...(visibilityError ? { visibility_error: visibilityError } : {}),
|
|
622
|
+
longpaths_enabled: longpathsEnabled,
|
|
623
|
+
dependencies: await linkUnitDependencies(deps.fs, deps.git, source.path, path),
|
|
624
|
+
};
|
|
367
625
|
}
|
|
368
626
|
const occupant = existing.find((w) => w.branch === branch);
|
|
369
627
|
if (occupant !== undefined) {
|
|
@@ -378,7 +636,29 @@ async function ensureUnit(deps, target) {
|
|
|
378
636
|
// An existing branch is checked out, not recreated: a flow that released its
|
|
379
637
|
// unit and asks again must land back on its own commits, not on a fresh branch
|
|
380
638
|
// that silently drops them.
|
|
381
|
-
const
|
|
639
|
+
const exists = await deps.git.branchExists(source.path, branch);
|
|
640
|
+
const custody = await readCustody(deps.fs, join(deps.paths.cwdSessionsDir(), target.identity.session));
|
|
641
|
+
const recorded = custody.status === "present"
|
|
642
|
+
? custody.custody.sources.find((s) => s.alias === source.alias)
|
|
643
|
+
: undefined;
|
|
644
|
+
if (exists &&
|
|
645
|
+
custody.status === "present" &&
|
|
646
|
+
(recorded?.unit_branch !== branch || recorded.unit_path !== path)) {
|
|
647
|
+
return {
|
|
648
|
+
error: "unit_foreign",
|
|
649
|
+
message: `La rama ${branch} no está registrada en la custodia de esta sesión.`,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
const from = exists ? null : base;
|
|
653
|
+
if (from === null && !exists)
|
|
654
|
+
return undeclaredWorkBranch(source.alias);
|
|
655
|
+
if (from !== null && !(await deps.git.branchExists(source.path, from))) {
|
|
656
|
+
return {
|
|
657
|
+
error: "base_missing",
|
|
658
|
+
message: `la rama base '${from}' no existe en ${source.alias}`,
|
|
659
|
+
hint: `restaurá '${from}' antes de abrir la unidad`,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
382
662
|
try {
|
|
383
663
|
await deps.git.worktreeAdd(source.path, path, branch, from);
|
|
384
664
|
}
|
|
@@ -386,13 +666,31 @@ async function ensureUnit(deps, target) {
|
|
|
386
666
|
return {
|
|
387
667
|
error: "worktree_add_failed",
|
|
388
668
|
message: err.message,
|
|
389
|
-
hint: `verificá que la rama base '${base}' exista en ${source.alias}`,
|
|
390
669
|
};
|
|
391
670
|
}
|
|
392
|
-
const sealed = await sealBaseline(deps, target);
|
|
671
|
+
const sealed = base === null ? null : await sealBaseline(deps, { ...target, base });
|
|
393
672
|
if (sealed !== null)
|
|
394
673
|
return sealed;
|
|
395
|
-
|
|
674
|
+
const visible = await attach(deps, path);
|
|
675
|
+
const visibilityError = [...staleErrors, visible.error].filter(Boolean).join("; ");
|
|
676
|
+
return {
|
|
677
|
+
...unitOf(target, true),
|
|
678
|
+
base,
|
|
679
|
+
visibility: visible.state,
|
|
680
|
+
...(visibilityError ? { visibility_error: visibilityError } : {}),
|
|
681
|
+
longpaths_enabled: longpathsEnabled,
|
|
682
|
+
dependencies: await linkUnitDependencies(deps.fs, deps.git, source.path, path),
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
async function enableLongPaths(deps, repo) {
|
|
686
|
+
if ((deps.platform ?? process.platform) !== "win32")
|
|
687
|
+
return false;
|
|
688
|
+
if (!deps.git.readConfig || !deps.git.writeLocalConfig)
|
|
689
|
+
throw new Error("git no permite configurar core.longpaths localmente");
|
|
690
|
+
if ((await deps.git.readConfig(repo, "core.longpaths"))?.toLowerCase() === "true")
|
|
691
|
+
return false;
|
|
692
|
+
await deps.git.writeLocalConfig(repo, "core.longpaths", "true");
|
|
693
|
+
return true;
|
|
396
694
|
}
|
|
397
695
|
/**
|
|
398
696
|
* Seal how the source stood before this unit gets edited, or refuse the unit.
|
|
@@ -430,38 +728,105 @@ async function releaseUnit(deps, target) {
|
|
|
430
728
|
};
|
|
431
729
|
}
|
|
432
730
|
const existing = await deps.git.worktreeList(source.path);
|
|
433
|
-
|
|
731
|
+
const registered = existing.find((w) => samePath(w.path, path));
|
|
732
|
+
if (registered && (await deps.fs.lstat(path)) === null) {
|
|
733
|
+
await deps.git.worktreePrune(source.path);
|
|
734
|
+
if ((await deps.git.worktreeList(source.path)).some((w) => samePath(w.path, path)))
|
|
735
|
+
return { error: "remove_blocked", message: `git aún registra la unidad sin carpeta ${path}` };
|
|
736
|
+
return releaseUnit(deps, target);
|
|
737
|
+
}
|
|
738
|
+
if (registered === undefined) {
|
|
434
739
|
await ensureWorklineMaterialized(deps.fs, deps.paths);
|
|
435
740
|
await deps.git.worktreePrune(source.path);
|
|
741
|
+
let completed = false;
|
|
742
|
+
if ((await deps.fs.lstat(path)) !== null) {
|
|
743
|
+
try {
|
|
744
|
+
const blockers = await finishResidue(deps.fs, deps.git, source.path, path, branch);
|
|
745
|
+
if (blockers.length > 0)
|
|
746
|
+
return {
|
|
747
|
+
error: "remove_blocked",
|
|
748
|
+
message: `quedó residuo en ${path}: ${blockers.join(", ")}`,
|
|
749
|
+
hint: `revisá los archivos nombrados y volvé a correr 'aw worktree release --source ${source.alias} --code ${identity.session}'`,
|
|
750
|
+
};
|
|
751
|
+
completed = true;
|
|
752
|
+
}
|
|
753
|
+
catch (err) {
|
|
754
|
+
return {
|
|
755
|
+
error: "remove_blocked",
|
|
756
|
+
message: `quedó residuo en ${path}: ${err.message}`,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const branchKept = await deleteContainedUnitBranch(deps, target);
|
|
761
|
+
const visible = await detach(deps, path);
|
|
436
762
|
return {
|
|
437
763
|
alias: source.alias,
|
|
438
764
|
session: identity.session,
|
|
439
765
|
path,
|
|
440
766
|
branch,
|
|
441
|
-
released:
|
|
442
|
-
|
|
767
|
+
released: completed,
|
|
768
|
+
residue_completed: completed,
|
|
769
|
+
...(branchKept ? { branch_kept: branchKept } : {}),
|
|
770
|
+
visibility: visible.state,
|
|
771
|
+
...(visible.error ? { visibility_error: visible.error } : {}),
|
|
443
772
|
};
|
|
444
773
|
}
|
|
774
|
+
if (registered.branch !== branch)
|
|
775
|
+
return {
|
|
776
|
+
error: "unit_foreign",
|
|
777
|
+
message: `la unidad ${path} usa ${registered.branch}, no ${branch}`,
|
|
778
|
+
};
|
|
445
779
|
await ensureWorklineMaterialized(deps.fs, deps.paths);
|
|
446
780
|
try {
|
|
447
|
-
await deps.git
|
|
781
|
+
await removeUnitSafely(deps.fs, deps.git, source.path, path);
|
|
448
782
|
}
|
|
449
783
|
catch (err) {
|
|
784
|
+
const now = await deps.git.worktreeList(source.path);
|
|
785
|
+
if (!now.some((w) => samePath(w.path, path)))
|
|
786
|
+
return releaseUnit(deps, target);
|
|
450
787
|
return {
|
|
451
|
-
error: "unit_not_clean",
|
|
452
|
-
message: err.message
|
|
453
|
-
hint:
|
|
788
|
+
error: (await deps.git.isDirty(path)) ? "unit_not_clean" : "remove_blocked",
|
|
789
|
+
message: `quedó ${path}: ${err.message}`,
|
|
790
|
+
hint: `revisá ${path} y volvé a correr 'aw worktree release --source ${source.alias} --code ${identity.session}'`,
|
|
454
791
|
};
|
|
455
792
|
}
|
|
793
|
+
if ((await deps.fs.lstat(path)) !== null) {
|
|
794
|
+
const blockers = await finishResidue(deps.fs, deps.git, source.path, path, branch);
|
|
795
|
+
if (blockers.length > 0)
|
|
796
|
+
return { error: "remove_blocked", message: `quedó ${path}: ${blockers.join(", ")}` };
|
|
797
|
+
}
|
|
798
|
+
const branchKept = await deleteContainedUnitBranch(deps, target);
|
|
799
|
+
const visible = await detach(deps, path);
|
|
456
800
|
return {
|
|
457
801
|
alias: source.alias,
|
|
458
802
|
session: identity.session,
|
|
459
803
|
path,
|
|
460
804
|
branch,
|
|
461
805
|
released: true,
|
|
462
|
-
|
|
806
|
+
...(branchKept ? { branch_kept: branchKept } : {}),
|
|
807
|
+
visibility: visible.state,
|
|
808
|
+
...(visible.error ? { visibility_error: visible.error } : {}),
|
|
463
809
|
};
|
|
464
810
|
}
|
|
811
|
+
/** Only a unit branch, only with a sealed/effective base, only by the observed tip. */
|
|
812
|
+
async function deleteContainedUnitBranch(deps, target) {
|
|
813
|
+
const { source, path, branch, base } = target;
|
|
814
|
+
if (!branch.startsWith("aw/") || branch === base)
|
|
815
|
+
return "no es una rama de unidad borrable";
|
|
816
|
+
if ((await deps.fs.lstat(path)) !== null ||
|
|
817
|
+
(await deps.git.worktreeList(source.path)).some((tree) => tree.branch === branch))
|
|
818
|
+
return "la carpeta o la rama siguen ocupadas; no se borra";
|
|
819
|
+
const tip = await deps.git.refValue(source.path, `refs/heads/${branch}`);
|
|
820
|
+
if (tip === null)
|
|
821
|
+
return null;
|
|
822
|
+
if (base === null)
|
|
823
|
+
return "sin base sellada o rama de trabajo; la rama se conserva";
|
|
824
|
+
const baseTip = await deps.git.refValue(source.path, `refs/heads/${base}`);
|
|
825
|
+
if (baseTip === null || !(await deps.git.isAncestor(source.path, tip, baseTip)))
|
|
826
|
+
return `commits de ${branch} no contenidos en ${base}; la rama se conserva`;
|
|
827
|
+
const deleted = await deps.git.deleteRef(source.path, `refs/heads/${branch}`, tip);
|
|
828
|
+
return deleted.ok ? null : `no se pudo borrar ${branch} con punta ${tip}: ${deleted.why}`;
|
|
829
|
+
}
|
|
465
830
|
/**
|
|
466
831
|
* Collect the residue: one act over the whole workspace, or over one session.
|
|
467
832
|
*
|
|
@@ -485,7 +850,7 @@ async function reclaimUnits(deps, input) {
|
|
|
485
850
|
return {
|
|
486
851
|
error: "no_sources_declared",
|
|
487
852
|
message: "el bloque WORKSPACE no declara ninguna fuente",
|
|
488
|
-
hint: "declará la fuente
|
|
853
|
+
hint: "declará la fuente con aw add-source <alias>:<ruta>:<rama> antes de pedir una recogida",
|
|
489
854
|
};
|
|
490
855
|
}
|
|
491
856
|
if (input.alias !== undefined && !sources.some((s) => s.alias === input.alias)) {
|
|
@@ -498,6 +863,7 @@ async function reclaimUnits(deps, input) {
|
|
|
498
863
|
const key = workspaceKey(deps.paths.workspaceDir());
|
|
499
864
|
const root = await canonicalUnitsRootForRead(deps);
|
|
500
865
|
const sessions = await sessionStates(deps);
|
|
866
|
+
const owns = await hubUnitPaths(deps.fs, deps.paths, root);
|
|
501
867
|
const only = input.sessionCode ?? null;
|
|
502
868
|
const reclaimed = [];
|
|
503
869
|
const retained = [];
|
|
@@ -505,9 +871,13 @@ async function reclaimUnits(deps, input) {
|
|
|
505
871
|
for (const source of sources) {
|
|
506
872
|
if (input.alias !== undefined && source.alias !== input.alias)
|
|
507
873
|
continue;
|
|
508
|
-
const swept = await sweepSource(deps, source, { root, key, only, sessions, block });
|
|
874
|
+
const swept = await sweepSource(deps, source, { root, key, only, sessions, block, owns });
|
|
509
875
|
if ("error" in swept) {
|
|
510
|
-
unreadable.push({
|
|
876
|
+
unreadable.push({
|
|
877
|
+
alias: source.alias,
|
|
878
|
+
error: swept.error,
|
|
879
|
+
...(swept.code ? { code: swept.code } : {}),
|
|
880
|
+
});
|
|
511
881
|
continue;
|
|
512
882
|
}
|
|
513
883
|
reclaimed.push(...swept.reclaimed);
|
|
@@ -519,21 +889,30 @@ async function reclaimUnits(deps, input) {
|
|
|
519
889
|
reclaimed,
|
|
520
890
|
retained,
|
|
521
891
|
unreadable,
|
|
522
|
-
next: retained[0]?.next ??
|
|
892
|
+
next: retained[0]?.next ??
|
|
893
|
+
(unreadable[0]?.code === "SOURCE_PATH_MISSING"
|
|
894
|
+
? `aw add-source ${unreadable[0].alias}:<ruta>`
|
|
895
|
+
: null),
|
|
523
896
|
};
|
|
524
897
|
}
|
|
525
898
|
/** What one source's residue produced, or why its trees could not be read. */
|
|
526
899
|
async function sweepSource(deps, source, ctx) {
|
|
527
|
-
|
|
900
|
+
let repo;
|
|
901
|
+
try {
|
|
902
|
+
repo = await requireSourcePath(deps.fs, source);
|
|
903
|
+
}
|
|
904
|
+
catch (err) {
|
|
905
|
+
return { error: err.message, code: "SOURCE_PATH_MISSING" };
|
|
906
|
+
}
|
|
907
|
+
if (!(await deps.git.isGitRepo(repo)))
|
|
528
908
|
return { reclaimed: [], retained: [] };
|
|
529
909
|
let trees;
|
|
530
910
|
try {
|
|
531
|
-
trees = await deps.git.worktreeList(
|
|
911
|
+
trees = await deps.git.worktreeList(repo);
|
|
532
912
|
}
|
|
533
913
|
catch (err) {
|
|
534
914
|
return { error: err.message };
|
|
535
915
|
}
|
|
536
|
-
const work = resolveSourceBranches(source, ctx.block).work;
|
|
537
916
|
const reclaimed = [];
|
|
538
917
|
const retained = [];
|
|
539
918
|
const vanished = [];
|
|
@@ -541,7 +920,8 @@ async function sweepSource(deps, source, ctx) {
|
|
|
541
920
|
const candidate = candidateOf(tree, ctx);
|
|
542
921
|
if (candidate === null)
|
|
543
922
|
continue;
|
|
544
|
-
const
|
|
923
|
+
const work = await baseForReclaim(deps, source.alias, candidate.session, resolveSourceBranches(source, ctx.block).work);
|
|
924
|
+
const swept = await sweepOne(deps, { ...source, path: repo }, tree, work, candidate);
|
|
545
925
|
if ("retained" in swept) {
|
|
546
926
|
retained.push(swept.retained);
|
|
547
927
|
continue;
|
|
@@ -555,18 +935,124 @@ async function sweepSource(deps, source, ctx) {
|
|
|
555
935
|
reclaimed.push(swept.reclaimed);
|
|
556
936
|
}
|
|
557
937
|
if (vanished.length > 0) {
|
|
558
|
-
const settled = await prune(deps, source, vanished);
|
|
938
|
+
const settled = await prune(deps, { ...source, path: repo }, vanished, ctx.block);
|
|
559
939
|
reclaimed.push(...settled.reclaimed);
|
|
560
940
|
retained.push(...settled.retained);
|
|
561
941
|
}
|
|
942
|
+
const registered = new Set(trees.map((tree) => normalizePath(tree.path)));
|
|
943
|
+
// Also visit previous workspace keys registered by 075, without claiming another hub's units.
|
|
944
|
+
for (const keyEntry of (await deps.fs.lstat(ctx.root)) === null
|
|
945
|
+
? []
|
|
946
|
+
: await deps.fs.list(ctx.root)) {
|
|
947
|
+
if (keyEntry.type !== "dir")
|
|
948
|
+
continue;
|
|
949
|
+
const aliasDir = join(keyEntry.path, source.alias);
|
|
950
|
+
if ((await deps.fs.lstat(aliasDir))?.type !== "dir")
|
|
951
|
+
continue;
|
|
952
|
+
for (const sessionEntry of await deps.fs.list(aliasDir)) {
|
|
953
|
+
if (sessionEntry.type !== "dir" ||
|
|
954
|
+
registered.has(normalizePath(sessionEntry.path)) ||
|
|
955
|
+
!ctx.owns(sessionEntry.path))
|
|
956
|
+
continue;
|
|
957
|
+
const identity = parseUnitPath(ctx.root, sessionEntry.path);
|
|
958
|
+
if (identity?.alias !== source.alias ||
|
|
959
|
+
(ctx.only !== null && !sessionFolderMatches(identity.session, ctx.only)))
|
|
960
|
+
continue;
|
|
961
|
+
const orphan = orphanReason(identity.session, ctx.sessions, false);
|
|
962
|
+
if (orphan === null && ctx.only === null)
|
|
963
|
+
continue;
|
|
964
|
+
const branch = await branchForResidue(deps, repo, identity, sessionEntry.path);
|
|
965
|
+
const where = {
|
|
966
|
+
alias: source.alias,
|
|
967
|
+
session: identity.session,
|
|
968
|
+
path: sessionEntry.path,
|
|
969
|
+
branch,
|
|
970
|
+
};
|
|
971
|
+
try {
|
|
972
|
+
const blockers = await finishResidue(deps.fs, deps.git, repo, sessionEntry.path, branch);
|
|
973
|
+
if (blockers.length > 0) {
|
|
974
|
+
retained.push({
|
|
975
|
+
...where,
|
|
976
|
+
reason: "archivos_no_recuperables",
|
|
977
|
+
detail: blockers.join(", "),
|
|
978
|
+
next: `revisá ${sessionEntry.path} y volvé a correr 'aw worktree reclaim --code ${identity.session}'`,
|
|
979
|
+
});
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
await deps.git.worktreePrune(repo);
|
|
983
|
+
const base = await baseForReclaim(deps, source.alias, identity.session, resolveSourceBranches(source, ctx.block).work);
|
|
984
|
+
const kept = await deleteContainedUnitBranch(deps, {
|
|
985
|
+
source: { ...source, path: repo },
|
|
986
|
+
path: sessionEntry.path,
|
|
987
|
+
branch: branch ?? "",
|
|
988
|
+
base,
|
|
989
|
+
});
|
|
990
|
+
const visible = await detach(deps, sessionEntry.path);
|
|
991
|
+
reclaimed.push({
|
|
992
|
+
...where,
|
|
993
|
+
reason: orphan ?? "already_on_work_branch",
|
|
994
|
+
...(kept ? { branch_kept: kept } : {}),
|
|
995
|
+
...(visible.error ? { visibility_error: visible.error } : {}),
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
catch (err) {
|
|
999
|
+
retained.push({
|
|
1000
|
+
...where,
|
|
1001
|
+
reason: "unreadable",
|
|
1002
|
+
detail: err.message,
|
|
1003
|
+
next: `revisá ${sessionEntry.path} antes de recoger`,
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
562
1008
|
return { reclaimed, retained };
|
|
563
1009
|
}
|
|
1010
|
+
async function branchForResidue(deps, repo, identity, path) {
|
|
1011
|
+
const custody = await readCustody(deps.fs, join(deps.paths.cwdSessionsDir(), identity.session));
|
|
1012
|
+
const recorded = custody.status === "present"
|
|
1013
|
+
? custody.custody.sources.find((s) => s.alias === identity.alias && s.unit_path === path)
|
|
1014
|
+
: undefined;
|
|
1015
|
+
if (recorded?.unit_branch)
|
|
1016
|
+
return recorded.unit_branch;
|
|
1017
|
+
const names = [
|
|
1018
|
+
`aw/${identity.workspaceKey.slice(-8)}/${identity.session}`,
|
|
1019
|
+
unitBranch(identity.session),
|
|
1020
|
+
];
|
|
1021
|
+
for (const branch of names)
|
|
1022
|
+
if (await deps.git.branchExists(repo, branch))
|
|
1023
|
+
return branch;
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
async function baseForReclaim(deps, alias, session, fallback) {
|
|
1027
|
+
const read = await readCustody(deps.fs, join(deps.paths.cwdSessionsDir(), session));
|
|
1028
|
+
if (read.status === "unreadable")
|
|
1029
|
+
return null;
|
|
1030
|
+
return read.status === "present"
|
|
1031
|
+
? (read.custody.sources.find((source) => source.alias === alias)?.base_branch ?? fallback)
|
|
1032
|
+
: fallback;
|
|
1033
|
+
}
|
|
564
1034
|
/** The vanished trees git dropped, or the same ones still listed and why. */
|
|
565
|
-
async function prune(deps, source, vanished) {
|
|
1035
|
+
async function prune(deps, source, vanished, block) {
|
|
566
1036
|
await ensureWorklineMaterialized(deps.fs, deps.paths);
|
|
567
1037
|
try {
|
|
568
1038
|
await deps.git.worktreePrune(source.path);
|
|
569
|
-
|
|
1039
|
+
const reclaimed = [];
|
|
1040
|
+
for (const unit of vanished) {
|
|
1041
|
+
const base = await baseForReclaim(deps, unit.alias, unit.session, resolveSourceBranches(source, block).work);
|
|
1042
|
+
const kept = await deleteContainedUnitBranch(deps, {
|
|
1043
|
+
source,
|
|
1044
|
+
path: unit.path,
|
|
1045
|
+
branch: unit.branch ?? "",
|
|
1046
|
+
base,
|
|
1047
|
+
});
|
|
1048
|
+
const visible = await detach(deps, unit.path);
|
|
1049
|
+
reclaimed.push({
|
|
1050
|
+
...unit,
|
|
1051
|
+
...(kept ? { branch_kept: kept } : {}),
|
|
1052
|
+
...(visible.error ? { visibility_error: visible.error } : {}),
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
return { reclaimed, retained: [] };
|
|
570
1056
|
}
|
|
571
1057
|
catch (err) {
|
|
572
1058
|
return {
|
|
@@ -586,7 +1072,7 @@ async function prune(deps, source, vanished) {
|
|
|
586
1072
|
/** The identity and residue state of a tree this sweep may act on; `null` otherwise. */
|
|
587
1073
|
function candidateOf(tree, ctx) {
|
|
588
1074
|
const identity = tree.main ? null : parseUnitPath(ctx.root, tree.path);
|
|
589
|
-
if (identity === null ||
|
|
1075
|
+
if (identity === null || !ctx.owns(tree.path))
|
|
590
1076
|
return null;
|
|
591
1077
|
if (ctx.only !== null && !sessionFolderMatches(identity.session, ctx.only))
|
|
592
1078
|
return null;
|
|
@@ -612,28 +1098,72 @@ async function sweepOne(deps, source, tree, work, candidate) {
|
|
|
612
1098
|
// was, so there is no work this can lose.
|
|
613
1099
|
return { reclaimed: { ...where, reason: candidate.orphan }, prune: true };
|
|
614
1100
|
}
|
|
1101
|
+
if (work === null)
|
|
1102
|
+
return {
|
|
1103
|
+
retained: {
|
|
1104
|
+
...where,
|
|
1105
|
+
reason: "unreadable",
|
|
1106
|
+
detail: `no se pudo determinar la base sellada ni una rama de trabajo para ${source.alias}`,
|
|
1107
|
+
next: `revisá la custodia de ${candidate.session} o usá 'aw set-working-branch ${source.alias} <rama>' antes de recoger`,
|
|
1108
|
+
},
|
|
1109
|
+
};
|
|
615
1110
|
const verdict = await reclaimability(deps, source, tree, work, candidate.orphan, candidate.session);
|
|
616
1111
|
if (!verdict.free)
|
|
617
1112
|
return { retained: { ...where, ...verdict.retention } };
|
|
618
1113
|
await ensureWorklineMaterialized(deps.fs, deps.paths);
|
|
619
1114
|
try {
|
|
620
|
-
await deps.git
|
|
1115
|
+
await removeUnitSafely(deps.fs, deps.git, source.path, tree.path);
|
|
621
1116
|
}
|
|
622
1117
|
catch (err) {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
632
|
-
|
|
1118
|
+
if ((await deps.git.worktreeList(source.path)).some((w) => samePath(w.path, tree.path)))
|
|
1119
|
+
return {
|
|
1120
|
+
retained: {
|
|
1121
|
+
...where,
|
|
1122
|
+
reason: "remove_refused",
|
|
1123
|
+
detail: err.message,
|
|
1124
|
+
next: `revisá ${tree.path} y volvé a correr 'aw worktree reclaim'; nada se borra por la fuerza`,
|
|
1125
|
+
},
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
if ((await deps.fs.lstat(tree.path)) !== null) {
|
|
1129
|
+
try {
|
|
1130
|
+
const blockers = await finishResidue(deps.fs, deps.git, source.path, tree.path, tree.branch);
|
|
1131
|
+
if (blockers.length > 0)
|
|
1132
|
+
return {
|
|
1133
|
+
retained: {
|
|
1134
|
+
...where,
|
|
1135
|
+
reason: "archivos_no_recuperables",
|
|
1136
|
+
detail: blockers.join(", "),
|
|
1137
|
+
next: `revisá ${tree.path} y volvé a recoger`,
|
|
1138
|
+
},
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
catch (err) {
|
|
1142
|
+
return {
|
|
1143
|
+
retained: {
|
|
1144
|
+
...where,
|
|
1145
|
+
reason: "remove_refused",
|
|
1146
|
+
detail: err.message,
|
|
1147
|
+
next: `revisá ${tree.path} y volvé a recoger`,
|
|
1148
|
+
},
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
633
1151
|
}
|
|
634
|
-
await
|
|
1152
|
+
await deps.git.worktreePrune(source.path);
|
|
1153
|
+
const branchKept = await deleteContainedUnitBranch(deps, {
|
|
1154
|
+
source,
|
|
1155
|
+
path: tree.path,
|
|
1156
|
+
branch: tree.branch ?? "",
|
|
1157
|
+
base: work,
|
|
1158
|
+
});
|
|
1159
|
+
const visible = await detach(deps, tree.path);
|
|
635
1160
|
return {
|
|
636
|
-
reclaimed: {
|
|
1161
|
+
reclaimed: {
|
|
1162
|
+
...where,
|
|
1163
|
+
reason: candidate.orphan ?? "already_on_work_branch",
|
|
1164
|
+
...(branchKept ? { branch_kept: branchKept } : {}),
|
|
1165
|
+
...(visible.error ? { visibility_error: visible.error } : {}),
|
|
1166
|
+
},
|
|
637
1167
|
prune: false,
|
|
638
1168
|
};
|
|
639
1169
|
}
|
|
@@ -675,6 +1205,9 @@ async function reclaimability(deps, source, tree, work, orphan, session) {
|
|
|
675
1205
|
if (tree.head === null) {
|
|
676
1206
|
return held("unreadable", "git no reportó el HEAD de la unidad", `revisá ${tree.path} a mano: sin HEAD no se puede decidir si sus commits ya están en '${work}'`);
|
|
677
1207
|
}
|
|
1208
|
+
if ((await deps.git.refValue(source.path, `refs/heads/${work}`)) === null) {
|
|
1209
|
+
return held("unreadable", `la base sellada '${work}' ya no existe en local`, `restaurá '${work}' antes de recoger la unidad ${tree.path}`);
|
|
1210
|
+
}
|
|
678
1211
|
if (!(await deps.git.isAncestor(source.path, tree.head, work))) {
|
|
679
1212
|
return held("commits_outside_work_branch", `la unidad tiene commits que no están en '${work}'`, recoveryFor(source, tree, orphan, work, session));
|
|
680
1213
|
}
|
|
@@ -683,7 +1216,7 @@ async function reclaimability(deps, source, tree, work, orphan, session) {
|
|
|
683
1216
|
/** How the work of a unit that is not on the working branch gets back onto it. */
|
|
684
1217
|
function recoveryFor(source, tree, orphan, work, session) {
|
|
685
1218
|
if (orphan === "session_closed") {
|
|
686
|
-
return `aw
|
|
1219
|
+
return `aw worktree integrate --source ${source.alias} --code ${session}`;
|
|
687
1220
|
}
|
|
688
1221
|
if (orphan === "session_absent") {
|
|
689
1222
|
return `la sesión ${session} ya no existe: revisá la rama ${tree.branch ?? unitBranch(session)} en ${source.path} y llevá sus commits a '${work}' antes de volver a recoger`;
|
|
@@ -713,16 +1246,21 @@ async function listUnits(deps, input) {
|
|
|
713
1246
|
const key = workspaceKey(deps.paths.workspaceDir());
|
|
714
1247
|
const root = await canonicalUnitsRootForRead(deps);
|
|
715
1248
|
const sessions = await sessionStates(deps);
|
|
1249
|
+
const owns = await hubUnitPaths(deps.fs, deps.paths, root);
|
|
716
1250
|
const units = [];
|
|
717
1251
|
const orphans = [];
|
|
718
1252
|
const unreadable = [];
|
|
719
1253
|
for (const source of block?.fuentes ?? []) {
|
|
720
|
-
const scanned = await scanSource(deps, source, { root, key, only, sessions });
|
|
1254
|
+
const scanned = await scanSource(deps, source, { root, key, only, sessions, owns });
|
|
721
1255
|
if ("error" in scanned) {
|
|
722
1256
|
// Reported, never skipped in silence: a source whose trees cannot be read
|
|
723
1257
|
// would otherwise show up as "no units", which is the one answer that is
|
|
724
1258
|
// certainly wrong — its flows are exactly the ones nobody would clean up.
|
|
725
|
-
unreadable.push({
|
|
1259
|
+
unreadable.push({
|
|
1260
|
+
alias: source.alias,
|
|
1261
|
+
error: scanned.error,
|
|
1262
|
+
...(scanned.code ? { code: scanned.code } : {}),
|
|
1263
|
+
});
|
|
726
1264
|
continue;
|
|
727
1265
|
}
|
|
728
1266
|
units.push(...scanned.units);
|
|
@@ -741,11 +1279,18 @@ async function scanSource(deps, source, ctx) {
|
|
|
741
1279
|
const empty = { units: [], orphans: [] };
|
|
742
1280
|
// Not a repo is not unreadable: it has no worktrees to report, and calling it
|
|
743
1281
|
// an error would put every non-git source in front of the reader forever.
|
|
744
|
-
|
|
1282
|
+
let repo;
|
|
1283
|
+
try {
|
|
1284
|
+
repo = await requireSourcePath(deps.fs, source);
|
|
1285
|
+
}
|
|
1286
|
+
catch (err) {
|
|
1287
|
+
return { error: err.message, code: "SOURCE_PATH_MISSING" };
|
|
1288
|
+
}
|
|
1289
|
+
if (!(await deps.git.isGitRepo(repo)))
|
|
745
1290
|
return empty;
|
|
746
1291
|
let trees;
|
|
747
1292
|
try {
|
|
748
|
-
trees = await deps.git.worktreeList(
|
|
1293
|
+
trees = await deps.git.worktreeList(repo);
|
|
749
1294
|
}
|
|
750
1295
|
catch (err) {
|
|
751
1296
|
return { error: err.message };
|
|
@@ -754,13 +1299,13 @@ async function scanSource(deps, source, ctx) {
|
|
|
754
1299
|
const orphans = [];
|
|
755
1300
|
for (const tree of trees) {
|
|
756
1301
|
const identity = tree.main ? null : parseUnitPath(ctx.root, tree.path);
|
|
757
|
-
if (identity === null ||
|
|
1302
|
+
if (identity === null || !ctx.owns(tree.path))
|
|
758
1303
|
continue;
|
|
759
1304
|
if (ctx.only !== null && identity.session !== ctx.only)
|
|
760
1305
|
continue;
|
|
761
1306
|
const reason = orphanReason(identity.session, ctx.sessions, tree.prunable);
|
|
762
1307
|
if (reason === null)
|
|
763
|
-
units.push(await liveUnit(deps, identity,
|
|
1308
|
+
units.push(await liveUnit(deps, identity, repo, tree));
|
|
764
1309
|
else
|
|
765
1310
|
orphans.push(orphanOf(identity, tree, reason));
|
|
766
1311
|
}
|
|
@@ -825,7 +1370,7 @@ async function sessionStates(deps) {
|
|
|
825
1370
|
for (const folder of folders) {
|
|
826
1371
|
// `.closed` is the only source of a session's state (the session resolver's
|
|
827
1372
|
// own rule); re-deriving it from anything else would let the two disagree.
|
|
828
|
-
if (
|
|
1373
|
+
if ((await readSessionState(deps.fs, folder.path)) === "active")
|
|
829
1374
|
active.add(folder.name);
|
|
830
1375
|
}
|
|
831
1376
|
return { known: new Set(folders.map((f) => f.name)), active };
|
|
@@ -860,11 +1405,15 @@ function unitOf(target, created) {
|
|
|
860
1405
|
*/
|
|
861
1406
|
async function attach(deps, path) {
|
|
862
1407
|
const result = await runMultiroot(deps.fs, deps.env, deps.paths, "attach", { paths: [path] });
|
|
863
|
-
return "error" in result
|
|
1408
|
+
return "error" in result
|
|
1409
|
+
? { state: "unavailable", error: `${result.error}: ${result.hint ?? "sin detalle"}` }
|
|
1410
|
+
: { state: "attached" };
|
|
864
1411
|
}
|
|
865
1412
|
async function detach(deps, path) {
|
|
866
1413
|
const result = await runMultiroot(deps.fs, deps.env, deps.paths, "detach", { paths: [path] });
|
|
867
|
-
return "error" in result
|
|
1414
|
+
return "error" in result
|
|
1415
|
+
? { state: "unavailable", error: `${result.error}: ${result.hint ?? "sin detalle"}` }
|
|
1416
|
+
: { state: "detached" };
|
|
868
1417
|
}
|
|
869
1418
|
function samePath(a, b) {
|
|
870
1419
|
return normalizePath(a) === normalizePath(b);
|