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