@seanmars/tospec 0.19.0-beta.1 → 0.19.0-beta.13
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 +530 -0
- package/README.md +71 -77
- package/assets/dashboard/app.js +14 -3
- package/assets/dashboard/style.css +7 -0
- package/assets/rules/tospec/decision.md +3 -0
- package/assets/rules/tospec/single-source-of-truth.md +19 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +254 -83
- 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 +374 -98
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/dashboard.d.ts +64 -81
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +346 -230
- 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 +400 -70
- 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 +69 -85
- 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 +39 -8
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/validate.d.ts +47 -30
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +282 -108
- 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 +84 -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 +386 -205
- package/dist/core/archive.js.map +1 -1
- package/dist/core/artifact-graph/graph.d.ts +29 -36
- package/dist/core/artifact-graph/graph.d.ts.map +1 -1
- package/dist/core/artifact-graph/graph.js +50 -58
- 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 +105 -100
- package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.js +176 -110
- package/dist/core/artifact-graph/instruction-loader.js.map +1 -1
- package/dist/core/artifact-graph/outputs.d.ts +11 -14
- package/dist/core/artifact-graph/outputs.d.ts.map +1 -1
- package/dist/core/artifact-graph/outputs.js +47 -29
- 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 +22 -0
- package/dist/core/codex-residue.d.ts.map +1 -0
- package/dist/core/codex-residue.js +61 -0
- package/dist/core/codex-residue.js.map +1 -0
- 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 +56 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +73 -2
- package/dist/core/config.js.map +1 -1
- package/dist/core/converters/json-converter.d.ts.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 +29 -11
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +232 -164
- 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 +60 -50
- package/dist/core/local-server.d.ts.map +1 -1
- package/dist/core/local-server.js +94 -66
- 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 +32 -15
- package/dist/core/migrate.d.ts.map +1 -1
- package/dist/core/migrate.js +220 -108
- 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 +169 -54
- 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/planning-home.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 +10 -18
- 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 +10 -0
- package/dist/core/rules.d.ts.map +1 -0
- package/dist/core/rules.js +43 -0
- package/dist/core/rules.js.map +1 -0
- 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 +27 -8
- package/dist/core/shared/rules-generation.d.ts.map +1 -1
- package/dist/core/shared/rules-generation.js +151 -16
- package/dist/core/shared/rules-generation.js.map +1 -1
- package/dist/core/shared/skill-generation.d.ts +38 -53
- 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 +40 -62
- 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.d.ts.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/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 +9 -12
- 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 +7 -5
- 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 -0
- 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 +0 -1
- 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 +2 -2
- 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.map +1 -1
- package/dist/core/update.d.ts +26 -21
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +165 -116
- 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 +19 -25
- 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 +50 -51
- package/dist/core/validation/validator.d.ts.map +1 -1
- package/dist/core/validation/validator.js +478 -269
- 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 +31 -54
- package/dist/utils/change-utils.d.ts.map +1 -1
- package/dist/utils/change-utils.js +143 -100
- 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 +13 -4
- package/dist/utils/link.d.ts.map +1 -1
- package/dist/utils/link.js +13 -4
- package/dist/utils/link.js.map +1 -1
- package/dist/utils/match.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 +10 -11
- package/dist/utils/spec-files.d.ts.map +1 -1
- package/dist/utils/spec-files.js +31 -22
- 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 +9 -10
- package/schemas/decision/templates/decision.md +3 -1
- package/schemas/decision/templates/index.md +2 -2
- package/schemas/issue/schema.yaml +11 -2
- package/schemas/issue/templates/spec.md +37 -3
- package/schemas/sdd/schema.yaml +24 -1
- package/schemas/sdd/templates/spec.md +37 -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 } 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,49 +241,121 @@ export function registerConfigCommand(program) {
|
|
|
165
241
|
.action((options) => {
|
|
166
242
|
const config = getGlobalConfig();
|
|
167
243
|
if (options.json) {
|
|
168
|
-
|
|
244
|
+
// Nested, not spread: the schema is `.passthrough()` and
|
|
245
|
+
// `config set --allow-unknown` writes any top-level key, so spreading
|
|
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
|
+
});
|
|
169
265
|
}
|
|
170
266
|
else {
|
|
171
|
-
|
|
172
|
-
// Must be the file that was actually read, not the write target — they
|
|
173
|
-
// differ until a legacy install migrates.
|
|
174
|
-
const configPath = getEffectiveConfigPath();
|
|
175
|
-
let rawConfig = {};
|
|
176
|
-
try {
|
|
177
|
-
if (fs.existsSync(configPath)) {
|
|
178
|
-
rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
catch {
|
|
182
|
-
// If reading fails, treat all as defaults
|
|
183
|
-
}
|
|
267
|
+
const rawConfig = rawGlobalConfig();
|
|
184
268
|
console.log(yaml.stringify(config).trimEnd());
|
|
185
|
-
//
|
|
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
|
+
}
|
|
186
276
|
const profileSource = rawConfig.profile !== undefined ? '(explicit)' : '(default)';
|
|
187
277
|
console.log(`\nProfile settings:`);
|
|
188
278
|
console.log(` profile: ${config.profile} ${profileSource}`);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
else if (config.workflows && config.workflows.length > 0) {
|
|
193
|
-
console.log(` workflows: ${config.workflows.join(', ')} (explicit)`);
|
|
279
|
+
const workflows = resolveListedWorkflows(config);
|
|
280
|
+
if (workflows.length === 0) {
|
|
281
|
+
console.log(` workflows: (none)`);
|
|
194
282
|
}
|
|
195
283
|
else {
|
|
196
|
-
|
|
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);
|
|
197
297
|
}
|
|
198
298
|
}
|
|
199
299
|
});
|
|
200
|
-
// config get
|
|
201
300
|
configCmd
|
|
202
301
|
.command('get <key>')
|
|
203
302
|
.description('Get a specific value (raw, scriptable)')
|
|
204
|
-
.
|
|
303
|
+
.option('--allow-unknown', 'Allow reading unknown keys')
|
|
304
|
+
.option('--json', 'Output as JSON')
|
|
305
|
+
.action((key, options) => {
|
|
306
|
+
const allowUnknown = Boolean(options.allowUnknown);
|
|
307
|
+
const keyValidation = validateConfigKeyPath(key);
|
|
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)) {
|
|
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
|
+
}
|
|
327
|
+
console.error(`Error: Invalid configuration key "${key}".${reason}`);
|
|
328
|
+
for (const line of fixLines)
|
|
329
|
+
console.error(line);
|
|
330
|
+
process.exitCode = 1;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
205
333
|
const config = getGlobalConfig();
|
|
206
334
|
const value = getNestedValue(config, key);
|
|
207
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
|
+
}
|
|
349
|
+
console.error(`Error: Key "${key}" is not set.`);
|
|
208
350
|
process.exitCode = 1;
|
|
209
351
|
return;
|
|
210
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
|
+
}
|
|
211
359
|
if (typeof value === 'object' && value !== null) {
|
|
212
360
|
console.log(JSON.stringify(value));
|
|
213
361
|
}
|
|
@@ -215,13 +363,25 @@ export function registerConfigCommand(program) {
|
|
|
215
363
|
console.log(String(value));
|
|
216
364
|
}
|
|
217
365
|
});
|
|
218
|
-
// config set
|
|
219
366
|
configCmd
|
|
220
367
|
.command('set <key> <value>')
|
|
221
368
|
.description('Set a value (auto-coerce types)')
|
|
222
369
|
.option('--string', 'Force value to be stored as string')
|
|
223
370
|
.option('--allow-unknown', 'Allow setting unknown keys')
|
|
371
|
+
.option('--json', 'Output as JSON')
|
|
224
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
|
+
};
|
|
225
385
|
const allowUnknown = Boolean(options.allowUnknown);
|
|
226
386
|
const keyValidation = validateConfigKeyPath(key);
|
|
227
387
|
// --allow-unknown relaxes the known-key check, but never the
|
|
@@ -229,60 +389,131 @@ export function registerConfigCommand(program) {
|
|
|
229
389
|
const unsafeKey = hasUnsafeKeySegment(key);
|
|
230
390
|
if (!keyValidation.valid && (!allowUnknown || unsafeKey)) {
|
|
231
391
|
const reason = keyValidation.reason ? ` ${keyValidation.reason}.` : '';
|
|
232
|
-
|
|
233
|
-
console.error('Use "tospec config list" to see available keys.');
|
|
392
|
+
const fixLines = ['Use "tospec config list" to see available keys.'];
|
|
234
393
|
if (!allowUnknown && !unsafeKey) {
|
|
235
|
-
|
|
394
|
+
fixLines.push('Pass --allow-unknown to bypass this check.');
|
|
236
395
|
}
|
|
237
|
-
|
|
396
|
+
fail(`Invalid configuration key "${key}".${reason}`, fixLines.join('\n'));
|
|
238
397
|
return;
|
|
239
398
|
}
|
|
240
399
|
const config = getGlobalConfig();
|
|
241
|
-
const coercedValue = coerceValue(value, options.string || false);
|
|
242
|
-
//
|
|
400
|
+
const coercedValue = coerceValue(value, options.string || false, key);
|
|
401
|
+
// Validate a copy before touching the config that is saved.
|
|
243
402
|
const newConfig = JSON.parse(JSON.stringify(config));
|
|
244
403
|
setNestedValue(newConfig, key, coercedValue);
|
|
245
|
-
// Validate the new config
|
|
246
404
|
const validation = validateConfig(newConfig);
|
|
247
405
|
if (!validation.success) {
|
|
248
|
-
|
|
249
|
-
process.exitCode = 1;
|
|
406
|
+
fail(`Invalid configuration - ${validation.error}`);
|
|
250
407
|
return;
|
|
251
408
|
}
|
|
252
|
-
//
|
|
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
|
+
}
|
|
253
419
|
setNestedValue(config, key, coercedValue);
|
|
254
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
|
+
}
|
|
255
457
|
const displayValue = typeof coercedValue === 'string' ? `"${coercedValue}"` : String(coercedValue);
|
|
256
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
|
+
}
|
|
257
464
|
});
|
|
258
|
-
// config unset
|
|
259
465
|
configCmd
|
|
260
466
|
.command('unset <key>')
|
|
261
467
|
.description('Remove a key (revert to default)')
|
|
262
|
-
.
|
|
468
|
+
.option('--json', 'Output as JSON')
|
|
469
|
+
.action((key, options) => {
|
|
263
470
|
const config = getGlobalConfig();
|
|
264
471
|
const existed = deleteNestedValue(config, key);
|
|
265
|
-
if (existed)
|
|
472
|
+
if (existed)
|
|
266
473
|
saveGlobalConfig(config);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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;
|
|
271
479
|
}
|
|
480
|
+
console.log(existed ? `Unset ${key} (reverted to default)` : `Key "${key}" was not set`);
|
|
272
481
|
});
|
|
273
|
-
// config reset
|
|
274
482
|
configCmd
|
|
275
483
|
.command('reset')
|
|
276
484
|
.description('Reset configuration to defaults')
|
|
277
485
|
.option('--all', 'Reset all configuration (required)')
|
|
278
486
|
.option('-y, --yes', 'Skip confirmation prompts')
|
|
487
|
+
.option('--json', 'Output as JSON (non-interactive; requires --all and --yes)')
|
|
279
488
|
.action(async (options) => {
|
|
489
|
+
// `list`, `get` and `set` all speak --json, so reset must too.
|
|
280
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
|
+
}
|
|
281
500
|
console.error('Error: --all flag is required for reset');
|
|
282
501
|
console.error('Usage: tospec config reset --all [-y]');
|
|
283
502
|
process.exitCode = 1;
|
|
284
503
|
return;
|
|
285
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
|
+
}
|
|
286
517
|
if (!options.yes) {
|
|
287
518
|
const { confirm } = await import('@inquirer/prompts');
|
|
288
519
|
let confirmed;
|
|
@@ -306,9 +537,12 @@ export function registerConfigCommand(program) {
|
|
|
306
537
|
}
|
|
307
538
|
}
|
|
308
539
|
saveGlobalConfig({ ...DEFAULT_CONFIG });
|
|
540
|
+
if (options.json) {
|
|
541
|
+
emitSuccess({ config: getGlobalConfig() });
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
309
544
|
console.log('Configuration reset to defaults');
|
|
310
545
|
});
|
|
311
|
-
// config edit
|
|
312
546
|
configCmd
|
|
313
547
|
.command('edit')
|
|
314
548
|
.description('Open config in $EDITOR')
|
|
@@ -322,13 +556,10 @@ export function registerConfigCommand(program) {
|
|
|
322
556
|
return;
|
|
323
557
|
}
|
|
324
558
|
const configPath = getGlobalConfigPath();
|
|
325
|
-
// Ensure config file exists with defaults
|
|
326
559
|
if (!fs.existsSync(configPath)) {
|
|
327
560
|
saveGlobalConfig({ ...DEFAULT_CONFIG });
|
|
328
561
|
}
|
|
329
|
-
//
|
|
330
|
-
// Avoid shell parsing to correctly handle paths with spaces in both
|
|
331
|
-
// the editor path and config path
|
|
562
|
+
// shell: false so a space in the editor or config path is not re-parsed.
|
|
332
563
|
const child = spawn(editor, [configPath], {
|
|
333
564
|
stdio: 'inherit',
|
|
334
565
|
shell: false,
|
|
@@ -346,7 +577,11 @@ export function registerConfigCommand(program) {
|
|
|
346
577
|
});
|
|
347
578
|
try {
|
|
348
579
|
const rawConfig = fs.readFileSync(configPath, 'utf-8');
|
|
349
|
-
|
|
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));
|
|
350
585
|
const validation = validateConfig(parsedConfig);
|
|
351
586
|
if (!validation.success) {
|
|
352
587
|
console.error(`Error: Invalid configuration - ${validation.error}`);
|
|
@@ -367,32 +602,71 @@ export function registerConfigCommand(program) {
|
|
|
367
602
|
process.exitCode = 1;
|
|
368
603
|
}
|
|
369
604
|
});
|
|
370
|
-
// config profile [preset]
|
|
371
605
|
configCmd
|
|
372
606
|
.command('profile [preset]')
|
|
373
607
|
.description('Configure workflow profile (interactive picker or preset shortcut)')
|
|
374
|
-
.
|
|
375
|
-
|
|
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.
|
|
376
631
|
if (preset === 'core') {
|
|
377
632
|
const config = getGlobalConfig();
|
|
378
633
|
config.profile = 'core';
|
|
379
|
-
config.workflows
|
|
634
|
+
delete config.workflows;
|
|
380
635
|
saveGlobalConfig(config);
|
|
381
|
-
|
|
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);
|
|
382
652
|
return;
|
|
383
653
|
}
|
|
384
654
|
if (preset) {
|
|
385
|
-
|
|
386
|
-
|
|
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');
|
|
387
663
|
return;
|
|
388
664
|
}
|
|
389
|
-
// Non-interactive check
|
|
390
665
|
if (!process.stdout.isTTY) {
|
|
391
|
-
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.');
|
|
392
667
|
process.exitCode = 1;
|
|
393
668
|
return;
|
|
394
669
|
}
|
|
395
|
-
// Interactive picker
|
|
396
670
|
const { select, checkbox, confirm } = await import('@inquirer/prompts');
|
|
397
671
|
try {
|
|
398
672
|
const config = getGlobalConfig();
|
|
@@ -426,9 +700,8 @@ export function registerConfigCommand(program) {
|
|
|
426
700
|
};
|
|
427
701
|
if (action === 'workflows') {
|
|
428
702
|
const formatWorkflowChoice = (workflow) => {
|
|
429
|
-
// The map is complete by construction
|
|
430
|
-
//
|
|
431
|
-
// 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.
|
|
432
705
|
const metadata = WORKFLOW_PROMPT_META[workflow] ?? {
|
|
433
706
|
name: workflow,
|
|
434
707
|
description: `Workflow: ${workflow}`,
|
|
@@ -466,9 +739,13 @@ export function registerConfigCommand(program) {
|
|
|
466
739
|
}
|
|
467
740
|
console.log();
|
|
468
741
|
config.profile = nextState.profile;
|
|
469
|
-
|
|
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;
|
|
470
748
|
saveGlobalConfig(config);
|
|
471
|
-
// Check if inside an Tospec project
|
|
472
749
|
const projectDir = process.cwd();
|
|
473
750
|
const tospecDir = path.join(projectDir, TOSPEC_DIR_NAME);
|
|
474
751
|
if (fs.existsSync(tospecDir)) {
|
|
@@ -478,9 +755,8 @@ export function registerConfigCommand(program) {
|
|
|
478
755
|
});
|
|
479
756
|
if (applyNow) {
|
|
480
757
|
try {
|
|
481
|
-
//
|
|
482
|
-
//
|
|
483
|
-
// monorepos.
|
|
758
|
+
// In-process, not `npx tospec update`: that depends on PATH and
|
|
759
|
+
// fails under global installs and monorepos.
|
|
484
760
|
await new UpdateCommand().execute(projectDir);
|
|
485
761
|
console.log('Run `tospec update` in your other projects to apply.');
|
|
486
762
|
}
|