wingfoil 0.2.2 → 0.3.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/README.md +83 -43
- package/dist/agent/discovery.d.ts +72 -0
- package/dist/agent/discovery.d.ts.map +1 -0
- package/dist/agent/discovery.js +210 -0
- package/dist/agent/discovery.js.map +1 -0
- package/dist/agent/execute.d.ts +206 -0
- package/dist/agent/execute.d.ts.map +1 -0
- package/dist/agent/execute.js +581 -0
- package/dist/agent/execute.js.map +1 -0
- package/dist/agent/index.d.ts +23 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +85 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/launch.d.ts +133 -0
- package/dist/agent/launch.d.ts.map +1 -0
- package/dist/agent/launch.js +380 -0
- package/dist/agent/launch.js.map +1 -0
- package/dist/agent/manifest.d.ts +30 -0
- package/dist/agent/manifest.d.ts.map +1 -0
- package/dist/agent/manifest.js +93 -0
- package/dist/agent/manifest.js.map +1 -0
- package/dist/agent/placeholders.d.ts +49 -0
- package/dist/agent/placeholders.d.ts.map +1 -0
- package/dist/agent/placeholders.js +177 -0
- package/dist/agent/placeholders.js.map +1 -0
- package/dist/agent/run-log.d.ts +254 -0
- package/dist/agent/run-log.d.ts.map +1 -0
- package/dist/agent/run-log.js +612 -0
- package/dist/agent/run-log.js.map +1 -0
- package/dist/agent/schema.d.ts +100 -0
- package/dist/agent/schema.d.ts.map +1 -0
- package/dist/agent/schema.js +89 -0
- package/dist/agent/schema.js.map +1 -0
- package/dist/agent/step.d.ts +41 -0
- package/dist/agent/step.d.ts.map +1 -0
- package/dist/agent/step.js +95 -0
- package/dist/agent/step.js.map +1 -0
- package/dist/build-info.json +4 -0
- package/dist/cli/error.d.ts +11 -2
- package/dist/cli/error.d.ts.map +1 -1
- package/dist/cli/error.js +27 -6
- package/dist/cli/error.js.map +1 -1
- package/dist/cli/exit.d.ts +33 -3
- package/dist/cli/exit.d.ts.map +1 -1
- package/dist/cli/exit.js +54 -3
- package/dist/cli/exit.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init-command.d.ts.map +1 -1
- package/dist/cli/init-command.js +4 -2
- package/dist/cli/init-command.js.map +1 -1
- package/dist/cli/mcp-command.d.ts +13 -4
- package/dist/cli/mcp-command.d.ts.map +1 -1
- package/dist/cli/mcp-command.js +25 -8
- package/dist/cli/mcp-command.js.map +1 -1
- package/dist/cli/output.d.ts +12 -4
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +15 -4
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/program.d.ts +1 -1
- package/dist/cli/program.d.ts.map +1 -1
- package/dist/cli/program.js +162 -40
- package/dist/cli/program.js.map +1 -1
- package/dist/cli/registrar.d.ts +1 -1
- package/dist/cli/registrar.d.ts.map +1 -1
- package/dist/cli/registrar.js +67 -6
- package/dist/cli/registrar.js.map +1 -1
- package/dist/cli/suggest.d.ts +20 -0
- package/dist/cli/suggest.d.ts.map +1 -0
- package/dist/cli/suggest.js +49 -0
- package/dist/cli/suggest.js.map +1 -0
- package/dist/cli/warning.d.ts +26 -0
- package/dist/cli/warning.d.ts.map +1 -0
- package/dist/cli/warning.js +66 -0
- package/dist/cli/warning.js.map +1 -0
- package/dist/cli.js +11 -2
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-execute.d.ts +44 -0
- package/dist/core/agent-execute.d.ts.map +1 -0
- package/dist/core/agent-execute.js +71 -0
- package/dist/core/agent-execute.js.map +1 -0
- package/dist/core/agent-list.d.ts +95 -0
- package/dist/core/agent-list.d.ts.map +1 -0
- package/dist/core/agent-list.js +207 -0
- package/dist/core/agent-list.js.map +1 -0
- package/dist/core/agent-show.d.ts +44 -0
- package/dist/core/agent-show.d.ts.map +1 -0
- package/dist/core/agent-show.js +188 -0
- package/dist/core/agent-show.js.map +1 -0
- package/dist/core/approval-authority.d.ts +17 -14
- package/dist/core/approval-authority.d.ts.map +1 -1
- package/dist/core/approval-authority.js +9 -9
- package/dist/core/approval-authority.js.map +1 -1
- package/dist/core/builtin-asset.d.ts +18 -15
- package/dist/core/builtin-asset.d.ts.map +1 -1
- package/dist/core/builtin-asset.js +17 -14
- package/dist/core/builtin-asset.js.map +1 -1
- package/dist/core/builtin-integrity.d.ts +12 -2
- package/dist/core/builtin-integrity.d.ts.map +1 -1
- package/dist/core/builtin-integrity.js +85 -9
- package/dist/core/builtin-integrity.js.map +1 -1
- package/dist/core/confinement.d.ts +3 -1
- package/dist/core/confinement.d.ts.map +1 -1
- package/dist/core/confinement.js +7 -4
- package/dist/core/confinement.js.map +1 -1
- package/dist/core/context.d.ts +150 -98
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +602 -66
- package/dist/core/context.js.map +1 -1
- package/dist/core/directive-assign.d.ts +33 -7
- package/dist/core/directive-assign.d.ts.map +1 -1
- package/dist/core/directive-assign.js +67 -27
- package/dist/core/directive-assign.js.map +1 -1
- package/dist/core/directive-scope.d.ts +36 -0
- package/dist/core/directive-scope.d.ts.map +1 -0
- package/dist/core/directive-scope.js +34 -0
- package/dist/core/directive-scope.js.map +1 -0
- package/dist/core/directives-list.d.ts +13 -3
- package/dist/core/directives-list.d.ts.map +1 -1
- package/dist/core/directives-list.js +18 -6
- package/dist/core/directives-list.js.map +1 -1
- package/dist/core/dry-run.d.ts +33 -0
- package/dist/core/dry-run.d.ts.map +1 -0
- package/dist/core/dry-run.js +45 -0
- package/dist/core/dry-run.js.map +1 -0
- package/dist/core/element-ref.d.ts +23 -0
- package/dist/core/element-ref.d.ts.map +1 -0
- package/dist/core/element-ref.js +24 -0
- package/dist/core/element-ref.js.map +1 -0
- package/dist/core/error-details.d.ts +20 -0
- package/dist/core/error-details.d.ts.map +1 -0
- package/dist/core/error-details.js +65 -0
- package/dist/core/error-details.js.map +1 -0
- package/dist/core/exit-code.d.ts.map +1 -1
- package/dist/core/exit-code.js +6 -7
- package/dist/core/exit-code.js.map +1 -1
- package/dist/core/git-identity.d.ts +46 -11
- package/dist/core/git-identity.d.ts.map +1 -1
- package/dist/core/git-identity.js +140 -27
- package/dist/core/git-identity.js.map +1 -1
- package/dist/core/index.d.ts +184 -10
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1045 -185
- package/dist/core/index.js.map +1 -1
- package/dist/core/init.d.ts +45 -5
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +134 -24
- package/dist/core/init.js.map +1 -1
- package/dist/core/loaders.d.ts +170 -14
- package/dist/core/loaders.d.ts.map +1 -1
- package/dist/core/loaders.js +570 -89
- package/dist/core/loaders.js.map +1 -1
- package/dist/core/memory-add-link.d.ts +28 -0
- package/dist/core/memory-add-link.d.ts.map +1 -0
- package/dist/core/memory-add-link.js +67 -0
- package/dist/core/memory-add-link.js.map +1 -0
- package/dist/core/memory-add-type.d.ts +25 -1
- package/dist/core/memory-add-type.d.ts.map +1 -1
- package/dist/core/memory-add-type.js +59 -1
- package/dist/core/memory-add-type.js.map +1 -1
- package/dist/core/memory-amend.d.ts +80 -0
- package/dist/core/memory-amend.d.ts.map +1 -0
- package/dist/core/memory-amend.js +160 -0
- package/dist/core/memory-amend.js.map +1 -0
- package/dist/core/memory-supersede.d.ts +28 -0
- package/dist/core/memory-supersede.d.ts.map +1 -0
- package/dist/core/memory-supersede.js +79 -0
- package/dist/core/memory-supersede.js.map +1 -0
- package/dist/core/memory-transition.d.ts +190 -30
- package/dist/core/memory-transition.d.ts.map +1 -1
- package/dist/core/memory-transition.js +480 -59
- package/dist/core/memory-transition.js.map +1 -1
- package/dist/core/registry.d.ts +82 -10
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +50 -0
- package/dist/core/registry.js.map +1 -1
- package/dist/core/relevance.d.ts +74 -12
- package/dist/core/relevance.d.ts.map +1 -1
- package/dist/core/relevance.js +45 -27
- package/dist/core/relevance.js.map +1 -1
- package/dist/core/require-reason.d.ts.map +1 -1
- package/dist/core/require-reason.js +6 -5
- package/dist/core/require-reason.js.map +1 -1
- package/dist/core/required-fields.d.ts +28 -0
- package/dist/core/required-fields.d.ts.map +1 -0
- package/dist/core/required-fields.js +60 -0
- package/dist/core/required-fields.js.map +1 -0
- package/dist/core/revision.d.ts +82 -0
- package/dist/core/revision.d.ts.map +1 -0
- package/dist/core/revision.js +144 -0
- package/dist/core/revision.js.map +1 -0
- package/dist/core/types.d.ts +28 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +12 -3
- package/dist/core/types.js.map +1 -1
- package/dist/core/workflow-core-checks.d.ts +34 -0
- package/dist/core/workflow-core-checks.d.ts.map +1 -0
- package/dist/core/workflow-core-checks.js +403 -0
- package/dist/core/workflow-core-checks.js.map +1 -0
- package/dist/core/workflow-create-remove.d.ts +65 -0
- package/dist/core/workflow-create-remove.d.ts.map +1 -0
- package/dist/core/workflow-create-remove.js +277 -0
- package/dist/core/workflow-create-remove.js.map +1 -0
- package/dist/core/workflow-deduction.d.ts +48 -0
- package/dist/core/workflow-deduction.d.ts.map +1 -0
- package/dist/core/workflow-deduction.js +523 -0
- package/dist/core/workflow-deduction.js.map +1 -0
- package/dist/core/workflow-diagnostics.d.ts +83 -0
- package/dist/core/workflow-diagnostics.d.ts.map +1 -0
- package/dist/core/workflow-diagnostics.js +375 -0
- package/dist/core/workflow-diagnostics.js.map +1 -0
- package/dist/core/workflow-exit-state.d.ts +125 -0
- package/dist/core/workflow-exit-state.d.ts.map +1 -0
- package/dist/core/workflow-exit-state.js +243 -0
- package/dist/core/workflow-exit-state.js.map +1 -0
- package/dist/core/workflow-finalize.d.ts +37 -0
- package/dist/core/workflow-finalize.d.ts.map +1 -0
- package/dist/core/workflow-finalize.js +153 -0
- package/dist/core/workflow-finalize.js.map +1 -0
- package/dist/core/workflow-list-show.d.ts +176 -0
- package/dist/core/workflow-list-show.d.ts.map +1 -0
- package/dist/core/workflow-list-show.js +268 -0
- package/dist/core/workflow-list-show.js.map +1 -0
- package/dist/core/workflow-next.d.ts +268 -0
- package/dist/core/workflow-next.d.ts.map +1 -0
- package/dist/core/workflow-next.js +503 -0
- package/dist/core/workflow-next.js.map +1 -0
- package/dist/core/workflow-registry.d.ts +52 -0
- package/dist/core/workflow-registry.d.ts.map +1 -0
- package/dist/core/workflow-registry.js +158 -0
- package/dist/core/workflow-registry.js.map +1 -0
- package/dist/core/workflow-start-end.d.ts +71 -0
- package/dist/core/workflow-start-end.d.ts.map +1 -0
- package/dist/core/workflow-start-end.js +298 -0
- package/dist/core/workflow-start-end.js.map +1 -0
- package/dist/core/workflow-status.d.ts +83 -0
- package/dist/core/workflow-status.d.ts.map +1 -0
- package/dist/core/workflow-status.js +125 -0
- package/dist/core/workflow-status.js.map +1 -0
- package/dist/core/write-guard.d.ts +51 -8
- package/dist/core/write-guard.d.ts.map +1 -1
- package/dist/core/write-guard.js +139 -9
- package/dist/core/write-guard.js.map +1 -1
- package/dist/directives/index.d.ts +3 -2
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/directives/index.js +5 -3
- package/dist/directives/index.js.map +1 -1
- package/dist/directives/schema.d.ts +34 -29
- package/dist/directives/schema.d.ts.map +1 -1
- package/dist/directives/schema.js +35 -29
- package/dist/directives/schema.js.map +1 -1
- package/dist/directives/version.d.ts +28 -0
- package/dist/directives/version.d.ts.map +1 -0
- package/dist/directives/version.js +51 -0
- package/dist/directives/version.js.map +1 -0
- package/dist/dna/edit.d.ts +7 -6
- package/dist/dna/edit.d.ts.map +1 -1
- package/dist/dna/edit.js +96 -41
- package/dist/dna/edit.js.map +1 -1
- package/dist/dna/mutate.js +18 -3
- package/dist/dna/mutate.js.map +1 -1
- package/dist/dna/path.d.ts +2 -2
- package/dist/dna/roles.d.ts +1 -1
- package/dist/dna/schema.d.ts +61 -3
- package/dist/dna/schema.d.ts.map +1 -1
- package/dist/dna/schema.js +121 -6
- package/dist/dna/schema.js.map +1 -1
- package/dist/mcp/dna-resource.d.ts.map +1 -1
- package/dist/mcp/dna-resource.js +4 -4
- package/dist/mcp/dna-resource.js.map +1 -1
- package/dist/mcp/index.d.ts +8 -4
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +13 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/memory-resource.d.ts.map +1 -1
- package/dist/mcp/memory-resource.js +30 -19
- package/dist/mcp/memory-resource.js.map +1 -1
- package/dist/mcp/prompt.d.ts +61 -24
- package/dist/mcp/prompt.d.ts.map +1 -1
- package/dist/mcp/prompt.js +176 -47
- package/dist/mcp/prompt.js.map +1 -1
- package/dist/mcp/read-only.d.ts +39 -0
- package/dist/mcp/read-only.d.ts.map +1 -1
- package/dist/mcp/read-only.js +61 -0
- package/dist/mcp/read-only.js.map +1 -1
- package/dist/mcp/registrar.d.ts +10 -0
- package/dist/mcp/registrar.d.ts.map +1 -1
- package/dist/mcp/registrar.js +48 -12
- package/dist/mcp/registrar.js.map +1 -1
- package/dist/mcp/server.d.ts +21 -14
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +41 -15
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session-resource.d.ts +13 -0
- package/dist/mcp/session-resource.d.ts.map +1 -0
- package/dist/mcp/session-resource.js +65 -0
- package/dist/mcp/session-resource.js.map +1 -0
- package/dist/mcp/workflow-resource.d.ts +18 -0
- package/dist/mcp/workflow-resource.d.ts.map +1 -1
- package/dist/mcp/workflow-resource.js +37 -8
- package/dist/mcp/workflow-resource.js.map +1 -1
- package/dist/memory/add.d.ts +110 -24
- package/dist/memory/add.d.ts.map +1 -1
- package/dist/memory/add.js +251 -64
- package/dist/memory/add.js.map +1 -1
- package/dist/memory/audit.d.ts +152 -39
- package/dist/memory/audit.d.ts.map +1 -1
- package/dist/memory/audit.js +257 -103
- package/dist/memory/audit.js.map +1 -1
- package/dist/memory/commit-message.d.ts +60 -38
- package/dist/memory/commit-message.d.ts.map +1 -1
- package/dist/memory/commit-message.js +167 -21
- package/dist/memory/commit-message.js.map +1 -1
- package/dist/memory/entry.d.ts.map +1 -1
- package/dist/memory/entry.js +1 -2
- package/dist/memory/entry.js.map +1 -1
- package/dist/memory/frontmatter-edit.d.ts +16 -4
- package/dist/memory/frontmatter-edit.d.ts.map +1 -1
- package/dist/memory/frontmatter-edit.js +13 -1
- package/dist/memory/frontmatter-edit.js.map +1 -1
- package/dist/memory/git-log.d.ts +18 -6
- package/dist/memory/git-log.d.ts.map +1 -1
- package/dist/memory/git-log.js +40 -13
- package/dist/memory/git-log.js.map +1 -1
- package/dist/memory/history.d.ts +73 -12
- package/dist/memory/history.d.ts.map +1 -1
- package/dist/memory/history.js +128 -44
- package/dist/memory/history.js.map +1 -1
- package/dist/memory/index.d.ts +13 -11
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +34 -3
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/query.d.ts +161 -19
- package/dist/memory/query.d.ts.map +1 -1
- package/dist/memory/query.js +435 -55
- package/dist/memory/query.js.map +1 -1
- package/dist/memory/schema.d.ts +52 -3
- package/dist/memory/schema.d.ts.map +1 -1
- package/dist/memory/schema.js +132 -8
- package/dist/memory/schema.js.map +1 -1
- package/dist/memory/state-machine.d.ts +65 -10
- package/dist/memory/state-machine.d.ts.map +1 -1
- package/dist/memory/state-machine.js +134 -46
- package/dist/memory/state-machine.js.map +1 -1
- package/dist/memory/submit.d.ts +52 -3
- package/dist/memory/submit.d.ts.map +1 -1
- package/dist/memory/submit.js +118 -11
- package/dist/memory/submit.js.map +1 -1
- package/dist/storage/build-stamp.d.ts +18 -0
- package/dist/storage/build-stamp.d.ts.map +1 -0
- package/dist/storage/build-stamp.js +68 -0
- package/dist/storage/build-stamp.js.map +1 -0
- package/dist/storage/builtin-adapters.d.ts +32 -0
- package/dist/storage/builtin-adapters.d.ts.map +1 -0
- package/dist/storage/builtin-adapters.js +100 -0
- package/dist/storage/builtin-adapters.js.map +1 -0
- package/dist/storage/builtin-directives.d.ts +0 -29
- package/dist/storage/builtin-directives.d.ts.map +1 -1
- package/dist/storage/builtin-directives.js +9 -4
- package/dist/storage/builtin-directives.js.map +1 -1
- package/dist/storage/commit.d.ts +310 -3
- package/dist/storage/commit.d.ts.map +1 -1
- package/dist/storage/commit.js +807 -33
- package/dist/storage/commit.js.map +1 -1
- package/dist/storage/document.d.ts +6 -0
- package/dist/storage/document.d.ts.map +1 -1
- package/dist/storage/document.js +10 -1
- package/dist/storage/document.js.map +1 -1
- package/dist/storage/dry-run.d.ts +65 -0
- package/dist/storage/dry-run.d.ts.map +1 -0
- package/dist/storage/dry-run.js +150 -0
- package/dist/storage/dry-run.js.map +1 -0
- package/dist/storage/errors.d.ts +26 -0
- package/dist/storage/errors.d.ts.map +1 -1
- package/dist/storage/errors.js +27 -1
- package/dist/storage/errors.js.map +1 -1
- package/dist/storage/frontmatter.d.ts +1 -1
- package/dist/storage/frontmatter.js +1 -1
- package/dist/storage/git-read.d.ts +64 -0
- package/dist/storage/git-read.d.ts.map +1 -0
- package/dist/storage/git-read.js +107 -0
- package/dist/storage/git-read.js.map +1 -0
- package/dist/storage/index.d.ts +13 -5
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +39 -2
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/layout.d.ts +2 -2
- package/dist/storage/layout.d.ts.map +1 -1
- package/dist/storage/layout.js +2 -7
- package/dist/storage/layout.js.map +1 -1
- package/dist/storage/memory-path.d.ts +18 -5
- package/dist/storage/memory-path.d.ts.map +1 -1
- package/dist/storage/memory-path.js +41 -5
- package/dist/storage/memory-path.js.map +1 -1
- package/dist/storage/snapshot.js +1 -1
- package/dist/storage/templates.d.ts +33 -6
- package/dist/storage/templates.d.ts.map +1 -1
- package/dist/storage/templates.js +170 -16
- package/dist/storage/templates.js.map +1 -1
- package/dist/validation/config-version.d.ts +19 -0
- package/dist/validation/config-version.d.ts.map +1 -0
- package/dist/validation/config-version.js +53 -0
- package/dist/validation/config-version.js.map +1 -0
- package/dist/validation/diagnostic.d.ts +40 -0
- package/dist/validation/diagnostic.d.ts.map +1 -0
- package/dist/validation/diagnostic.js +41 -0
- package/dist/validation/diagnostic.js.map +1 -0
- package/dist/validation/format.d.ts +74 -0
- package/dist/validation/format.d.ts.map +1 -0
- package/dist/validation/format.js +104 -0
- package/dist/validation/format.js.map +1 -0
- package/dist/validation/id.d.ts +14 -0
- package/dist/validation/id.d.ts.map +1 -1
- package/dist/validation/id.js +35 -11
- package/dist/validation/id.js.map +1 -1
- package/dist/validation/identity.d.ts +40 -0
- package/dist/validation/identity.d.ts.map +1 -0
- package/dist/validation/identity.js +78 -0
- package/dist/validation/identity.js.map +1 -0
- package/dist/validation/index.d.ts +9 -3
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +30 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/secret-scan.d.ts +3 -2
- package/dist/validation/secret-scan.d.ts.map +1 -1
- package/dist/validation/secret-scan.js +32 -11
- package/dist/validation/secret-scan.js.map +1 -1
- package/dist/validation/warning.d.ts +30 -16
- package/dist/validation/warning.d.ts.map +1 -1
- package/dist/validation/warning.js +74 -7
- package/dist/validation/warning.js.map +1 -1
- package/dist/workflow/bindings.d.ts +152 -0
- package/dist/workflow/bindings.d.ts.map +1 -0
- package/dist/workflow/bindings.js +249 -0
- package/dist/workflow/bindings.js.map +1 -0
- package/dist/workflow/create.d.ts +13 -0
- package/dist/workflow/create.d.ts.map +1 -0
- package/dist/workflow/create.js +41 -0
- package/dist/workflow/create.js.map +1 -0
- package/dist/workflow/deduce.d.ts +400 -0
- package/dist/workflow/deduce.d.ts.map +1 -0
- package/dist/workflow/deduce.js +1109 -0
- package/dist/workflow/deduce.js.map +1 -0
- package/dist/workflow/manifest-edit.d.ts +19 -0
- package/dist/workflow/manifest-edit.d.ts.map +1 -0
- package/dist/workflow/manifest-edit.js +124 -0
- package/dist/workflow/manifest-edit.js.map +1 -0
- package/dist/workflow/schema.d.ts +158 -19
- package/dist/workflow/schema.d.ts.map +1 -1
- package/dist/workflow/schema.js +163 -15
- package/dist/workflow/schema.js.map +1 -1
- package/package.json +18 -8
package/dist/core/index.js
CHANGED
|
@@ -14,7 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.
|
|
17
|
+
exports.workflowStatusAtHead = exports.statusWorkflow = exports.renderStatusConsole = exports.W_APPROVAL_ROLE_WITHOUT_AUTHORITY = exports.routeApproval = exports.approvalDiagnostics = exports.WORKFLOWS_MANIFEST_PATH = exports.workflowExistsMessage = exports.renderRemoveConsole = exports.renderCreateConsole = exports.NO_DECLARATION_MESSAGE = exports.KIND_CONFLICT_MESSAGE = exports.renderStartConsole = exports.renderEndConsole = exports.NO_PLAN_TYPE_MESSAGE = exports.NO_ACTIVE_WORKFLOW_MESSAGE = exports.workflowFinalizeAtHead = exports.renderFinalizeConsole = exports.phaseRecordTrailers = exports.NO_ACTIVE_WORKFLOW_TO_FINALIZE = exports.workflowNextAtHead = exports.renderNextConsole = exports.nextWorkflow = exports.buildStep = exports.loadWorkflowRegistryAtRev = exports.loadWorkflowRegistryAtHead = exports.loadWorkflowRegistry = exports.RevisionError = exports.resolveRevision = exports.isWellFormedRevision = exports.loadWorkflowsYamlAtRev = exports.loadWorkflowsYaml = exports.loadRolesYamlAtRev = exports.loadRolesYamlAtHead = exports.loadRolesYaml = exports.loadMemoryYamlAtRev = exports.loadMemoryYamlAtHead = exports.loadMemoryYaml = exports.loadDnaYamlAtRev = exports.loadDnaYamlAtHead = exports.loadDnaYaml = exports.loadDirectivesAtRev = exports.loadDirectivesAtHead = exports.loadDirectives = exports.ROLES_YAML_PATH = exports.MEMORY_YAML_PATH = exports.DNA_YAML_PATH = exports.DIRECTIVES_DIR_PATH = exports.ConfigFileMissingError = exports.MODULE_NAME = void 0;
|
|
18
|
+
exports.requireUnmodifiedTargets = exports.requireUnmodifiedTarget = exports.requireNoDivergentStage = exports.requireInspectableTarget = exports.requireAbsentTarget = exports.committedScopeError = exports.CONFIG_WRITE_CONTRACT = exports.resolveAddType = exports.verifyCommittedScope = exports.commitMemoryTransition = exports.beginMemoryTransition = exports.selectRelevantMemoryDocuments = exports.NO_RELEVANT_MEMORY_NOTE = exports.filterRelevantMemoryDocuments = exports.DEFAULT_CONTEXT_LIMITS = exports.WINGFOIL_NOT_INITIALIZED = exports.WINGFOIL_ALREADY_INITIALIZED = exports.requireInitializedProject = exports.initWingfoilProject = exports.initWingfoilStorage = exports.runAsDryRun = exports.DRY_RUN_FLAG = exports.UNASSIGNED_ASSIGNMENT = exports.GLOBAL_ASSIGNMENT = exports.loadDirectiveListing = exports.buildDirectiveListing = exports.WrittenTimestamp = exports.validateExecutionContext = exports.serializeExecutionContext = exports.selectDirectivesById = exports.resolveRoleDirectives = exports.CONTEXT_PAYLOAD_FORMAT = exports.assembleExecutionContext = exports.parseElementRef = exports.malformedElementRefMessage = exports.W_UNRESOLVED_TOKEN = exports.W_UNCOMMITTED_INPUTS = exports.W_MEMORY_INVALID_STATE = exports.W_INSTANCE_WORKFLOW_UNKNOWN = exports.resolveInstanceRef = exports.NO_ITERATION_NOTE = exports.deduceWorkflowState = exports.selectWorkflowInstance = exports.readDeductionSnapshotAtHead = exports.deduceWorkflowStateAtHead = exports.workflowExitStates = exports.machineStates = exports.iterationStartState = exports.workflowCoreDiagnostics = exports.W_WORKFLOW_CHECKS_NOT_RUN = void 0;
|
|
19
|
+
exports.CORE_MODULES = exports.dnaEntryFieldOfOption = exports.dnaEntryOptionName = exports.DNA_ENTRY_OPTION_PREFIX = exports.DNA_REWRITE_PLANNED_WARNING = exports.DNA_REWRITE_WARNING = exports.verifyBuiltinTemplates = exports.verifyCommittedPaths = exports.undeclaredCommittedPaths = void 0;
|
|
20
|
+
exports.coreErrorOf = coreErrorOf;
|
|
21
|
+
exports.loadDnaRoleSet = loadDnaRoleSet;
|
|
22
|
+
exports.dnaRewriteConflict = dnaRewriteConflict;
|
|
18
23
|
/**
|
|
19
24
|
* `core` module — shared domain logic; single behavior behind both the CLI and MCP surfaces
|
|
20
25
|
* (REQ-SYS-05). Exposes one loader per Project pillar (task-004-decoupled-pillars, REQ-SYS-02):
|
|
@@ -43,24 +48,42 @@ const storage_1 = require("../storage");
|
|
|
43
48
|
// (same as `src/memory/entry.ts`): `memory add` needs the CONFINED target path before it writes, to
|
|
44
49
|
// run task-092's absence guard on it.
|
|
45
50
|
const memory_path_1 = require("../storage/memory-path");
|
|
51
|
+
const errors_1 = require("../storage/errors");
|
|
46
52
|
const memory_1 = require("../memory");
|
|
47
53
|
const loaders_1 = require("./loaders");
|
|
54
|
+
const workflow_list_show_1 = require("./workflow-list-show");
|
|
55
|
+
const workflow_next_1 = require("./workflow-next");
|
|
56
|
+
const workflow_start_end_1 = require("./workflow-start-end");
|
|
57
|
+
const workflow_finalize_1 = require("./workflow-finalize");
|
|
58
|
+
const workflow_status_1 = require("./workflow-status");
|
|
59
|
+
const workflow_create_remove_1 = require("./workflow-create-remove");
|
|
48
60
|
const directives_list_1 = require("./directives-list");
|
|
49
61
|
const context_1 = require("./context");
|
|
50
62
|
const directive_assign_1 = require("./directive-assign");
|
|
51
63
|
const git_identity_1 = require("./git-identity");
|
|
64
|
+
const required_fields_1 = require("./required-fields");
|
|
65
|
+
const init_1 = require("./init");
|
|
52
66
|
const builtin_asset_1 = require("./builtin-asset");
|
|
53
67
|
const confinement_1 = require("./confinement");
|
|
54
68
|
const approval_authority_1 = require("./approval-authority");
|
|
55
69
|
const require_reason_1 = require("./require-reason");
|
|
56
70
|
const memory_transition_1 = require("./memory-transition");
|
|
71
|
+
const memory_supersede_1 = require("./memory-supersede");
|
|
72
|
+
const memory_amend_1 = require("./memory-amend");
|
|
57
73
|
const memory_add_type_1 = require("./memory-add-type");
|
|
74
|
+
const memory_add_link_1 = require("./memory-add-link");
|
|
58
75
|
const write_guard_1 = require("./write-guard");
|
|
76
|
+
const agent_execute_1 = require("./agent-execute");
|
|
77
|
+
const agent_list_1 = require("./agent-list");
|
|
78
|
+
const agent_show_1 = require("./agent-show");
|
|
59
79
|
const usage_error_1 = require("./usage-error");
|
|
80
|
+
const registry_1 = require("./registry");
|
|
60
81
|
const types_1 = require("./types");
|
|
61
82
|
/** This module's `dna.yaml` name (`core`) — the stable identifier surfaces and tests key it by. */
|
|
62
83
|
exports.MODULE_NAME = 'core';
|
|
63
84
|
var loaders_2 = require("./loaders");
|
|
85
|
+
// A missing working-tree configuration file, named repository-relative (task-179, `bug-245`).
|
|
86
|
+
Object.defineProperty(exports, "ConfigFileMissingError", { enumerable: true, get: function () { return loaders_2.ConfigFileMissingError; } });
|
|
64
87
|
Object.defineProperty(exports, "DIRECTIVES_DIR_PATH", { enumerable: true, get: function () { return loaders_2.DIRECTIVES_DIR_PATH; } });
|
|
65
88
|
Object.defineProperty(exports, "DNA_YAML_PATH", { enumerable: true, get: function () { return loaders_2.DNA_YAML_PATH; } });
|
|
66
89
|
Object.defineProperty(exports, "MEMORY_YAML_PATH", { enumerable: true, get: function () { return loaders_2.MEMORY_YAML_PATH; } });
|
|
@@ -70,17 +93,98 @@ Object.defineProperty(exports, "loadDirectives", { enumerable: true, get: functi
|
|
|
70
93
|
// task-090 kept `loadDnaYamlAtHead` private while that rule was undecided; it is decided now, and a
|
|
71
94
|
// facility the ruling tells the next implementer to use has to be reachable (task-091, D3).
|
|
72
95
|
Object.defineProperty(exports, "loadDirectivesAtHead", { enumerable: true, get: function () { return loaders_2.loadDirectivesAtHead; } });
|
|
96
|
+
// The same readers at any commit (task-137, `spec-012` §2 `stateRef`); each `…AtHead` is its
|
|
97
|
+
// `…AtRev` at `'HEAD'`.
|
|
98
|
+
Object.defineProperty(exports, "loadDirectivesAtRev", { enumerable: true, get: function () { return loaders_2.loadDirectivesAtRev; } });
|
|
73
99
|
Object.defineProperty(exports, "loadDnaYaml", { enumerable: true, get: function () { return loaders_2.loadDnaYaml; } });
|
|
74
100
|
Object.defineProperty(exports, "loadDnaYamlAtHead", { enumerable: true, get: function () { return loaders_2.loadDnaYamlAtHead; } });
|
|
101
|
+
Object.defineProperty(exports, "loadDnaYamlAtRev", { enumerable: true, get: function () { return loaders_2.loadDnaYamlAtRev; } });
|
|
75
102
|
Object.defineProperty(exports, "loadMemoryYaml", { enumerable: true, get: function () { return loaders_2.loadMemoryYaml; } });
|
|
76
103
|
Object.defineProperty(exports, "loadMemoryYamlAtHead", { enumerable: true, get: function () { return loaders_2.loadMemoryYamlAtHead; } });
|
|
104
|
+
Object.defineProperty(exports, "loadMemoryYamlAtRev", { enumerable: true, get: function () { return loaders_2.loadMemoryYamlAtRev; } });
|
|
77
105
|
Object.defineProperty(exports, "loadRolesYaml", { enumerable: true, get: function () { return loaders_2.loadRolesYaml; } });
|
|
78
106
|
Object.defineProperty(exports, "loadRolesYamlAtHead", { enumerable: true, get: function () { return loaders_2.loadRolesYamlAtHead; } });
|
|
107
|
+
Object.defineProperty(exports, "loadRolesYamlAtRev", { enumerable: true, get: function () { return loaders_2.loadRolesYamlAtRev; } });
|
|
79
108
|
Object.defineProperty(exports, "loadWorkflowsYaml", { enumerable: true, get: function () { return loaders_2.loadWorkflowsYaml; } });
|
|
109
|
+
Object.defineProperty(exports, "loadWorkflowsYamlAtRev", { enumerable: true, get: function () { return loaders_2.loadWorkflowsYamlAtRev; } });
|
|
110
|
+
var revision_1 = require("./revision");
|
|
111
|
+
Object.defineProperty(exports, "isWellFormedRevision", { enumerable: true, get: function () { return revision_1.isWellFormedRevision; } });
|
|
112
|
+
Object.defineProperty(exports, "resolveRevision", { enumerable: true, get: function () { return revision_1.resolveRevision; } });
|
|
113
|
+
Object.defineProperty(exports, "RevisionError", { enumerable: true, get: function () { return revision_1.RevisionError; } });
|
|
114
|
+
// task-194: the workflow registry with spec-003's core checks, and the exit-state computation
|
|
115
|
+
// (spec-017 §4.4) task-198's deduction reuses.
|
|
116
|
+
var workflow_registry_1 = require("./workflow-registry");
|
|
117
|
+
Object.defineProperty(exports, "loadWorkflowRegistry", { enumerable: true, get: function () { return workflow_registry_1.loadWorkflowRegistry; } });
|
|
118
|
+
Object.defineProperty(exports, "loadWorkflowRegistryAtHead", { enumerable: true, get: function () { return workflow_registry_1.loadWorkflowRegistryAtHead; } });
|
|
119
|
+
Object.defineProperty(exports, "loadWorkflowRegistryAtRev", { enumerable: true, get: function () { return workflow_registry_1.loadWorkflowRegistryAtRev; } });
|
|
120
|
+
// task-216: `workflow next` at `HEAD` (`spec-017` §6, §7.3, §8 `Step` / `NextResult`); the operation is
|
|
121
|
+
// `CORE_MODULES`' `workflowNext`, and the step builder is what `agent execute --next` (task-235) and `status`
|
|
122
|
+
// (task-225) reuse. `workflowNextAtHead` is also what the MCP Resource `wingfoil://workflows/-/next` serves (task-239).
|
|
123
|
+
var workflow_next_2 = require("./workflow-next");
|
|
124
|
+
Object.defineProperty(exports, "buildStep", { enumerable: true, get: function () { return workflow_next_2.buildStep; } });
|
|
125
|
+
Object.defineProperty(exports, "nextWorkflow", { enumerable: true, get: function () { return workflow_next_2.nextWorkflow; } });
|
|
126
|
+
Object.defineProperty(exports, "renderNextConsole", { enumerable: true, get: function () { return workflow_next_2.renderNextConsole; } });
|
|
127
|
+
Object.defineProperty(exports, "workflowNextAtHead", { enumerable: true, get: function () { return workflow_next_2.workflowNextAtHead; } });
|
|
128
|
+
// task-226: `workflow finalize` (`spec-017` §7.9, §8 `FinalizeResult`); the operation is `CORE_MODULES`'
|
|
129
|
+
// `workflowFinalize`.
|
|
130
|
+
var workflow_finalize_2 = require("./workflow-finalize");
|
|
131
|
+
Object.defineProperty(exports, "NO_ACTIVE_WORKFLOW_TO_FINALIZE", { enumerable: true, get: function () { return workflow_finalize_2.NO_ACTIVE_WORKFLOW_TO_FINALIZE; } });
|
|
132
|
+
Object.defineProperty(exports, "phaseRecordTrailers", { enumerable: true, get: function () { return workflow_finalize_2.phaseRecordTrailers; } });
|
|
133
|
+
Object.defineProperty(exports, "renderFinalizeConsole", { enumerable: true, get: function () { return workflow_finalize_2.renderFinalizeConsole; } });
|
|
134
|
+
Object.defineProperty(exports, "workflowFinalizeAtHead", { enumerable: true, get: function () { return workflow_finalize_2.workflowFinalizeAtHead; } });
|
|
135
|
+
// task-217: `CORE_MODULES`' `workflowStart` / `workflowEnd` (`spec-017` §7.1–§7.2) and their values.
|
|
136
|
+
var workflow_start_end_2 = require("./workflow-start-end");
|
|
137
|
+
Object.defineProperty(exports, "NO_ACTIVE_WORKFLOW_MESSAGE", { enumerable: true, get: function () { return workflow_start_end_2.NO_ACTIVE_WORKFLOW_MESSAGE; } });
|
|
138
|
+
Object.defineProperty(exports, "NO_PLAN_TYPE_MESSAGE", { enumerable: true, get: function () { return workflow_start_end_2.NO_PLAN_TYPE_MESSAGE; } });
|
|
139
|
+
Object.defineProperty(exports, "renderEndConsole", { enumerable: true, get: function () { return workflow_start_end_2.renderEndConsole; } });
|
|
140
|
+
Object.defineProperty(exports, "renderStartConsole", { enumerable: true, get: function () { return workflow_start_end_2.renderStartConsole; } });
|
|
141
|
+
var workflow_create_remove_2 = require("./workflow-create-remove");
|
|
142
|
+
Object.defineProperty(exports, "KIND_CONFLICT_MESSAGE", { enumerable: true, get: function () { return workflow_create_remove_2.KIND_CONFLICT_MESSAGE; } });
|
|
143
|
+
Object.defineProperty(exports, "NO_DECLARATION_MESSAGE", { enumerable: true, get: function () { return workflow_create_remove_2.NO_DECLARATION_MESSAGE; } });
|
|
144
|
+
Object.defineProperty(exports, "renderCreateConsole", { enumerable: true, get: function () { return workflow_create_remove_2.renderCreateConsole; } });
|
|
145
|
+
Object.defineProperty(exports, "renderRemoveConsole", { enumerable: true, get: function () { return workflow_create_remove_2.renderRemoveConsole; } });
|
|
146
|
+
Object.defineProperty(exports, "workflowExistsMessage", { enumerable: true, get: function () { return workflow_create_remove_2.workflowExistsMessage; } });
|
|
147
|
+
Object.defineProperty(exports, "WORKFLOWS_MANIFEST_PATH", { enumerable: true, get: function () { return workflow_create_remove_2.WORKFLOWS_MANIFEST_PATH; } });
|
|
148
|
+
// task-225: `workflow status` at `HEAD` (`spec-017` §5.1–§5.4, §7.4, §8 `StatusResult`); approval detection and
|
|
149
|
+
// routing live in the step builder above, so `next` reports them too. The operation is `CORE_MODULES`' `workflowStatus`.
|
|
150
|
+
var workflow_next_3 = require("./workflow-next");
|
|
151
|
+
Object.defineProperty(exports, "approvalDiagnostics", { enumerable: true, get: function () { return workflow_next_3.approvalDiagnostics; } });
|
|
152
|
+
Object.defineProperty(exports, "routeApproval", { enumerable: true, get: function () { return workflow_next_3.routeApproval; } });
|
|
153
|
+
Object.defineProperty(exports, "W_APPROVAL_ROLE_WITHOUT_AUTHORITY", { enumerable: true, get: function () { return workflow_next_3.W_APPROVAL_ROLE_WITHOUT_AUTHORITY; } });
|
|
154
|
+
var workflow_status_2 = require("./workflow-status");
|
|
155
|
+
Object.defineProperty(exports, "renderStatusConsole", { enumerable: true, get: function () { return workflow_status_2.renderStatusConsole; } });
|
|
156
|
+
Object.defineProperty(exports, "statusWorkflow", { enumerable: true, get: function () { return workflow_status_2.statusWorkflow; } });
|
|
157
|
+
Object.defineProperty(exports, "workflowStatusAtHead", { enumerable: true, get: function () { return workflow_status_2.workflowStatusAtHead; } });
|
|
158
|
+
var workflow_core_checks_1 = require("./workflow-core-checks");
|
|
159
|
+
Object.defineProperty(exports, "W_WORKFLOW_CHECKS_NOT_RUN", { enumerable: true, get: function () { return workflow_core_checks_1.W_WORKFLOW_CHECKS_NOT_RUN; } });
|
|
160
|
+
Object.defineProperty(exports, "workflowCoreDiagnostics", { enumerable: true, get: function () { return workflow_core_checks_1.workflowCoreDiagnostics; } });
|
|
161
|
+
var workflow_exit_state_1 = require("./workflow-exit-state");
|
|
162
|
+
Object.defineProperty(exports, "iterationStartState", { enumerable: true, get: function () { return workflow_exit_state_1.iterationStartState; } });
|
|
163
|
+
Object.defineProperty(exports, "machineStates", { enumerable: true, get: function () { return workflow_exit_state_1.machineStates; } });
|
|
164
|
+
Object.defineProperty(exports, "workflowExitStates", { enumerable: true, get: function () { return workflow_exit_state_1.workflowExitStates; } });
|
|
165
|
+
var workflow_deduction_1 = require("./workflow-deduction");
|
|
166
|
+
Object.defineProperty(exports, "deduceWorkflowStateAtHead", { enumerable: true, get: function () { return workflow_deduction_1.deduceWorkflowStateAtHead; } });
|
|
167
|
+
Object.defineProperty(exports, "readDeductionSnapshotAtHead", { enumerable: true, get: function () { return workflow_deduction_1.readDeductionSnapshotAtHead; } });
|
|
168
|
+
Object.defineProperty(exports, "selectWorkflowInstance", { enumerable: true, get: function () { return workflow_deduction_1.selectWorkflowInstance; } });
|
|
169
|
+
var deduce_1 = require("../workflow/deduce");
|
|
170
|
+
Object.defineProperty(exports, "deduceWorkflowState", { enumerable: true, get: function () { return deduce_1.deduceWorkflowState; } });
|
|
171
|
+
Object.defineProperty(exports, "NO_ITERATION_NOTE", { enumerable: true, get: function () { return deduce_1.NO_ITERATION_NOTE; } });
|
|
172
|
+
Object.defineProperty(exports, "resolveInstanceRef", { enumerable: true, get: function () { return deduce_1.resolveInstanceRef; } });
|
|
173
|
+
Object.defineProperty(exports, "W_INSTANCE_WORKFLOW_UNKNOWN", { enumerable: true, get: function () { return deduce_1.W_INSTANCE_WORKFLOW_UNKNOWN; } });
|
|
174
|
+
Object.defineProperty(exports, "W_MEMORY_INVALID_STATE", { enumerable: true, get: function () { return deduce_1.W_MEMORY_INVALID_STATE; } });
|
|
175
|
+
Object.defineProperty(exports, "W_UNCOMMITTED_INPUTS", { enumerable: true, get: function () { return deduce_1.W_UNCOMMITTED_INPUTS; } });
|
|
176
|
+
Object.defineProperty(exports, "W_UNRESOLVED_TOKEN", { enumerable: true, get: function () { return deduce_1.W_UNRESOLVED_TOKEN; } });
|
|
177
|
+
var element_ref_1 = require("./element-ref");
|
|
178
|
+
Object.defineProperty(exports, "malformedElementRefMessage", { enumerable: true, get: function () { return element_ref_1.malformedElementRefMessage; } });
|
|
179
|
+
Object.defineProperty(exports, "parseElementRef", { enumerable: true, get: function () { return element_ref_1.parseElementRef; } });
|
|
80
180
|
var context_2 = require("./context");
|
|
81
181
|
Object.defineProperty(exports, "assembleExecutionContext", { enumerable: true, get: function () { return context_2.assembleExecutionContext; } });
|
|
182
|
+
Object.defineProperty(exports, "CONTEXT_PAYLOAD_FORMAT", { enumerable: true, get: function () { return context_2.CONTEXT_PAYLOAD_FORMAT; } });
|
|
82
183
|
Object.defineProperty(exports, "resolveRoleDirectives", { enumerable: true, get: function () { return context_2.resolveRoleDirectives; } });
|
|
83
184
|
Object.defineProperty(exports, "selectDirectivesById", { enumerable: true, get: function () { return context_2.selectDirectivesById; } });
|
|
185
|
+
Object.defineProperty(exports, "serializeExecutionContext", { enumerable: true, get: function () { return context_2.serializeExecutionContext; } });
|
|
186
|
+
Object.defineProperty(exports, "validateExecutionContext", { enumerable: true, get: function () { return context_2.validateExecutionContext; } });
|
|
187
|
+
Object.defineProperty(exports, "WrittenTimestamp", { enumerable: true, get: function () { return context_2.WrittenTimestamp; } });
|
|
84
188
|
var directives_list_2 = require("./directives-list");
|
|
85
189
|
Object.defineProperty(exports, "buildDirectiveListing", { enumerable: true, get: function () { return directives_list_2.buildDirectiveListing; } });
|
|
86
190
|
Object.defineProperty(exports, "loadDirectiveListing", { enumerable: true, get: function () { return directives_list_2.loadDirectiveListing; } });
|
|
@@ -95,17 +199,25 @@ __exportStar(require("./require-reason"), exports);
|
|
|
95
199
|
__exportStar(require("./builtin-asset"), exports);
|
|
96
200
|
__exportStar(require("./confinement"), exports);
|
|
97
201
|
__exportStar(require("./usage-error"), exports);
|
|
98
|
-
var
|
|
99
|
-
Object.defineProperty(exports, "
|
|
100
|
-
Object.defineProperty(exports, "
|
|
101
|
-
|
|
202
|
+
var dry_run_1 = require("./dry-run");
|
|
203
|
+
Object.defineProperty(exports, "DRY_RUN_FLAG", { enumerable: true, get: function () { return dry_run_1.DRY_RUN_FLAG; } });
|
|
204
|
+
Object.defineProperty(exports, "runAsDryRun", { enumerable: true, get: function () { return dry_run_1.runAsDryRun; } });
|
|
205
|
+
var init_2 = require("./init");
|
|
206
|
+
Object.defineProperty(exports, "initWingfoilStorage", { enumerable: true, get: function () { return init_2.initWingfoilStorage; } });
|
|
207
|
+
Object.defineProperty(exports, "initWingfoilProject", { enumerable: true, get: function () { return init_2.initWingfoilProject; } });
|
|
208
|
+
Object.defineProperty(exports, "requireInitializedProject", { enumerable: true, get: function () { return init_2.requireInitializedProject; } });
|
|
209
|
+
Object.defineProperty(exports, "WINGFOIL_ALREADY_INITIALIZED", { enumerable: true, get: function () { return init_2.WINGFOIL_ALREADY_INITIALIZED; } });
|
|
210
|
+
Object.defineProperty(exports, "WINGFOIL_NOT_INITIALIZED", { enumerable: true, get: function () { return init_2.WINGFOIL_NOT_INITIALIZED; } });
|
|
102
211
|
var relevance_1 = require("./relevance");
|
|
103
212
|
Object.defineProperty(exports, "DEFAULT_CONTEXT_LIMITS", { enumerable: true, get: function () { return relevance_1.DEFAULT_CONTEXT_LIMITS; } });
|
|
104
213
|
Object.defineProperty(exports, "filterRelevantMemoryDocuments", { enumerable: true, get: function () { return relevance_1.filterRelevantMemoryDocuments; } });
|
|
105
214
|
Object.defineProperty(exports, "NO_RELEVANT_MEMORY_NOTE", { enumerable: true, get: function () { return relevance_1.NO_RELEVANT_MEMORY_NOTE; } });
|
|
215
|
+
Object.defineProperty(exports, "selectRelevantMemoryDocuments", { enumerable: true, get: function () { return relevance_1.selectRelevantMemoryDocuments; } });
|
|
216
|
+
// `prepareMemoryTransition` is deliberately not re-exported: outside `./memory-transition` a transition
|
|
217
|
+
// starts at `beginMemoryTransition`, which runs the identity pre-flight first (task-132 review).
|
|
106
218
|
var memory_transition_2 = require("./memory-transition");
|
|
219
|
+
Object.defineProperty(exports, "beginMemoryTransition", { enumerable: true, get: function () { return memory_transition_2.beginMemoryTransition; } });
|
|
107
220
|
Object.defineProperty(exports, "commitMemoryTransition", { enumerable: true, get: function () { return memory_transition_2.commitMemoryTransition; } });
|
|
108
|
-
Object.defineProperty(exports, "prepareMemoryTransition", { enumerable: true, get: function () { return memory_transition_2.prepareMemoryTransition; } });
|
|
109
221
|
Object.defineProperty(exports, "verifyCommittedScope", { enumerable: true, get: function () { return memory_transition_2.verifyCommittedScope; } });
|
|
110
222
|
var memory_add_type_2 = require("./memory-add-type");
|
|
111
223
|
Object.defineProperty(exports, "resolveAddType", { enumerable: true, get: function () { return memory_add_type_2.resolveAddType; } });
|
|
@@ -113,6 +225,8 @@ var write_guard_2 = require("./write-guard");
|
|
|
113
225
|
Object.defineProperty(exports, "CONFIG_WRITE_CONTRACT", { enumerable: true, get: function () { return write_guard_2.CONFIG_WRITE_CONTRACT; } });
|
|
114
226
|
Object.defineProperty(exports, "committedScopeError", { enumerable: true, get: function () { return write_guard_2.committedScopeError; } });
|
|
115
227
|
Object.defineProperty(exports, "requireAbsentTarget", { enumerable: true, get: function () { return write_guard_2.requireAbsentTarget; } });
|
|
228
|
+
Object.defineProperty(exports, "requireInspectableTarget", { enumerable: true, get: function () { return write_guard_2.requireInspectableTarget; } });
|
|
229
|
+
Object.defineProperty(exports, "requireNoDivergentStage", { enumerable: true, get: function () { return write_guard_2.requireNoDivergentStage; } });
|
|
116
230
|
Object.defineProperty(exports, "requireUnmodifiedTarget", { enumerable: true, get: function () { return write_guard_2.requireUnmodifiedTarget; } });
|
|
117
231
|
Object.defineProperty(exports, "requireUnmodifiedTargets", { enumerable: true, get: function () { return write_guard_2.requireUnmodifiedTargets; } });
|
|
118
232
|
Object.defineProperty(exports, "undeclaredCommittedPaths", { enumerable: true, get: function () { return write_guard_2.undeclaredCommittedPaths; } });
|
|
@@ -124,31 +238,71 @@ Object.defineProperty(exports, "verifyBuiltinTemplates", { enumerable: true, get
|
|
|
124
238
|
* failures onto a `CoreResult` (spec-006 §2): a `ValidationError` from the shared two-pass pipeline
|
|
125
239
|
* becomes `VALIDATION`, a missing file (Node's `ENOENT`) becomes `NOT_FOUND`; anything else
|
|
126
240
|
* propagates as a genuine thrown exception (a programmer bug, not a domain failure — spec-006 §2's
|
|
127
|
-
* "no function throws for *expected* domain failures" implies unexpected ones still may).
|
|
128
|
-
*
|
|
129
|
-
*
|
|
241
|
+
* "no function throws for *expected* domain failures" implies unexpected ones still may). Every
|
|
242
|
+
* read-only query (`dnaShowFn`, `pathsFn`, `workflowListFn`, …) maps its loader's failures through it,
|
|
243
|
+
* so the mapping is written once. (The `wrapReadOnly` adapter it was factored out of had one caller
|
|
244
|
+
* left, `workflowList`, until task-204 gave that operation a flag and was removed with it.)
|
|
130
245
|
*/
|
|
131
246
|
function loadOrError(loader) {
|
|
132
247
|
try {
|
|
133
248
|
return (0, types_1.coreOk)(loader());
|
|
134
249
|
}
|
|
135
250
|
catch (error) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (errno && errno.code === 'ENOENT') {
|
|
141
|
-
return (0, types_1.coreErr)({ code: 'NOT_FOUND', message: errno.message });
|
|
142
|
-
}
|
|
143
|
-
throw error;
|
|
251
|
+
const refusal = coreErrorOf(error);
|
|
252
|
+
if (refusal === null)
|
|
253
|
+
throw error;
|
|
254
|
+
return (0, types_1.coreErr)(refusal);
|
|
144
255
|
}
|
|
145
256
|
}
|
|
146
|
-
/**
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
257
|
+
/**
|
|
258
|
+
* The expected failure a pillar loader throws, as the {@link CoreError} a read-only query returns for
|
|
259
|
+
* it, or `null` when `error` is not one (a programmer bug, which the caller re-throws). This is
|
|
260
|
+
* {@link loadOrError}'s mapping on its own, for a surface that calls a loader directly rather than
|
|
261
|
+
* through a `CoreOperation` — the MCP server's Resource and Prompt handlers (task-174, `bug-184`), so a
|
|
262
|
+
* refusal there carries the same details (`./error-details.ts`) the CLI prints:
|
|
263
|
+
*
|
|
264
|
+
* - a loader that collects spec-003 diagnostics (task-136): the first error is the reason, and the whole
|
|
265
|
+
* ordered array rides `details` under spec-003's own name for it, `diagnostics`;
|
|
266
|
+
* - a `ValidationError` from the shared two-pass pipeline: `details.issues`;
|
|
267
|
+
* - a missing file (`ENOENT`): `NOT_FOUND`, with the system message.
|
|
268
|
+
*/
|
|
269
|
+
function coreErrorOf(error) {
|
|
270
|
+
if (error instanceof validation_1.DiagnosticsError) {
|
|
271
|
+
return { code: 'VALIDATION', message: error.message, details: { diagnostics: error.diagnostics } };
|
|
272
|
+
}
|
|
273
|
+
if (error instanceof validation_1.ValidationError) {
|
|
274
|
+
return { code: 'VALIDATION', message: error.message, details: { issues: error.issues } };
|
|
275
|
+
}
|
|
276
|
+
const errno = error;
|
|
277
|
+
if (errno && errno.code === 'ENOENT') {
|
|
278
|
+
return { code: 'NOT_FOUND', message: errno.message };
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* The DNA role set — `dna.yaml` `team.roles[].name`, in declaration order — read once, by `wingfoil
|
|
284
|
+
* mcp`'s pre-flight, for the Prompts channel to serve for the server's whole life (`dl-049` (b),
|
|
285
|
+
* spec-004 §3.1 "derived from DNA at server start", spec-014 §1; task-174).
|
|
286
|
+
*
|
|
287
|
+
* A root with no `.wingfoil/` is refused with the shared not-initialized message
|
|
288
|
+
* ({@link requireInitializedProject}, task-143) before anything is read, so the refusal names no path
|
|
289
|
+
* (`bug-035`); an unreadable or invalid `dna.yaml` is refused as every read-only query refuses it.
|
|
290
|
+
*/
|
|
291
|
+
function loadDnaRoleSet(root) {
|
|
292
|
+
return loadConfigOrError(root, () => (0, loaders_1.loadDnaYaml)(root).team.roles.map(({ name }) => name));
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* {@link loadOrError} for a read that needs the project's configuration: a root with no `.wingfoil/` is
|
|
296
|
+
* refused first with the shared not-initialized message ({@link requireInitializedProject}, task-143),
|
|
297
|
+
* which names no path; otherwise the loader runs and its failures map as `loadOrError` maps them — a
|
|
298
|
+
* missing file as `ConfigFileMissingError`'s repository-relative message (task-179, `bug-198`,
|
|
299
|
+
* `bug-245`).
|
|
300
|
+
*/
|
|
301
|
+
function loadConfigOrError(root, loader) {
|
|
302
|
+
const initialized = (0, init_1.requireInitializedProject)(root);
|
|
303
|
+
if (!initialized.ok)
|
|
304
|
+
return initialized;
|
|
305
|
+
return loadOrError(loader);
|
|
152
306
|
}
|
|
153
307
|
/**
|
|
154
308
|
* `paths` `CoreOperation.fn` (P2.5, spec-002-dna-yaml-schema's `Paths` node) —
|
|
@@ -169,7 +323,7 @@ function wrapReadOnly(loader) {
|
|
|
169
323
|
*/
|
|
170
324
|
const pathsFn = async (params) => {
|
|
171
325
|
const { root, positional: category } = params;
|
|
172
|
-
const loaded =
|
|
326
|
+
const loaded = loadConfigOrError(root, () => (0, loaders_1.loadDnaYaml)(root));
|
|
173
327
|
if (!loaded.ok)
|
|
174
328
|
return loaded;
|
|
175
329
|
if (category === undefined)
|
|
@@ -196,7 +350,7 @@ const pathsFn = async (params) => {
|
|
|
196
350
|
*/
|
|
197
351
|
const dnaShowFn = async (params) => {
|
|
198
352
|
const { root, positional: section } = params;
|
|
199
|
-
const loaded =
|
|
353
|
+
const loaded = loadConfigOrError(root, () => (0, loaders_1.loadDnaYaml)(root));
|
|
200
354
|
if (!loaded.ok || section === undefined)
|
|
201
355
|
return loaded;
|
|
202
356
|
const key = set_1.DNA_KEY_ALIASES[section] ?? section;
|
|
@@ -206,6 +360,25 @@ const dnaShowFn = async (params) => {
|
|
|
206
360
|
}
|
|
207
361
|
return (0, types_1.coreOk)(dna[key]);
|
|
208
362
|
};
|
|
363
|
+
/**
|
|
364
|
+
* The refusal of a DNA write the in-place editor cannot express, when `--force` was not given
|
|
365
|
+
* (task-193, approver ruling R20/Q9 of `release-planning-rel-v0.3-plan`: the same rule as
|
|
366
|
+
* `dl-062` for `roles.yaml`, task-169's `rolesRewriteConflict`). `CONFLICT`, exit 1, nothing
|
|
367
|
+
* written. Pinned in `spec-008` §6.
|
|
368
|
+
*/
|
|
369
|
+
function dnaRewriteConflict(field) {
|
|
370
|
+
return `dna.yaml cannot be updated in place; edit ${field} by hand, or pass --force to rewrite the whole file`;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* The warning a `--force`d whole-file rewrite of `dna.yaml` carries on its success (task-193), on
|
|
374
|
+
* task-169's success-warning channel (`CoreResult.warnings`). It names what the `js-yaml` `dump` of
|
|
375
|
+
* the parsed document does not keep — the list `ROLES_REWRITE_WARNING` gives for `roles.yaml`,
|
|
376
|
+
* since the two files go through the same `dump`. Pinned in `spec-008` §6.
|
|
377
|
+
*/
|
|
378
|
+
exports.DNA_REWRITE_WARNING = 'dna.yaml was rewritten as a whole file (--force): comments are not kept, and neither are quoting, flow style, ' +
|
|
379
|
+
'blank lines, line endings or number formatting (1.0 becomes 1)';
|
|
380
|
+
/** {@link DNA_REWRITE_WARNING} as a `--dry-run` words it: the rewrite is planned, not done (task-210 review). */
|
|
381
|
+
exports.DNA_REWRITE_PLANNED_WARNING = exports.DNA_REWRITE_WARNING.replace('dna.yaml was rewritten', 'dna.yaml would be rewritten');
|
|
209
382
|
/**
|
|
210
383
|
* The shared body of every DNA mutation (`dna set`, and `dna add|remove|update` since
|
|
211
384
|
* task-093-dna-mutation-surface-add-remove-update) — the mutating-op template `dna set` established
|
|
@@ -240,13 +413,20 @@ const dnaShowFn = async (params) => {
|
|
|
240
413
|
* in-place textual edit, so every comment survives — including the inline `[SPEC]`/`[AUTHORING]`
|
|
241
414
|
* field-provenance annotations a whole-file re-serialization deletes
|
|
242
415
|
* (bug-004-dna-set-strips-yaml-comments, task-063). When no provably-minimal edit exists it returns
|
|
243
|
-
* `undefined
|
|
244
|
-
* Both
|
|
416
|
+
* `undefined`, and the only other candidate is the whole-file `dump(dna, { lineWidth: -1 })` —
|
|
417
|
+
* correct, but it keeps no comment. Both are deterministic (REQ-SYS-07). Since task-193 (`bug-019`,
|
|
418
|
+
* `bug-126`; ruling R20/Q9, as `dl-062`) that rewrite is **refused** (`CONFLICT`, exit 1,
|
|
419
|
+
* {@link dnaRewriteConflict}) unless `force` authorizes it, and a forced one succeeds with
|
|
420
|
+
* {@link DNA_REWRITE_WARNING}. The refusal comes after step 7, so a write the schema would refuse
|
|
421
|
+
* anyway is reported as the `VALIDATION` it is, not as something `--force` could fix. `force`
|
|
422
|
+
* authorizes the rewrite and does not demand it: an in-place edit is still made in place, unwarned.
|
|
245
423
|
* 7. **Re-validate the written bytes** against `DnaYaml` (spec-002) BEFORE persisting — re-parsing the
|
|
246
424
|
* serialized form, not the in-memory object, so the check honours YAML's own scalar coercion and
|
|
247
|
-
* validates the exact bytes about to be written
|
|
248
|
-
*
|
|
249
|
-
*
|
|
425
|
+
* validates the exact bytes about to be written: `--value` is a string, so
|
|
426
|
+
* `wingfoil dna set version --value 2` serializes `'2'` and fails `z.number()` on read-back — refused
|
|
427
|
+
* at exit 1, `expected number, received string`, nothing written (measured with that command on a
|
|
428
|
+
* fresh `init --template scrum` repository, task-184, bug-096). It is also what enforces the
|
|
429
|
+
* cross-field rules the verbs do not duplicate: a member naming a role absent from `team.roles` fails here (REQ-SYS-08,
|
|
250
430
|
* `Team.superRefine`), which is why `dl-081` records that referential integrity needs no new check.
|
|
251
431
|
* 8. **Persist + commit** through the single storage primitives (task-018) — `writeDocument` then
|
|
252
432
|
* `commitPaths` on the ONE scoped path `.wingfoil/dna.yaml`; the returned sha rides
|
|
@@ -257,14 +437,20 @@ const dnaShowFn = async (params) => {
|
|
|
257
437
|
* with the step-3 guard in place no argument can reach it, but a repository-local `pre-commit`
|
|
258
438
|
* hook that rewrites and re-stages the file still can.
|
|
259
439
|
*/
|
|
260
|
-
async function runDnaMutation(root, request, subject
|
|
440
|
+
async function runDnaMutation(root, request, subject, scalarOnly, force) {
|
|
261
441
|
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
262
442
|
if (!identity.ok)
|
|
263
443
|
return identity;
|
|
444
|
+
// REQ-SEC-06 / bug-121 (task-172): `writeDocument` follows a symlinked `dna.yaml`, so a file that
|
|
445
|
+
// leads outside the project, or is itself a link, is refused before any read or write. First, so a
|
|
446
|
+
// path that leaves the project is reported as leaving it (`requireConfinedTarget`'s ordering rule).
|
|
447
|
+
const confined = (0, confinement_1.requireConfinedWriteTarget)(root, loaders_1.DNA_YAML_PATH, 'write');
|
|
448
|
+
if (!confined.ok)
|
|
449
|
+
return confined;
|
|
264
450
|
const unmodified = (0, write_guard_1.requireUnmodifiedTarget)(root, loaders_1.DNA_YAML_PATH);
|
|
265
451
|
if (!unmodified.ok)
|
|
266
452
|
return unmodified;
|
|
267
|
-
const loaded =
|
|
453
|
+
const loaded = loadConfigOrError(root, () => (0, loaders_1.loadDnaYaml)(root));
|
|
268
454
|
if (!loaded.ok)
|
|
269
455
|
return loaded;
|
|
270
456
|
if (scalarOnly) {
|
|
@@ -283,21 +469,33 @@ async function runDnaMutation(root, request, subject = dnaCommitSubject(request)
|
|
|
283
469
|
return (0, types_1.coreErr)({ code: 'VALIDATION', message: applied.message });
|
|
284
470
|
const dnaPath = (0, path_1.join)(root, loaders_1.DNA_YAML_PATH);
|
|
285
471
|
const current = (0, storage_1.readDocument)(dnaPath);
|
|
286
|
-
const
|
|
287
|
-
const
|
|
472
|
+
const inPlace = (0, edit_1.applyDnaEditInText)(current, applied.edit, applied.dna);
|
|
473
|
+
const serialized = inPlace ?? (0, js_yaml_1.dump)(applied.dna, { lineWidth: -1 });
|
|
474
|
+
// Labelled repository-relative, as every working-tree loader labels it (task-179, `bug-245`).
|
|
475
|
+
const parsed = schema_1.DnaYaml.safeParse((0, validation_1.parseYaml)(serialized, loaders_1.DNA_YAML_PATH));
|
|
288
476
|
if (!parsed.success) {
|
|
289
|
-
const validationError = (0, validation_1.toValidationError)(parsed.error,
|
|
477
|
+
const validationError = (0, validation_1.toValidationError)(parsed.error, loaders_1.DNA_YAML_PATH);
|
|
290
478
|
return (0, types_1.coreErr)({ code: 'VALIDATION', message: validationError.message, details: { issues: validationError.issues } });
|
|
291
479
|
}
|
|
292
480
|
const outcome = { key: request.field, value: request.value };
|
|
293
481
|
if (current === serialized)
|
|
294
482
|
return (0, types_1.coreOk)(outcome);
|
|
295
|
-
|
|
296
|
-
const
|
|
483
|
+
const warnings = [];
|
|
484
|
+
const plannedWarnings = [];
|
|
485
|
+
if (inPlace === undefined) {
|
|
486
|
+
if (!force)
|
|
487
|
+
return (0, types_1.coreErr)({ code: 'CONFLICT', message: dnaRewriteConflict(request.field) });
|
|
488
|
+
warnings.push(exports.DNA_REWRITE_WARNING);
|
|
489
|
+
plannedWarnings.push(exports.DNA_REWRITE_PLANNED_WARNING);
|
|
490
|
+
}
|
|
491
|
+
const sha = (0, storage_1.writeAndCommit)(root, [{ path: loaders_1.DNA_YAML_PATH, content: serialized }], subject, {
|
|
492
|
+
warnings,
|
|
493
|
+
plannedWarnings,
|
|
494
|
+
});
|
|
297
495
|
const leaked = (0, write_guard_1.committedScopeError)(root, sha, loaders_1.DNA_YAML_PATH, serialized);
|
|
298
496
|
if (leaked)
|
|
299
497
|
return leaked;
|
|
300
|
-
return (0, types_1.coreOk)(outcome, { sha, message: subject });
|
|
498
|
+
return (0, types_1.coreOk)(outcome, { sha, message: subject }, warnings);
|
|
301
499
|
}
|
|
302
500
|
/**
|
|
303
501
|
* The one-line commit subject a DNA mutation writes: `wf(dna): {verb} {field} [{value}]`, extending
|
|
@@ -313,39 +511,40 @@ function dnaCommitSubject(request) {
|
|
|
313
511
|
* agree on where their target comes from (`dl-082-cli-parameter-shape`: a positional carries the
|
|
314
512
|
* identity of the target).
|
|
315
513
|
*
|
|
316
|
-
*
|
|
514
|
+
* Two usage errors, in the order they are checked:
|
|
317
515
|
*
|
|
318
|
-
* 1. **Missing** — nothing to act on
|
|
516
|
+
* 1. **Missing** — nothing to act on: {@link missingOperandReason}'s `missing required argument: <path>`,
|
|
517
|
+
* the wording every command uses (task-179, `bug-168`). The CLI registrar refuses it first, with the
|
|
518
|
+
* usage hint; this is the same refusal for a caller that reaches core without the registrar.
|
|
319
519
|
* 2. **Unparseable** — whatever `splitDnaPath` (`src/dna/set.ts`) refuses, reported with **its own**
|
|
320
520
|
* message rather than a flat one, because the three cases are not the same complaint: an empty
|
|
321
521
|
* segment (the BDD's `..language`) is malformed, an unterminated quote is a typo in the
|
|
322
522
|
* delimiters, and a `"` no delimiter can account for means the *name* has no spelling at all
|
|
323
|
-
* (`dl-083-dotted-entry-names-in-paths`, task-099).
|
|
324
|
-
* so that `dna set ..language python`, which is `P2.1-dna-set.feature`'s third scenario verbatim,
|
|
325
|
-
* keeps reporting `invalid key path: '..language'` rather than the migration hint. A stale
|
|
326
|
-
* invocation that is also malformed is malformed first.
|
|
327
|
-
* 3. **Extra positionals** — the old `dna set <key> <value>` spelling, and any slip of the same
|
|
328
|
-
* shape on the three new verbs. It gets a named message rather than being ignored, because
|
|
329
|
-
* `dl-082` is a breaking change to a shipped command and silently dropping the second word would
|
|
330
|
-
* make `dna set project.license MIT` look like it worked (`--value` absent, the write refused for
|
|
331
|
-
* a reason that names neither the second word nor the new grammar).
|
|
523
|
+
* (`dl-083-dotted-entry-names-in-paths`, task-099).
|
|
332
524
|
*
|
|
333
|
-
*
|
|
525
|
+
* A surplus operand — the old `dna set <key> <value>` spelling — is not checked here: since task-179
|
|
526
|
+
* (`bug-180`) the registrar refuses it before the project root is resolved, as for every command, with
|
|
527
|
+
* the `the value travels in --value` migration hint the four verbs declare ({@link DNA_SURPLUS_HINT}).
|
|
528
|
+
*
|
|
529
|
+
* Both are exit `2` (`UsageError` → `exitCodeForThrow`, `spec-005-cli-command-contract` §1).
|
|
334
530
|
*/
|
|
335
|
-
function dnaPathPositional(
|
|
531
|
+
function dnaPathPositional(positionals) {
|
|
336
532
|
const path = positionals?.[0];
|
|
337
533
|
if (path === undefined) {
|
|
338
|
-
throw new usage_error_1.UsageError(
|
|
534
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('path'));
|
|
339
535
|
}
|
|
340
536
|
const split = (0, set_1.splitDnaPath)(path);
|
|
341
537
|
if (!split.ok) {
|
|
342
538
|
throw new usage_error_1.UsageError(split.message);
|
|
343
539
|
}
|
|
344
|
-
if ((positionals?.length ?? 0) > 1) {
|
|
345
|
-
throw new usage_error_1.UsageError(`wingfoil dna ${verb} takes one positional <path>; the value travels in --value (got ${positionals.length} positionals)`);
|
|
346
|
-
}
|
|
347
540
|
return path;
|
|
348
541
|
}
|
|
542
|
+
/**
|
|
543
|
+
* What the surplus-operand refusal of the four DNA path verbs adds (`CorePositional.surplusHint`): the
|
|
544
|
+
* value moved out of a second positional into `--value` (`dl-082`, task-093), so a stale
|
|
545
|
+
* `dna set <key> <value>` is told where it goes.
|
|
546
|
+
*/
|
|
547
|
+
const DNA_SURPLUS_HINT = 'the value travels in --value';
|
|
349
548
|
/**
|
|
350
549
|
* `dna set <path> --value <v>` `CoreOperation.fn` (P2.1) — kept, and still the scalar spelling
|
|
351
550
|
* (`spec-002`, `spec-005` §4). Since task-093 it is **`update` restricted to a single value**: the
|
|
@@ -364,8 +563,8 @@ function dnaPathPositional(verb, positionals) {
|
|
|
364
563
|
* write the field.
|
|
365
564
|
*/
|
|
366
565
|
const dnaSetFn = async (params) => {
|
|
367
|
-
const { root, positionals, options } = params;
|
|
368
|
-
const keyPath = dnaPathPositional(
|
|
566
|
+
const { root, positionals, options, force } = params;
|
|
567
|
+
const keyPath = dnaPathPositional(positionals);
|
|
369
568
|
const value = options?.value;
|
|
370
569
|
if (value === undefined) {
|
|
371
570
|
throw new usage_error_1.UsageError('missing required argument: --value');
|
|
@@ -374,11 +573,11 @@ const dnaSetFn = async (params) => {
|
|
|
374
573
|
// reader (and `git log --grep`) sees the command that was run, not the verb it delegates to. The
|
|
375
574
|
// scalar-only restriction, `dl-080`(B)'s dirty-target guard (task-092) and the committed-tree
|
|
376
575
|
// post-condition all live in the shared pipeline now, on the one document it loads.
|
|
377
|
-
return runDnaMutation(root, { verb: 'update', field: keyPath, value }, `wf(dna): set ${keyPath}`, true);
|
|
576
|
+
return runDnaMutation(root, { verb: 'update', field: keyPath, value }, `wf(dna): set ${keyPath}`, true, force === true);
|
|
378
577
|
};
|
|
379
578
|
/** Split a verb's invocation into the positional `<path>`, `--value` and the per-entry `--entry-<field>` values. */
|
|
380
579
|
function dnaMutationRequest(verb, positionals, options) {
|
|
381
|
-
const field = dnaPathPositional(
|
|
580
|
+
const field = dnaPathPositional(positionals);
|
|
382
581
|
if (verb === 'add' && options?.value === undefined)
|
|
383
582
|
throw new usage_error_1.UsageError('missing required argument: --value');
|
|
384
583
|
// Every remaining option is an entry field, carried under its `--entry-<field>` name (see
|
|
@@ -404,18 +603,21 @@ function dnaMutationRequest(verb, positionals, options) {
|
|
|
404
603
|
}
|
|
405
604
|
/** `dna add` — create an entry, or append to a list (`dl-081` option (E); spec-006 §3, Tool `dna.add`). */
|
|
406
605
|
const dnaAddFn = async (params) => {
|
|
407
|
-
const { root, positionals, options } = params;
|
|
408
|
-
|
|
606
|
+
const { root, positionals, options, force } = params;
|
|
607
|
+
const request = dnaMutationRequest('add', positionals, options);
|
|
608
|
+
return runDnaMutation(root, request, dnaCommitSubject(request), false, force === true);
|
|
409
609
|
};
|
|
410
610
|
/** `dna remove` — drop an entry, a value from a list, or an optional field (Tool `dna.remove`). */
|
|
411
611
|
const dnaRemoveFn = async (params) => {
|
|
412
|
-
const { root, positionals, options } = params;
|
|
413
|
-
|
|
612
|
+
const { root, positionals, options, force } = params;
|
|
613
|
+
const request = dnaMutationRequest('remove', positionals, options);
|
|
614
|
+
return runDnaMutation(root, request, dnaCommitSubject(request), false, force === true);
|
|
414
615
|
};
|
|
415
616
|
/** `dna update` — change a value, a list, or the fields of one entry (Tool `dna.update`). */
|
|
416
617
|
const dnaUpdateFn = async (params) => {
|
|
417
|
-
const { root, positionals, options } = params;
|
|
418
|
-
|
|
618
|
+
const { root, positionals, options, force } = params;
|
|
619
|
+
const request = dnaMutationRequest('update', positionals, options);
|
|
620
|
+
return runDnaMutation(root, request, dnaCommitSubject(request), false, force === true);
|
|
419
621
|
};
|
|
420
622
|
/**
|
|
421
623
|
* The `--entry-<field>` namespace, re-exported from the DNA pillar (`src/dna/path.ts`), which owns it
|
|
@@ -449,6 +651,14 @@ const DNA_SET_VALUE_OPTION = {
|
|
|
449
651
|
required: true,
|
|
450
652
|
description: 'the new value for <path> (scalar fields only — use dna update for collections and lists)',
|
|
451
653
|
};
|
|
654
|
+
/**
|
|
655
|
+
* `--force` on the four DNA write verbs (task-193, ruling R20/Q9): the opt-in to the whole-file rewrite
|
|
656
|
+
* the in-place editor would otherwise refuse. A per-command flag, listed in `spec-008` §12.
|
|
657
|
+
*/
|
|
658
|
+
const DNA_FORCE_FLAG = {
|
|
659
|
+
name: 'force',
|
|
660
|
+
description: 'rewrite the whole dna.yaml when it cannot be edited in place (comments and formatting are not kept)',
|
|
661
|
+
};
|
|
452
662
|
const DNA_ENTRY_OPTIONS = (0, path_2.dnaEntryOptionNames)().map((field) => ({
|
|
453
663
|
name: (0, path_2.dnaEntryOptionName)(field),
|
|
454
664
|
description: `'${field}' of the entry <path> names, where that collection declares it`,
|
|
@@ -478,18 +688,26 @@ function singleOption(options, name) {
|
|
|
478
688
|
* none, so no call path can reach the decision with a working-tree registry. An unknown type is a
|
|
479
689
|
* domain `NOT_FOUND` (exit 1) with the exact P1.3 message
|
|
480
690
|
* `unknown memory type '<t>' (not defined in memory.yaml)`, returned BEFORE any write.
|
|
691
|
+
* With `--workflow <ref> [--step <key>]` (task-227, `spec-017` §7.10), the step the element is linked to
|
|
692
|
+
* is then resolved from the deduction at `HEAD` ({@link resolveAddLink}); a refusal (`NOT_FOUND`,
|
|
693
|
+
* `CONFLICT`, `VALIDATION`) is returned before the id is allocated, and the add commit carries the
|
|
694
|
+
* `WingFoil-Instance` / `WingFoil-Step` trailers. `--step` without `--workflow` is a usage error (exit 2).
|
|
481
695
|
* 4. **Generate the id** deterministically from the type's committed `id_pattern` (task-002's
|
|
482
696
|
* `generateId`, REQ-SYS-07), in `spec-001`'s fixed order (task-110, `dl-107` S2): first every
|
|
483
697
|
* frontmatter/context token from `--set` (a `--set` name the committed `id_pattern` and `path` do
|
|
484
698
|
* not contain is refused, exit 1; a token with no value names the option that supplies it), then
|
|
485
|
-
* the `{slug}` from the title, and — only for a `{n}`-token pattern — a sequence counter
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
699
|
+
* the `{slug}` from the title, and — only for a `{n}`-token pattern — a sequence counter matched
|
|
700
|
+
* against the already-materialized pattern (`src/memory/add.ts`; no wall-clock/random): the
|
|
701
|
+
* highest number any local branch, remote-tracking ref, `HEAD` or the working tree holds, across
|
|
702
|
+
* every folder the committed `path` pattern can resolve to, plus one (task-128, `dl-101` §2 (a),
|
|
703
|
+
* `bug-087`, `bug-162`). That read is wider than `HEAD` on purpose — it can only raise the number —
|
|
704
|
+
* and is the declared baseline `command-baseline` records for it.
|
|
705
|
+
* 5. **Fill the committed scaffold's bytes** with only the `id`/`status`/`--title`/`--tags`
|
|
491
706
|
* skeleton (P1.3; spec-010-memory-frontmatter-schema) plus the `--set` fields (a context token only
|
|
492
|
-
* where the scaffold declares it)
|
|
707
|
+
* where the scaffold declares it). `status` is the head of the type's committed machine
|
|
708
|
+
* (`spec-001`; `bug-214`, task-180), and a WIP limit declared on that state is checked
|
|
709
|
+
* (`requireWipSlot`, `CONFLICT`, exit 1), its holders counted at `HEAD` and on every local and
|
|
710
|
+
* remote-tracking branch (`dl-156` Q1 (a), task-274). Then **write + commit** through task-022's
|
|
493
711
|
* confined `writeMemoryEntry` (REQ-SEC-06 refuse-before-write + one scoped commit
|
|
494
712
|
* `wf(<type>): add <id>`); the returned sha rides `CoreResult.commit`.
|
|
495
713
|
*
|
|
@@ -499,7 +717,7 @@ function singleOption(options, name) {
|
|
|
499
717
|
* only exit-2 path and are thrown before this try.
|
|
500
718
|
*/
|
|
501
719
|
const memoryAddFn = async (params) => {
|
|
502
|
-
const { root, options } = params;
|
|
720
|
+
const { root, options, fields: extraFields } = params;
|
|
503
721
|
const type = singleOption(options, 'type');
|
|
504
722
|
if (type === undefined)
|
|
505
723
|
throw new usage_error_1.UsageError('missing required argument: --type');
|
|
@@ -510,6 +728,11 @@ const memoryAddFn = async (params) => {
|
|
|
510
728
|
const set = (0, memory_1.parseSetOptions)(options?.set);
|
|
511
729
|
if (!set.ok)
|
|
512
730
|
throw new usage_error_1.UsageError(set.message);
|
|
731
|
+
// task-227 (`spec-017` §7.10): `--step` picks a step of the instance `--workflow` names.
|
|
732
|
+
const workflowRef = singleOption(options, 'workflow');
|
|
733
|
+
const stepKey = singleOption(options, 'step');
|
|
734
|
+
if (stepKey !== undefined && workflowRef === undefined)
|
|
735
|
+
throw new usage_error_1.UsageError('missing required argument: --workflow');
|
|
513
736
|
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
514
737
|
if (!identity.ok)
|
|
515
738
|
return identity;
|
|
@@ -519,7 +742,7 @@ const memoryAddFn = async (params) => {
|
|
|
519
742
|
const resolved = (0, memory_add_type_1.resolveAddType)(root, type);
|
|
520
743
|
if (!resolved.ok)
|
|
521
744
|
return resolved;
|
|
522
|
-
const { pathPattern, idPattern, scaffold } = resolved.value;
|
|
745
|
+
const { pathPattern, idPattern, scaffold, initialState, memoryYaml: committedMemoryYaml } = resolved.value;
|
|
523
746
|
const unknown = (0, memory_1.unknownSetNames)(set.values, idPattern, pathPattern);
|
|
524
747
|
if (unknown.length > 0) {
|
|
525
748
|
return (0, types_1.coreErr)({
|
|
@@ -530,20 +753,41 @@ const memoryAddFn = async (params) => {
|
|
|
530
753
|
});
|
|
531
754
|
}
|
|
532
755
|
try {
|
|
533
|
-
//
|
|
534
|
-
|
|
756
|
+
// task-227: the step the element is linked to, decided at HEAD before the id is allocated, so every
|
|
757
|
+
// refusal precedes the write (and a dry run refuses the same way). A `spec-003` error at HEAD maps as
|
|
758
|
+
// `workflow next` maps it; a git failure is a `StorageError`, mapped below.
|
|
759
|
+
let link;
|
|
760
|
+
if (workflowRef !== undefined) {
|
|
761
|
+
const resolvedLink = loadOrError(() => (0, memory_add_link_1.resolveAddLink)(root, workflowRef, stepKey, type));
|
|
762
|
+
const linked = resolvedLink.ok ? resolvedLink.value : resolvedLink;
|
|
763
|
+
if (!linked.ok)
|
|
764
|
+
return linked;
|
|
765
|
+
link = linked.value;
|
|
766
|
+
}
|
|
767
|
+
const trailers = link === undefined ? [] : (0, memory_add_link_1.addLinkTrailers)(link);
|
|
768
|
+
// spec-001's order: {date} → {author} → field/context tokens → {slug} → {n}, so the counter sees
|
|
769
|
+
// the materialized prefix. {date} is read ONCE, from the author date git records for this add's
|
|
770
|
+
// commit, and the commit is pinned to it below, as it is to the author {author} slugs (task-163).
|
|
771
|
+
const date = (0, validation_1.patternTokens)(idPattern).includes('date') ? (0, memory_1.readAuthorDate)(root, identity.value) : undefined;
|
|
772
|
+
const materialized = (0, memory_1.expandFieldTokens)(idPattern, set.values, { date, authorName: identity.value.name });
|
|
535
773
|
const sequence = (0, memory_1.hasNumericToken)(materialized)
|
|
536
|
-
? (0, memory_1.nextSequenceNumber)(
|
|
774
|
+
? (0, memory_1.nextSequenceNumber)(root, pathPattern, materialized)
|
|
537
775
|
: 0;
|
|
538
776
|
const id = (0, validation_1.generateId)(materialized, { slug: (0, memory_1.slugifyTitle)(title), n: sequence });
|
|
539
|
-
const
|
|
777
|
+
const extra = Object.keys(extraFields ?? {})
|
|
778
|
+
.sort()
|
|
779
|
+
.map((name) => [name, extraFields[name]]);
|
|
780
|
+
const content = (0, memory_1.renderAddDocument)(scaffold, { id, title, tags, fields: [...(0, memory_1.writtenFields)(scaffold, set.values), ...extra], status: initialState });
|
|
540
781
|
const pathValues = { ...set.values, id };
|
|
541
|
-
const
|
|
782
|
+
const subject = `wf(${type}): add ${id}`;
|
|
783
|
+
// What the operation reports is the message it records, the build signature aside (`commitPaths`).
|
|
784
|
+
const message = trailers.length === 0 ? subject : `${subject}\n\n${trailers.join('\n')}`;
|
|
542
785
|
// dl-080 (B) / bug-078, AC3: this verb CREATES, so the rule is absence rather than cleanliness.
|
|
543
|
-
// `
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
//
|
|
786
|
+
// Since task-128 a `{n}` id is above every number any baseline holds, but a slug-only id
|
|
787
|
+
// (`note-{slug}`) still lands on an occupied path whenever the title repeats, and a `{n}` id can
|
|
788
|
+
// land on a git-ignored file (no baseline the counter reads holds it). The write below
|
|
789
|
+
// is unconditional, which would turn `wf(<type>): add <id>` into a commit that overwrites (or
|
|
790
|
+
// deletes lines from) an existing element. The path is resolved here rather than taken from
|
|
547
791
|
// `writeMemoryEntry`'s return value because the guard must run BEFORE the write; the resolution
|
|
548
792
|
// is pure, so doing it twice is free of side effects and keeps that throwing storage primitive's
|
|
549
793
|
// contract untouched. A confinement escape still throws `StorageError` from this same call.
|
|
@@ -551,19 +795,40 @@ const memoryAddFn = async (params) => {
|
|
|
551
795
|
const absent = (0, write_guard_1.requireAbsentTarget)(root, targetPath);
|
|
552
796
|
if (!absent.ok)
|
|
553
797
|
return absent;
|
|
554
|
-
|
|
798
|
+
// `add` is the verb that enters the initial state, so a WIP limit declared there holds here too
|
|
799
|
+
// (task-180, `dl-110` P3 (a)), counted at `HEAD` — the commit the type was resolved at — and on
|
|
800
|
+
// every local and remote-tracking branch (`dl-156` Q1 (a), task-274).
|
|
801
|
+
const slot = (0, memory_transition_1.requireWipSlot)(root, 'HEAD', committedMemoryYaml, type, initialState);
|
|
802
|
+
if (!slot.ok)
|
|
803
|
+
return slot;
|
|
804
|
+
const warnings = slot.value.map(validation_1.formatDiagnostic);
|
|
805
|
+
const { path, sha } = (0, memory_1.writeMemoryEntry)(root, pathPattern, pathValues, content, subject, {
|
|
806
|
+
author: identity.value,
|
|
807
|
+
warnings,
|
|
808
|
+
trailers,
|
|
809
|
+
// `@`: git reads a bare `<seconds> <offset>` as a timestamp only from 9 digits of seconds up
|
|
810
|
+
// (`0 +0000` is "invalid date format"); `@<seconds> <offset>` is a timestamp at any width.
|
|
811
|
+
...(date === undefined ? {} : { env: { GIT_AUTHOR_DATE: `@${date}` } }),
|
|
812
|
+
});
|
|
555
813
|
const leaked = (0, write_guard_1.committedScopeError)(root, sha, targetPath, content);
|
|
556
814
|
if (leaked)
|
|
557
815
|
return leaked;
|
|
558
|
-
return (0, types_1.coreOk)({ id, path: (0, path_1.relative)(root, path) }, { sha, message });
|
|
816
|
+
return (0, types_1.coreOk)({ id, path: (0, path_1.relative)(root, path) }, { sha, message }, warnings);
|
|
559
817
|
}
|
|
560
818
|
catch (error) {
|
|
561
819
|
if (error instanceof storage_1.StorageError) {
|
|
562
|
-
|
|
820
|
+
// One rule, one code (task-130, `bug-123`, `spec-005` §3): a confinement refusal is `VALIDATION`
|
|
821
|
+
// here as it is from the transition verbs' guard — the request named a target REQ-SEC-06
|
|
822
|
+
// forbids, or one outside its type's folder (task-281, `bug-325`, approver ruling D1: exit 1).
|
|
823
|
+
// Every other storage failure (a git read, an unresolved path token) stays `IO`.
|
|
824
|
+
const confinement = error.code === errors_1.E_PATH_ESCAPES_ROOT || error.code === errors_1.E_PATH_OUTSIDE_TYPE_FOLDER || error.code === errors_1.E_TARGET_IS_SYMLINK;
|
|
825
|
+
return (0, types_1.coreErr)({ code: confinement ? 'VALIDATION' : 'IO', message: error.message });
|
|
563
826
|
}
|
|
564
827
|
if (error instanceof validation_1.ValidationError) {
|
|
565
|
-
|
|
566
|
-
|
|
828
|
+
// Every `ValidationError` src/ builds carries at least one issue (task-189: each construction
|
|
829
|
+
// site passes a literal issue, is guarded by `issues.length > 0`, or maps a failed Zod parse),
|
|
830
|
+
// so the issues ARE the reason; there is no issue-less fallback to choose.
|
|
831
|
+
return (0, types_1.coreErr)({ code: 'VALIDATION', message: error.issues.map((issue) => issue.message).join('; ') });
|
|
567
832
|
}
|
|
568
833
|
throw error;
|
|
569
834
|
}
|
|
@@ -613,22 +878,26 @@ const memorySearchFn = async (params) => {
|
|
|
613
878
|
if (positional !== undefined)
|
|
614
879
|
(0, memory_1.validateSearchQuery)(positional);
|
|
615
880
|
const query = positional ?? '';
|
|
616
|
-
const loaded =
|
|
881
|
+
const loaded = loadConfigOrError(root, () => (0, loaders_1.loadMemoryYaml)(root));
|
|
617
882
|
if (!loaded.ok)
|
|
618
883
|
return loaded;
|
|
619
884
|
const tag = options?.tag;
|
|
620
885
|
const type = options?.type;
|
|
621
886
|
const status = options?.status;
|
|
887
|
+
const diagnostics = [];
|
|
622
888
|
const scanned = (0, memory_1.searchMemoryDocuments)(root, loaded.value, query, {
|
|
623
889
|
...(tag !== undefined ? { tag } : {}),
|
|
624
890
|
...((0, memory_1.isArchivedStatus)(status) ? { includeArchived: true } : {}),
|
|
891
|
+
onDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
|
|
625
892
|
});
|
|
626
893
|
const matches = scanned
|
|
627
894
|
.filter((match) => (type === undefined || match.type === type) && (status === undefined || match.status === status))
|
|
628
895
|
.map(({ path, id, title, type: docType, status: docStatus, tags }) => ({ path, id, title, type: docType, status: docStatus, tags }));
|
|
896
|
+
// task-171 (`bug-031`): a file the scan could not read is a warning, never the search's failure.
|
|
897
|
+
const warnings = diagnostics.map(validation_1.formatDiagnostic);
|
|
629
898
|
return matches.length === 0
|
|
630
|
-
? (0, types_1.coreOk)({ query, matches, message: NO_MEMORY_SEARCH_MATCHES_MESSAGE })
|
|
631
|
-
: (0, types_1.coreOk)({ query, matches });
|
|
899
|
+
? (0, types_1.coreOk)({ query, matches, message: NO_MEMORY_SEARCH_MATCHES_MESSAGE }, undefined, warnings)
|
|
900
|
+
: (0, types_1.coreOk)({ query, matches }, undefined, warnings);
|
|
632
901
|
};
|
|
633
902
|
/**
|
|
634
903
|
* `memory history` `CoreOperation.fn` (P1.10, `mutates: false`; spec-006-core-domain-api §3 memory
|
|
@@ -645,7 +914,9 @@ const memorySearchFn = async (params) => {
|
|
|
645
914
|
* 3. **Resolve the id to a document** with task-009's {@link findMemoryDocumentById} — an EXACT
|
|
646
915
|
* frontmatter-`id` match over the deterministically-sorted document set, never a substring (that
|
|
647
916
|
* is `memory search`'s job). No match is a domain `NOT_FOUND` (exit 1) with the exact P1.10 message
|
|
648
|
-
* `document not found: <id>`, returned rather than thrown.
|
|
917
|
+
* `document not found: <id>`, returned rather than thrown. A document written in a `format` newer
|
|
918
|
+
* than this build reads is refused, `VALIDATION` (exit 1) with the `dl-149` `E_INVALID_FORMAT` text
|
|
919
|
+
* (task-257, `bug-241`); one the lookup passes on its way is a warning. Archived documents are deliberately NOT
|
|
649
920
|
* excluded: REQ-STATE-06 scopes its exclusion to default *search* results, and explicitly
|
|
650
921
|
* guarantees an archived element remains "present on disk and in git history" — which is precisely
|
|
651
922
|
* what this command reads.
|
|
@@ -657,20 +928,49 @@ const memorySearchFn = async (params) => {
|
|
|
657
928
|
*
|
|
658
929
|
* A document that exists on disk but has never been committed yields `entries: []` — correctly, not
|
|
659
930
|
* as an error: per ADR-007 git history IS the audit trail, so an uncommitted document has none yet.
|
|
931
|
+
* A git read that fails — a tree git cannot read, an answer past the read buffer — is a `CoreError`
|
|
932
|
+
* `IO` (exit `1`) carrying git's message, never an empty trail (task-142, `bug-072`).
|
|
660
933
|
*/
|
|
661
934
|
const memoryHistoryFn = async (params) => {
|
|
662
935
|
const { root, positional: id } = params;
|
|
663
936
|
if (id === undefined || id.trim().length === 0) {
|
|
664
|
-
throw new usage_error_1.UsageError('
|
|
937
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
|
|
665
938
|
}
|
|
666
|
-
const loaded =
|
|
939
|
+
const loaded = loadConfigOrError(root, () => (0, loaders_1.loadMemoryYaml)(root));
|
|
667
940
|
if (!loaded.ok)
|
|
668
941
|
return loaded;
|
|
669
|
-
const
|
|
942
|
+
const diagnostics = [];
|
|
943
|
+
let found;
|
|
944
|
+
try {
|
|
945
|
+
found = (0, memory_1.findMemoryDocumentById)(root, loaded.value, id, { onDiagnostic: (diagnostic) => diagnostics.push(diagnostic) });
|
|
946
|
+
}
|
|
947
|
+
catch (error) {
|
|
948
|
+
// The `dl-149` refusal of the element the lookup names (task-257, `bug-241`): a document written in
|
|
949
|
+
// a newer format is not read by today's rules. Any other failure (an unreadable directory) is not
|
|
950
|
+
// that refusal and propagates as it did (review F2).
|
|
951
|
+
if (!(0, validation_1.isNewerFormatError)(error))
|
|
952
|
+
throw error;
|
|
953
|
+
return (0, types_1.coreErr)({
|
|
954
|
+
code: 'VALIDATION',
|
|
955
|
+
message: `cannot read the history of ${id}: its document is written in a newer format: ${error.message}`,
|
|
956
|
+
details: { issues: error.issues },
|
|
957
|
+
});
|
|
958
|
+
}
|
|
670
959
|
if (!found) {
|
|
671
960
|
return (0, types_1.coreErr)({ code: 'NOT_FOUND', message: `document not found: ${id}` });
|
|
672
961
|
}
|
|
673
|
-
|
|
962
|
+
let transitions;
|
|
963
|
+
try {
|
|
964
|
+
transitions = (0, memory_1.reconstructMemoryTransitions)(root, found.path);
|
|
965
|
+
}
|
|
966
|
+
catch (error) {
|
|
967
|
+
// A git read that failed is `IO`, never an empty trail (task-142, `bug-072`): the walk throws
|
|
968
|
+
// rather than answering "no history" for a history it could not read. Since task-171 the tolerant
|
|
969
|
+
// reconstruction throws nothing else (a revision that does not parse is an entry, `bug-188`), so
|
|
970
|
+
// what reaches here is a `StorageError`.
|
|
971
|
+
return (0, types_1.coreErr)({ code: 'IO', message: error.message });
|
|
972
|
+
}
|
|
973
|
+
const entries = transitions.map((transition) => ({
|
|
674
974
|
sha: transition.sha,
|
|
675
975
|
author: `${transition.authorName} <${transition.authorEmail}>`,
|
|
676
976
|
timestamp: transition.date,
|
|
@@ -682,8 +982,17 @@ const memoryHistoryFn = async (params) => {
|
|
|
682
982
|
: null,
|
|
683
983
|
reason: transition.reason,
|
|
684
984
|
subject: transition.subject,
|
|
985
|
+
wingfoil: transition.wingfoil,
|
|
986
|
+
...(transition.unreadable !== undefined ? { unreadable: transition.unreadable } : {}),
|
|
685
987
|
}));
|
|
686
|
-
|
|
988
|
+
// task-171: the documents the lookup could not read (`bug-031`), then each revision of this one whose
|
|
989
|
+
// frontmatter does not parse (`bug-188`), named with its commit.
|
|
990
|
+
for (const transition of transitions) {
|
|
991
|
+
if (transition.unreadable !== undefined) {
|
|
992
|
+
diagnostics.push((0, memory_1.memoryUnreadableDiagnostic)(found.path, `${(0, memory_1.firstLineOf)(transition.unreadable)} (at ${transition.sha})`));
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return (0, types_1.coreOk)({ id, path: found.path, entries }, undefined, diagnostics.map(validation_1.formatDiagnostic));
|
|
687
996
|
};
|
|
688
997
|
/**
|
|
689
998
|
* `memory submit` `CoreOperation.fn` (P1.6, `mutates: true`; spec-006-core-domain-api §3). Submit is
|
|
@@ -692,16 +1001,22 @@ const memoryHistoryFn = async (params) => {
|
|
|
692
1001
|
*
|
|
693
1002
|
* 1. **`<id>`** absent or blank → `UsageError` (exit 2), as `memory history` — before the identity
|
|
694
1003
|
* check, so the exit code does not depend on the machine (task-125, `bug-172`).
|
|
695
|
-
* 2. **`requireGitIdentity`** (REQ-SEC-01) — exit 1.
|
|
696
|
-
*
|
|
697
|
-
*
|
|
1004
|
+
* 2. **`requireGitIdentity`** (REQ-SEC-01) — exit 1. Steps 2 and 3 are {@link beginMemoryTransition},
|
|
1005
|
+
* the preamble every transition verb shares (task-132, `bug-142`; `spec-006` §7).
|
|
1006
|
+
* 3. **{@link prepareMemoryTransition}** — it resolves the `memory.yaml` and the document committed at
|
|
1007
|
+
* `HEAD` itself (task-091, `dl-080` (B); task-247, `bug-187`); no committed machine, an unreadable one, not found, unknown type,
|
|
698
1008
|
* invalid state, or an illegal transition, each a `CoreResult.error` (exit 1); an illegal one
|
|
699
1009
|
* carries the pinned `illegal transition <from> -> <to> for type '<type>'` (`dl-032`, P1.6 sc.2).
|
|
700
1010
|
* 5. **Required fields** (spec-010 validation rules) — `title` and every `template.frontmatter.required`
|
|
701
1011
|
* field must be non-empty, else `VALIDATION` `missing required field on submit: <fields>` (exit 1).
|
|
1012
|
+
* A field the type lists in `template.frontmatter.not_applicable_allowed` may hold
|
|
1013
|
+
* `"n/a — <reason>"`; a not-applicable value anywhere else, or one with no reason, is `VALIDATION`
|
|
1014
|
+
* `not-applicable value on submit: …` naming the field (`dl-124`, task-168, {@link requireRequiredFields}).
|
|
702
1015
|
* 6. **Edit + commit** — `status` set to the target and `rejection_reason` removed (spec-010 field-write
|
|
703
1016
|
* ownership; every other byte kept), then one commit scoped to that file, subject
|
|
704
|
-
* `wf(<type>): submit <id>` with no bracket
|
|
1017
|
+
* `wf(<type>): submit <id>` with no bracket (spec-004 §4.3, `dl-054`) and, when the author's edits
|
|
1018
|
+
* ride along, one body line `Carries content: …` naming them (`describeSubmitContent`; task-209,
|
|
1019
|
+
* `dl-106` W1 (a), `spec-008` §2); a pure transition has no body. The rendered document is
|
|
705
1020
|
* re-parsed first (`commitMemoryTransition`'s post-condition: `status` is the target, no
|
|
706
1021
|
* `rejection_reason`, nothing else changed); a failure is `VALIDATION` (exit 1) with nothing written. No `--reason`: spec-008 §2
|
|
707
1022
|
* requires it only on the approval gates (`dl-027`).
|
|
@@ -709,29 +1024,39 @@ const memoryHistoryFn = async (params) => {
|
|
|
709
1024
|
const memorySubmitFn = async (params) => {
|
|
710
1025
|
const { root, positional: id } = params;
|
|
711
1026
|
if (id === undefined || id.trim().length === 0) {
|
|
712
|
-
throw new usage_error_1.UsageError('
|
|
1027
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
|
|
713
1028
|
}
|
|
714
|
-
const
|
|
715
|
-
if (!identity.ok)
|
|
716
|
-
return identity;
|
|
717
|
-
const prepared = (0, memory_transition_1.prepareMemoryTransition)(root, id, 'submit');
|
|
1029
|
+
const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'submit');
|
|
718
1030
|
if (!prepared.ok)
|
|
719
1031
|
return prepared;
|
|
720
1032
|
const { type, path, frontmatter, content, from, to } = prepared.value;
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
1033
|
+
const fieldsFilled = (0, required_fields_1.requireRequiredFields)(prepared.value.memoryYaml, type, frontmatter, 'submit');
|
|
1034
|
+
if (!fieldsFilled.ok)
|
|
1035
|
+
return fieldsFilled;
|
|
1036
|
+
// The subject stays plain (`dl-054`, ruling R20); the body declares the content the commit carries
|
|
1037
|
+
// beyond the state move, measured against the document committed at the sha the transition was
|
|
1038
|
+
// decided at (task-209, `dl-106` W1 (a)). A pure transition gets no body line.
|
|
727
1039
|
const rendered = (0, memory_1.renderSubmitDocument)(content, to);
|
|
1040
|
+
const committedContent = (0, storage_1.readPathAtRev)(root, prepared.value.sha, path);
|
|
1041
|
+
const described = (0, memory_1.describeSubmitContent)(committedContent, content, to);
|
|
1042
|
+
// `describeSubmitContent` compares line-ending-normalized text; whether the line endings change the
|
|
1043
|
+
// blob is git's answer, after its own filters (`core.autocrlf`, `.gitattributes`; review F1). It is
|
|
1044
|
+
// asked whatever else changed (re-review 1): against the rendered committed copy when nothing else
|
|
1045
|
+
// did, otherwise against this rendering written in the committed document's line endings.
|
|
1046
|
+
let lineEndingsChanged = false;
|
|
1047
|
+
if (committedContent !== null) {
|
|
1048
|
+
const baseline = described.length === 0 ? (0, memory_1.renderSubmitDocument)(committedContent, to) : (0, memory_1.withLineEndingsOf)(rendered, committedContent);
|
|
1049
|
+
lineEndingsChanged = !(0, storage_1.storesAsBlob)(root, path, rendered, baseline);
|
|
1050
|
+
}
|
|
1051
|
+
const carries = lineEndingsChanged ? [...described, memory_1.LINE_ENDINGS_ITEM] : described;
|
|
1052
|
+
const message = (0, memory_1.formatMemoryCommitMessage)({ type, op: 'submit', ids: [id], carries });
|
|
728
1053
|
// `carries-content`: submit is the ONE verb entitled to bring the author's body and required fields
|
|
729
1054
|
// into its commit (spec-010's field-write ownership row), so it is not guarded against a modified
|
|
730
1055
|
// working tree and its commit need not differ from HEAD~1 by `status` alone (task-088, bug-076 AC4).
|
|
731
1056
|
const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message, { [memory_1.REJECTION_REASON_FIELD]: undefined }, 'carries-content');
|
|
732
1057
|
if (!committed.ok)
|
|
733
1058
|
return committed;
|
|
734
|
-
return (0, types_1.coreOk)({ id, path, from, to }, { sha: committed.value, message });
|
|
1059
|
+
return (0, types_1.coreOk)({ id, path, from, to }, { sha: committed.value, message }, prepared.value.warnings);
|
|
735
1060
|
};
|
|
736
1061
|
/**
|
|
737
1062
|
* `memory approve` `CoreOperation.fn` (P1.7, `mutates: true`; spec-006-core-domain-api §3). Approve is
|
|
@@ -744,18 +1069,20 @@ const memorySubmitFn = async (params) => {
|
|
|
744
1069
|
* 2. **`requireReason`** (REQ-SEC-04, task-041) → `UsageError` `missing required argument: --reason`
|
|
745
1070
|
* (exit 2, P1.7 sc.2). Placed before any file is read, so an omitted reason touches nothing. Both
|
|
746
1071
|
* usage checks precede the identity check (task-125, `bug-172`).
|
|
747
|
-
* 3. **`requireGitIdentity`** (REQ-SEC-01) — exit 1.
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
* (
|
|
1072
|
+
* 3. **`requireGitIdentity`** (REQ-SEC-01) — exit 1. Steps 3 and 4 are {@link beginMemoryTransition}
|
|
1073
|
+
* (task-132, `bug-142`; `spec-006` §7), which reads the identity ONCE: step 6 authorizes it, the
|
|
1074
|
+
* `Approver:` line names it, and the commit is authored as it (`--author`), so the three can never
|
|
1075
|
+
* name different principals (`bug-149`).
|
|
1076
|
+
* 4. **{@link prepareMemoryTransition}** — it resolves the `memory.yaml` and the document committed at
|
|
1077
|
+
* `HEAD` itself (task-091, `dl-080` (B); task-247, `bug-187`); no committed machine, an unreadable one, not found, unknown type,
|
|
751
1078
|
* invalid state, or an illegal transition, each a `CoreResult.error` (exit 1); an illegal one
|
|
752
1079
|
* carries the pinned `illegal transition <from> -> <to> for type '<type>'` (`dl-032`), whose `<to>`
|
|
753
|
-
* is `approve`
|
|
1080
|
+
* is `(none)`: `approve` reaches nothing from `<from>` (task-181, `bug-165`).
|
|
754
1081
|
* 6. **`requireApprovalAuthority`** (REQ-SEC-03, task-040) — exit 1 with
|
|
755
1082
|
* `user not authorized to approve type '<type>'` (P1.7 sc.3). It runs after step 5 because its
|
|
756
1083
|
* message interpolates the document's type, which is only knowable once the document is located,
|
|
757
|
-
* and `
|
|
758
|
-
* twice would double the cost of every approve. Nothing is weakened by the order: the authority
|
|
1084
|
+
* and `findMemoryDocumentByIdAtRev` at `HEAD` is a full scan of the registered content roots —
|
|
1085
|
+
* resolving the type twice would double the cost of every approve. Nothing is weakened by the order: the authority
|
|
759
1086
|
* *decision* depends on nothing step 5 computes (adr-006 fixes one uniform `approver` role, keyed
|
|
760
1087
|
* on the git identity), and step 5 writes nothing. It reads the roles from the **committed**
|
|
761
1088
|
* `.wingfoil/dna.yaml` and resolves that file itself (task-090, `bug-079`) — which is why no
|
|
@@ -764,29 +1091,40 @@ const memorySubmitFn = async (params) => {
|
|
|
764
1091
|
* 7. **Edit + commit** — `status` set to the target and **nothing else** (spec-010 field-write
|
|
765
1092
|
* ownership: approve changes only `status`; approver and reason live in the commit message). One
|
|
766
1093
|
* commit scoped to that file, subject `wf(<type>): approve <id> [<from> → <to>]` with the mandatory
|
|
767
|
-
* `Approver:` / `Reason:` body (
|
|
1094
|
+
* `Approver:` / `Reason:` body (AGENTS.md §5.1; `dl-054` confines the bracket to the
|
|
768
1095
|
* approver-gated verbs, which is why `memory submit` has none). `commitMemoryTransition` re-parses
|
|
769
1096
|
* the rendered frontmatter first and refuses (exit 1, nothing written) unless `status` is the
|
|
770
1097
|
* target and no other field's value moved — which is exactly spec-010's "only `status`" rule, so
|
|
771
1098
|
* no extra `expected` entry is needed here.
|
|
1099
|
+
* 8. **The `supersedes:` trigger** (task-162, `dl-065` Q1.1; {@link prepareSupersede}). When the target
|
|
1100
|
+
* is the `waiting` state whose forward edge leads to `superseded` (`adr` `accepted`, `tech-spec`
|
|
1101
|
+
* `approved`) and the document's committed `supersedes:` names an element, that element is
|
|
1102
|
+
* resolved at the same `HEAD` sha, and refused (exit `1`) when it is missing, of another type, not
|
|
1103
|
+
* in that state, or not writable as a status change. This runs after step 6 and before step 7's
|
|
1104
|
+
* write, and both documents pass `checkMemoryTransition` before either is written, so a refusal
|
|
1105
|
+
* leaves the repository as it was. The approve commit comes first; then a second commit, scoped
|
|
1106
|
+
* to the superseded document, `wf(<type>): finalize <A> [<state> → superseded]`, with a `Reason:`
|
|
1107
|
+
* citing the approve sha and no `Approver:` line (`spec-008` §2). The result reports both; its
|
|
1108
|
+
* `commit` is the approve's.
|
|
772
1109
|
*/
|
|
773
1110
|
const memoryApproveFn = async (params) => {
|
|
774
1111
|
const { root, positional: id, options } = params;
|
|
775
1112
|
if (id === undefined || id.trim().length === 0) {
|
|
776
|
-
throw new usage_error_1.UsageError('
|
|
1113
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
|
|
777
1114
|
}
|
|
778
1115
|
const reason = (0, require_reason_1.requireReason)(options);
|
|
779
|
-
const
|
|
780
|
-
if (!identity.ok)
|
|
781
|
-
return identity;
|
|
782
|
-
const prepared = (0, memory_transition_1.prepareMemoryTransition)(root, id, 'approve');
|
|
1116
|
+
const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'approve');
|
|
783
1117
|
if (!prepared.ok)
|
|
784
1118
|
return prepared;
|
|
785
1119
|
const { type, path, content, from, to } = prepared.value;
|
|
786
|
-
const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type);
|
|
1120
|
+
const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type, prepared.value.identity);
|
|
787
1121
|
if (!authorized.ok)
|
|
788
1122
|
return authorized;
|
|
789
|
-
const
|
|
1123
|
+
const supersede = (0, memory_supersede_1.prepareSupersede)(root, prepared.value);
|
|
1124
|
+
if (!supersede.ok)
|
|
1125
|
+
return supersede;
|
|
1126
|
+
const superseded = supersede.value;
|
|
1127
|
+
const { name, email } = prepared.value.identity;
|
|
790
1128
|
const message = (0, memory_1.formatMemoryCommitMessage)({
|
|
791
1129
|
type,
|
|
792
1130
|
op: 'approve',
|
|
@@ -795,25 +1133,89 @@ const memoryApproveFn = async (params) => {
|
|
|
795
1133
|
approver: { name, email, role: approval_authority_1.APPROVER_ROLE },
|
|
796
1134
|
reason,
|
|
797
1135
|
});
|
|
798
|
-
const
|
|
1136
|
+
const rendered = (0, memory_1.setFrontmatterField)(content, 'status', to);
|
|
1137
|
+
let renderedSuperseded = '';
|
|
1138
|
+
if (superseded !== null) {
|
|
1139
|
+
// Both documents are checked before either is written, so a refusal of the second leaves the
|
|
1140
|
+
// first unwritten too (task-162). The approved one is checked again by its own commit below.
|
|
1141
|
+
renderedSuperseded = (0, memory_1.setFrontmatterField)(superseded.content, 'status', superseded.to);
|
|
1142
|
+
const checkedApproved = (0, memory_transition_1.checkMemoryTransition)(root, prepared.value, rendered);
|
|
1143
|
+
if (!checkedApproved.ok)
|
|
1144
|
+
return checkedApproved;
|
|
1145
|
+
const checkedSuperseded = (0, memory_transition_1.checkMemoryTransition)(root, superseded, renderedSuperseded);
|
|
1146
|
+
if (!checkedSuperseded.ok) {
|
|
1147
|
+
return (0, types_1.coreErr)({
|
|
1148
|
+
...checkedSuperseded.error,
|
|
1149
|
+
message: `cannot approve ${id}: its supersedes: field names ${superseded.id}, which cannot be superseded: ${checkedSuperseded.error.message}`,
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
// Both lookups read the same HEAD sha in path order; a file unreadable to both is named once. Known
|
|
1154
|
+
// before the first commit, so a dry run, which stops there, reports them too (task-210 review, F4).
|
|
1155
|
+
const warnings = superseded === null ? prepared.value.warnings : [...new Set([...prepared.value.warnings, ...superseded.warnings])];
|
|
1156
|
+
const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message, {}, 'declared-fields-only', warnings);
|
|
799
1157
|
if (!committed.ok)
|
|
800
1158
|
return committed;
|
|
801
|
-
|
|
1159
|
+
if (superseded === null)
|
|
1160
|
+
return (0, types_1.coreOk)({ id, path, from, to }, { sha: committed.value, message }, warnings);
|
|
1161
|
+
const finalizeMessage = (0, memory_1.formatMemoryCommitMessage)({
|
|
1162
|
+
type,
|
|
1163
|
+
op: 'finalize',
|
|
1164
|
+
ids: [superseded.id],
|
|
1165
|
+
transition: { from: superseded.from, to: superseded.to },
|
|
1166
|
+
reason: (0, memory_supersede_1.supersedeReason)(id, committed.value),
|
|
1167
|
+
});
|
|
1168
|
+
let finalized;
|
|
1169
|
+
try {
|
|
1170
|
+
finalized = (0, memory_transition_1.commitMemoryTransition)(root, superseded, renderedSuperseded, finalizeMessage);
|
|
1171
|
+
}
|
|
1172
|
+
catch (error) {
|
|
1173
|
+
// A refused commit is worded from its parts, without the primitive's "the working tree and the index
|
|
1174
|
+
// are as they were": the next statement makes the working tree differ on purpose (task-210 review,
|
|
1175
|
+
// F5). Any other error keeps `String`'s `name: message`.
|
|
1176
|
+
finalized = (0, types_1.coreErr)({ code: 'IO', message: (0, storage_1.commitFailureSummary)(error) });
|
|
1177
|
+
// The commit primitive put the file back when git refused the commit (task-210, `bug-217`); the
|
|
1178
|
+
// recovery below commits the file as it stands, so write the finalized status into it again — the
|
|
1179
|
+
// one write here meant to outlive a failed commit, because the approve has already happened.
|
|
1180
|
+
(0, storage_1.writeDocument)((0, path_1.join)(root, superseded.path), renderedSuperseded);
|
|
1181
|
+
}
|
|
1182
|
+
if (!finalized.ok) {
|
|
1183
|
+
// Reachable only when git itself fails between the two commits (a hook, a full disk): every
|
|
1184
|
+
// refusal was checked above. The approve stays — rewriting a commit behind the user's back is
|
|
1185
|
+
// worse (dl-035) — so say exactly what is left to do.
|
|
1186
|
+
return (0, types_1.coreErr)({
|
|
1187
|
+
...finalized.error,
|
|
1188
|
+
message: `${id} was approved in ${committed.value}, but the commit moving ${superseded.id} to ${superseded.to} failed: ` +
|
|
1189
|
+
`${finalized.error.message.trim()}. Its status is written in the working tree (${superseded.path}). ` +
|
|
1190
|
+
`Complete the pair by committing that file alone, from the repository root:\n\n` +
|
|
1191
|
+
// `--cleanup=whitespace`, as `commitPaths` passes it (bug-051, task-192 review): under the
|
|
1192
|
+
// operator's `commit.cleanup=strip` a reason line opening with `#` would otherwise be lost.
|
|
1193
|
+
`git commit --only --cleanup=whitespace -F - -- ${superseded.path} <<'EOF'\n${finalizeMessage}\nEOF`,
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
return (0, types_1.coreOk)({
|
|
1197
|
+
id,
|
|
1198
|
+
path,
|
|
1199
|
+
from,
|
|
1200
|
+
to,
|
|
1201
|
+
superseded: { id: superseded.id, path: superseded.path, from: superseded.from, to: superseded.to },
|
|
1202
|
+
}, { sha: committed.value, message }, warnings);
|
|
802
1203
|
};
|
|
803
1204
|
/**
|
|
804
1205
|
* `memory reject` `CoreOperation.fn` (P1.8, `mutates: true`; spec-006-core-domain-api §3) — the FIRST
|
|
805
1206
|
* approver-gated verb, so the first operation that enforces REQ-SEC-03 (role authority) and REQ-SEC-04
|
|
806
1207
|
* (mandatory reason) and the first to emit a commit with a `[from → to]` subject bracket and an
|
|
807
|
-
* `Approver:`/`Reason:` body (`dl-054-submit-commit-subject-bracket`,
|
|
1208
|
+
* `Approver:`/`Reason:` body (`dl-054-submit-commit-subject-bracket`, AGENTS.md §5.1). Order, every
|
|
808
1209
|
* refusal before the single write:
|
|
809
1210
|
*
|
|
810
1211
|
* 1. **`<id>`** absent or blank → `UsageError` (exit `2`), as `memory submit`.
|
|
811
1212
|
* 2. **`requireReason`** (REQ-SEC-04, task-041-mandatory-reason-on-verbs) → `UsageError`
|
|
812
1213
|
* `missing required argument: --reason` (exit `2`, P1.8 sc.3). Both usage checks precede the
|
|
813
1214
|
* identity check (task-125, `bug-172`).
|
|
814
|
-
* 3. **`requireGitIdentity`** (REQ-SEC-01) — exit `1`.
|
|
815
|
-
*
|
|
816
|
-
*
|
|
1215
|
+
* 3. **`requireGitIdentity`** (REQ-SEC-01) — exit `1`. Steps 3 and 4 are {@link beginMemoryTransition}
|
|
1216
|
+
* (task-132, `bug-142`; `spec-006` §7).
|
|
1217
|
+
* 4. **{@link prepareMemoryTransition}** with op `reject`, against the `memory.yaml` and the document
|
|
1218
|
+
* committed at `HEAD` (task-091, `dl-080` (B); task-247, `bug-187`) — no committed machine, an unreadable one, not found,
|
|
817
1219
|
* unknown type, invalid state, or an illegal transition (the document is in no `gates`
|
|
818
1220
|
* state), each a `CoreResult.error` at exit `1` carrying `dl-032`'s pinned contract message. The
|
|
819
1221
|
* target is the type's `gates.<from>.reject` value, taken verbatim (`spec-001`), so it need not be
|
|
@@ -831,27 +1233,24 @@ const memoryApproveFn = async (params) => {
|
|
|
831
1233
|
* document and refuses (`VALIDATION`, exit `1`, nothing written) unless `status` AND
|
|
832
1234
|
* `rejection_reason` hold their expected values and no other field moved.
|
|
833
1235
|
*
|
|
834
|
-
* The `Approver:` identity is the
|
|
835
|
-
*
|
|
836
|
-
* (adr-001/adr-006) — under the `APPROVER_ROLE` the authority was exercised as.
|
|
1236
|
+
* The `Approver:` identity is the one step 3 resolved — the same value step 5 checked and the commit
|
|
1237
|
+
* is authored as (`--author`, task-132, `bug-149`), so the body line can never disagree with the
|
|
1238
|
+
* attribution (adr-001/adr-006) — under the `APPROVER_ROLE` the authority was exercised as.
|
|
837
1239
|
*/
|
|
838
1240
|
const memoryRejectFn = async (params) => {
|
|
839
1241
|
const { root, positional: id, options } = params;
|
|
840
1242
|
if (id === undefined || id.trim().length === 0) {
|
|
841
|
-
throw new usage_error_1.UsageError('
|
|
1243
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
|
|
842
1244
|
}
|
|
843
1245
|
const reason = (0, require_reason_1.requireReason)(options);
|
|
844
|
-
const
|
|
845
|
-
if (!identity.ok)
|
|
846
|
-
return identity;
|
|
847
|
-
const prepared = (0, memory_transition_1.prepareMemoryTransition)(root, id, 'reject');
|
|
1246
|
+
const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'reject');
|
|
848
1247
|
if (!prepared.ok)
|
|
849
1248
|
return prepared;
|
|
850
1249
|
const { type, path, from, to, content } = prepared.value;
|
|
851
|
-
const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type);
|
|
1250
|
+
const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type, prepared.value.identity);
|
|
852
1251
|
if (!authorized.ok)
|
|
853
1252
|
return authorized;
|
|
854
|
-
const { name, email } =
|
|
1253
|
+
const { name, email } = prepared.value.identity;
|
|
855
1254
|
const message = (0, memory_1.formatMemoryCommitMessage)({
|
|
856
1255
|
type,
|
|
857
1256
|
op: 'reject',
|
|
@@ -864,7 +1263,7 @@ const memoryRejectFn = async (params) => {
|
|
|
864
1263
|
const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message, { [memory_1.REJECTION_REASON_FIELD]: reason });
|
|
865
1264
|
if (!committed.ok)
|
|
866
1265
|
return committed;
|
|
867
|
-
return (0, types_1.coreOk)({ id, path, from, to, reason }, { sha: committed.value, message });
|
|
1266
|
+
return (0, types_1.coreOk)({ id, path, from, to, reason }, { sha: committed.value, message }, prepared.value.warnings);
|
|
868
1267
|
};
|
|
869
1268
|
/**
|
|
870
1269
|
* `memory deprecate` `CoreFn` (P1.9, `mutates: true`; spec-006-core-domain-api §3) — the retire verb.
|
|
@@ -875,17 +1274,19 @@ const memoryRejectFn = async (params) => {
|
|
|
875
1274
|
* from a literal here — so this one function retires a `task` sitting in a `waiting` state, an
|
|
876
1275
|
* `accepted` `adr`, a terminal `decision-log`, and a type that falls back to `defaults.states`, with
|
|
877
1276
|
* no per-type branch. (`adr`/`tech-spec`'s `superseded` is NOT this verb's target: it is a `waiting`
|
|
878
|
-
* edge fired by a later element's `supersedes:` field — see
|
|
879
|
-
* `src/memory/state-machine.ts`
|
|
1277
|
+
* edge fired by a later element's `supersedes:` field when that element is approved — see
|
|
1278
|
+
* `SUPERSEDED_STATE` in `src/memory/state-machine.ts`, `./memory-supersede.ts` (task-162) and
|
|
1279
|
+
* task-048's Execution Notes, decision D1. A `superseded` element can still be deprecated.)
|
|
880
1280
|
*
|
|
881
1281
|
* Order, every refusal before the single write:
|
|
882
1282
|
*
|
|
883
1283
|
* 1. **`<id>`** absent or blank → `UsageError` (exit `2`), as `memory submit`/`memory reject`; so is a
|
|
884
|
-
* `--reason` that is given but blank
|
|
885
|
-
* identity check (task-125, `bug-172`).
|
|
886
|
-
* 2. **`requireGitIdentity`** (REQ-SEC-01) — exit `1`.
|
|
887
|
-
*
|
|
888
|
-
*
|
|
1284
|
+
* `--reason` that is given but blank, carrying a control character other than tab or newline, or
|
|
1285
|
+
* trailer-shaped (`optionalReason`). Both precede the identity check (task-125, `bug-172`).
|
|
1286
|
+
* 2. **`requireGitIdentity`** (REQ-SEC-01) — exit `1`. Steps 2 and 3 are {@link beginMemoryTransition}
|
|
1287
|
+
* (task-132, `bug-142`; `spec-006` §7).
|
|
1288
|
+
* 3. **{@link prepareMemoryTransition}** with op `deprecate`, against the `memory.yaml` and the
|
|
1289
|
+
* document committed at `HEAD` (task-091, `dl-080` (B); task-247, `bug-187`) — no committed machine, an unreadable one, not found,
|
|
889
1290
|
* unknown type, or a `status` that is not a state of the type, each a
|
|
890
1291
|
* `CoreResult.error` (exit `1`). There is no illegal-transition branch: the wildcard edge is legal
|
|
891
1292
|
* from every state.
|
|
@@ -910,16 +1311,14 @@ const memoryRejectFn = async (params) => {
|
|
|
910
1311
|
const memoryDeprecateFn = async (params) => {
|
|
911
1312
|
const { root, positional: id, options } = params;
|
|
912
1313
|
if (id === undefined || id.trim().length === 0) {
|
|
913
|
-
throw new usage_error_1.UsageError('
|
|
1314
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
|
|
914
1315
|
}
|
|
915
1316
|
// `--reason` is optional here (`dl-027`), but a reason that IS given must be recordable in the
|
|
916
|
-
// trailer — blank
|
|
917
|
-
// (`dl-067` clauses 1 and 4
|
|
1317
|
+
// trailer — blank, carrying a control character other than tab or newline, or trailer-shaped is a
|
|
1318
|
+
// usage error at exit 2, before anything is read or written (`dl-067` clauses 1 and 4 as amended;
|
|
1319
|
+
// `bug-042` F2/F3, whose amendment makes THIS verb the exploitable one).
|
|
918
1320
|
const reason = (0, require_reason_1.optionalReason)(options);
|
|
919
|
-
const
|
|
920
|
-
if (!identity.ok)
|
|
921
|
-
return identity;
|
|
922
|
-
const prepared = (0, memory_transition_1.prepareMemoryTransition)(root, id, 'deprecate');
|
|
1321
|
+
const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'deprecate');
|
|
923
1322
|
if (!prepared.ok)
|
|
924
1323
|
return prepared;
|
|
925
1324
|
const { type, path, from, to, content } = prepared.value;
|
|
@@ -931,7 +1330,132 @@ const memoryDeprecateFn = async (params) => {
|
|
|
931
1330
|
const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message);
|
|
932
1331
|
if (!committed.ok)
|
|
933
1332
|
return committed;
|
|
934
|
-
return (0, types_1.coreOk)({ id, path, from, to, reason }, { sha: committed.value, message });
|
|
1333
|
+
return (0, types_1.coreOk)({ id, path, from, to, reason }, { sha: committed.value, message }, prepared.value.warnings);
|
|
1334
|
+
};
|
|
1335
|
+
/**
|
|
1336
|
+
* `memory park` `CoreOperation.fn` (task-180, `mutates: true`; `dl-110` P1 (a)) — return a started
|
|
1337
|
+
* element to an earlier state along its type's declared `returns` edge (`spec-001`), e.g. a `task` from
|
|
1338
|
+
* `in-progress` back to `backlog`. Returning is not rejecting (`dl-110` Rationale): the commit says the
|
|
1339
|
+
* work is not being done now, not that it was wrong, so `memory history` keeps the two apart. Order,
|
|
1340
|
+
* every refusal before the single write (`spec-006` §7):
|
|
1341
|
+
*
|
|
1342
|
+
* 1. **`<id>`** absent or blank, then **`requireReason`** (`dl-067`) → `UsageError` (exit `2`), before
|
|
1343
|
+
* the identity is read (task-125, `bug-172`). The reason is mandatory: a park records why.
|
|
1344
|
+
* 2. **{@link beginMemoryTransition}** with op `park` — identity (REQ-SEC-01), then the document and
|
|
1345
|
+
* its edge at `HEAD` (`dl-080`): a state with no `returns` edge is `INVALID_TRANSITION` with the
|
|
1346
|
+
* `dl-032` contract message (exit `1`). A WIP limit declared on the target state does not refuse a
|
|
1347
|
+
* park: a return along a `returns` edge takes no slot check (`dl-156` Q2 (i), task-274).
|
|
1348
|
+
* 3. **Edit + commit** — `status` set to the target and nothing else (`spec-010` field-write ownership,
|
|
1349
|
+
* enforced by `commitMemoryTransition`'s post-condition), one commit scoped to that file:
|
|
1350
|
+
* `wf(<type>): park <id> [<from> → <to>]` with a `Reason:` block (`spec-008` §2).
|
|
1351
|
+
*
|
|
1352
|
+
* Deliberately absent: no `Approver:` line and no authority check. A park is a scheduling decision, not
|
|
1353
|
+
* an approval gate; `dl-110` names no approver for it. What parking does to the work around the element
|
|
1354
|
+
* under `dev-loop` (the task's worktree, its bugs' `sync_state`) is `dl-110` P2, owned by the workflow,
|
|
1355
|
+
* not by this verb.
|
|
1356
|
+
*/
|
|
1357
|
+
const memoryParkFn = async (params) => {
|
|
1358
|
+
const { root, positional: id, options } = params;
|
|
1359
|
+
if (id === undefined || id.trim().length === 0) {
|
|
1360
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
|
|
1361
|
+
}
|
|
1362
|
+
const reason = (0, require_reason_1.requireReason)(options);
|
|
1363
|
+
const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'park');
|
|
1364
|
+
if (!prepared.ok)
|
|
1365
|
+
return prepared;
|
|
1366
|
+
const { type, path, from, to, content } = prepared.value;
|
|
1367
|
+
const message = (0, memory_1.formatMemoryCommitMessage)({ type, op: 'park', ids: [id], transition: { from, to }, reason });
|
|
1368
|
+
const rendered = (0, memory_1.setFrontmatterField)(content, 'status', to);
|
|
1369
|
+
const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message);
|
|
1370
|
+
if (!committed.ok)
|
|
1371
|
+
return committed;
|
|
1372
|
+
return (0, types_1.coreOk)({ id, path, from, to, reason }, { sha: committed.value, message }, prepared.value.warnings);
|
|
1373
|
+
};
|
|
1374
|
+
/**
|
|
1375
|
+
* `memory amend` `CoreOperation.fn` (task-127, `mutates: true`; `dl-108` A1 (a), A2 (i), A3). It
|
|
1376
|
+
* records the author's uncommitted edit of one document as an amendment: one commit, that file only,
|
|
1377
|
+
* `status` untouched, under an approver's name. It is the verb for a correction to an element no
|
|
1378
|
+
* transition verb can move (an `approved` tech-spec, a `ready` decision-log). Order, every refusal
|
|
1379
|
+
* before the single write:
|
|
1380
|
+
*
|
|
1381
|
+
* 1. **`<id>`** absent or blank, then **`requireReason`** (`dl-067`) → `UsageError` (exit `2`), before
|
|
1382
|
+
* anything is read. Both precede the identity check (task-125, `bug-172`), as on `approve`.
|
|
1383
|
+
* 2. **`requireGitIdentity`** (REQ-SEC-01) — exit `1`. Step 2 and the lookup in step 3 are
|
|
1384
|
+
* {@link beginMemoryTransition} (task-132, `bug-142`; `spec-006` §7); the identity it returns is
|
|
1385
|
+
* the one step 4 authorizes, the `Approver:` line names and the commit is authored as.
|
|
1386
|
+
* 3. **{@link prepareMemoryTransition}** with op `amend` — the document located at `HEAD`, its type and
|
|
1387
|
+
* state resolved against the `memory.yaml` committed there (`dl-080` (B); task-247, `bug-187`), its
|
|
1388
|
+
* content taken from the working tree; its target is its own
|
|
1389
|
+
* state, so no illegal-transition refusal exists for this verb. Then
|
|
1390
|
+
* {@link requireConfinedWriteTarget} (REQ-SEC-06): a document outside the project, or one that is
|
|
1391
|
+
* itself a symbolic link, is refused before any question about its content.
|
|
1392
|
+
* 4. **{@link requireApprovalAuthority}** (REQ-SEC-03, `dl-108` A2 (i)) — the same check and message
|
|
1393
|
+
* as `approve`, from the committed `dna.yaml`; exit `1`. It runs as soon as the type is known
|
|
1394
|
+
* (step 3), the same place `approve` runs it, so a caller without authority learns nothing about
|
|
1395
|
+
* the edit (task-127 review F6).
|
|
1396
|
+
* 5. **{@link requireAmendableType}** — the committed entry must declare `amendable: true`
|
|
1397
|
+
* (`dl-108` A3, `spec-001`); exit `1`.
|
|
1398
|
+
* Then **{@link requireReadableScaffold}**: the type's scaffold committed at `HEAD`, which step 6
|
|
1399
|
+
* reads, must be one `memory add` would read — a newer or malformed `format` is refused with
|
|
1400
|
+
* `memory add`'s message (`dl-149`; task-257, `bug-243`); exit `1`.
|
|
1401
|
+
* 6. **{@link requireAmendableEdit}** — the document must be committed at `HEAD`, carry a change, and
|
|
1402
|
+
* leave every field the type's {@link amendReservedFields} lists as committed (`spec-010` §
|
|
1403
|
+
* Field-write ownership: `release` only where the committed scaffold declares it, task-170);
|
|
1404
|
+
* exit `1`, naming the field. Then {@link requireRequiredFieldsKept}: past the initial
|
|
1405
|
+
* state, `title` and the type's required fields must stay non-empty (`spec-010` § Validation
|
|
1406
|
+
* rules); exit `1`, naming the field.
|
|
1407
|
+
* 7. **Commit** — {@link commitMemoryTransition} under `carries-content`, the scope `memory submit`
|
|
1408
|
+
* uses: the working-tree bytes are the content of record, so they are written back unchanged and
|
|
1409
|
+
* committed as that one path. `commitPaths` stages only that path, so other modified or staged
|
|
1410
|
+
* files stay where they are (`bug-027`); the post-condition checks that the commit holds only that
|
|
1411
|
+
* path and that `status` is the unchanged state (`bug-076`). Subject
|
|
1412
|
+
* `wf(<type>): amend <id> [<s> → <s>]` with the `Approver:` / `Reason:` body (`spec-008` §2).
|
|
1413
|
+
*/
|
|
1414
|
+
const memoryAmendFn = async (params) => {
|
|
1415
|
+
const { root, positional: id, options } = params;
|
|
1416
|
+
if (id === undefined || id.trim().length === 0) {
|
|
1417
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
|
|
1418
|
+
}
|
|
1419
|
+
const reason = (0, require_reason_1.requireReason)(options);
|
|
1420
|
+
const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'amend');
|
|
1421
|
+
if (!prepared.ok)
|
|
1422
|
+
return prepared;
|
|
1423
|
+
const { memoryYaml, type, path, content, frontmatter, from, to } = prepared.value;
|
|
1424
|
+
// Before any question about the edit: is this file the project's to write at all (REQ-SEC-06,
|
|
1425
|
+
// `bug-117`/`bug-120`)? The other verbs ask it first inside `commitMemoryTransition`; `amend` asks
|
|
1426
|
+
// it here too, because its own checks read the document against `HEAD`, and a symlinked document
|
|
1427
|
+
// would otherwise be refused for a field change it never made.
|
|
1428
|
+
const confined = (0, confinement_1.requireConfinedWriteTarget)(root, path, 'write');
|
|
1429
|
+
if (!confined.ok)
|
|
1430
|
+
return confined;
|
|
1431
|
+
const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type, prepared.value.identity);
|
|
1432
|
+
if (!authorized.ok)
|
|
1433
|
+
return authorized;
|
|
1434
|
+
const amendable = (0, memory_amend_1.requireAmendableType)(memoryYaml, type);
|
|
1435
|
+
if (!amendable.ok)
|
|
1436
|
+
return amendable;
|
|
1437
|
+
const scaffold = (0, memory_amend_1.requireReadableScaffold)(root, memoryYaml, type);
|
|
1438
|
+
if (!scaffold.ok)
|
|
1439
|
+
return scaffold;
|
|
1440
|
+
const edit = (0, memory_amend_1.requireAmendableEdit)(root, id, path, content, (0, memory_amend_1.amendReservedFields)(root, memoryYaml, type));
|
|
1441
|
+
if (!edit.ok)
|
|
1442
|
+
return edit;
|
|
1443
|
+
const filled = (0, memory_amend_1.requireRequiredFieldsKept)(memoryYaml, type, from, frontmatter);
|
|
1444
|
+
if (!filled.ok)
|
|
1445
|
+
return filled;
|
|
1446
|
+
const { name, email } = prepared.value.identity;
|
|
1447
|
+
const message = (0, memory_1.formatMemoryCommitMessage)({
|
|
1448
|
+
type,
|
|
1449
|
+
op: 'amend',
|
|
1450
|
+
ids: [id],
|
|
1451
|
+
transition: { from, to },
|
|
1452
|
+
approver: { name, email, role: approval_authority_1.APPROVER_ROLE },
|
|
1453
|
+
reason,
|
|
1454
|
+
});
|
|
1455
|
+
const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, content, message, {}, 'carries-content');
|
|
1456
|
+
if (!committed.ok)
|
|
1457
|
+
return committed;
|
|
1458
|
+
return (0, types_1.coreOk)({ id, path, from, to }, { sha: committed.value, message }, prepared.value.warnings);
|
|
935
1459
|
};
|
|
936
1460
|
/**
|
|
937
1461
|
* `directive create` `CoreOperation.fn` (P3.1, `mutates: true` — the FIRST Directives-pillar mutation
|
|
@@ -976,6 +1500,12 @@ const directiveCreateFn = async (params) => {
|
|
|
976
1500
|
// correct absolute path on Windows; the root-relative form is what `commitPaths` stages.
|
|
977
1501
|
const relativePath = `.wingfoil/directives/custom/${name}.md`;
|
|
978
1502
|
const absolutePath = (0, path_1.join)(root, relativePath);
|
|
1503
|
+
// REQ-SEC-06 / bug-121 (task-172): a DANGLING link at the path reads as free to `documentExists`
|
|
1504
|
+
// (`existsSync` follows it), and `writeDocument` would create the file wherever it points. Refused
|
|
1505
|
+
// before the existence check, so a path that leaves the project is reported as leaving it.
|
|
1506
|
+
const confined = (0, confinement_1.requireConfinedWriteTarget)(root, relativePath, 'write');
|
|
1507
|
+
if (!confined.ok)
|
|
1508
|
+
return confined;
|
|
979
1509
|
if ((0, storage_1.documentExists)(absolutePath)) {
|
|
980
1510
|
return (0, types_1.coreErr)({ code: 'CONFLICT', message: `directive already exists: ${name}` });
|
|
981
1511
|
}
|
|
@@ -986,9 +1516,8 @@ const directiveCreateFn = async (params) => {
|
|
|
986
1516
|
if (!unmodified.ok)
|
|
987
1517
|
return unmodified;
|
|
988
1518
|
const content = (0, create_1.renderCustomDirective)(name);
|
|
989
|
-
(0, storage_1.writeDocument)(absolutePath, content);
|
|
990
1519
|
const message = `wf(directive): create ${name}`;
|
|
991
|
-
const sha = (0, storage_1.
|
|
1520
|
+
const sha = (0, storage_1.writeAndCommit)(root, [{ path: relativePath, content }], message);
|
|
992
1521
|
const leaked = (0, write_guard_1.committedScopeError)(root, sha, relativePath, content);
|
|
993
1522
|
if (leaked)
|
|
994
1523
|
return leaked;
|
|
@@ -1019,10 +1548,12 @@ const directiveCreateFn = async (params) => {
|
|
|
1019
1548
|
* never sorting) through the comment-preserving `roles.yaml` writer and makes ONE commit,
|
|
1020
1549
|
* `wf(directive): assign <id1>, <id2> to <role>`, staging only `.wingfoil/roles.yaml`. A request
|
|
1021
1550
|
* whose ids are all already bound is a success with no write and no commit (P3.7 Sc.2 "Binding is
|
|
1022
|
-
* idempotent"); a partially overlapping one appends only what is missing.
|
|
1551
|
+
* idempotent"); a partially overlapping one appends only what is missing. A file the in-place
|
|
1552
|
+
* editor cannot edit is refused (`CONFLICT`, exit 1) unless `--force` authorizes the whole-file
|
|
1553
|
+
* rewrite, whose success then carries a warning the CLI prints on stderr (task-169, `dl-062`).
|
|
1023
1554
|
*/
|
|
1024
1555
|
const directiveAssignFn = async (params) => {
|
|
1025
|
-
const { root, options } = params;
|
|
1556
|
+
const { root, options, force } = params;
|
|
1026
1557
|
const directive = options?.directive;
|
|
1027
1558
|
if (directive === undefined)
|
|
1028
1559
|
throw new usage_error_1.UsageError('missing required argument: --directive');
|
|
@@ -1039,10 +1570,12 @@ const directiveAssignFn = async (params) => {
|
|
|
1039
1570
|
if (invalid)
|
|
1040
1571
|
return (0, types_1.coreErr)(invalid);
|
|
1041
1572
|
const message = `wf(directive): assign ${directives.join(', ')} to ${role}`;
|
|
1042
|
-
const updated = (0, directive_assign_1.updateRoleAssignments)(root, role, (current) => (0, roles_edit_1.withAssignedDirectives)(current, directives), message
|
|
1573
|
+
const updated = (0, directive_assign_1.updateRoleAssignments)(root, role, (current) => (0, roles_edit_1.withAssignedDirectives)(current, directives), message, {
|
|
1574
|
+
force: force === true,
|
|
1575
|
+
});
|
|
1043
1576
|
if (!updated.ok)
|
|
1044
1577
|
return updated;
|
|
1045
|
-
return (0, types_1.coreOk)({ directives, role, assignments: updated.value.assignments }, updated.commit);
|
|
1578
|
+
return (0, types_1.coreOk)({ directives, role, assignments: updated.value.assignments }, updated.commit, updated.warnings);
|
|
1046
1579
|
};
|
|
1047
1580
|
/**
|
|
1048
1581
|
* `directive remove` `CoreOperation.fn` (P3.3, `mutates: true`; spec-006 §3 row `directiveRemove`,
|
|
@@ -1053,7 +1586,7 @@ const directiveAssignFn = async (params) => {
|
|
|
1053
1586
|
* strictly **before** anything on disk changes:
|
|
1054
1587
|
*
|
|
1055
1588
|
* 1. `<name>` presence — a missing or blank positional is a `UsageError` → exit **2**
|
|
1056
|
-
* (`missing required argument:
|
|
1589
|
+
* (`missing required argument: <name>`, the one missing-operand wording, task-179).
|
|
1057
1590
|
* 2. `requireGitIdentity` pre-flight (REQ-SEC-01), after the usage check (task-125, `bug-172`).
|
|
1058
1591
|
* 3. **Resolve the name to a real file**, never to a constructed path. `loadDirectives` reads every
|
|
1059
1592
|
* installed directive and {@link selectDirectivesById} picks the winner for that id under
|
|
@@ -1103,17 +1636,25 @@ const directiveAssignFn = async (params) => {
|
|
|
1103
1636
|
const directiveRemoveFn = async (params) => {
|
|
1104
1637
|
const { root, positional: name } = params;
|
|
1105
1638
|
if (name === undefined || name.trim().length === 0) {
|
|
1106
|
-
throw new usage_error_1.UsageError('
|
|
1639
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('name'));
|
|
1107
1640
|
}
|
|
1108
1641
|
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
1109
1642
|
if (!identity.ok)
|
|
1110
1643
|
return identity;
|
|
1111
|
-
const
|
|
1112
|
-
if (!
|
|
1113
|
-
return
|
|
1114
|
-
const target = (0, context_1.selectDirectivesById)(
|
|
1644
|
+
const inventory = loadOrError(() => (0, loaders_1.loadDirectiveInventory)(root));
|
|
1645
|
+
if (!inventory.ok)
|
|
1646
|
+
return inventory;
|
|
1647
|
+
const target = (0, context_1.selectDirectivesById)(inventory.value.files, new Set([name])).byId.get(name);
|
|
1115
1648
|
if (target === undefined) {
|
|
1116
|
-
|
|
1649
|
+
// task-143: the entry asked for may be one the loader skipped (a dangling link, an unreadable
|
|
1650
|
+
// file), so its warnings ride this refusal's `details` — the channel both surfaces render
|
|
1651
|
+
// (task-130) — rather than a stderr line from core, which `--format json` cannot carry.
|
|
1652
|
+
const { warnings } = inventory.value;
|
|
1653
|
+
return (0, types_1.coreErr)({
|
|
1654
|
+
code: 'NOT_FOUND',
|
|
1655
|
+
message: `unknown directive: ${name}`,
|
|
1656
|
+
...(warnings.length > 0 ? { details: { issues: warnings.map((detail) => ({ detail })) } } : {}),
|
|
1657
|
+
});
|
|
1117
1658
|
}
|
|
1118
1659
|
const custom = (0, builtin_asset_1.requireCustomAsset)('directive', target.path);
|
|
1119
1660
|
if (!custom.ok)
|
|
@@ -1140,9 +1681,8 @@ const directiveRemoveFn = async (params) => {
|
|
|
1140
1681
|
const unmodified = (0, write_guard_1.requireUnmodifiedTarget)(root, relativePath);
|
|
1141
1682
|
if (!unmodified.ok)
|
|
1142
1683
|
return unmodified;
|
|
1143
|
-
(0, storage_1.removeDocument)((0, path_1.join)(root, relativePath));
|
|
1144
1684
|
const message = `wf(directive): remove ${name}`;
|
|
1145
|
-
const sha = (0, storage_1.
|
|
1685
|
+
const sha = (0, storage_1.writeAndCommit)(root, [{ path: relativePath, content: null }], message);
|
|
1146
1686
|
const leaked = (0, write_guard_1.committedScopeError)(root, sha, relativePath, null);
|
|
1147
1687
|
if (leaked)
|
|
1148
1688
|
return leaked;
|
|
@@ -1161,8 +1701,86 @@ const directiveRemoveFn = async (params) => {
|
|
|
1161
1701
|
*/
|
|
1162
1702
|
const directivesListFn = async (params) => {
|
|
1163
1703
|
const { root, options } = params;
|
|
1704
|
+
// task-143 (bug-154): with no `.wingfoil/` the loaders read nothing and the listing came back empty,
|
|
1705
|
+
// exit 0 — under `--role`, with a "no directives assigned" warning nothing was read to establish.
|
|
1706
|
+
const initialized = (0, init_1.requireInitializedProject)(root);
|
|
1707
|
+
if (!initialized.ok)
|
|
1708
|
+
return initialized;
|
|
1164
1709
|
return loadOrError(() => (0, directives_list_1.loadDirectiveListing)(root, options?.role));
|
|
1165
1710
|
};
|
|
1711
|
+
/** `workflowList` `CoreOperation.fn` (P4.6, `spec-017` §7.5): {@link workflowListAtHead}, refusals mapped. */
|
|
1712
|
+
const workflowListFn = async (params) => {
|
|
1713
|
+
const { root, all } = params;
|
|
1714
|
+
return loadOrError(() => (0, workflow_list_show_1.workflowListAtHead)(root, all === true));
|
|
1715
|
+
};
|
|
1716
|
+
/**
|
|
1717
|
+
* `workflowShow` `CoreOperation.fn` (P4.7, `spec-017` §7.6): {@link workflowShowAtHead}, refusals mapped; a
|
|
1718
|
+
* missing `<ref>` is the one missing-operand usage error (exit `2`) for a caller that bypasses the registrar.
|
|
1719
|
+
*/
|
|
1720
|
+
const workflowShowFn = async (params) => {
|
|
1721
|
+
const { root, positional } = params;
|
|
1722
|
+
if (positional === undefined)
|
|
1723
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('ref'));
|
|
1724
|
+
const result = loadOrError(() => (0, workflow_list_show_1.workflowShowAtHead)(root, positional));
|
|
1725
|
+
return result.ok ? result.value : result;
|
|
1726
|
+
};
|
|
1727
|
+
/**
|
|
1728
|
+
* `workflowNext` `CoreOperation.fn` (P4.4, `spec-017` §7.3): {@link workflowNextAtHead}, refusals mapped — a
|
|
1729
|
+
* `spec-003` error at `HEAD` is `VALIDATION` (exit 1), a `<ref>` naming no open instance `NOT_FOUND` (exit 1);
|
|
1730
|
+
* every deduced outcome exits 0.
|
|
1731
|
+
*/
|
|
1732
|
+
const workflowNextFn = async (params) => {
|
|
1733
|
+
const { root, positional, options } = params;
|
|
1734
|
+
const result = loadOrError(() => (0, workflow_next_1.workflowNextAtHead)(root, positional, options?.['assigned-to']));
|
|
1735
|
+
return result.ok ? result.value : result;
|
|
1736
|
+
};
|
|
1737
|
+
/**
|
|
1738
|
+
* `workflowFinalize` `CoreOperation.fn` (`spec-017` §7.9, ruling R11): {@link workflowFinalizeAtHead}, its
|
|
1739
|
+
* refusals returned (`CONFLICT`, `NOT_FOUND`, `VALIDATION`: exit 1) or thrown as a `UsageError` (an
|
|
1740
|
+
* unrecordable or missing `--reason`: exit 2); a `spec-003` error at `HEAD` is `VALIDATION`, exit 1.
|
|
1741
|
+
*/
|
|
1742
|
+
const workflowFinalizeFn = async (params) => {
|
|
1743
|
+
const { root, positional, options } = params;
|
|
1744
|
+
const step = options?.['step'];
|
|
1745
|
+
const result = loadOrError(() => (0, workflow_finalize_1.workflowFinalizeAtHead)(root, { ...(positional === undefined ? {} : { ref: positional }), ...(step === undefined ? {} : { step }), ...(options === undefined ? {} : { options }) }));
|
|
1746
|
+
return result.ok ? result.value : result;
|
|
1747
|
+
};
|
|
1748
|
+
/**
|
|
1749
|
+
* `workflowStart` / `workflowEnd` `CoreOperation.fn`s (P4.2, P4.3; `spec-017` §7.1–§7.2; task-217): the two
|
|
1750
|
+
* operations of `./workflow-start-end`, with `start` writing through this registry's own `memoryAdd` and
|
|
1751
|
+
* `memorySubmit`. A `spec-003` error at `HEAD` (or a token value outside the id class) is `VALIDATION`,
|
|
1752
|
+
* exit 1, as every workflow operation maps it ({@link coreErrorOf}).
|
|
1753
|
+
*/
|
|
1754
|
+
async function refusalOf(run) {
|
|
1755
|
+
try {
|
|
1756
|
+
return await run();
|
|
1757
|
+
}
|
|
1758
|
+
catch (error) {
|
|
1759
|
+
const refusal = coreErrorOf(error);
|
|
1760
|
+
if (refusal === null)
|
|
1761
|
+
throw error;
|
|
1762
|
+
return (0, types_1.coreErr)(refusal);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
const workflowStartFn = (params) => refusalOf(() => (0, workflow_start_end_1.workflowStart)(params, { add: memoryAddFn, submit: memorySubmitFn }));
|
|
1766
|
+
const workflowEndFn = (params) => refusalOf(() => (0, workflow_start_end_1.workflowEnd)(params));
|
|
1767
|
+
/**
|
|
1768
|
+
* `workflowCreate` / `workflowRemove` `CoreOperation.fn`s (P4.8, P4.9; `spec-017` §7.7–§7.8; task-211): the two
|
|
1769
|
+
* operations of `./workflow-create-remove`. A `spec-003` error at `HEAD` is `VALIDATION`, exit 1
|
|
1770
|
+
* ({@link coreErrorOf}); the argument errors are thrown `UsageError`s (exit 2).
|
|
1771
|
+
*/
|
|
1772
|
+
const workflowCreateFn = (params) => refusalOf(() => (0, workflow_create_remove_1.workflowCreate)(params));
|
|
1773
|
+
const workflowRemoveFn = (params) => refusalOf(() => (0, workflow_create_remove_1.workflowRemove)(params));
|
|
1774
|
+
/**
|
|
1775
|
+
* `workflowStatus` `CoreOperation.fn` (P4.5, `spec-017` §7.4): {@link workflowStatusAtHead}, refusals mapped — a
|
|
1776
|
+
* `spec-003` error at `HEAD` is `VALIDATION` (exit 1), a `<ref>` naming no open instance `NOT_FOUND` (exit 1);
|
|
1777
|
+
* every deduced outcome exits 0, a routing error included (BDD P4.14 sc. 3).
|
|
1778
|
+
*/
|
|
1779
|
+
const workflowStatusFn = async (params) => {
|
|
1780
|
+
const { root, positional } = params;
|
|
1781
|
+
const result = loadOrError(() => (0, workflow_status_1.workflowStatusAtHead)(root, positional));
|
|
1782
|
+
return result.ok ? result.value : result;
|
|
1783
|
+
};
|
|
1166
1784
|
/**
|
|
1167
1785
|
* The production `CoreModule` registry (spec-006 §2, §4). `src/cli`'s command registrar and
|
|
1168
1786
|
* `src/mcp`'s Tool/Resource registrar both import this exact array — see spec-006 §4.1: "no
|
|
@@ -1191,6 +1809,72 @@ const directivesListFn = async (params) => {
|
|
|
1191
1809
|
* Tool, or the diff fails.
|
|
1192
1810
|
*/
|
|
1193
1811
|
exports.CORE_MODULES = [
|
|
1812
|
+
// task-177 (`spec-016` §1): the `agent` module, registered under the name its operations carry
|
|
1813
|
+
// (`agentExecute`, `agentList`, `agentShow`, spec-016 §8). `agentShow` is the first (task-220);
|
|
1814
|
+
// `agentExecute` follows (task-218 the pre-launch half, task-228 the launch), and `agentList`
|
|
1815
|
+
// (task-240) completes the v0.3 set. `src/agent` holds the adapter manifest, the run log and the pipeline.
|
|
1816
|
+
{
|
|
1817
|
+
name: 'agent',
|
|
1818
|
+
description: 'launch an agent on an element, and read recorded agent runs',
|
|
1819
|
+
operations: {
|
|
1820
|
+
// task-218 (`spec-016` §3): `mutates: true` — its one write is the run record's commit (§4.4,
|
|
1821
|
+
// task-228), so it takes `--dry-run`, which prints the launch plan. CLI only in v0.3: the
|
|
1822
|
+
// production server does not run `registerCoreModules`, and the `agent.execute` Tool is v0.4
|
|
1823
|
+
// (spec-016 §7, §8). Reads `HEAD` (spec-008 §11).
|
|
1824
|
+
agentExecute: {
|
|
1825
|
+
name: 'agentExecute',
|
|
1826
|
+
mutates: true,
|
|
1827
|
+
description: 'launch an agent CLI through its adapter on one Memory element, with its context assembled and checked first',
|
|
1828
|
+
// task-235 (spec-016 §3.1, ruling R16): the step forms. `--workflow` and `--step` imply `--next`.
|
|
1829
|
+
flags: [{ name: 'next', description: "take the workflow, phase, element and role from the active workflow's next step" }],
|
|
1830
|
+
options: [
|
|
1831
|
+
{ name: 'workflow', valueName: 'ref', description: 'take the step from this workflow (its most recent open instance) or open instance id; implies --next' },
|
|
1832
|
+
{ name: 'step', valueName: 'key', description: 'take this step of the frontier, e.g. dev-loop.red@task:task-042-login-form; implies --next' },
|
|
1833
|
+
{ name: 'element', valueName: 'type:id', description: "the element the agent works on, e.g. task:task-042-login-form; with a step, it overrides the step's element" },
|
|
1834
|
+
{ name: 'role', valueName: 'role', description: "the role the agent runs as, a dna.yaml team.roles name (default: the step's role; with no step, developer, with a warning)" },
|
|
1835
|
+
{ name: 'agent', valueName: 'name', description: 'the team.agents entry to launch (default: the first with an adapter that executes the role)' },
|
|
1836
|
+
],
|
|
1837
|
+
example: 'wingfoil agent execute --next',
|
|
1838
|
+
fn: agent_execute_1.agentExecuteFn,
|
|
1839
|
+
// spec-016 §3.4 (task-228): the agent owns stdout, so the recorded run is reported on stderr.
|
|
1840
|
+
renderToStderr: agent_execute_1.renderAgentExecuteStderr,
|
|
1841
|
+
},
|
|
1842
|
+
// task-240 (`spec-016` §5): read-only, and a declared `HEAD` read (§5.1, `spec-006` §6 item 6): the
|
|
1843
|
+
// run log and the workflow deduction at one commit. Its console rendering is §5's sections
|
|
1844
|
+
// (`renderConsole`); each diagnostic is also a success warning, the console's `warning:` line.
|
|
1845
|
+
// `--active` is v0.4 and not declared, so it is refused as an unknown option (exit 2). CLI only
|
|
1846
|
+
// in v0.3 (spec-016 §7, §8).
|
|
1847
|
+
agentList: {
|
|
1848
|
+
name: 'agentList',
|
|
1849
|
+
mutates: false,
|
|
1850
|
+
description: 'list the recorded agent runs and the workflow steps waiting for an agent, as committed at HEAD',
|
|
1851
|
+
flags: [
|
|
1852
|
+
{ name: 'past', description: 'list the recorded runs (the run log at HEAD); without --past or --waiting, both sections' },
|
|
1853
|
+
{ name: 'waiting', description: "list the open workflows' ready steps whose role an agent executes; without --past or --waiting, both sections" },
|
|
1854
|
+
],
|
|
1855
|
+
options: [
|
|
1856
|
+
{ name: 'element', valueName: 'type:id', description: 'keep the runs and steps of this element, e.g. task:task-042-login-form' },
|
|
1857
|
+
{ name: 'phase', valueName: 'name', description: 'keep the runs and steps of this workflow phase, e.g. red' },
|
|
1858
|
+
],
|
|
1859
|
+
example: 'wingfoil agent list --past --element task:task-042-login-form',
|
|
1860
|
+
fn: agent_list_1.agentListFn,
|
|
1861
|
+
renderConsole: agent_list_1.renderAgentListConsole,
|
|
1862
|
+
},
|
|
1863
|
+
// task-220 (`spec-016` §6): read-only, and a declared `HEAD` read (§5.1, `spec-006` §6 item 6).
|
|
1864
|
+
// Its console rendering is the `key: value` lines §6 defines, not the indented JSON every other
|
|
1865
|
+
// command prints until P5.1.4 (`renderConsole`, spec-008 §2). Not served over MCP before v0.4
|
|
1866
|
+
// (spec-016 §7): the production server does not run `registerCoreModules`.
|
|
1867
|
+
agentShow: {
|
|
1868
|
+
name: 'agentShow',
|
|
1869
|
+
mutates: false,
|
|
1870
|
+
description: 'print one recorded agent run and the commit that added it',
|
|
1871
|
+
positional: { name: 'run-id', required: true, description: 'the run id, <element-id>/<phase>/<n>' },
|
|
1872
|
+
example: 'wingfoil agent show task-042-login-form/red/1',
|
|
1873
|
+
fn: agent_show_1.agentShowFn,
|
|
1874
|
+
renderConsole: agent_show_1.renderAgentShowConsole,
|
|
1875
|
+
},
|
|
1876
|
+
},
|
|
1877
|
+
},
|
|
1194
1878
|
{
|
|
1195
1879
|
name: 'dna',
|
|
1196
1880
|
description: "read and change dna.yaml, the project's structural map",
|
|
@@ -1206,9 +1890,10 @@ exports.CORE_MODULES = [
|
|
|
1206
1890
|
name: 'dnaAdd',
|
|
1207
1891
|
mutates: true,
|
|
1208
1892
|
description: 'add an entry to a collection, or values to a list',
|
|
1209
|
-
positional: { name: 'path', required: true, description: 'the collection (e.g. team.members) or list (e.g. paths.sources) to add to' },
|
|
1893
|
+
positional: { name: 'path', required: true, description: 'the collection (e.g. team.members) or list (e.g. paths.sources) to add to', surplusHint: DNA_SURPLUS_HINT },
|
|
1210
1894
|
options: [DNA_VALUE_OPTION, ...DNA_ENTRY_OPTIONS],
|
|
1211
1895
|
example: 'wingfoil dna add team.members --value "Ada Lovelace" --entry-email ada@example.com --entry-roles approver',
|
|
1896
|
+
flags: [DNA_FORCE_FLAG],
|
|
1212
1897
|
fn: dnaAddFn,
|
|
1213
1898
|
},
|
|
1214
1899
|
// `remove` declares no entry-field options: it takes what to drop, never what to write.
|
|
@@ -1216,9 +1901,10 @@ exports.CORE_MODULES = [
|
|
|
1216
1901
|
name: 'dnaRemove',
|
|
1217
1902
|
mutates: true,
|
|
1218
1903
|
description: 'remove a collection entry, or values from a list',
|
|
1219
|
-
positional: { name: 'path', required: true, description: 'the entry to remove (<collection>.<name>), or the list to remove the --value values from' },
|
|
1904
|
+
positional: { name: 'path', required: true, description: 'the entry to remove (<collection>.<name>), or the list to remove the --value values from', surplusHint: DNA_SURPLUS_HINT },
|
|
1220
1905
|
options: [DNA_VALUE_OPTION],
|
|
1221
1906
|
example: 'wingfoil dna remove paths.docs --value README.md',
|
|
1907
|
+
flags: [DNA_FORCE_FLAG],
|
|
1222
1908
|
fn: dnaRemoveFn,
|
|
1223
1909
|
},
|
|
1224
1910
|
// The FIRST `mutates: true` operation in production (spec-006 §3 dna table) — by construction an
|
|
@@ -1229,9 +1915,10 @@ exports.CORE_MODULES = [
|
|
|
1229
1915
|
name: 'dnaSet',
|
|
1230
1916
|
mutates: true,
|
|
1231
1917
|
description: 'set one scalar field',
|
|
1232
|
-
positional: { name: 'path', required: true, description: 'the dotted path of the scalar field, e.g. project.name (double-quote a segment that contains a dot)' },
|
|
1918
|
+
positional: { name: 'path', required: true, description: 'the dotted path of the scalar field, e.g. project.name (double-quote a segment that contains a dot)', surplusHint: DNA_SURPLUS_HINT },
|
|
1233
1919
|
options: [DNA_SET_VALUE_OPTION],
|
|
1234
1920
|
example: 'wingfoil dna set project.name --value "My Project"',
|
|
1921
|
+
flags: [DNA_FORCE_FLAG],
|
|
1235
1922
|
fn: dnaSetFn,
|
|
1236
1923
|
},
|
|
1237
1924
|
dnaShow: {
|
|
@@ -1246,9 +1933,10 @@ exports.CORE_MODULES = [
|
|
|
1246
1933
|
name: 'dnaUpdate',
|
|
1247
1934
|
mutates: true,
|
|
1248
1935
|
description: 'change fields of an existing collection entry',
|
|
1249
|
-
positional: { name: 'path', required: true, description: 'the entry (<collection>.<name>), or one of its fields (<collection>.<name>.<field>) with --value' },
|
|
1936
|
+
positional: { name: 'path', required: true, description: 'the entry (<collection>.<name>), or one of its fields (<collection>.<name>.<field>) with --value', surplusHint: DNA_SURPLUS_HINT },
|
|
1250
1937
|
options: [DNA_VALUE_OPTION, ...DNA_ENTRY_OPTIONS],
|
|
1251
1938
|
example: 'wingfoil dna update modules.api --entry-description "Public HTTP API"',
|
|
1939
|
+
flags: [DNA_FORCE_FLAG],
|
|
1252
1940
|
fn: dnaUpdateFn,
|
|
1253
1941
|
},
|
|
1254
1942
|
},
|
|
@@ -1266,7 +1954,7 @@ exports.CORE_MODULES = [
|
|
|
1266
1954
|
memoryAdd: {
|
|
1267
1955
|
name: 'memoryAdd',
|
|
1268
1956
|
mutates: true,
|
|
1269
|
-
description: "create a document in its type's initial state (draft) from the type's template",
|
|
1957
|
+
description: "create a document in its type's initial state (the first state of its sequence; draft by default) from the type's template",
|
|
1270
1958
|
options: [
|
|
1271
1959
|
{ name: 'type', required: true, valueName: 'type', description: 'the Memory type, as the committed memory.yaml declares it' },
|
|
1272
1960
|
{ name: 'title', required: true, valueName: 'title', description: 'the document title; also the source of the {slug} token' },
|
|
@@ -1280,6 +1968,18 @@ exports.CORE_MODULES = [
|
|
|
1280
1968
|
valueName: 'name=value',
|
|
1281
1969
|
description: 'fill the id_pattern/path token {name} and write the frontmatter field of that name (repeatable)',
|
|
1282
1970
|
},
|
|
1971
|
+
// task-227 (`spec-017` §7.10, ruling R16; `spec-008` §12): link the added element to a workflow step,
|
|
1972
|
+
// through the `WingFoil-Instance` / `WingFoil-Step` trailers of the add commit (§4.8).
|
|
1973
|
+
{
|
|
1974
|
+
name: 'workflow',
|
|
1975
|
+
valueName: 'ref',
|
|
1976
|
+
description: 'link the document to a step of this open workflow instance (an instance id, or a workflow name)',
|
|
1977
|
+
},
|
|
1978
|
+
{
|
|
1979
|
+
name: 'step',
|
|
1980
|
+
valueName: 'key',
|
|
1981
|
+
description: 'the frontier step to link to, when several of the instance add this type (needs --workflow)',
|
|
1982
|
+
},
|
|
1283
1983
|
],
|
|
1284
1984
|
example: 'wingfoil memory add --type task --title "My first task" --tags demo,quickstart',
|
|
1285
1985
|
fn: memoryAddFn,
|
|
@@ -1366,6 +2066,39 @@ exports.CORE_MODULES = [
|
|
|
1366
2066
|
example: 'wingfoil memory deprecate dl-001-use-postgresql --reason "Superseded by the hosted-DB decision."',
|
|
1367
2067
|
fn: memoryDeprecateFn,
|
|
1368
2068
|
},
|
|
2069
|
+
// task-180 (`dl-110` P1 (a)) — `mutates: true`: CLI `wingfoil memory park <id> --reason <text>` + MCP
|
|
2070
|
+
// Tool `memory.park`.
|
|
2071
|
+
// `--reason` is `required` (metadata; `memoryParkFn` enforces it via `requireReason`). The id
|
|
2072
|
+
// rides the bare `positional` seam (spec-008 §7).
|
|
2073
|
+
memoryPark: {
|
|
2074
|
+
name: 'memoryPark',
|
|
2075
|
+
mutates: true,
|
|
2076
|
+
description: "return a started document to an earlier state along its type's declared returns edge (for a task, in-progress → backlog)",
|
|
2077
|
+
positional: { name: 'id', required: true, description: 'the document id, e.g. task-001-my-first-task' },
|
|
2078
|
+
options: [{ name: 'reason', required: true, valueName: 'text', description: "why the work stops for now, recorded as the commit's Reason:" }],
|
|
2079
|
+
example: 'wingfoil memory park task-001-my-first-task --reason "Blocked on the schema decision; back to the backlog."',
|
|
2080
|
+
fn: memoryParkFn,
|
|
2081
|
+
},
|
|
2082
|
+
// task-127 (`dl-108`) — `mutates: true`: CLI `wingfoil memory amend <id> --reason <text>` + MCP
|
|
2083
|
+
// Tool `memory.amend`. Approver-gated like `memoryApprove`, so `--reason` is `required`
|
|
2084
|
+
// (metadata; `memoryAmendFn` enforces it via `requireReason`). The id rides the bare
|
|
2085
|
+
// `positional` seam (spec-008 §7), which also gives it task-129's surplus-operand refusal.
|
|
2086
|
+
memoryAmend: {
|
|
2087
|
+
name: 'memoryAmend',
|
|
2088
|
+
mutates: true,
|
|
2089
|
+
description: 'record an uncommitted correction to a document as an amendment, leaving its state unchanged',
|
|
2090
|
+
positional: { name: 'id', required: true, description: 'the document id, e.g. spec-001-storage-layout' },
|
|
2091
|
+
options: [
|
|
2092
|
+
{
|
|
2093
|
+
name: 'reason',
|
|
2094
|
+
required: true,
|
|
2095
|
+
valueName: 'text',
|
|
2096
|
+
description: "why the document is corrected, recorded as the commit's Reason:; needs the approver role",
|
|
2097
|
+
},
|
|
2098
|
+
],
|
|
2099
|
+
example: 'wingfoil memory amend spec-001-storage-layout --reason "Later measurements corrected the §2 figures."',
|
|
2100
|
+
fn: memoryAmendFn,
|
|
2101
|
+
},
|
|
1369
2102
|
},
|
|
1370
2103
|
},
|
|
1371
2104
|
{
|
|
@@ -1392,7 +2125,7 @@ exports.CORE_MODULES = [
|
|
|
1392
2125
|
name: 'paths',
|
|
1393
2126
|
mutates: false,
|
|
1394
2127
|
description: 'print the resource paths declared in dna.yaml paths:',
|
|
1395
|
-
positional: { name: 'category', description: 'sources, tests, docs, config or
|
|
2128
|
+
positional: { name: 'category', description: 'sources, tests, docs, config, governance, runs or health (omit it for the whole map)' },
|
|
1396
2129
|
flags: [{ name: 'list', description: 'accepted for the planned drill-down view; it does not change the output yet' }],
|
|
1397
2130
|
example: 'wingfoil paths sources',
|
|
1398
2131
|
fn: pathsFn,
|
|
@@ -1401,14 +2134,138 @@ exports.CORE_MODULES = [
|
|
|
1401
2134
|
},
|
|
1402
2135
|
{
|
|
1403
2136
|
name: 'workflow',
|
|
1404
|
-
description: 'read the workflows the project declares
|
|
2137
|
+
description: 'start and end workflows, read, create and remove the workflows the project declares, and record the steps nothing else completes (no step is executed yet)',
|
|
1405
2138
|
operations: {
|
|
2139
|
+
// task-204 (`spec-017` §7.5–§7.6, §8): both read `HEAD`, a declared exception (§1.1, ruling R15;
|
|
2140
|
+
// `spec-006` §6 item 6), from the snapshot and deduction `workflow next` / `status` answer from. A
|
|
2141
|
+
// `spec-003` error at `HEAD` (loader or core check, task-194) is `VALIDATION`, exit 1, with every
|
|
2142
|
+
// diagnostic in `details`; warnings ride the payload's `diagnostics`. The MCP Resources
|
|
2143
|
+
// `wingfoil://workflows…` are not these operations and keep the working tree (spec-017 §9).
|
|
1406
2144
|
workflowList: {
|
|
1407
2145
|
name: 'workflowList',
|
|
1408
2146
|
mutates: false,
|
|
1409
|
-
description: '
|
|
1410
|
-
|
|
1411
|
-
|
|
2147
|
+
description: 'list the workflows you can run now, as committed at HEAD',
|
|
2148
|
+
flags: [{ name: 'all', description: 'list every workflow, the includable ones no open workflow has reached as well' }],
|
|
2149
|
+
example: 'wingfoil workflow list --all',
|
|
2150
|
+
fn: workflowListFn,
|
|
2151
|
+
},
|
|
2152
|
+
// task-216 (`spec-017` §7.3): reads `HEAD`, gating (§1.1 item 1): its first step is what `agent execute
|
|
2153
|
+
// --next` launches on. Exit 0 for every deduced outcome; 1 for a `spec-003` error or an unknown `<ref>`.
|
|
2154
|
+
// Console: the step's key, trail, role, scope, actions with bindings, directive ids and any "human
|
|
2155
|
+
// needed" line (§8). The MCP Resource `wingfoil://workflows/-/next` serves the same payload (task-239,
|
|
2156
|
+
// `src/mcp/workflow-resource.ts`).
|
|
2157
|
+
workflowNext: {
|
|
2158
|
+
name: 'workflowNext',
|
|
2159
|
+
mutates: false,
|
|
2160
|
+
description: 'print the next step of an open workflow: its verb, role, element, directives and bindings, as committed at HEAD',
|
|
2161
|
+
positional: { name: 'ref', description: 'a workflow name or an open workflow instance id (default: the active instance)' },
|
|
2162
|
+
options: [{ name: 'assigned-to', valueName: 'who', description: "keep the steps whose role 'me', a member name or email, or a role name holds" }],
|
|
2163
|
+
example: 'wingfoil workflow next --assigned-to me',
|
|
2164
|
+
fn: workflowNextFn,
|
|
2165
|
+
renderConsole: workflow_next_1.renderNextConsole,
|
|
2166
|
+
},
|
|
2167
|
+
// task-217 (`spec-017` §7.1–§7.2): both decide at `HEAD` (§1.1 item 1, gating). `start` records the
|
|
2168
|
+
// instance as a `plan` element in two commits (`wf(plan): add`, `wf(plan): submit`); `end` closes it
|
|
2169
|
+
// with `wf(plan): finalize <id> [active → done]`. The MCP Tools `workflow.start` / `workflow.end` are
|
|
2170
|
+
// v0.4 (P5.2.3); the production server serves no Tool.
|
|
2171
|
+
workflowStart: {
|
|
2172
|
+
name: 'workflowStart',
|
|
2173
|
+
mutates: true,
|
|
2174
|
+
description: 'start a workflow: record it as an active plan element and print its first step',
|
|
2175
|
+
positional: { name: 'name', required: true, description: 'the startable workflow to start' },
|
|
2176
|
+
options: [
|
|
2177
|
+
{ name: 'element', valueName: 'type:id', description: 'the element the workflow runs on, when it declares one (default: the active workflow\'s element of that type)' },
|
|
2178
|
+
{ name: 'title', valueName: 'title', description: "the plan's title (default: '<workflow> — <phase>')" },
|
|
2179
|
+
{
|
|
2180
|
+
name: 'set',
|
|
2181
|
+
repeatable: true,
|
|
2182
|
+
valueName: 'name=value',
|
|
2183
|
+
description: "override a derived plan token: phase=<v> (default: the element's id, else 1) or scope=<v> (default: the workflow name)",
|
|
2184
|
+
},
|
|
2185
|
+
],
|
|
2186
|
+
example: 'wingfoil workflow start release-cycle --element release:minor-v1',
|
|
2187
|
+
fn: workflowStartFn,
|
|
2188
|
+
renderConsole: workflow_start_end_1.renderStartConsole,
|
|
2189
|
+
},
|
|
2190
|
+
workflowEnd: {
|
|
2191
|
+
name: 'workflowEnd',
|
|
2192
|
+
mutates: true,
|
|
2193
|
+
description: 'end a complete workflow: move its plan element to done; the previous open workflow becomes active',
|
|
2194
|
+
positional: { name: 'ref', description: 'a workflow name or an open workflow instance id (default: the active instance)' },
|
|
2195
|
+
example: 'wingfoil workflow end',
|
|
2196
|
+
fn: workflowEndFn,
|
|
2197
|
+
renderConsole: workflow_start_end_1.renderEndConsole,
|
|
2198
|
+
},
|
|
2199
|
+
// task-226 (`spec-017` §7.9, ruling R11): records a checkpoint, an `awaits` or an approval no element
|
|
2200
|
+
// carries as one commit with no file change, `workflow: finalize <instance> <key>`, carrying the
|
|
2201
|
+
// phase-record trailers. Gates on the deduction and `dna.yaml` at `HEAD` (§11). The MCP Tool
|
|
2202
|
+
// `workflow.finalize` is v0.4 (P5.2.3): the production server serves no Tool (§9).
|
|
2203
|
+
workflowFinalize: {
|
|
2204
|
+
name: 'workflowFinalize',
|
|
2205
|
+
mutates: true,
|
|
2206
|
+
description: 'record that a step of an open workflow is complete, as an empty commit with the phase-record trailers',
|
|
2207
|
+
positional: { name: 'ref', description: 'a workflow name or an open workflow instance id (default: the active instance)' },
|
|
2208
|
+
options: [
|
|
2209
|
+
{ name: 'step', valueName: 'key', description: 'the frontier step to record, by its key (default: the next step)' },
|
|
2210
|
+
{ name: 'reason', valueName: 'text', description: 'why; required on a phase that declares approval:, recorded with an Approver: line' },
|
|
2211
|
+
],
|
|
2212
|
+
example: 'wingfoil workflow finalize --step dev-loop.red@task:task-130',
|
|
2213
|
+
fn: workflowFinalizeFn,
|
|
2214
|
+
renderConsole: workflow_finalize_1.renderFinalizeConsole,
|
|
2215
|
+
},
|
|
2216
|
+
// task-225 (`spec-017` §7.4, §5.1–§5.4): reads `HEAD`, declared (§1.1, ruling R15), from the deduction `next`
|
|
2217
|
+
// answers from. Every open instance with its phases, frontier and late elements, the pending approvals routed
|
|
2218
|
+
// to `dna.yaml` members, and the ungated elements; the console view's "human needed" lines are the v0.3
|
|
2219
|
+
// notification channel (§5.3). The MCP Resource `wingfoil://workflows/-/status` serves the same payload
|
|
2220
|
+
// (task-239, `src/mcp/workflow-resource.ts`).
|
|
2221
|
+
workflowStatus: {
|
|
2222
|
+
name: 'workflowStatus',
|
|
2223
|
+
mutates: false,
|
|
2224
|
+
description: 'print every open workflow, its pending approvals and who they are routed to, and the elements waiting at a gate, as committed at HEAD',
|
|
2225
|
+
positional: { name: 'ref', description: 'a workflow name or an open workflow instance id (default: every open instance)' },
|
|
2226
|
+
example: 'wingfoil workflow status',
|
|
2227
|
+
fn: workflowStatusFn,
|
|
2228
|
+
renderConsole: workflow_status_1.renderStatusConsole,
|
|
2229
|
+
},
|
|
2230
|
+
workflowShow: {
|
|
2231
|
+
name: 'workflowShow',
|
|
2232
|
+
mutates: false,
|
|
2233
|
+
description: 'print one workflow resolved, its included workflows nested under their phases, as committed at HEAD',
|
|
2234
|
+
positional: { name: 'ref', required: true, description: 'a workflow name, or the id of an open workflow instance' },
|
|
2235
|
+
example: 'wingfoil workflow show release-cycle',
|
|
2236
|
+
fn: workflowShowFn,
|
|
2237
|
+
},
|
|
2238
|
+
// task-211 (`spec-017` §7.7–§7.8, REQ-SEC-07): both decide at `HEAD` (§11) and write one commit with the
|
|
2239
|
+
// workflow file and `workflows.yaml` (`wf(workflow): create|remove <name>`), after loading the registry the
|
|
2240
|
+
// commit would leave. `create`'s one filesystem read is its target's existence (`dl-086`); `remove` refuses
|
|
2241
|
+
// a built-in, a workflow another one includes (`dl-030`; the referrer source is `include:` alone, `dl-066`
|
|
2242
|
+
// option 1) and one with an open instance. The MCP Tools `workflow.create` / `workflow.remove` are v0.4.
|
|
2243
|
+
workflowCreate: {
|
|
2244
|
+
name: 'workflowCreate',
|
|
2245
|
+
mutates: true,
|
|
2246
|
+
description: 'create a custom workflow file with one phase and include it in workflows.yaml',
|
|
2247
|
+
positional: { name: 'name', required: true, description: 'the new workflow, written to .wingfoil/workflows/custom/<name>.yaml' },
|
|
2248
|
+
options: [
|
|
2249
|
+
{ name: 'kind', valueName: 'main|sub', description: 'main: startable; sub: includable (or declare --startable / --includable instead)' },
|
|
2250
|
+
{ name: 'element', valueName: 'type', description: 'the Memory type the workflow runs on' },
|
|
2251
|
+
{ name: 'description', valueName: 'text', description: "the workflow's description" },
|
|
2252
|
+
],
|
|
2253
|
+
flags: [
|
|
2254
|
+
{ name: 'startable', description: 'the workflow can be started on its own (instead of --kind)' },
|
|
2255
|
+
{ name: 'includable', description: "a phase can include the workflow (instead of --kind)" },
|
|
2256
|
+
],
|
|
2257
|
+
example: 'wingfoil workflow create arch-review --kind main',
|
|
2258
|
+
fn: workflowCreateFn,
|
|
2259
|
+
renderConsole: workflow_create_remove_1.renderCreateConsole,
|
|
2260
|
+
},
|
|
2261
|
+
workflowRemove: {
|
|
2262
|
+
name: 'workflowRemove',
|
|
2263
|
+
mutates: true,
|
|
2264
|
+
description: 'delete a custom workflow and its workflows.yaml entry, unless another workflow includes it or an open workflow runs it',
|
|
2265
|
+
positional: { name: 'ref', required: true, description: 'a workflow name, or the id of an open workflow instance (built-in workflows cannot be removed)' },
|
|
2266
|
+
example: 'wingfoil workflow remove arch-review',
|
|
2267
|
+
fn: workflowRemoveFn,
|
|
2268
|
+
renderConsole: workflow_create_remove_1.renderRemoveConsole,
|
|
1412
2269
|
},
|
|
1413
2270
|
},
|
|
1414
2271
|
},
|
|
@@ -1452,6 +2309,9 @@ exports.CORE_MODULES = [
|
|
|
1452
2309
|
{ name: 'directive', required: true, valueName: 'name[,name...]', description: 'the directive(s) to assign, comma-separated' },
|
|
1453
2310
|
{ name: 'role', required: true, valueName: 'role', description: 'the role to assign them to, as the committed dna.yaml declares it' },
|
|
1454
2311
|
],
|
|
2312
|
+
// task-169 (`dl-062` Q1 option 3): the opt-in to the whole-file rewrite the in-place editor
|
|
2313
|
+
// would otherwise refuse. A per-command flag, documented in spec-008 §12, not §2.
|
|
2314
|
+
flags: [{ name: 'force', description: 'rewrite the whole roles.yaml when it cannot be edited in place (comments and formatting are not kept)' }],
|
|
1455
2315
|
example: 'wingfoil directive assign --directive api-style --role developer',
|
|
1456
2316
|
fn: directiveAssignFn,
|
|
1457
2317
|
},
|