@seanmars/tospec 0.19.0-beta.8 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -310
- package/README.md +69 -82
- package/assets/dashboard/app.js +11 -0
- package/assets/dashboard/style.css +7 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +89 -106
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/config.d.ts +9 -17
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +293 -145
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/dashboard.d.ts +57 -99
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +190 -313
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/decision.d.ts +38 -27
- package/dist/commands/decision.d.ts.map +1 -1
- package/dist/commands/decision.js +296 -128
- package/dist/commands/decision.js.map +1 -1
- package/dist/commands/metrics.d.ts +28 -51
- package/dist/commands/metrics.d.ts.map +1 -1
- package/dist/commands/metrics.js +62 -93
- package/dist/commands/metrics.js.map +1 -1
- package/dist/commands/shared-output.d.ts +12 -27
- package/dist/commands/shared-output.d.ts.map +1 -1
- package/dist/commands/shared-output.js +22 -45
- package/dist/commands/shared-output.js.map +1 -1
- package/dist/commands/show.d.ts +4 -7
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.js +23 -11
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/validate.d.ts +34 -58
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +225 -141
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/workflow/index.d.ts +1 -5
- package/dist/commands/workflow/index.d.ts.map +1 -1
- package/dist/commands/workflow/index.js +1 -5
- package/dist/commands/workflow/index.js.map +1 -1
- package/dist/commands/workflow/instructions.d.ts +14 -24
- package/dist/commands/workflow/instructions.d.ts.map +1 -1
- package/dist/commands/workflow/instructions.js +219 -128
- package/dist/commands/workflow/instructions.js.map +1 -1
- package/dist/commands/workflow/new-change.d.ts +2 -5
- package/dist/commands/workflow/new-change.d.ts.map +1 -1
- package/dist/commands/workflow/new-change.js +69 -32
- package/dist/commands/workflow/new-change.js.map +1 -1
- package/dist/commands/workflow/schemas.d.ts +1 -5
- package/dist/commands/workflow/schemas.d.ts.map +1 -1
- package/dist/commands/workflow/schemas.js +6 -17
- package/dist/commands/workflow/schemas.js.map +1 -1
- package/dist/commands/workflow/shared.d.ts +37 -42
- package/dist/commands/workflow/shared.d.ts.map +1 -1
- package/dist/commands/workflow/shared.js +23 -54
- package/dist/commands/workflow/shared.js.map +1 -1
- package/dist/commands/workflow/status.d.ts +7 -17
- package/dist/commands/workflow/status.d.ts.map +1 -1
- package/dist/commands/workflow/status.js +57 -72
- package/dist/commands/workflow/status.js.map +1 -1
- package/dist/commands/workflow/templates.d.ts +8 -8
- package/dist/commands/workflow/templates.d.ts.map +1 -1
- package/dist/commands/workflow/templates.js +32 -46
- package/dist/commands/workflow/templates.js.map +1 -1
- package/dist/core/archive.d.ts +22 -31
- package/dist/core/archive.d.ts.map +1 -1
- package/dist/core/archive.js +296 -283
- package/dist/core/archive.js.map +1 -1
- package/dist/core/artifact-graph/graph.d.ts +25 -42
- package/dist/core/artifact-graph/graph.d.ts.map +1 -1
- package/dist/core/artifact-graph/graph.js +45 -63
- package/dist/core/artifact-graph/graph.js.map +1 -1
- package/dist/core/artifact-graph/index.d.ts +1 -1
- package/dist/core/artifact-graph/index.d.ts.map +1 -1
- package/dist/core/artifact-graph/index.js +1 -1
- package/dist/core/artifact-graph/index.js.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +54 -120
- package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.js +129 -111
- package/dist/core/artifact-graph/instruction-loader.js.map +1 -1
- package/dist/core/artifact-graph/outputs.d.ts +9 -23
- package/dist/core/artifact-graph/outputs.d.ts.map +1 -1
- package/dist/core/artifact-graph/outputs.js +45 -38
- package/dist/core/artifact-graph/outputs.js.map +1 -1
- package/dist/core/artifact-graph/resolver.d.ts +36 -81
- package/dist/core/artifact-graph/resolver.d.ts.map +1 -1
- package/dist/core/artifact-graph/resolver.js +60 -101
- package/dist/core/artifact-graph/resolver.js.map +1 -1
- package/dist/core/artifact-graph/schema.d.ts +0 -6
- package/dist/core/artifact-graph/schema.d.ts.map +1 -1
- package/dist/core/artifact-graph/schema.js +7 -32
- package/dist/core/artifact-graph/schema.js.map +1 -1
- package/dist/core/artifact-graph/state.d.ts +1 -8
- package/dist/core/artifact-graph/state.d.ts.map +1 -1
- package/dist/core/artifact-graph/state.js +2 -17
- package/dist/core/artifact-graph/state.js.map +1 -1
- package/dist/core/artifact-graph/stub-detection.d.ts +6 -14
- package/dist/core/artifact-graph/stub-detection.d.ts.map +1 -1
- package/dist/core/artifact-graph/stub-detection.js +13 -16
- package/dist/core/artifact-graph/stub-detection.js.map +1 -1
- package/dist/core/artifact-graph/types.d.ts +4 -0
- package/dist/core/artifact-graph/types.d.ts.map +1 -1
- package/dist/core/artifact-graph/types.js +30 -10
- package/dist/core/artifact-graph/types.js.map +1 -1
- package/dist/core/available-tools.d.ts +3 -12
- package/dist/core/available-tools.d.ts.map +1 -1
- package/dist/core/available-tools.js +4 -13
- package/dist/core/available-tools.js.map +1 -1
- package/dist/core/change-metadata/schema.d.ts +1 -1
- package/dist/core/change-metadata/schema.d.ts.map +1 -1
- package/dist/core/change-metadata/schema.js +10 -7
- package/dist/core/change-metadata/schema.js.map +1 -1
- package/dist/core/change-presenter.d.ts +16 -27
- package/dist/core/change-presenter.d.ts.map +1 -1
- package/dist/core/change-presenter.js +53 -53
- package/dist/core/change-presenter.js.map +1 -1
- package/dist/core/change-status-policy.d.ts +4 -8
- package/dist/core/change-status-policy.d.ts.map +1 -1
- package/dist/core/change-status-policy.js +9 -17
- package/dist/core/change-status-policy.js.map +1 -1
- package/dist/core/codex-metrics.d.ts +25 -45
- package/dist/core/codex-metrics.d.ts.map +1 -1
- package/dist/core/codex-metrics.js +44 -88
- package/dist/core/codex-metrics.js.map +1 -1
- package/dist/core/codex-residue.d.ts +14 -15
- package/dist/core/codex-residue.d.ts.map +1 -1
- package/dist/core/codex-residue.js +18 -22
- package/dist/core/codex-residue.js.map +1 -1
- package/dist/core/command-generation/adapters/claude.d.ts +2 -9
- package/dist/core/command-generation/adapters/claude.d.ts.map +1 -1
- package/dist/core/command-generation/adapters/claude.js +2 -12
- package/dist/core/command-generation/adapters/claude.js.map +1 -1
- package/dist/core/command-generation/adapters/index.d.ts +1 -9
- package/dist/core/command-generation/adapters/index.d.ts.map +1 -1
- package/dist/core/command-generation/adapters/index.js +1 -9
- package/dist/core/command-generation/adapters/index.js.map +1 -1
- package/dist/core/command-generation/generator.d.ts +0 -17
- package/dist/core/command-generation/generator.d.ts.map +1 -1
- package/dist/core/command-generation/generator.js +0 -17
- package/dist/core/command-generation/generator.js.map +1 -1
- package/dist/core/command-generation/index.d.ts +2 -5
- package/dist/core/command-generation/index.d.ts.map +1 -1
- package/dist/core/command-generation/index.js +0 -9
- package/dist/core/command-generation/index.js.map +1 -1
- package/dist/core/command-generation/types.d.ts +10 -36
- package/dist/core/command-generation/types.d.ts.map +1 -1
- package/dist/core/command-generation/types.js +0 -6
- package/dist/core/command-generation/types.js.map +1 -1
- package/dist/core/command-generation/yaml.d.ts +3 -18
- package/dist/core/command-generation/yaml.d.ts.map +1 -1
- package/dist/core/command-generation/yaml.js +5 -23
- package/dist/core/command-generation/yaml.js.map +1 -1
- package/dist/core/config-prompts.d.ts +2 -4
- package/dist/core/config-prompts.d.ts.map +1 -1
- package/dist/core/config-prompts.js +2 -7
- package/dist/core/config-prompts.js.map +1 -1
- package/dist/core/config-schema.d.ts +7 -41
- package/dist/core/config-schema.d.ts.map +1 -1
- package/dist/core/config-schema.js +35 -74
- package/dist/core/config-schema.js.map +1 -1
- package/dist/core/config.d.ts +25 -49
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +22 -45
- package/dist/core/config.js.map +1 -1
- package/dist/core/dashboard-activity.d.ts +7 -9
- package/dist/core/dashboard-activity.d.ts.map +1 -1
- package/dist/core/dashboard-activity.js +26 -24
- package/dist/core/dashboard-activity.js.map +1 -1
- package/dist/core/dashboard-data.d.ts +35 -22
- package/dist/core/dashboard-data.d.ts.map +1 -1
- package/dist/core/dashboard-data.js +57 -72
- package/dist/core/dashboard-data.js.map +1 -1
- package/dist/core/global-config.d.ts +24 -53
- package/dist/core/global-config.d.ts.map +1 -1
- package/dist/core/global-config.js +38 -67
- package/dist/core/global-config.js.map +1 -1
- package/dist/core/init.d.ts +12 -28
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +90 -168
- package/dist/core/init.js.map +1 -1
- package/dist/core/list.d.ts.map +1 -1
- package/dist/core/list.js +80 -38
- package/dist/core/list.js.map +1 -1
- package/dist/core/local-server.d.ts +41 -83
- package/dist/core/local-server.d.ts.map +1 -1
- package/dist/core/local-server.js +53 -98
- package/dist/core/local-server.js.map +1 -1
- package/dist/core/markdown-render.d.ts +15 -23
- package/dist/core/markdown-render.d.ts.map +1 -1
- package/dist/core/markdown-render.js +25 -34
- package/dist/core/markdown-render.js.map +1 -1
- package/dist/core/migrate.d.ts +19 -16
- package/dist/core/migrate.d.ts.map +1 -1
- package/dist/core/migrate.js +151 -135
- package/dist/core/migrate.js.map +1 -1
- package/dist/core/parsers/change-parser.d.ts +7 -10
- package/dist/core/parsers/change-parser.d.ts.map +1 -1
- package/dist/core/parsers/change-parser.js +48 -56
- package/dist/core/parsers/change-parser.js.map +1 -1
- package/dist/core/parsers/markdown-parser.d.ts +8 -9
- package/dist/core/parsers/markdown-parser.d.ts.map +1 -1
- package/dist/core/parsers/markdown-parser.js +23 -30
- package/dist/core/parsers/markdown-parser.js.map +1 -1
- package/dist/core/parsers/requirement-blocks.d.ts +43 -15
- package/dist/core/parsers/requirement-blocks.d.ts.map +1 -1
- package/dist/core/parsers/requirement-blocks.js +142 -70
- package/dist/core/parsers/requirement-blocks.js.map +1 -1
- package/dist/core/parsers/requirement-text.d.ts +73 -79
- package/dist/core/parsers/requirement-text.d.ts.map +1 -1
- package/dist/core/parsers/requirement-text.js +137 -79
- package/dist/core/parsers/requirement-text.js.map +1 -1
- package/dist/core/parsers/spec-structure.d.ts.map +1 -1
- package/dist/core/parsers/spec-structure.js +10 -6
- package/dist/core/parsers/spec-structure.js.map +1 -1
- package/dist/core/profiles.d.ts +3 -10
- package/dist/core/profiles.d.ts.map +1 -1
- package/dist/core/profiles.js +5 -12
- package/dist/core/profiles.js.map +1 -1
- package/dist/core/project-config.d.ts +43 -44
- package/dist/core/project-config.d.ts.map +1 -1
- package/dist/core/project-config.js +107 -82
- package/dist/core/project-config.js.map +1 -1
- package/dist/core/project-layout.d.ts +9 -17
- package/dist/core/project-layout.d.ts.map +1 -1
- package/dist/core/project-layout.js +16 -26
- package/dist/core/project-layout.js.map +1 -1
- package/dist/core/root-selection.d.ts +8 -14
- package/dist/core/root-selection.d.ts.map +1 -1
- package/dist/core/root-selection.js +3 -6
- package/dist/core/root-selection.js.map +1 -1
- package/dist/core/rules.d.ts.map +1 -1
- package/dist/core/rules.js +2 -3
- package/dist/core/rules.js.map +1 -1
- package/dist/core/schema-names.d.ts +16 -0
- package/dist/core/schema-names.d.ts.map +1 -0
- package/dist/core/schema-names.js +16 -0
- package/dist/core/schema-names.js.map +1 -0
- package/dist/core/schemas/base.schema.d.ts.map +1 -1
- package/dist/core/schemas/base.schema.js +6 -12
- package/dist/core/schemas/base.schema.js.map +1 -1
- package/dist/core/schemas/change.schema.d.ts +8 -0
- package/dist/core/schemas/change.schema.d.ts.map +1 -1
- package/dist/core/schemas/change.schema.js +41 -10
- package/dist/core/schemas/change.schema.js.map +1 -1
- package/dist/core/shared/index.d.ts +2 -7
- package/dist/core/shared/index.d.ts.map +1 -1
- package/dist/core/shared/index.js +2 -7
- package/dist/core/shared/index.js.map +1 -1
- package/dist/core/shared/rules-generation.d.ts +5 -15
- package/dist/core/shared/rules-generation.d.ts.map +1 -1
- package/dist/core/shared/rules-generation.js +33 -37
- package/dist/core/shared/rules-generation.js.map +1 -1
- package/dist/core/shared/skill-generation.d.ts +28 -43
- package/dist/core/shared/skill-generation.d.ts.map +1 -1
- package/dist/core/shared/skill-generation.js +82 -51
- package/dist/core/shared/skill-generation.js.map +1 -1
- package/dist/core/shared/tool-detection.d.ts +35 -76
- package/dist/core/shared/tool-detection.d.ts.map +1 -1
- package/dist/core/shared/tool-detection.js +73 -93
- package/dist/core/shared/tool-detection.js.map +1 -1
- package/dist/core/skill-metrics.d.ts +36 -63
- package/dist/core/skill-metrics.d.ts.map +1 -1
- package/dist/core/skill-metrics.js +34 -73
- package/dist/core/skill-metrics.js.map +1 -1
- package/dist/core/spec-presenter.d.ts.map +1 -1
- package/dist/core/spec-presenter.js +5 -10
- package/dist/core/spec-presenter.js.map +1 -1
- package/dist/core/specs-apply.d.ts +16 -31
- package/dist/core/specs-apply.d.ts.map +1 -1
- package/dist/core/specs-apply.js +146 -195
- package/dist/core/specs-apply.js.map +1 -1
- package/dist/core/templates/fragments/interview.d.ts +2 -6
- package/dist/core/templates/fragments/interview.d.ts.map +1 -1
- package/dist/core/templates/fragments/interview.js +2 -6
- package/dist/core/templates/fragments/interview.js.map +1 -1
- package/dist/core/templates/fragments/next-step.d.ts +4 -8
- package/dist/core/templates/fragments/next-step.d.ts.map +1 -1
- package/dist/core/templates/fragments/next-step.js +4 -8
- package/dist/core/templates/fragments/next-step.js.map +1 -1
- package/dist/core/templates/fragments/validate.d.ts +13 -0
- package/dist/core/templates/fragments/validate.d.ts.map +1 -0
- package/dist/core/templates/fragments/validate.js +13 -0
- package/dist/core/templates/fragments/validate.js.map +1 -0
- package/dist/core/templates/fragments/verify.d.ts +9 -12
- package/dist/core/templates/fragments/verify.d.ts.map +1 -1
- package/dist/core/templates/fragments/verify.js +9 -12
- package/dist/core/templates/fragments/verify.js.map +1 -1
- package/dist/core/templates/index.d.ts +0 -6
- package/dist/core/templates/index.d.ts.map +1 -1
- package/dist/core/templates/index.js +0 -7
- package/dist/core/templates/index.js.map +1 -1
- package/dist/core/templates/skill-templates.d.ts +1 -5
- package/dist/core/templates/skill-templates.d.ts.map +1 -1
- package/dist/core/templates/skill-templates.js +0 -5
- package/dist/core/templates/skill-templates.js.map +1 -1
- package/dist/core/templates/types.d.ts +3 -7
- package/dist/core/templates/types.d.ts.map +1 -1
- package/dist/core/templates/types.js +0 -3
- package/dist/core/templates/types.js.map +1 -1
- package/dist/core/templates/workflows/apply.d.ts +3 -9
- package/dist/core/templates/workflows/apply.d.ts.map +1 -1
- package/dist/core/templates/workflows/apply.js +11 -13
- package/dist/core/templates/workflows/apply.js.map +1 -1
- package/dist/core/templates/workflows/archive.d.ts +0 -6
- package/dist/core/templates/workflows/archive.d.ts.map +1 -1
- package/dist/core/templates/workflows/archive.js +16 -5
- package/dist/core/templates/workflows/archive.js.map +1 -1
- package/dist/core/templates/workflows/decision.js +3 -3
- package/dist/core/templates/workflows/decision.js.map +1 -1
- package/dist/core/templates/workflows/explore.js +1 -1
- package/dist/core/templates/workflows/grill.d.ts.map +1 -1
- package/dist/core/templates/workflows/grill.js +0 -2
- package/dist/core/templates/workflows/grill.js.map +1 -1
- package/dist/core/templates/workflows/issue.d.ts +0 -6
- package/dist/core/templates/workflows/issue.d.ts.map +1 -1
- package/dist/core/templates/workflows/issue.js +3 -2
- package/dist/core/templates/workflows/issue.js.map +1 -1
- package/dist/core/templates/workflows/propose.d.ts +0 -6
- package/dist/core/templates/workflows/propose.d.ts.map +1 -1
- package/dist/core/templates/workflows/propose.js +2 -2
- package/dist/core/templates/workflows/propose.js.map +1 -1
- package/dist/core/templates/workflows/sync.d.ts +2 -8
- package/dist/core/templates/workflows/sync.d.ts.map +1 -1
- package/dist/core/templates/workflows/sync.js +4 -3
- package/dist/core/templates/workflows/sync.js.map +1 -1
- package/dist/core/templates/workflows/update.d.ts +0 -6
- package/dist/core/templates/workflows/update.d.ts.map +1 -1
- package/dist/core/templates/workflows/update.js +9 -2
- package/dist/core/templates/workflows/update.js.map +1 -1
- package/dist/core/update.d.ts +10 -36
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +59 -126
- package/dist/core/update.js.map +1 -1
- package/dist/core/user-state-migration.d.ts +13 -15
- package/dist/core/user-state-migration.d.ts.map +1 -1
- package/dist/core/user-state-migration.js +16 -20
- package/dist/core/user-state-migration.js.map +1 -1
- package/dist/core/validation/constants.d.ts +4 -10
- package/dist/core/validation/constants.d.ts.map +1 -1
- package/dist/core/validation/constants.js +25 -25
- package/dist/core/validation/constants.js.map +1 -1
- package/dist/core/validation/prose-length.d.ts +15 -0
- package/dist/core/validation/prose-length.d.ts.map +1 -0
- package/dist/core/validation/prose-length.js +29 -0
- package/dist/core/validation/prose-length.js.map +1 -0
- package/dist/core/validation/purpose-placeholder.d.ts +9 -16
- package/dist/core/validation/purpose-placeholder.d.ts.map +1 -1
- package/dist/core/validation/purpose-placeholder.js +30 -44
- package/dist/core/validation/purpose-placeholder.js.map +1 -1
- package/dist/core/validation/section-validator.d.ts +4 -4
- package/dist/core/validation/section-validator.d.ts.map +1 -1
- package/dist/core/validation/section-validator.js +30 -12
- package/dist/core/validation/section-validator.js.map +1 -1
- package/dist/core/validation/task-numbering.d.ts +6 -3
- package/dist/core/validation/task-numbering.d.ts.map +1 -1
- package/dist/core/validation/task-numbering.js +23 -11
- package/dist/core/validation/task-numbering.js.map +1 -1
- package/dist/core/validation/types.d.ts +18 -0
- package/dist/core/validation/types.d.ts.map +1 -1
- package/dist/core/validation/types.js +12 -1
- package/dist/core/validation/types.js.map +1 -1
- package/dist/core/validation/validator.d.ts +42 -68
- package/dist/core/validation/validator.d.ts.map +1 -1
- package/dist/core/validation/validator.js +468 -285
- package/dist/core/validation/validator.js.map +1 -1
- package/dist/prompts/searchable-multi-select.d.ts +3 -8
- package/dist/prompts/searchable-multi-select.d.ts.map +1 -1
- package/dist/prompts/searchable-multi-select.js +16 -39
- package/dist/prompts/searchable-multi-select.js.map +1 -1
- package/dist/utils/change-metadata.d.ts +11 -50
- package/dist/utils/change-metadata.d.ts.map +1 -1
- package/dist/utils/change-metadata.js +48 -67
- package/dist/utils/change-metadata.js.map +1 -1
- package/dist/utils/change-utils.d.ts +24 -76
- package/dist/utils/change-utils.d.ts.map +1 -1
- package/dist/utils/change-utils.js +95 -142
- package/dist/utils/change-utils.js.map +1 -1
- package/dist/utils/file-lock.d.ts +39 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +149 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/file-system.d.ts +12 -32
- package/dist/utils/file-system.d.ts.map +1 -1
- package/dist/utils/file-system.js +16 -40
- package/dist/utils/file-system.js.map +1 -1
- package/dist/utils/frontmatter.d.ts +7 -11
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +11 -11
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/interactive.d.ts +4 -9
- package/dist/utils/interactive.d.ts.map +1 -1
- package/dist/utils/interactive.js +2 -4
- package/dist/utils/interactive.js.map +1 -1
- package/dist/utils/item-discovery.d.ts +10 -20
- package/dist/utils/item-discovery.d.ts.map +1 -1
- package/dist/utils/item-discovery.js +31 -55
- package/dist/utils/item-discovery.js.map +1 -1
- package/dist/utils/link.d.ts +9 -18
- package/dist/utils/link.d.ts.map +1 -1
- package/dist/utils/link.js +9 -18
- package/dist/utils/link.js.map +1 -1
- package/dist/utils/requirement-diff.d.ts +13 -23
- package/dist/utils/requirement-diff.d.ts.map +1 -1
- package/dist/utils/requirement-diff.js +13 -23
- package/dist/utils/requirement-diff.js.map +1 -1
- package/dist/utils/spec-files.d.ts +7 -20
- package/dist/utils/spec-files.d.ts.map +1 -1
- package/dist/utils/spec-files.js +26 -51
- package/dist/utils/spec-files.js.map +1 -1
- package/dist/utils/task-progress.d.ts +11 -9
- package/dist/utils/task-progress.d.ts.map +1 -1
- package/dist/utils/task-progress.js +53 -32
- package/dist/utils/task-progress.js.map +1 -1
- package/dist/utils/timestamp.d.ts +5 -8
- package/dist/utils/timestamp.d.ts.map +1 -1
- package/dist/utils/timestamp.js +5 -8
- package/dist/utils/timestamp.js.map +1 -1
- package/package.json +2 -3
- package/schemas/issue/schema.yaml +8 -1
- package/schemas/issue/templates/spec.md +20 -3
- package/schemas/sdd/schema.yaml +20 -1
- package/schemas/sdd/templates/spec.md +20 -3
package/dist/commands/config.js
CHANGED
|
@@ -5,14 +5,31 @@ import { getGlobalConfigPath, getEffectiveConfigPath, getGlobalConfig, saveGloba
|
|
|
5
5
|
import * as yaml from 'yaml';
|
|
6
6
|
import { getNestedValue, setNestedValue, deleteNestedValue, coerceValue, validateConfigKeyPath, hasUnsafeKeySegment, validateConfig, DEFAULT_CONFIG, } from '../core/config-schema.js';
|
|
7
7
|
import { ALL_WORKFLOWS, getProfileWorkflows } from '../core/profiles.js';
|
|
8
|
+
import { describeWorkflowReferenceGaps, findWorkflowReferenceGaps, } from '../core/shared/skill-generation.js';
|
|
8
9
|
import { TOSPEC_DIR_NAME } from '../core/config.js';
|
|
10
|
+
import { stripBom } from '../core/parsers/requirement-text.js';
|
|
9
11
|
import { UpdateCommand } from '../core/update.js';
|
|
10
|
-
import { isPromptCancellationError, asErrorMessage, CliAbortError, emitSuccess, } from './shared-output.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
import { isPromptCancellationError, asErrorMessage, CliAbortError, emitSuccess, emitFailureStatus, } from './shared-output.js';
|
|
13
|
+
/**
|
|
14
|
+
* The `--json` null-shapes, one per subcommand, keyed by the data key each
|
|
15
|
+
* success payload carries. Shared by the commander-level failure table in
|
|
16
|
+
* cli/index.ts and the catch sites here, so a parse-time failure (`--bogus`)
|
|
17
|
+
* mirrors the same shape an action-time failure does.
|
|
18
|
+
*/
|
|
19
|
+
export const CONFIG_FAILURE_PAYLOADS = {
|
|
20
|
+
path: { path: null },
|
|
21
|
+
list: { config: null, profileSettings: null },
|
|
22
|
+
get: { get: null },
|
|
23
|
+
set: { set: null },
|
|
24
|
+
unset: { unset: null },
|
|
25
|
+
reset: { config: null },
|
|
26
|
+
profile: { profile: null },
|
|
27
|
+
};
|
|
28
|
+
/** The parent `config` command has no data of its own; its preAction abort reports this. */
|
|
29
|
+
export const CONFIG_SCOPE_FAILURE_PAYLOAD = { config: null };
|
|
30
|
+
// Keyed by WorkflowId, not string: this list runs parallel to WORKFLOW_DEFS, and
|
|
31
|
+
// under a `string` key a renamed workflow drifts silently because the lookup
|
|
32
|
+
// below falls back instead of failing. Typed, the next rename is a compile error.
|
|
16
33
|
const WORKFLOW_PROMPT_META = {
|
|
17
34
|
explore: {
|
|
18
35
|
name: 'Explore ideas',
|
|
@@ -55,9 +72,6 @@ const WORKFLOW_PROMPT_META = {
|
|
|
55
72
|
description: 'Run a spec/code sync when warranted, then finalize and archive',
|
|
56
73
|
},
|
|
57
74
|
};
|
|
58
|
-
/**
|
|
59
|
-
* Resolve the effective current profile state from global config defaults.
|
|
60
|
-
*/
|
|
61
75
|
export function resolveCurrentProfileState(config) {
|
|
62
76
|
const profile = config.profile || 'core';
|
|
63
77
|
const workflows = [
|
|
@@ -65,28 +79,19 @@ export function resolveCurrentProfileState(config) {
|
|
|
65
79
|
];
|
|
66
80
|
return { profile, workflows };
|
|
67
81
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Derive profile type from selected workflows.
|
|
70
|
-
*/
|
|
71
82
|
export function deriveProfileFromWorkflowSelection(selectedWorkflows) {
|
|
72
83
|
const isCoreMatch = selectedWorkflows.length === ALL_WORKFLOWS.length &&
|
|
73
84
|
ALL_WORKFLOWS.every((w) => selectedWorkflows.includes(w));
|
|
74
85
|
return isCoreMatch ? 'core' : 'custom';
|
|
75
86
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Format a compact workflow summary for the profile header.
|
|
78
|
-
*/
|
|
79
87
|
export function formatWorkflowSummary(workflows, profile) {
|
|
80
88
|
return `${workflows.length} selected (${profile})`;
|
|
81
89
|
}
|
|
82
90
|
/**
|
|
83
|
-
* The finding that a stored `workflows` list will not be used, or null.
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* at the moment of setting; `config list` then showed the list again with
|
|
88
|
-
* nothing to say it does not apply (round 4 report 16). Worded to match
|
|
89
|
-
* `update`'s `update_workflows_ignored`, so the two commands say one thing.
|
|
91
|
+
* The finding that a stored `workflows` list will not be used, or null. Only the
|
|
92
|
+
* `custom` profile consults it, so under any other profile it is stored,
|
|
93
|
+
* validated, displayed — and ignored. Worded to match `update`'s
|
|
94
|
+
* `update_workflows_ignored`.
|
|
90
95
|
*/
|
|
91
96
|
function ignoredWorkflowsDiagnostic(config) {
|
|
92
97
|
const profile = config.profile ?? 'core';
|
|
@@ -101,6 +106,25 @@ function ignoredWorkflowsDiagnostic(config) {
|
|
|
101
106
|
fix: 'Run "tospec config profile custom" to apply it, or "tospec config unset workflows" to drop it.',
|
|
102
107
|
};
|
|
103
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* The finding that the selected workflows' own instructions name workflows the
|
|
111
|
+
* selection leaves out, or null. Raised here as well as at install time because
|
|
112
|
+
* this is where the selection is made: `config set workflows
|
|
113
|
+
* "propose,apply,archive"` otherwise reports success while the resulting
|
|
114
|
+
* `tospec-archive` still tells an agent to run an uninstalled `tospec-sync`.
|
|
115
|
+
*/
|
|
116
|
+
function workflowReferenceDiagnostic(config) {
|
|
117
|
+
const gaps = findWorkflowReferenceGaps(resolveListedWorkflows(config));
|
|
118
|
+
if (gaps.length === 0)
|
|
119
|
+
return null;
|
|
120
|
+
const missing = [...new Set(gaps.map((gap) => gap.missing))].sort();
|
|
121
|
+
return {
|
|
122
|
+
severity: 'warning',
|
|
123
|
+
code: 'config_workflow_reference_missing',
|
|
124
|
+
message: `Selected skills point at workflows this profile leaves out: ${describeWorkflowReferenceGaps(gaps)}.`,
|
|
125
|
+
fix: `Add them with "tospec config set workflows" (missing: ${missing.join(', ')}), or expect agents to hit instructions they cannot follow.`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
104
128
|
function stableWorkflowOrder(workflows) {
|
|
105
129
|
const seen = new Set();
|
|
106
130
|
const ordered = [];
|
|
@@ -120,9 +144,6 @@ function stableWorkflowOrder(workflows) {
|
|
|
120
144
|
}
|
|
121
145
|
return ordered;
|
|
122
146
|
}
|
|
123
|
-
/**
|
|
124
|
-
* Build a user-facing diff summary between two profile states.
|
|
125
|
-
*/
|
|
126
147
|
export function diffProfileState(before, after) {
|
|
127
148
|
const lines = [];
|
|
128
149
|
if (before.profile !== after.profile) {
|
|
@@ -150,33 +171,26 @@ export function diffProfileState(before, after) {
|
|
|
150
171
|
};
|
|
151
172
|
}
|
|
152
173
|
/**
|
|
153
|
-
* The config file's raw contents,
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* Reads the file that was actually loaded, not the write target: the two differ
|
|
157
|
-
* until a legacy install migrates.
|
|
174
|
+
* The config file's raw contents, only to tell an explicit value from a default —
|
|
175
|
+
* `getGlobalConfig()` has merged the two. Reads the file actually loaded, not
|
|
176
|
+
* the write target: the two differ until a legacy install migrates.
|
|
158
177
|
*/
|
|
159
178
|
function rawGlobalConfig() {
|
|
160
179
|
try {
|
|
161
180
|
const configPath = getEffectiveConfigPath();
|
|
162
181
|
if (fs.existsSync(configPath)) {
|
|
163
|
-
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
182
|
+
return JSON.parse(stripBom(fs.readFileSync(configPath, 'utf-8')));
|
|
164
183
|
}
|
|
165
184
|
}
|
|
166
185
|
catch {
|
|
167
|
-
// Unreadable
|
|
168
|
-
// failing a read-only listing.
|
|
186
|
+
// Unreadable: treat every value as a default rather than fail a listing.
|
|
169
187
|
}
|
|
170
188
|
return {};
|
|
171
189
|
}
|
|
172
190
|
/**
|
|
173
191
|
* The workflows a profile resolves to — `core` expands to the full set, any
|
|
174
|
-
* other profile uses its explicit list.
|
|
175
|
-
*
|
|
176
|
-
* Shared by the text and JSON branches of `config list`. The JSON branch used to
|
|
177
|
-
* report only the stored config, leaving a caller unable to turn
|
|
178
|
-
* `profile: "core"` into the workflow names the text branch prints; deriving
|
|
179
|
-
* both from here is what keeps the two answers the same.
|
|
192
|
+
* other profile uses its explicit list. Shared by both branches of `config
|
|
193
|
+
* list`, so a `--json` caller can turn `profile: "core"` into workflow names.
|
|
180
194
|
*/
|
|
181
195
|
function resolveListedWorkflows(config) {
|
|
182
196
|
if (config.profile === 'core')
|
|
@@ -186,34 +200,40 @@ function resolveListedWorkflows(config) {
|
|
|
186
200
|
function printConfigProfileApplyGuidance() {
|
|
187
201
|
console.log('Config updated. Run `tospec update` in your projects to apply.');
|
|
188
202
|
}
|
|
189
|
-
/**
|
|
190
|
-
* Register the config command and all its subcommands.
|
|
191
|
-
*
|
|
192
|
-
* @param program - The Commander program instance
|
|
193
|
-
*/
|
|
194
203
|
export function registerConfigCommand(program) {
|
|
195
204
|
const configCmd = program
|
|
196
205
|
.command('config')
|
|
197
206
|
.description('View and modify global tospec configuration')
|
|
198
207
|
.option('--scope <scope>', 'Config scope (only "global" supported currently)')
|
|
199
|
-
.hook('preAction', (thisCommand) => {
|
|
208
|
+
.hook('preAction', (thisCommand, actionCommand) => {
|
|
200
209
|
const opts = thisCommand.opts();
|
|
201
210
|
if (opts.scope && opts.scope !== 'global') {
|
|
202
|
-
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
|
|
211
|
+
const message = 'Project-local config is not yet implemented';
|
|
212
|
+
// The subcommand's --json, not the parent's: `config --scope project
|
|
213
|
+
// get x --json` used to abort with nothing on stdout, so an agent saw
|
|
214
|
+
// exit 1 and no document at all.
|
|
215
|
+
if (actionCommand.opts().json) {
|
|
216
|
+
emitFailureStatus(CONFIG_FAILURE_PAYLOADS[actionCommand.name()] ?? CONFIG_SCOPE_FAILURE_PAYLOAD, { severity: 'error', code: 'config_scope_unsupported', message, fix: 'Omit --scope, or pass --scope global.' });
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.error(`Error: ${message}`);
|
|
220
|
+
}
|
|
221
|
+
// Not `process.exit(1)`: exiting here kills the process before stderr
|
|
222
|
+
// has flushed.
|
|
223
|
+
throw new CliAbortError(message);
|
|
207
224
|
}
|
|
208
225
|
});
|
|
209
|
-
// config path
|
|
210
226
|
configCmd
|
|
211
227
|
.command('path')
|
|
212
228
|
.description('Show config file location')
|
|
213
|
-
.
|
|
229
|
+
.option('--json', 'Output as JSON')
|
|
230
|
+
.action((options) => {
|
|
231
|
+
if (options.json) {
|
|
232
|
+
emitSuccess({ path: getGlobalConfigPath() });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
214
235
|
console.log(getGlobalConfigPath());
|
|
215
236
|
});
|
|
216
|
-
// config list
|
|
217
237
|
configCmd
|
|
218
238
|
.command('list')
|
|
219
239
|
.description('Show all current settings')
|
|
@@ -221,22 +241,18 @@ export function registerConfigCommand(program) {
|
|
|
221
241
|
.action((options) => {
|
|
222
242
|
const config = getGlobalConfig();
|
|
223
243
|
if (options.json) {
|
|
224
|
-
// Nested, not spread
|
|
244
|
+
// Nested, not spread: the schema is `.passthrough()` and
|
|
225
245
|
// `config set --allow-unknown` writes any top-level key, so spreading
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
// Giving the config its own key is what makes that structurally
|
|
230
|
-
// impossible instead of patching one field name at a time.
|
|
231
|
-
// See tospec/decisions/20260916_121544-config-list-json-nests-under-config-key.md
|
|
246
|
+
// lets user data shadow the envelope's reserved fields (`version`,
|
|
247
|
+
// `root`, `status`).
|
|
248
|
+
// tospec/decisions/20260916_121544-config-list-json-nests-under-config-key.md
|
|
232
249
|
//
|
|
233
|
-
// `profileSettings` mirrors
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const ignored = ignoredWorkflowsDiagnostic(config);
|
|
250
|
+
// `profileSettings` mirrors the human branch below, the only other place
|
|
251
|
+
// saying which workflows `profile: "core"` resolves to.
|
|
252
|
+
const listStatus = [
|
|
253
|
+
ignoredWorkflowsDiagnostic(config),
|
|
254
|
+
workflowReferenceDiagnostic(config),
|
|
255
|
+
].filter((entry) => entry !== null);
|
|
240
256
|
emitSuccess({
|
|
241
257
|
config,
|
|
242
258
|
profileSettings: {
|
|
@@ -244,22 +260,19 @@ export function registerConfigCommand(program) {
|
|
|
244
260
|
explicit: rawGlobalConfig().profile !== undefined,
|
|
245
261
|
workflows: resolveListedWorkflows(config),
|
|
246
262
|
},
|
|
247
|
-
...(
|
|
263
|
+
...(listStatus.length ? { status: listStatus } : {}),
|
|
248
264
|
});
|
|
249
265
|
}
|
|
250
266
|
else {
|
|
251
267
|
const rawConfig = rawGlobalConfig();
|
|
252
268
|
console.log(yaml.stringify(config).trimEnd());
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
// resolved one below — with nothing saying the first is not used.
|
|
257
|
-
// `config set` said so once, at the moment of setting, and never again.
|
|
269
|
+
// The dump above is the stored config, so under a profile that ignores
|
|
270
|
+
// `workflows` it and the resolved list below are two different answers
|
|
271
|
+
// for one key; this marks which is which.
|
|
258
272
|
const ignored = ignoredWorkflowsDiagnostic(config);
|
|
259
273
|
if (ignored) {
|
|
260
274
|
console.log(` ^ ${ignored.message}`);
|
|
261
275
|
}
|
|
262
|
-
// Annotate profile settings
|
|
263
276
|
const profileSource = rawConfig.profile !== undefined ? '(explicit)' : '(default)';
|
|
264
277
|
console.log(`\nProfile settings:`);
|
|
265
278
|
console.log(` profile: ${config.profile} ${profileSource}`);
|
|
@@ -275,41 +288,74 @@ export function registerConfigCommand(program) {
|
|
|
275
288
|
console.log();
|
|
276
289
|
console.log(ignored.fix);
|
|
277
290
|
}
|
|
291
|
+
const referenceGap = workflowReferenceDiagnostic(config);
|
|
292
|
+
if (referenceGap) {
|
|
293
|
+
console.log();
|
|
294
|
+
console.log(`Warning: ${referenceGap.message}`);
|
|
295
|
+
if (referenceGap.fix)
|
|
296
|
+
console.log(referenceGap.fix);
|
|
297
|
+
}
|
|
278
298
|
}
|
|
279
299
|
});
|
|
280
|
-
// config get
|
|
281
300
|
configCmd
|
|
282
301
|
.command('get <key>')
|
|
283
302
|
.description('Get a specific value (raw, scriptable)')
|
|
284
303
|
.option('--allow-unknown', 'Allow reading unknown keys')
|
|
304
|
+
.option('--json', 'Output as JSON')
|
|
285
305
|
.action((key, options) => {
|
|
286
306
|
const allowUnknown = Boolean(options.allowUnknown);
|
|
287
307
|
const keyValidation = validateConfigKeyPath(key);
|
|
288
|
-
// Paired with the same flag on `set
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
// exists to feed — refused to read it back.
|
|
292
|
-
//
|
|
293
|
-
// Relaxes the known-key check only, never the prototype-safety one; the
|
|
294
|
-
// same split `set` makes, for the same reason.
|
|
308
|
+
// Paired with the same flag on `set`: without it a key written with
|
|
309
|
+
// --allow-unknown could be listed and unset but never read back. Relaxes
|
|
310
|
+
// the known-key check only, never the prototype-safety one.
|
|
295
311
|
const unsafeKey = hasUnsafeKeySegment(key);
|
|
296
312
|
if (!keyValidation.valid && (!allowUnknown || unsafeKey)) {
|
|
297
313
|
const reason = keyValidation.reason ? ` ${keyValidation.reason}.` : '';
|
|
298
|
-
|
|
299
|
-
console.error('Use "tospec config list" to see available keys.');
|
|
314
|
+
const fixLines = ['Use "tospec config list" to see available keys.'];
|
|
300
315
|
if (!allowUnknown && !unsafeKey) {
|
|
301
|
-
|
|
316
|
+
fixLines.push('Pass --allow-unknown to read a key set that way.');
|
|
317
|
+
}
|
|
318
|
+
if (options.json) {
|
|
319
|
+
emitFailureStatus({ get: null }, {
|
|
320
|
+
severity: 'error',
|
|
321
|
+
code: 'config_error',
|
|
322
|
+
message: `Invalid configuration key "${key}".${reason}`,
|
|
323
|
+
fix: fixLines.join('\n'),
|
|
324
|
+
});
|
|
325
|
+
return;
|
|
302
326
|
}
|
|
327
|
+
console.error(`Error: Invalid configuration key "${key}".${reason}`);
|
|
328
|
+
for (const line of fixLines)
|
|
329
|
+
console.error(line);
|
|
303
330
|
process.exitCode = 1;
|
|
304
331
|
return;
|
|
305
332
|
}
|
|
306
333
|
const config = getGlobalConfig();
|
|
307
334
|
const value = getNestedValue(config, key);
|
|
308
335
|
if (value === undefined) {
|
|
336
|
+
if (options.json) {
|
|
337
|
+
emitFailureStatus({ get: null }, {
|
|
338
|
+
severity: 'error',
|
|
339
|
+
code: 'config_error',
|
|
340
|
+
message: `Key "${key}" is not set.`,
|
|
341
|
+
// `workflows` is a list, so the generic `<value>` placeholder
|
|
342
|
+
// hides the one thing a caller needs: the comma-separated form.
|
|
343
|
+
fix: key === 'workflows'
|
|
344
|
+
? `tospec config set workflows "${ALL_WORKFLOWS.slice(0, 3).join(',')}"`
|
|
345
|
+
: `tospec config set ${key} <value>`,
|
|
346
|
+
});
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
309
349
|
console.error(`Error: Key "${key}" is not set.`);
|
|
310
350
|
process.exitCode = 1;
|
|
311
351
|
return;
|
|
312
352
|
}
|
|
353
|
+
if (options.json) {
|
|
354
|
+
// The value as stored, not stringified: the human branch below exists
|
|
355
|
+
// to be scriptable in a shell, which JSON does not need.
|
|
356
|
+
emitSuccess({ get: { key, value } });
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
313
359
|
if (typeof value === 'object' && value !== null) {
|
|
314
360
|
console.log(JSON.stringify(value));
|
|
315
361
|
}
|
|
@@ -317,13 +363,25 @@ export function registerConfigCommand(program) {
|
|
|
317
363
|
console.log(String(value));
|
|
318
364
|
}
|
|
319
365
|
});
|
|
320
|
-
// config set
|
|
321
366
|
configCmd
|
|
322
367
|
.command('set <key> <value>')
|
|
323
368
|
.description('Set a value (auto-coerce types)')
|
|
324
369
|
.option('--string', 'Force value to be stored as string')
|
|
325
370
|
.option('--allow-unknown', 'Allow setting unknown keys')
|
|
371
|
+
.option('--json', 'Output as JSON')
|
|
326
372
|
.action((key, value, options) => {
|
|
373
|
+
/** Every rejection below shares this null-shape, per the envelope contract. */
|
|
374
|
+
const fail = (message, fix) => {
|
|
375
|
+
if (options.json) {
|
|
376
|
+
emitFailureStatus(CONFIG_FAILURE_PAYLOADS.set, { severity: 'error', code: 'config_error', message, ...(fix ? { fix } : {}) });
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
console.error(`Error: ${message}`);
|
|
380
|
+
if (fix)
|
|
381
|
+
for (const line of fix.split('\n'))
|
|
382
|
+
console.error(line);
|
|
383
|
+
process.exitCode = 1;
|
|
384
|
+
};
|
|
327
385
|
const allowUnknown = Boolean(options.allowUnknown);
|
|
328
386
|
const keyValidation = validateConfigKeyPath(key);
|
|
329
387
|
// --allow-unknown relaxes the known-key check, but never the
|
|
@@ -331,86 +389,131 @@ export function registerConfigCommand(program) {
|
|
|
331
389
|
const unsafeKey = hasUnsafeKeySegment(key);
|
|
332
390
|
if (!keyValidation.valid && (!allowUnknown || unsafeKey)) {
|
|
333
391
|
const reason = keyValidation.reason ? ` ${keyValidation.reason}.` : '';
|
|
334
|
-
|
|
335
|
-
console.error('Use "tospec config list" to see available keys.');
|
|
392
|
+
const fixLines = ['Use "tospec config list" to see available keys.'];
|
|
336
393
|
if (!allowUnknown && !unsafeKey) {
|
|
337
|
-
|
|
394
|
+
fixLines.push('Pass --allow-unknown to bypass this check.');
|
|
338
395
|
}
|
|
339
|
-
|
|
396
|
+
fail(`Invalid configuration key "${key}".${reason}`, fixLines.join('\n'));
|
|
340
397
|
return;
|
|
341
398
|
}
|
|
342
399
|
const config = getGlobalConfig();
|
|
343
400
|
const coercedValue = coerceValue(value, options.string || false, key);
|
|
344
|
-
//
|
|
401
|
+
// Validate a copy before touching the config that is saved.
|
|
345
402
|
const newConfig = JSON.parse(JSON.stringify(config));
|
|
346
403
|
setNestedValue(newConfig, key, coercedValue);
|
|
347
|
-
// Validate the new config
|
|
348
404
|
const validation = validateConfig(newConfig);
|
|
349
405
|
if (!validation.success) {
|
|
350
|
-
|
|
351
|
-
process.exitCode = 1;
|
|
406
|
+
fail(`Invalid configuration - ${validation.error}`);
|
|
352
407
|
return;
|
|
353
408
|
}
|
|
354
409
|
// The schema types `workflows` as string[], so a misspelled id passes it
|
|
355
|
-
// and is
|
|
356
|
-
//
|
|
357
|
-
// this is the only point that still has the word the user typed.
|
|
410
|
+
// and is later dropped by `update`'s filter against ALL_WORKFLOWS. This is
|
|
411
|
+
// the last point that still has the word the user typed.
|
|
358
412
|
if (key === 'workflows' && Array.isArray(coercedValue)) {
|
|
359
413
|
const unknown = coercedValue.filter((entry) => !ALL_WORKFLOWS.includes(String(entry)));
|
|
360
414
|
if (unknown.length > 0) {
|
|
361
|
-
|
|
362
|
-
console.error(`Available: ${ALL_WORKFLOWS.join(', ')}`);
|
|
363
|
-
process.exitCode = 1;
|
|
415
|
+
fail(`Unknown workflow(s): ${unknown.join(', ')}`, `Available: ${ALL_WORKFLOWS.join(', ')}`);
|
|
364
416
|
return;
|
|
365
417
|
}
|
|
366
418
|
}
|
|
367
|
-
// Apply changes and save
|
|
368
419
|
setNestedValue(config, key, coercedValue);
|
|
369
420
|
saveGlobalConfig(config);
|
|
421
|
+
// Only `custom` reads `workflows`; under `core` getProfileWorkflows
|
|
422
|
+
// discards it. Said alongside the confirmation, not instead of it: the
|
|
423
|
+
// value genuinely was stored, it is just not what `update` acts on.
|
|
424
|
+
const profile = config.profile ?? 'core';
|
|
425
|
+
const workflowsIgnored = key === 'workflows' && profile !== 'custom';
|
|
426
|
+
// The mirror image: `config profile custom` refuses without a list, but
|
|
427
|
+
// `set` is the raw write and stays permissive — so it has to say what
|
|
428
|
+
// that write means, or the next `init` installs zero skills unannounced.
|
|
429
|
+
const storedWorkflows = config.workflows;
|
|
430
|
+
const profileNeedsWorkflows = key === 'profile' &&
|
|
431
|
+
coercedValue === 'custom' &&
|
|
432
|
+
(!storedWorkflows || storedWorkflows.length === 0);
|
|
433
|
+
const warnings = [];
|
|
434
|
+
if (workflowsIgnored) {
|
|
435
|
+
warnings.push({
|
|
436
|
+
severity: 'warning',
|
|
437
|
+
code: 'config_workflows_ignored',
|
|
438
|
+
message: `Profile '${profile}' installs every workflow, so this list is not used.`,
|
|
439
|
+
fix: 'Run "tospec config profile custom" to make it take effect.',
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
if (profileNeedsWorkflows) {
|
|
443
|
+
warnings.push({
|
|
444
|
+
severity: 'warning',
|
|
445
|
+
code: 'config_profile_needs_workflows',
|
|
446
|
+
message: 'Profile "custom" needs a workflows list, and none is configured, so init and update install no skills.',
|
|
447
|
+
fix: `tospec config set workflows "${ALL_WORKFLOWS.slice(0, 3).join(',')}"`,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (options.json) {
|
|
451
|
+
emitSuccess({
|
|
452
|
+
set: { key, value: coercedValue },
|
|
453
|
+
...(warnings.length > 0 ? { status: warnings } : {}),
|
|
454
|
+
});
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
370
457
|
const displayValue = typeof coercedValue === 'string' ? `"${coercedValue}"` : String(coercedValue);
|
|
371
458
|
console.log(`Set ${key} = ${displayValue}`);
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
// Said after the confirmation rather than instead of it, because the value
|
|
377
|
-
// genuinely was stored; it just is not what `update` will act on.
|
|
378
|
-
if (key === 'workflows') {
|
|
379
|
-
const profile = config.profile ?? 'core';
|
|
380
|
-
if (profile !== 'custom') {
|
|
381
|
-
console.error(`Warning: profile '${profile}' installs every workflow, so this list is not used.`);
|
|
382
|
-
console.error('Run "tospec config profile custom" to make it take effect.');
|
|
383
|
-
}
|
|
459
|
+
for (const warning of warnings) {
|
|
460
|
+
console.error(`Warning: ${warning.message}`);
|
|
461
|
+
if (warning.fix)
|
|
462
|
+
console.error(warning.fix);
|
|
384
463
|
}
|
|
385
464
|
});
|
|
386
|
-
// config unset
|
|
387
465
|
configCmd
|
|
388
466
|
.command('unset <key>')
|
|
389
467
|
.description('Remove a key (revert to default)')
|
|
390
|
-
.
|
|
468
|
+
.option('--json', 'Output as JSON')
|
|
469
|
+
.action((key, options) => {
|
|
391
470
|
const config = getGlobalConfig();
|
|
392
471
|
const existed = deleteNestedValue(config, key);
|
|
393
|
-
if (existed)
|
|
472
|
+
if (existed)
|
|
394
473
|
saveGlobalConfig(config);
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
474
|
+
if (options.json) {
|
|
475
|
+
// Success either way: unsetting an absent key is the state the caller
|
|
476
|
+
// asked for. `existed` is what says whether anything changed.
|
|
477
|
+
emitSuccess({ unset: { key, existed } });
|
|
478
|
+
return;
|
|
399
479
|
}
|
|
480
|
+
console.log(existed ? `Unset ${key} (reverted to default)` : `Key "${key}" was not set`);
|
|
400
481
|
});
|
|
401
|
-
// config reset
|
|
402
482
|
configCmd
|
|
403
483
|
.command('reset')
|
|
404
484
|
.description('Reset configuration to defaults')
|
|
405
485
|
.option('--all', 'Reset all configuration (required)')
|
|
406
486
|
.option('-y, --yes', 'Skip confirmation prompts')
|
|
487
|
+
.option('--json', 'Output as JSON (non-interactive; requires --all and --yes)')
|
|
407
488
|
.action(async (options) => {
|
|
489
|
+
// `list`, `get` and `set` all speak --json, so reset must too.
|
|
408
490
|
if (!options.all) {
|
|
491
|
+
if (options.json) {
|
|
492
|
+
emitFailureStatus({ config: null }, {
|
|
493
|
+
severity: 'error',
|
|
494
|
+
code: 'config_error',
|
|
495
|
+
message: '--all flag is required for reset.',
|
|
496
|
+
fix: 'tospec config reset --all --yes --json',
|
|
497
|
+
});
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
409
500
|
console.error('Error: --all flag is required for reset');
|
|
410
501
|
console.error('Usage: tospec config reset --all [-y]');
|
|
411
502
|
process.exitCode = 1;
|
|
412
503
|
return;
|
|
413
504
|
}
|
|
505
|
+
// --json is non-interactive by contract: a prompt would block a caller
|
|
506
|
+
// that cannot answer and put non-JSON on stdout. Refuse rather than reset
|
|
507
|
+
// unasked — the flag must not become a quieter `--yes`.
|
|
508
|
+
if (options.json && !options.yes) {
|
|
509
|
+
emitFailureStatus({ config: null }, {
|
|
510
|
+
severity: 'error',
|
|
511
|
+
code: 'config_error',
|
|
512
|
+
message: '--json cannot prompt for confirmation.',
|
|
513
|
+
fix: 'tospec config reset --all --yes --json',
|
|
514
|
+
});
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
414
517
|
if (!options.yes) {
|
|
415
518
|
const { confirm } = await import('@inquirer/prompts');
|
|
416
519
|
let confirmed;
|
|
@@ -434,9 +537,12 @@ export function registerConfigCommand(program) {
|
|
|
434
537
|
}
|
|
435
538
|
}
|
|
436
539
|
saveGlobalConfig({ ...DEFAULT_CONFIG });
|
|
540
|
+
if (options.json) {
|
|
541
|
+
emitSuccess({ config: getGlobalConfig() });
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
437
544
|
console.log('Configuration reset to defaults');
|
|
438
545
|
});
|
|
439
|
-
// config edit
|
|
440
546
|
configCmd
|
|
441
547
|
.command('edit')
|
|
442
548
|
.description('Open config in $EDITOR')
|
|
@@ -450,13 +556,10 @@ export function registerConfigCommand(program) {
|
|
|
450
556
|
return;
|
|
451
557
|
}
|
|
452
558
|
const configPath = getGlobalConfigPath();
|
|
453
|
-
// Ensure config file exists with defaults
|
|
454
559
|
if (!fs.existsSync(configPath)) {
|
|
455
560
|
saveGlobalConfig({ ...DEFAULT_CONFIG });
|
|
456
561
|
}
|
|
457
|
-
//
|
|
458
|
-
// Avoid shell parsing to correctly handle paths with spaces in both
|
|
459
|
-
// the editor path and config path
|
|
562
|
+
// shell: false so a space in the editor or config path is not re-parsed.
|
|
460
563
|
const child = spawn(editor, [configPath], {
|
|
461
564
|
stdio: 'inherit',
|
|
462
565
|
shell: false,
|
|
@@ -474,7 +577,11 @@ export function registerConfigCommand(program) {
|
|
|
474
577
|
});
|
|
475
578
|
try {
|
|
476
579
|
const rawConfig = fs.readFileSync(configPath, 'utf-8');
|
|
477
|
-
|
|
580
|
+
// `config edit` just handed this file to the user's editor, and a
|
|
581
|
+
// Windows editor saving it back adds a BOM nobody asked for. Rejecting
|
|
582
|
+
// the result as "Invalid JSON" would blame the user for an edit tospec
|
|
583
|
+
// invited.
|
|
584
|
+
const parsedConfig = JSON.parse(stripBom(rawConfig));
|
|
478
585
|
const validation = validateConfig(parsedConfig);
|
|
479
586
|
if (!validation.success) {
|
|
480
587
|
console.error(`Error: Invalid configuration - ${validation.error}`);
|
|
@@ -495,32 +602,71 @@ export function registerConfigCommand(program) {
|
|
|
495
602
|
process.exitCode = 1;
|
|
496
603
|
}
|
|
497
604
|
});
|
|
498
|
-
// config profile [preset]
|
|
499
605
|
configCmd
|
|
500
606
|
.command('profile [preset]')
|
|
501
607
|
.description('Configure workflow profile (interactive picker or preset shortcut)')
|
|
502
|
-
.
|
|
503
|
-
|
|
608
|
+
.option('--json', 'Output as JSON (non-interactive; requires a preset)')
|
|
609
|
+
.action(async (preset, options) => {
|
|
610
|
+
/** Every rejection below shares this null-shape, per the envelope contract. */
|
|
611
|
+
const fail = (message, fix) => {
|
|
612
|
+
if (options.json) {
|
|
613
|
+
emitFailureStatus(CONFIG_FAILURE_PAYLOADS.profile, { severity: 'error', code: 'config_error', message, ...(fix ? { fix } : {}) });
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
console.error(`Error: ${message}`);
|
|
617
|
+
if (fix)
|
|
618
|
+
console.error(`Fix: ${fix}`);
|
|
619
|
+
process.exitCode = 1;
|
|
620
|
+
};
|
|
621
|
+
/** The resolved state, so a caller need not re-read it with `config list`. */
|
|
622
|
+
const reportApplied = (config) => {
|
|
623
|
+
if (options.json) {
|
|
624
|
+
emitSuccess({ profile: resolveCurrentProfileState(config) });
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
printConfigProfileApplyGuidance();
|
|
628
|
+
};
|
|
629
|
+
// The stored list is dropped, not rewritten to ALL_WORKFLOWS: rewriting it
|
|
630
|
+
// manufactures the state `ignoredWorkflowsDiagnostic` warns about.
|
|
504
631
|
if (preset === 'core') {
|
|
505
632
|
const config = getGlobalConfig();
|
|
506
633
|
config.profile = 'core';
|
|
507
|
-
config.workflows
|
|
634
|
+
delete config.workflows;
|
|
508
635
|
saveGlobalConfig(config);
|
|
509
|
-
|
|
636
|
+
reportApplied(config);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
// The command `config set workflows` and `config list` both name as the
|
|
640
|
+
// way to make a workflow list take effect.
|
|
641
|
+
if (preset === 'custom') {
|
|
642
|
+
const config = getGlobalConfig();
|
|
643
|
+
// Without a list, `custom` resolves to zero workflows and the next
|
|
644
|
+
// `tospec update` deletes every installed skill directory.
|
|
645
|
+
if (!config.workflows || config.workflows.length === 0) {
|
|
646
|
+
fail('Profile "custom" needs a workflows list, and none is configured.', `tospec config set workflows "${ALL_WORKFLOWS.slice(0, 3).join(',')}"`);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
config.profile = 'custom';
|
|
650
|
+
saveGlobalConfig(config);
|
|
651
|
+
reportApplied(config);
|
|
510
652
|
return;
|
|
511
653
|
}
|
|
512
654
|
if (preset) {
|
|
513
|
-
|
|
514
|
-
|
|
655
|
+
fail(`Unknown profile preset "${preset}". Available presets: core, custom`);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
// --json has no way to answer a picker, so it never opens one: the
|
|
659
|
+
// failure names the presets rather than hanging on a prompt no caller
|
|
660
|
+
// will see.
|
|
661
|
+
if (options.json) {
|
|
662
|
+
fail('Interactive profile selection is not available under --json.', 'tospec config profile core --json | tospec config profile custom --json');
|
|
515
663
|
return;
|
|
516
664
|
}
|
|
517
|
-
// Non-interactive check
|
|
518
665
|
if (!process.stdout.isTTY) {
|
|
519
|
-
console.error('Interactive mode required. Use `tospec config profile core` or set config via environment/flags.');
|
|
666
|
+
console.error('Interactive mode required. Use `tospec config profile core|custom` or set config via environment/flags.');
|
|
520
667
|
process.exitCode = 1;
|
|
521
668
|
return;
|
|
522
669
|
}
|
|
523
|
-
// Interactive picker
|
|
524
670
|
const { select, checkbox, confirm } = await import('@inquirer/prompts');
|
|
525
671
|
try {
|
|
526
672
|
const config = getGlobalConfig();
|
|
@@ -554,9 +700,8 @@ export function registerConfigCommand(program) {
|
|
|
554
700
|
};
|
|
555
701
|
if (action === 'workflows') {
|
|
556
702
|
const formatWorkflowChoice = (workflow) => {
|
|
557
|
-
// The map is complete by construction
|
|
558
|
-
//
|
|
559
|
-
// this build no longer ships.
|
|
703
|
+
// The map is complete by construction; this only catches an id a
|
|
704
|
+
// user's custom config carries that this build no longer ships.
|
|
560
705
|
const metadata = WORKFLOW_PROMPT_META[workflow] ?? {
|
|
561
706
|
name: workflow,
|
|
562
707
|
description: `Workflow: ${workflow}`,
|
|
@@ -594,9 +739,13 @@ export function registerConfigCommand(program) {
|
|
|
594
739
|
}
|
|
595
740
|
console.log();
|
|
596
741
|
config.profile = nextState.profile;
|
|
597
|
-
|
|
742
|
+
// Same rule as the `core` preset above: a list stored under `core` is an
|
|
743
|
+
// ignored key every later `config list` warns about.
|
|
744
|
+
if (nextState.profile === 'core')
|
|
745
|
+
delete config.workflows;
|
|
746
|
+
else
|
|
747
|
+
config.workflows = nextState.workflows;
|
|
598
748
|
saveGlobalConfig(config);
|
|
599
|
-
// Check if inside an Tospec project
|
|
600
749
|
const projectDir = process.cwd();
|
|
601
750
|
const tospecDir = path.join(projectDir, TOSPEC_DIR_NAME);
|
|
602
751
|
if (fs.existsSync(tospecDir)) {
|
|
@@ -606,9 +755,8 @@ export function registerConfigCommand(program) {
|
|
|
606
755
|
});
|
|
607
756
|
if (applyNow) {
|
|
608
757
|
try {
|
|
609
|
-
//
|
|
610
|
-
//
|
|
611
|
-
// monorepos.
|
|
758
|
+
// In-process, not `npx tospec update`: that depends on PATH and
|
|
759
|
+
// fails under global installs and monorepos.
|
|
612
760
|
await new UpdateCommand().execute(projectDir);
|
|
613
761
|
console.log('Run `tospec update` in your other projects to apply.');
|
|
614
762
|
}
|