wingfoil 0.2.1 → 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 +88 -42
- 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 +2 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +3 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init-command.d.ts.map +1 -1
- package/dist/cli/init-command.js +6 -3
- 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 +270 -60
- package/dist/cli/program.js.map +1 -1
- package/dist/cli/registrar.d.ts +16 -7
- package/dist/cli/registrar.d.ts.map +1 -1
- package/dist/cli/registrar.js +71 -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.js +6 -6
- 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 +189 -14
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1241 -238
- package/dist/core/index.js.map +1 -1
- package/dist/core/init.d.ts +50 -8
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +139 -27
- 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 +147 -23
- 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 +35 -30
- package/dist/directives/schema.d.ts.map +1 -1
- package/dist/directives/schema.js +36 -30
- 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 +9 -5
- 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 +160 -17
- package/dist/memory/add.d.ts.map +1 -1
- package/dist/memory/add.js +361 -51
- package/dist/memory/add.js.map +1 -1
- package/dist/memory/audit.d.ts +165 -32
- package/dist/memory/audit.d.ts.map +1 -1
- package/dist/memory/audit.js +267 -97
- 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 +74 -13
- package/dist/memory/history.d.ts.map +1 -1
- package/dist/memory/history.js +129 -45
- 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 +38 -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 +136 -48
- 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 +182 -17
- 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 +40 -0
- package/dist/validation/id.d.ts.map +1 -1
- package/dist/validation/id.js +99 -12
- 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 +33 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/secret-scan.d.ts +10 -7
- package/dist/validation/secret-scan.d.ts.map +1 -1
- package/dist/validation/secret-scan.js +39 -16
- 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 +190 -16
- package/dist/workflow/schema.js.map +1 -1
- package/package.json +37 -13
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,18 +360,39 @@ 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
|
|
212
385
|
* (P2.1, task-025), now with one structure-aware traversal underneath all four verbs:
|
|
213
386
|
*
|
|
214
|
-
* 1.
|
|
387
|
+
* 1. **Argument validation** — done by the callers before this body runs (`dnaPathPositional`,
|
|
388
|
+
* `dnaMutationRequest`, `dnaSetFn`'s `--value` check): a missing required argument, or a dotted
|
|
389
|
+
* path `splitDnaPath` cannot parse (an empty segment, the BDD's `..language`; an unterminated
|
|
390
|
+
* quote; a `"` no delimiter can account for), is a usage error: `throw new UsageError(...)` → exit
|
|
391
|
+
* **2** (mapped by `exitCodeForThrow`), per spec-005-cli-command-contract §1, whatever the git
|
|
392
|
+
* identity (task-125, `bug-172`). A path that is well-formed but names nothing the schema declares
|
|
393
|
+
* is a different failure and exits **1** — see step 5.
|
|
394
|
+
* 2. **`requireGitIdentity` pre-flight** (REQ-SEC-01, task-014) — refuse before any read/write when
|
|
215
395
|
* `user.name`/`user.email` are unset, returning its `CoreResult.error` unchanged (exit 1).
|
|
216
|
-
* 2. **Argument validation** — a missing required argument, or a dotted path `splitDnaPath` cannot
|
|
217
|
-
* parse (an empty segment, the BDD's `..language`; an unterminated quote; a `"` no delimiter can
|
|
218
|
-
* account for), is a usage error: `throw new UsageError(...)` → exit **2**
|
|
219
|
-
* (mapped by `exitCodeForThrow`), per spec-005-cli-command-contract §1. A path that is well-formed
|
|
220
|
-
* but names nothing the schema declares is a different failure and exits **1** — see step 4.
|
|
221
396
|
* 3. **`requireUnmodifiedTarget` pre-flight** (`dl-080`(B) / `bug-078`, task-092) — refuse, before
|
|
222
397
|
* anything is read or written, while `dna.yaml` carries modifications this operation does not own.
|
|
223
398
|
* The plain "refuse a dirty target" rule applies here unaltered: all four verbs **edit an existing
|
|
@@ -238,13 +413,20 @@ const dnaShowFn = async (params) => {
|
|
|
238
413
|
* in-place textual edit, so every comment survives — including the inline `[SPEC]`/`[AUTHORING]`
|
|
239
414
|
* field-provenance annotations a whole-file re-serialization deletes
|
|
240
415
|
* (bug-004-dna-set-strips-yaml-comments, task-063). When no provably-minimal edit exists it returns
|
|
241
|
-
* `undefined
|
|
242
|
-
* 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.
|
|
243
423
|
* 7. **Re-validate the written bytes** against `DnaYaml` (spec-002) BEFORE persisting — re-parsing the
|
|
244
424
|
* serialized form, not the in-memory object, so the check honours YAML's own scalar coercion and
|
|
245
|
-
* validates the exact bytes about to be written
|
|
246
|
-
*
|
|
247
|
-
*
|
|
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,
|
|
248
430
|
* `Team.superRefine`), which is why `dl-081` records that referential integrity needs no new check.
|
|
249
431
|
* 8. **Persist + commit** through the single storage primitives (task-018) — `writeDocument` then
|
|
250
432
|
* `commitPaths` on the ONE scoped path `.wingfoil/dna.yaml`; the returned sha rides
|
|
@@ -255,14 +437,20 @@ const dnaShowFn = async (params) => {
|
|
|
255
437
|
* with the step-3 guard in place no argument can reach it, but a repository-local `pre-commit`
|
|
256
438
|
* hook that rewrites and re-stages the file still can.
|
|
257
439
|
*/
|
|
258
|
-
async function runDnaMutation(root, request, subject
|
|
440
|
+
async function runDnaMutation(root, request, subject, scalarOnly, force) {
|
|
259
441
|
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
260
442
|
if (!identity.ok)
|
|
261
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;
|
|
262
450
|
const unmodified = (0, write_guard_1.requireUnmodifiedTarget)(root, loaders_1.DNA_YAML_PATH);
|
|
263
451
|
if (!unmodified.ok)
|
|
264
452
|
return unmodified;
|
|
265
|
-
const loaded =
|
|
453
|
+
const loaded = loadConfigOrError(root, () => (0, loaders_1.loadDnaYaml)(root));
|
|
266
454
|
if (!loaded.ok)
|
|
267
455
|
return loaded;
|
|
268
456
|
if (scalarOnly) {
|
|
@@ -281,21 +469,33 @@ async function runDnaMutation(root, request, subject = dnaCommitSubject(request)
|
|
|
281
469
|
return (0, types_1.coreErr)({ code: 'VALIDATION', message: applied.message });
|
|
282
470
|
const dnaPath = (0, path_1.join)(root, loaders_1.DNA_YAML_PATH);
|
|
283
471
|
const current = (0, storage_1.readDocument)(dnaPath);
|
|
284
|
-
const
|
|
285
|
-
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));
|
|
286
476
|
if (!parsed.success) {
|
|
287
|
-
const validationError = (0, validation_1.toValidationError)(parsed.error,
|
|
477
|
+
const validationError = (0, validation_1.toValidationError)(parsed.error, loaders_1.DNA_YAML_PATH);
|
|
288
478
|
return (0, types_1.coreErr)({ code: 'VALIDATION', message: validationError.message, details: { issues: validationError.issues } });
|
|
289
479
|
}
|
|
290
480
|
const outcome = { key: request.field, value: request.value };
|
|
291
481
|
if (current === serialized)
|
|
292
482
|
return (0, types_1.coreOk)(outcome);
|
|
293
|
-
|
|
294
|
-
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
|
+
});
|
|
295
495
|
const leaked = (0, write_guard_1.committedScopeError)(root, sha, loaders_1.DNA_YAML_PATH, serialized);
|
|
296
496
|
if (leaked)
|
|
297
497
|
return leaked;
|
|
298
|
-
return (0, types_1.coreOk)(outcome, { sha, message: subject });
|
|
498
|
+
return (0, types_1.coreOk)(outcome, { sha, message: subject }, warnings);
|
|
299
499
|
}
|
|
300
500
|
/**
|
|
301
501
|
* The one-line commit subject a DNA mutation writes: `wf(dna): {verb} {field} [{value}]`, extending
|
|
@@ -311,39 +511,40 @@ function dnaCommitSubject(request) {
|
|
|
311
511
|
* agree on where their target comes from (`dl-082-cli-parameter-shape`: a positional carries the
|
|
312
512
|
* identity of the target).
|
|
313
513
|
*
|
|
314
|
-
*
|
|
514
|
+
* Two usage errors, in the order they are checked:
|
|
315
515
|
*
|
|
316
|
-
* 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.
|
|
317
519
|
* 2. **Unparseable** — whatever `splitDnaPath` (`src/dna/set.ts`) refuses, reported with **its own**
|
|
318
520
|
* message rather than a flat one, because the three cases are not the same complaint: an empty
|
|
319
521
|
* segment (the BDD's `..language`) is malformed, an unterminated quote is a typo in the
|
|
320
522
|
* delimiters, and a `"` no delimiter can account for means the *name* has no spelling at all
|
|
321
|
-
* (`dl-083-dotted-entry-names-in-paths`, task-099).
|
|
322
|
-
* so that `dna set ..language python`, which is `P2.1-dna-set.feature`'s third scenario verbatim,
|
|
323
|
-
* keeps reporting `invalid key path: '..language'` rather than the migration hint. A stale
|
|
324
|
-
* invocation that is also malformed is malformed first.
|
|
325
|
-
* 3. **Extra positionals** — the old `dna set <key> <value>` spelling, and any slip of the same
|
|
326
|
-
* shape on the three new verbs. It gets a named message rather than being ignored, because
|
|
327
|
-
* `dl-082` is a breaking change to a shipped command and silently dropping the second word would
|
|
328
|
-
* make `dna set project.license MIT` look like it worked (`--value` absent, the write refused for
|
|
329
|
-
* a reason that names neither the second word nor the new grammar).
|
|
523
|
+
* (`dl-083-dotted-entry-names-in-paths`, task-099).
|
|
330
524
|
*
|
|
331
|
-
*
|
|
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).
|
|
332
530
|
*/
|
|
333
|
-
function dnaPathPositional(
|
|
531
|
+
function dnaPathPositional(positionals) {
|
|
334
532
|
const path = positionals?.[0];
|
|
335
533
|
if (path === undefined) {
|
|
336
|
-
throw new usage_error_1.UsageError(
|
|
534
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('path'));
|
|
337
535
|
}
|
|
338
536
|
const split = (0, set_1.splitDnaPath)(path);
|
|
339
537
|
if (!split.ok) {
|
|
340
538
|
throw new usage_error_1.UsageError(split.message);
|
|
341
539
|
}
|
|
342
|
-
if ((positionals?.length ?? 0) > 1) {
|
|
343
|
-
throw new usage_error_1.UsageError(`wingfoil dna ${verb} takes one positional <path>; the value travels in --value (got ${positionals.length} positionals)`);
|
|
344
|
-
}
|
|
345
540
|
return path;
|
|
346
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';
|
|
347
548
|
/**
|
|
348
549
|
* `dna set <path> --value <v>` `CoreOperation.fn` (P2.1) — kept, and still the scalar spelling
|
|
349
550
|
* (`spec-002`, `spec-005` §4). Since task-093 it is **`update` restricted to a single value**: the
|
|
@@ -362,8 +563,8 @@ function dnaPathPositional(verb, positionals) {
|
|
|
362
563
|
* write the field.
|
|
363
564
|
*/
|
|
364
565
|
const dnaSetFn = async (params) => {
|
|
365
|
-
const { root, positionals, options } = params;
|
|
366
|
-
const keyPath = dnaPathPositional(
|
|
566
|
+
const { root, positionals, options, force } = params;
|
|
567
|
+
const keyPath = dnaPathPositional(positionals);
|
|
367
568
|
const value = options?.value;
|
|
368
569
|
if (value === undefined) {
|
|
369
570
|
throw new usage_error_1.UsageError('missing required argument: --value');
|
|
@@ -372,11 +573,11 @@ const dnaSetFn = async (params) => {
|
|
|
372
573
|
// reader (and `git log --grep`) sees the command that was run, not the verb it delegates to. The
|
|
373
574
|
// scalar-only restriction, `dl-080`(B)'s dirty-target guard (task-092) and the committed-tree
|
|
374
575
|
// post-condition all live in the shared pipeline now, on the one document it loads.
|
|
375
|
-
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);
|
|
376
577
|
};
|
|
377
578
|
/** Split a verb's invocation into the positional `<path>`, `--value` and the per-entry `--entry-<field>` values. */
|
|
378
579
|
function dnaMutationRequest(verb, positionals, options) {
|
|
379
|
-
const field = dnaPathPositional(
|
|
580
|
+
const field = dnaPathPositional(positionals);
|
|
380
581
|
if (verb === 'add' && options?.value === undefined)
|
|
381
582
|
throw new usage_error_1.UsageError('missing required argument: --value');
|
|
382
583
|
// Every remaining option is an entry field, carried under its `--entry-<field>` name (see
|
|
@@ -402,18 +603,21 @@ function dnaMutationRequest(verb, positionals, options) {
|
|
|
402
603
|
}
|
|
403
604
|
/** `dna add` — create an entry, or append to a list (`dl-081` option (E); spec-006 §3, Tool `dna.add`). */
|
|
404
605
|
const dnaAddFn = async (params) => {
|
|
405
|
-
const { root, positionals, options } = params;
|
|
406
|
-
|
|
606
|
+
const { root, positionals, options, force } = params;
|
|
607
|
+
const request = dnaMutationRequest('add', positionals, options);
|
|
608
|
+
return runDnaMutation(root, request, dnaCommitSubject(request), false, force === true);
|
|
407
609
|
};
|
|
408
610
|
/** `dna remove` — drop an entry, a value from a list, or an optional field (Tool `dna.remove`). */
|
|
409
611
|
const dnaRemoveFn = async (params) => {
|
|
410
|
-
const { root, positionals, options } = params;
|
|
411
|
-
|
|
612
|
+
const { root, positionals, options, force } = params;
|
|
613
|
+
const request = dnaMutationRequest('remove', positionals, options);
|
|
614
|
+
return runDnaMutation(root, request, dnaCommitSubject(request), false, force === true);
|
|
412
615
|
};
|
|
413
616
|
/** `dna update` — change a value, a list, or the fields of one entry (Tool `dna.update`). */
|
|
414
617
|
const dnaUpdateFn = async (params) => {
|
|
415
|
-
const { root, positionals, options } = params;
|
|
416
|
-
|
|
618
|
+
const { root, positionals, options, force } = params;
|
|
619
|
+
const request = dnaMutationRequest('update', positionals, options);
|
|
620
|
+
return runDnaMutation(root, request, dnaCommitSubject(request), false, force === true);
|
|
417
621
|
};
|
|
418
622
|
/**
|
|
419
623
|
* The `--entry-<field>` namespace, re-exported from the DNA pillar (`src/dna/path.ts`), which owns it
|
|
@@ -445,23 +649,38 @@ const DNA_VALUE_OPTION = {
|
|
|
445
649
|
const DNA_SET_VALUE_OPTION = {
|
|
446
650
|
name: 'value',
|
|
447
651
|
required: true,
|
|
448
|
-
description: 'the new value for <path> (scalar fields only — use
|
|
652
|
+
description: 'the new value for <path> (scalar fields only — use dna update for collections and lists)',
|
|
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)',
|
|
449
661
|
};
|
|
450
662
|
const DNA_ENTRY_OPTIONS = (0, path_2.dnaEntryOptionNames)().map((field) => ({
|
|
451
663
|
name: (0, path_2.dnaEntryOptionName)(field),
|
|
452
664
|
description: `'${field}' of the entry <path> names, where that collection declares it`,
|
|
453
665
|
}));
|
|
666
|
+
/** A single-valued option of {@link MemoryAddParams.options}: a repeated occurrence keeps the last. */
|
|
667
|
+
function singleOption(options, name) {
|
|
668
|
+
const value = options?.[name];
|
|
669
|
+
return typeof value === 'string' || value === undefined ? value : value[value.length - 1];
|
|
670
|
+
}
|
|
454
671
|
/**
|
|
455
672
|
* `memory add` `CoreOperation.fn` (P1.3, `mutates: true` — the first Memory-document mutation and the
|
|
456
673
|
* pattern for all Memory CRUD; spec-006-core-domain-api §3 memory table). Follows `dna set`'s
|
|
457
674
|
* mutating-op template (task-025) exactly, over the Memory store instead of `dna.yaml`:
|
|
458
675
|
*
|
|
459
|
-
* 1.
|
|
460
|
-
* the git identity is unset, returning its `CoreResult.error` unchanged (exit 1).
|
|
461
|
-
* 2. **Argument validation** — a missing `--type`/`--title` is a usage error: `throw new UsageError`
|
|
676
|
+
* 1. **Argument validation** — a missing `--type`/`--title` is a usage error: `throw new UsageError`
|
|
462
677
|
* → exit **2** with the exact `missing required argument: --<name>` message
|
|
463
678
|
* (spec-008-cli-grammar §5, mapped by `exitCodeForThrow`), same classification as `dna set`'s
|
|
464
|
-
* missing positional.
|
|
679
|
+
* missing positional. So is every spelling fault of `--set` (`spec-008` §10: no `=`, a name that
|
|
680
|
+
* is not a field name, a reserved name, a blank value, a name given twice — task-110). It runs
|
|
681
|
+
* first, so a malformed invocation exits 2 whatever the git identity (task-125, `bug-172`).
|
|
682
|
+
* 2. **`requireGitIdentity` pre-flight** (REQ-SEC-01, task-014) — refuse before any read/write when
|
|
683
|
+
* the git identity is unset, returning its `CoreResult.error` unchanged (exit 1).
|
|
465
684
|
* 3. **Resolve `--type` against the `memory.yaml` committed at `HEAD`** (spec-001-memory-yaml-schema)
|
|
466
685
|
* via {@link resolveAddType} — the type registry, the type's `path` and its `template` scaffold
|
|
467
686
|
* all resolve against the repository as committed, never against the working tree (task-095,
|
|
@@ -469,14 +688,26 @@ const DNA_ENTRY_OPTIONS = (0, path_2.dnaEntryOptionNames)().map((field) => ({
|
|
|
469
688
|
* none, so no call path can reach the decision with a working-tree registry. An unknown type is a
|
|
470
689
|
* domain `NOT_FOUND` (exit 1) with the exact P1.3 message
|
|
471
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).
|
|
472
695
|
* 4. **Generate the id** deterministically from the type's committed `id_pattern` (task-002's
|
|
473
|
-
* `generateId`, REQ-SYS-07)
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
696
|
+
* `generateId`, REQ-SYS-07), in `spec-001`'s fixed order (task-110, `dl-107` S2): first every
|
|
697
|
+
* frontmatter/context token from `--set` (a `--set` name the committed `id_pattern` and `path` do
|
|
698
|
+
* not contain is refused, exit 1; a token with no value names the option that supplies it), then
|
|
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`
|
|
706
|
+
* skeleton (P1.3; spec-010-memory-frontmatter-schema) plus the `--set` fields (a context token only
|
|
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
|
|
480
711
|
* confined `writeMemoryEntry` (REQ-SEC-06 refuse-before-write + one scoped commit
|
|
481
712
|
* `wf(<type>): add <id>`); the returned sha rides `CoreResult.commit`.
|
|
482
713
|
*
|
|
@@ -486,56 +717,118 @@ const DNA_ENTRY_OPTIONS = (0, path_2.dnaEntryOptionNames)().map((field) => ({
|
|
|
486
717
|
* only exit-2 path and are thrown before this try.
|
|
487
718
|
*/
|
|
488
719
|
const memoryAddFn = async (params) => {
|
|
489
|
-
const { root, options } = params;
|
|
490
|
-
const
|
|
491
|
-
if (!identity.ok)
|
|
492
|
-
return identity;
|
|
493
|
-
const type = options?.type;
|
|
720
|
+
const { root, options, fields: extraFields } = params;
|
|
721
|
+
const type = singleOption(options, 'type');
|
|
494
722
|
if (type === undefined)
|
|
495
723
|
throw new usage_error_1.UsageError('missing required argument: --type');
|
|
496
|
-
const title = options
|
|
724
|
+
const title = singleOption(options, 'title');
|
|
497
725
|
if (title === undefined)
|
|
498
726
|
throw new usage_error_1.UsageError('missing required argument: --title');
|
|
499
|
-
const tags = (0, memory_1.parseTags)(options
|
|
727
|
+
const tags = (0, memory_1.parseTags)(singleOption(options, 'tags'));
|
|
728
|
+
const set = (0, memory_1.parseSetOptions)(options?.set);
|
|
729
|
+
if (!set.ok)
|
|
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');
|
|
736
|
+
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
737
|
+
if (!identity.ok)
|
|
738
|
+
return identity;
|
|
500
739
|
// task-095 / `bug-085`: the registry, the `path` and the scaffold all resolve at HEAD, inside a
|
|
501
740
|
// function that accepts no parsed `MemoryYaml` — so this verb cannot decide any of the three from
|
|
502
741
|
// a working-tree copy, in the same way task-091 made that true of the four transition verbs.
|
|
503
742
|
const resolved = (0, memory_add_type_1.resolveAddType)(root, type);
|
|
504
743
|
if (!resolved.ok)
|
|
505
744
|
return resolved;
|
|
506
|
-
const { pathPattern, idPattern, scaffold } = resolved.value;
|
|
745
|
+
const { pathPattern, idPattern, scaffold, initialState, memoryYaml: committedMemoryYaml } = resolved.value;
|
|
746
|
+
const unknown = (0, memory_1.unknownSetNames)(set.values, idPattern, pathPattern);
|
|
747
|
+
if (unknown.length > 0) {
|
|
748
|
+
return (0, types_1.coreErr)({
|
|
749
|
+
code: 'VALIDATION',
|
|
750
|
+
message: unknown
|
|
751
|
+
.map((name) => `--set ${name}: memory type '${type}' has no token {${name}} in its id_pattern or path`)
|
|
752
|
+
.join('; '),
|
|
753
|
+
});
|
|
754
|
+
}
|
|
507
755
|
try {
|
|
508
|
-
|
|
509
|
-
|
|
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 });
|
|
773
|
+
const sequence = (0, memory_1.hasNumericToken)(materialized)
|
|
774
|
+
? (0, memory_1.nextSequenceNumber)(root, pathPattern, materialized)
|
|
510
775
|
: 0;
|
|
511
|
-
const id = (0, validation_1.generateId)(
|
|
512
|
-
const
|
|
513
|
-
|
|
776
|
+
const id = (0, validation_1.generateId)(materialized, { slug: (0, memory_1.slugifyTitle)(title), n: sequence });
|
|
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 });
|
|
781
|
+
const pathValues = { ...set.values, id };
|
|
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')}`;
|
|
514
785
|
// dl-080 (B) / bug-078, AC3: this verb CREATES, so the rule is absence rather than cleanliness.
|
|
515
|
-
// `
|
|
516
|
-
//
|
|
517
|
-
//
|
|
518
|
-
//
|
|
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
|
|
519
791
|
// `writeMemoryEntry`'s return value because the guard must run BEFORE the write; the resolution
|
|
520
792
|
// is pure, so doing it twice is free of side effects and keeps that throwing storage primitive's
|
|
521
793
|
// contract untouched. A confinement escape still throws `StorageError` from this same call.
|
|
522
|
-
const targetPath = (0, path_1.relative)(root, (0, memory_path_1.resolveConfinedMemoryPath)(root, pathPattern,
|
|
794
|
+
const targetPath = (0, path_1.relative)(root, (0, memory_path_1.resolveConfinedMemoryPath)(root, pathPattern, pathValues)).split(path_1.sep).join('/');
|
|
523
795
|
const absent = (0, write_guard_1.requireAbsentTarget)(root, targetPath);
|
|
524
796
|
if (!absent.ok)
|
|
525
797
|
return absent;
|
|
526
|
-
|
|
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
|
+
});
|
|
527
813
|
const leaked = (0, write_guard_1.committedScopeError)(root, sha, targetPath, content);
|
|
528
814
|
if (leaked)
|
|
529
815
|
return leaked;
|
|
530
|
-
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);
|
|
531
817
|
}
|
|
532
818
|
catch (error) {
|
|
533
819
|
if (error instanceof storage_1.StorageError) {
|
|
534
|
-
|
|
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 });
|
|
535
826
|
}
|
|
536
827
|
if (error instanceof validation_1.ValidationError) {
|
|
537
|
-
|
|
538
|
-
|
|
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('; ') });
|
|
539
832
|
}
|
|
540
833
|
throw error;
|
|
541
834
|
}
|
|
@@ -585,22 +878,26 @@ const memorySearchFn = async (params) => {
|
|
|
585
878
|
if (positional !== undefined)
|
|
586
879
|
(0, memory_1.validateSearchQuery)(positional);
|
|
587
880
|
const query = positional ?? '';
|
|
588
|
-
const loaded =
|
|
881
|
+
const loaded = loadConfigOrError(root, () => (0, loaders_1.loadMemoryYaml)(root));
|
|
589
882
|
if (!loaded.ok)
|
|
590
883
|
return loaded;
|
|
591
884
|
const tag = options?.tag;
|
|
592
885
|
const type = options?.type;
|
|
593
886
|
const status = options?.status;
|
|
887
|
+
const diagnostics = [];
|
|
594
888
|
const scanned = (0, memory_1.searchMemoryDocuments)(root, loaded.value, query, {
|
|
595
889
|
...(tag !== undefined ? { tag } : {}),
|
|
596
890
|
...((0, memory_1.isArchivedStatus)(status) ? { includeArchived: true } : {}),
|
|
891
|
+
onDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
|
|
597
892
|
});
|
|
598
893
|
const matches = scanned
|
|
599
894
|
.filter((match) => (type === undefined || match.type === type) && (status === undefined || match.status === status))
|
|
600
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);
|
|
601
898
|
return matches.length === 0
|
|
602
|
-
? (0, types_1.coreOk)({ query, matches, message: NO_MEMORY_SEARCH_MATCHES_MESSAGE })
|
|
603
|
-
: (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);
|
|
604
901
|
};
|
|
605
902
|
/**
|
|
606
903
|
* `memory history` `CoreOperation.fn` (P1.10, `mutates: false`; spec-006-core-domain-api §3 memory
|
|
@@ -617,7 +914,9 @@ const memorySearchFn = async (params) => {
|
|
|
617
914
|
* 3. **Resolve the id to a document** with task-009's {@link findMemoryDocumentById} — an EXACT
|
|
618
915
|
* frontmatter-`id` match over the deterministically-sorted document set, never a substring (that
|
|
619
916
|
* is `memory search`'s job). No match is a domain `NOT_FOUND` (exit 1) with the exact P1.10 message
|
|
620
|
-
* `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
|
|
621
920
|
* excluded: REQ-STATE-06 scopes its exclusion to default *search* results, and explicitly
|
|
622
921
|
* guarantees an archived element remains "present on disk and in git history" — which is precisely
|
|
623
922
|
* what this command reads.
|
|
@@ -629,20 +928,49 @@ const memorySearchFn = async (params) => {
|
|
|
629
928
|
*
|
|
630
929
|
* A document that exists on disk but has never been committed yields `entries: []` — correctly, not
|
|
631
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`).
|
|
632
933
|
*/
|
|
633
934
|
const memoryHistoryFn = async (params) => {
|
|
634
935
|
const { root, positional: id } = params;
|
|
635
936
|
if (id === undefined || id.trim().length === 0) {
|
|
636
|
-
throw new usage_error_1.UsageError('
|
|
937
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
|
|
637
938
|
}
|
|
638
|
-
const loaded =
|
|
939
|
+
const loaded = loadConfigOrError(root, () => (0, loaders_1.loadMemoryYaml)(root));
|
|
639
940
|
if (!loaded.ok)
|
|
640
941
|
return loaded;
|
|
641
|
-
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
|
+
}
|
|
642
959
|
if (!found) {
|
|
643
960
|
return (0, types_1.coreErr)({ code: 'NOT_FOUND', message: `document not found: ${id}` });
|
|
644
961
|
}
|
|
645
|
-
|
|
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) => ({
|
|
646
974
|
sha: transition.sha,
|
|
647
975
|
author: `${transition.authorName} <${transition.authorEmail}>`,
|
|
648
976
|
timestamp: transition.date,
|
|
@@ -654,55 +982,81 @@ const memoryHistoryFn = async (params) => {
|
|
|
654
982
|
: null,
|
|
655
983
|
reason: transition.reason,
|
|
656
984
|
subject: transition.subject,
|
|
985
|
+
wingfoil: transition.wingfoil,
|
|
986
|
+
...(transition.unreadable !== undefined ? { unreadable: transition.unreadable } : {}),
|
|
657
987
|
}));
|
|
658
|
-
|
|
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));
|
|
659
996
|
};
|
|
660
997
|
/**
|
|
661
998
|
* `memory submit` `CoreOperation.fn` (P1.6, `mutates: true`; spec-006-core-domain-api §3). Submit is
|
|
662
999
|
* "the content is written — move it forward": it commits the document as the author left it, with
|
|
663
1000
|
* `status` advanced. Order, every refusal before the single write:
|
|
664
1001
|
*
|
|
665
|
-
* 1.
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
* (task-
|
|
1002
|
+
* 1. **`<id>`** absent or blank → `UsageError` (exit 2), as `memory history` — before the identity
|
|
1003
|
+
* check, so the exit code does not depend on the machine (task-125, `bug-172`).
|
|
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,
|
|
669
1008
|
* invalid state, or an illegal transition, each a `CoreResult.error` (exit 1); an illegal one
|
|
670
1009
|
* carries the pinned `illegal transition <from> -> <to> for type '<type>'` (`dl-032`, P1.6 sc.2).
|
|
671
1010
|
* 5. **Required fields** (spec-010 validation rules) — `title` and every `template.frontmatter.required`
|
|
672
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}).
|
|
673
1015
|
* 6. **Edit + commit** — `status` set to the target and `rejection_reason` removed (spec-010 field-write
|
|
674
1016
|
* ownership; every other byte kept), then one commit scoped to that file, subject
|
|
675
|
-
* `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
|
|
676
1020
|
* re-parsed first (`commitMemoryTransition`'s post-condition: `status` is the target, no
|
|
677
1021
|
* `rejection_reason`, nothing else changed); a failure is `VALIDATION` (exit 1) with nothing written. No `--reason`: spec-008 §2
|
|
678
1022
|
* requires it only on the approval gates (`dl-027`).
|
|
679
1023
|
*/
|
|
680
1024
|
const memorySubmitFn = async (params) => {
|
|
681
1025
|
const { root, positional: id } = params;
|
|
682
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
683
|
-
if (!identity.ok)
|
|
684
|
-
return identity;
|
|
685
1026
|
if (id === undefined || id.trim().length === 0) {
|
|
686
|
-
throw new usage_error_1.UsageError('
|
|
1027
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
|
|
687
1028
|
}
|
|
688
|
-
const prepared = (0, memory_transition_1.
|
|
1029
|
+
const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'submit');
|
|
689
1030
|
if (!prepared.ok)
|
|
690
1031
|
return prepared;
|
|
691
1032
|
const { type, path, frontmatter, content, from, to } = prepared.value;
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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.
|
|
698
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 });
|
|
699
1053
|
// `carries-content`: submit is the ONE verb entitled to bring the author's body and required fields
|
|
700
1054
|
// into its commit (spec-010's field-write ownership row), so it is not guarded against a modified
|
|
701
1055
|
// working tree and its commit need not differ from HEAD~1 by `status` alone (task-088, bug-076 AC4).
|
|
702
1056
|
const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message, { [memory_1.REJECTION_REASON_FIELD]: undefined }, 'carries-content');
|
|
703
1057
|
if (!committed.ok)
|
|
704
1058
|
return committed;
|
|
705
|
-
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);
|
|
706
1060
|
};
|
|
707
1061
|
/**
|
|
708
1062
|
* `memory approve` `CoreOperation.fn` (P1.7, `mutates: true`; spec-006-core-domain-api §3). Approve is
|
|
@@ -711,21 +1065,24 @@ const memorySubmitFn = async (params) => {
|
|
|
711
1065
|
* (P1.2/P1.10), never written into the message. Order, every refusal before the single write, so
|
|
712
1066
|
* "the state is unchanged" (P1.7 sc.2/sc.3) holds by construction:
|
|
713
1067
|
*
|
|
714
|
-
* 1.
|
|
715
|
-
*
|
|
716
|
-
* 2.
|
|
717
|
-
*
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
1068
|
+
* 1. **`<id>`** absent or blank → `UsageError` (exit 2), as `memory submit`/`memory history`.
|
|
1069
|
+
* 2. **`requireReason`** (REQ-SEC-04, task-041) → `UsageError` `missing required argument: --reason`
|
|
1070
|
+
* (exit 2, P1.7 sc.2). Placed before any file is read, so an omitted reason touches nothing. Both
|
|
1071
|
+
* usage checks precede the identity check (task-125, `bug-172`).
|
|
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,
|
|
721
1078
|
* invalid state, or an illegal transition, each a `CoreResult.error` (exit 1); an illegal one
|
|
722
1079
|
* carries the pinned `illegal transition <from> -> <to> for type '<type>'` (`dl-032`), whose `<to>`
|
|
723
|
-
* is `approve`
|
|
1080
|
+
* is `(none)`: `approve` reaches nothing from `<from>` (task-181, `bug-165`).
|
|
724
1081
|
* 6. **`requireApprovalAuthority`** (REQ-SEC-03, task-040) — exit 1 with
|
|
725
1082
|
* `user not authorized to approve type '<type>'` (P1.7 sc.3). It runs after step 5 because its
|
|
726
1083
|
* message interpolates the document's type, which is only knowable once the document is located,
|
|
727
|
-
* and `
|
|
728
|
-
* 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
|
|
729
1086
|
* *decision* depends on nothing step 5 computes (adr-006 fixes one uniform `approver` role, keyed
|
|
730
1087
|
* on the git identity), and step 5 writes nothing. It reads the roles from the **committed**
|
|
731
1088
|
* `.wingfoil/dna.yaml` and resolves that file itself (task-090, `bug-079`) — which is why no
|
|
@@ -734,29 +1091,40 @@ const memorySubmitFn = async (params) => {
|
|
|
734
1091
|
* 7. **Edit + commit** — `status` set to the target and **nothing else** (spec-010 field-write
|
|
735
1092
|
* ownership: approve changes only `status`; approver and reason live in the commit message). One
|
|
736
1093
|
* commit scoped to that file, subject `wf(<type>): approve <id> [<from> → <to>]` with the mandatory
|
|
737
|
-
* `Approver:` / `Reason:` body (
|
|
1094
|
+
* `Approver:` / `Reason:` body (AGENTS.md §5.1; `dl-054` confines the bracket to the
|
|
738
1095
|
* approver-gated verbs, which is why `memory submit` has none). `commitMemoryTransition` re-parses
|
|
739
1096
|
* the rendered frontmatter first and refuses (exit 1, nothing written) unless `status` is the
|
|
740
1097
|
* target and no other field's value moved — which is exactly spec-010's "only `status`" rule, so
|
|
741
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.
|
|
742
1109
|
*/
|
|
743
1110
|
const memoryApproveFn = async (params) => {
|
|
744
1111
|
const { root, positional: id, options } = params;
|
|
745
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
746
|
-
if (!identity.ok)
|
|
747
|
-
return identity;
|
|
748
1112
|
if (id === undefined || id.trim().length === 0) {
|
|
749
|
-
throw new usage_error_1.UsageError('
|
|
1113
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
|
|
750
1114
|
}
|
|
751
1115
|
const reason = (0, require_reason_1.requireReason)(options);
|
|
752
|
-
const prepared = (0, memory_transition_1.
|
|
1116
|
+
const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'approve');
|
|
753
1117
|
if (!prepared.ok)
|
|
754
1118
|
return prepared;
|
|
755
1119
|
const { type, path, content, from, to } = prepared.value;
|
|
756
|
-
const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type);
|
|
1120
|
+
const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type, prepared.value.identity);
|
|
757
1121
|
if (!authorized.ok)
|
|
758
1122
|
return authorized;
|
|
759
|
-
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;
|
|
760
1128
|
const message = (0, memory_1.formatMemoryCommitMessage)({
|
|
761
1129
|
type,
|
|
762
1130
|
op: 'approve',
|
|
@@ -765,24 +1133,89 @@ const memoryApproveFn = async (params) => {
|
|
|
765
1133
|
approver: { name, email, role: approval_authority_1.APPROVER_ROLE },
|
|
766
1134
|
reason,
|
|
767
1135
|
});
|
|
768
|
-
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);
|
|
769
1157
|
if (!committed.ok)
|
|
770
1158
|
return committed;
|
|
771
|
-
|
|
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);
|
|
772
1203
|
};
|
|
773
1204
|
/**
|
|
774
1205
|
* `memory reject` `CoreOperation.fn` (P1.8, `mutates: true`; spec-006-core-domain-api §3) — the FIRST
|
|
775
1206
|
* approver-gated verb, so the first operation that enforces REQ-SEC-03 (role authority) and REQ-SEC-04
|
|
776
1207
|
* (mandatory reason) and the first to emit a commit with a `[from → to]` subject bracket and an
|
|
777
|
-
* `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
|
|
778
1209
|
* refusal before the single write:
|
|
779
1210
|
*
|
|
780
|
-
* 1.
|
|
781
|
-
* 2.
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
1211
|
+
* 1. **`<id>`** absent or blank → `UsageError` (exit `2`), as `memory submit`.
|
|
1212
|
+
* 2. **`requireReason`** (REQ-SEC-04, task-041-mandatory-reason-on-verbs) → `UsageError`
|
|
1213
|
+
* `missing required argument: --reason` (exit `2`, P1.8 sc.3). Both usage checks precede the
|
|
1214
|
+
* identity check (task-125, `bug-172`).
|
|
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,
|
|
786
1219
|
* unknown type, invalid state, or an illegal transition (the document is in no `gates`
|
|
787
1220
|
* state), each a `CoreResult.error` at exit `1` carrying `dl-032`'s pinned contract message. The
|
|
788
1221
|
* target is the type's `gates.<from>.reject` value, taken verbatim (`spec-001`), so it need not be
|
|
@@ -800,27 +1233,24 @@ const memoryApproveFn = async (params) => {
|
|
|
800
1233
|
* document and refuses (`VALIDATION`, exit `1`, nothing written) unless `status` AND
|
|
801
1234
|
* `rejection_reason` hold their expected values and no other field moved.
|
|
802
1235
|
*
|
|
803
|
-
* The `Approver:` identity is the
|
|
804
|
-
*
|
|
805
|
-
* (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.
|
|
806
1239
|
*/
|
|
807
1240
|
const memoryRejectFn = async (params) => {
|
|
808
1241
|
const { root, positional: id, options } = params;
|
|
809
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
810
|
-
if (!identity.ok)
|
|
811
|
-
return identity;
|
|
812
1242
|
if (id === undefined || id.trim().length === 0) {
|
|
813
|
-
throw new usage_error_1.UsageError('
|
|
1243
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
|
|
814
1244
|
}
|
|
815
1245
|
const reason = (0, require_reason_1.requireReason)(options);
|
|
816
|
-
const prepared = (0, memory_transition_1.
|
|
1246
|
+
const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'reject');
|
|
817
1247
|
if (!prepared.ok)
|
|
818
1248
|
return prepared;
|
|
819
1249
|
const { type, path, from, to, content } = prepared.value;
|
|
820
|
-
const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type);
|
|
1250
|
+
const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type, prepared.value.identity);
|
|
821
1251
|
if (!authorized.ok)
|
|
822
1252
|
return authorized;
|
|
823
|
-
const { name, email } =
|
|
1253
|
+
const { name, email } = prepared.value.identity;
|
|
824
1254
|
const message = (0, memory_1.formatMemoryCommitMessage)({
|
|
825
1255
|
type,
|
|
826
1256
|
op: 'reject',
|
|
@@ -833,7 +1263,7 @@ const memoryRejectFn = async (params) => {
|
|
|
833
1263
|
const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message, { [memory_1.REJECTION_REASON_FIELD]: reason });
|
|
834
1264
|
if (!committed.ok)
|
|
835
1265
|
return committed;
|
|
836
|
-
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);
|
|
837
1267
|
};
|
|
838
1268
|
/**
|
|
839
1269
|
* `memory deprecate` `CoreFn` (P1.9, `mutates: true`; spec-006-core-domain-api §3) — the retire verb.
|
|
@@ -844,15 +1274,19 @@ const memoryRejectFn = async (params) => {
|
|
|
844
1274
|
* from a literal here — so this one function retires a `task` sitting in a `waiting` state, an
|
|
845
1275
|
* `accepted` `adr`, a terminal `decision-log`, and a type that falls back to `defaults.states`, with
|
|
846
1276
|
* no per-type branch. (`adr`/`tech-spec`'s `superseded` is NOT this verb's target: it is a `waiting`
|
|
847
|
-
* edge fired by a later element's `supersedes:` field — see
|
|
848
|
-
* `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.)
|
|
849
1280
|
*
|
|
850
1281
|
* Order, every refusal before the single write:
|
|
851
1282
|
*
|
|
852
|
-
* 1.
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
*
|
|
1283
|
+
* 1. **`<id>`** absent or blank → `UsageError` (exit `2`), as `memory submit`/`memory reject`; so is a
|
|
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,
|
|
856
1290
|
* unknown type, or a `status` that is not a state of the type, each a
|
|
857
1291
|
* `CoreResult.error` (exit `1`). There is no illegal-transition branch: the wildcard edge is legal
|
|
858
1292
|
* from every state.
|
|
@@ -876,17 +1310,15 @@ const memoryRejectFn = async (params) => {
|
|
|
876
1310
|
*/
|
|
877
1311
|
const memoryDeprecateFn = async (params) => {
|
|
878
1312
|
const { root, positional: id, options } = params;
|
|
879
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
880
|
-
if (!identity.ok)
|
|
881
|
-
return identity;
|
|
882
1313
|
if (id === undefined || id.trim().length === 0) {
|
|
883
|
-
throw new usage_error_1.UsageError('
|
|
1314
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
|
|
884
1315
|
}
|
|
885
1316
|
// `--reason` is optional here (`dl-027`), but a reason that IS given must be recordable in the
|
|
886
|
-
// trailer — blank
|
|
887
|
-
// (`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).
|
|
888
1320
|
const reason = (0, require_reason_1.optionalReason)(options);
|
|
889
|
-
const prepared = (0, memory_transition_1.
|
|
1321
|
+
const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'deprecate');
|
|
890
1322
|
if (!prepared.ok)
|
|
891
1323
|
return prepared;
|
|
892
1324
|
const { type, path, from, to, content } = prepared.value;
|
|
@@ -898,21 +1330,148 @@ const memoryDeprecateFn = async (params) => {
|
|
|
898
1330
|
const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message);
|
|
899
1331
|
if (!committed.ok)
|
|
900
1332
|
return committed;
|
|
901
|
-
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);
|
|
902
1459
|
};
|
|
903
1460
|
/**
|
|
904
1461
|
* `directive create` `CoreOperation.fn` (P3.1, `mutates: true` — the FIRST Directives-pillar mutation
|
|
905
1462
|
* and the pillar's first CLI verb; spec-006-core-domain-api §3 directives table). Follows `dna set`'s
|
|
906
1463
|
* mutating-op template (task-025) exactly, over the Directives pillar instead of `dna.yaml`:
|
|
907
1464
|
*
|
|
908
|
-
* 1.
|
|
909
|
-
* the git identity is unset, returning its `CoreResult.error` unchanged (exit 1).
|
|
910
|
-
* 2. **Argument validation** — both failures are usage errors (`throw new UsageError` → exit **2**,
|
|
1465
|
+
* 1. **Argument validation** — both failures are usage errors (`throw new UsageError` → exit **2**,
|
|
911
1466
|
* mapped by `exitCodeForThrow`): an absent `--name` uses spec-008-cli-grammar §4's exact
|
|
912
1467
|
* `missing required argument: --<name>` wording (same as `memoryAdd`), and a non-kebab-case name
|
|
913
1468
|
* uses P3.1's exact `invalid directive name (use kebab-case)` message
|
|
914
1469
|
* ({@link INVALID_DIRECTIVE_NAME_MESSAGE}). Because {@link isValidDirectiveName} runs before any
|
|
915
1470
|
* path is built, a traversal-shaped name (`../x`, `a/b`, `/etc/passwd`) can never reach the write.
|
|
1471
|
+
* It runs before the identity check, so the exit code does not depend on the machine (task-125,
|
|
1472
|
+
* `bug-172`).
|
|
1473
|
+
* 2. **`requireGitIdentity` pre-flight** (REQ-SEC-01, task-014) — refuse before any read/write when
|
|
1474
|
+
* the git identity is unset, returning its `CoreResult.error` unchanged (exit 1).
|
|
916
1475
|
* 3. **Already-exists check** — a file at `.wingfoil/directives/custom/<name>.md` makes this a domain
|
|
917
1476
|
* `CONFLICT` (the input is well-formed; the target is taken), returned NOT thrown, with P3.1's exact
|
|
918
1477
|
* `directive already exists: <name>` message → exit 1 via `exitCodeForError`. Nothing is written, so
|
|
@@ -928,19 +1487,25 @@ const memoryDeprecateFn = async (params) => {
|
|
|
928
1487
|
*/
|
|
929
1488
|
const directiveCreateFn = async (params) => {
|
|
930
1489
|
const { root, options } = params;
|
|
931
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
932
|
-
if (!identity.ok)
|
|
933
|
-
return identity;
|
|
934
1490
|
const name = options?.name;
|
|
935
1491
|
if (name === undefined)
|
|
936
1492
|
throw new usage_error_1.UsageError('missing required argument: --name');
|
|
937
1493
|
if (!(0, create_1.isValidDirectiveName)(name))
|
|
938
1494
|
throw new usage_error_1.UsageError(create_1.INVALID_DIRECTIVE_NAME_MESSAGE);
|
|
1495
|
+
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
1496
|
+
if (!identity.ok)
|
|
1497
|
+
return identity;
|
|
939
1498
|
// One spelling of the location, reused for the existence check, the write and the commit scope, so
|
|
940
1499
|
// the three can never drift apart. `join` normalizes the separators, so the POSIX form is also the
|
|
941
1500
|
// correct absolute path on Windows; the root-relative form is what `commitPaths` stages.
|
|
942
1501
|
const relativePath = `.wingfoil/directives/custom/${name}.md`;
|
|
943
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;
|
|
944
1509
|
if ((0, storage_1.documentExists)(absolutePath)) {
|
|
945
1510
|
return (0, types_1.coreErr)({ code: 'CONFLICT', message: `directive already exists: ${name}` });
|
|
946
1511
|
}
|
|
@@ -951,9 +1516,8 @@ const directiveCreateFn = async (params) => {
|
|
|
951
1516
|
if (!unmodified.ok)
|
|
952
1517
|
return unmodified;
|
|
953
1518
|
const content = (0, create_1.renderCustomDirective)(name);
|
|
954
|
-
(0, storage_1.writeDocument)(absolutePath, content);
|
|
955
1519
|
const message = `wf(directive): create ${name}`;
|
|
956
|
-
const sha = (0, storage_1.
|
|
1520
|
+
const sha = (0, storage_1.writeAndCommit)(root, [{ path: relativePath, content }], message);
|
|
957
1521
|
const leaked = (0, write_guard_1.committedScopeError)(root, sha, relativePath, content);
|
|
958
1522
|
if (leaked)
|
|
959
1523
|
return leaked;
|
|
@@ -966,11 +1530,11 @@ const directiveCreateFn = async (params) => {
|
|
|
966
1530
|
* it, and `spec-008` §1 / `X_cli-cmds.md` list no fourth directive verb — so it is this operation
|
|
967
1531
|
* with a list-valued `--directive`. Same mutating-op order as {@link directiveCreateFn}:
|
|
968
1532
|
*
|
|
969
|
-
* 1. `
|
|
970
|
-
* 2. `--directive` then `--role` presence — `UsageError`, spec-008 §4 wording, exit 2. A `--directive`
|
|
1533
|
+
* 1. `--directive` then `--role` presence — `UsageError`, spec-008 §4 wording, exit 2. A `--directive`
|
|
971
1534
|
* value that parses to **no ids** (`""`, `" "`, `","`) counts as absent, the same reading
|
|
972
1535
|
* `parseTags` takes of an empty `--tags` (`[AUTHORING]`, task-056 D3: before P3.7 such a value
|
|
973
1536
|
* reached step 3 and failed with an empty id in the message, `unknown directive: `).
|
|
1537
|
+
* 2. `requireGitIdentity` pre-flight (REQ-SEC-01), after the usage checks (task-125, `bug-172`).
|
|
974
1538
|
* 3. `checkAssignable` — which resolves **both** of its baselines from the repository as committed at
|
|
975
1539
|
* `HEAD` itself, the role catalogue (task-091, `bug-082`) and the directive inventory (task-096,
|
|
976
1540
|
* `bug-086`), per `dl-080` (B) — returns P3.2's exact
|
|
@@ -984,13 +1548,12 @@ const directiveCreateFn = async (params) => {
|
|
|
984
1548
|
* never sorting) through the comment-preserving `roles.yaml` writer and makes ONE commit,
|
|
985
1549
|
* `wf(directive): assign <id1>, <id2> to <role>`, staging only `.wingfoil/roles.yaml`. A request
|
|
986
1550
|
* whose ids are all already bound is a success with no write and no commit (P3.7 Sc.2 "Binding is
|
|
987
|
-
* 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`).
|
|
988
1554
|
*/
|
|
989
1555
|
const directiveAssignFn = async (params) => {
|
|
990
|
-
const { root, options } = params;
|
|
991
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
992
|
-
if (!identity.ok)
|
|
993
|
-
return identity;
|
|
1556
|
+
const { root, options, force } = params;
|
|
994
1557
|
const directive = options?.directive;
|
|
995
1558
|
if (directive === undefined)
|
|
996
1559
|
throw new usage_error_1.UsageError('missing required argument: --directive');
|
|
@@ -1000,14 +1563,19 @@ const directiveAssignFn = async (params) => {
|
|
|
1000
1563
|
const role = options?.role;
|
|
1001
1564
|
if (role === undefined)
|
|
1002
1565
|
throw new usage_error_1.UsageError('missing required argument: --role');
|
|
1566
|
+
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
1567
|
+
if (!identity.ok)
|
|
1568
|
+
return identity;
|
|
1003
1569
|
const invalid = (0, directive_assign_1.checkAssignable)(root, role, directives);
|
|
1004
1570
|
if (invalid)
|
|
1005
1571
|
return (0, types_1.coreErr)(invalid);
|
|
1006
1572
|
const message = `wf(directive): assign ${directives.join(', ')} to ${role}`;
|
|
1007
|
-
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
|
+
});
|
|
1008
1576
|
if (!updated.ok)
|
|
1009
1577
|
return updated;
|
|
1010
|
-
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);
|
|
1011
1579
|
};
|
|
1012
1580
|
/**
|
|
1013
1581
|
* `directive remove` `CoreOperation.fn` (P3.3, `mutates: true`; spec-006 §3 row `directiveRemove`,
|
|
@@ -1017,9 +1585,9 @@ const directiveAssignFn = async (params) => {
|
|
|
1017
1585
|
* option (b)) assigns clause (b)'s directive half here. Both are enforced, in this order, and both
|
|
1018
1586
|
* strictly **before** anything on disk changes:
|
|
1019
1587
|
*
|
|
1020
|
-
* 1. `
|
|
1021
|
-
*
|
|
1022
|
-
*
|
|
1588
|
+
* 1. `<name>` presence — a missing or blank positional is a `UsageError` → exit **2**
|
|
1589
|
+
* (`missing required argument: <name>`, the one missing-operand wording, task-179).
|
|
1590
|
+
* 2. `requireGitIdentity` pre-flight (REQ-SEC-01), after the usage check (task-125, `bug-172`).
|
|
1023
1591
|
* 3. **Resolve the name to a real file**, never to a constructed path. `loadDirectives` reads every
|
|
1024
1592
|
* installed directive and {@link selectDirectivesById} picks the winner for that id under
|
|
1025
1593
|
* `dl-037`'s custom-wins precedence (`src/core/context.ts` — the same primitive `directives list`
|
|
@@ -1067,18 +1635,26 @@ const directiveAssignFn = async (params) => {
|
|
|
1067
1635
|
*/
|
|
1068
1636
|
const directiveRemoveFn = async (params) => {
|
|
1069
1637
|
const { root, positional: name } = params;
|
|
1638
|
+
if (name === undefined || name.trim().length === 0) {
|
|
1639
|
+
throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('name'));
|
|
1640
|
+
}
|
|
1070
1641
|
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
1071
1642
|
if (!identity.ok)
|
|
1072
1643
|
return identity;
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
const
|
|
1077
|
-
if (!directiveFiles.ok)
|
|
1078
|
-
return directiveFiles;
|
|
1079
|
-
const target = (0, context_1.selectDirectivesById)(directiveFiles.value, new Set([name])).byId.get(name);
|
|
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);
|
|
1080
1648
|
if (target === undefined) {
|
|
1081
|
-
|
|
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
|
+
});
|
|
1082
1658
|
}
|
|
1083
1659
|
const custom = (0, builtin_asset_1.requireCustomAsset)('directive', target.path);
|
|
1084
1660
|
if (!custom.ok)
|
|
@@ -1105,9 +1681,8 @@ const directiveRemoveFn = async (params) => {
|
|
|
1105
1681
|
const unmodified = (0, write_guard_1.requireUnmodifiedTarget)(root, relativePath);
|
|
1106
1682
|
if (!unmodified.ok)
|
|
1107
1683
|
return unmodified;
|
|
1108
|
-
(0, storage_1.removeDocument)((0, path_1.join)(root, relativePath));
|
|
1109
1684
|
const message = `wf(directive): remove ${name}`;
|
|
1110
|
-
const sha = (0, storage_1.
|
|
1685
|
+
const sha = (0, storage_1.writeAndCommit)(root, [{ path: relativePath, content: null }], message);
|
|
1111
1686
|
const leaked = (0, write_guard_1.committedScopeError)(root, sha, relativePath, null);
|
|
1112
1687
|
if (leaked)
|
|
1113
1688
|
return leaked;
|
|
@@ -1126,8 +1701,86 @@ const directiveRemoveFn = async (params) => {
|
|
|
1126
1701
|
*/
|
|
1127
1702
|
const directivesListFn = async (params) => {
|
|
1128
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;
|
|
1129
1709
|
return loadOrError(() => (0, directives_list_1.loadDirectiveListing)(root, options?.role));
|
|
1130
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
|
+
};
|
|
1131
1784
|
/**
|
|
1132
1785
|
* The production `CoreModule` registry (spec-006 §2, §4). `src/cli`'s command registrar and
|
|
1133
1786
|
* `src/mcp`'s Tool/Resource registrar both import this exact array — see spec-006 §4.1: "no
|
|
@@ -1156,30 +1809,141 @@ const directivesListFn = async (params) => {
|
|
|
1156
1809
|
* Tool, or the diff fails.
|
|
1157
1810
|
*/
|
|
1158
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
|
+
},
|
|
1159
1878
|
{
|
|
1160
1879
|
name: 'dna',
|
|
1880
|
+
description: "read and change dna.yaml, the project's structural map",
|
|
1161
1881
|
operations: {
|
|
1162
1882
|
// The DNA mutation surface (task-093, `dl-081-dna-mutation-surface-shape` option (E)): three
|
|
1163
1883
|
// verbs carrying the collection in their argument rather than in the verb name, so the verb
|
|
1164
1884
|
// count stays constant as `spec-002`'s schema grows and spec-006 §3's one-Tool-per-function rule
|
|
1165
1885
|
// costs three Tools (`dna.add`, `dna.remove`, `dna.update`) instead of the dozen a
|
|
1166
1886
|
// per-collection verb set would need. The path itself is the POSITIONAL `<path>` every command
|
|
1167
|
-
// in this CLI uses for its target (`dl-082-cli-parameter-shape`)
|
|
1168
|
-
// `
|
|
1169
|
-
dnaAdd: {
|
|
1887
|
+
// in this CLI uses for its target (`dl-082-cli-parameter-shape`): each verb declares it as its
|
|
1888
|
+
// `positional` (task-120, what `--help` names), which is why no option declares it here.
|
|
1889
|
+
dnaAdd: {
|
|
1890
|
+
name: 'dnaAdd',
|
|
1891
|
+
mutates: true,
|
|
1892
|
+
description: 'add an entry to a collection, or values to a list',
|
|
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 },
|
|
1894
|
+
options: [DNA_VALUE_OPTION, ...DNA_ENTRY_OPTIONS],
|
|
1895
|
+
example: 'wingfoil dna add team.members --value "Ada Lovelace" --entry-email ada@example.com --entry-roles approver',
|
|
1896
|
+
flags: [DNA_FORCE_FLAG],
|
|
1897
|
+
fn: dnaAddFn,
|
|
1898
|
+
},
|
|
1170
1899
|
// `remove` declares no entry-field options: it takes what to drop, never what to write.
|
|
1171
|
-
dnaRemove: {
|
|
1900
|
+
dnaRemove: {
|
|
1901
|
+
name: 'dnaRemove',
|
|
1902
|
+
mutates: true,
|
|
1903
|
+
description: 'remove a collection entry, or values from a list',
|
|
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 },
|
|
1905
|
+
options: [DNA_VALUE_OPTION],
|
|
1906
|
+
example: 'wingfoil dna remove paths.docs --value README.md',
|
|
1907
|
+
flags: [DNA_FORCE_FLAG],
|
|
1908
|
+
fn: dnaRemoveFn,
|
|
1909
|
+
},
|
|
1172
1910
|
// The FIRST `mutates: true` operation in production (spec-006 §3 dna table) — by construction an
|
|
1173
1911
|
// MCP Tool (`dna.set`) + CLI command (`wingfoil dna set`), and the op the REQ-SYS-05 parity test
|
|
1174
1912
|
// now actually guards (task-025-implement-dna-set). Its second positional became `--value` in
|
|
1175
1913
|
// task-093 (`dl-082`) — a breaking change to a shipped command, made before `minor-v0.2` ships.
|
|
1176
|
-
dnaSet: {
|
|
1177
|
-
|
|
1178
|
-
|
|
1914
|
+
dnaSet: {
|
|
1915
|
+
name: 'dnaSet',
|
|
1916
|
+
mutates: true,
|
|
1917
|
+
description: 'set one scalar field',
|
|
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 },
|
|
1919
|
+
options: [DNA_SET_VALUE_OPTION],
|
|
1920
|
+
example: 'wingfoil dna set project.name --value "My Project"',
|
|
1921
|
+
flags: [DNA_FORCE_FLAG],
|
|
1922
|
+
fn: dnaSetFn,
|
|
1923
|
+
},
|
|
1924
|
+
dnaShow: {
|
|
1925
|
+
name: 'dnaShow',
|
|
1926
|
+
mutates: false,
|
|
1927
|
+
description: 'print dna.yaml, or one top-level section of it',
|
|
1928
|
+
positional: { name: 'section', description: 'a top-level key: project, modules, stacks, team or paths (omit it for the whole file)' },
|
|
1929
|
+
example: 'wingfoil dna show project',
|
|
1930
|
+
fn: dnaShowFn,
|
|
1931
|
+
},
|
|
1932
|
+
dnaUpdate: {
|
|
1933
|
+
name: 'dnaUpdate',
|
|
1934
|
+
mutates: true,
|
|
1935
|
+
description: 'change fields of an existing collection entry',
|
|
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 },
|
|
1937
|
+
options: [DNA_VALUE_OPTION, ...DNA_ENTRY_OPTIONS],
|
|
1938
|
+
example: 'wingfoil dna update modules.api --entry-description "Public HTTP API"',
|
|
1939
|
+
flags: [DNA_FORCE_FLAG],
|
|
1940
|
+
fn: dnaUpdateFn,
|
|
1941
|
+
},
|
|
1179
1942
|
},
|
|
1180
1943
|
},
|
|
1181
1944
|
{
|
|
1182
1945
|
name: 'memory',
|
|
1946
|
+
description: 'create Memory documents, move them through their state machine, and read them back',
|
|
1183
1947
|
operations: {
|
|
1184
1948
|
// The FIRST Memory-document mutation (P1.3, spec-006 §3 memory table) — `mutates: true`, so by
|
|
1185
1949
|
// construction an MCP Tool (`memory.add`) + CLI command (`wingfoil memory add`), and the second
|
|
@@ -1190,18 +1954,48 @@ exports.CORE_MODULES = [
|
|
|
1190
1954
|
memoryAdd: {
|
|
1191
1955
|
name: 'memoryAdd',
|
|
1192
1956
|
mutates: true,
|
|
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",
|
|
1193
1958
|
options: [
|
|
1194
|
-
{ name: 'type', required: true },
|
|
1195
|
-
{ name: 'title', required: true },
|
|
1196
|
-
{ name: 'tags' },
|
|
1959
|
+
{ name: 'type', required: true, valueName: 'type', description: 'the Memory type, as the committed memory.yaml declares it' },
|
|
1960
|
+
{ name: 'title', required: true, valueName: 'title', description: 'the document title; also the source of the {slug} token' },
|
|
1961
|
+
{ name: 'tags', valueName: 't1,t2', description: 'comma-separated tags, written as the tags list' },
|
|
1962
|
+
// task-110, `spec-008-cli-grammar` §10 (`dl-107` S2): the value of an id_pattern/path token,
|
|
1963
|
+
// written into the frontmatter field of that name. One declared option — not one derived
|
|
1964
|
+
// option per field, whose first member, `--version`, is a global action flag.
|
|
1965
|
+
{
|
|
1966
|
+
name: 'set',
|
|
1967
|
+
repeatable: true,
|
|
1968
|
+
valueName: 'name=value',
|
|
1969
|
+
description: 'fill the id_pattern/path token {name} and write the frontmatter field of that name (repeatable)',
|
|
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
|
+
},
|
|
1197
1983
|
],
|
|
1984
|
+
example: 'wingfoil memory add --type task --title "My first task" --tags demo,quickstart',
|
|
1198
1985
|
fn: memoryAddFn,
|
|
1199
1986
|
},
|
|
1200
1987
|
// P1.10 (task-049-memory-history) — `mutates: false`, so by construction an MCP Resource +
|
|
1201
1988
|
// CLI command (`wingfoil memory history <id>`). It declares no flags and no value options: the
|
|
1202
1989
|
// document id rides the generic bare `positional` seam (task-026), per spec-008-cli-grammar §7's
|
|
1203
1990
|
// bare-`<id>` rule for a command whose noun already scopes the type.
|
|
1204
|
-
memoryHistory: {
|
|
1991
|
+
memoryHistory: {
|
|
1992
|
+
name: 'memoryHistory',
|
|
1993
|
+
mutates: false,
|
|
1994
|
+
description: "print a document's audit trail, reconstructed from git",
|
|
1995
|
+
positional: { name: 'id', required: true, description: 'the document id, e.g. task-001-my-first-task' },
|
|
1996
|
+
example: 'wingfoil memory history task-001-my-first-task',
|
|
1997
|
+
fn: memoryHistoryFn,
|
|
1998
|
+
},
|
|
1205
1999
|
// The FIRST Memory-document READ operation (P1.5, spec-006 §3 memory table) — `mutates: false`,
|
|
1206
2000
|
// so by construction an MCP Resource (`wingfoil://memory/search`, the mechanical zero-argument
|
|
1207
2001
|
// form spec-006 §3 pins verbatim) + CLI command (`wingfoil memory search`). Its keyword rides the
|
|
@@ -1211,12 +2005,26 @@ exports.CORE_MODULES = [
|
|
|
1211
2005
|
memorySearch: {
|
|
1212
2006
|
name: 'memorySearch',
|
|
1213
2007
|
mutates: false,
|
|
1214
|
-
|
|
2008
|
+
description: 'find documents by keyword and/or metadata',
|
|
2009
|
+
positional: { name: 'keyword', description: 'a case-insensitive substring matched against title, id, tags and body (omit it to filter by metadata alone)' },
|
|
2010
|
+
options: [
|
|
2011
|
+
{ name: 'tag', valueName: 'tag', description: 'keep documents carrying this tag' },
|
|
2012
|
+
{ name: 'status', valueName: 'status', description: 'keep documents in this state' },
|
|
2013
|
+
{ name: 'type', valueName: 'type', description: 'keep documents of this Memory type' },
|
|
2014
|
+
],
|
|
2015
|
+
example: 'wingfoil memory search --status approved --type task',
|
|
1215
2016
|
fn: memorySearchFn,
|
|
1216
2017
|
},
|
|
1217
2018
|
// P1.6 (task-045-memory-submit) — `mutates: true`: CLI `wingfoil memory submit <id>` + MCP Tool
|
|
1218
2019
|
// `memory.submit`. The id rides the bare `positional` seam (spec-008 §7); no flags, no options.
|
|
1219
|
-
memorySubmit: {
|
|
2020
|
+
memorySubmit: {
|
|
2021
|
+
name: 'memorySubmit',
|
|
2022
|
+
mutates: true,
|
|
2023
|
+
description: "move a document one step forward along its type's sequence — for the default machine, draft → pending",
|
|
2024
|
+
positional: { name: 'id', required: true, description: 'the document id, e.g. task-001-my-first-task' },
|
|
2025
|
+
example: 'wingfoil memory submit task-001-my-first-task',
|
|
2026
|
+
fn: memorySubmitFn,
|
|
2027
|
+
},
|
|
1220
2028
|
// P1.7 (task-046-memory-approve) — `mutates: true`: CLI `wingfoil memory approve <id> --reason
|
|
1221
2029
|
// <text>` + MCP Tool `memory.approve`. The id rides the bare `positional` seam (spec-008 §7);
|
|
1222
2030
|
// `--reason` is the one declared value option, `required` per spec-008 §2 / REQ-SEC-04 (the
|
|
@@ -1224,7 +2032,10 @@ exports.CORE_MODULES = [
|
|
|
1224
2032
|
memoryApprove: {
|
|
1225
2033
|
name: 'memoryApprove',
|
|
1226
2034
|
mutates: true,
|
|
1227
|
-
|
|
2035
|
+
description: 'pass a gate: move a document forward from a gated state (default machine: pending → approved)',
|
|
2036
|
+
positional: { name: 'id', required: true, description: 'the document id, e.g. task-001-my-first-task' },
|
|
2037
|
+
options: [{ name: 'reason', required: true, valueName: 'text', description: "why you approve it, recorded as the commit's Reason:; needs the approver role" }],
|
|
2038
|
+
example: 'wingfoil memory approve task-001-my-first-task --reason "Scope and acceptance criteria are clear."',
|
|
1228
2039
|
fn: memoryApproveFn,
|
|
1229
2040
|
},
|
|
1230
2041
|
// P1.8 (task-047-memory-reject) — `mutates: true`: CLI `wingfoil memory reject <id> --reason
|
|
@@ -1235,7 +2046,10 @@ exports.CORE_MODULES = [
|
|
|
1235
2046
|
memoryReject: {
|
|
1236
2047
|
name: 'memoryReject',
|
|
1237
2048
|
mutates: true,
|
|
1238
|
-
|
|
2049
|
+
description: "send a gated document back to its gate's reject target (default machine: pending → draft)",
|
|
2050
|
+
positional: { name: 'id', required: true, description: 'the document id, e.g. task-001-my-first-task' },
|
|
2051
|
+
options: [{ name: 'reason', required: true, valueName: 'text', description: "what must change before resubmitting, recorded as the commit's Reason: and as rejection_reason; needs the approver role" }],
|
|
2052
|
+
example: 'wingfoil memory reject task-001-my-first-task --reason "Add acceptance criteria before resubmitting."',
|
|
1239
2053
|
fn: memoryRejectFn,
|
|
1240
2054
|
},
|
|
1241
2055
|
// P1.9 (task-048-memory-deprecate) — `mutates: true`: CLI `wingfoil memory deprecate <id>
|
|
@@ -1246,18 +2060,57 @@ exports.CORE_MODULES = [
|
|
|
1246
2060
|
memoryDeprecate: {
|
|
1247
2061
|
name: 'memoryDeprecate',
|
|
1248
2062
|
mutates: true,
|
|
1249
|
-
|
|
2063
|
+
description: 'retire a document, from any state, to deprecated',
|
|
2064
|
+
positional: { name: 'id', required: true, description: 'the document id, e.g. task-001-my-first-task' },
|
|
2065
|
+
options: [{ name: 'reason', valueName: 'text', description: "why the document is retired, recorded as the commit's Reason: (not blank when given)" }],
|
|
2066
|
+
example: 'wingfoil memory deprecate dl-001-use-postgresql --reason "Superseded by the hosted-DB decision."',
|
|
1250
2067
|
fn: memoryDeprecateFn,
|
|
1251
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
|
+
},
|
|
1252
2102
|
},
|
|
1253
2103
|
},
|
|
1254
2104
|
{
|
|
1255
2105
|
name: 'directives',
|
|
2106
|
+
description: 'list the directives and the roles they apply to',
|
|
1256
2107
|
operations: {
|
|
1257
2108
|
directivesList: {
|
|
1258
2109
|
name: 'directivesList',
|
|
1259
2110
|
mutates: false,
|
|
1260
|
-
|
|
2111
|
+
description: 'list directives with the roles each is assigned to',
|
|
2112
|
+
options: [{ name: 'role', valueName: 'role', description: 'keep only the directives that apply to this role, globals included' }],
|
|
2113
|
+
example: 'wingfoil directives list --role developer',
|
|
1261
2114
|
fn: directivesListFn,
|
|
1262
2115
|
},
|
|
1263
2116
|
},
|
|
@@ -1267,17 +2120,152 @@ exports.CORE_MODULES = [
|
|
|
1267
2120
|
operations: {
|
|
1268
2121
|
// Self-named (operation name === module name) — the flat/no-verb `wingfoil paths [category]`
|
|
1269
2122
|
// form (see `deriveVerb`, `./registry.ts`), not `wingfoil paths paths`. `category` rides the
|
|
1270
|
-
// generic bare positional (task-026's seam)
|
|
1271
|
-
paths: {
|
|
2123
|
+
// generic bare positional (task-026's seam), declared as `positional` so `--help` names it.
|
|
2124
|
+
paths: {
|
|
2125
|
+
name: 'paths',
|
|
2126
|
+
mutates: false,
|
|
2127
|
+
description: 'print the resource paths declared in dna.yaml paths:',
|
|
2128
|
+
positional: { name: 'category', description: 'sources, tests, docs, config, governance, runs or health (omit it for the whole map)' },
|
|
2129
|
+
flags: [{ name: 'list', description: 'accepted for the planned drill-down view; it does not change the output yet' }],
|
|
2130
|
+
example: 'wingfoil paths sources',
|
|
2131
|
+
fn: pathsFn,
|
|
2132
|
+
},
|
|
1272
2133
|
},
|
|
1273
2134
|
},
|
|
1274
2135
|
{
|
|
1275
2136
|
name: 'workflow',
|
|
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)',
|
|
1276
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).
|
|
1277
2144
|
workflowList: {
|
|
1278
2145
|
name: 'workflowList',
|
|
1279
2146
|
mutates: false,
|
|
1280
|
-
|
|
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,
|
|
1281
2269
|
},
|
|
1282
2270
|
},
|
|
1283
2271
|
},
|
|
@@ -1296,6 +2284,7 @@ exports.CORE_MODULES = [
|
|
|
1296
2284
|
// the concurrently-edited `directives` block; `enumerateOperations` sorts, so position is inert.
|
|
1297
2285
|
{
|
|
1298
2286
|
name: 'directive',
|
|
2287
|
+
description: 'create, assign and remove custom directives',
|
|
1299
2288
|
operations: {
|
|
1300
2289
|
// The FIRST Directives-pillar mutation (P3.1) — `mutates: true`, so by construction an MCP Tool
|
|
1301
2290
|
// (`directive.create`) + CLI command (`wingfoil directive create`), and the third op the
|
|
@@ -1305,7 +2294,9 @@ exports.CORE_MODULES = [
|
|
|
1305
2294
|
directiveCreate: {
|
|
1306
2295
|
name: 'directiveCreate',
|
|
1307
2296
|
mutates: true,
|
|
1308
|
-
|
|
2297
|
+
description: 'create a custom directive from a scaffold',
|
|
2298
|
+
options: [{ name: 'name', required: true, valueName: 'name', description: 'the new directive, written to .wingfoil/directives/custom/<name>.md' }],
|
|
2299
|
+
example: 'wingfoil directive create --name api-style',
|
|
1309
2300
|
fn: directiveCreateFn,
|
|
1310
2301
|
},
|
|
1311
2302
|
// P3.2 (task-051-directive-assign) — on this SINGULAR module per dl-041 B, so `deriveVerb` yields
|
|
@@ -1313,10 +2304,15 @@ exports.CORE_MODULES = [
|
|
|
1313
2304
|
directiveAssign: {
|
|
1314
2305
|
name: 'directiveAssign',
|
|
1315
2306
|
mutates: true,
|
|
2307
|
+
description: 'assign one or more directives to a role in roles.yaml',
|
|
1316
2308
|
options: [
|
|
1317
|
-
{ name: 'directive', required: true },
|
|
1318
|
-
{ name: 'role', required: true },
|
|
2309
|
+
{ name: 'directive', required: true, valueName: 'name[,name...]', description: 'the directive(s) to assign, comma-separated' },
|
|
2310
|
+
{ name: 'role', required: true, valueName: 'role', description: 'the role to assign them to, as the committed dna.yaml declares it' },
|
|
1319
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)' }],
|
|
2315
|
+
example: 'wingfoil directive assign --directive api-style --role developer',
|
|
1320
2316
|
fn: directiveAssignFn,
|
|
1321
2317
|
},
|
|
1322
2318
|
// P3.3 (task-052-directive-remove) — on this SINGULAR module per dl-041 B, so `deriveVerb`
|
|
@@ -1324,7 +2320,14 @@ exports.CORE_MODULES = [
|
|
|
1324
2320
|
// no flags and no value options: the name rides the bare `positional` seam (spec-008 §7), like
|
|
1325
2321
|
// `memory submit <id>`. Completes REQ-SEC-07 for the directive surface (clause (a) via
|
|
1326
2322
|
// task-042's `requireCustomAsset`, clause (b) via `checkUnreferenced` — dl-030).
|
|
1327
|
-
directiveRemove: {
|
|
2323
|
+
directiveRemove: {
|
|
2324
|
+
name: 'directiveRemove',
|
|
2325
|
+
mutates: true,
|
|
2326
|
+
description: 'delete a custom directive',
|
|
2327
|
+
positional: { name: 'name', required: true, description: 'the custom directive to delete (built-in directives cannot be removed)' },
|
|
2328
|
+
example: 'wingfoil directive remove api-style',
|
|
2329
|
+
fn: directiveRemoveFn,
|
|
2330
|
+
},
|
|
1328
2331
|
},
|
|
1329
2332
|
},
|
|
1330
2333
|
];
|