@rsaglobaltech/specgate 0.8.1
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 +1016 -0
- package/README.md +148 -0
- package/bin/specgate.js +2 -0
- package/bin/specgate.ts +383 -0
- package/dist/bin/specgate.js +342 -0
- package/dist/bin/specgate.js.map +1 -0
- package/dist/packages/core/src/application/AddRequirementUseCase.js +24 -0
- package/dist/packages/core/src/application/AddRequirementUseCase.js.map +1 -0
- package/dist/packages/core/src/application/ArchiveChangeUseCase.js +185 -0
- package/dist/packages/core/src/application/ArchiveChangeUseCase.js.map +1 -0
- package/dist/packages/core/src/application/CheckAgainstLockUseCase.js +134 -0
- package/dist/packages/core/src/application/CheckAgainstLockUseCase.js.map +1 -0
- package/dist/packages/core/src/application/GenerateAgentPromptUseCase.js +25 -0
- package/dist/packages/core/src/application/GenerateAgentPromptUseCase.js.map +1 -0
- package/dist/packages/core/src/application/GeneratePlanUseCase.js +135 -0
- package/dist/packages/core/src/application/GeneratePlanUseCase.js.map +1 -0
- package/dist/packages/core/src/application/LinkRequirementUseCase.js +25 -0
- package/dist/packages/core/src/application/LinkRequirementUseCase.js.map +1 -0
- package/dist/packages/core/src/application/RemovePackUseCase.js +28 -0
- package/dist/packages/core/src/application/RemovePackUseCase.js.map +1 -0
- package/dist/packages/core/src/application/UpdateRequirementStatusUseCase.js +37 -0
- package/dist/packages/core/src/application/UpdateRequirementStatusUseCase.js.map +1 -0
- package/dist/packages/core/src/application/ValidateChangeUseCase.js +86 -0
- package/dist/packages/core/src/application/ValidateChangeUseCase.js.map +1 -0
- package/dist/packages/core/src/application/ValidateProjectUseCase.js +101 -0
- package/dist/packages/core/src/application/ValidateProjectUseCase.js.map +1 -0
- package/dist/packages/core/src/application/ports/IDomainPackRepository.js +3 -0
- package/dist/packages/core/src/application/ports/IDomainPackRepository.js.map +1 -0
- package/dist/packages/core/src/application/ports/IHarnessConfigRepository.js +3 -0
- package/dist/packages/core/src/application/ports/IHarnessConfigRepository.js.map +1 -0
- package/dist/packages/core/src/application/ports/ILockfileRepository.js +3 -0
- package/dist/packages/core/src/application/ports/ILockfileRepository.js.map +1 -0
- package/dist/packages/core/src/application/ports/IManifestRepository.js +3 -0
- package/dist/packages/core/src/application/ports/IManifestRepository.js.map +1 -0
- package/dist/packages/core/src/application/ports/IProjectRepository.js +3 -0
- package/dist/packages/core/src/application/ports/IProjectRepository.js.map +1 -0
- package/dist/packages/core/src/application/ports/IRequirementGraphRepository.js +3 -0
- package/dist/packages/core/src/application/ports/IRequirementGraphRepository.js.map +1 -0
- package/dist/packages/core/src/application/ports/ITraceabilityRepository.js +3 -0
- package/dist/packages/core/src/application/ports/ITraceabilityRepository.js.map +1 -0
- package/dist/packages/core/src/domain/AgentPrompt.js +79 -0
- package/dist/packages/core/src/domain/AgentPrompt.js.map +1 -0
- package/dist/packages/core/src/domain/ArchivePlan.js +26 -0
- package/dist/packages/core/src/domain/ArchivePlan.js.map +1 -0
- package/dist/packages/core/src/domain/AuthoringScope.js +48 -0
- package/dist/packages/core/src/domain/AuthoringScope.js.map +1 -0
- package/dist/packages/core/src/domain/ChangeTemplates.js +133 -0
- package/dist/packages/core/src/domain/ChangeTemplates.js.map +1 -0
- package/dist/packages/core/src/domain/CucumberMessages.js +256 -0
- package/dist/packages/core/src/domain/CucumberMessages.js.map +1 -0
- package/dist/packages/core/src/domain/DeclaredArtifacts.js +117 -0
- package/dist/packages/core/src/domain/DeclaredArtifacts.js.map +1 -0
- package/dist/packages/core/src/domain/DeltaSpec.js +219 -0
- package/dist/packages/core/src/domain/DeltaSpec.js.map +1 -0
- package/dist/packages/core/src/domain/Diagnostic.js +75 -0
- package/dist/packages/core/src/domain/Diagnostic.js.map +1 -0
- package/dist/packages/core/src/domain/DomainPack.js +62 -0
- package/dist/packages/core/src/domain/DomainPack.js.map +1 -0
- package/dist/packages/core/src/domain/EmptyAttempt.js +60 -0
- package/dist/packages/core/src/domain/EmptyAttempt.js.map +1 -0
- package/dist/packages/core/src/domain/FileChangeSet.js +37 -0
- package/dist/packages/core/src/domain/FileChangeSet.js.map +1 -0
- package/dist/packages/core/src/domain/Gherkin.js +272 -0
- package/dist/packages/core/src/domain/Gherkin.js.map +1 -0
- package/dist/packages/core/src/domain/GherkinDialects.js +97 -0
- package/dist/packages/core/src/domain/GherkinDialects.js.map +1 -0
- package/dist/packages/core/src/domain/GherkinQuality.js +160 -0
- package/dist/packages/core/src/domain/GherkinQuality.js.map +1 -0
- package/dist/packages/core/src/domain/GherkinTags.js +119 -0
- package/dist/packages/core/src/domain/GherkinTags.js.map +1 -0
- package/dist/packages/core/src/domain/GitSafety.js +77 -0
- package/dist/packages/core/src/domain/GitSafety.js.map +1 -0
- package/dist/packages/core/src/domain/HarnessConfig.js +58 -0
- package/dist/packages/core/src/domain/HarnessConfig.js.map +1 -0
- package/dist/packages/core/src/domain/HarnessReport.js +162 -0
- package/dist/packages/core/src/domain/HarnessReport.js.map +1 -0
- package/dist/packages/core/src/domain/HarnessRun.js +154 -0
- package/dist/packages/core/src/domain/HarnessRun.js.map +1 -0
- package/dist/packages/core/src/domain/InboundChange.js +127 -0
- package/dist/packages/core/src/domain/InboundChange.js.map +1 -0
- package/dist/packages/core/src/domain/Language.js +112 -0
- package/dist/packages/core/src/domain/Language.js.map +1 -0
- package/dist/packages/core/src/domain/Lockfile.js +64 -0
- package/dist/packages/core/src/domain/Lockfile.js.map +1 -0
- package/dist/packages/core/src/domain/PackContribution.js +106 -0
- package/dist/packages/core/src/domain/PackContribution.js.map +1 -0
- package/dist/packages/core/src/domain/PackDelta.js +298 -0
- package/dist/packages/core/src/domain/PackDelta.js.map +1 -0
- package/dist/packages/core/src/domain/PackSpec.js +481 -0
- package/dist/packages/core/src/domain/PackSpec.js.map +1 -0
- package/dist/packages/core/src/domain/ProfileMatch.js +87 -0
- package/dist/packages/core/src/domain/ProfileMatch.js.map +1 -0
- package/dist/packages/core/src/domain/Reconciliation.js +72 -0
- package/dist/packages/core/src/domain/Reconciliation.js.map +1 -0
- package/dist/packages/core/src/domain/RequirementGraph.js +197 -0
- package/dist/packages/core/src/domain/RequirementGraph.js.map +1 -0
- package/dist/packages/core/src/domain/RequirementPlan.js +85 -0
- package/dist/packages/core/src/domain/RequirementPlan.js.map +1 -0
- package/dist/packages/core/src/domain/RequirementReadiness.js +115 -0
- package/dist/packages/core/src/domain/RequirementReadiness.js.map +1 -0
- package/dist/packages/core/src/domain/RequirementSyntax.js +91 -0
- package/dist/packages/core/src/domain/RequirementSyntax.js.map +1 -0
- package/dist/packages/core/src/domain/ResumeState.js +115 -0
- package/dist/packages/core/src/domain/ResumeState.js.map +1 -0
- package/dist/packages/core/src/domain/RunBudget.js +103 -0
- package/dist/packages/core/src/domain/RunBudget.js.map +1 -0
- package/dist/packages/core/src/domain/SpecDiff.js +48 -0
- package/dist/packages/core/src/domain/SpecDiff.js.map +1 -0
- package/dist/packages/core/src/domain/SpecParser.js +406 -0
- package/dist/packages/core/src/domain/SpecParser.js.map +1 -0
- package/dist/packages/core/src/domain/SpecopsManifest.js +90 -0
- package/dist/packages/core/src/domain/SpecopsManifest.js.map +1 -0
- package/dist/packages/core/src/domain/TraceabilityFormat.js +317 -0
- package/dist/packages/core/src/domain/TraceabilityFormat.js.map +1 -0
- package/dist/packages/core/src/domain/TraceabilityMatrix.js +200 -0
- package/dist/packages/core/src/domain/TraceabilityMatrix.js.map +1 -0
- package/dist/packages/core/src/domain/TraceabilityMerge.js +180 -0
- package/dist/packages/core/src/domain/TraceabilityMerge.js.map +1 -0
- package/dist/packages/core/src/domain/ValidationReport.js +53 -0
- package/dist/packages/core/src/domain/ValidationReport.js.map +1 -0
- package/dist/packages/core/src/domain/ValueAnnotations.js +138 -0
- package/dist/packages/core/src/domain/ValueAnnotations.js.map +1 -0
- package/dist/packages/core/src/domain/WriteScope.js +155 -0
- package/dist/packages/core/src/domain/WriteScope.js.map +1 -0
- package/dist/packages/core/src/domain/YamlLite.js +226 -0
- package/dist/packages/core/src/domain/YamlLite.js.map +1 -0
- package/dist/packages/core/src/index.js +120 -0
- package/dist/packages/core/src/index.js.map +1 -0
- package/dist/packages/core/src/infrastructure/ChangeWorkspace.js +276 -0
- package/dist/packages/core/src/infrastructure/ChangeWorkspace.js.map +1 -0
- package/dist/packages/core/src/infrastructure/ConsoleReporter.js +19 -0
- package/dist/packages/core/src/infrastructure/ConsoleReporter.js.map +1 -0
- package/dist/packages/core/src/infrastructure/DirectorySnapshot.js +106 -0
- package/dist/packages/core/src/infrastructure/DirectorySnapshot.js.map +1 -0
- package/dist/packages/core/src/infrastructure/DiskDomainPackRepository.js +57 -0
- package/dist/packages/core/src/infrastructure/DiskDomainPackRepository.js.map +1 -0
- package/dist/packages/core/src/infrastructure/DiskHarnessConfigRepository.js +58 -0
- package/dist/packages/core/src/infrastructure/DiskHarnessConfigRepository.js.map +1 -0
- package/dist/packages/core/src/infrastructure/DiskLanguageRepository.js +69 -0
- package/dist/packages/core/src/infrastructure/DiskLanguageRepository.js.map +1 -0
- package/dist/packages/core/src/infrastructure/DiskLockfileRepository.js +64 -0
- package/dist/packages/core/src/infrastructure/DiskLockfileRepository.js.map +1 -0
- package/dist/packages/core/src/infrastructure/DiskManifestRepository.js +88 -0
- package/dist/packages/core/src/infrastructure/DiskManifestRepository.js.map +1 -0
- package/dist/packages/core/src/infrastructure/DiskPackDeltaRepository.js +138 -0
- package/dist/packages/core/src/infrastructure/DiskPackDeltaRepository.js.map +1 -0
- package/dist/packages/core/src/infrastructure/DiskPackRepository.js +130 -0
- package/dist/packages/core/src/infrastructure/DiskPackRepository.js.map +1 -0
- package/dist/packages/core/src/infrastructure/DiskProjectRepository.js +127 -0
- package/dist/packages/core/src/infrastructure/DiskProjectRepository.js.map +1 -0
- package/dist/packages/core/src/infrastructure/DiskRequirementGraphRepository.js +124 -0
- package/dist/packages/core/src/infrastructure/DiskRequirementGraphRepository.js.map +1 -0
- package/dist/packages/core/src/infrastructure/DiskTraceabilityRepository.js +53 -0
- package/dist/packages/core/src/infrastructure/DiskTraceabilityRepository.js.map +1 -0
- package/dist/packages/core/src/infrastructure/GitContributionStager.js +99 -0
- package/dist/packages/core/src/infrastructure/GitContributionStager.js.map +1 -0
- package/dist/packages/core/src/infrastructure/GitMergeDriver.js +103 -0
- package/dist/packages/core/src/infrastructure/GitMergeDriver.js.map +1 -0
- package/dist/packages/core/src/infrastructure/HarnessConfigFile.js +348 -0
- package/dist/packages/core/src/infrastructure/HarnessConfigFile.js.map +1 -0
- package/dist/packages/core/src/infrastructure/PackChangeDeposit.js +106 -0
- package/dist/packages/core/src/infrastructure/PackChangeDeposit.js.map +1 -0
- package/dist/packages/core/src/infrastructure/RemotePackResolver.js +181 -0
- package/dist/packages/core/src/infrastructure/RemotePackResolver.js.map +1 -0
- package/dist/packages/core/src/infrastructure/SpecopsConfigFile.js +94 -0
- package/dist/packages/core/src/infrastructure/SpecopsConfigFile.js.map +1 -0
- package/dist/packages/core/test/unit/authoring-scope.test.js +102 -0
- package/dist/packages/core/test/unit/authoring-scope.test.js.map +1 -0
- package/dist/packages/core/test/unit/change-templates.test.js +96 -0
- package/dist/packages/core/test/unit/change-templates.test.js.map +1 -0
- package/dist/packages/core/test/unit/cucumber-messages.test.js +232 -0
- package/dist/packages/core/test/unit/cucumber-messages.test.js.map +1 -0
- package/dist/packages/core/test/unit/declared-artifacts.test.js +124 -0
- package/dist/packages/core/test/unit/declared-artifacts.test.js.map +1 -0
- package/dist/packages/core/test/unit/empty-attempt.test.js +79 -0
- package/dist/packages/core/test/unit/empty-attempt.test.js.map +1 -0
- package/dist/packages/core/test/unit/gherkin-dialects.test.js +115 -0
- package/dist/packages/core/test/unit/gherkin-dialects.test.js.map +1 -0
- package/dist/packages/core/test/unit/gherkin-keyword-case.test.js +110 -0
- package/dist/packages/core/test/unit/gherkin-keyword-case.test.js.map +1 -0
- package/dist/packages/core/test/unit/gherkin-quality.test.js +151 -0
- package/dist/packages/core/test/unit/gherkin-quality.test.js.map +1 -0
- package/dist/packages/core/test/unit/gherkin-tags.test.js +127 -0
- package/dist/packages/core/test/unit/gherkin-tags.test.js.map +1 -0
- package/dist/packages/core/test/unit/git-safety.test.js +104 -0
- package/dist/packages/core/test/unit/git-safety.test.js.map +1 -0
- package/dist/packages/core/test/unit/harness-report.test.js +192 -0
- package/dist/packages/core/test/unit/harness-report.test.js.map +1 -0
- package/dist/packages/core/test/unit/harness-roles.test.js +86 -0
- package/dist/packages/core/test/unit/harness-roles.test.js.map +1 -0
- package/dist/packages/core/test/unit/inbound-change.test.js +145 -0
- package/dist/packages/core/test/unit/inbound-change.test.js.map +1 -0
- package/dist/packages/core/test/unit/matrix-dependencies.test.js +100 -0
- package/dist/packages/core/test/unit/matrix-dependencies.test.js.map +1 -0
- package/dist/packages/core/test/unit/profile-match.test.js +115 -0
- package/dist/packages/core/test/unit/profile-match.test.js.map +1 -0
- package/dist/packages/core/test/unit/reconciliation.test.js +112 -0
- package/dist/packages/core/test/unit/reconciliation.test.js.map +1 -0
- package/dist/packages/core/test/unit/requirement-readiness.test.js +161 -0
- package/dist/packages/core/test/unit/requirement-readiness.test.js.map +1 -0
- package/dist/packages/core/test/unit/resume-state.test.js +124 -0
- package/dist/packages/core/test/unit/resume-state.test.js.map +1 -0
- package/dist/packages/core/test/unit/run-budget.test.js +113 -0
- package/dist/packages/core/test/unit/run-budget.test.js.map +1 -0
- package/dist/packages/core/test/unit/traceability-merge.test.js +159 -0
- package/dist/packages/core/test/unit/traceability-merge.test.js.map +1 -0
- package/dist/packages/core/test/unit/validate-project.test.js +156 -0
- package/dist/packages/core/test/unit/validate-project.test.js.map +1 -0
- package/dist/packages/core/test/unit/value-annotations.test.js +149 -0
- package/dist/packages/core/test/unit/value-annotations.test.js.map +1 -0
- package/dist/packages/core/test/unit/write-scope.test.js +165 -0
- package/dist/packages/core/test/unit/write-scope.test.js.map +1 -0
- package/dist/packages/core/test/unit/yaml-lite.test.js +78 -0
- package/dist/packages/core/test/unit/yaml-lite.test.js.map +1 -0
- package/dist/scripts/adopt_project.js +11 -0
- package/dist/scripts/adopt_project.js.map +1 -0
- package/dist/scripts/agents/commands.js +100 -0
- package/dist/scripts/agents/commands.js.map +1 -0
- package/dist/scripts/agents/init.js +482 -0
- package/dist/scripts/agents/init.js.map +1 -0
- package/dist/scripts/alm/cli.js +154 -0
- package/dist/scripts/alm/cli.js.map +1 -0
- package/dist/scripts/alm/clients.js +30 -0
- package/dist/scripts/alm/clients.js.map +1 -0
- package/dist/scripts/alm/core.js +258 -0
- package/dist/scripts/alm/core.js.map +1 -0
- package/dist/scripts/alm/port.js +116 -0
- package/dist/scripts/alm/port.js.map +1 -0
- package/dist/scripts/alm/providers/azure.js +79 -0
- package/dist/scripts/alm/providers/azure.js.map +1 -0
- package/dist/scripts/alm/providers/github.js +124 -0
- package/dist/scripts/alm/providers/github.js.map +1 -0
- package/dist/scripts/alm/providers/http-client.js +47 -0
- package/dist/scripts/alm/providers/http-client.js.map +1 -0
- package/dist/scripts/alm/providers/index.js +107 -0
- package/dist/scripts/alm/providers/index.js.map +1 -0
- package/dist/scripts/alm/providers/jira.js +129 -0
- package/dist/scripts/alm/providers/jira.js.map +1 -0
- package/dist/scripts/bundle_pack.js +10 -0
- package/dist/scripts/bundle_pack.js.map +1 -0
- package/dist/scripts/change/archive.js +68 -0
- package/dist/scripts/change/archive.js.map +1 -0
- package/dist/scripts/change/artifacts.js +143 -0
- package/dist/scripts/change/artifacts.js.map +1 -0
- package/dist/scripts/change/cli.js +39 -0
- package/dist/scripts/change/cli.js.map +1 -0
- package/dist/scripts/change/delta.js +20 -0
- package/dist/scripts/change/delta.js.map +1 -0
- package/dist/scripts/change/instructions.js +339 -0
- package/dist/scripts/change/instructions.js.map +1 -0
- package/dist/scripts/ci_init.js +12 -0
- package/dist/scripts/ci_init.js.map +1 -0
- package/dist/scripts/cli/commands/alm/PullCommand.js +238 -0
- package/dist/scripts/cli/commands/alm/PullCommand.js.map +1 -0
- package/dist/scripts/cli/commands/change/ChangeCommand.js +867 -0
- package/dist/scripts/cli/commands/change/ChangeCommand.js.map +1 -0
- package/dist/scripts/cli/commands/config/CiInitCommand.js +144 -0
- package/dist/scripts/cli/commands/config/CiInitCommand.js.map +1 -0
- package/dist/scripts/cli/commands/config/CompletionCommand.js +160 -0
- package/dist/scripts/cli/commands/config/CompletionCommand.js.map +1 -0
- package/dist/scripts/cli/commands/config/ConfigInitCommand.js +108 -0
- package/dist/scripts/cli/commands/config/ConfigInitCommand.js.map +1 -0
- package/dist/scripts/cli/commands/config/ConfigSetCommand.js +166 -0
- package/dist/scripts/cli/commands/config/ConfigSetCommand.js.map +1 -0
- package/dist/scripts/cli/commands/config/GenAgentContractCommand.js +212 -0
- package/dist/scripts/cli/commands/config/GenAgentContractCommand.js.map +1 -0
- package/dist/scripts/cli/commands/harness/RunCommand.js +1922 -0
- package/dist/scripts/cli/commands/harness/RunCommand.js.map +1 -0
- package/dist/scripts/cli/commands/mcp/McpInstallCommand.js +114 -0
- package/dist/scripts/cli/commands/mcp/McpInstallCommand.js.map +1 -0
- package/dist/scripts/cli/commands/mcp/index.js +29 -0
- package/dist/scripts/cli/commands/mcp/index.js.map +1 -0
- package/dist/scripts/cli/commands/pack/BundlePackCommand.js +59 -0
- package/dist/scripts/cli/commands/pack/BundlePackCommand.js.map +1 -0
- package/dist/scripts/cli/commands/pack/ExpandDomainPackCommand.js +559 -0
- package/dist/scripts/cli/commands/pack/ExpandDomainPackCommand.js.map +1 -0
- package/dist/scripts/cli/commands/pack/InferPackCommand.js +304 -0
- package/dist/scripts/cli/commands/pack/InferPackCommand.js.map +1 -0
- package/dist/scripts/cli/commands/pack/InitPackCommand.js +368 -0
- package/dist/scripts/cli/commands/pack/InitPackCommand.js.map +1 -0
- package/dist/scripts/cli/commands/pack/LintPackCommand.js +631 -0
- package/dist/scripts/cli/commands/pack/LintPackCommand.js.map +1 -0
- package/dist/scripts/cli/commands/pack/pack-args.js +83 -0
- package/dist/scripts/cli/commands/pack/pack-args.js.map +1 -0
- package/dist/scripts/cli/commands/project/AdoptCommand.js +414 -0
- package/dist/scripts/cli/commands/project/AdoptCommand.js.map +1 -0
- package/dist/scripts/cli/commands/project/InitFromPackCommand.js +188 -0
- package/dist/scripts/cli/commands/project/InitFromPackCommand.js.map +1 -0
- package/dist/scripts/cli/commands/project/InitProjectCommand.js +791 -0
- package/dist/scripts/cli/commands/project/InitProjectCommand.js.map +1 -0
- package/dist/scripts/cli/commands/project/OnboardCommand.js +655 -0
- package/dist/scripts/cli/commands/project/OnboardCommand.js.map +1 -0
- package/dist/scripts/cli/commands/project/UpdateCommand.js +206 -0
- package/dist/scripts/cli/commands/project/UpdateCommand.js.map +1 -0
- package/dist/scripts/cli/commands/project/WizardCommand.js +166 -0
- package/dist/scripts/cli/commands/project/WizardCommand.js.map +1 -0
- package/dist/scripts/cli/commands/quality/DoctorCommand.js +553 -0
- package/dist/scripts/cli/commands/quality/DoctorCommand.js.map +1 -0
- package/dist/scripts/cli/commands/quality/FixCommand.js +201 -0
- package/dist/scripts/cli/commands/quality/FixCommand.js.map +1 -0
- package/dist/scripts/cli/commands/quality/LicenseCheckCommand.js +187 -0
- package/dist/scripts/cli/commands/quality/LicenseCheckCommand.js.map +1 -0
- package/dist/scripts/cli/commands/quality/ReportCommand.js +557 -0
- package/dist/scripts/cli/commands/quality/ReportCommand.js.map +1 -0
- package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js +764 -0
- package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js.map +1 -0
- package/dist/scripts/cli/commands/spec/DoneCommand.js +116 -0
- package/dist/scripts/cli/commands/spec/DoneCommand.js.map +1 -0
- package/dist/scripts/cli/commands/spec/PlanCommand.js +397 -0
- package/dist/scripts/cli/commands/spec/PlanCommand.js.map +1 -0
- package/dist/scripts/cli/commands/spec/ReqCommand.js +303 -0
- package/dist/scripts/cli/commands/spec/ReqCommand.js.map +1 -0
- package/dist/scripts/cli/commands/spec/StatusCommand.js +206 -0
- package/dist/scripts/cli/commands/spec/StatusCommand.js.map +1 -0
- package/dist/scripts/cli/commands/spec/StudioCommand.js +241 -0
- package/dist/scripts/cli/commands/spec/StudioCommand.js.map +1 -0
- package/dist/scripts/cli/commands/specops/ContributeCommand.js +313 -0
- package/dist/scripts/cli/commands/specops/ContributeCommand.js.map +1 -0
- package/dist/scripts/cli/commands/specops/DiffCommand.js +394 -0
- package/dist/scripts/cli/commands/specops/DiffCommand.js.map +1 -0
- package/dist/scripts/cli/commands/specops/SyncCommand.js +380 -0
- package/dist/scripts/cli/commands/specops/SyncCommand.js.map +1 -0
- package/dist/scripts/completion.js +24 -0
- package/dist/scripts/completion.js.map +1 -0
- package/dist/scripts/config_init.js +12 -0
- package/dist/scripts/config_init.js.map +1 -0
- package/dist/scripts/config_set.js +20 -0
- package/dist/scripts/config_set.js.map +1 -0
- package/dist/scripts/docs/blocks.js +137 -0
- package/dist/scripts/docs/blocks.js.map +1 -0
- package/dist/scripts/docs/build-site.js +425 -0
- package/dist/scripts/docs/build-site.js.map +1 -0
- package/dist/scripts/docs/nav.js +198 -0
- package/dist/scripts/docs/nav.js.map +1 -0
- package/dist/scripts/docs/record-terminal.js +477 -0
- package/dist/scripts/docs/record-terminal.js.map +1 -0
- package/dist/scripts/docs/terminal-storyboard.js +121 -0
- package/dist/scripts/docs/terminal-storyboard.js.map +1 -0
- package/dist/scripts/doctor.js +14 -0
- package/dist/scripts/doctor.js.map +1 -0
- package/dist/scripts/done.js +17 -0
- package/dist/scripts/done.js.map +1 -0
- package/dist/scripts/expand_domain_pack.js +18 -0
- package/dist/scripts/expand_domain_pack.js.map +1 -0
- package/dist/scripts/fix.js +12 -0
- package/dist/scripts/fix.js.map +1 -0
- package/dist/scripts/gen_agent_contract.js +14 -0
- package/dist/scripts/gen_agent_contract.js.map +1 -0
- package/dist/scripts/gen_mcp_tools.js +264 -0
- package/dist/scripts/gen_mcp_tools.js.map +1 -0
- package/dist/scripts/harness/init.js +358 -0
- package/dist/scripts/harness/init.js.map +1 -0
- package/dist/scripts/harness/prompt.js +18 -0
- package/dist/scripts/harness/prompt.js.map +1 -0
- package/dist/scripts/harness/report.js +323 -0
- package/dist/scripts/harness/report.js.map +1 -0
- package/dist/scripts/harness/run.js +33 -0
- package/dist/scripts/harness/run.js.map +1 -0
- package/dist/scripts/infer_pack.js +17 -0
- package/dist/scripts/infer_pack.js.map +1 -0
- package/dist/scripts/init_from_pack.js +16 -0
- package/dist/scripts/init_from_pack.js.map +1 -0
- package/dist/scripts/init_pack.js +16 -0
- package/dist/scripts/init_pack.js.map +1 -0
- package/dist/scripts/init_project.js +17 -0
- package/dist/scripts/init_project.js.map +1 -0
- package/dist/scripts/lib/agent.js +99 -0
- package/dist/scripts/lib/agent.js.map +1 -0
- package/dist/scripts/lib/capability-specs.js +80 -0
- package/dist/scripts/lib/capability-specs.js.map +1 -0
- package/dist/scripts/lib/command.js +17 -0
- package/dist/scripts/lib/command.js.map +1 -0
- package/dist/scripts/lib/diagnostics.js +62 -0
- package/dist/scripts/lib/diagnostics.js.map +1 -0
- package/dist/scripts/lib/monorepo-fanout.js +118 -0
- package/dist/scripts/lib/monorepo-fanout.js.map +1 -0
- package/dist/scripts/lib/placeholders.js +190 -0
- package/dist/scripts/lib/placeholders.js.map +1 -0
- package/dist/scripts/lib/project-root.js +124 -0
- package/dist/scripts/lib/project-root.js.map +1 -0
- package/dist/scripts/lib/requirement-graph.js +31 -0
- package/dist/scripts/lib/requirement-graph.js.map +1 -0
- package/dist/scripts/lib/surface.js +609 -0
- package/dist/scripts/lib/surface.js.map +1 -0
- package/dist/scripts/license_check.js +15 -0
- package/dist/scripts/license_check.js.map +1 -0
- package/dist/scripts/lint_pack.js +18 -0
- package/dist/scripts/lint_pack.js.map +1 -0
- package/dist/scripts/merge-traceability.js +106 -0
- package/dist/scripts/merge-traceability.js.map +1 -0
- package/dist/scripts/onboard.js +22 -0
- package/dist/scripts/onboard.js.map +1 -0
- package/dist/scripts/plan.js +17 -0
- package/dist/scripts/plan.js.map +1 -0
- package/dist/scripts/plugin/gate-hook.js +171 -0
- package/dist/scripts/plugin/gate-hook.js.map +1 -0
- package/dist/scripts/report.js +17 -0
- package/dist/scripts/report.js.map +1 -0
- package/dist/scripts/req.js +22 -0
- package/dist/scripts/req.js.map +1 -0
- package/dist/scripts/schema/cli.js +215 -0
- package/dist/scripts/schema/cli.js.map +1 -0
- package/dist/scripts/schema/registry.js +264 -0
- package/dist/scripts/schema/registry.js.map +1 -0
- package/dist/scripts/specops/add.js +180 -0
- package/dist/scripts/specops/add.js.map +1 -0
- package/dist/scripts/specops/against_lock.js +100 -0
- package/dist/scripts/specops/against_lock.js.map +1 -0
- package/dist/scripts/specops/as_change.js +49 -0
- package/dist/scripts/specops/as_change.js.map +1 -0
- package/dist/scripts/specops/contribute.js +24 -0
- package/dist/scripts/specops/contribute.js.map +1 -0
- package/dist/scripts/specops/diff.js +32 -0
- package/dist/scripts/specops/diff.js.map +1 -0
- package/dist/scripts/specops/lock.js +39 -0
- package/dist/scripts/specops/lock.js.map +1 -0
- package/dist/scripts/specops/manifest.js +90 -0
- package/dist/scripts/specops/manifest.js.map +1 -0
- package/dist/scripts/specops/remove.js +102 -0
- package/dist/scripts/specops/remove.js.map +1 -0
- package/dist/scripts/specops/sync.js +27 -0
- package/dist/scripts/specops/sync.js.map +1 -0
- package/dist/scripts/specops/verify.js +149 -0
- package/dist/scripts/specops/verify.js.map +1 -0
- package/dist/scripts/status.js +17 -0
- package/dist/scripts/status.js.map +1 -0
- package/dist/scripts/studio.js +14 -0
- package/dist/scripts/studio.js.map +1 -0
- package/dist/scripts/update.js +17 -0
- package/dist/scripts/update.js.map +1 -0
- package/dist/scripts/validate_specs.js +10 -0
- package/dist/scripts/validate_specs.js.map +1 -0
- package/examples/project.config.example +19 -0
- package/examples/project.yaml.example +17 -0
- package/package.json +105 -0
- package/templates/adopt/AI_RULES.md.tpl +27 -0
- package/templates/adopt/adr-readme.md.tpl +5 -0
- package/templates/adopt/baseline.feature.tpl +9 -0
- package/templates/adopt/spec.md.tpl +25 -0
- package/templates/adopt/traceability.md.tpl +11 -0
- package/templates/backend/AI_RULES.md.tpl +35 -0
- package/templates/backend/features/core/health.feature.tpl +7 -0
- package/templates/base/.devcontainer/devcontainer.json.tpl +18 -0
- package/templates/base/.dockerignore.tpl +11 -0
- package/templates/base/.env.dev.app.tpl +13 -0
- package/templates/base/.env.dev.infra.tpl +6 -0
- package/templates/base/.env.dev.tpl +16 -0
- package/templates/base/.env.example.tpl +17 -0
- package/templates/base/.env.feature.app.tpl +11 -0
- package/templates/base/.env.feature.infra.tpl +4 -0
- package/templates/base/.env.feature.tpl +16 -0
- package/templates/base/.env.prod.app.tpl +13 -0
- package/templates/base/.env.prod.infra.tpl +6 -0
- package/templates/base/.env.prod.tpl +16 -0
- package/templates/base/.github/workflows/specs.yml.tpl +26 -0
- package/templates/base/.gitignore.tpl +9 -0
- package/templates/base/.pre-commit-config.yaml.tpl +10 -0
- package/templates/base/AGENTS.md.tpl +32 -0
- package/templates/base/README.md.tpl +32 -0
- package/templates/base/docker-compose.yml.tpl +13 -0
- package/templates/base/docs/specs/adr/README.md.tpl +10 -0
- package/templates/base/docs/specs/aggregates.md.tpl +5 -0
- package/templates/base/docs/specs/commands.md.tpl +13 -0
- package/templates/base/docs/specs/domain-model.md.tpl +30 -0
- package/templates/base/docs/specs/events.md.tpl +5 -0
- package/templates/base/docs/specs/review-checklist.md.tpl +18 -0
- package/templates/base/docs/specs/runtime-environments.md.tpl +26 -0
- package/templates/base/docs/specs/status-model.md.tpl +16 -0
- package/templates/base/docs/specs/traceability.md.tpl +7 -0
- package/templates/base/docs/specs/use-cases.md.tpl +5 -0
- package/templates/base/spec.md.tpl +63 -0
- package/templates/ci/azure.yml.tpl +40 -0
- package/templates/ci/github.yml.tpl +40 -0
- package/templates/ci/gitlab.yml.tpl +21 -0
- package/templates/ci/jenkins.tpl +35 -0
- package/templates/contracts/AI_RULES.md.tpl +55 -0
- package/templates/contracts/asyncapi-stub.yaml.tpl +24 -0
- package/templates/contracts/contract-traceability.md.tpl +10 -0
- package/templates/contracts/openapi-stub.yaml.tpl +29 -0
- package/templates/contracts/pacts-gitkeep.tpl +0 -0
- package/templates/contracts/test-strategy.md.tpl +94 -0
- package/templates/frontend/AI_RULES.md.tpl +34 -0
- package/templates/frontend/features/core/health.feature.tpl +6 -0
- package/templates/harness/harness.config.yaml.tpl +74 -0
- package/templates/harness/prompt-prefix.md.tpl +60 -0
- package/templates/mobile/AI_RULES.md.tpl +56 -0
- package/templates/mobile/features/core/health.feature.tpl +7 -0
- package/templates/modules/auth/backend/registration_and_login.feature.tpl +10 -0
- package/templates/modules/auth/frontend/login.feature.tpl +5 -0
- package/templates/modules/billing/backend/billing_settlement.feature.tpl +5 -0
- package/templates/modules/billing/frontend/billing_summary.feature.tpl +5 -0
- package/templates/modules/dashboard/backend/dashboard_kpi.feature.tpl +5 -0
- package/templates/modules/dashboard/frontend/dashboard.feature.tpl +5 -0
- package/vendor/mermaid.min.js +3636 -0
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ValidateSpecsCommand = void 0;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const node_child_process_1 = require("node:child_process");
|
|
40
|
+
const YamlLite_1 = require("../../../../packages/core/src/domain/YamlLite");
|
|
41
|
+
const ChangeWorkspace_1 = require("../../../../packages/core/src/infrastructure/ChangeWorkspace");
|
|
42
|
+
const ValidateChangeUseCase_1 = require("../../../../packages/core/src/application/ValidateChangeUseCase");
|
|
43
|
+
const DiskProjectRepository_1 = require("../../../../packages/core/src/infrastructure/DiskProjectRepository");
|
|
44
|
+
const diagnostics_1 = require("../../../lib/diagnostics");
|
|
45
|
+
const against_lock_1 = require("../../../specops/against_lock");
|
|
46
|
+
const requirement_graph_1 = require("../../../lib/requirement-graph");
|
|
47
|
+
const agent_1 = require("../../../lib/agent");
|
|
48
|
+
const placeholders_1 = require("../../../lib/placeholders");
|
|
49
|
+
const command_1 = require("../../../lib/command");
|
|
50
|
+
const ValidateProjectUseCase_1 = require("../../../../packages/core/src/application/ValidateProjectUseCase");
|
|
51
|
+
const DiskTraceabilityRepository_1 = require("../../../../packages/core/src/infrastructure/DiskTraceabilityRepository");
|
|
52
|
+
const TraceabilityFormat_1 = require("../../../../packages/core/src/domain/TraceabilityFormat");
|
|
53
|
+
const project_root_1 = require("../../../lib/project-root");
|
|
54
|
+
const GherkinQuality_1 = require("../../../../packages/core/src/domain/GherkinQuality");
|
|
55
|
+
const GherkinTags_1 = require("../../../../packages/core/src/domain/GherkinTags");
|
|
56
|
+
const TraceabilityFormat_2 = require("../../../../packages/core/src/domain/TraceabilityFormat");
|
|
57
|
+
const RequirementSyntax_1 = require("../../../../packages/core/src/domain/RequirementSyntax");
|
|
58
|
+
const DeclaredArtifacts_1 = require("../../../../packages/core/src/domain/DeclaredArtifacts");
|
|
59
|
+
const SpecParser_1 = require("../../../../packages/core/src/domain/SpecParser");
|
|
60
|
+
const ChangeWorkspace_2 = require("../../../../packages/core/src/infrastructure/ChangeWorkspace");
|
|
61
|
+
class ValidateSpecsCommand extends command_1.BaseCommand {
|
|
62
|
+
io = null;
|
|
63
|
+
logInfo(msg) {
|
|
64
|
+
const stream = this.io && this.io.json ? process.stderr : process.stdout;
|
|
65
|
+
stream.write(`ℹ️ [INFO] ${msg}\n`);
|
|
66
|
+
}
|
|
67
|
+
logError(msg) {
|
|
68
|
+
process.stderr.write(`❌ [ERROR] ${msg}\n`);
|
|
69
|
+
}
|
|
70
|
+
usage() {
|
|
71
|
+
process.stdout.write("🔎 Usage:\n" +
|
|
72
|
+
" validate_specs.js <project_dir> [--strict-tdd] [--strict-scenarios] " +
|
|
73
|
+
"[--strict-requirements] [--strict-links] [--against-lock]\n\n" +
|
|
74
|
+
"Checks:\n" +
|
|
75
|
+
"- minimum SDD structure\n" +
|
|
76
|
+
"- required files\n" +
|
|
77
|
+
"- at least one .feature file\n" +
|
|
78
|
+
"- unresolved placeholders ({{...}})\n" +
|
|
79
|
+
"- feature coverage in traceability.md\n" +
|
|
80
|
+
"- allowed status values in traceability.md\n" +
|
|
81
|
+
"- expected DDD Lite document headers when present\n\n" +
|
|
82
|
+
"--against-lock additionally enforces:\n" +
|
|
83
|
+
"- Every requirement the locked packs declare is present in the matrix\n" +
|
|
84
|
+
"- Its scenario and feature file still match what the pack declares\n\n" +
|
|
85
|
+
"--strict-scenarios additionally enforces, over features/**/*.feature:\n" +
|
|
86
|
+
"- No scenario Cucumber would see as empty (upper-case keywords are the usual cause)\n" +
|
|
87
|
+
"- Every scenario has a When and a Then, at least three steps and a title that names the behaviour\n" +
|
|
88
|
+
"- No vague, unfalsifiable step text; no Scenario Outline without Examples\n\n" +
|
|
89
|
+
"--strict-tdd additionally enforces:\n" +
|
|
90
|
+
"- No 'Test Artifact = TBD' when Status is In Dev or later\n" +
|
|
91
|
+
"- Every requirement has at least one traceability row\n" +
|
|
92
|
+
"- Every scenario row has a non-empty Scenario ID\n\n" +
|
|
93
|
+
"--strict-requirements additionally enforces, over docs/specs/capabilities/**/spec.md:\n" +
|
|
94
|
+
"- Every requirement states an obligation (SHALL / MUST / SHOULD / MAY / DEBE / DEBERÁ)\n" +
|
|
95
|
+
"- A requirement that opens with IF resolves with THEN in the same sentence\n\n" +
|
|
96
|
+
"--strict-links additionally enforces:\n" +
|
|
97
|
+
"- Every Feature file / Technical artifact / Test artifact the matrix declares as a\n" +
|
|
98
|
+
" path still exists on disk\n");
|
|
99
|
+
}
|
|
100
|
+
logFix(lines) {
|
|
101
|
+
for (const line of Array.isArray(lines) ? lines : [lines]) {
|
|
102
|
+
process.stderr.write(`💡 [FIX] ${line}\n`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
logWarn(msg) {
|
|
106
|
+
const stream = this.io && this.io.json ? process.stderr : process.stdout;
|
|
107
|
+
stream.write(`⚠️ [WARN] ${msg}\n`);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* True when the only Gherkin in the project is the placeholder `adopt` writes.
|
|
111
|
+
*
|
|
112
|
+
* `adopt` installs a skeleton and a baseline scenario so that `validate` passes
|
|
113
|
+
* on day one — deliberately, because a gate that rejects a fresh adoption is a
|
|
114
|
+
* gate nobody installs. The cost is that a project which never retro-filled a
|
|
115
|
+
* single requirement is indistinguishable from a healthy one at the only place
|
|
116
|
+
* most people look: the CI gate. Seen on `lixi-platform`, adopted months ago,
|
|
117
|
+
* still one placeholder requirement against 297 real tests (H15).
|
|
118
|
+
*
|
|
119
|
+
* So: still a pass, never an error — but it says so.
|
|
120
|
+
*/
|
|
121
|
+
isAdoptionSkeletonOnly(targetDir, featureFiles) {
|
|
122
|
+
if (featureFiles.length !== 1)
|
|
123
|
+
return false;
|
|
124
|
+
const rel = path.relative(targetDir, featureFiles[0]).split(path.sep).join("/");
|
|
125
|
+
return rel === "features/adoption/baseline.feature";
|
|
126
|
+
}
|
|
127
|
+
fail(code, msg, exitCode = 1, fix = null) {
|
|
128
|
+
const fixLines = fix ? (Array.isArray(fix) ? fix : [fix]) : [];
|
|
129
|
+
if (this.io && this.io.json) {
|
|
130
|
+
const diag = (0, diagnostics_1.error)(code, msg, fixLines.length ? { fix: fixLines.join(" ") } : undefined);
|
|
131
|
+
if (exitCode === agent_1.EXIT.USAGE)
|
|
132
|
+
this.io.usage({ validation: null }, [diag]);
|
|
133
|
+
this.io.fail({ validation: null }, [diag]);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
this.logError(msg);
|
|
137
|
+
if (fixLines.length)
|
|
138
|
+
this.logFix(fixLines);
|
|
139
|
+
process.exit(exitCode);
|
|
140
|
+
}
|
|
141
|
+
validateMonorepo(targetDir, strictTdd, strictScenarios, strictRequirements, strictLinks) {
|
|
142
|
+
const cfgPath = path.join(targetDir, "specops.config.yaml");
|
|
143
|
+
if (!fs.existsSync(cfgPath))
|
|
144
|
+
return null;
|
|
145
|
+
let cfg;
|
|
146
|
+
try {
|
|
147
|
+
cfg = (0, YamlLite_1.parseYamlLite)(fs.readFileSync(cfgPath, "utf8"));
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const projects = cfg && Array.isArray(cfg.projects) ? cfg.projects : null;
|
|
153
|
+
if (!projects || projects.length === 0)
|
|
154
|
+
return null;
|
|
155
|
+
this.logInfo(`🗂️ Monorepo: validating ${projects.length} project(s) from specops.config.yaml`);
|
|
156
|
+
const results = [];
|
|
157
|
+
for (const entry of projects) {
|
|
158
|
+
const rel = typeof entry === "string" ? entry : entry && entry.path;
|
|
159
|
+
if (!rel) {
|
|
160
|
+
results.push({ project: String(entry), ok: false, detail: "invalid projects entry" });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const subDir = path.join(targetDir, rel);
|
|
164
|
+
process.stdout.write(`\n── ${rel} ──\n`);
|
|
165
|
+
if (!fs.existsSync(subDir)) {
|
|
166
|
+
this.logError(`Project directory not found: ${rel}`);
|
|
167
|
+
this.logFix(`Fix the 'projects:' entry in specops.config.yaml or create ${rel}.`);
|
|
168
|
+
results.push({ project: rel, ok: false, detail: "directory not found" });
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const root = (0, project_root_1.findCliRoot)(__dirname);
|
|
172
|
+
const scriptPath = fs.existsSync(path.join(root, "dist", "scripts", "validate_specs.js"))
|
|
173
|
+
? path.join(root, "dist", "scripts", "validate_specs.js")
|
|
174
|
+
: path.join(root, "scripts", "validate_specs.js");
|
|
175
|
+
const args = [scriptPath, subDir];
|
|
176
|
+
if (strictTdd)
|
|
177
|
+
args.push("--strict-tdd");
|
|
178
|
+
if (strictScenarios)
|
|
179
|
+
args.push("--strict-scenarios");
|
|
180
|
+
if (strictRequirements)
|
|
181
|
+
args.push("--strict-requirements");
|
|
182
|
+
if (strictLinks)
|
|
183
|
+
args.push("--strict-links");
|
|
184
|
+
const r = (0, node_child_process_1.spawnSync)(process.execPath, args, { encoding: "utf8" });
|
|
185
|
+
process.stdout.write(r.stdout || "");
|
|
186
|
+
process.stderr.write(r.stderr || "");
|
|
187
|
+
results.push({ project: rel, ok: r.status === 0 });
|
|
188
|
+
}
|
|
189
|
+
const failures = results.filter((r) => !r.ok);
|
|
190
|
+
process.stdout.write("\n── monorepo summary ──\n");
|
|
191
|
+
for (const r of results) {
|
|
192
|
+
process.stdout.write(` ${r.ok ? "✅" : "❌"} ${r.project}${r.detail ? ` (${r.detail})` : ""}\n`);
|
|
193
|
+
}
|
|
194
|
+
process.stdout.write(`\n${failures.length === 0 ? "✅" : "❌"} ${results.length - failures.length}/${results.length} project(s) passed\n`);
|
|
195
|
+
return { failures: failures.length };
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* `--strict-scenarios`: the pack's quality rules, applied where the harness
|
|
199
|
+
* actually runs (A3).
|
|
200
|
+
*
|
|
201
|
+
* `docs/specs/harness.md` says the gate is only as strong as its scenarios,
|
|
202
|
+
* and until now the rules that enforce that lived in `pack lint` and judged
|
|
203
|
+
* one thing: a `pack.yaml`. But a project's features arrive by three routes
|
|
204
|
+
* that never touch `pack lint` — `change archive`, `req add`, and a person
|
|
205
|
+
* with an editor. This closes that gap by reading the same domain rules.
|
|
206
|
+
*
|
|
207
|
+
* **Why a flag and not the default.** A project brought in with `specgate adopt`
|
|
208
|
+
* can have dozens of weak features written long before this tool existed;
|
|
209
|
+
* failing its first `validate` would teach people to skip the gate. `specgate
|
|
210
|
+
* doctor` reports the same findings as advisories, which is the gradual path
|
|
211
|
+
* this tool uses everywhere else. Under the flag, warnings fail too — asking
|
|
212
|
+
* for strict and getting lenient is the H14 mistake in a different costume.
|
|
213
|
+
*/
|
|
214
|
+
checkScenarioQuality(targetDir, featureFiles) {
|
|
215
|
+
const findings = [];
|
|
216
|
+
for (const file of featureFiles.slice().sort()) {
|
|
217
|
+
const rel = path.relative(targetDir, file).split(path.sep).join("/");
|
|
218
|
+
findings.push(...(0, GherkinQuality_1.analyseGherkinSource)(fs.readFileSync(file, "utf8"), rel));
|
|
219
|
+
}
|
|
220
|
+
if (findings.length === 0)
|
|
221
|
+
return;
|
|
222
|
+
if (this.io.json) {
|
|
223
|
+
this.io.fail({ validation: null }, findings);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const errs = findings.filter((f) => f.severity === "error");
|
|
227
|
+
this.logError(`--strict-scenarios violations detected: ${findings.length} ` +
|
|
228
|
+
`(${errs.length} that make a scenario pass without testing anything)`);
|
|
229
|
+
for (const f of findings)
|
|
230
|
+
process.stderr.write(` ${(0, diagnostics_1.formatDiagnostic)(f)}\n`);
|
|
231
|
+
this.logFix([
|
|
232
|
+
"Fix the errors first: a scenario Cucumber sees as empty reports `0 steps · exit 0`,",
|
|
233
|
+
"so the gate approves the requirement without having checked it.",
|
|
234
|
+
]);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Does the scenario the matrix names actually exist in the file? (F4)
|
|
239
|
+
*
|
|
240
|
+
* The help text has been asking for a Scenario ID "that matches a scenario in
|
|
241
|
+
* its feature file" without ever comparing the two. Measured before writing
|
|
242
|
+
* this: rename the scenario and both `--strict-tdd` and `--strict-scenarios`
|
|
243
|
+
* still pass, with the matrix pointing at something that is not there.
|
|
244
|
+
*
|
|
245
|
+
* The check runs off tags, because a tag survives the rename and a title does
|
|
246
|
+
* not — and the matrix carries an id, not a title, so there is nothing else to
|
|
247
|
+
* compare. `specgate expand` writes them; nobody types them.
|
|
248
|
+
*
|
|
249
|
+
* **Only files that carry our tags are checked.** A project brought in with
|
|
250
|
+
* `specgate adopt`, or one scaffolded before this existed, has none — and failing
|
|
251
|
+
* it here would punish it for a link it was never given the means to make.
|
|
252
|
+
* Once a file is tagged, a row pointing into it has to be right.
|
|
253
|
+
*/
|
|
254
|
+
checkScenarioTags(targetDir, traceContent) {
|
|
255
|
+
let rows = [];
|
|
256
|
+
try {
|
|
257
|
+
rows = (0, TraceabilityFormat_2.parseTraceabilityRows)(traceContent).rows || [];
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
for (const row of rows) {
|
|
263
|
+
const scenarioId = String(row.scenarioId || "").trim();
|
|
264
|
+
const featureRel = String(row.featureFile || "")
|
|
265
|
+
.replace(/`/g, "")
|
|
266
|
+
.split("#")[0]
|
|
267
|
+
.trim();
|
|
268
|
+
if (!/^SCN-[A-Za-z0-9.]+$/.test(scenarioId) || !featureRel)
|
|
269
|
+
continue;
|
|
270
|
+
const file = path.join(targetDir, featureRel);
|
|
271
|
+
if (!fs.existsSync(file))
|
|
272
|
+
continue; // the coverage check below reports this
|
|
273
|
+
let tags = [];
|
|
274
|
+
try {
|
|
275
|
+
tags = (0, GherkinTags_1.csdaTagsIn)(fs.readFileSync(file, "utf8"));
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (tags.length === 0)
|
|
281
|
+
continue; // untagged file — nothing to compare against
|
|
282
|
+
if (!tags.includes(`@${scenarioId}`)) {
|
|
283
|
+
this.fail("scenario_id_not_in_feature", `${featureRel} carries traceability tags but not @${scenarioId}, which ` +
|
|
284
|
+
`${row.requirement || "a row"} declares. The matrix points at a scenario ` +
|
|
285
|
+
`that is not there.`, 1, [
|
|
286
|
+
`Tag the scenario: put \`@${scenarioId}\` above its \`Scenario:\` line,`,
|
|
287
|
+
"or correct the Scenario ID in docs/specs/traceability.md.",
|
|
288
|
+
]);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Does a declared link still point at something that exists? (F6 follow-on,
|
|
294
|
+
* `PLAN_PREDICTABLE_CODE_EVOLUTION.md` §8.5)
|
|
295
|
+
*
|
|
296
|
+
* The plan names the first honest check beyond paperwork: not formal
|
|
297
|
+
* verification, just the matrix's own promises kept — a file gets renamed
|
|
298
|
+
* or deleted and the row that names it is never updated, so the matrix
|
|
299
|
+
* points a reader (or an agent) at code that is not there.
|
|
300
|
+
*
|
|
301
|
+
* **`--strict-links`, not unconditional — measured, not assumed.** The
|
|
302
|
+
* first version of this check ran by default on the theory that "this path
|
|
303
|
+
* does not exist" has no legitimate reading. `tests/unit/validate-strict-
|
|
304
|
+
* tdd.test.ts` disproved that immediately: a `Draft` or `In Dev` row
|
|
305
|
+
* routinely names the file a requirement is *going to* land in before
|
|
306
|
+
* anyone writes it — planning ahead, not documentary drift. That is exactly
|
|
307
|
+
* the shape `DeclaredArtifacts`' own `declared_artifact_untouched` already
|
|
308
|
+
* treats as a warning, not a certainty, and the same reasoning applies here.
|
|
309
|
+
* Opt-in, same promise as `--strict-scenarios`: a project with aspirational
|
|
310
|
+
* rows does not fail its next `validate` over this.
|
|
311
|
+
*/
|
|
312
|
+
checkDeclaredArtifactsExist(targetDir, traceContent) {
|
|
313
|
+
let rows = [];
|
|
314
|
+
try {
|
|
315
|
+
rows = (0, TraceabilityFormat_2.parseTraceabilityRows)(traceContent).rows || [];
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const findings = [];
|
|
321
|
+
const seen = new Set();
|
|
322
|
+
const columns = [
|
|
323
|
+
["feature file", "featureFile"],
|
|
324
|
+
["technical artifact", "technicalArtifact"],
|
|
325
|
+
["test artifact", "testArtifact"],
|
|
326
|
+
];
|
|
327
|
+
for (const row of rows) {
|
|
328
|
+
for (const [label, key] of columns) {
|
|
329
|
+
for (const declared of (0, DeclaredArtifacts_1.declaredPaths)(row[key])) {
|
|
330
|
+
// A cell may anchor a line range (`src/auth/login.ts#L15-L89`); the
|
|
331
|
+
// anchor is not part of the filesystem path.
|
|
332
|
+
const rel = declared.split("#")[0];
|
|
333
|
+
if (!rel || seen.has(`${row.requirement || row.feature}::${rel}`))
|
|
334
|
+
continue;
|
|
335
|
+
if (fs.existsSync(path.join(targetDir, rel)))
|
|
336
|
+
continue;
|
|
337
|
+
seen.add(`${row.requirement || row.feature}::${rel}`);
|
|
338
|
+
findings.push((0, diagnostics_1.error)("declared_artifact_missing", `${row.requirement || row.feature || "a row"}'s ${label} \`${rel}\` does not exist.`, {
|
|
339
|
+
target: row.requirement || row.feature,
|
|
340
|
+
file: "docs/specs/traceability.md",
|
|
341
|
+
fix: `Fix the path, or restore ${rel} if it was removed by mistake.`,
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (findings.length === 0)
|
|
347
|
+
return;
|
|
348
|
+
if (this.io.json) {
|
|
349
|
+
this.io.fail({ validation: null }, findings);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
this.logError(`Declared artifacts that no longer exist: ${findings.length}`);
|
|
353
|
+
for (const f of findings)
|
|
354
|
+
process.stderr.write(` ${(0, diagnostics_1.formatDiagnostic)(f)}\n`);
|
|
355
|
+
this.logFix([
|
|
356
|
+
"The matrix is pointing at a file that is not there — fix the path,",
|
|
357
|
+
"or restore the file if it was removed by mistake.",
|
|
358
|
+
]);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* `--strict-requirements`: EARS-checkable requirement prose, at rest (F6).
|
|
363
|
+
*
|
|
364
|
+
* `PLAN_PREDICTABLE_CODE_EVOLUTION.md` §8.1 names the prerequisite for any
|
|
365
|
+
* code-level verifier: the spec has to say something a machine can check.
|
|
366
|
+
* `DeltaSpec` already enforces an obligation keyword (`no_rfc2119_keyword`),
|
|
367
|
+
* but only inside a delta — a capability spec at rest, `docs/specs/
|
|
368
|
+
* capabilities/<cap>/spec.md`, has never been checked. This reads every one
|
|
369
|
+
* of those, the only `spec.md` grammar in the project with a real parser
|
|
370
|
+
* (§8.3), and applies the same rule plus the one EARS shape a regex can
|
|
371
|
+
* check honestly.
|
|
372
|
+
*
|
|
373
|
+
* **Opt-in, same reasoning as `--strict-scenarios`.** A project's capability
|
|
374
|
+
* specs may predate this check; failing the default `validate` on them would
|
|
375
|
+
* teach people to skip the gate rather than fix the prose.
|
|
376
|
+
*
|
|
377
|
+
* **A project with no `docs/specs/capabilities/` is not a failure.** Most
|
|
378
|
+
* scaffolded projects never grow one — it is the change-lifecycle structure,
|
|
379
|
+
* not something `specgate init` writes. Nothing to check is not a violation.
|
|
380
|
+
*/
|
|
381
|
+
checkRequirementSyntax(targetDir) {
|
|
382
|
+
const capabilitiesDir = path.join(targetDir, ChangeWorkspace_2.CAPABILITIES_DIR);
|
|
383
|
+
if (!fs.existsSync(capabilitiesDir))
|
|
384
|
+
return;
|
|
385
|
+
const findings = [];
|
|
386
|
+
for (const entry of fs
|
|
387
|
+
.readdirSync(capabilitiesDir, { withFileTypes: true })
|
|
388
|
+
.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
389
|
+
if (!entry.isDirectory())
|
|
390
|
+
continue;
|
|
391
|
+
const specPath = path.join(capabilitiesDir, entry.name, "spec.md");
|
|
392
|
+
if (!fs.existsSync(specPath))
|
|
393
|
+
continue;
|
|
394
|
+
const rel = path.relative(targetDir, specPath).split(path.sep).join("/");
|
|
395
|
+
const spec = (0, SpecParser_1.parseSpec)(fs.readFileSync(specPath, "utf8"));
|
|
396
|
+
for (const req of spec.requirements) {
|
|
397
|
+
const text = (0, SpecParser_1.blockText)(req.text);
|
|
398
|
+
if (!text.trim())
|
|
399
|
+
continue;
|
|
400
|
+
findings.push(...(0, RequirementSyntax_1.analyseRequirementText)(text, {
|
|
401
|
+
target: req.id || (0, SpecParser_1.requirementKey)(req) || req.name,
|
|
402
|
+
file: rel,
|
|
403
|
+
line: req.line,
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (findings.length === 0)
|
|
408
|
+
return;
|
|
409
|
+
if (this.io.json) {
|
|
410
|
+
this.io.fail({ validation: null }, findings);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
this.logError(`--strict-requirements violations detected: ${findings.length}`);
|
|
414
|
+
for (const f of findings)
|
|
415
|
+
process.stderr.write(` ${(0, diagnostics_1.formatDiagnostic)(f)}\n`);
|
|
416
|
+
this.logFix([
|
|
417
|
+
"State an obligation (SHALL / MUST / SHOULD / MAY, or DEBE / DEBERÁ), and if the",
|
|
418
|
+
"requirement opens with IF, resolve it with THEN in the same sentence.",
|
|
419
|
+
]);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
execute() {
|
|
423
|
+
const rawArgs = this.args[0] === "validate" ? this.args.slice(1) : this.args;
|
|
424
|
+
const argv = rawArgs;
|
|
425
|
+
this.io = (0, agent_1.agentIo)((0, agent_1.wantsJson)(argv));
|
|
426
|
+
const strictTdd = argv.includes("--strict-tdd");
|
|
427
|
+
const strictScenarios = argv.includes("--strict-scenarios");
|
|
428
|
+
const strictRequirements = argv.includes("--strict-requirements");
|
|
429
|
+
const strictLinks = argv.includes("--strict-links");
|
|
430
|
+
const againstLock = argv.includes("--against-lock");
|
|
431
|
+
const positional = argv.filter((a) => !a.startsWith("-"));
|
|
432
|
+
const targetDir = positional[0];
|
|
433
|
+
if (!targetDir) {
|
|
434
|
+
this.usage();
|
|
435
|
+
process.exit(2);
|
|
436
|
+
}
|
|
437
|
+
if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
|
|
438
|
+
this.fail("project_dir_not_found", `Directory not found: ${targetDir}`, agent_1.EXIT.USAGE, [
|
|
439
|
+
"Check the path for typos, or scaffold a new project first:",
|
|
440
|
+
" specgate init",
|
|
441
|
+
]);
|
|
442
|
+
}
|
|
443
|
+
const monorepo = this.validateMonorepo(targetDir, strictTdd, strictScenarios, strictRequirements, strictLinks);
|
|
444
|
+
if (monorepo !== null) {
|
|
445
|
+
process.exit(monorepo.failures === 0 ? 0 : 1);
|
|
446
|
+
}
|
|
447
|
+
const SKIP_DIRS = new Set([
|
|
448
|
+
"node_modules",
|
|
449
|
+
"dist",
|
|
450
|
+
"build",
|
|
451
|
+
"out",
|
|
452
|
+
"target",
|
|
453
|
+
"coverage",
|
|
454
|
+
"vendor",
|
|
455
|
+
".git",
|
|
456
|
+
".next",
|
|
457
|
+
".gradle",
|
|
458
|
+
".specops",
|
|
459
|
+
]);
|
|
460
|
+
function walk(dir) {
|
|
461
|
+
const out = [];
|
|
462
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
463
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name))
|
|
464
|
+
continue;
|
|
465
|
+
const full = path.join(dir, entry.name);
|
|
466
|
+
if (entry.isDirectory())
|
|
467
|
+
out.push(...walk(full));
|
|
468
|
+
else
|
|
469
|
+
out.push(full);
|
|
470
|
+
}
|
|
471
|
+
return out;
|
|
472
|
+
}
|
|
473
|
+
function findRecursive(rootDir, predicate) {
|
|
474
|
+
return walk(rootDir).filter(predicate);
|
|
475
|
+
}
|
|
476
|
+
const REQUIRED_FILES = [
|
|
477
|
+
"spec.md",
|
|
478
|
+
"AI_RULES.md",
|
|
479
|
+
"README.md",
|
|
480
|
+
"docs/specs/traceability.md",
|
|
481
|
+
"docs/specs/adr/README.md",
|
|
482
|
+
];
|
|
483
|
+
const REQUIRED_DIRS = ["features", "docs/specs"];
|
|
484
|
+
const PLACEHOLDER_RE = /\{\{[A-Z_][A-Z0-9_]*\}\}/;
|
|
485
|
+
for (const d of REQUIRED_DIRS) {
|
|
486
|
+
if (!fs.existsSync(path.join(targetDir, d))) {
|
|
487
|
+
this.fail("missing_required_dir", `Missing required directory: ${d}`, 1, [
|
|
488
|
+
`Create it: mkdir -p ${d}`,
|
|
489
|
+
"Or scaffold the full SDD structure with `specgate init` / `specops add`.",
|
|
490
|
+
]);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const FILE_FIXES = {
|
|
494
|
+
"spec.md": "Author it from the shipped template (templates/base/spec.md.tpl) — one `## REQ-NNN — <title>` section per requirement.",
|
|
495
|
+
"AI_RULES.md": "Author it from templates/backend/AI_RULES.md.tpl (or frontend) — it is the agent's project rulebook.",
|
|
496
|
+
"README.md": "Add a README.md describing how to build and test the project.",
|
|
497
|
+
"docs/specs/traceability.md": `Create it with the rich matrix header:\n ${TraceabilityFormat_1.RICH_HEADER}`,
|
|
498
|
+
"docs/specs/adr/README.md": "Create docs/specs/adr/README.md as the index of your Architecture Decision Records.",
|
|
499
|
+
};
|
|
500
|
+
for (const f of REQUIRED_FILES) {
|
|
501
|
+
if (!fs.existsSync(path.join(targetDir, f))) {
|
|
502
|
+
this.fail("missing_required_file", `Missing required file: ${f}`, 1, [
|
|
503
|
+
FILE_FIXES[f] || `Create ${f}.`,
|
|
504
|
+
"Generated projects include every required file — compare with `specgate init --yes --out <tmp>`.",
|
|
505
|
+
]);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const featuresDir = path.join(targetDir, "features");
|
|
509
|
+
const featureFiles = findRecursive(featuresDir, (f) => f.endsWith(".feature"));
|
|
510
|
+
if (featureFiles.length < 1) {
|
|
511
|
+
this.fail("no_feature_files", "No .feature files were found in features/", 1, [
|
|
512
|
+
"Write at least one Gherkin scenario, e.g. features/<area>/<name>.feature,",
|
|
513
|
+
"or pull scenarios from a domain pack: specgate specops add --pack-repo <url> …",
|
|
514
|
+
]);
|
|
515
|
+
}
|
|
516
|
+
const featureCount = featureFiles.length;
|
|
517
|
+
if (strictScenarios) {
|
|
518
|
+
this.checkScenarioQuality(targetDir, featureFiles);
|
|
519
|
+
}
|
|
520
|
+
if (strictRequirements) {
|
|
521
|
+
this.checkRequirementSyntax(targetDir);
|
|
522
|
+
}
|
|
523
|
+
const offenders = (0, placeholders_1.findUnresolvedPlaceholders)(targetDir).map((rel) => path.join(targetDir, rel));
|
|
524
|
+
if (offenders.length > 0) {
|
|
525
|
+
this.logError("Unresolved placeholders detected");
|
|
526
|
+
const tokens = new Set();
|
|
527
|
+
for (const f of offenders) {
|
|
528
|
+
const lines = fs.readFileSync(f, "utf8").split("\n");
|
|
529
|
+
for (let i = 0; i < lines.length; i++) {
|
|
530
|
+
if (PLACEHOLDER_RE.test(lines[i])) {
|
|
531
|
+
process.stderr.write(`${f}:${i + 1}:${lines[i]}\n`);
|
|
532
|
+
for (const m of lines[i].match(/\{\{[A-Z_][A-Z0-9_]*\}\}/g) || [])
|
|
533
|
+
tokens.add(m);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const varList = [...tokens].map((t) => String(t).replace(/[{}]/g, "")).join(", ");
|
|
538
|
+
this.logFix([
|
|
539
|
+
`Replace the tokens with real values, or re-expand the pack passing each variable:`,
|
|
540
|
+
` specgate specops sync --project-dir . (after adding the missing vars to .specops.lock)`,
|
|
541
|
+
`Missing variables: ${varList}`,
|
|
542
|
+
]);
|
|
543
|
+
process.exit(1);
|
|
544
|
+
}
|
|
545
|
+
const tracePath = path.join(targetDir, "docs/specs/traceability.md");
|
|
546
|
+
const traceContent = fs.readFileSync(tracePath, "utf8");
|
|
547
|
+
// Every judgement about the matrix's contents comes from the use case; this
|
|
548
|
+
// command's job from here is to render what it found and pick the exit code.
|
|
549
|
+
const specPath = path.join(targetDir, "spec.md");
|
|
550
|
+
const { report, mode, requirements: reqsInMatrix, } = new ValidateProjectUseCase_1.ValidateProjectUseCase(new DiskTraceabilityRepository_1.DiskTraceabilityRepository()).checkMatrix(traceContent, {
|
|
551
|
+
strictTdd,
|
|
552
|
+
specContent: fs.existsSync(specPath) ? fs.readFileSync(specPath, "utf8") : null,
|
|
553
|
+
});
|
|
554
|
+
const strictTddViolations = report.findings
|
|
555
|
+
.filter((f) => f.code === "strict_tdd_violation")
|
|
556
|
+
.map((f) => f.message);
|
|
557
|
+
for (const finding of report.findings) {
|
|
558
|
+
if (finding.code === "strict_tdd_violation")
|
|
559
|
+
continue; // reported together, below
|
|
560
|
+
this.fail(finding.code, finding.message, 1, [...(finding.fixLines || [])]);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const traceMode = mode;
|
|
564
|
+
const TDD_FIXES = {
|
|
565
|
+
"TDD-1": "Write the test first, then set its path in the row's 'Test artifact' column (or move the status back to Draft).",
|
|
566
|
+
"TDD-2": "Give the row a Scenario ID that matches a scenario in its feature file (e.g. SCN-001).",
|
|
567
|
+
"TDD-3": "Add a traceability row for the requirement — run `specgate plan` to list what each REQ still needs.",
|
|
568
|
+
};
|
|
569
|
+
if (strictTddViolations.length > 0) {
|
|
570
|
+
if (this.io.json) {
|
|
571
|
+
this.io.fail({ validation: null }, strictTddViolations.map((v) => {
|
|
572
|
+
const tdd = v.slice(1, 6);
|
|
573
|
+
return (0, diagnostics_1.error)(`strict_tdd_${tdd.split("-")[1]}`, v, {
|
|
574
|
+
fix: TDD_FIXES[tdd],
|
|
575
|
+
});
|
|
576
|
+
}));
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
this.logError("--strict-tdd violations detected:");
|
|
580
|
+
for (const v of strictTddViolations) {
|
|
581
|
+
process.stderr.write(` ${v}\n`);
|
|
582
|
+
}
|
|
583
|
+
const codes = new Set(strictTddViolations.map((v) => v.slice(1, 6)));
|
|
584
|
+
this.logFix([...codes].sort().map((c) => `${c}: ${TDD_FIXES[c]}`));
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
this.checkScenarioTags(targetDir, traceContent);
|
|
588
|
+
if (strictLinks) {
|
|
589
|
+
this.checkDeclaredArtifactsExist(targetDir, traceContent);
|
|
590
|
+
}
|
|
591
|
+
for (const ff of featureFiles.sort()) {
|
|
592
|
+
const rel = path.relative(targetDir, ff).split(path.sep).join("/");
|
|
593
|
+
if (!traceContent.includes(rel)) {
|
|
594
|
+
const exampleRow = traceMode === "rich"
|
|
595
|
+
? `| REQ-TBD | SCN-TBD | \`${rel}\` | UC-TBD | TBD | TBD | TBD | TBD | TBD | Draft |`
|
|
596
|
+
: `| \`${rel}\` | <scenario> | TBD | Draft |`;
|
|
597
|
+
this.fail("feature_not_in_matrix", `Feature file missing from traceability.md: ${rel}`, 1, [
|
|
598
|
+
"Add a row for it to docs/specs/traceability.md, e.g.:",
|
|
599
|
+
` ${exampleRow}`,
|
|
600
|
+
]);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
const useCasesPath = path.join(targetDir, "docs/specs/use-cases.md");
|
|
604
|
+
if (fs.existsSync(useCasesPath)) {
|
|
605
|
+
const content = fs.readFileSync(useCasesPath, "utf8");
|
|
606
|
+
if (!content.includes("| ID | Use Case | Actor | Requirement | Command/Query | Aggregate | Emits")) {
|
|
607
|
+
this.fail("use_cases_header_missing", "use-cases.md is missing the expected table header", 1, [
|
|
608
|
+
"Start the use-case table with:",
|
|
609
|
+
" | ID | Use Case | Actor | Requirement | Command/Query | Aggregate | Emits |",
|
|
610
|
+
]);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const eventsPath = path.join(targetDir, "docs/specs/events.md");
|
|
614
|
+
if (fs.existsSync(eventsPath)) {
|
|
615
|
+
const content = fs.readFileSync(eventsPath, "utf8");
|
|
616
|
+
if (!content.includes("| ID | Event | Producer | Consumers | Payload |")) {
|
|
617
|
+
this.fail("events_header_missing", "events.md is missing the expected table header", 1, [
|
|
618
|
+
"Start the events table with:",
|
|
619
|
+
" | ID | Event | Producer | Consumers | Payload |",
|
|
620
|
+
]);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
let changeCount = 0;
|
|
624
|
+
const changesDir = path.join(targetDir, "docs/specs/changes");
|
|
625
|
+
if (fs.existsSync(changesDir)) {
|
|
626
|
+
const ids = (0, ChangeWorkspace_1.listChangeIds)(targetDir);
|
|
627
|
+
changeCount = ids.length;
|
|
628
|
+
const problems = [];
|
|
629
|
+
const rawProblems = [];
|
|
630
|
+
for (const id of ids) {
|
|
631
|
+
const result = new ValidateChangeUseCase_1.ValidateChangeUseCase(new DiskProjectRepository_1.DiskProjectRepository(targetDir)).execute(id, {
|
|
632
|
+
strict: false,
|
|
633
|
+
});
|
|
634
|
+
for (const d of result.diagnostics) {
|
|
635
|
+
if (d.severity === "error") {
|
|
636
|
+
rawProblems.push(d);
|
|
637
|
+
problems.push((0, diagnostics_1.formatDiagnostic)(d));
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (problems.length > 0) {
|
|
642
|
+
if (this.io.json) {
|
|
643
|
+
this.io.fail({ validation: null }, rawProblems);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
this.logError("Active changes have invalid delta specs:");
|
|
647
|
+
for (const line of problems)
|
|
648
|
+
process.stderr.write(` ${line}\n`);
|
|
649
|
+
process.exit(1);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
let lockChecked = 0;
|
|
653
|
+
let lockAdvisories = [];
|
|
654
|
+
if (againstLock) {
|
|
655
|
+
const result = (0, against_lock_1.checkAgainstLock)(targetDir, {});
|
|
656
|
+
lockChecked = result.checked;
|
|
657
|
+
const errors = result.diagnostics.filter((d) => d.severity === "error");
|
|
658
|
+
const rest = result.diagnostics.filter((d) => d.severity !== "error");
|
|
659
|
+
if (!this.io.json) {
|
|
660
|
+
for (const d of rest)
|
|
661
|
+
process.stdout.write(` ${(0, diagnostics_1.formatDiagnostic)(d)}\n`);
|
|
662
|
+
}
|
|
663
|
+
if (errors.length > 0) {
|
|
664
|
+
if (this.io.json) {
|
|
665
|
+
this.io.fail({ validation: null }, result.diagnostics);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
this.logError("--against-lock violations detected:");
|
|
669
|
+
for (const d of errors)
|
|
670
|
+
process.stderr.write(` ${(0, diagnostics_1.formatDiagnostic)(d)}\n`);
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
lockAdvisories = rest;
|
|
674
|
+
}
|
|
675
|
+
{
|
|
676
|
+
const graph = (0, requirement_graph_1.requirementGraphFromProject)(targetDir, [...reqsInMatrix]);
|
|
677
|
+
const graphProblems = [];
|
|
678
|
+
for (const cycle of graph.cycles) {
|
|
679
|
+
const loop = [...cycle, cycle[0]].join(" → ");
|
|
680
|
+
graphProblems.push((0, diagnostics_1.error)("requirement_cycle", `Requirements depend on each other in a cycle: ${loop}`, {
|
|
681
|
+
target: cycle[0],
|
|
682
|
+
fix: `Remove one \`depends=\` from the csda:trace comment of one of them — ` +
|
|
683
|
+
`${cycle.join(", ")} cannot all come after each other.`,
|
|
684
|
+
}));
|
|
685
|
+
}
|
|
686
|
+
for (const { requirement, dependency } of graph.unknown) {
|
|
687
|
+
graphProblems.push((0, diagnostics_1.error)("unknown_dependency", `${requirement} declares depends=${dependency}, which is not a requirement in this project.`, {
|
|
688
|
+
target: requirement,
|
|
689
|
+
fix: `Correct the id in ${requirement}'s csda:trace comment, or add a traceability row for ${dependency}.`,
|
|
690
|
+
}));
|
|
691
|
+
}
|
|
692
|
+
for (const requirement of graph.selfReferential) {
|
|
693
|
+
graphProblems.push((0, diagnostics_1.error)("self_dependency", `${requirement} declares that it depends on itself.`, {
|
|
694
|
+
target: requirement,
|
|
695
|
+
fix: `Remove \`depends=${requirement}\` from its csda:trace comment.`,
|
|
696
|
+
}));
|
|
697
|
+
}
|
|
698
|
+
if (graphProblems.length > 0) {
|
|
699
|
+
if (this.io.json) {
|
|
700
|
+
this.io.fail({ validation: null }, graphProblems);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
this.logError("Requirement dependency problems:");
|
|
704
|
+
for (const d of graphProblems)
|
|
705
|
+
process.stderr.write(` ${(0, diagnostics_1.formatDiagnostic)(d)}\n`);
|
|
706
|
+
process.exit(1);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
const skeletonOnly = this.isAdoptionSkeletonOnly(targetDir, featureFiles);
|
|
710
|
+
const advisories = skeletonOnly
|
|
711
|
+
? lockAdvisories.concat([
|
|
712
|
+
{
|
|
713
|
+
severity: "warning",
|
|
714
|
+
code: "adoption_not_retrofilled",
|
|
715
|
+
message: "The only scenario in this project is the adoption baseline. Nothing the codebase actually does is specified yet.",
|
|
716
|
+
target: "features/adoption/baseline.feature",
|
|
717
|
+
fix: 'Retro-fill one requirement you already rely on: specgate onboard, then specgate req add "<behaviour>" and specgate req link.',
|
|
718
|
+
},
|
|
719
|
+
])
|
|
720
|
+
: lockAdvisories;
|
|
721
|
+
if (this.io.json) {
|
|
722
|
+
this.io.emit({
|
|
723
|
+
validation: {
|
|
724
|
+
projectDir: targetDir,
|
|
725
|
+
passed: true,
|
|
726
|
+
features: featureCount,
|
|
727
|
+
activeChanges: changeCount,
|
|
728
|
+
traceabilityMode: traceMode,
|
|
729
|
+
strictTdd,
|
|
730
|
+
againstLock,
|
|
731
|
+
packsChecked: lockChecked,
|
|
732
|
+
adoptionRetrofilled: !skeletonOnly,
|
|
733
|
+
},
|
|
734
|
+
status: advisories,
|
|
735
|
+
});
|
|
736
|
+
process.exit(agent_1.EXIT.OK);
|
|
737
|
+
}
|
|
738
|
+
this.logInfo("✅ Validation passed");
|
|
739
|
+
this.logInfo(`- Features detected: ${featureCount}`);
|
|
740
|
+
if (changeCount > 0)
|
|
741
|
+
this.logInfo(`- Active changes: ${changeCount} (deltas valid)`);
|
|
742
|
+
if (againstLock) {
|
|
743
|
+
this.logInfo(lockChecked > 0
|
|
744
|
+
? `- Lock drift gate: passed (${lockChecked} pack(s) checked)`
|
|
745
|
+
: "- Lock drift gate: no locked packs to check");
|
|
746
|
+
}
|
|
747
|
+
this.logInfo("- Base SDD structure: complete");
|
|
748
|
+
this.logInfo(`- Traceability mode: ${traceMode}`);
|
|
749
|
+
if (strictTdd)
|
|
750
|
+
this.logInfo("- Strict TDD gate: passed");
|
|
751
|
+
if (skeletonOnly) {
|
|
752
|
+
this.logWarn("Adoption never retro-filled — the only scenario is the adoption baseline.");
|
|
753
|
+
this.logFix([
|
|
754
|
+
"This passes, but it certifies the skeleton, not the code.",
|
|
755
|
+
" specgate onboard # what this codebase already implies",
|
|
756
|
+
' specgate req add "<behaviour>" # one requirement you already rely on',
|
|
757
|
+
" specgate req link REQ-NNN --code <path> --test <path>",
|
|
758
|
+
]);
|
|
759
|
+
}
|
|
760
|
+
process.exit(0);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
exports.ValidateSpecsCommand = ValidateSpecsCommand;
|
|
764
|
+
//# sourceMappingURL=ValidateSpecsCommand.js.map
|