@seanmars/tospec 0.19.0-beta.7 → 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 -231
- package/README.md +69 -82
- package/assets/dashboard/app.js +14 -3
- package/assets/dashboard/style.css +7 -0
- package/assets/rules/tospec/decision.md +3 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +227 -86
- 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 +366 -107
- 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 +224 -282
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/decision.d.ts +41 -19
- package/dist/commands/decision.d.ts.map +1 -1
- package/dist/commands/decision.js +385 -79
- 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 +18 -21
- package/dist/commands/shared-output.d.ts.map +1 -1
- package/dist/commands/shared-output.js +49 -23
- package/dist/commands/shared-output.js.map +1 -1
- package/dist/commands/show.d.ts +7 -0
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.js +29 -7
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/validate.d.ts +43 -45
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +262 -129
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/workflow/index.d.ts +6 -10
- package/dist/commands/workflow/index.d.ts.map +1 -1
- package/dist/commands/workflow/index.js +6 -10
- package/dist/commands/workflow/index.js.map +1 -1
- package/dist/commands/workflow/instructions.d.ts +21 -8
- package/dist/commands/workflow/instructions.d.ts.map +1 -1
- package/dist/commands/workflow/instructions.js +254 -95
- package/dist/commands/workflow/instructions.js.map +1 -1
- package/dist/commands/workflow/new-change.d.ts +4 -5
- package/dist/commands/workflow/new-change.d.ts.map +1 -1
- package/dist/commands/workflow/new-change.js +90 -25
- package/dist/commands/workflow/new-change.js.map +1 -1
- package/dist/commands/workflow/schemas.d.ts +3 -5
- package/dist/commands/workflow/schemas.d.ts.map +1 -1
- package/dist/commands/workflow/schemas.js +37 -11
- package/dist/commands/workflow/schemas.js.map +1 -1
- package/dist/commands/workflow/shared.d.ts +48 -21
- package/dist/commands/workflow/shared.d.ts.map +1 -1
- package/dist/commands/workflow/shared.js +36 -33
- package/dist/commands/workflow/shared.js.map +1 -1
- package/dist/commands/workflow/status.d.ts +10 -6
- package/dist/commands/workflow/status.d.ts.map +1 -1
- package/dist/commands/workflow/status.js +81 -42
- package/dist/commands/workflow/status.js.map +1 -1
- package/dist/commands/workflow/templates.d.ts +10 -3
- package/dist/commands/workflow/templates.d.ts.map +1 -1
- package/dist/commands/workflow/templates.js +39 -40
- package/dist/commands/workflow/templates.js.map +1 -1
- package/dist/core/archive.d.ts +26 -21
- package/dist/core/archive.d.ts.map +1 -1
- package/dist/core/archive.js +378 -235
- 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 +97 -103
- package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.js +146 -119
- 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 +44 -63
- package/dist/core/artifact-graph/resolver.d.ts.map +1 -1
- package/dist/core/artifact-graph/resolver.js +85 -86
- 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 +12 -0
- package/dist/core/artifact-graph/stub-detection.d.ts.map +1 -0
- package/dist/core/artifact-graph/stub-detection.js +39 -0
- package/dist/core/artifact-graph/stub-detection.js.map +1 -0
- 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 +23 -18
- package/dist/core/change-presenter.d.ts.map +1 -1
- package/dist/core/change-presenter.js +102 -43
- package/dist/core/change-presenter.js.map +1 -1
- package/dist/core/change-status-policy.d.ts +8 -1
- package/dist/core/change-status-policy.d.ts.map +1 -1
- package/dist/core/change-status-policy.js +25 -1
- 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 +8 -53
- package/dist/core/config-schema.d.ts.map +1 -1
- package/dist/core/config-schema.js +49 -62
- package/dist/core/config-schema.js.map +1 -1
- package/dist/core/config.d.ts +39 -26
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +36 -22
- 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 +40 -22
- package/dist/core/dashboard-data.d.ts.map +1 -1
- package/dist/core/dashboard-data.js +62 -70
- 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 +25 -19
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +168 -181
- package/dist/core/init.js.map +1 -1
- package/dist/core/list.d.ts +1 -1
- package/dist/core/list.d.ts.map +1 -1
- package/dist/core/list.js +121 -28
- 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 +25 -0
- package/dist/core/markdown-render.d.ts.map +1 -0
- package/dist/core/markdown-render.js +94 -0
- package/dist/core/markdown-render.js.map +1 -0
- package/dist/core/migrate.d.ts +29 -14
- package/dist/core/migrate.d.ts.map +1 -1
- package/dist/core/migrate.js +184 -122
- 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 +31 -22
- 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 +11 -7
- package/dist/core/root-selection.d.ts.map +1 -1
- package/dist/core/root-selection.js +7 -8
- package/dist/core/root-selection.js.map +1 -1
- package/dist/core/rules.d.ts +6 -1
- package/dist/core/rules.d.ts.map +1 -1
- package/dist/core/rules.js +23 -2
- 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 +3 -0
- package/dist/core/schemas/base.schema.d.ts.map +1 -1
- package/dist/core/schemas/base.schema.js +22 -6
- package/dist/core/schemas/base.schema.js.map +1 -1
- package/dist/core/schemas/change.schema.d.ts +16 -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/schemas/spec.schema.d.ts +2 -0
- package/dist/core/schemas/spec.schema.d.ts.map +1 -1
- package/dist/core/shared/index.d.ts +3 -8
- package/dist/core/shared/index.d.ts.map +1 -1
- package/dist/core/shared/index.js +3 -8
- package/dist/core/shared/index.js.map +1 -1
- package/dist/core/shared/rules-generation.d.ts +19 -8
- package/dist/core/shared/rules-generation.d.ts.map +1 -1
- package/dist/core/shared/rules-generation.js +117 -50
- 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 +39 -61
- package/dist/core/shared/tool-detection.d.ts.map +1 -1
- package/dist/core/shared/tool-detection.js +88 -80
- 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.js +6 -6
- package/dist/core/spec-presenter.js.map +1 -1
- package/dist/core/specs-apply.d.ts +22 -23
- package/dist/core/specs-apply.d.ts.map +1 -1
- package/dist/core/specs-apply.js +166 -193
- 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 +19 -6
- package/dist/core/templates/workflows/archive.js.map +1 -1
- package/dist/core/templates/workflows/decision.js +4 -4
- 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 +23 -23
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +139 -118
- 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 -20
- 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 +43 -7
- 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 +43 -63
- package/dist/core/validation/validator.d.ts.map +1 -1
- package/dist/core/validation/validator.js +500 -267
- 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 +30 -54
- package/dist/utils/change-utils.d.ts.map +1 -1
- package/dist/utils/change-utils.js +133 -91
- 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 +15 -10
- package/dist/utils/item-discovery.d.ts.map +1 -1
- package/dist/utils/item-discovery.js +42 -47
- 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/decision/templates/decision.md +3 -1
- package/schemas/decision/templates/index.md +2 -2
- package/schemas/issue/schema.yaml +8 -1
- package/schemas/issue/templates/spec.md +27 -2
- package/schemas/sdd/schema.yaml +20 -1
- package/schemas/sdd/templates/spec.md +27 -2
- /package/assets/rules/tospec/{single-sourc-of-truth.md → single-source-of-truth.md} +0 -0
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,20 +79,52 @@ 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
|
}
|
|
90
|
+
/**
|
|
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`.
|
|
95
|
+
*/
|
|
96
|
+
function ignoredWorkflowsDiagnostic(config) {
|
|
97
|
+
const profile = config.profile ?? 'core';
|
|
98
|
+
if (profile === 'custom')
|
|
99
|
+
return null;
|
|
100
|
+
if (!config.workflows || config.workflows.length === 0)
|
|
101
|
+
return null;
|
|
102
|
+
return {
|
|
103
|
+
severity: 'warning',
|
|
104
|
+
code: 'config_workflows_ignored',
|
|
105
|
+
message: `Profile '${profile}' installs every workflow, so the configured workflows list is not used.`,
|
|
106
|
+
fix: 'Run "tospec config profile custom" to apply it, or "tospec config unset workflows" to drop it.',
|
|
107
|
+
};
|
|
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
|
+
}
|
|
82
128
|
function stableWorkflowOrder(workflows) {
|
|
83
129
|
const seen = new Set();
|
|
84
130
|
const ordered = [];
|
|
@@ -98,9 +144,6 @@ function stableWorkflowOrder(workflows) {
|
|
|
98
144
|
}
|
|
99
145
|
return ordered;
|
|
100
146
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Build a user-facing diff summary between two profile states.
|
|
103
|
-
*/
|
|
104
147
|
export function diffProfileState(before, after) {
|
|
105
148
|
const lines = [];
|
|
106
149
|
if (before.profile !== after.profile) {
|
|
@@ -127,37 +170,70 @@ export function diffProfileState(before, after) {
|
|
|
127
170
|
lines,
|
|
128
171
|
};
|
|
129
172
|
}
|
|
130
|
-
|
|
131
|
-
|
|
173
|
+
/**
|
|
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.
|
|
177
|
+
*/
|
|
178
|
+
function rawGlobalConfig() {
|
|
179
|
+
try {
|
|
180
|
+
const configPath = getEffectiveConfigPath();
|
|
181
|
+
if (fs.existsSync(configPath)) {
|
|
182
|
+
return JSON.parse(stripBom(fs.readFileSync(configPath, 'utf-8')));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// Unreadable: treat every value as a default rather than fail a listing.
|
|
187
|
+
}
|
|
188
|
+
return {};
|
|
132
189
|
}
|
|
133
190
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
191
|
+
* The workflows a profile resolves to — `core` expands to the full set, any
|
|
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.
|
|
137
194
|
*/
|
|
195
|
+
function resolveListedWorkflows(config) {
|
|
196
|
+
if (config.profile === 'core')
|
|
197
|
+
return [...ALL_WORKFLOWS];
|
|
198
|
+
return [...(config.workflows ?? [])];
|
|
199
|
+
}
|
|
200
|
+
function printConfigProfileApplyGuidance() {
|
|
201
|
+
console.log('Config updated. Run `tospec update` in your projects to apply.');
|
|
202
|
+
}
|
|
138
203
|
export function registerConfigCommand(program) {
|
|
139
204
|
const configCmd = program
|
|
140
205
|
.command('config')
|
|
141
206
|
.description('View and modify global tospec configuration')
|
|
142
207
|
.option('--scope <scope>', 'Config scope (only "global" supported currently)')
|
|
143
|
-
.hook('preAction', (thisCommand) => {
|
|
208
|
+
.hook('preAction', (thisCommand, actionCommand) => {
|
|
144
209
|
const opts = thisCommand.opts();
|
|
145
210
|
if (opts.scope && opts.scope !== 'global') {
|
|
146
|
-
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
|
|
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);
|
|
151
224
|
}
|
|
152
225
|
});
|
|
153
|
-
// config path
|
|
154
226
|
configCmd
|
|
155
227
|
.command('path')
|
|
156
228
|
.description('Show config file location')
|
|
157
|
-
.
|
|
229
|
+
.option('--json', 'Output as JSON')
|
|
230
|
+
.action((options) => {
|
|
231
|
+
if (options.json) {
|
|
232
|
+
emitSuccess({ path: getGlobalConfigPath() });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
158
235
|
console.log(getGlobalConfigPath());
|
|
159
236
|
});
|
|
160
|
-
// config list
|
|
161
237
|
configCmd
|
|
162
238
|
.command('list')
|
|
163
239
|
.description('Show all current settings')
|
|
@@ -165,66 +241,121 @@ export function registerConfigCommand(program) {
|
|
|
165
241
|
.action((options) => {
|
|
166
242
|
const config = getGlobalConfig();
|
|
167
243
|
if (options.json) {
|
|
168
|
-
// Nested, not spread
|
|
244
|
+
// Nested, not spread: the schema is `.passthrough()` and
|
|
169
245
|
// `config set --allow-unknown` writes any top-level key, so spreading
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
|
|
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
|
|
249
|
+
//
|
|
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);
|
|
256
|
+
emitSuccess({
|
|
257
|
+
config,
|
|
258
|
+
profileSettings: {
|
|
259
|
+
profile: config.profile ?? 'core',
|
|
260
|
+
explicit: rawGlobalConfig().profile !== undefined,
|
|
261
|
+
workflows: resolveListedWorkflows(config),
|
|
262
|
+
},
|
|
263
|
+
...(listStatus.length ? { status: listStatus } : {}),
|
|
264
|
+
});
|
|
177
265
|
}
|
|
178
266
|
else {
|
|
179
|
-
|
|
180
|
-
// Must be the file that was actually read, not the write target — they
|
|
181
|
-
// differ until a legacy install migrates.
|
|
182
|
-
const configPath = getEffectiveConfigPath();
|
|
183
|
-
let rawConfig = {};
|
|
184
|
-
try {
|
|
185
|
-
if (fs.existsSync(configPath)) {
|
|
186
|
-
rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
catch {
|
|
190
|
-
// If reading fails, treat all as defaults
|
|
191
|
-
}
|
|
267
|
+
const rawConfig = rawGlobalConfig();
|
|
192
268
|
console.log(yaml.stringify(config).trimEnd());
|
|
193
|
-
//
|
|
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.
|
|
272
|
+
const ignored = ignoredWorkflowsDiagnostic(config);
|
|
273
|
+
if (ignored) {
|
|
274
|
+
console.log(` ^ ${ignored.message}`);
|
|
275
|
+
}
|
|
194
276
|
const profileSource = rawConfig.profile !== undefined ? '(explicit)' : '(default)';
|
|
195
277
|
console.log(`\nProfile settings:`);
|
|
196
278
|
console.log(` profile: ${config.profile} ${profileSource}`);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
else if (config.workflows && config.workflows.length > 0) {
|
|
201
|
-
console.log(` workflows: ${config.workflows.join(', ')} (explicit)`);
|
|
279
|
+
const workflows = resolveListedWorkflows(config);
|
|
280
|
+
if (workflows.length === 0) {
|
|
281
|
+
console.log(` workflows: (none)`);
|
|
202
282
|
}
|
|
203
283
|
else {
|
|
204
|
-
|
|
284
|
+
const origin = config.profile === 'core' ? '(from core profile)' : '(explicit)';
|
|
285
|
+
console.log(` workflows: ${workflows.join(', ')} ${origin}`);
|
|
286
|
+
}
|
|
287
|
+
if (ignored?.fix) {
|
|
288
|
+
console.log();
|
|
289
|
+
console.log(ignored.fix);
|
|
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);
|
|
205
297
|
}
|
|
206
298
|
}
|
|
207
299
|
});
|
|
208
|
-
// config get
|
|
209
300
|
configCmd
|
|
210
301
|
.command('get <key>')
|
|
211
302
|
.description('Get a specific value (raw, scriptable)')
|
|
212
|
-
.
|
|
303
|
+
.option('--allow-unknown', 'Allow reading unknown keys')
|
|
304
|
+
.option('--json', 'Output as JSON')
|
|
305
|
+
.action((key, options) => {
|
|
306
|
+
const allowUnknown = Boolean(options.allowUnknown);
|
|
213
307
|
const keyValidation = validateConfigKeyPath(key);
|
|
214
|
-
|
|
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.
|
|
311
|
+
const unsafeKey = hasUnsafeKeySegment(key);
|
|
312
|
+
if (!keyValidation.valid && (!allowUnknown || unsafeKey)) {
|
|
215
313
|
const reason = keyValidation.reason ? ` ${keyValidation.reason}.` : '';
|
|
314
|
+
const fixLines = ['Use "tospec config list" to see available keys.'];
|
|
315
|
+
if (!allowUnknown && !unsafeKey) {
|
|
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;
|
|
326
|
+
}
|
|
216
327
|
console.error(`Error: Invalid configuration key "${key}".${reason}`);
|
|
217
|
-
|
|
328
|
+
for (const line of fixLines)
|
|
329
|
+
console.error(line);
|
|
218
330
|
process.exitCode = 1;
|
|
219
331
|
return;
|
|
220
332
|
}
|
|
221
333
|
const config = getGlobalConfig();
|
|
222
334
|
const value = getNestedValue(config, key);
|
|
223
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
|
+
}
|
|
224
349
|
console.error(`Error: Key "${key}" is not set.`);
|
|
225
350
|
process.exitCode = 1;
|
|
226
351
|
return;
|
|
227
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
|
+
}
|
|
228
359
|
if (typeof value === 'object' && value !== null) {
|
|
229
360
|
console.log(JSON.stringify(value));
|
|
230
361
|
}
|
|
@@ -232,13 +363,25 @@ export function registerConfigCommand(program) {
|
|
|
232
363
|
console.log(String(value));
|
|
233
364
|
}
|
|
234
365
|
});
|
|
235
|
-
// config set
|
|
236
366
|
configCmd
|
|
237
367
|
.command('set <key> <value>')
|
|
238
368
|
.description('Set a value (auto-coerce types)')
|
|
239
369
|
.option('--string', 'Force value to be stored as string')
|
|
240
370
|
.option('--allow-unknown', 'Allow setting unknown keys')
|
|
371
|
+
.option('--json', 'Output as JSON')
|
|
241
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
|
+
};
|
|
242
385
|
const allowUnknown = Boolean(options.allowUnknown);
|
|
243
386
|
const keyValidation = validateConfigKeyPath(key);
|
|
244
387
|
// --allow-unknown relaxes the known-key check, but never the
|
|
@@ -246,60 +389,131 @@ export function registerConfigCommand(program) {
|
|
|
246
389
|
const unsafeKey = hasUnsafeKeySegment(key);
|
|
247
390
|
if (!keyValidation.valid && (!allowUnknown || unsafeKey)) {
|
|
248
391
|
const reason = keyValidation.reason ? ` ${keyValidation.reason}.` : '';
|
|
249
|
-
|
|
250
|
-
console.error('Use "tospec config list" to see available keys.');
|
|
392
|
+
const fixLines = ['Use "tospec config list" to see available keys.'];
|
|
251
393
|
if (!allowUnknown && !unsafeKey) {
|
|
252
|
-
|
|
394
|
+
fixLines.push('Pass --allow-unknown to bypass this check.');
|
|
253
395
|
}
|
|
254
|
-
|
|
396
|
+
fail(`Invalid configuration key "${key}".${reason}`, fixLines.join('\n'));
|
|
255
397
|
return;
|
|
256
398
|
}
|
|
257
399
|
const config = getGlobalConfig();
|
|
258
|
-
const coercedValue = coerceValue(value, options.string || false);
|
|
259
|
-
//
|
|
400
|
+
const coercedValue = coerceValue(value, options.string || false, key);
|
|
401
|
+
// Validate a copy before touching the config that is saved.
|
|
260
402
|
const newConfig = JSON.parse(JSON.stringify(config));
|
|
261
403
|
setNestedValue(newConfig, key, coercedValue);
|
|
262
|
-
// Validate the new config
|
|
263
404
|
const validation = validateConfig(newConfig);
|
|
264
405
|
if (!validation.success) {
|
|
265
|
-
|
|
266
|
-
process.exitCode = 1;
|
|
406
|
+
fail(`Invalid configuration - ${validation.error}`);
|
|
267
407
|
return;
|
|
268
408
|
}
|
|
269
|
-
//
|
|
409
|
+
// The schema types `workflows` as string[], so a misspelled id passes it
|
|
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.
|
|
412
|
+
if (key === 'workflows' && Array.isArray(coercedValue)) {
|
|
413
|
+
const unknown = coercedValue.filter((entry) => !ALL_WORKFLOWS.includes(String(entry)));
|
|
414
|
+
if (unknown.length > 0) {
|
|
415
|
+
fail(`Unknown workflow(s): ${unknown.join(', ')}`, `Available: ${ALL_WORKFLOWS.join(', ')}`);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
270
419
|
setNestedValue(config, key, coercedValue);
|
|
271
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
|
+
}
|
|
272
457
|
const displayValue = typeof coercedValue === 'string' ? `"${coercedValue}"` : String(coercedValue);
|
|
273
458
|
console.log(`Set ${key} = ${displayValue}`);
|
|
459
|
+
for (const warning of warnings) {
|
|
460
|
+
console.error(`Warning: ${warning.message}`);
|
|
461
|
+
if (warning.fix)
|
|
462
|
+
console.error(warning.fix);
|
|
463
|
+
}
|
|
274
464
|
});
|
|
275
|
-
// config unset
|
|
276
465
|
configCmd
|
|
277
466
|
.command('unset <key>')
|
|
278
467
|
.description('Remove a key (revert to default)')
|
|
279
|
-
.
|
|
468
|
+
.option('--json', 'Output as JSON')
|
|
469
|
+
.action((key, options) => {
|
|
280
470
|
const config = getGlobalConfig();
|
|
281
471
|
const existed = deleteNestedValue(config, key);
|
|
282
|
-
if (existed)
|
|
472
|
+
if (existed)
|
|
283
473
|
saveGlobalConfig(config);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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;
|
|
288
479
|
}
|
|
480
|
+
console.log(existed ? `Unset ${key} (reverted to default)` : `Key "${key}" was not set`);
|
|
289
481
|
});
|
|
290
|
-
// config reset
|
|
291
482
|
configCmd
|
|
292
483
|
.command('reset')
|
|
293
484
|
.description('Reset configuration to defaults')
|
|
294
485
|
.option('--all', 'Reset all configuration (required)')
|
|
295
486
|
.option('-y, --yes', 'Skip confirmation prompts')
|
|
487
|
+
.option('--json', 'Output as JSON (non-interactive; requires --all and --yes)')
|
|
296
488
|
.action(async (options) => {
|
|
489
|
+
// `list`, `get` and `set` all speak --json, so reset must too.
|
|
297
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
|
+
}
|
|
298
500
|
console.error('Error: --all flag is required for reset');
|
|
299
501
|
console.error('Usage: tospec config reset --all [-y]');
|
|
300
502
|
process.exitCode = 1;
|
|
301
503
|
return;
|
|
302
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
|
+
}
|
|
303
517
|
if (!options.yes) {
|
|
304
518
|
const { confirm } = await import('@inquirer/prompts');
|
|
305
519
|
let confirmed;
|
|
@@ -323,9 +537,12 @@ export function registerConfigCommand(program) {
|
|
|
323
537
|
}
|
|
324
538
|
}
|
|
325
539
|
saveGlobalConfig({ ...DEFAULT_CONFIG });
|
|
540
|
+
if (options.json) {
|
|
541
|
+
emitSuccess({ config: getGlobalConfig() });
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
326
544
|
console.log('Configuration reset to defaults');
|
|
327
545
|
});
|
|
328
|
-
// config edit
|
|
329
546
|
configCmd
|
|
330
547
|
.command('edit')
|
|
331
548
|
.description('Open config in $EDITOR')
|
|
@@ -339,13 +556,10 @@ export function registerConfigCommand(program) {
|
|
|
339
556
|
return;
|
|
340
557
|
}
|
|
341
558
|
const configPath = getGlobalConfigPath();
|
|
342
|
-
// Ensure config file exists with defaults
|
|
343
559
|
if (!fs.existsSync(configPath)) {
|
|
344
560
|
saveGlobalConfig({ ...DEFAULT_CONFIG });
|
|
345
561
|
}
|
|
346
|
-
//
|
|
347
|
-
// Avoid shell parsing to correctly handle paths with spaces in both
|
|
348
|
-
// the editor path and config path
|
|
562
|
+
// shell: false so a space in the editor or config path is not re-parsed.
|
|
349
563
|
const child = spawn(editor, [configPath], {
|
|
350
564
|
stdio: 'inherit',
|
|
351
565
|
shell: false,
|
|
@@ -363,7 +577,11 @@ export function registerConfigCommand(program) {
|
|
|
363
577
|
});
|
|
364
578
|
try {
|
|
365
579
|
const rawConfig = fs.readFileSync(configPath, 'utf-8');
|
|
366
|
-
|
|
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));
|
|
367
585
|
const validation = validateConfig(parsedConfig);
|
|
368
586
|
if (!validation.success) {
|
|
369
587
|
console.error(`Error: Invalid configuration - ${validation.error}`);
|
|
@@ -384,32 +602,71 @@ export function registerConfigCommand(program) {
|
|
|
384
602
|
process.exitCode = 1;
|
|
385
603
|
}
|
|
386
604
|
});
|
|
387
|
-
// config profile [preset]
|
|
388
605
|
configCmd
|
|
389
606
|
.command('profile [preset]')
|
|
390
607
|
.description('Configure workflow profile (interactive picker or preset shortcut)')
|
|
391
|
-
.
|
|
392
|
-
|
|
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.
|
|
393
631
|
if (preset === 'core') {
|
|
394
632
|
const config = getGlobalConfig();
|
|
395
633
|
config.profile = 'core';
|
|
396
|
-
config.workflows
|
|
634
|
+
delete config.workflows;
|
|
397
635
|
saveGlobalConfig(config);
|
|
398
|
-
|
|
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);
|
|
399
652
|
return;
|
|
400
653
|
}
|
|
401
654
|
if (preset) {
|
|
402
|
-
|
|
403
|
-
|
|
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');
|
|
404
663
|
return;
|
|
405
664
|
}
|
|
406
|
-
// Non-interactive check
|
|
407
665
|
if (!process.stdout.isTTY) {
|
|
408
|
-
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.');
|
|
409
667
|
process.exitCode = 1;
|
|
410
668
|
return;
|
|
411
669
|
}
|
|
412
|
-
// Interactive picker
|
|
413
670
|
const { select, checkbox, confirm } = await import('@inquirer/prompts');
|
|
414
671
|
try {
|
|
415
672
|
const config = getGlobalConfig();
|
|
@@ -443,9 +700,8 @@ export function registerConfigCommand(program) {
|
|
|
443
700
|
};
|
|
444
701
|
if (action === 'workflows') {
|
|
445
702
|
const formatWorkflowChoice = (workflow) => {
|
|
446
|
-
// The map is complete by construction
|
|
447
|
-
//
|
|
448
|
-
// 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.
|
|
449
705
|
const metadata = WORKFLOW_PROMPT_META[workflow] ?? {
|
|
450
706
|
name: workflow,
|
|
451
707
|
description: `Workflow: ${workflow}`,
|
|
@@ -483,9 +739,13 @@ export function registerConfigCommand(program) {
|
|
|
483
739
|
}
|
|
484
740
|
console.log();
|
|
485
741
|
config.profile = nextState.profile;
|
|
486
|
-
|
|
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;
|
|
487
748
|
saveGlobalConfig(config);
|
|
488
|
-
// Check if inside an Tospec project
|
|
489
749
|
const projectDir = process.cwd();
|
|
490
750
|
const tospecDir = path.join(projectDir, TOSPEC_DIR_NAME);
|
|
491
751
|
if (fs.existsSync(tospecDir)) {
|
|
@@ -495,9 +755,8 @@ export function registerConfigCommand(program) {
|
|
|
495
755
|
});
|
|
496
756
|
if (applyNow) {
|
|
497
757
|
try {
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
// monorepos.
|
|
758
|
+
// In-process, not `npx tospec update`: that depends on PATH and
|
|
759
|
+
// fails under global installs and monorepos.
|
|
501
760
|
await new UpdateCommand().execute(projectDir);
|
|
502
761
|
console.log('Run `tospec update` in your other projects to apply.');
|
|
503
762
|
}
|