@tacuchi/agent-workflow-cli 25.8.0 → 26.0.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 +106 -0
- package/README.md +6 -3
- package/dist/adapters/git-cli.js +225 -17
- 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 +218 -43
- package/dist/application/capability/design-handler.js.map +1 -1
- package/dist/application/capability/dispatcher.js +182 -19
- 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 +8 -54
- package/dist/application/capability/readiness.js.map +1 -1
- package/dist/application/capability/resolution.js +124 -192
- package/dist/application/capability/resolution.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 +166 -20
- 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 +3 -3
- 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 +9 -1
- package/dist/application/doctor/provider-mcps.js.map +1 -1
- package/dist/application/doctor/provider-skills.js +29 -115
- 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 +36 -39
- 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/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 +202 -25
- 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 +63 -10
- 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 +25 -14
- package/dist/application/flow/close-at-boundary.js.map +1 -1
- 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 +46 -3
- package/dist/application/flow/flow-service.js.map +1 -1
- package/dist/application/flow/internal-actions.js +300 -82
- package/dist/application/flow/internal-actions.js.map +1 -1
- package/dist/application/flow/internal-drive.js +24 -1
- 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 +12 -5
- package/dist/application/flow/reopen-run.js.map +1 -1
- package/dist/application/flow/run-state-service.js +2 -0
- package/dist/application/flow/run-state-service.js.map +1 -1
- package/dist/application/flow/submit.js +584 -117
- 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-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 +33 -5
- 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 +122 -50
- 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/clean-legacy.js +1 -2
- package/dist/application/self/clean-legacy.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 +5 -49
- package/dist/application/self/install-skill.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/skill-files.js +29 -0
- package/dist/application/self/skill-files.js.map +1 -0
- package/dist/application/self/uninstall.js +1 -1
- 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 +42 -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 +134 -13
- 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 +6 -92
- 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 +105 -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 +2 -0
- package/dist/cli/ascii.js.map +1 -1
- 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 -3
- package/dist/cli/commands/amend.js.map +1 -1
- package/dist/cli/commands/capability.js +23 -4
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/designs.js +14 -5
- package/dist/cli/commands/designs.js.map +1 -1
- package/dist/cli/commands/dev-only.js +19 -6
- 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 +5 -1
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/export.js +78 -18
- package/dist/cli/commands/export.js.map +1 -1
- package/dist/cli/commands/fix-git.js +89 -14
- package/dist/cli/commands/fix-git.js.map +1 -1
- package/dist/cli/commands/flow.js +120 -15
- package/dist/cli/commands/flow.js.map +1 -1
- package/dist/cli/commands/generate-launch.js +14 -2
- package/dist/cli/commands/generate-launch.js.map +1 -1
- package/dist/cli/commands/history-update.js +1 -1
- 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 +15 -2
- package/dist/cli/commands/hook.js.map +1 -1
- package/dist/cli/commands/index.js +18 -5
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/merge-state.js +2 -2
- package/dist/cli/commands/merge-state.js.map +1 -1
- package/dist/cli/commands/persist.js +6 -5
- package/dist/cli/commands/persist.js.map +1 -1
- package/dist/cli/commands/project-md-upsert.js +5 -3
- package/dist/cli/commands/project-md-upsert.js.map +1 -1
- package/dist/cli/commands/release-pass.js +3 -3
- package/dist/cli/commands/release-pass.js.map +1 -1
- package/dist/cli/commands/resume.js +12 -3
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/commands/self.js +8 -24
- package/dist/cli/commands/self.js.map +1 -1
- package/dist/cli/commands/session-close.js +64 -9
- package/dist/cli/commands/session-close.js.map +1 -1
- package/dist/cli/commands/session-create.js +4 -2
- 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/sessions.js +16 -3
- package/dist/cli/commands/sessions.js.map +1 -1
- package/dist/cli/commands/set-branch.js +28 -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/skills.js +4 -11
- package/dist/cli/commands/skills.js.map +1 -1
- package/dist/cli/commands/sources.js +4 -0
- package/dist/cli/commands/sources.js.map +1 -1
- package/dist/cli/commands/status.js +35 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/unknown-flags.js +1 -0
- package/dist/cli/commands/unknown-flags.js.map +1 -1
- package/dist/cli/commands/visibility.js +1 -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 +39 -2
- package/dist/cli/commands/workspace-init.js.map +1 -1
- package/dist/cli/commands/workspace-migrate.js +31 -3
- 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 +4 -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 +76 -19
- 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 -453
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/parser.js +4 -0
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/tui/app.js +3 -4
- package/dist/cli/tui/app.js.map +1 -1
- package/dist/cli/tui/components/host-admin-section.js +2 -13
- package/dist/cli/tui/components/host-admin-section.js.map +1 -1
- package/dist/cli/tui/components/tabs-config.js +1 -2
- package/dist/cli/tui/components/tabs-config.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/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/cli/tui/tui-prefs.js +2 -0
- package/dist/cli/tui/tui-prefs.js.map +1 -1
- package/dist/dist-manifest.json +3973 -0
- package/dist/domain/capability/protocol.js +3 -1
- 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 +20 -12
- 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 +265 -130
- package/dist/domain/flow/answer.js.map +1 -1
- package/dist/domain/flow/authority.js +194 -37
- 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 +91 -2
- package/dist/domain/flow/execution-result.js.map +1 -1
- package/dist/domain/flow/run-state.js +352 -24
- 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 +14 -5
- 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 +4 -6
- package/dist/domain/skills.js.map +1 -1
- package/dist/domain/sql-lexer.js +1 -1
- 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 +6 -5
- package/skills/w/SKILL.md +12 -17
- 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/README.md +4 -4
- package/skills/w/exports/export-diagrams/EXPORT.md +4 -4
- 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 +46 -22
- package/skills/w/harness/HARNESS.md +4 -7
- package/skills/w/hooks/hooks.template.json +6 -4
- package/skills/w/loops/CHASSIS.md +12 -6
- package/skills/w/loops/CODE-POLICIES.md +8 -5
- package/skills/w/loops/README.md +2 -2
- 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 +7 -6
- package/skills/w/loops/spec-refine-loop/LOOP.md +1 -1
- 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/README.md +11 -47
- package/skills/w/roles/design/CONTRACT.md +6 -4
- package/skills/w/roles/design/ROLE.md +7 -3
- package/skills/w/roles/diagrams/ROLE.md +1 -1
- package/skills/w/roles/git/ROLE.md +17 -10
- package/skills/w/roles/research/ROLE.md +2 -2
- package/skills/w/roles/sql/ROLE.md +17 -15
- package/skills/w/schemas/design/ui-screen.v1.schema.json +9 -0
- package/dist/application/capability/descriptor-loader.js +0 -109
- package/dist/application/capability/descriptor-loader.js.map +0 -1
- package/dist/application/capability/installed-inventory.js +0 -168
- package/dist/application/capability/installed-inventory.js.map +0 -1
- package/dist/application/self/install-plugin-skills-git.js +0 -226
- package/dist/application/self/install-plugin-skills-git.js.map +0 -1
- package/dist/application/self/install-plugin-skills.js +0 -203
- package/dist/application/self/install-plugin-skills.js.map +0 -1
- package/dist/application/self/plugin-cache-clear.js +0 -133
- package/dist/application/self/plugin-cache-clear.js.map +0 -1
- package/dist/application/self/plugin-cache-reload.js +0 -141
- package/dist/application/self/plugin-cache-reload.js.map +0 -1
- package/dist/application/self/skills-apply.js +0 -623
- package/dist/application/self/skills-apply.js.map +0 -1
- package/dist/application/self/skills-catalog.js +0 -35
- package/dist/application/self/skills-catalog.js.map +0 -1
- package/dist/application/self/skills-change.js +0 -479
- package/dist/application/self/skills-change.js.map +0 -1
- package/dist/application/self/skills-discovery.js +0 -275
- package/dist/application/self/skills-discovery.js.map +0 -1
- package/dist/application/self/skills-manager.js +0 -253
- package/dist/application/self/skills-manager.js.map +0 -1
- package/dist/application/self/skills-payload.js +0 -566
- package/dist/application/self/skills-payload.js.map +0 -1
- package/dist/application/self/skills-registry.js +0 -107
- package/dist/application/self/skills-registry.js.map +0 -1
- package/dist/application/skill-index-service.js +0 -78
- package/dist/application/skill-index-service.js.map +0 -1
- package/dist/cli/commands/plugin-cache.js +0 -25
- package/dist/cli/commands/plugin-cache.js.map +0 -1
- package/dist/cli/commands/skill-index.js +0 -19
- package/dist/cli/commands/skill-index.js.map +0 -1
- package/dist/cli/tui/data/recommended-skills.js +0 -482
- package/dist/cli/tui/data/recommended-skills.js.map +0 -1
- package/dist/cli/tui/tabs/skills-tab.js +0 -664
- package/dist/cli/tui/tabs/skills-tab.js.map +0 -1
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { basename, isAbsolute, join, relative, sep } from "node:path";
|
|
3
|
+
import { isCorrelative, leadingCorrelative, sameCorrelative } from "../domain/correlative.js";
|
|
4
|
+
import { baseDigest } from "../domain/proposal.js";
|
|
5
|
+
import { FOLDER_RESERVATION_MARKER, reservationMarker } from "../domain/reservation.js";
|
|
6
|
+
import { appendClaimEvent } from "./claims-ledger.js";
|
|
3
7
|
import { localDateIso } from "./dates.js";
|
|
4
8
|
import { runNextNumber } from "./dev-only-services.js";
|
|
5
9
|
import { resolveDocsCanon } from "./docs-canon-service.js";
|
|
10
|
+
import { checkExportCatalog } from "./export-catalog-check.js";
|
|
6
11
|
import { appendPublications, publicationRows } from "./history-publications.js";
|
|
12
|
+
import { publishedCorrelatives } from "./history-publications.js";
|
|
7
13
|
import { withCwdLock } from "./lock-service.js";
|
|
8
14
|
import { runReleaseData } from "./release-data-service.js";
|
|
15
|
+
import { readScriptsArtifacts } from "./release-data/artifacts.js";
|
|
16
|
+
import { listGraduatedBundles, listStandaloneSql } from "./release-data/bundles.js";
|
|
17
|
+
import { collectFilesByExt } from "./release-data/common.js";
|
|
9
18
|
import { derivePasses, readReleasePasses } from "./release-pass-ledger.js";
|
|
10
|
-
import { approvalDigest, buildSemanticRequest, parseSemanticResponse, readEnvelopeScope, } from "./semantic-operation/protocol.js";
|
|
19
|
+
import { approvalDigest, buildSemanticRequest, parseSemanticResponse, readEnvelopeScope, semanticDigest, } from "./semantic-operation/protocol.js";
|
|
11
20
|
import { publishArtifacts } from "./semantic-operation/publish.js";
|
|
21
|
+
import { resolveSessionTarget } from "./session-resolver.js";
|
|
12
22
|
export const EXPORT_CATEGORIES = [
|
|
13
23
|
"diagrams",
|
|
14
24
|
"manuals",
|
|
@@ -20,7 +30,7 @@ export const EXPORT_CATEGORIES = [
|
|
|
20
30
|
* command guide. Keep its semantic anchors exported so a small parity guard
|
|
21
31
|
* catches doctrine that drifts away from what the CLI actually sends.
|
|
22
32
|
*/
|
|
23
|
-
export const SCRIPTS_FINAL_STATE_CONTRACT = "Un dossier con 00-ROLLBACK.sql
|
|
33
|
+
export const SCRIPTS_FINAL_STATE_CONTRACT = "Un dossier con README.md y rollback/00-global/00-ROLLBACK.sql obligatorios. Forwards NN-<nombre>.sql en 01-ddl-tablas/, 02-ddl-funciones/, 03-migracion/, 04-inserts/ o 05-grants/, numerados desde 01 dentro de cada carpeta; por cada forward, rollback/<categoría>/NN-<nombre>.rollback.sql. Ningún .sql en la raíz ni directamente en rollback/. El CLI NUNCA ejecuta SQL. El bundle publica el ESTADO FINAL NETO de la secuencia, no una réplica por sesión: lo que nace y muere dentro de la secuencia se omite; lo migrado va directo a su forma final; lo que el contexto declara retirado se omite aunque ningún script lo elimine. 00-ROLLBACK.sql invierte ese ESTADO FINAL en orden seguro para las dependencias, no el reverso literal de los forwards. Reconciliá contra el código además de las sesiones y la base. Excluí identidades concretas y semillas de prueba; conservá sólo objetos compartidos y necesarios para el estado final. Un bundle previo que entra al origen es MATERIAL A RECONCILIAR, no historia intocable: dos bundles que se contradicen publican el estado final neto resultante, nunca su suma cronológica.";
|
|
24
34
|
export const SCRIPTS_FINAL_STATE_CONTRACT_ANCHORS = [
|
|
25
35
|
"ESTADO FINAL NETO",
|
|
26
36
|
"orden seguro para las dependencias",
|
|
@@ -54,19 +64,27 @@ const POLICIES = {
|
|
|
54
64
|
scripts: {
|
|
55
65
|
dir: "docs/scripts",
|
|
56
66
|
shape: "dossier",
|
|
57
|
-
required: ["00-ROLLBACK.sql", "README.md"],
|
|
58
|
-
extensions: [".sql", ".md"],
|
|
67
|
+
required: ["rollback/00-global/00-ROLLBACK.sql", "README.md"],
|
|
68
|
+
extensions: [".sql", ".md", ".json"],
|
|
59
69
|
contract: SCRIPTS_FINAL_STATE_CONTRACT,
|
|
60
70
|
},
|
|
61
71
|
};
|
|
62
|
-
const
|
|
63
|
-
const
|
|
72
|
+
const FORWARD_MAX_BYTES = 512 * 1024;
|
|
73
|
+
const LIMITS = { max_artifacts: 64, max_artifact_bytes: 4 * 1024 * 1024 };
|
|
74
|
+
const SCRIPT_CATEGORIES = [
|
|
75
|
+
"01-ddl-tablas",
|
|
76
|
+
"02-ddl-funciones",
|
|
77
|
+
"03-migracion",
|
|
78
|
+
"04-inserts",
|
|
79
|
+
"05-grants",
|
|
80
|
+
];
|
|
81
|
+
const FORWARD_RE = /^(\d{2})-(?!.*\.rollback\.sql$)[^/]+\.sql$/;
|
|
64
82
|
const EXPORT_BASES = ["sessions", "bundles", "workspace"];
|
|
65
83
|
// ── prepare ──────────────────────────────────────────────────────────────────
|
|
66
84
|
export async function prepareExport(fs, env, paths, category, selection = {},
|
|
67
85
|
// Injected so the midnight boundary is testable: a preparation that outlives
|
|
68
86
|
// the day used to rename its own destination on the next stage.
|
|
69
|
-
now = () => new Date()) {
|
|
87
|
+
now = () => new Date(), catalog) {
|
|
70
88
|
const canon = await resolveDocsCanon(fs, paths, EXPORT_CATEGORIES);
|
|
71
89
|
if (!canon.ok) {
|
|
72
90
|
return {
|
|
@@ -79,15 +97,23 @@ now = () => new Date()) {
|
|
|
79
97
|
};
|
|
80
98
|
}
|
|
81
99
|
const policy = resolvePolicy(category, canon.canon[category]);
|
|
100
|
+
const existingManuals = category === "manuals" && (await fs.exists(join(paths.workspaceDir(), policy.dir)))
|
|
101
|
+
? (await fs.list(join(paths.workspaceDir(), policy.dir)))
|
|
102
|
+
.filter((entry) => entry.type === "file" && entry.name.endsWith(".md"))
|
|
103
|
+
.map((entry) => `${policy.dir}/${entry.name}`)
|
|
104
|
+
: [];
|
|
82
105
|
const resolved = await resolveMaterial(fs, env, paths, category, selection);
|
|
83
106
|
if (!resolved.ok)
|
|
84
107
|
return resolved;
|
|
85
108
|
const material = resolved.value;
|
|
109
|
+
if (category === "scripts") {
|
|
110
|
+
material.sessions = await sessionsWithSql(fs, paths, material.sessions);
|
|
111
|
+
if (materialCount(material) === 0)
|
|
112
|
+
return { ok: false, failure: emptyOrigin(material) };
|
|
113
|
+
}
|
|
86
114
|
// Pinned when the answer echoed them, derived only on a first preparation:
|
|
87
115
|
// re-deriving either at `validate` renames the very unit the answer wrote to,
|
|
88
116
|
// and neither is workspace state that a stale check should be defending.
|
|
89
|
-
const next = selection.next ??
|
|
90
|
-
(await runNextNumber(fs, env, paths, { directory: policy.dir, dryRun: true })).next;
|
|
91
117
|
const date = selection.date ?? localDateIso(now());
|
|
92
118
|
// Rejected HERE and not only when the envelope comes back: `prepare` used to
|
|
93
119
|
// accept any string, mint `…-export-manuals-lunes` and let the composer be
|
|
@@ -103,8 +129,68 @@ now = () => new Date()) {
|
|
|
103
129
|
},
|
|
104
130
|
};
|
|
105
131
|
}
|
|
132
|
+
if (selection.catalog !== undefined) {
|
|
133
|
+
if (catalog === undefined)
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
failure: {
|
|
137
|
+
code: "EXPORT_CATALOG_UNAVAILABLE",
|
|
138
|
+
message: `--catalog ${selection.catalog} no tiene catálogo de sólo lectura disponible`,
|
|
139
|
+
action: "revisá la conexión y repetí prepare",
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
const read = await catalog.lookupColumns(selection.catalog, ["public"]);
|
|
143
|
+
if (!read.ok)
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
failure: {
|
|
147
|
+
code: "EXPORT_CATALOG_UNAVAILABLE",
|
|
148
|
+
message: `no se pudo consultar '${selection.catalog}' (${read.code}): ${read.message}`,
|
|
149
|
+
action: "revisá la conexión de sólo lectura y repetí prepare",
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const scriptsMaterial = category === "scripts" ? await sqlMaterial(fs, paths, material) : null;
|
|
154
|
+
const reservationOwner = await exportReservationOwner(fs, paths, category, policy.dir, date, selection, scriptsMaterial);
|
|
155
|
+
if ("failure" in reservationOwner)
|
|
156
|
+
return { ok: false, failure: reservationOwner.failure };
|
|
157
|
+
const owner = selection.reservationOwner ?? reservationOwner.owner;
|
|
158
|
+
const materialDigest = selection.reservationMaterialDigest ?? semanticDigest(scriptsMaterial);
|
|
159
|
+
const marker = owner === null ? null : reservationMarker(owner, materialDigest);
|
|
160
|
+
if (category === "scripts" && selection.next !== undefined) {
|
|
161
|
+
const markerPath = join(paths.workspaceDir(), policy.dir, `${selection.next}-export-scripts-${date}`, FOLDER_RESERVATION_MARKER);
|
|
162
|
+
if (!(await fs.exists(markerPath)) || (await fs.readText(markerPath)) !== marker) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
failure: {
|
|
166
|
+
code: "EXPORT_NUMBER_TAKEN",
|
|
167
|
+
message: `la reserva ${selection.next} no está intacta`,
|
|
168
|
+
action: "volvé a correr prepare sobre el material vigente",
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const next = selection.next ??
|
|
174
|
+
(await runNextNumber(fs, env, paths, {
|
|
175
|
+
directory: policy.dir,
|
|
176
|
+
...(owner === null
|
|
177
|
+
? { dryRun: true }
|
|
178
|
+
: {
|
|
179
|
+
claim: {
|
|
180
|
+
name: `export-scripts-${date}`,
|
|
181
|
+
owner,
|
|
182
|
+
folder: true,
|
|
183
|
+
material: materialDigest,
|
|
184
|
+
},
|
|
185
|
+
}),
|
|
186
|
+
})).next;
|
|
106
187
|
const unit = policy.shape === "dossier" ? `${policy.dir}/${next}-export-${category}-${date}` : policy.dir;
|
|
107
188
|
const scope = {
|
|
189
|
+
...(selection.code !== undefined ? { code: selection.code } : {}),
|
|
190
|
+
...(selection.catalog !== undefined ? { catalog: selection.catalog } : {}),
|
|
191
|
+
...(owner === null
|
|
192
|
+
? {}
|
|
193
|
+
: { reservationOwner: owner, reservationMaterialDigest: materialDigest }),
|
|
108
194
|
...(selection.sessions !== undefined ? { sessions: selection.sessions } : {}),
|
|
109
195
|
...(selection.since !== undefined ? { since: selection.since } : {}),
|
|
110
196
|
...(selection.source !== undefined ? { source: selection.source } : {}),
|
|
@@ -114,6 +200,16 @@ now = () => new Date()) {
|
|
|
114
200
|
date,
|
|
115
201
|
next,
|
|
116
202
|
};
|
|
203
|
+
scope.seal = Object.fromEntries(Object.entries(scope).map(([key, value]) => [key, baseDigest(JSON.stringify(value))]));
|
|
204
|
+
const bundleOrigin = category === "scripts" ? await manifestOrigin(fs, paths, material) : undefined;
|
|
205
|
+
const allBundles = category === "scripts" ? await listGraduatedBundles(fs, paths.workspaceDir(), paths) : [];
|
|
206
|
+
const availableBundles = category === "scripts" ? allBundles.map((bundle) => basename(bundle.path)) : undefined;
|
|
207
|
+
const looseSql = category === "scripts" ? await listStandaloneSql(fs, paths.workspaceDir(), paths) : [];
|
|
208
|
+
const coveredSql = new Set(allBundles.flatMap((bundle) => bundle.origin_standalone_sql ?? []));
|
|
209
|
+
const unbundledSql = looseSql.filter((file) => !coveredSql.has(relative(paths.workspaceDir(), file.path).split(sep).join("/")));
|
|
210
|
+
const bundleWarnings = category === "scripts" && selection.environment !== undefined
|
|
211
|
+
? await environmentWarnings(fs, paths, selection.environment, material.bundles)
|
|
212
|
+
: [];
|
|
117
213
|
const inventory = {
|
|
118
214
|
category,
|
|
119
215
|
destination: unit,
|
|
@@ -131,6 +227,14 @@ now = () => new Date()) {
|
|
|
131
227
|
excluded: material.excluded,
|
|
132
228
|
exclude_unmatched: material.unmatched,
|
|
133
229
|
environment: material.environment,
|
|
230
|
+
...(bundleOrigin === undefined
|
|
231
|
+
? {}
|
|
232
|
+
: {
|
|
233
|
+
bundle_origin: bundleOrigin,
|
|
234
|
+
available_bundles: availableBundles,
|
|
235
|
+
unbundled_sql: unbundledSql,
|
|
236
|
+
bundle_warnings: bundleWarnings,
|
|
237
|
+
}),
|
|
134
238
|
date,
|
|
135
239
|
};
|
|
136
240
|
const readSet = materialPaths(material);
|
|
@@ -142,22 +246,172 @@ now = () => new Date()) {
|
|
|
142
246
|
// — and the folder this workspace publishes to. The scope rides along so an
|
|
143
247
|
// altered echo cannot pass as the original one.
|
|
144
248
|
inputs: {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
249
|
+
...(scriptsMaterial === null
|
|
250
|
+
? { corpus: material.sessions, bundles: material.bundles, standalone: material.standalone }
|
|
251
|
+
: { sql: scriptsMaterial }),
|
|
148
252
|
dir: policy.dir,
|
|
253
|
+
...(category === "manuals" ? { existing_manuals: existingManuals } : {}),
|
|
149
254
|
scope,
|
|
255
|
+
workspace: paths.workspaceDir(),
|
|
150
256
|
},
|
|
151
257
|
sealed: "el material del alcance o el destino declarado de la categoría",
|
|
152
258
|
scope,
|
|
153
|
-
contract: `${policy.contract} Respondé artifacts con paths dentro de ${unit}${policy.overwritable === null ? "" : ` (o exactamente ${policy.overwritable})`}. El NNN es
|
|
259
|
+
contract: `${policy.contract} Cada pieza divisible (incluidos forwards) admite ${FORWARD_MAX_BYTES} B y se divide en más archivos; un informe, README.md, RUNBOOK.md o rollback/00-global/00-ROLLBACK.sql admite hasta ${LIMITS.max_artifact_bytes} B. Respondé artifacts con paths dentro de ${unit}${policy.overwritable === null ? "" : ` (o exactamente ${policy.overwritable})`}. El NNN aprobado es el número publicado: nunca se reasigna en apply. Copiá 'scope' TAL CUAL, incluido scope.seal, en tu respuesta.${category === "scripts" ? " Declará decisions.supersedes y decisions.requires como listas de nombres de bundles existentes; NO incluyas bundle.json: lo genera el CLI con los digests del origen y lo sella en validate." : ""}`,
|
|
154
260
|
inventory,
|
|
155
|
-
allowedDestinations: [
|
|
261
|
+
allowedDestinations: [
|
|
262
|
+
unit,
|
|
263
|
+
...(category === "manuals"
|
|
264
|
+
? [policy.dir]
|
|
265
|
+
: policy.overwritable === null
|
|
266
|
+
? []
|
|
267
|
+
: [policy.overwritable]),
|
|
268
|
+
],
|
|
156
269
|
limits: LIMITS,
|
|
157
270
|
readSet,
|
|
158
271
|
readSetBytes: readSet.length,
|
|
159
272
|
});
|
|
160
|
-
return {
|
|
273
|
+
return {
|
|
274
|
+
ok: true,
|
|
275
|
+
value: {
|
|
276
|
+
category,
|
|
277
|
+
request,
|
|
278
|
+
dir: policy.dir,
|
|
279
|
+
scope,
|
|
280
|
+
next,
|
|
281
|
+
unit,
|
|
282
|
+
...(category === "manuals" ? { existingManuals } : {}),
|
|
283
|
+
...(bundleOrigin === undefined
|
|
284
|
+
? {}
|
|
285
|
+
: {
|
|
286
|
+
bundleOrigin,
|
|
287
|
+
availableBundles: availableBundles ?? [],
|
|
288
|
+
reservationOwner: owner ?? "",
|
|
289
|
+
reservationMaterialDigest: materialDigest,
|
|
290
|
+
}),
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async function exportReservationOwner(fs, paths, category, dir, date, selection, sql) {
|
|
295
|
+
if (category !== "scripts")
|
|
296
|
+
return { owner: null };
|
|
297
|
+
if (selection.code !== undefined) {
|
|
298
|
+
const session = await resolveSessionTarget(fs, paths, {
|
|
299
|
+
code: selection.code,
|
|
300
|
+
intent: "write",
|
|
301
|
+
});
|
|
302
|
+
if (session.outcome !== "resolved")
|
|
303
|
+
return {
|
|
304
|
+
failure: {
|
|
305
|
+
code: "EXPORT_SCOPE_INVALID",
|
|
306
|
+
message: `--code ${selection.code} no resuelve una sesión activa`,
|
|
307
|
+
action: "seleccioná una sesión activa o quitá --code",
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
return { owner: session.session.folder };
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
owner: `operation-${semanticDigest({
|
|
314
|
+
category,
|
|
315
|
+
dir,
|
|
316
|
+
date,
|
|
317
|
+
sql,
|
|
318
|
+
selection: {
|
|
319
|
+
sessions: selection.sessions,
|
|
320
|
+
since: selection.since,
|
|
321
|
+
source: selection.source,
|
|
322
|
+
from: selection.from,
|
|
323
|
+
exclude: selection.exclude,
|
|
324
|
+
environment: selection.environment,
|
|
325
|
+
catalog: selection.catalog,
|
|
326
|
+
date,
|
|
327
|
+
},
|
|
328
|
+
})}`,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
async function manifestOrigin(fs, paths, material) {
|
|
332
|
+
const digest = async (path) => ({
|
|
333
|
+
path,
|
|
334
|
+
digest: `sha256:${createHash("sha256")
|
|
335
|
+
.update(await fs.readBytes(path))
|
|
336
|
+
.digest("hex")}`,
|
|
337
|
+
});
|
|
338
|
+
const sessions = [];
|
|
339
|
+
for (const session of material.sessions) {
|
|
340
|
+
const relativeRoot = session.path ?? session.folder;
|
|
341
|
+
const root = isAbsolute(relativeRoot) ? relativeRoot : join(paths.workspaceDir(), relativeRoot);
|
|
342
|
+
const files = [];
|
|
343
|
+
for (const sql of await readScriptsArtifacts(fs, root)) {
|
|
344
|
+
files.push({ path: sql.name, digest: (await digest(sql.path)).digest });
|
|
345
|
+
}
|
|
346
|
+
sessions.push({
|
|
347
|
+
session: session.folder,
|
|
348
|
+
files: files.sort((a, b) => a.path.localeCompare(b.path)),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
const standalone_sql = await Promise.all(material.standalone.map(async (file) => ({
|
|
352
|
+
path: relative(paths.workspaceDir(), file.path).split(sep).join("/"),
|
|
353
|
+
digest: (await digest(file.path)).digest,
|
|
354
|
+
})));
|
|
355
|
+
const bundles = [];
|
|
356
|
+
for (const bundle of material.bundles) {
|
|
357
|
+
const files = await Promise.all((await collectFilesByExt(fs, bundle.path, "")).map(async (path) => ({
|
|
358
|
+
path: relative(bundle.path, path).split(sep).join("/"),
|
|
359
|
+
digest: (await digest(path)).digest,
|
|
360
|
+
})));
|
|
361
|
+
bundles.push({
|
|
362
|
+
bundle: basename(bundle.path),
|
|
363
|
+
files: files.sort((a, b) => a.path.localeCompare(b.path)),
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
sessions,
|
|
368
|
+
standalone_sql: standalone_sql.sort((a, b) => a.path.localeCompare(b.path)),
|
|
369
|
+
bundles,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/** Script seals depend on SQL bytes and paths, never on session metadata. */
|
|
373
|
+
async function sessionsWithSql(fs, paths, sessions) {
|
|
374
|
+
const kept = [];
|
|
375
|
+
for (const session of sessions) {
|
|
376
|
+
const root = session.path ?? session.folder;
|
|
377
|
+
if ((await readScriptsArtifacts(fs, isAbsolute(root) ? root : join(paths.workspaceDir(), root)))
|
|
378
|
+
.length > 0)
|
|
379
|
+
kept.push(session);
|
|
380
|
+
}
|
|
381
|
+
return kept;
|
|
382
|
+
}
|
|
383
|
+
async function sqlMaterial(fs, paths, material) {
|
|
384
|
+
const entries = [];
|
|
385
|
+
for (const session of material.sessions) {
|
|
386
|
+
const path = session.path ?? session.folder;
|
|
387
|
+
const root = isAbsolute(path) ? path : join(paths.workspaceDir(), path);
|
|
388
|
+
for (const sql of await readScriptsArtifacts(fs, root)) {
|
|
389
|
+
entries.push({
|
|
390
|
+
origin: session.folder,
|
|
391
|
+
path: sql.name,
|
|
392
|
+
digest: baseDigest(await fs.readText(sql.path)),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
for (const file of material.standalone) {
|
|
397
|
+
entries.push({
|
|
398
|
+
origin: "standalone-sql",
|
|
399
|
+
path: file.path,
|
|
400
|
+
digest: baseDigest(await fs.readText(file.path)),
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
for (const bundle of material.bundles) {
|
|
404
|
+
for (const file of await collectFilesByExt(fs, bundle.path, "")) {
|
|
405
|
+
entries.push({
|
|
406
|
+
origin: basename(bundle.path),
|
|
407
|
+
path: relative(bundle.path, file).split(sep).join("/"),
|
|
408
|
+
digest: `sha256:${createHash("sha256")
|
|
409
|
+
.update(await fs.readBytes(file))
|
|
410
|
+
.digest("hex")}`,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return entries.sort((a, b) => `${a.origin}/${a.path}`.localeCompare(`${b.origin}/${b.path}`));
|
|
161
415
|
}
|
|
162
416
|
/** The category's policy with the folder this workspace actually publishes to. */
|
|
163
417
|
function resolvePolicy(category, dir) {
|
|
@@ -180,7 +434,7 @@ function checkComposableSelection(category, selection) {
|
|
|
180
434
|
// Belonging comes FIRST: telling a category that does not compose its origin
|
|
181
435
|
// which bases exist would send it to fix a value that was never going to be
|
|
182
436
|
// read, and it would be rejected again on the next invocation.
|
|
183
|
-
const named = ["from", "exclude", "environment"].filter((key) => selection[key] !== undefined);
|
|
437
|
+
const named = ["from", "exclude", "environment", "code", "catalog"].filter((key) => selection[key] !== undefined);
|
|
184
438
|
if (category !== "scripts" && named.length > 0) {
|
|
185
439
|
return {
|
|
186
440
|
code: "EXPORT_SCOPE_INVALID",
|
|
@@ -350,6 +604,29 @@ async function filterByEnvironment(fs, paths, environment, bundles) {
|
|
|
350
604
|
},
|
|
351
605
|
};
|
|
352
606
|
}
|
|
607
|
+
async function environmentWarnings(fs, paths, environment, bundles) {
|
|
608
|
+
const passes = derivePasses((await readReleasePasses(fs, paths)).events);
|
|
609
|
+
const recorded = new Set(passes
|
|
610
|
+
.filter((pass) => pass.application.axis === "applied" &&
|
|
611
|
+
pass.application.environments.includes(environment))
|
|
612
|
+
.flatMap((pass) => pass.artifacts.map((artifact) => basename(artifact.replaceAll("\\", "/")))));
|
|
613
|
+
const warnings = [];
|
|
614
|
+
for (const bundle of bundles) {
|
|
615
|
+
const name = basename(bundle.path);
|
|
616
|
+
for (const newer of bundle.superseded_by ?? []) {
|
|
617
|
+
warnings.push({ code: "BUNDLE_SUPERSEDED", bundle: name, detail: `superado por ${newer}` });
|
|
618
|
+
}
|
|
619
|
+
for (const requirement of bundle.requires ?? []) {
|
|
620
|
+
if (!recorded.has(requirement))
|
|
621
|
+
warnings.push({
|
|
622
|
+
code: "BUNDLE_REQUIRES_UNRECORDED",
|
|
623
|
+
bundle: name,
|
|
624
|
+
detail: `${requirement} no consta aplicado en ${environment}`,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return warnings;
|
|
629
|
+
}
|
|
353
630
|
/** One spelling for a path that two different producers wrote. */
|
|
354
631
|
function slashed(path) {
|
|
355
632
|
return path.split(sep).join("/");
|
|
@@ -398,6 +675,14 @@ export function readExportScope(raw) {
|
|
|
398
675
|
return {
|
|
399
676
|
ok: true,
|
|
400
677
|
value: {
|
|
678
|
+
...(scope.code !== undefined ? { code: scope.code } : {}),
|
|
679
|
+
...(scope.catalog !== undefined ? { catalog: scope.catalog } : {}),
|
|
680
|
+
...(scope.reservationOwner !== undefined
|
|
681
|
+
? { reservationOwner: scope.reservationOwner }
|
|
682
|
+
: {}),
|
|
683
|
+
...(scope.reservationMaterialDigest !== undefined
|
|
684
|
+
? { reservationMaterialDigest: scope.reservationMaterialDigest }
|
|
685
|
+
: {}),
|
|
401
686
|
...(scope.sessions !== undefined ? { sessions: scope.sessions } : {}),
|
|
402
687
|
...(scope.since !== undefined ? { since: scope.since } : {}),
|
|
403
688
|
...(scope.source !== undefined ? { source: scope.source } : {}),
|
|
@@ -406,11 +691,44 @@ export function readExportScope(raw) {
|
|
|
406
691
|
...(scope.environment !== undefined ? { environment: scope.environment } : {}),
|
|
407
692
|
date: scope.date,
|
|
408
693
|
next: scope.next,
|
|
694
|
+
...(scope.seal !== undefined ? { seal: scope.seal } : {}),
|
|
409
695
|
},
|
|
410
696
|
};
|
|
411
697
|
}
|
|
412
698
|
/** Why this echo is not the scope `prepare` emitted, or `null` when it is. */
|
|
413
699
|
function scopeShapeError(scope) {
|
|
700
|
+
const allowed = [
|
|
701
|
+
"code",
|
|
702
|
+
"catalog",
|
|
703
|
+
"reservationOwner",
|
|
704
|
+
"reservationMaterialDigest",
|
|
705
|
+
"sessions",
|
|
706
|
+
"since",
|
|
707
|
+
"source",
|
|
708
|
+
"from",
|
|
709
|
+
"exclude",
|
|
710
|
+
"environment",
|
|
711
|
+
"date",
|
|
712
|
+
"next",
|
|
713
|
+
"seal",
|
|
714
|
+
];
|
|
715
|
+
const unknown = Object.keys(scope).find((key) => !allowed.includes(key));
|
|
716
|
+
if (unknown !== undefined)
|
|
717
|
+
return `'${unknown}' no es una clave de scope`;
|
|
718
|
+
if (scope.seal !== undefined) {
|
|
719
|
+
if (typeof scope.seal !== "object" || scope.seal === null || Array.isArray(scope.seal))
|
|
720
|
+
return "'seal' debe contener los sellos por clave";
|
|
721
|
+
const seals = scope.seal;
|
|
722
|
+
for (const key of new Set([
|
|
723
|
+
...Object.keys(scope).filter((k) => k !== "seal"),
|
|
724
|
+
...Object.keys(seals),
|
|
725
|
+
])) {
|
|
726
|
+
if (!(key in scope) ||
|
|
727
|
+
typeof seals[key] !== "string" ||
|
|
728
|
+
seals[key] !== baseDigest(JSON.stringify(scope[key])))
|
|
729
|
+
return `'${key}' no coincide con scope.seal`;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
414
732
|
if (typeof scope.date !== "string" || !DATE_RE.test(scope.date)) {
|
|
415
733
|
return "'date' tiene que ser YYYY-MM-DD";
|
|
416
734
|
}
|
|
@@ -426,7 +744,15 @@ function scopeShapeError(scope) {
|
|
|
426
744
|
if (scope.from !== undefined && !EXPORT_BASES.includes(scope.from)) {
|
|
427
745
|
return `'from' tiene que ser ${EXPORT_BASES.join(", ")}`;
|
|
428
746
|
}
|
|
429
|
-
for (const key of [
|
|
747
|
+
for (const key of [
|
|
748
|
+
"since",
|
|
749
|
+
"source",
|
|
750
|
+
"environment",
|
|
751
|
+
"code",
|
|
752
|
+
"catalog",
|
|
753
|
+
"reservationOwner",
|
|
754
|
+
"reservationMaterialDigest",
|
|
755
|
+
]) {
|
|
430
756
|
if (scope[key] !== undefined && typeof scope[key] !== "string") {
|
|
431
757
|
return `'${key}' tiene que ser texto`;
|
|
432
758
|
}
|
|
@@ -440,7 +766,7 @@ function malformedScope(why) {
|
|
|
440
766
|
return {
|
|
441
767
|
ok: false,
|
|
442
768
|
failure: {
|
|
443
|
-
code: "
|
|
769
|
+
code: "EXPORT_SCOPE_MISMATCH",
|
|
444
770
|
message: `el 'scope' del sobre no tiene la forma que prepare emitió: ${why}`,
|
|
445
771
|
action: "copiá el 'scope' del request TAL CUAL, sin reescribirlo",
|
|
446
772
|
},
|
|
@@ -469,6 +795,8 @@ export function conflictingScopeFlags(echoed, flags) {
|
|
|
469
795
|
["--date", "date"],
|
|
470
796
|
["--from", "from"],
|
|
471
797
|
["--environment", "environment"],
|
|
798
|
+
["--code", "code"],
|
|
799
|
+
["--catalog", "catalog"],
|
|
472
800
|
]) {
|
|
473
801
|
if (flags[key] !== undefined && flags[key] !== echoed[key])
|
|
474
802
|
conflicts.push(flag);
|
|
@@ -477,36 +805,195 @@ export function conflictingScopeFlags(echoed, flags) {
|
|
|
477
805
|
}
|
|
478
806
|
// ── validate ─────────────────────────────────────────────────────────────────
|
|
479
807
|
export function validateExport(raw, prepared) {
|
|
808
|
+
const echoed = readExportScope(raw);
|
|
809
|
+
if (!echoed.ok)
|
|
810
|
+
return echoed;
|
|
811
|
+
if (echoed.value === null && prepared.scope.seal !== undefined) {
|
|
812
|
+
return {
|
|
813
|
+
ok: false,
|
|
814
|
+
failure: {
|
|
815
|
+
code: "EXPORT_SCOPE_MISMATCH",
|
|
816
|
+
message: "falta 'scope' en el sobre",
|
|
817
|
+
action: "copiá el scope completo del request",
|
|
818
|
+
},
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
if (echoed.value !== null) {
|
|
822
|
+
const keys = new Set([...Object.keys(prepared.scope), ...Object.keys(echoed.value)]);
|
|
823
|
+
for (const key of keys) {
|
|
824
|
+
if (JSON.stringify(prepared.scope[key]) !==
|
|
825
|
+
JSON.stringify(echoed.value[key])) {
|
|
826
|
+
return {
|
|
827
|
+
ok: false,
|
|
828
|
+
failure: {
|
|
829
|
+
code: "EXPORT_SCOPE_MISMATCH",
|
|
830
|
+
message: `el alcance cambió en '${key}'`,
|
|
831
|
+
action: "copiá scope y scope.seal del request original sin modificar ninguna clave",
|
|
832
|
+
},
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
480
837
|
const parsed = parseSemanticResponse(raw, prepared.request);
|
|
481
838
|
if (!parsed.ok)
|
|
482
839
|
return parsed;
|
|
840
|
+
const assembled = assembleExportArtifacts(parsed.value, prepared);
|
|
841
|
+
if (!assembled.ok)
|
|
842
|
+
return assembled;
|
|
483
843
|
// From what was prepared, never from the policy table: the folder is the
|
|
484
844
|
// workspace's, and re-reading it here would let the two stages disagree.
|
|
485
845
|
const policy = resolvePolicy(prepared.category, prepared.dir);
|
|
486
|
-
const artifacts =
|
|
846
|
+
const artifacts = assembled.value;
|
|
487
847
|
const inUnit = artifacts.filter((a) => a.path !== policy.overwritable);
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
848
|
+
const manual = prepared.category === "manuals" ? manualShape(artifacts, policy, prepared.unit) : null;
|
|
849
|
+
const replacements = manual === null
|
|
850
|
+
? []
|
|
851
|
+
: artifacts
|
|
852
|
+
.filter((artifact) => (prepared.existingManuals ?? []).includes(artifact.path))
|
|
853
|
+
.map((artifact) => artifact.path);
|
|
854
|
+
const overwrites = manual === null
|
|
855
|
+
? artifacts.some((a) => a.path === policy.overwritable)
|
|
856
|
+
? policy.overwritable
|
|
857
|
+
: null
|
|
858
|
+
: replacements.includes(policy.overwritable ?? "")
|
|
859
|
+
? policy.overwritable
|
|
860
|
+
: null;
|
|
861
|
+
const shape = manual === null ? checkShape(inUnit, policy, prepared.unit) : manual.failure;
|
|
492
862
|
if (shape !== null)
|
|
493
863
|
return { ok: false, failure: shape };
|
|
864
|
+
if (policy.shape === "document" &&
|
|
865
|
+
!(inUnit[0]?.path.startsWith(`${policy.dir}/${prepared.next}-`) &&
|
|
866
|
+
/^[^/]+\.md$/.test(inUnit[0].path.slice(`${policy.dir}/${prepared.next}-`.length)))) {
|
|
867
|
+
return {
|
|
868
|
+
ok: false,
|
|
869
|
+
failure: reject(`el informe debe estar en ${policy.dir}/${prepared.next}-<slug>.md`),
|
|
870
|
+
};
|
|
871
|
+
}
|
|
494
872
|
return {
|
|
495
873
|
ok: true,
|
|
496
874
|
value: {
|
|
497
875
|
preview: {
|
|
498
876
|
category: prepared.category,
|
|
499
|
-
destination: prepared.unit,
|
|
877
|
+
destination: manual !== null && manual.mode !== "regenerate" ? policy.dir : prepared.unit,
|
|
500
878
|
files: artifacts.map((a) => ({
|
|
501
879
|
path: a.path,
|
|
502
880
|
bytes: Buffer.byteLength(a.content, "utf8"),
|
|
503
881
|
})),
|
|
504
882
|
overwrites,
|
|
883
|
+
...(manual === null ? {} : { mode: manual.mode, replacements }),
|
|
884
|
+
},
|
|
885
|
+
approval_digest: approvalDigest({ ...parsed.value, artifacts }),
|
|
886
|
+
},
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
/** When requested, the target catalog is consulted again over the proposed forwards. */
|
|
890
|
+
export async function validateExportWithCatalog(raw, prepared, catalog) {
|
|
891
|
+
const validated = validateExport(raw, prepared);
|
|
892
|
+
if (!validated.ok || prepared.scope.catalog === undefined)
|
|
893
|
+
return validated;
|
|
894
|
+
if (catalog === undefined)
|
|
895
|
+
return {
|
|
896
|
+
ok: false,
|
|
897
|
+
failure: {
|
|
898
|
+
code: "EXPORT_CATALOG_UNAVAILABLE",
|
|
899
|
+
message: `no hay catálogo disponible para '${prepared.scope.catalog}'`,
|
|
900
|
+
action: "revisá la conexión y repetí validate",
|
|
901
|
+
},
|
|
902
|
+
};
|
|
903
|
+
const parsed = parseSemanticResponse(raw, prepared.request);
|
|
904
|
+
if (!parsed.ok)
|
|
905
|
+
return parsed;
|
|
906
|
+
const assembled = assembleExportArtifacts(parsed.value, prepared);
|
|
907
|
+
if (!assembled.ok)
|
|
908
|
+
return assembled;
|
|
909
|
+
const checked = await checkExportCatalog(assembled.value, prepared.unit, prepared.scope.catalog, catalog);
|
|
910
|
+
if (!checked.ok)
|
|
911
|
+
return checked;
|
|
912
|
+
return {
|
|
913
|
+
ok: true,
|
|
914
|
+
value: {
|
|
915
|
+
...validated.value,
|
|
916
|
+
preview: {
|
|
917
|
+
...validated.value.preview,
|
|
918
|
+
unverified: checked.value.unverified,
|
|
505
919
|
},
|
|
506
|
-
approval_digest: approvalDigest(parsed.value),
|
|
507
920
|
},
|
|
508
921
|
};
|
|
509
922
|
}
|
|
923
|
+
function manualShape(artifacts, policy, unit) {
|
|
924
|
+
const files = artifacts.filter((artifact) => artifact.path !== policy.overwritable);
|
|
925
|
+
if (files.length === 0)
|
|
926
|
+
return { mode: "complement", failure: null };
|
|
927
|
+
const direct = files.every((artifact) => {
|
|
928
|
+
const name = artifact.path.slice(policy.dir.length + 1);
|
|
929
|
+
return (artifact.path.startsWith(`${policy.dir}/`) &&
|
|
930
|
+
!name.includes("/") &&
|
|
931
|
+
/^[a-z0-9]+(?:-[a-z0-9]+)*\.md$/.test(name));
|
|
932
|
+
});
|
|
933
|
+
if (direct)
|
|
934
|
+
return { mode: "flat", failure: null };
|
|
935
|
+
if (files.every((artifact) => artifact.path.startsWith(`${unit}/`))) {
|
|
936
|
+
return { mode: "regenerate", failure: checkShape(files, policy, unit) };
|
|
937
|
+
}
|
|
938
|
+
return {
|
|
939
|
+
mode: "flat",
|
|
940
|
+
failure: reject("manuals admite sólo INDEX.md, archivos <slug>.md planos o un dossier numerado"),
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function assembleExportArtifacts(response, prepared) {
|
|
944
|
+
const artifacts = response.artifacts ?? [];
|
|
945
|
+
if (prepared.category !== "scripts")
|
|
946
|
+
return { ok: true, value: artifacts };
|
|
947
|
+
if (artifacts.some((a) => a.path === `${prepared.unit}/bundle.json`)) {
|
|
948
|
+
return {
|
|
949
|
+
ok: false,
|
|
950
|
+
failure: reject("bundle.json lo genera el CLI; no lo incluyas en artifacts"),
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
const decisions = response.decisions ?? {};
|
|
954
|
+
const bundles = new Set(prepared.availableBundles ?? []);
|
|
955
|
+
const lists = { supersedes: [], requires: [] };
|
|
956
|
+
for (const key of ["supersedes", "requires"]) {
|
|
957
|
+
const value = decisions[key];
|
|
958
|
+
if (!Array.isArray(value) || !value.every((name) => typeof name === "string")) {
|
|
959
|
+
return {
|
|
960
|
+
ok: false,
|
|
961
|
+
failure: reject(`decisions.${key} debe ser una lista de nombres de bundles`),
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
const names = value;
|
|
965
|
+
if (names.includes(basename(prepared.unit)))
|
|
966
|
+
return { ok: false, failure: reject(`${key} incluye el bundle que se está publicando`) };
|
|
967
|
+
const missing = names.filter((name) => !bundles.has(name));
|
|
968
|
+
if (missing.length > 0)
|
|
969
|
+
return {
|
|
970
|
+
ok: false,
|
|
971
|
+
failure: reject(`${key}: bundle(s) inexistente(s): ${missing.join(", ")}`),
|
|
972
|
+
};
|
|
973
|
+
if (new Set(names).size !== names.length)
|
|
974
|
+
return { ok: false, failure: reject(`${key} repite un bundle`) };
|
|
975
|
+
lists[key] = [...names].sort();
|
|
976
|
+
}
|
|
977
|
+
const overlap = lists.supersedes.filter((name) => lists.requires.includes(name));
|
|
978
|
+
if (overlap.length > 0)
|
|
979
|
+
return {
|
|
980
|
+
ok: false,
|
|
981
|
+
failure: reject(`no se puede superar y requerir el mismo bundle: ${overlap.join(", ")}`),
|
|
982
|
+
};
|
|
983
|
+
const manifest = {
|
|
984
|
+
version: 1,
|
|
985
|
+
supersedes: lists.supersedes,
|
|
986
|
+
requires: lists.requires,
|
|
987
|
+
origin: prepared.bundleOrigin ?? { sessions: [], standalone_sql: [], bundles: [] },
|
|
988
|
+
};
|
|
989
|
+
return {
|
|
990
|
+
ok: true,
|
|
991
|
+
value: [
|
|
992
|
+
...artifacts,
|
|
993
|
+
{ path: `${prepared.unit}/bundle.json`, content: `${JSON.stringify(manifest, null, 2)}\n` },
|
|
994
|
+
],
|
|
995
|
+
};
|
|
996
|
+
}
|
|
510
997
|
function checkShape(artifacts, policy, unit) {
|
|
511
998
|
if (artifacts.length === 0)
|
|
512
999
|
return reject("la propuesta no trae ningún artefacto del dossier");
|
|
@@ -515,6 +1002,17 @@ function checkShape(artifacts, policy, unit) {
|
|
|
515
1002
|
}
|
|
516
1003
|
const names = artifacts.map((a) => a.path.slice(unit.length + 1));
|
|
517
1004
|
for (const artifact of artifacts) {
|
|
1005
|
+
const name = artifact.path.slice(unit.length + 1);
|
|
1006
|
+
const indivisible = policy.shape === "document" ||
|
|
1007
|
+
["README.md", "RUNBOOK.md", "00-ROLLBACK.sql", "rollback/00-global/00-ROLLBACK.sql"].includes(name);
|
|
1008
|
+
const bytes = Buffer.byteLength(artifact.content, "utf8");
|
|
1009
|
+
if (!indivisible && bytes > FORWARD_MAX_BYTES) {
|
|
1010
|
+
return {
|
|
1011
|
+
code: "EXPORT_LIMIT_EXCEEDED",
|
|
1012
|
+
message: `'${artifact.path}' pesa ${bytes} B y el máximo por pieza divisible es ${FORWARD_MAX_BYTES} B`,
|
|
1013
|
+
action: "partí el forward en archivos NN-<nombre>.sql consecutivos y repetí validate",
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
518
1016
|
if (!policy.extensions.some((ext) => artifact.path.endsWith(ext))) {
|
|
519
1017
|
return reject(`'${artifact.path}' no usa una extensión permitida (${policy.extensions.join(", ")})`);
|
|
520
1018
|
}
|
|
@@ -522,30 +1020,119 @@ function checkShape(artifacts, policy, unit) {
|
|
|
522
1020
|
return reject(`'${artifact.path}' está vacío`);
|
|
523
1021
|
}
|
|
524
1022
|
}
|
|
1023
|
+
if (policy.required.includes("rollback/00-global/00-ROLLBACK.sql")) {
|
|
1024
|
+
return checkScriptsStructure(artifacts, unit);
|
|
1025
|
+
}
|
|
525
1026
|
for (const required of policy.required) {
|
|
526
1027
|
if (!names.includes(required))
|
|
527
1028
|
return reject(`falta '${required}' en el dossier`);
|
|
528
1029
|
}
|
|
529
|
-
return
|
|
530
|
-
}
|
|
531
|
-
/**
|
|
532
|
-
function
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
.
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
/** Check all structural errors together, including broken citations in delivery instructions. */
|
|
1033
|
+
function checkScriptsStructure(artifacts, unit) {
|
|
1034
|
+
const names = new Set(artifacts.map((a) => a.path.slice(unit.length + 1)));
|
|
1035
|
+
// The CLI generates this file in the next phase; agents must not supply it.
|
|
1036
|
+
names.add("bundle.json");
|
|
1037
|
+
const failures = [
|
|
1038
|
+
...["README.md", "rollback/00-global/00-ROLLBACK.sql"]
|
|
1039
|
+
.filter((required) => !names.has(required))
|
|
1040
|
+
.map((required) => `falta '${required}' en el dossier`),
|
|
1041
|
+
...scriptLayoutFailures(names),
|
|
1042
|
+
...scriptCitationFailures(artifacts, unit, names),
|
|
1043
|
+
];
|
|
1044
|
+
return failures.length === 0 ? null : reject(failures.join("; "));
|
|
1045
|
+
}
|
|
1046
|
+
function scriptLayoutFailures(names) {
|
|
1047
|
+
const failures = [];
|
|
1048
|
+
const forwards = new Set();
|
|
1049
|
+
const reverses = new Set();
|
|
1050
|
+
for (const name of names) {
|
|
1051
|
+
const classified = classifyScriptPath(name);
|
|
1052
|
+
if (classified.kind === "forward")
|
|
1053
|
+
forwards.add(name);
|
|
1054
|
+
if (classified.kind === "reverse")
|
|
1055
|
+
reverses.add(classified.forward);
|
|
1056
|
+
if (classified.kind === "invalid")
|
|
1057
|
+
failures.push(classified.why);
|
|
1058
|
+
}
|
|
1059
|
+
if (forwards.size === 0)
|
|
1060
|
+
failures.push("el bundle no trae ningún forward NN-<nombre>.sql");
|
|
1061
|
+
for (const forward of forwards) {
|
|
1062
|
+
const inverse = `rollback/${forward.replace(/\.sql$/, ".rollback.sql")}`;
|
|
1063
|
+
if (!names.has(inverse))
|
|
1064
|
+
failures.push(`falta '${inverse}' para '${forward}'`);
|
|
1065
|
+
}
|
|
1066
|
+
for (const reverse of reverses) {
|
|
1067
|
+
if (!forwards.has(reverse))
|
|
1068
|
+
failures.push(`rollback huérfano para '${reverse}'`);
|
|
1069
|
+
}
|
|
1070
|
+
return [...failures, ...scriptNumberingFailures(forwards)];
|
|
1071
|
+
}
|
|
1072
|
+
function classifyScriptPath(path) {
|
|
1073
|
+
if (!path.endsWith(".sql") || path === "rollback/00-global/00-ROLLBACK.sql")
|
|
1074
|
+
return { kind: "other" };
|
|
1075
|
+
if (path.startsWith("rollback/")) {
|
|
1076
|
+
const match = /^rollback\/(01-ddl-tablas|02-ddl-funciones|03-migracion|04-inserts|05-grants)\/(\d{2}-[^/]+)\.rollback\.sql$/.exec(path);
|
|
1077
|
+
return match
|
|
1078
|
+
? { kind: "reverse", forward: `${match[1]}/${match[2]}.sql` }
|
|
1079
|
+
: { kind: "invalid", why: `'${path}' no es un rollback acoplado válido` };
|
|
1080
|
+
}
|
|
1081
|
+
const [folder, filename, extra] = path.split("/");
|
|
1082
|
+
if (extra === undefined &&
|
|
1083
|
+
filename !== undefined &&
|
|
1084
|
+
SCRIPT_CATEGORIES.includes(folder) &&
|
|
1085
|
+
FORWARD_RE.test(filename))
|
|
1086
|
+
return { kind: "forward" };
|
|
1087
|
+
return {
|
|
1088
|
+
kind: "invalid",
|
|
1089
|
+
why: `'${path}' no es un forward NN-<nombre>.sql en una carpeta de categoría`,
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
function scriptNumberingFailures(forwards) {
|
|
1093
|
+
const failures = [];
|
|
1094
|
+
for (const category of SCRIPT_CATEGORIES) {
|
|
1095
|
+
const numbers = [...forwards]
|
|
1096
|
+
.filter((name) => name.startsWith(`${category}/`))
|
|
1097
|
+
.map((name) => Number.parseInt(name.slice(category.length + 1, category.length + 3), 10))
|
|
1098
|
+
.sort((a, b) => a - b);
|
|
1099
|
+
if (numbers.some((n, i) => n !== i + 1))
|
|
1100
|
+
failures.push(`numeración de forwards no continua desde 01 en ${category}: ${numbers.join(", ")}`);
|
|
1101
|
+
}
|
|
1102
|
+
return failures;
|
|
1103
|
+
}
|
|
1104
|
+
function scriptCitationFailures(artifacts, unit, names) {
|
|
1105
|
+
const failures = [];
|
|
1106
|
+
for (const artifact of artifacts) {
|
|
1107
|
+
const name = artifact.path.slice(unit.length + 1);
|
|
1108
|
+
if (name !== "README.md" && !/(^|\/)RUNBOOK\.md$/i.test(name))
|
|
1109
|
+
continue;
|
|
1110
|
+
for (const citation of citedBundleFiles(artifact.content)) {
|
|
1111
|
+
if (!names.has(citation))
|
|
1112
|
+
failures.push(`${name} cita '${citation}' que no existe en el bundle`);
|
|
543
1113
|
}
|
|
544
1114
|
}
|
|
545
|
-
return
|
|
1115
|
+
return failures;
|
|
1116
|
+
}
|
|
1117
|
+
function citedBundleFiles(text) {
|
|
1118
|
+
const names = new Set();
|
|
1119
|
+
for (const match of text.matchAll(/`([^`]+)`|\[[^\]]+\]\(([^)]+)\)/g)) {
|
|
1120
|
+
const raw = match[1] ?? match[2] ?? "";
|
|
1121
|
+
if (/(?:^|\/)\d{3,}-[^/]+\//.test(raw))
|
|
1122
|
+
continue; // another bundle is not an internal citation
|
|
1123
|
+
for (const token of raw.split(/\s+/)) {
|
|
1124
|
+
const candidate = token
|
|
1125
|
+
.trim()
|
|
1126
|
+
.replace(/^['"`]+|['"`,;]+$/g, "")
|
|
1127
|
+
.replace(/^\.\//, "");
|
|
1128
|
+
if (/^(?:[\w-]+\/)*[\w.-]+\.(?:sql|md|json)$/i.test(candidate))
|
|
1129
|
+
names.add(candidate);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
return [...names];
|
|
546
1133
|
}
|
|
547
|
-
export async function applyExport(fs,
|
|
548
|
-
const validated =
|
|
1134
|
+
export async function applyExport(fs, _env, paths, input, catalog) {
|
|
1135
|
+
const validated = await validateExportWithCatalog(input.raw, input.prepared, catalog);
|
|
549
1136
|
if (!validated.ok)
|
|
550
1137
|
return validated;
|
|
551
1138
|
if (validated.value.approval_digest !== input.approval) {
|
|
@@ -558,12 +1145,14 @@ export async function applyExport(fs, env, paths, input) {
|
|
|
558
1145
|
},
|
|
559
1146
|
};
|
|
560
1147
|
}
|
|
561
|
-
|
|
1148
|
+
const replacing = validated.value.preview.replacements ??
|
|
1149
|
+
(validated.value.preview.overwrites === null ? [] : [validated.value.preview.overwrites]);
|
|
1150
|
+
if (replacing.length > 0 && input.allowOverwrite !== true) {
|
|
562
1151
|
return {
|
|
563
1152
|
ok: false,
|
|
564
1153
|
failure: {
|
|
565
1154
|
code: "OVERWRITE_NOT_AUTHORIZED",
|
|
566
|
-
message: `la propuesta reemplaza
|
|
1155
|
+
message: `la propuesta reemplaza ${replacing.join(", ")}`,
|
|
567
1156
|
action: "confirmá con --overwrite si querés reemplazarlo, o quitalo de la propuesta",
|
|
568
1157
|
},
|
|
569
1158
|
};
|
|
@@ -571,18 +1160,94 @@ export async function applyExport(fs, env, paths, input) {
|
|
|
571
1160
|
const parsed = parseSemanticResponse(input.raw, input.prepared.request);
|
|
572
1161
|
if (!parsed.ok)
|
|
573
1162
|
return parsed;
|
|
1163
|
+
const assembled = assembleExportArtifacts(parsed.value, input.prepared);
|
|
1164
|
+
if (!assembled.ok)
|
|
1165
|
+
return assembled;
|
|
574
1166
|
const policy = resolvePolicy(input.prepared.category, input.prepared.dir);
|
|
575
1167
|
const result = await withCwdLock(fs, paths, async () => {
|
|
576
|
-
const
|
|
577
|
-
const
|
|
1168
|
+
const number = input.prepared.next;
|
|
1169
|
+
const folderMarker = input.prepared.category === "scripts"
|
|
1170
|
+
? join(paths.workspaceDir(), input.prepared.unit, FOLDER_RESERVATION_MARKER)
|
|
1171
|
+
: null;
|
|
1172
|
+
const expected = input.prepared.reservationOwner === undefined
|
|
1173
|
+
? null
|
|
1174
|
+
: reservationMarker(input.prepared.reservationOwner, input.prepared.reservationMaterialDigest);
|
|
1175
|
+
if (folderMarker !== null &&
|
|
1176
|
+
(!(await fs.exists(folderMarker)) || (await fs.readText(folderMarker)) !== expected)) {
|
|
1177
|
+
return {
|
|
1178
|
+
ok: false,
|
|
1179
|
+
failure: {
|
|
1180
|
+
code: "EXPORT_NUMBER_TAKEN",
|
|
1181
|
+
message: `la reserva ${number} ya no pertenece a esta operación`,
|
|
1182
|
+
action: "volvé a correr prepare",
|
|
1183
|
+
},
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
const entries = (await fs.exists(join(paths.workspaceDir(), policy.dir)))
|
|
1187
|
+
? await fs.list(join(paths.workspaceDir(), policy.dir))
|
|
1188
|
+
: [];
|
|
1189
|
+
const usedNumbers = await publishedCorrelatives(fs, paths.cwdHistoryFile(), policy.dir);
|
|
1190
|
+
for (const pass of (await readReleasePasses(fs, paths)).events) {
|
|
1191
|
+
if (pass.event !== "linked" || !pass.artifact.startsWith(`${policy.dir}/`))
|
|
1192
|
+
continue;
|
|
1193
|
+
const recorded = leadingCorrelative(pass.artifact.slice(policy.dir.length + 1).split("/")[0] ?? "");
|
|
1194
|
+
if (recorded !== null)
|
|
1195
|
+
usedNumbers.add(recorded);
|
|
1196
|
+
}
|
|
1197
|
+
const occupant = entries.find((entry) => {
|
|
1198
|
+
const found = leadingCorrelative(entry.name);
|
|
1199
|
+
return found !== null && sameCorrelative(found, number);
|
|
1200
|
+
});
|
|
1201
|
+
if (validated.value.preview.mode !== "complement" &&
|
|
1202
|
+
validated.value.preview.mode !== "flat" &&
|
|
1203
|
+
((occupant !== undefined &&
|
|
1204
|
+
(folderMarker === null || occupant.name !== basename(input.prepared.unit))) ||
|
|
1205
|
+
[...usedNumbers].some((n) => sameCorrelative(n, number)))) {
|
|
1206
|
+
return {
|
|
1207
|
+
ok: false,
|
|
1208
|
+
failure: {
|
|
1209
|
+
code: "EXPORT_NUMBER_TAKEN",
|
|
1210
|
+
message: `el correlativo ${number} ya está ocupado por '${occupant?.name ?? "una publicación registrada"}'`,
|
|
1211
|
+
action: "volvé a correr prepare y validate con un número nuevo",
|
|
1212
|
+
},
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
const artifacts = assembled.value.map((artifact) => ({
|
|
1216
|
+
...artifact,
|
|
1217
|
+
overwrite: input.allowOverwrite === true && replacing.includes(artifact.path),
|
|
1218
|
+
}));
|
|
578
1219
|
// Whole dossier or nothing: `publishArtifacts` restores every previous
|
|
579
1220
|
// state on the first failure.
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
1221
|
+
const alreadyWritten = folderMarker !== null &&
|
|
1222
|
+
(await Promise.all(artifacts.map(async (artifact) => {
|
|
1223
|
+
const target = join(paths.workspaceDir(), artifact.path);
|
|
1224
|
+
try {
|
|
1225
|
+
return (await fs.readText(target)) === artifact.content;
|
|
1226
|
+
}
|
|
1227
|
+
catch {
|
|
1228
|
+
return false;
|
|
1229
|
+
}
|
|
1230
|
+
}))).every(Boolean);
|
|
1231
|
+
const published = alreadyWritten
|
|
1232
|
+
? { ok: true, value: { written: artifacts.map((artifact) => artifact.path) } }
|
|
1233
|
+
: await publishArtifacts(fs, paths.workspaceDir(), artifacts, { overwrite: false });
|
|
583
1234
|
// Under the SAME lock as the write, for the same reason as in `persist`: two
|
|
584
1235
|
// concurrent publications outside it would lose one of the two rows.
|
|
585
1236
|
if (published.ok) {
|
|
1237
|
+
if (folderMarker !== null && input.prepared.reservationOwner !== undefined) {
|
|
1238
|
+
await appendClaimEvent(fs, paths, {
|
|
1239
|
+
at: new Date().toISOString(),
|
|
1240
|
+
event: "published",
|
|
1241
|
+
claim: {
|
|
1242
|
+
category: basename(policy.dir),
|
|
1243
|
+
correlative: number,
|
|
1244
|
+
name: basename(input.prepared.unit).slice(number.length + 1),
|
|
1245
|
+
owner: input.prepared.reservationOwner,
|
|
1246
|
+
},
|
|
1247
|
+
cause: "aw export-scripts apply: dossier aprobado publicado",
|
|
1248
|
+
});
|
|
1249
|
+
await fs.remove(folderMarker);
|
|
1250
|
+
}
|
|
586
1251
|
await appendPublications(fs, paths.cwdHistoryFile(), publicationRows(published.value.written, `export-${input.prepared.category}`));
|
|
587
1252
|
}
|
|
588
1253
|
return published;
|
|
@@ -601,34 +1266,6 @@ export async function applyExport(fs, env, paths, input) {
|
|
|
601
1266
|
return result;
|
|
602
1267
|
return { ok: true, value: { category: input.prepared.category, written: result.value.written } };
|
|
603
1268
|
}
|
|
604
|
-
/**
|
|
605
|
-
* The number in the answer was consultative. The real one is minted inside the
|
|
606
|
-
* lock, so the destination is rebuilt here — never trusted from the proposal.
|
|
607
|
-
*/
|
|
608
|
-
function renumber(artifact, prepared, minted, policy) {
|
|
609
|
-
if (artifact.path === policy.overwritable)
|
|
610
|
-
return artifact;
|
|
611
|
-
if (policy.shape === "document") {
|
|
612
|
-
const name = artifact.path
|
|
613
|
-
.slice(policy.dir.length + 1)
|
|
614
|
-
.replace(new RegExp(`^${CORRELATIVE_SOURCE}-`), "");
|
|
615
|
-
return { path: `${policy.dir}/${minted}-${name}`, content: artifact.content };
|
|
616
|
-
}
|
|
617
|
-
// The number to move is the UNIT's own, which is its LAST segment — never the
|
|
618
|
-
// first `/NNN-` of the path. Since the category's folder became configurable,
|
|
619
|
-
// a canon that is itself numbered (`docs/003-manuales`) would eat the
|
|
620
|
-
// replacement: the export would be approved into `docs/003-manuales/…` and
|
|
621
|
-
// written into `docs/001-manuales/…`, a folder outside `allowed_destinations`
|
|
622
|
-
// that nothing downstream re-checks.
|
|
623
|
-
const unitName = prepared.unit
|
|
624
|
-
.slice(policy.dir.length + 1)
|
|
625
|
-
.replace(new RegExp(`^${CORRELATIVE_SOURCE}-`), `${minted}-`);
|
|
626
|
-
const unit = `${policy.dir}/${unitName}`;
|
|
627
|
-
return {
|
|
628
|
-
path: `${unit}/${artifact.path.slice(prepared.unit.length + 1)}`,
|
|
629
|
-
content: artifact.content,
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
1269
|
function reject(message) {
|
|
633
1270
|
return {
|
|
634
1271
|
code: "EXPORT_SHAPE_INVALID",
|