@seanmars/tospec 0.19.0-beta.8 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -310
- package/README.md +69 -82
- package/assets/dashboard/app.js +11 -0
- package/assets/dashboard/style.css +7 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +89 -106
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/config.d.ts +9 -17
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +293 -145
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/dashboard.d.ts +57 -99
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +190 -313
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/decision.d.ts +38 -27
- package/dist/commands/decision.d.ts.map +1 -1
- package/dist/commands/decision.js +296 -128
- package/dist/commands/decision.js.map +1 -1
- package/dist/commands/metrics.d.ts +28 -51
- package/dist/commands/metrics.d.ts.map +1 -1
- package/dist/commands/metrics.js +62 -93
- package/dist/commands/metrics.js.map +1 -1
- package/dist/commands/shared-output.d.ts +12 -27
- package/dist/commands/shared-output.d.ts.map +1 -1
- package/dist/commands/shared-output.js +22 -45
- package/dist/commands/shared-output.js.map +1 -1
- package/dist/commands/show.d.ts +4 -7
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.js +23 -11
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/validate.d.ts +34 -58
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +225 -141
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/workflow/index.d.ts +1 -5
- package/dist/commands/workflow/index.d.ts.map +1 -1
- package/dist/commands/workflow/index.js +1 -5
- package/dist/commands/workflow/index.js.map +1 -1
- package/dist/commands/workflow/instructions.d.ts +14 -24
- package/dist/commands/workflow/instructions.d.ts.map +1 -1
- package/dist/commands/workflow/instructions.js +219 -128
- package/dist/commands/workflow/instructions.js.map +1 -1
- package/dist/commands/workflow/new-change.d.ts +2 -5
- package/dist/commands/workflow/new-change.d.ts.map +1 -1
- package/dist/commands/workflow/new-change.js +69 -32
- package/dist/commands/workflow/new-change.js.map +1 -1
- package/dist/commands/workflow/schemas.d.ts +1 -5
- package/dist/commands/workflow/schemas.d.ts.map +1 -1
- package/dist/commands/workflow/schemas.js +6 -17
- package/dist/commands/workflow/schemas.js.map +1 -1
- package/dist/commands/workflow/shared.d.ts +37 -42
- package/dist/commands/workflow/shared.d.ts.map +1 -1
- package/dist/commands/workflow/shared.js +23 -54
- package/dist/commands/workflow/shared.js.map +1 -1
- package/dist/commands/workflow/status.d.ts +7 -17
- package/dist/commands/workflow/status.d.ts.map +1 -1
- package/dist/commands/workflow/status.js +57 -72
- package/dist/commands/workflow/status.js.map +1 -1
- package/dist/commands/workflow/templates.d.ts +8 -8
- package/dist/commands/workflow/templates.d.ts.map +1 -1
- package/dist/commands/workflow/templates.js +32 -46
- package/dist/commands/workflow/templates.js.map +1 -1
- package/dist/core/archive.d.ts +22 -31
- package/dist/core/archive.d.ts.map +1 -1
- package/dist/core/archive.js +296 -283
- package/dist/core/archive.js.map +1 -1
- package/dist/core/artifact-graph/graph.d.ts +25 -42
- package/dist/core/artifact-graph/graph.d.ts.map +1 -1
- package/dist/core/artifact-graph/graph.js +45 -63
- package/dist/core/artifact-graph/graph.js.map +1 -1
- package/dist/core/artifact-graph/index.d.ts +1 -1
- package/dist/core/artifact-graph/index.d.ts.map +1 -1
- package/dist/core/artifact-graph/index.js +1 -1
- package/dist/core/artifact-graph/index.js.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +54 -120
- package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.js +129 -111
- package/dist/core/artifact-graph/instruction-loader.js.map +1 -1
- package/dist/core/artifact-graph/outputs.d.ts +9 -23
- package/dist/core/artifact-graph/outputs.d.ts.map +1 -1
- package/dist/core/artifact-graph/outputs.js +45 -38
- package/dist/core/artifact-graph/outputs.js.map +1 -1
- package/dist/core/artifact-graph/resolver.d.ts +36 -81
- package/dist/core/artifact-graph/resolver.d.ts.map +1 -1
- package/dist/core/artifact-graph/resolver.js +60 -101
- package/dist/core/artifact-graph/resolver.js.map +1 -1
- package/dist/core/artifact-graph/schema.d.ts +0 -6
- package/dist/core/artifact-graph/schema.d.ts.map +1 -1
- package/dist/core/artifact-graph/schema.js +7 -32
- package/dist/core/artifact-graph/schema.js.map +1 -1
- package/dist/core/artifact-graph/state.d.ts +1 -8
- package/dist/core/artifact-graph/state.d.ts.map +1 -1
- package/dist/core/artifact-graph/state.js +2 -17
- package/dist/core/artifact-graph/state.js.map +1 -1
- package/dist/core/artifact-graph/stub-detection.d.ts +6 -14
- package/dist/core/artifact-graph/stub-detection.d.ts.map +1 -1
- package/dist/core/artifact-graph/stub-detection.js +13 -16
- package/dist/core/artifact-graph/stub-detection.js.map +1 -1
- package/dist/core/artifact-graph/types.d.ts +4 -0
- package/dist/core/artifact-graph/types.d.ts.map +1 -1
- package/dist/core/artifact-graph/types.js +30 -10
- package/dist/core/artifact-graph/types.js.map +1 -1
- package/dist/core/available-tools.d.ts +3 -12
- package/dist/core/available-tools.d.ts.map +1 -1
- package/dist/core/available-tools.js +4 -13
- package/dist/core/available-tools.js.map +1 -1
- package/dist/core/change-metadata/schema.d.ts +1 -1
- package/dist/core/change-metadata/schema.d.ts.map +1 -1
- package/dist/core/change-metadata/schema.js +10 -7
- package/dist/core/change-metadata/schema.js.map +1 -1
- package/dist/core/change-presenter.d.ts +16 -27
- package/dist/core/change-presenter.d.ts.map +1 -1
- package/dist/core/change-presenter.js +53 -53
- package/dist/core/change-presenter.js.map +1 -1
- package/dist/core/change-status-policy.d.ts +4 -8
- package/dist/core/change-status-policy.d.ts.map +1 -1
- package/dist/core/change-status-policy.js +9 -17
- package/dist/core/change-status-policy.js.map +1 -1
- package/dist/core/codex-metrics.d.ts +25 -45
- package/dist/core/codex-metrics.d.ts.map +1 -1
- package/dist/core/codex-metrics.js +44 -88
- package/dist/core/codex-metrics.js.map +1 -1
- package/dist/core/codex-residue.d.ts +14 -15
- package/dist/core/codex-residue.d.ts.map +1 -1
- package/dist/core/codex-residue.js +18 -22
- package/dist/core/codex-residue.js.map +1 -1
- package/dist/core/command-generation/adapters/claude.d.ts +2 -9
- package/dist/core/command-generation/adapters/claude.d.ts.map +1 -1
- package/dist/core/command-generation/adapters/claude.js +2 -12
- package/dist/core/command-generation/adapters/claude.js.map +1 -1
- package/dist/core/command-generation/adapters/index.d.ts +1 -9
- package/dist/core/command-generation/adapters/index.d.ts.map +1 -1
- package/dist/core/command-generation/adapters/index.js +1 -9
- package/dist/core/command-generation/adapters/index.js.map +1 -1
- package/dist/core/command-generation/generator.d.ts +0 -17
- package/dist/core/command-generation/generator.d.ts.map +1 -1
- package/dist/core/command-generation/generator.js +0 -17
- package/dist/core/command-generation/generator.js.map +1 -1
- package/dist/core/command-generation/index.d.ts +2 -5
- package/dist/core/command-generation/index.d.ts.map +1 -1
- package/dist/core/command-generation/index.js +0 -9
- package/dist/core/command-generation/index.js.map +1 -1
- package/dist/core/command-generation/types.d.ts +10 -36
- package/dist/core/command-generation/types.d.ts.map +1 -1
- package/dist/core/command-generation/types.js +0 -6
- package/dist/core/command-generation/types.js.map +1 -1
- package/dist/core/command-generation/yaml.d.ts +3 -18
- package/dist/core/command-generation/yaml.d.ts.map +1 -1
- package/dist/core/command-generation/yaml.js +5 -23
- package/dist/core/command-generation/yaml.js.map +1 -1
- package/dist/core/config-prompts.d.ts +2 -4
- package/dist/core/config-prompts.d.ts.map +1 -1
- package/dist/core/config-prompts.js +2 -7
- package/dist/core/config-prompts.js.map +1 -1
- package/dist/core/config-schema.d.ts +7 -41
- package/dist/core/config-schema.d.ts.map +1 -1
- package/dist/core/config-schema.js +35 -74
- package/dist/core/config-schema.js.map +1 -1
- package/dist/core/config.d.ts +25 -49
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +22 -45
- package/dist/core/config.js.map +1 -1
- package/dist/core/dashboard-activity.d.ts +7 -9
- package/dist/core/dashboard-activity.d.ts.map +1 -1
- package/dist/core/dashboard-activity.js +26 -24
- package/dist/core/dashboard-activity.js.map +1 -1
- package/dist/core/dashboard-data.d.ts +35 -22
- package/dist/core/dashboard-data.d.ts.map +1 -1
- package/dist/core/dashboard-data.js +57 -72
- package/dist/core/dashboard-data.js.map +1 -1
- package/dist/core/global-config.d.ts +24 -53
- package/dist/core/global-config.d.ts.map +1 -1
- package/dist/core/global-config.js +38 -67
- package/dist/core/global-config.js.map +1 -1
- package/dist/core/init.d.ts +12 -28
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +90 -168
- package/dist/core/init.js.map +1 -1
- package/dist/core/list.d.ts.map +1 -1
- package/dist/core/list.js +80 -38
- package/dist/core/list.js.map +1 -1
- package/dist/core/local-server.d.ts +41 -83
- package/dist/core/local-server.d.ts.map +1 -1
- package/dist/core/local-server.js +53 -98
- package/dist/core/local-server.js.map +1 -1
- package/dist/core/markdown-render.d.ts +15 -23
- package/dist/core/markdown-render.d.ts.map +1 -1
- package/dist/core/markdown-render.js +25 -34
- package/dist/core/markdown-render.js.map +1 -1
- package/dist/core/migrate.d.ts +19 -16
- package/dist/core/migrate.d.ts.map +1 -1
- package/dist/core/migrate.js +151 -135
- package/dist/core/migrate.js.map +1 -1
- package/dist/core/parsers/change-parser.d.ts +7 -10
- package/dist/core/parsers/change-parser.d.ts.map +1 -1
- package/dist/core/parsers/change-parser.js +48 -56
- package/dist/core/parsers/change-parser.js.map +1 -1
- package/dist/core/parsers/markdown-parser.d.ts +8 -9
- package/dist/core/parsers/markdown-parser.d.ts.map +1 -1
- package/dist/core/parsers/markdown-parser.js +23 -30
- package/dist/core/parsers/markdown-parser.js.map +1 -1
- package/dist/core/parsers/requirement-blocks.d.ts +43 -15
- package/dist/core/parsers/requirement-blocks.d.ts.map +1 -1
- package/dist/core/parsers/requirement-blocks.js +142 -70
- package/dist/core/parsers/requirement-blocks.js.map +1 -1
- package/dist/core/parsers/requirement-text.d.ts +73 -79
- package/dist/core/parsers/requirement-text.d.ts.map +1 -1
- package/dist/core/parsers/requirement-text.js +137 -79
- package/dist/core/parsers/requirement-text.js.map +1 -1
- package/dist/core/parsers/spec-structure.d.ts.map +1 -1
- package/dist/core/parsers/spec-structure.js +10 -6
- package/dist/core/parsers/spec-structure.js.map +1 -1
- package/dist/core/profiles.d.ts +3 -10
- package/dist/core/profiles.d.ts.map +1 -1
- package/dist/core/profiles.js +5 -12
- package/dist/core/profiles.js.map +1 -1
- package/dist/core/project-config.d.ts +43 -44
- package/dist/core/project-config.d.ts.map +1 -1
- package/dist/core/project-config.js +107 -82
- package/dist/core/project-config.js.map +1 -1
- package/dist/core/project-layout.d.ts +9 -17
- package/dist/core/project-layout.d.ts.map +1 -1
- package/dist/core/project-layout.js +16 -26
- package/dist/core/project-layout.js.map +1 -1
- package/dist/core/root-selection.d.ts +8 -14
- package/dist/core/root-selection.d.ts.map +1 -1
- package/dist/core/root-selection.js +3 -6
- package/dist/core/root-selection.js.map +1 -1
- package/dist/core/rules.d.ts.map +1 -1
- package/dist/core/rules.js +2 -3
- package/dist/core/rules.js.map +1 -1
- package/dist/core/schema-names.d.ts +16 -0
- package/dist/core/schema-names.d.ts.map +1 -0
- package/dist/core/schema-names.js +16 -0
- package/dist/core/schema-names.js.map +1 -0
- package/dist/core/schemas/base.schema.d.ts.map +1 -1
- package/dist/core/schemas/base.schema.js +6 -12
- package/dist/core/schemas/base.schema.js.map +1 -1
- package/dist/core/schemas/change.schema.d.ts +8 -0
- package/dist/core/schemas/change.schema.d.ts.map +1 -1
- package/dist/core/schemas/change.schema.js +41 -10
- package/dist/core/schemas/change.schema.js.map +1 -1
- package/dist/core/shared/index.d.ts +2 -7
- package/dist/core/shared/index.d.ts.map +1 -1
- package/dist/core/shared/index.js +2 -7
- package/dist/core/shared/index.js.map +1 -1
- package/dist/core/shared/rules-generation.d.ts +5 -15
- package/dist/core/shared/rules-generation.d.ts.map +1 -1
- package/dist/core/shared/rules-generation.js +33 -37
- package/dist/core/shared/rules-generation.js.map +1 -1
- package/dist/core/shared/skill-generation.d.ts +28 -43
- package/dist/core/shared/skill-generation.d.ts.map +1 -1
- package/dist/core/shared/skill-generation.js +82 -51
- package/dist/core/shared/skill-generation.js.map +1 -1
- package/dist/core/shared/tool-detection.d.ts +35 -76
- package/dist/core/shared/tool-detection.d.ts.map +1 -1
- package/dist/core/shared/tool-detection.js +73 -93
- package/dist/core/shared/tool-detection.js.map +1 -1
- package/dist/core/skill-metrics.d.ts +36 -63
- package/dist/core/skill-metrics.d.ts.map +1 -1
- package/dist/core/skill-metrics.js +34 -73
- package/dist/core/skill-metrics.js.map +1 -1
- package/dist/core/spec-presenter.d.ts.map +1 -1
- package/dist/core/spec-presenter.js +5 -10
- package/dist/core/spec-presenter.js.map +1 -1
- package/dist/core/specs-apply.d.ts +16 -31
- package/dist/core/specs-apply.d.ts.map +1 -1
- package/dist/core/specs-apply.js +146 -195
- package/dist/core/specs-apply.js.map +1 -1
- package/dist/core/templates/fragments/interview.d.ts +2 -6
- package/dist/core/templates/fragments/interview.d.ts.map +1 -1
- package/dist/core/templates/fragments/interview.js +2 -6
- package/dist/core/templates/fragments/interview.js.map +1 -1
- package/dist/core/templates/fragments/next-step.d.ts +4 -8
- package/dist/core/templates/fragments/next-step.d.ts.map +1 -1
- package/dist/core/templates/fragments/next-step.js +4 -8
- package/dist/core/templates/fragments/next-step.js.map +1 -1
- package/dist/core/templates/fragments/validate.d.ts +13 -0
- package/dist/core/templates/fragments/validate.d.ts.map +1 -0
- package/dist/core/templates/fragments/validate.js +13 -0
- package/dist/core/templates/fragments/validate.js.map +1 -0
- package/dist/core/templates/fragments/verify.d.ts +9 -12
- package/dist/core/templates/fragments/verify.d.ts.map +1 -1
- package/dist/core/templates/fragments/verify.js +9 -12
- package/dist/core/templates/fragments/verify.js.map +1 -1
- package/dist/core/templates/index.d.ts +0 -6
- package/dist/core/templates/index.d.ts.map +1 -1
- package/dist/core/templates/index.js +0 -7
- package/dist/core/templates/index.js.map +1 -1
- package/dist/core/templates/skill-templates.d.ts +1 -5
- package/dist/core/templates/skill-templates.d.ts.map +1 -1
- package/dist/core/templates/skill-templates.js +0 -5
- package/dist/core/templates/skill-templates.js.map +1 -1
- package/dist/core/templates/types.d.ts +3 -7
- package/dist/core/templates/types.d.ts.map +1 -1
- package/dist/core/templates/types.js +0 -3
- package/dist/core/templates/types.js.map +1 -1
- package/dist/core/templates/workflows/apply.d.ts +3 -9
- package/dist/core/templates/workflows/apply.d.ts.map +1 -1
- package/dist/core/templates/workflows/apply.js +11 -13
- package/dist/core/templates/workflows/apply.js.map +1 -1
- package/dist/core/templates/workflows/archive.d.ts +0 -6
- package/dist/core/templates/workflows/archive.d.ts.map +1 -1
- package/dist/core/templates/workflows/archive.js +16 -5
- package/dist/core/templates/workflows/archive.js.map +1 -1
- package/dist/core/templates/workflows/decision.js +3 -3
- package/dist/core/templates/workflows/decision.js.map +1 -1
- package/dist/core/templates/workflows/explore.js +1 -1
- package/dist/core/templates/workflows/grill.d.ts.map +1 -1
- package/dist/core/templates/workflows/grill.js +0 -2
- package/dist/core/templates/workflows/grill.js.map +1 -1
- package/dist/core/templates/workflows/issue.d.ts +0 -6
- package/dist/core/templates/workflows/issue.d.ts.map +1 -1
- package/dist/core/templates/workflows/issue.js +3 -2
- package/dist/core/templates/workflows/issue.js.map +1 -1
- package/dist/core/templates/workflows/propose.d.ts +0 -6
- package/dist/core/templates/workflows/propose.d.ts.map +1 -1
- package/dist/core/templates/workflows/propose.js +2 -2
- package/dist/core/templates/workflows/propose.js.map +1 -1
- package/dist/core/templates/workflows/sync.d.ts +2 -8
- package/dist/core/templates/workflows/sync.d.ts.map +1 -1
- package/dist/core/templates/workflows/sync.js +4 -3
- package/dist/core/templates/workflows/sync.js.map +1 -1
- package/dist/core/templates/workflows/update.d.ts +0 -6
- package/dist/core/templates/workflows/update.d.ts.map +1 -1
- package/dist/core/templates/workflows/update.js +9 -2
- package/dist/core/templates/workflows/update.js.map +1 -1
- package/dist/core/update.d.ts +10 -36
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +59 -126
- package/dist/core/update.js.map +1 -1
- package/dist/core/user-state-migration.d.ts +13 -15
- package/dist/core/user-state-migration.d.ts.map +1 -1
- package/dist/core/user-state-migration.js +16 -20
- package/dist/core/user-state-migration.js.map +1 -1
- package/dist/core/validation/constants.d.ts +4 -10
- package/dist/core/validation/constants.d.ts.map +1 -1
- package/dist/core/validation/constants.js +25 -25
- package/dist/core/validation/constants.js.map +1 -1
- package/dist/core/validation/prose-length.d.ts +15 -0
- package/dist/core/validation/prose-length.d.ts.map +1 -0
- package/dist/core/validation/prose-length.js +29 -0
- package/dist/core/validation/prose-length.js.map +1 -0
- package/dist/core/validation/purpose-placeholder.d.ts +9 -16
- package/dist/core/validation/purpose-placeholder.d.ts.map +1 -1
- package/dist/core/validation/purpose-placeholder.js +30 -44
- package/dist/core/validation/purpose-placeholder.js.map +1 -1
- package/dist/core/validation/section-validator.d.ts +4 -4
- package/dist/core/validation/section-validator.d.ts.map +1 -1
- package/dist/core/validation/section-validator.js +30 -12
- package/dist/core/validation/section-validator.js.map +1 -1
- package/dist/core/validation/task-numbering.d.ts +6 -3
- package/dist/core/validation/task-numbering.d.ts.map +1 -1
- package/dist/core/validation/task-numbering.js +23 -11
- package/dist/core/validation/task-numbering.js.map +1 -1
- package/dist/core/validation/types.d.ts +18 -0
- package/dist/core/validation/types.d.ts.map +1 -1
- package/dist/core/validation/types.js +12 -1
- package/dist/core/validation/types.js.map +1 -1
- package/dist/core/validation/validator.d.ts +42 -68
- package/dist/core/validation/validator.d.ts.map +1 -1
- package/dist/core/validation/validator.js +468 -285
- package/dist/core/validation/validator.js.map +1 -1
- package/dist/prompts/searchable-multi-select.d.ts +3 -8
- package/dist/prompts/searchable-multi-select.d.ts.map +1 -1
- package/dist/prompts/searchable-multi-select.js +16 -39
- package/dist/prompts/searchable-multi-select.js.map +1 -1
- package/dist/utils/change-metadata.d.ts +11 -50
- package/dist/utils/change-metadata.d.ts.map +1 -1
- package/dist/utils/change-metadata.js +48 -67
- package/dist/utils/change-metadata.js.map +1 -1
- package/dist/utils/change-utils.d.ts +24 -76
- package/dist/utils/change-utils.d.ts.map +1 -1
- package/dist/utils/change-utils.js +95 -142
- package/dist/utils/change-utils.js.map +1 -1
- package/dist/utils/file-lock.d.ts +39 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +149 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/file-system.d.ts +12 -32
- package/dist/utils/file-system.d.ts.map +1 -1
- package/dist/utils/file-system.js +16 -40
- package/dist/utils/file-system.js.map +1 -1
- package/dist/utils/frontmatter.d.ts +7 -11
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +11 -11
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/interactive.d.ts +4 -9
- package/dist/utils/interactive.d.ts.map +1 -1
- package/dist/utils/interactive.js +2 -4
- package/dist/utils/interactive.js.map +1 -1
- package/dist/utils/item-discovery.d.ts +10 -20
- package/dist/utils/item-discovery.d.ts.map +1 -1
- package/dist/utils/item-discovery.js +31 -55
- package/dist/utils/item-discovery.js.map +1 -1
- package/dist/utils/link.d.ts +9 -18
- package/dist/utils/link.d.ts.map +1 -1
- package/dist/utils/link.js +9 -18
- package/dist/utils/link.js.map +1 -1
- package/dist/utils/requirement-diff.d.ts +13 -23
- package/dist/utils/requirement-diff.d.ts.map +1 -1
- package/dist/utils/requirement-diff.js +13 -23
- package/dist/utils/requirement-diff.js.map +1 -1
- package/dist/utils/spec-files.d.ts +7 -20
- package/dist/utils/spec-files.d.ts.map +1 -1
- package/dist/utils/spec-files.js +26 -51
- package/dist/utils/spec-files.js.map +1 -1
- package/dist/utils/task-progress.d.ts +11 -9
- package/dist/utils/task-progress.d.ts.map +1 -1
- package/dist/utils/task-progress.js +53 -32
- package/dist/utils/task-progress.js.map +1 -1
- package/dist/utils/timestamp.d.ts +5 -8
- package/dist/utils/timestamp.d.ts.map +1 -1
- package/dist/utils/timestamp.js +5 -8
- package/dist/utils/timestamp.js.map +1 -1
- package/package.json +2 -3
- package/schemas/issue/schema.yaml +8 -1
- package/schemas/issue/templates/spec.md +20 -3
- package/schemas/sdd/schema.yaml +20 -1
- package/schemas/sdd/templates/spec.md +20 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/core/validation/constants.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/core/validation/constants.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAOxC,eAAO,MAAM,0BAA0B,uCAAuC,CAAC;AAC/E,eAAO,MAAM,0BAA0B,oCAAoC,CAAC;AAE5E,eAAO,MAAM,mBAAmB;aAE9B,cAAc,EAAE,+BAA+B;aAC/C,iBAAiB,EAAE,kCAAkC;aACrD,oBAAoB,EAAE,gDAAgD;aACtE,wBAAwB,EAAE,6CAA6C;aACvE,eAAe,EAAE,2BAA2B;aAC5C,kBAAkB,EAAE,iCAAiC;aACrD,oBAAoB,EAAE,yCAAyC;aAC/D,iBAAiB,EAAE,6BAA6B;aAChD,iBAAiB,EAAE,sCAAsC;aACzD,gBAAgB,EAAE,qCAAqC;aACvD,sBAAsB;aACtB,gBAAgB,EAAE,2BAA2B;aAC7C,uBAAuB,EAAE,mCAAmC;aAG5D,iBAAiB;aACjB,sBAAsB;aAQtB,uBAAuB;aAIvB,qBAAqB;aAIrB,4BAA4B;aAK5B,oBAAoB;aAKpB,eAAe,EACb,4hBAA4hB;aAC9hB,2BAA2B,EACzB,6QAA6Q;aAC/Q,6BAA6B,EAC3B,0IAA0I;aAC5I,qBAAqB,EACnB,0IAA0I;CACpI,CAAC"}
|
|
@@ -1,22 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// Minimum character lengths
|
|
5
|
-
export const MIN_WHY_SECTION_LENGTH = 50;
|
|
1
|
+
// Thresholds shared by the validator's imperative checks. Per-section bounds
|
|
2
|
+
// such as `Why`'s are not here: those are declared per artifact in
|
|
3
|
+
// `schemas/<name>/schema.yaml` (`minSectionLength` / `maxSectionLength`).
|
|
6
4
|
export const MIN_PURPOSE_LENGTH = 50;
|
|
7
|
-
// Maximum character/item limits
|
|
8
|
-
export const MAX_WHY_SECTION_LENGTH = 1000;
|
|
9
5
|
export const MAX_REQUIREMENT_TEXT_LENGTH = 500;
|
|
10
6
|
export const MAX_DELTAS_PER_CHANGE = 10;
|
|
11
|
-
// The Purpose `tospec archive` stamps into a main spec it creates
|
|
12
|
-
// introduced the capability without a usable `## Purpose`.
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// nothing looks exactly like a check that found nothing.
|
|
7
|
+
// The Purpose `tospec archive` stamps into a main spec it creates for a delta
|
|
8
|
+
// that introduced the capability without a usable `## Purpose`. Composed from
|
|
9
|
+
// these two halves at the write site so validation recognises it through the
|
|
10
|
+
// same definition that produces it: a second spelling stops matching the day
|
|
11
|
+
// the wording changes, and a check that matches nothing looks like a clean one.
|
|
17
12
|
export const PURPOSE_PLACEHOLDER_PREFIX = 'TBD - created by archiving change ';
|
|
18
13
|
export const PURPOSE_PLACEHOLDER_SUFFIX = '. Update Purpose after archive.';
|
|
19
|
-
// Validation messages
|
|
20
14
|
export const VALIDATION_MESSAGES = {
|
|
21
15
|
// Required content
|
|
22
16
|
SCENARIO_EMPTY: 'Scenario text cannot be empty',
|
|
@@ -27,8 +21,6 @@ export const VALIDATION_MESSAGES = {
|
|
|
27
21
|
SPEC_PURPOSE_EMPTY: 'Purpose section cannot be empty',
|
|
28
22
|
SPEC_NO_REQUIREMENTS: 'Spec must have at least one requirement',
|
|
29
23
|
CHANGE_NAME_EMPTY: 'Change name cannot be empty',
|
|
30
|
-
CHANGE_WHY_TOO_SHORT: `Why section must be at least ${MIN_WHY_SECTION_LENGTH} characters`,
|
|
31
|
-
CHANGE_WHY_TOO_LONG: `Why section should not exceed ${MAX_WHY_SECTION_LENGTH} characters`,
|
|
32
24
|
CHANGE_WHAT_EMPTY: 'What Changes section cannot be empty',
|
|
33
25
|
CHANGE_NO_DELTAS: 'Change must have at least one delta',
|
|
34
26
|
CHANGE_TOO_MANY_DELTAS: `Consider splitting changes with more than ${MAX_DELTAS_PER_CHANGE} deltas`,
|
|
@@ -40,16 +32,24 @@ export const VALIDATION_MESSAGES = {
|
|
|
40
32
|
'writes for a new capability, or a `TBD`/`TODO` marker left in its place). Replace it with what this ' +
|
|
41
33
|
'capability is for, editing the main spec directly: a `## Purpose` in a delta is read only when the ' +
|
|
42
34
|
'capability is created, so it cannot replace this one.',
|
|
35
|
+
// Delta-side twins of the two rules above, worded separately on purpose: the
|
|
36
|
+
// main-spec messages say to edit the main spec, and there is none to edit
|
|
37
|
+
// while the capability exists only as a delta.
|
|
38
|
+
DELTA_PURPOSE_TOO_BRIEF: `Purpose section is too brief (less than ${MIN_PURPOSE_LENGTH} characters). ` +
|
|
39
|
+
'This capability has no main spec yet, so `tospec archive` will copy this Purpose into the one it ' +
|
|
40
|
+
'creates — write it here, in the delta, while you still can.',
|
|
41
|
+
DELTA_PURPOSE_MISSING: 'Delta has no `## Purpose`. This capability has no main spec yet, so `tospec archive` will create ' +
|
|
42
|
+
'one and, with nothing to copy, write a `TBD` placeholder as its Purpose. Open the delta with one ' +
|
|
43
|
+
'sentence on why this capability exists.',
|
|
44
|
+
DELTA_PURPOSE_IS_PLACEHOLDER: 'Purpose section is still a placeholder rather than a Purpose anyone wrote (a `TBD`/`TODO` marker, or ' +
|
|
45
|
+
'the sentence `tospec archive` writes when a delta supplies none). This capability has no main spec ' +
|
|
46
|
+
'yet, so archive will copy this text into the one it creates — replace it here, in the delta, with ' +
|
|
47
|
+
'what this capability is for.',
|
|
43
48
|
REQUIREMENT_TOO_LONG: `Requirement text is very long (>${MAX_REQUIREMENT_TEXT_LENGTH} characters). Consider breaking it down.`,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
// way out — write a delta — to a change that may genuinely have no observable
|
|
49
|
-
// behaviour, which is exactly the pressure the schema warns against when it
|
|
50
|
-
// says "never invent a Requirement to get past validation". Naming the second,
|
|
51
|
-
// legitimate exit is what stops the first from being taken dishonestly.
|
|
52
|
-
GUIDE_NO_DELTAS: 'No deltas found. Ensure your change has a specs/ directory with capability folders (e.g. specs/http-server/spec.md) containing .md files that use delta headers (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) and that each requirement includes at least one "#### Scenario:" block. Tip: run "tospec show <change-id> --json --deltas-only" to inspect parsed deltas. If this change genuinely has no externally observable effect (tooling, CI, docs-only), set `skip_specs: true` in its .tospec.yaml instead — never invent a Requirement to satisfy this check.',
|
|
49
|
+
// Guidance snippets, appended to a primary message. The skip_specs sentence is
|
|
50
|
+
// load-bearing: without it the only way out offered is "write a delta", to a
|
|
51
|
+
// change that may genuinely have no observable behaviour.
|
|
52
|
+
GUIDE_NO_DELTAS: 'No deltas found. Ensure your change has a specs/ directory with capability folders (e.g. specs/http-server/spec.md) containing .md files that use delta headers (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) and that each requirement includes at least one "#### Scenario:" block. Tip: run "tospec show <change-id> --json" to inspect parsed deltas. If this change genuinely has no externally observable effect (tooling, CI, docs-only), set `skip_specs: true` in its .tospec.yaml instead — never invent a Requirement to satisfy this check.',
|
|
53
53
|
GUIDE_MISSING_SPEC_SECTIONS: 'Missing required sections. Expected headers: "## Purpose" and "## Requirements". Example:\n## Purpose\n[brief purpose]\n\n## Requirements\n### Requirement: Clear requirement statement\nUsers SHALL ...\n\n#### Scenario: Descriptive name\n- **WHEN** ...\n- **THEN** ...',
|
|
54
54
|
GUIDE_MISSING_CHANGE_SECTIONS: 'Missing required sections. Expected headers: "## Why" and "## What Changes". Ensure deltas are documented in specs/ using delta headers.',
|
|
55
55
|
GUIDE_SCENARIO_FORMAT: 'Scenarios must use level-4 headers. Convert bullet lists into:\n#### Scenario: Short name\n- **WHEN** ...\n- **THEN** ...\n- **AND** ...',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/core/validation/constants.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/core/validation/constants.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,mEAAmE;AACnE,0EAA0E;AAC1E,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AACrC,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAC/C,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAExC,8EAA8E;AAC9E,8EAA8E;AAC9E,6EAA6E;AAC7E,6EAA6E;AAC7E,gFAAgF;AAChF,MAAM,CAAC,MAAM,0BAA0B,GAAG,oCAAoC,CAAC;AAC/E,MAAM,CAAC,MAAM,0BAA0B,GAAG,iCAAiC,CAAC;AAE5E,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,mBAAmB;IACnB,cAAc,EAAE,+BAA+B;IAC/C,iBAAiB,EAAE,kCAAkC;IACrD,oBAAoB,EAAE,gDAAgD;IACtE,wBAAwB,EAAE,6CAA6C;IACvE,eAAe,EAAE,2BAA2B;IAC5C,kBAAkB,EAAE,iCAAiC;IACrD,oBAAoB,EAAE,yCAAyC;IAC/D,iBAAiB,EAAE,6BAA6B;IAChD,iBAAiB,EAAE,sCAAsC;IACzD,gBAAgB,EAAE,qCAAqC;IACvD,sBAAsB,EAAE,6CAA6C,qBAAqB,SAAS;IACnG,gBAAgB,EAAE,2BAA2B;IAC7C,uBAAuB,EAAE,mCAAmC;IAE5D,WAAW;IACX,iBAAiB,EAAE,2CAA2C,kBAAkB,cAAc;IAC9F,sBAAsB,EACpB,2GAA2G;QAC3G,sGAAsG;QACtG,qGAAqG;QACrG,uDAAuD;IACzD,6EAA6E;IAC7E,0EAA0E;IAC1E,+CAA+C;IAC/C,uBAAuB,EACrB,2CAA2C,kBAAkB,gBAAgB;QAC7E,mGAAmG;QACnG,6DAA6D;IAC/D,qBAAqB,EACnB,mGAAmG;QACnG,mGAAmG;QACnG,yCAAyC;IAC3C,4BAA4B,EAC1B,uGAAuG;QACvG,qGAAqG;QACrG,oGAAoG;QACpG,8BAA8B;IAChC,oBAAoB,EAAE,mCAAmC,2BAA2B,0CAA0C;IAE9H,+EAA+E;IAC/E,6EAA6E;IAC7E,0DAA0D;IAC1D,eAAe,EACb,4hBAA4hB;IAC9hB,2BAA2B,EACzB,6QAA6Q;IAC/Q,6BAA6B,EAC3B,0IAA0I;IAC5I,qBAAqB,EACnB,0IAA0I;CACpI,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much prose a string carries, one unit being roughly one English
|
|
3
|
+
* character.
|
|
4
|
+
*
|
|
5
|
+
* The minimum-length thresholds were calibrated on English — 50 characters is
|
|
6
|
+
* about eight words — while the same eight words of Traditional Chinese are
|
|
7
|
+
* around 20, so a full answer reads as too brief purely for being written in
|
|
8
|
+
* Chinese. Counting a double-width character as two units puts both languages
|
|
9
|
+
* on one scale.
|
|
10
|
+
*
|
|
11
|
+
* Applied to minimums only. A maximum asks "is this an essay?", where the raw
|
|
12
|
+
* character count is the conservative answer.
|
|
13
|
+
*/
|
|
14
|
+
export declare function proseLength(text: string): number;
|
|
15
|
+
//# sourceMappingURL=prose-length.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prose-length.d.ts","sourceRoot":"","sources":["../../../src/core/validation/prose-length.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQhD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* East Asian Wide and Fullwidth code points — the ones a terminal draws in two
|
|
3
|
+
* columns. The conventional wcwidth set, not a full Unicode property scan: this
|
|
4
|
+
* is a length *heuristic*, and a missed rare block costs one unit.
|
|
5
|
+
*/
|
|
6
|
+
const WIDE_CHARACTER = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-가-힣豈-︐-︙︰--⦆¢-₩]|[\u{20000}-\u{3FFFD}]/u;
|
|
7
|
+
/**
|
|
8
|
+
* How much prose a string carries, one unit being roughly one English
|
|
9
|
+
* character.
|
|
10
|
+
*
|
|
11
|
+
* The minimum-length thresholds were calibrated on English — 50 characters is
|
|
12
|
+
* about eight words — while the same eight words of Traditional Chinese are
|
|
13
|
+
* around 20, so a full answer reads as too brief purely for being written in
|
|
14
|
+
* Chinese. Counting a double-width character as two units puts both languages
|
|
15
|
+
* on one scale.
|
|
16
|
+
*
|
|
17
|
+
* Applied to minimums only. A maximum asks "is this an essay?", where the raw
|
|
18
|
+
* character count is the conservative answer.
|
|
19
|
+
*/
|
|
20
|
+
export function proseLength(text) {
|
|
21
|
+
let units = 0;
|
|
22
|
+
// Iterating yields whole code points, so an astral CJK character counts once
|
|
23
|
+
// (as 2 units) rather than twice as its surrogate halves.
|
|
24
|
+
for (const character of text) {
|
|
25
|
+
units += WIDE_CHARACTER.test(character) ? 2 : 1;
|
|
26
|
+
}
|
|
27
|
+
return units;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=prose-length.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prose-length.js","sourceRoot":"","sources":["../../../src/core/validation/prose-length.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,cAAc,GAClB,+DAA+D,CAAC;AAElE;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,6EAA6E;IAC7E,0DAA0D;IAC1D,KAAK,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;QAC7B,KAAK,IAAI,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -2,24 +2,17 @@
|
|
|
2
2
|
* Detects a `## Purpose` that is still a placeholder rather than one somebody
|
|
3
3
|
* wrote.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* MIN_PURPOSE_LENGTH, so the brevity check cannot reach it: the one rule that
|
|
8
|
-
* exists to catch a Purpose nobody wrote is satisfied by the exact string
|
|
9
|
-
* meaning nobody wrote one. Nothing else read it afterwards, so the capability
|
|
10
|
-
* kept a to-do in it while every command reported success.
|
|
5
|
+
* Archive's generated placeholder is longer than MIN_PURPOSE_LENGTH, so the
|
|
6
|
+
* brevity check cannot reach it.
|
|
11
7
|
*
|
|
12
8
|
* Two things count, and deliberately nothing else:
|
|
13
9
|
*
|
|
14
|
-
* - the placeholder
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* - a `TBD` or `TODO` *opening* the Purpose, which is the marker left behind
|
|
18
|
-
* when someone is told to leave a brief placeholder and never comes back.
|
|
10
|
+
* - the generated placeholder, recognised through the same constants the writer
|
|
11
|
+
* composes it from, wherever it sits;
|
|
12
|
+
* - a `TBD` or `TODO` *opening* the Purpose.
|
|
19
13
|
*
|
|
20
14
|
* A marker inside a sentence is left alone: "the retry budget is TBD pending
|
|
21
|
-
* benchmarks" is a real Purpose with an open question in it
|
|
22
|
-
* would teach people to ignore the warning.
|
|
15
|
+
* benchmarks" is a real Purpose with an open question in it.
|
|
23
16
|
*/
|
|
24
17
|
export interface PurposePlaceholderIssue {
|
|
25
18
|
/** 1-based line of the placeholder text, when it can be located. */
|
|
@@ -29,9 +22,9 @@ export interface PurposePlaceholderIssue {
|
|
|
29
22
|
* Reports the Purpose of a main spec as an unwritten placeholder, or null when
|
|
30
23
|
* it reads as authored content.
|
|
31
24
|
*
|
|
32
|
-
* An empty Purpose needs no branch
|
|
33
|
-
*
|
|
34
|
-
*
|
|
25
|
+
* An empty Purpose needs no branch: neither rule matches empty text, so it falls
|
|
26
|
+
* through to null and SPEC_PURPOSE_EMPTY covers it. A Purpose that is nothing
|
|
27
|
+
* but a fenced block reduces to the same empty text here.
|
|
35
28
|
*/
|
|
36
29
|
export declare function findPurposePlaceholderIssue(overview: string, content?: string): PurposePlaceholderIssue | null;
|
|
37
30
|
//# sourceMappingURL=purpose-placeholder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"purpose-placeholder.d.ts","sourceRoot":"","sources":["../../../src/core/validation/purpose-placeholder.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"purpose-placeholder.d.ts","sourceRoot":"","sources":["../../../src/core/validation/purpose-placeholder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA2CD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,uBAAuB,GAAG,IAAI,CAOhC"}
|
|
@@ -2,62 +2,51 @@
|
|
|
2
2
|
* Detects a `## Purpose` that is still a placeholder rather than one somebody
|
|
3
3
|
* wrote.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* MIN_PURPOSE_LENGTH, so the brevity check cannot reach it: the one rule that
|
|
8
|
-
* exists to catch a Purpose nobody wrote is satisfied by the exact string
|
|
9
|
-
* meaning nobody wrote one. Nothing else read it afterwards, so the capability
|
|
10
|
-
* kept a to-do in it while every command reported success.
|
|
5
|
+
* Archive's generated placeholder is longer than MIN_PURPOSE_LENGTH, so the
|
|
6
|
+
* brevity check cannot reach it.
|
|
11
7
|
*
|
|
12
8
|
* Two things count, and deliberately nothing else:
|
|
13
9
|
*
|
|
14
|
-
* - the placeholder
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* - a `TBD` or `TODO` *opening* the Purpose, which is the marker left behind
|
|
18
|
-
* when someone is told to leave a brief placeholder and never comes back.
|
|
10
|
+
* - the generated placeholder, recognised through the same constants the writer
|
|
11
|
+
* composes it from, wherever it sits;
|
|
12
|
+
* - a `TBD` or `TODO` *opening* the Purpose.
|
|
19
13
|
*
|
|
20
14
|
* A marker inside a sentence is left alone: "the retry budget is TBD pending
|
|
21
|
-
* benchmarks" is a real Purpose with an open question in it
|
|
22
|
-
* would teach people to ignore the warning.
|
|
15
|
+
* benchmarks" is a real Purpose with an open question in it.
|
|
23
16
|
*/
|
|
24
|
-
import { buildCodeFenceMask } from '../parsers/requirement-text.js';
|
|
17
|
+
import { buildCodeFenceMask, normalizeDocument } from '../parsers/requirement-text.js';
|
|
25
18
|
import { PURPOSE_PLACEHOLDER_PREFIX, PURPOSE_PLACEHOLDER_SUFFIX } from './constants.js';
|
|
26
19
|
/**
|
|
27
20
|
* A `TBD`/`TODO` opening the Purpose. The lookahead keeps it off a longer word
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
21
|
+
* starting with those letters ("TODOs are tracked in the issue") while allowing
|
|
22
|
+
* the punctuation a marker carries: `TODO:`, `TBD -`. It rejects any letter,
|
|
23
|
+
* digit or combining mark rather than only the ASCII ones `\b` knows about,
|
|
24
|
+
* since a Purpose is prose and prose is not always Latin script.
|
|
32
25
|
*/
|
|
33
26
|
const LEADING_MARKER = /^(?:TBD|TODO)(?![\p{L}\p{N}\p{M}_])/iu;
|
|
34
27
|
const PURPOSE_HEADER = /^ {0,3}##(?!#)[ \t]+Purpose[ \t]*$/i;
|
|
35
28
|
const TOP_LEVEL_HEADER = /^ {0,3}#{1,2}(?!#)[ \t]+/;
|
|
36
29
|
/**
|
|
37
|
-
* The lines of `text` outside a fenced code block
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* spec documenting the sentence archive writes is reported as carrying it — the
|
|
41
|
-
* check failing the one document that explains it. `buildCodeFenceMask` is the
|
|
42
|
-
* masker the requirement and structure parsers already share, so all three read
|
|
43
|
-
* fences the same way.
|
|
30
|
+
* The lines of `text` outside a fenced code block. Fenced content is quoted
|
|
31
|
+
* material, not the Purpose speaking: without this, a spec documenting the
|
|
32
|
+
* sentence archive writes is reported as carrying it.
|
|
44
33
|
*/
|
|
45
34
|
function unfencedLines(text) {
|
|
46
|
-
const lines = text
|
|
35
|
+
const lines = normalizeDocument(text).split('\n');
|
|
47
36
|
const fenced = buildCodeFenceMask(lines);
|
|
48
37
|
return lines.filter((_, index) => !fenced[index]);
|
|
49
38
|
}
|
|
50
39
|
/**
|
|
51
40
|
* Offset of the generated placeholder in `text`, or undefined. Matched as its
|
|
52
|
-
* two fixed halves in order because the change name between them varies
|
|
53
|
-
* check follows the writer's own definition
|
|
41
|
+
* two fixed halves in order because the change name between them varies, so the
|
|
42
|
+
* check follows the writer's own definition rather than a copy of it.
|
|
54
43
|
*/
|
|
55
44
|
function generatedPlaceholderPrefixIndex(text) {
|
|
56
45
|
let suffixAt = text.indexOf(PURPOSE_PLACEHOLDER_SUFFIX);
|
|
57
46
|
while (suffixAt !== -1) {
|
|
58
47
|
// The closest prefix before this suffix: authored prose can mention the
|
|
59
|
-
// prefix above the real placeholder, and the first
|
|
60
|
-
//
|
|
48
|
+
// prefix above the real placeholder, and the first one would point the
|
|
49
|
+
// diagnostic at the explanation instead of the text to replace.
|
|
61
50
|
const prefixAt = text.lastIndexOf(PURPOSE_PLACEHOLDER_PREFIX, suffixAt);
|
|
62
51
|
if (prefixAt !== -1)
|
|
63
52
|
return prefixAt;
|
|
@@ -69,9 +58,9 @@ function generatedPlaceholderPrefixIndex(text) {
|
|
|
69
58
|
* Reports the Purpose of a main spec as an unwritten placeholder, or null when
|
|
70
59
|
* it reads as authored content.
|
|
71
60
|
*
|
|
72
|
-
* An empty Purpose needs no branch
|
|
73
|
-
*
|
|
74
|
-
*
|
|
61
|
+
* An empty Purpose needs no branch: neither rule matches empty text, so it falls
|
|
62
|
+
* through to null and SPEC_PURPOSE_EMPTY covers it. A Purpose that is nothing
|
|
63
|
+
* but a fenced block reduces to the same empty text here.
|
|
75
64
|
*/
|
|
76
65
|
export function findPurposePlaceholderIssue(overview, content) {
|
|
77
66
|
const prose = unfencedLines(overview).join('\n').trim();
|
|
@@ -79,7 +68,7 @@ export function findPurposePlaceholderIssue(overview, content) {
|
|
|
79
68
|
if (!leading && generatedPlaceholderPrefixIndex(prose) === undefined)
|
|
80
69
|
return null;
|
|
81
70
|
// Which rule matched decides where the placeholder sits, so the locator is
|
|
82
|
-
// told. When both match the leading marker wins
|
|
71
|
+
// told. When both match the leading marker wins, being the earlier.
|
|
83
72
|
return { line: content === undefined ? undefined : findPlaceholderLine(content, leading) };
|
|
84
73
|
}
|
|
85
74
|
/**
|
|
@@ -87,25 +76,22 @@ export function findPurposePlaceholderIssue(overview, content) {
|
|
|
87
76
|
* at the text to replace rather than at the file.
|
|
88
77
|
*
|
|
89
78
|
* Which line that is depends on the rule that matched. A leading marker is the
|
|
90
|
-
* section's first non-blank line by definition; the generated sentence is not
|
|
91
|
-
*
|
|
92
|
-
* points at a line the reader can see is fine, which reads as the check being
|
|
93
|
-
* broken rather than the Purpose being unwritten.
|
|
79
|
+
* section's first non-blank line by definition; the generated sentence is not,
|
|
80
|
+
* and can follow prose somebody wrote.
|
|
94
81
|
*
|
|
95
|
-
* Undefined when the placeholder cannot be located
|
|
96
|
-
*
|
|
97
|
-
* the same line on Windows and on macOS/Linux.
|
|
82
|
+
* Undefined when the placeholder cannot be located: a wrong line number is worse
|
|
83
|
+
* than none.
|
|
98
84
|
*/
|
|
99
85
|
function findPlaceholderLine(content, leading) {
|
|
100
|
-
const lines = content
|
|
86
|
+
const lines = normalizeDocument(content).split('\n');
|
|
101
87
|
const fenced = buildCodeFenceMask(lines);
|
|
102
88
|
const headerIndex = lines.findIndex((line, index) => !fenced[index] && PURPOSE_HEADER.test(line));
|
|
103
89
|
if (headerIndex === -1)
|
|
104
90
|
return undefined;
|
|
105
91
|
const purposeLines = [];
|
|
106
92
|
for (let i = headerIndex + 1; i < lines.length; i++) {
|
|
107
|
-
//
|
|
108
|
-
//
|
|
93
|
+
// Skipped as in detection, and so a `## Requirements` quoted inside a fence
|
|
94
|
+
// cannot end the section early.
|
|
109
95
|
if (fenced[i])
|
|
110
96
|
continue;
|
|
111
97
|
if (TOP_LEVEL_HEADER.test(lines[i]))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"purpose-placeholder.js","sourceRoot":"","sources":["../../../src/core/validation/purpose-placeholder.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"purpose-placeholder.js","sourceRoot":"","sources":["../../../src/core/validation/purpose-placeholder.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import type { ValidationIssue } from './types.js';
|
|
|
2
2
|
export interface SectionValidationRules {
|
|
3
3
|
requiredSections: string[];
|
|
4
4
|
minSectionLength: Record<string, number>;
|
|
5
|
+
maxSectionLength?: Record<string, number>;
|
|
5
6
|
}
|
|
6
7
|
export interface SectionValidationContext {
|
|
7
8
|
/** Reported as ValidationIssue.path (typically the file's relative path). */
|
|
@@ -10,10 +11,9 @@ export interface SectionValidationContext {
|
|
|
10
11
|
templateRef?: string;
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* types don't require validator code changes.
|
|
14
|
+
* Heading rules for artifacts (ticket.md, design.md, task.md, ...) that have no
|
|
15
|
+
* Requirement grammar to check structurally. The rules live in schema.yaml so a
|
|
16
|
+
* new document type needs no validator code.
|
|
17
17
|
*/
|
|
18
18
|
export declare function validateSections(content: string, rules: SectionValidationRules, context: SectionValidationContext): ValidationIssue[];
|
|
19
19
|
//# sourceMappingURL=section-validator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section-validator.d.ts","sourceRoot":"","sources":["../../../src/core/validation/section-validator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"section-validator.d.ts","sourceRoot":"","sources":["../../../src/core/validation/section-validator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,wBAAwB;IACvC,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAcD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,sBAAsB,EAC7B,OAAO,EAAE,wBAAwB,GAChC,eAAe,EAAE,CAiEnB"}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import { MarkdownParser, findSection } from '../parsers/markdown-parser.js';
|
|
2
|
+
import { proseLength } from './prose-length.js';
|
|
2
3
|
/**
|
|
3
4
|
* The prose a section actually carries, with HTML comments removed.
|
|
4
5
|
*
|
|
5
6
|
* Templates put their guidance in `<!-- ... -->`, which counts as text to a
|
|
6
|
-
* naive length check
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* (round 4 report 4). What is measured has to be what the author wrote.
|
|
7
|
+
* naive length check: the proposal template's Why hint is 71 characters against
|
|
8
|
+
* a 50-character minimum, so an untouched template would satisfy its own
|
|
9
|
+
* threshold while a real ten-word answer is warned about.
|
|
10
10
|
*/
|
|
11
11
|
function authoredContent(sectionContent) {
|
|
12
12
|
return sectionContent.replace(/<!--[\s\S]*?-->/g, '').trim();
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* types don't require validator code changes.
|
|
15
|
+
* Heading rules for artifacts (ticket.md, design.md, task.md, ...) that have no
|
|
16
|
+
* Requirement grammar to check structurally. The rules live in schema.yaml so a
|
|
17
|
+
* new document type needs no validator code.
|
|
19
18
|
*/
|
|
20
19
|
export function validateSections(content, rules, context) {
|
|
21
20
|
const issues = [];
|
|
@@ -32,9 +31,9 @@ export function validateSections(content, rules, context) {
|
|
|
32
31
|
continue;
|
|
33
32
|
}
|
|
34
33
|
if (!authoredContent(section.content)) {
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
34
|
+
// Told apart because the fix differs: a blank section needs writing, a
|
|
35
|
+
// comment-only one needs the placeholder replaced — and "is empty" reads
|
|
36
|
+
// as wrong to someone looking at text under the heading.
|
|
38
37
|
const onlyComments = section.content.trim().length > 0;
|
|
39
38
|
issues.push({
|
|
40
39
|
level: 'WARNING',
|
|
@@ -50,7 +49,9 @@ export function validateSections(content, rules, context) {
|
|
|
50
49
|
if (!section) {
|
|
51
50
|
continue; // absence is already reported above when also required
|
|
52
51
|
}
|
|
53
|
-
|
|
52
|
+
// Width-aware, so the English-calibrated threshold means the same amount
|
|
53
|
+
// of prose in a double-width script.
|
|
54
|
+
const length = proseLength(authoredContent(section.content));
|
|
54
55
|
if (length > 0 && length < minLength) {
|
|
55
56
|
issues.push({
|
|
56
57
|
level: 'WARNING',
|
|
@@ -59,6 +60,23 @@ export function validateSections(content, rules, context) {
|
|
|
59
60
|
});
|
|
60
61
|
}
|
|
61
62
|
}
|
|
63
|
+
for (const [sectionTitle, maxLength] of Object.entries(rules.maxSectionLength ?? {})) {
|
|
64
|
+
const section = findSection(sections, sectionTitle);
|
|
65
|
+
if (!section)
|
|
66
|
+
continue;
|
|
67
|
+
// Raw characters: unlike the floor, a ceiling asks "is this an essay?", and
|
|
68
|
+
// widening the unit for double-width scripts would reject long but
|
|
69
|
+
// legitimate CJK prose.
|
|
70
|
+
// tospec/decisions/20260919_193441-width-aware-minimum-length.md
|
|
71
|
+
const length = authoredContent(section.content).length;
|
|
72
|
+
if (length > maxLength) {
|
|
73
|
+
issues.push({
|
|
74
|
+
level: 'WARNING',
|
|
75
|
+
path: context.path,
|
|
76
|
+
message: `Section "${sectionTitle}" is longer than the recommended ${maxLength} characters (found ${length}) — consider moving detail into design.md or splitting the change`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
62
80
|
return issues;
|
|
63
81
|
}
|
|
64
82
|
//# sourceMappingURL=section-validator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section-validator.js","sourceRoot":"","sources":["../../../src/core/validation/section-validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"section-validator.js","sourceRoot":"","sources":["../../../src/core/validation/section-validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Task IDs are how apply refers to work ("continue with 2.3"), so a duplicated
|
|
5
5
|
* ID or one that contradicts its group heading makes that reference point at
|
|
6
|
-
* the wrong item
|
|
6
|
+
* the wrong item, silently.
|
|
7
7
|
*
|
|
8
8
|
* Numbering is interpreted only inside numbered group headings. Unnumbered
|
|
9
|
-
* sections
|
|
10
|
-
*
|
|
9
|
+
* sections and tasks are a legitimate style, not a defect.
|
|
10
|
+
*
|
|
11
|
+
* Gaps are not checked: 1.1 followed by 1.3 still leaves every ID pointing at
|
|
12
|
+
* exactly one task, which is the property this module defends. Demanding a
|
|
13
|
+
* renumber would invalidate every reference already made.
|
|
11
14
|
*/
|
|
12
15
|
export interface TaskNumberingDocument {
|
|
13
16
|
/** Display path, relative to the change directory. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-numbering.d.ts","sourceRoot":"","sources":["../../../src/core/validation/task-numbering.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task-numbering.d.ts","sourceRoot":"","sources":["../../../src/core/validation/task-numbering.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,qBAAqB;IACpC,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAsBD,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,SAAS,qBAAqB,EAAE,GAC1C,kBAAkB,EAAE,CAkEtB"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { parseTasksFromContent } from '../../utils/task-progress.js';
|
|
2
|
+
import { buildCodeFenceMask, normalizeDocument } from '../parsers/requirement-text.js';
|
|
2
3
|
/**
|
|
3
4
|
* A group heading is level 2 or 3, because the two schemas differ: sdd's
|
|
4
|
-
* `tasks.md` numbers slices at `## 1.`, while issue's `task.md` puts
|
|
5
|
-
* under an unnumbered `## Tasks`. Matching
|
|
6
|
-
*
|
|
5
|
+
* `tasks.md` numbers slices at `## 1.`, while issue's `task.md` puts
|
|
6
|
+
* `### 1. Fix` under an unnumbered `## Tasks`. Matching one level would leave
|
|
7
|
+
* the check inert for the other schema.
|
|
7
8
|
*/
|
|
8
9
|
const GROUP_HEADING = /^ {0,3}#{2,3}[ \t]+/;
|
|
9
10
|
const NUMBERED_GROUP_HEADING = /^ {0,3}#{2,3}[ \t]+(\d+)\./;
|
|
@@ -15,24 +16,35 @@ export function findTaskNumberingIssues(documents) {
|
|
|
15
16
|
const issues = [];
|
|
16
17
|
const firstLocationById = new Map();
|
|
17
18
|
for (const document of documents) {
|
|
18
|
-
const lines = document.content.split(
|
|
19
|
+
const lines = normalizeDocument(document.content).split('\n');
|
|
19
20
|
if (!lines.some((line) => NUMBERED_GROUP_HEADING.test(line)))
|
|
20
21
|
continue;
|
|
22
|
+
// Parsed once for the whole document, then addressed by line — never
|
|
23
|
+
// `parseTasksFromContent(line)` per line, which opts out of fence masking:
|
|
24
|
+
// the mask is built from surrounding lines, so a line handed over alone can
|
|
25
|
+
// never be inside a fence, and a tasks.md documenting its own format would
|
|
26
|
+
// draw warnings over its fenced examples.
|
|
27
|
+
const taskByLine = new Map();
|
|
28
|
+
for (const task of parseTasksFromContent(document.content)) {
|
|
29
|
+
taskByLine.set(task.line, task);
|
|
30
|
+
}
|
|
31
|
+
// Headings need the mask for the same reason: a `## 9. …` inside a fence is
|
|
32
|
+
// a quoted example, and letting it open a numbered group misjudges every
|
|
33
|
+
// real task below it.
|
|
34
|
+
const fenced = buildCodeFenceMask(lines);
|
|
21
35
|
let currentGroup;
|
|
22
36
|
lines.forEach((line, index) => {
|
|
37
|
+
if (fenced[index])
|
|
38
|
+
return;
|
|
23
39
|
if (GROUP_HEADING.test(line)) {
|
|
24
|
-
// An unnumbered heading at either level ends the numbered group
|
|
25
|
-
//
|
|
40
|
+
// An unnumbered heading at either level ends the numbered group, so it
|
|
41
|
+
// cannot leak across sections.
|
|
26
42
|
currentGroup = line.match(NUMBERED_GROUP_HEADING)?.[1];
|
|
27
43
|
return;
|
|
28
44
|
}
|
|
29
45
|
if (currentGroup === undefined)
|
|
30
46
|
return;
|
|
31
|
-
|
|
32
|
-
// blocks and all. A checkbox in a fence is miscounted by both or neither;
|
|
33
|
-
// making only this side fence-aware would put the two readers back out of
|
|
34
|
-
// step, which is the failure mode worth avoiding here.
|
|
35
|
-
const task = parseTasksFromContent(line)[0];
|
|
47
|
+
const task = taskByLine.get(index + 1);
|
|
36
48
|
const id = task?.text.match(TASK_ID)?.[1];
|
|
37
49
|
if (!id)
|
|
38
50
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-numbering.js","sourceRoot":"","sources":["../../../src/core/validation/task-numbering.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,
|
|
1
|
+
{"version":3,"file":"task-numbering.js","sourceRoot":"","sources":["../../../src/core/validation/task-numbering.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,16 @@ export interface ValidationIssue {
|
|
|
5
5
|
message: string;
|
|
6
6
|
line?: number;
|
|
7
7
|
column?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Background explaining the rule rather than this occurrence of it — why the
|
|
10
|
+
* check is a warning, what it cannot see, what to do in general.
|
|
11
|
+
*
|
|
12
|
+
* Separate from the per-occurrence `message` because it is identical every
|
|
13
|
+
* time the rule fires; inlined, it repeats once per occurrence and buries the
|
|
14
|
+
* names that differ. `createReport` hoists these into
|
|
15
|
+
* `ValidationReport.notes`, deduplicated, and clears them from the issues.
|
|
16
|
+
*/
|
|
17
|
+
note?: string;
|
|
8
18
|
}
|
|
9
19
|
export interface ValidationReport {
|
|
10
20
|
valid: boolean;
|
|
@@ -14,5 +24,13 @@ export interface ValidationReport {
|
|
|
14
24
|
warnings: number;
|
|
15
25
|
info: number;
|
|
16
26
|
};
|
|
27
|
+
/** Distinct `ValidationIssue.note` values, each carried once. */
|
|
28
|
+
notes?: string[];
|
|
17
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Severity symbol for human output. Shared so no two surfaces show different
|
|
32
|
+
* glyphs for one level; the line formats around it differ on purpose
|
|
33
|
+
* (`validate` prints the level, `archive` does not), so only this is common.
|
|
34
|
+
*/
|
|
35
|
+
export declare function issueSymbol(level: ValidationLevel | string): string;
|
|
18
36
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/validation/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/validation/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,GAAG,MAAM,CAInE"}
|