agent-sdd 1.0.3
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/LICENSE +21 -0
- package/README.md +1028 -0
- package/README.ru.md +1046 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +867 -0
- package/dist/features/approve/adapters/inbound/CliApproveHandler.d.ts +17 -0
- package/dist/features/approve/adapters/inbound/CliApproveHandler.js +108 -0
- package/dist/features/approve/adapters/outbound/NodeApproveFileSystem.d.ts +8 -0
- package/dist/features/approve/adapters/outbound/NodeApproveFileSystem.js +147 -0
- package/dist/features/approve/adapters/outbound/NodePlanFileWriter.d.ts +6 -0
- package/dist/features/approve/adapters/outbound/NodePlanFileWriter.js +92 -0
- package/dist/features/approve/adapters/outbound/SystemApproveClock.d.ts +4 -0
- package/dist/features/approve/adapters/outbound/SystemApproveClock.js +5 -0
- package/dist/features/approve/application/ApplyApproval.d.ts +19 -0
- package/dist/features/approve/application/ApplyApproval.js +30 -0
- package/dist/features/approve/application/WriteAttestation.d.ts +19 -0
- package/dist/features/approve/application/WriteAttestation.js +23 -0
- package/dist/features/approve/domain/ApproveRequest.d.ts +24 -0
- package/dist/features/approve/domain/ApproveRequest.js +24 -0
- package/dist/features/approve/domain/Rewrite.d.ts +1 -0
- package/dist/features/approve/domain/Rewrite.js +6 -0
- package/dist/features/approve/ports/inbound/ApproveCommand.d.ts +10 -0
- package/dist/features/approve/ports/inbound/ApproveCommand.js +1 -0
- package/dist/features/approve/ports/outbound/ApproveClock.d.ts +3 -0
- package/dist/features/approve/ports/outbound/ApproveClock.js +1 -0
- package/dist/features/approve/ports/outbound/ApproveConfigPort.d.ts +4 -0
- package/dist/features/approve/ports/outbound/ApproveConfigPort.js +1 -0
- package/dist/features/approve/ports/outbound/ApproveFileSystem.d.ts +8 -0
- package/dist/features/approve/ports/outbound/ApproveFileSystem.js +1 -0
- package/dist/features/approve/ports/outbound/PlanFileWriter.d.ts +13 -0
- package/dist/features/approve/ports/outbound/PlanFileWriter.js +1 -0
- package/dist/features/check/adapters/inbound/CliCheckHandler.d.ts +8 -0
- package/dist/features/check/adapters/inbound/CliCheckHandler.js +62 -0
- package/dist/features/check/adapters/outbound/ChildProcessCheckGit.d.ts +8 -0
- package/dist/features/check/adapters/outbound/ChildProcessCheckGit.js +112 -0
- package/dist/features/check/adapters/outbound/NodeCheckFileReader.d.ts +7 -0
- package/dist/features/check/adapters/outbound/NodeCheckFileReader.js +44 -0
- package/dist/features/check/application/CheckBaseline.d.ts +28 -0
- package/dist/features/check/application/CheckBaseline.js +54 -0
- package/dist/features/check/domain/BaselineComparison.d.ts +1 -0
- package/dist/features/check/domain/BaselineComparison.js +2 -0
- package/dist/features/check/ports/inbound/CheckCommand.d.ts +4 -0
- package/dist/features/check/ports/inbound/CheckCommand.js +1 -0
- package/dist/features/check/ports/outbound/CheckConfigPort.d.ts +4 -0
- package/dist/features/check/ports/outbound/CheckConfigPort.js +1 -0
- package/dist/features/check/ports/outbound/CheckGitPort.d.ts +7 -0
- package/dist/features/check/ports/outbound/CheckGitPort.js +1 -0
- package/dist/features/check/ports/outbound/CheckSpecPort.d.ts +9 -0
- package/dist/features/check/ports/outbound/CheckSpecPort.js +1 -0
- package/dist/features/doctor/adapters/inbound/CliDoctorHandler.d.ts +8 -0
- package/dist/features/doctor/adapters/inbound/CliDoctorHandler.js +77 -0
- package/dist/features/doctor/adapters/outbound/NodeRegistryReader.d.ts +5 -0
- package/dist/features/doctor/adapters/outbound/NodeRegistryReader.js +40 -0
- package/dist/features/doctor/application/RunDoctor.d.ts +30 -0
- package/dist/features/doctor/application/RunDoctor.js +78 -0
- package/dist/features/doctor/domain/RegistryRow.d.ts +23 -0
- package/dist/features/doctor/domain/RegistryRow.js +114 -0
- package/dist/features/doctor/domain/SemverRange.d.ts +7 -0
- package/dist/features/doctor/domain/SemverRange.js +82 -0
- package/dist/features/doctor/ports/inbound/DoctorCommand.d.ts +8 -0
- package/dist/features/doctor/ports/inbound/DoctorCommand.js +1 -0
- package/dist/features/doctor/ports/outbound/RegistryReader.d.ts +12 -0
- package/dist/features/doctor/ports/outbound/RegistryReader.js +1 -0
- package/dist/features/finalize/adapters/inbound/CliFinalizeHandler.d.ts +8 -0
- package/dist/features/finalize/adapters/inbound/CliFinalizeHandler.js +80 -0
- package/dist/features/finalize/adapters/outbound/NodeFinalizeFileSystem.d.ts +11 -0
- package/dist/features/finalize/adapters/outbound/NodeFinalizeFileSystem.js +167 -0
- package/dist/features/finalize/adapters/outbound/NodePlanRepo.d.ts +7 -0
- package/dist/features/finalize/adapters/outbound/NodePlanRepo.js +82 -0
- package/dist/features/finalize/adapters/outbound/SystemFinalizeClock.d.ts +4 -0
- package/dist/features/finalize/adapters/outbound/SystemFinalizeClock.js +5 -0
- package/dist/features/finalize/application/RunFinalize.d.ts +34 -0
- package/dist/features/finalize/application/RunFinalize.js +98 -0
- package/dist/features/finalize/domain/ValidateFinalizeGraph.d.ts +9 -0
- package/dist/features/finalize/domain/ValidateFinalizeGraph.js +86 -0
- package/dist/features/finalize/ports/inbound/FinalizeCommand.d.ts +7 -0
- package/dist/features/finalize/ports/inbound/FinalizeCommand.js +1 -0
- package/dist/features/finalize/ports/outbound/FinalizeClock.d.ts +3 -0
- package/dist/features/finalize/ports/outbound/FinalizeClock.js +1 -0
- package/dist/features/finalize/ports/outbound/FinalizeConfigPort.d.ts +4 -0
- package/dist/features/finalize/ports/outbound/FinalizeConfigPort.js +1 -0
- package/dist/features/finalize/ports/outbound/FinalizeFileSystem.d.ts +14 -0
- package/dist/features/finalize/ports/outbound/FinalizeFileSystem.js +1 -0
- package/dist/features/finalize/ports/outbound/PlanRepo.d.ts +21 -0
- package/dist/features/finalize/ports/outbound/PlanRepo.js +1 -0
- package/dist/features/install/adapters/inbound/CliInstallHandler.d.ts +8 -0
- package/dist/features/install/adapters/inbound/CliInstallHandler.js +54 -0
- package/dist/features/install/adapters/outbound/NodeInstallSource.d.ts +7 -0
- package/dist/features/install/adapters/outbound/NodeInstallSource.js +24 -0
- package/dist/features/install/adapters/outbound/NodeInstallTargetFs.d.ts +7 -0
- package/dist/features/install/adapters/outbound/NodeInstallTargetFs.js +30 -0
- package/dist/features/install/application/InstallRules.d.ts +10 -0
- package/dist/features/install/application/InstallRules.js +73 -0
- package/dist/features/install/domain/InstallPlan.d.ts +27 -0
- package/dist/features/install/domain/InstallPlan.js +168 -0
- package/dist/features/install/domain/InstallResult.d.ts +23 -0
- package/dist/features/install/domain/InstallResult.js +1 -0
- package/dist/features/install/domain/InstallTarget.d.ts +6 -0
- package/dist/features/install/domain/InstallTarget.js +7 -0
- package/dist/features/install/domain/ManagedBlock.d.ts +3 -0
- package/dist/features/install/domain/ManagedBlock.js +20 -0
- package/dist/features/install/domain/RuleManifest.d.ts +17 -0
- package/dist/features/install/domain/RuleManifest.js +69 -0
- package/dist/features/install/domain/SettingsMerge.d.ts +5 -0
- package/dist/features/install/domain/SettingsMerge.js +43 -0
- package/dist/features/install/ports/inbound/InstallCommand.d.ts +10 -0
- package/dist/features/install/ports/inbound/InstallCommand.js +1 -0
- package/dist/features/install/ports/outbound/InstallSource.d.ts +4 -0
- package/dist/features/install/ports/outbound/InstallSource.js +1 -0
- package/dist/features/install/ports/outbound/InstallTargetFs.d.ts +6 -0
- package/dist/features/install/ports/outbound/InstallTargetFs.js +1 -0
- package/dist/features/lint/adapters/inbound/CliLintHandler.d.ts +8 -0
- package/dist/features/lint/adapters/inbound/CliLintHandler.js +61 -0
- package/dist/features/lint/adapters/outbound/NodeLintFileReader.d.ts +7 -0
- package/dist/features/lint/adapters/outbound/NodeLintFileReader.js +165 -0
- package/dist/features/lint/application/RunLint.d.ts +10 -0
- package/dist/features/lint/application/RunLint.js +100 -0
- package/dist/features/lint/domain/Diagnostic.d.ts +1 -0
- package/dist/features/lint/domain/Diagnostic.js +2 -0
- package/dist/features/lint/domain/Record.d.ts +1 -0
- package/dist/features/lint/domain/Record.js +5 -0
- package/dist/features/lint/domain/Rules.d.ts +1 -0
- package/dist/features/lint/domain/Rules.js +2 -0
- package/dist/features/lint/domain/SpecParser.d.ts +1 -0
- package/dist/features/lint/domain/SpecParser.js +2 -0
- package/dist/features/lint/ports/inbound/LintCommand.d.ts +4 -0
- package/dist/features/lint/ports/inbound/LintCommand.js +1 -0
- package/dist/features/lint/ports/outbound/LintConfigPort.d.ts +4 -0
- package/dist/features/lint/ports/outbound/LintConfigPort.js +1 -0
- package/dist/features/lint/ports/outbound/LintFileReader.d.ts +10 -0
- package/dist/features/lint/ports/outbound/LintFileReader.js +1 -0
- package/dist/features/plan/adapters/inbound/CliPlanShowHandler.d.ts +8 -0
- package/dist/features/plan/adapters/inbound/CliPlanShowHandler.js +73 -0
- package/dist/features/plan/adapters/outbound/NodePlanReader.d.ts +7 -0
- package/dist/features/plan/adapters/outbound/NodePlanReader.js +68 -0
- package/dist/features/plan/application/ShowPlan.d.ts +7 -0
- package/dist/features/plan/application/ShowPlan.js +4 -0
- package/dist/features/plan/ports/inbound/PlanShowCommand.d.ts +7 -0
- package/dist/features/plan/ports/inbound/PlanShowCommand.js +1 -0
- package/dist/features/plan/ports/outbound/PlanConfigPort.d.ts +4 -0
- package/dist/features/plan/ports/outbound/PlanConfigPort.js +1 -0
- package/dist/features/plan/ports/outbound/PlanReader.d.ts +19 -0
- package/dist/features/plan/ports/outbound/PlanReader.js +1 -0
- package/dist/features/ready/adapters/inbound/CliReadyHandler.d.ts +8 -0
- package/dist/features/ready/adapters/inbound/CliReadyHandler.js +79 -0
- package/dist/features/ready/adapters/outbound/ChildProcessReadyGit.d.ts +9 -0
- package/dist/features/ready/adapters/outbound/ChildProcessReadyGit.js +113 -0
- package/dist/features/ready/adapters/outbound/NodeReadyFileSystem.d.ts +8 -0
- package/dist/features/ready/adapters/outbound/NodeReadyFileSystem.js +159 -0
- package/dist/features/ready/application/RunReady.d.ts +16 -0
- package/dist/features/ready/application/RunReady.js +572 -0
- package/dist/features/ready/domain/AggregatedRules.d.ts +16 -0
- package/dist/features/ready/domain/AggregatedRules.js +42 -0
- package/dist/features/ready/domain/MarkerParser.d.ts +17 -0
- package/dist/features/ready/domain/MarkerParser.js +108 -0
- package/dist/features/ready/domain/PartitionResolver.d.ts +1 -0
- package/dist/features/ready/domain/PartitionResolver.js +5 -0
- package/dist/features/ready/domain/ReadyInput.d.ts +6 -0
- package/dist/features/ready/domain/ReadyInput.js +1 -0
- package/dist/features/ready/domain/ReadyViolation.d.ts +38 -0
- package/dist/features/ready/domain/ReadyViolation.js +19 -0
- package/dist/features/ready/domain/Rules.d.ts +22 -0
- package/dist/features/ready/domain/Rules.js +243 -0
- package/dist/features/ready/domain/SpecDiff.d.ts +33 -0
- package/dist/features/ready/domain/SpecDiff.js +321 -0
- package/dist/features/ready/ports/inbound/ReadyCommand.d.ts +4 -0
- package/dist/features/ready/ports/inbound/ReadyCommand.js +1 -0
- package/dist/features/ready/ports/outbound/ReadyConfigPort.d.ts +4 -0
- package/dist/features/ready/ports/outbound/ReadyConfigPort.js +1 -0
- package/dist/features/ready/ports/outbound/ReadyFileReader.d.ts +12 -0
- package/dist/features/ready/ports/outbound/ReadyFileReader.js +1 -0
- package/dist/features/ready/ports/outbound/ReadyGitPort.d.ts +10 -0
- package/dist/features/ready/ports/outbound/ReadyGitPort.js +5 -0
- package/dist/features/record/adapters/inbound/CliRecordHandler.d.ts +10 -0
- package/dist/features/record/adapters/inbound/CliRecordHandler.js +111 -0
- package/dist/features/record/adapters/outbound/NodeRecordFileSystem.d.ts +9 -0
- package/dist/features/record/adapters/outbound/NodeRecordFileSystem.js +152 -0
- package/dist/features/record/application/AddRecord.d.ts +11 -0
- package/dist/features/record/application/AddRecord.js +84 -0
- package/dist/features/record/application/GetRecord.d.ts +8 -0
- package/dist/features/record/application/GetRecord.js +22 -0
- package/dist/features/record/application/ListRecords.d.ts +9 -0
- package/dist/features/record/application/ListRecords.js +24 -0
- package/dist/features/record/application/SetRecord.d.ts +11 -0
- package/dist/features/record/application/SetRecord.js +68 -0
- package/dist/features/record/domain/RecordBody.d.ts +12 -0
- package/dist/features/record/domain/RecordBody.js +66 -0
- package/dist/features/record/domain/RecordPartition.d.ts +1 -0
- package/dist/features/record/domain/RecordPartition.js +7 -0
- package/dist/features/record/domain/RecordSlice.d.ts +7 -0
- package/dist/features/record/domain/RecordSlice.js +1 -0
- package/dist/features/record/domain/RecordSummary.d.ts +11 -0
- package/dist/features/record/domain/RecordSummary.js +13 -0
- package/dist/features/record/domain/RecordWrite.d.ts +14 -0
- package/dist/features/record/domain/RecordWrite.js +8 -0
- package/dist/features/record/ports/inbound/RecordCommand.d.ts +19 -0
- package/dist/features/record/ports/inbound/RecordCommand.js +1 -0
- package/dist/features/record/ports/outbound/RecordConfigPort.d.ts +4 -0
- package/dist/features/record/ports/outbound/RecordConfigPort.js +1 -0
- package/dist/features/record/ports/outbound/RecordFileReader.d.ts +10 -0
- package/dist/features/record/ports/outbound/RecordFileReader.js +1 -0
- package/dist/features/record/ports/outbound/RecordFileWriter.d.ts +6 -0
- package/dist/features/record/ports/outbound/RecordFileWriter.js +1 -0
- package/dist/features/refresh/adapters/inbound/CliRefreshHandler.d.ts +8 -0
- package/dist/features/refresh/adapters/inbound/CliRefreshHandler.js +24 -0
- package/dist/features/refresh/adapters/outbound/ChildProcessRefreshGit.d.ts +8 -0
- package/dist/features/refresh/adapters/outbound/ChildProcessRefreshGit.js +118 -0
- package/dist/features/refresh/adapters/outbound/NodeRefreshFileReader.d.ts +7 -0
- package/dist/features/refresh/adapters/outbound/NodeRefreshFileReader.js +44 -0
- package/dist/features/refresh/adapters/outbound/SystemRefreshClock.d.ts +4 -0
- package/dist/features/refresh/adapters/outbound/SystemRefreshClock.js +5 -0
- package/dist/features/refresh/application/BuildRefreshStubs.d.ts +25 -0
- package/dist/features/refresh/application/BuildRefreshStubs.js +43 -0
- package/dist/features/refresh/domain/DiffStubs.d.ts +24 -0
- package/dist/features/refresh/domain/DiffStubs.js +81 -0
- package/dist/features/refresh/domain/Footprint.d.ts +14 -0
- package/dist/features/refresh/domain/Footprint.js +45 -0
- package/dist/features/refresh/ports/inbound/RefreshCommand.d.ts +4 -0
- package/dist/features/refresh/ports/inbound/RefreshCommand.js +1 -0
- package/dist/features/refresh/ports/outbound/RefreshClockPort.d.ts +3 -0
- package/dist/features/refresh/ports/outbound/RefreshClockPort.js +1 -0
- package/dist/features/refresh/ports/outbound/RefreshConfigPort.d.ts +4 -0
- package/dist/features/refresh/ports/outbound/RefreshConfigPort.js +1 -0
- package/dist/features/refresh/ports/outbound/RefreshGitPort.d.ts +7 -0
- package/dist/features/refresh/ports/outbound/RefreshGitPort.js +1 -0
- package/dist/features/refresh/ports/outbound/RefreshSpecPort.d.ts +9 -0
- package/dist/features/refresh/ports/outbound/RefreshSpecPort.js +1 -0
- package/dist/features/report/adapters/inbound/CliReportHandler.d.ts +8 -0
- package/dist/features/report/adapters/inbound/CliReportHandler.js +35 -0
- package/dist/features/report/adapters/outbound/NodeReportFileSystem.d.ts +7 -0
- package/dist/features/report/adapters/outbound/NodeReportFileSystem.js +128 -0
- package/dist/features/report/application/RunReport.d.ts +19 -0
- package/dist/features/report/application/RunReport.js +161 -0
- package/dist/features/report/ports/inbound/ReportCommand.d.ts +8 -0
- package/dist/features/report/ports/inbound/ReportCommand.js +1 -0
- package/dist/features/report/ports/outbound/ReportConfigPort.d.ts +4 -0
- package/dist/features/report/ports/outbound/ReportConfigPort.js +1 -0
- package/dist/features/report/ports/outbound/ReportFileReader.d.ts +7 -0
- package/dist/features/report/ports/outbound/ReportFileReader.js +1 -0
- package/dist/features/token/adapters/inbound/CliTokenHandler.d.ts +8 -0
- package/dist/features/token/adapters/inbound/CliTokenHandler.js +53 -0
- package/dist/features/token/adapters/outbound/ChildProcessTokenGit.d.ts +8 -0
- package/dist/features/token/adapters/outbound/ChildProcessTokenGit.js +112 -0
- package/dist/features/token/adapters/outbound/NodeTokenConfigReader.d.ts +5 -0
- package/dist/features/token/adapters/outbound/NodeTokenConfigReader.js +41 -0
- package/dist/features/token/application/ComputeToken.d.ts +18 -0
- package/dist/features/token/application/ComputeToken.js +27 -0
- package/dist/features/token/ports/inbound/TokenCommand.d.ts +4 -0
- package/dist/features/token/ports/inbound/TokenCommand.js +1 -0
- package/dist/features/token/ports/outbound/TokenConfigPort.d.ts +4 -0
- package/dist/features/token/ports/outbound/TokenConfigPort.js +1 -0
- package/dist/features/token/ports/outbound/TokenGitPort.d.ts +7 -0
- package/dist/features/token/ports/outbound/TokenGitPort.js +1 -0
- package/dist/shared/domain/AgentBlocklist.d.ts +5 -0
- package/dist/shared/domain/AgentBlocklist.js +28 -0
- package/dist/shared/domain/BoundaryReachability.d.ts +5 -0
- package/dist/shared/domain/BoundaryReachability.js +71 -0
- package/dist/shared/domain/CheckOutcome.d.ts +10 -0
- package/dist/shared/domain/CheckOutcome.js +7 -0
- package/dist/shared/domain/CliOutput.d.ts +10 -0
- package/dist/shared/domain/CliOutput.js +29 -0
- package/dist/shared/domain/Config.d.ts +29 -0
- package/dist/shared/domain/Config.js +201 -0
- package/dist/shared/domain/DiagnosticRegistry.d.ts +8 -0
- package/dist/shared/domain/DiagnosticRegistry.js +71 -0
- package/dist/shared/domain/Errors.d.ts +12 -0
- package/dist/shared/domain/Errors.js +23 -0
- package/dist/shared/domain/GlobMatch.d.ts +2 -0
- package/dist/shared/domain/GlobMatch.js +58 -0
- package/dist/shared/domain/LintReport.d.ts +16 -0
- package/dist/shared/domain/LintReport.js +11 -0
- package/dist/shared/domain/LintRules.d.ts +67 -0
- package/dist/shared/domain/LintRules.js +956 -0
- package/dist/shared/domain/PartitionGrammar.d.ts +4 -0
- package/dist/shared/domain/PartitionGrammar.js +16 -0
- package/dist/shared/domain/PlanFile.d.ts +28 -0
- package/dist/shared/domain/PlanFile.js +112 -0
- package/dist/shared/domain/Scope.d.ts +1 -0
- package/dist/shared/domain/Scope.js +3 -0
- package/dist/shared/domain/SpecApprovalRewrite.d.ts +23 -0
- package/dist/shared/domain/SpecApprovalRewrite.js +254 -0
- package/dist/shared/domain/SpecBlocks.d.ts +12 -0
- package/dist/shared/domain/SpecBlocks.js +96 -0
- package/dist/shared/domain/SpecRecord.d.ts +17 -0
- package/dist/shared/domain/SpecRecord.js +208 -0
- package/dist/shared/domain/TemplateFieldMetadata.d.ts +2 -0
- package/dist/shared/domain/TemplateFieldMetadata.js +177 -0
- package/dist/shared/domain/Token.d.ts +2 -0
- package/dist/shared/domain/Token.js +5 -0
- package/dist/shared/domain/WeaselWords.d.ts +3 -0
- package/dist/shared/domain/WeaselWords.js +32 -0
- package/package.json +71 -0
- package/rules/enforcement_registry.md +126 -0
- package/rules/hooks/sdd-lint-reminder.sh +33 -0
- package/rules/hooks/sdd-spec-read-guard.sh +73 -0
- package/rules/manifest.json +15 -0
- package/rules/review-sdd.md +9 -0
- package/rules/sdd-cli-usage.md +91 -0
- package/rules/skills/spec-driven-development/SKILL.md +554 -0
- package/rules/skills/spec-driven-development/data/weasel-words.json +22 -0
- package/rules/spec-driven-development.md +69 -0
- package/rules/tdd-sdd.md +127 -0
- package/rules/workflow-sdd.md +56 -0
- package/schema/sdd.config.schema.json +104 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Single source of truth for the partition-prefix grammar (CST-007),
|
|
3
|
+
* imported by both `MarkerParser` and `Config` so the two cannot drift.
|
|
4
|
+
*/
|
|
5
|
+
/*
|
|
6
|
+
* One or more lowercase tokens joined by `:`, so adopters can namespace
|
|
7
|
+
* partitions (e.g. `bridge:commands`).
|
|
8
|
+
*/
|
|
9
|
+
export const PARTITION_PREFIX_RE_SRC = "[a-z][a-z0-9-]*(?::[a-z][a-z0-9-]*)*";
|
|
10
|
+
/*
|
|
11
|
+
* Normative-ID tail. Contains no `:`, which is what makes the rightmost-`:`
|
|
12
|
+
* split in `MarkerParser.lastIndexOf(":")` unambiguous (CST-007).
|
|
13
|
+
*/
|
|
14
|
+
export const ID_TAIL_RE_SRC = "[A-Z]+-\\d+";
|
|
15
|
+
export const PARTITION_NAME_RE = new RegExp(`^${PARTITION_PREFIX_RE_SRC}$`);
|
|
16
|
+
export const NORMATIVE_ID_RE = new RegExp(`^${PARTITION_PREFIX_RE_SRC}:${ID_TAIL_RE_SRC}$`);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type AttestationTargetStatus = "approved" | "deprecated" | "removed";
|
|
2
|
+
export interface PendingAttestation {
|
|
3
|
+
id: string;
|
|
4
|
+
ownerRole: string;
|
|
5
|
+
approverIdentity: string;
|
|
6
|
+
timestamp: string;
|
|
7
|
+
changeRequest: string;
|
|
8
|
+
scope: string;
|
|
9
|
+
targetStatus: AttestationTargetStatus;
|
|
10
|
+
reviewedTestOracle?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface PlanFileShape {
|
|
13
|
+
planId: string;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
pendingAttestations: PendingAttestation[];
|
|
16
|
+
}
|
|
17
|
+
export declare const PLAN_ID_GRAMMAR: RegExp;
|
|
18
|
+
/** Generate a plan-id from a Date and 5 [a-z0-9] random chars.
|
|
19
|
+
* rand: optional injectable RNG (yields a value in [0, 1)) for determinism. */
|
|
20
|
+
export declare function generatePlanId(now: Date, rand?: () => number): string;
|
|
21
|
+
/** Format a Date as YYYY-MM-DDTHHMMSSZ (ISO-8601 basic UTC, second precision). */
|
|
22
|
+
export declare function isoBasicUtc(now: Date): string;
|
|
23
|
+
/** Parse a YAML-decoded plan-file object into a typed PlanFileShape.
|
|
24
|
+
* Throws on shape violation. */
|
|
25
|
+
export declare function parsePlanFile(value: unknown): PlanFileShape;
|
|
26
|
+
/** Serialise a PlanFileShape into a stable YAML string. The fields are
|
|
27
|
+
* emitted in fixed order so plan files diff cleanly across edits. */
|
|
28
|
+
export declare function serialisePlanFile(plan: PlanFileShape): string;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* CTR-019 / SUR-014
|
|
3
|
+
* Plan-file shape: one YAML file per plan_id, holding pending attestations
|
|
4
|
+
* that `sdd finalize` materialises atomically into spec files. Pure module
|
|
5
|
+
* (no node:*); filesystem I/O lives in feature-local outbound adapters.
|
|
6
|
+
*/
|
|
7
|
+
export const PLAN_ID_GRAMMAR = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{6}Z-[a-z0-9]{5}$/;
|
|
8
|
+
const RAND_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
9
|
+
/** Generate a plan-id from a Date and 5 [a-z0-9] random chars.
|
|
10
|
+
* rand: optional injectable RNG (yields a value in [0, 1)) for determinism. */
|
|
11
|
+
export function generatePlanId(now, rand = Math.random) {
|
|
12
|
+
const ts = isoBasicUtc(now);
|
|
13
|
+
let suffix = "";
|
|
14
|
+
for (let i = 0; i < 5; i++) {
|
|
15
|
+
const r = Math.floor(rand() * RAND_ALPHABET.length);
|
|
16
|
+
suffix += RAND_ALPHABET[r];
|
|
17
|
+
}
|
|
18
|
+
return `${ts}-${suffix}`;
|
|
19
|
+
}
|
|
20
|
+
/** Format a Date as YYYY-MM-DDTHHMMSSZ (ISO-8601 basic UTC, second precision). */
|
|
21
|
+
export function isoBasicUtc(now) {
|
|
22
|
+
const y = String(now.getUTCFullYear()).padStart(4, "0");
|
|
23
|
+
const m = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
24
|
+
const d = String(now.getUTCDate()).padStart(2, "0");
|
|
25
|
+
const hh = String(now.getUTCHours()).padStart(2, "0");
|
|
26
|
+
const mm = String(now.getUTCMinutes()).padStart(2, "0");
|
|
27
|
+
const ss = String(now.getUTCSeconds()).padStart(2, "0");
|
|
28
|
+
return `${y}-${m}-${d}T${hh}${mm}${ss}Z`;
|
|
29
|
+
}
|
|
30
|
+
/** Parse a YAML-decoded plan-file object into a typed PlanFileShape.
|
|
31
|
+
* Throws on shape violation. */
|
|
32
|
+
export function parsePlanFile(value) {
|
|
33
|
+
if (!isObj(value)) {
|
|
34
|
+
throw new Error("plan-file must be an object");
|
|
35
|
+
}
|
|
36
|
+
const planId = strField(value, "plan_id");
|
|
37
|
+
if (!PLAN_ID_GRAMMAR.test(planId)) {
|
|
38
|
+
throw new Error(`plan_id "${planId}" does not match grammar ${PLAN_ID_GRAMMAR.source}`);
|
|
39
|
+
}
|
|
40
|
+
const createdAt = strField(value, "created_at");
|
|
41
|
+
const pendingRaw = value.pending_attestations;
|
|
42
|
+
if (!Array.isArray(pendingRaw)) {
|
|
43
|
+
throw new Error("plan-file.pending_attestations must be an array");
|
|
44
|
+
}
|
|
45
|
+
const pending = pendingRaw.map((item, idx) => {
|
|
46
|
+
if (!isObj(item)) {
|
|
47
|
+
throw new Error(`pending_attestations[${idx}] must be an object`);
|
|
48
|
+
}
|
|
49
|
+
const ts = strField(item, "target_status", "approved");
|
|
50
|
+
if (!isTargetStatus(ts)) {
|
|
51
|
+
throw new Error(`pending_attestations[${idx}].target_status invalid: ${ts}`);
|
|
52
|
+
}
|
|
53
|
+
const out = {
|
|
54
|
+
id: strField(item, "id"),
|
|
55
|
+
ownerRole: strField(item, "owner_role"),
|
|
56
|
+
approverIdentity: strField(item, "approver_identity"),
|
|
57
|
+
timestamp: strField(item, "timestamp"),
|
|
58
|
+
changeRequest: strField(item, "change_request"),
|
|
59
|
+
scope: strField(item, "scope", "first-time-approval"),
|
|
60
|
+
targetStatus: ts,
|
|
61
|
+
};
|
|
62
|
+
const oracle = item.reviewed_test_oracle;
|
|
63
|
+
if (typeof oracle === "string" && oracle.length > 0) {
|
|
64
|
+
out.reviewedTestOracle = oracle;
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
});
|
|
68
|
+
return { planId, createdAt, pendingAttestations: pending };
|
|
69
|
+
}
|
|
70
|
+
/** Serialise a PlanFileShape into a stable YAML string. The fields are
|
|
71
|
+
* emitted in fixed order so plan files diff cleanly across edits. */
|
|
72
|
+
export function serialisePlanFile(plan) {
|
|
73
|
+
const lines = [];
|
|
74
|
+
lines.push(`plan_id: ${plan.planId}`);
|
|
75
|
+
lines.push(`created_at: ${plan.createdAt}`);
|
|
76
|
+
lines.push("pending_attestations:");
|
|
77
|
+
for (const a of plan.pendingAttestations) {
|
|
78
|
+
lines.push(` - id: ${a.id}`);
|
|
79
|
+
lines.push(` owner_role: ${a.ownerRole}`);
|
|
80
|
+
lines.push(` approver_identity: ${a.approverIdentity}`);
|
|
81
|
+
lines.push(` timestamp: ${a.timestamp}`);
|
|
82
|
+
lines.push(` change_request: ${quoteIfNeeded(a.changeRequest)}`);
|
|
83
|
+
lines.push(` scope: ${a.scope}`);
|
|
84
|
+
lines.push(` target_status: ${a.targetStatus}`);
|
|
85
|
+
if (a.reviewedTestOracle !== undefined) {
|
|
86
|
+
lines.push(` reviewed_test_oracle: ${a.reviewedTestOracle}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return lines.join("\n") + "\n";
|
|
90
|
+
}
|
|
91
|
+
function quoteIfNeeded(s) {
|
|
92
|
+
if (/^[a-zA-Z0-9_./:?=&%@+-]+$/.test(s)) {
|
|
93
|
+
return s;
|
|
94
|
+
}
|
|
95
|
+
return JSON.stringify(s);
|
|
96
|
+
}
|
|
97
|
+
function isTargetStatus(v) {
|
|
98
|
+
return v === "approved" || v === "deprecated" || v === "removed";
|
|
99
|
+
}
|
|
100
|
+
function isObj(v) {
|
|
101
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
102
|
+
}
|
|
103
|
+
function strField(o, key, fallback) {
|
|
104
|
+
const v = o[key];
|
|
105
|
+
if (typeof v === "string" && v.length > 0) {
|
|
106
|
+
return v;
|
|
107
|
+
}
|
|
108
|
+
if (v === undefined && fallback !== undefined) {
|
|
109
|
+
return fallback;
|
|
110
|
+
}
|
|
111
|
+
throw new Error(`plan-file field ${key} must be a non-empty string`);
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function globScopeEntries(scope: readonly string[]): string[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type ApprovalTargetStatus = "approved" | "deprecated" | "removed";
|
|
2
|
+
export interface ApprovalAttestation {
|
|
3
|
+
id: string;
|
|
4
|
+
approver: string;
|
|
5
|
+
ownerRole: string;
|
|
6
|
+
changeRequest: string;
|
|
7
|
+
scope: string;
|
|
8
|
+
targetStatus: ApprovalTargetStatus;
|
|
9
|
+
reviewedTestOracle: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface IdMatch {
|
|
12
|
+
id: string;
|
|
13
|
+
startLine: number;
|
|
14
|
+
endLine: number;
|
|
15
|
+
indent: string;
|
|
16
|
+
}
|
|
17
|
+
export interface RewriteResult {
|
|
18
|
+
newContent: string;
|
|
19
|
+
matched: IdMatch[];
|
|
20
|
+
}
|
|
21
|
+
export declare function rewriteApproval(content: string, req: ApprovalAttestation, when: Date): RewriteResult;
|
|
22
|
+
export declare function findMatches(lines: readonly string[], idOrGlob: string): IdMatch[];
|
|
23
|
+
export declare function matchId(pattern: string, candidate: string): boolean;
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* CST-003 / INV-004: pure rewriter shared by the approve and finalize slices;
|
|
3
|
+
* no I/O — the adapter handles read/write.
|
|
4
|
+
*/
|
|
5
|
+
export function rewriteApproval(content, req, when) {
|
|
6
|
+
const lines = content.split(/\r?\n/);
|
|
7
|
+
const matches = findMatches(lines, req.id);
|
|
8
|
+
if (matches.length === 0) {
|
|
9
|
+
return { newContent: content, matched: [] };
|
|
10
|
+
}
|
|
11
|
+
/* Apply matches from last to first so line indices stay stable. */
|
|
12
|
+
const sorted = [...matches].sort((a, b) => b.startLine - a.startLine);
|
|
13
|
+
let buf = [...lines];
|
|
14
|
+
for (const m of sorted) {
|
|
15
|
+
buf = applyOne(buf, m, req, when);
|
|
16
|
+
}
|
|
17
|
+
return { newContent: buf.join("\n"), matched: matches };
|
|
18
|
+
}
|
|
19
|
+
/*
|
|
20
|
+
* INV-007: the lifecycle anchor exists in two YAML shapes (flat
|
|
21
|
+
* `lifecycle.status:` and nested `lifecycle:` + `status:`); pick the first
|
|
22
|
+
* anchor found so it can be written back in the same shape.
|
|
23
|
+
*/
|
|
24
|
+
function scanAnchors(block, fallbackIndent) {
|
|
25
|
+
let statusKind = null;
|
|
26
|
+
let statusIdx = -1;
|
|
27
|
+
let statusIndent = fallbackIndent;
|
|
28
|
+
let approvalIndent = fallbackIndent;
|
|
29
|
+
let approvalIdx = -1;
|
|
30
|
+
for (let i = 0; i < block.length; i++) {
|
|
31
|
+
const line = block[i];
|
|
32
|
+
if (statusKind === null) {
|
|
33
|
+
const flat = /^(\s*)lifecycle\.status:/.exec(line);
|
|
34
|
+
if (flat !== null) {
|
|
35
|
+
statusKind = "flat";
|
|
36
|
+
statusIdx = i;
|
|
37
|
+
statusIndent = flat[1];
|
|
38
|
+
approvalIndent = flat[1];
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const nestedHeader = /^(\s*)lifecycle:\s*$/.exec(line);
|
|
42
|
+
if (nestedHeader !== null) {
|
|
43
|
+
const headerIndent = nestedHeader[1];
|
|
44
|
+
const childIndent = `${headerIndent} `;
|
|
45
|
+
for (let j = i + 1; j < block.length; j++) {
|
|
46
|
+
const child = block[j];
|
|
47
|
+
if (new RegExp(`^${childIndent}status:\\s*\\S`).test(child)) {
|
|
48
|
+
statusKind = "nested";
|
|
49
|
+
statusIdx = j;
|
|
50
|
+
statusIndent = childIndent;
|
|
51
|
+
approvalIndent = childIndent;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
if (child.length > 0 && !child.startsWith(headerIndent)) {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
if (child.length > 0 &&
|
|
58
|
+
child.startsWith(headerIndent) &&
|
|
59
|
+
!child.startsWith(childIndent)) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (approvalIdx < 0) {
|
|
66
|
+
const ar = /^(\s*)approval_record:/.exec(line);
|
|
67
|
+
if (ar !== null) {
|
|
68
|
+
approvalIdx = i;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { statusKind, statusIdx, statusIndent, approvalIndent, approvalIdx };
|
|
73
|
+
}
|
|
74
|
+
/*
|
|
75
|
+
* Strip the existing block (header + its deeper-indented children) before
|
|
76
|
+
* writing the new one, so repeated approvals don't interleave records.
|
|
77
|
+
*/
|
|
78
|
+
function approvalBlockEnd(block, approvalIdx) {
|
|
79
|
+
const headerMatch = /^(\s*)approval_record:/.exec(block[approvalIdx] ?? "");
|
|
80
|
+
const headerIndent = headerMatch !== null ? headerMatch[1].length : 0;
|
|
81
|
+
let endExclusive = approvalIdx + 1;
|
|
82
|
+
while (endExclusive < block.length) {
|
|
83
|
+
const line = block[endExclusive];
|
|
84
|
+
if (line.trim().length === 0) {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
const indentMatch = /^(\s*)/.exec(line);
|
|
88
|
+
const lineIndent = indentMatch !== null ? indentMatch[1].length : 0;
|
|
89
|
+
if (lineIndent <= headerIndent) {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
endExclusive++;
|
|
93
|
+
}
|
|
94
|
+
return endExclusive;
|
|
95
|
+
}
|
|
96
|
+
function applyOne(lines, m, req, when) {
|
|
97
|
+
const sliceStart = m.startLine - 1;
|
|
98
|
+
const sliceEndExclusive = m.endLine - 1;
|
|
99
|
+
const before = lines.slice(0, sliceStart);
|
|
100
|
+
const block = lines.slice(sliceStart, sliceEndExclusive);
|
|
101
|
+
const after = lines.slice(sliceEndExclusive);
|
|
102
|
+
const { statusKind, statusIdx, statusIndent, approvalIndent, approvalIdx } = scanAnchors(block, m.indent);
|
|
103
|
+
if (statusKind === null) {
|
|
104
|
+
return [...before, ...block, ...after];
|
|
105
|
+
}
|
|
106
|
+
if (statusKind === "flat") {
|
|
107
|
+
block[statusIdx] = `${statusIndent}lifecycle.status: ${req.targetStatus}`;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
block[statusIdx] = `${statusIndent}status: ${req.targetStatus}`;
|
|
111
|
+
}
|
|
112
|
+
const approvalLines = approvalBlock(req, when, approvalIndent);
|
|
113
|
+
if (approvalIdx >= 0) {
|
|
114
|
+
const endExclusive = approvalBlockEnd(block, approvalIdx);
|
|
115
|
+
block.splice(approvalIdx, endExclusive - approvalIdx, ...approvalLines);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
block.splice(statusIdx + 1, 0, ...approvalLines);
|
|
119
|
+
}
|
|
120
|
+
return [...before, ...block, ...after];
|
|
121
|
+
}
|
|
122
|
+
function approvalBlock(req, when, indent) {
|
|
123
|
+
const out = [
|
|
124
|
+
`${indent}approval_record:`,
|
|
125
|
+
`${indent} owner_role: ${yamlScalar(req.ownerRole)}`,
|
|
126
|
+
`${indent} approver_identity: ${yamlScalar(req.approver)}`,
|
|
127
|
+
`${indent} timestamp: ${when.toISOString()}`,
|
|
128
|
+
`${indent} change_request: ${yamlScalar(req.changeRequest)}`,
|
|
129
|
+
`${indent} scope: ${yamlScalar(req.scope)}`,
|
|
130
|
+
];
|
|
131
|
+
if (req.reviewedTestOracle !== null) {
|
|
132
|
+
out.push(`${indent} reviewed_test_oracle: ${yamlScalar(req.reviewedTestOracle)}`);
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
/*
|
|
137
|
+
* Emit a value as a YAML scalar, quoting only when the raw string would parse
|
|
138
|
+
* differently as plain YAML (e.g. `:` followed by whitespace reads as a nested
|
|
139
|
+
* mapping). Plain URLs lack `: ` and stay unquoted.
|
|
140
|
+
*/
|
|
141
|
+
function yamlScalar(value) {
|
|
142
|
+
if (value.length === 0) {
|
|
143
|
+
return '""';
|
|
144
|
+
}
|
|
145
|
+
const needsQuoting = /:\s/.test(value) || /^[\s?\-#&*!|>'"%@`]/.test(value) || /\s$/.test(value);
|
|
146
|
+
if (!needsQuoting) {
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
150
|
+
}
|
|
151
|
+
class RecordCursor {
|
|
152
|
+
idOrGlob;
|
|
153
|
+
out;
|
|
154
|
+
start = -1;
|
|
155
|
+
id = "";
|
|
156
|
+
indent = "";
|
|
157
|
+
anchorIndent = "";
|
|
158
|
+
constructor(idOrGlob, out) {
|
|
159
|
+
this.idOrGlob = idOrGlob;
|
|
160
|
+
this.out = out;
|
|
161
|
+
}
|
|
162
|
+
close(endLine) {
|
|
163
|
+
if (this.start < 0) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (matchId(this.idOrGlob, this.id)) {
|
|
167
|
+
this.out.push({
|
|
168
|
+
id: this.id,
|
|
169
|
+
startLine: this.start + 1,
|
|
170
|
+
endLine: endLine + 1,
|
|
171
|
+
indent: this.indent,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
this.start = -1;
|
|
175
|
+
this.id = "";
|
|
176
|
+
this.indent = "";
|
|
177
|
+
this.anchorIndent = "";
|
|
178
|
+
}
|
|
179
|
+
open(start, id, indent, anchorIndent) {
|
|
180
|
+
this.start = start;
|
|
181
|
+
this.id = id;
|
|
182
|
+
this.indent = indent;
|
|
183
|
+
this.anchorIndent = anchorIndent;
|
|
184
|
+
}
|
|
185
|
+
isOpen() {
|
|
186
|
+
return this.start >= 0;
|
|
187
|
+
}
|
|
188
|
+
anchorIndentLength() {
|
|
189
|
+
return this.anchorIndent.length;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function classifyLine(line, i, fenceStart, cursor) {
|
|
193
|
+
if (line === "---") {
|
|
194
|
+
cursor.close(i);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const listM = /^(\s*)-\s+id:\s*(\S+)/.exec(line);
|
|
198
|
+
if (listM !== null) {
|
|
199
|
+
/*
|
|
200
|
+
* INV-007: a `- id:` is a record boundary only when no record is open or
|
|
201
|
+
* it is a sibling-or-shallower list item; a deeper `- id:` is record body.
|
|
202
|
+
*/
|
|
203
|
+
const lead = listM[1];
|
|
204
|
+
if (!cursor.isOpen() || lead.length <= cursor.anchorIndentLength()) {
|
|
205
|
+
cursor.close(i);
|
|
206
|
+
cursor.open(i, listM[2], `${lead} `, lead);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const flatM = /^(\s*)id:\s*(\S+)/.exec(line);
|
|
211
|
+
if (flatM !== null && fenceStart >= 0 && i === fenceStart + 1) {
|
|
212
|
+
cursor.close(i);
|
|
213
|
+
cursor.open(i, flatM[2], flatM[1], flatM[1]);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (flatM !== null && !cursor.isOpen()) {
|
|
217
|
+
cursor.open(i, flatM[2], flatM[1], flatM[1]);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export function findMatches(lines, idOrGlob) {
|
|
221
|
+
const out = [];
|
|
222
|
+
const cursor = new RecordCursor(idOrGlob, out);
|
|
223
|
+
let inFence = false;
|
|
224
|
+
let fenceStart = -1;
|
|
225
|
+
for (let i = 0; i < lines.length; i++) {
|
|
226
|
+
const line = lines[i];
|
|
227
|
+
if (!inFence && /^```yaml\s*$/.test(line)) {
|
|
228
|
+
inFence = true;
|
|
229
|
+
fenceStart = i;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (inFence && /^```\s*$/.test(line)) {
|
|
233
|
+
cursor.close(i);
|
|
234
|
+
inFence = false;
|
|
235
|
+
fenceStart = -1;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (!inFence) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
classifyLine(line, i, fenceStart, cursor);
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
export function matchId(pattern, candidate) {
|
|
246
|
+
if (pattern === candidate) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
if (!pattern.includes("*")) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
const re = new RegExp(`^${pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`);
|
|
253
|
+
return re.test(candidate);
|
|
254
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface SpecBlock {
|
|
2
|
+
id: string;
|
|
3
|
+
type: string;
|
|
4
|
+
parsed: Record<string, unknown>;
|
|
5
|
+
raw: string;
|
|
6
|
+
line: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function specBlocks(markdown: string): SpecBlock[];
|
|
9
|
+
export declare function blocksById(blocks: readonly SpecBlock[], id: string): SpecBlock[];
|
|
10
|
+
export declare function blocksByNeutralPrefix(blocks: readonly SpecBlock[], prefix: string): SpecBlock[];
|
|
11
|
+
export declare function typedBlock(blocks: readonly SpecBlock[], id: string, type: string, specPath: string): SpecBlock;
|
|
12
|
+
export declare function stringValue(block: SpecBlock, field: string, specPath: string): string;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { parseDocument } from "yaml";
|
|
2
|
+
import { configFailure } from "./Errors.js";
|
|
3
|
+
export function specBlocks(markdown) {
|
|
4
|
+
return yamlDocuments(markdown).map((document) => specBlock(document));
|
|
5
|
+
}
|
|
6
|
+
export function blocksById(blocks, id) {
|
|
7
|
+
return blocks.filter((block) => block.id === id);
|
|
8
|
+
}
|
|
9
|
+
export function blocksByNeutralPrefix(blocks, prefix) {
|
|
10
|
+
return blocks.filter((block) => neutralId(block.id).startsWith(prefix));
|
|
11
|
+
}
|
|
12
|
+
export function typedBlock(blocks, id, type, specPath) {
|
|
13
|
+
const matches = blocksById(blocks, id).filter((block) => block.type === type);
|
|
14
|
+
if (matches.length === 0) {
|
|
15
|
+
throw configFailure("baseline-block-missing", `missing ${type} block: ${id}`, undefined, specPath);
|
|
16
|
+
}
|
|
17
|
+
if (matches.length > 1) {
|
|
18
|
+
throw configFailure("baseline-block-duplicate", `duplicate ${type} block: ${id}`, undefined, specPath);
|
|
19
|
+
}
|
|
20
|
+
return matches[0];
|
|
21
|
+
}
|
|
22
|
+
export function stringValue(block, field, specPath) {
|
|
23
|
+
const value = block.parsed[field];
|
|
24
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
25
|
+
throw configFailure("config-invalid", `${block.id}.${field} must be a non-empty string`, undefined, specPath);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function specBlock(document) {
|
|
30
|
+
const parsed = parseDocument(document.raw, { prettyErrors: false });
|
|
31
|
+
if (parsed.errors.length > 0) {
|
|
32
|
+
const message = parsed.errors.map((error) => error.message).join("; ");
|
|
33
|
+
throw configFailure("config-invalid", `invalid YAML block at line ${document.line}`, message);
|
|
34
|
+
}
|
|
35
|
+
const value = parsed.toJS();
|
|
36
|
+
if (!isRecord(value)) {
|
|
37
|
+
throw configFailure("config-invalid", `YAML block at line ${document.line} must be an object`);
|
|
38
|
+
}
|
|
39
|
+
const id = value.id;
|
|
40
|
+
const type = value.type;
|
|
41
|
+
if (typeof id !== "string" || typeof type !== "string") {
|
|
42
|
+
throw configFailure("config-invalid", `YAML block at line ${document.line} must include string id and type`);
|
|
43
|
+
}
|
|
44
|
+
return { id, type, parsed: value, raw: document.raw, line: document.line };
|
|
45
|
+
}
|
|
46
|
+
function yamlDocuments(markdown) {
|
|
47
|
+
const lines = markdown.split(/\r?\n/);
|
|
48
|
+
const documents = [];
|
|
49
|
+
let isInsideYamlFence = false;
|
|
50
|
+
let fenceLines = [];
|
|
51
|
+
let fenceStartLine = 0;
|
|
52
|
+
lines.forEach((line, index) => {
|
|
53
|
+
if (!isInsideYamlFence && line.trim() === "```yaml") {
|
|
54
|
+
isInsideYamlFence = true;
|
|
55
|
+
fenceLines = [];
|
|
56
|
+
fenceStartLine = index + 1;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (isInsideYamlFence && line.trim() === "```") {
|
|
60
|
+
documents.push(...documentsInFence(fenceLines, fenceStartLine + 1));
|
|
61
|
+
isInsideYamlFence = false;
|
|
62
|
+
fenceLines = [];
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (isInsideYamlFence) {
|
|
66
|
+
fenceLines.push(line);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return documents;
|
|
70
|
+
}
|
|
71
|
+
function documentsInFence(lines, firstLine) {
|
|
72
|
+
const documents = [];
|
|
73
|
+
let startIndex;
|
|
74
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
75
|
+
if (lines[index] !== "---") {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (startIndex === undefined) {
|
|
79
|
+
startIndex = index + 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const raw = lines.slice(startIndex, index).join("\n").trim();
|
|
83
|
+
if (raw.length > 0) {
|
|
84
|
+
documents.push({ raw, line: firstLine + startIndex });
|
|
85
|
+
}
|
|
86
|
+
startIndex = undefined;
|
|
87
|
+
}
|
|
88
|
+
return documents;
|
|
89
|
+
}
|
|
90
|
+
function neutralId(id) {
|
|
91
|
+
const parts = id.split(":");
|
|
92
|
+
return parts[parts.length - 1];
|
|
93
|
+
}
|
|
94
|
+
function isRecord(value) {
|
|
95
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
96
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type LintTemplate = "Behavior" | "Invariant" | "Contract" | "Scenario" | "NFR" | "Constraint" | "Policy" | "Migration" | "Delta" | "GeneratedArtifact" | "ExternalDependency" | "LocalizationContract" | "Surface" | "BrownfieldBaseline" | "Partition" | "ImplementationBinding";
|
|
2
|
+
export declare const NORMATIVE_TEMPLATES: ReadonlySet<LintTemplate>;
|
|
3
|
+
export type LifecycleStatus = "draft" | "proposed" | "approved" | "deprecated" | "removed";
|
|
4
|
+
export declare const VALID_LIFECYCLE_STATUS: ReadonlySet<LifecycleStatus>;
|
|
5
|
+
export interface LintRecord {
|
|
6
|
+
id: string;
|
|
7
|
+
template: string | null;
|
|
8
|
+
lifecycleStatus: string | null;
|
|
9
|
+
approvalRecord: string | null;
|
|
10
|
+
testObligations: string[];
|
|
11
|
+
hasAliasedObligations: boolean;
|
|
12
|
+
parsed: Record<string, unknown>;
|
|
13
|
+
file: string;
|
|
14
|
+
line: number;
|
|
15
|
+
rawBlock: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function lintRecordsFromMarkdown(file: string, markdown: string): LintRecord[];
|