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,114 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ENF-008: parser for the methodology `enforcement_registry.md`
|
|
3
|
+
* (compatibility metadata + registry rows). See the spec record for the
|
|
4
|
+
* table shape and diagnostic_id escaping rules. Pure parser, no node:*.
|
|
5
|
+
*/
|
|
6
|
+
/** Parse a methodology enforcement-registry markdown blob into a typed
|
|
7
|
+
* RegistryDocument. Returns a discriminated outcome — never throws. */
|
|
8
|
+
export function parseRegistry(markdown) {
|
|
9
|
+
const compat = readCompatibilityRange(markdown);
|
|
10
|
+
if (compat === null) {
|
|
11
|
+
return {
|
|
12
|
+
ok: false,
|
|
13
|
+
error: {
|
|
14
|
+
reason: "missing or invalid compatible_sdd_cli in ## Compatibility section",
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const rows = readRegistryRows(markdown);
|
|
19
|
+
return { ok: true, doc: { compatibleSddCli: compat, rows } };
|
|
20
|
+
}
|
|
21
|
+
function readCompatibilityRange(markdown) {
|
|
22
|
+
for (const line of markdown.split(/\r?\n/)) {
|
|
23
|
+
const row = parseTableRow(line);
|
|
24
|
+
if (row === null) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (row.length >= 2 && row[0] === "compatible_sdd_cli") {
|
|
28
|
+
const value = row[1].replace(/^"|"$/g, "").trim();
|
|
29
|
+
return value === "" ? null : value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function readRegistryRows(markdown) {
|
|
35
|
+
const out = [];
|
|
36
|
+
const lines = markdown.split(/\r?\n/);
|
|
37
|
+
let inTable = false;
|
|
38
|
+
let cols = [];
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
if (!inTable) {
|
|
41
|
+
const header = parseTableRow(line);
|
|
42
|
+
if (header !== null &&
|
|
43
|
+
header.includes("id") &&
|
|
44
|
+
header.includes("requirement") &&
|
|
45
|
+
header.includes("maturity") &&
|
|
46
|
+
header.includes("diagnostic_id")) {
|
|
47
|
+
cols = header;
|
|
48
|
+
inTable = true;
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
/* skip the |---|---| separator immediately after the header */
|
|
53
|
+
if (/^\s*\|?\s*-+/.test(line)) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const row = parseTableRow(line);
|
|
57
|
+
if (row === null) {
|
|
58
|
+
inTable = false;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (row.length !== cols.length) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const rec = {};
|
|
65
|
+
for (let i = 0; i < cols.length; i++) {
|
|
66
|
+
rec[cols[i]] = row[i].trim();
|
|
67
|
+
}
|
|
68
|
+
/*
|
|
69
|
+
* A trailing `:hybrid` marks rows mixing mechanical + human verdicts; the
|
|
70
|
+
* base maturity drives reconciliation.
|
|
71
|
+
*/
|
|
72
|
+
const maturity = rec.maturity.split(":")[0].trim();
|
|
73
|
+
if (!isRegistryMaturity(maturity)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const enfId = rec.id;
|
|
77
|
+
const ruleName = rec.requirement;
|
|
78
|
+
if (!enfId || !ruleName) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const diagnosticIds = parseDiagnosticIds(rec.diagnostic_id ?? "");
|
|
82
|
+
out.push({ enfId, ruleName, maturity, diagnosticIds });
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
function isRegistryMaturity(value) {
|
|
87
|
+
return (value === "planned" ||
|
|
88
|
+
value === "implemented" ||
|
|
89
|
+
value === "deprecated" ||
|
|
90
|
+
value === "out_of_scope");
|
|
91
|
+
}
|
|
92
|
+
function parseDiagnosticIds(cell) {
|
|
93
|
+
return cell
|
|
94
|
+
.split("|")
|
|
95
|
+
.map((s) => s.trim())
|
|
96
|
+
.filter((s) => s !== "" && s !== "—" && s !== "-");
|
|
97
|
+
}
|
|
98
|
+
function parseTableRow(line) {
|
|
99
|
+
const trimmed = line.trim();
|
|
100
|
+
if (!trimmed.startsWith("|")) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (!trimmed.endsWith("|")) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const inner = trimmed.slice(1, -1);
|
|
107
|
+
const cells = inner
|
|
108
|
+
.split(/(?<!\\)\|/)
|
|
109
|
+
.map((c) => c.replace(/\\\|/g, "|").trim());
|
|
110
|
+
if (cells.length < 2) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return cells;
|
|
114
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tiny semver-range tester. Supports only the form sdd-cli's enforcement
|
|
3
|
+
* registry actually uses: `">=A.B <C.D"` (whitespace-separated comparators).
|
|
4
|
+
* No prerelease, no caret/tilde, no logical OR.
|
|
5
|
+
*
|
|
6
|
+
* Pure logic. No node:* imports.
|
|
7
|
+
*/
|
|
8
|
+
export function parseVersion(s) {
|
|
9
|
+
const m = /^(\d+)\.(\d+)(?:\.(\d+))?$/.exec(s.trim());
|
|
10
|
+
if (m === null) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
major: Number.parseInt(m[1], 10),
|
|
15
|
+
minor: Number.parseInt(m[2], 10),
|
|
16
|
+
patch: m[3] !== undefined ? Number.parseInt(m[3], 10) : 0,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function rangeIncludes(range, version) {
|
|
20
|
+
const parsed = parseVersion(version);
|
|
21
|
+
if (parsed === null) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const comparators = parseRange(range);
|
|
25
|
+
if (comparators === null) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return comparators.every((c) => satisfies(parsed, c));
|
|
29
|
+
}
|
|
30
|
+
function parseRange(range) {
|
|
31
|
+
const parts = range.trim().split(/\s+/);
|
|
32
|
+
const out = [];
|
|
33
|
+
for (const part of parts) {
|
|
34
|
+
const m = /^(>=|<=|>|<|=)?(\d+)\.(\d+)(?:\.(\d+))?$/.exec(part);
|
|
35
|
+
if (m === null) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const rawOp = m[1] ?? "=";
|
|
39
|
+
if (!isComparatorOp(rawOp)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const op = rawOp;
|
|
43
|
+
const version = {
|
|
44
|
+
major: Number.parseInt(m[2], 10),
|
|
45
|
+
minor: Number.parseInt(m[3], 10),
|
|
46
|
+
patch: m[4] !== undefined ? Number.parseInt(m[4], 10) : 0,
|
|
47
|
+
};
|
|
48
|
+
out.push({ op, version });
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function isComparatorOp(value) {
|
|
53
|
+
return (value === ">=" ||
|
|
54
|
+
value === ">" ||
|
|
55
|
+
value === "<=" ||
|
|
56
|
+
value === "<" ||
|
|
57
|
+
value === "=");
|
|
58
|
+
}
|
|
59
|
+
function satisfies(v, c) {
|
|
60
|
+
const cmp = compare(v, c.version);
|
|
61
|
+
switch (c.op) {
|
|
62
|
+
case ">=":
|
|
63
|
+
return cmp >= 0;
|
|
64
|
+
case ">":
|
|
65
|
+
return cmp > 0;
|
|
66
|
+
case "<=":
|
|
67
|
+
return cmp <= 0;
|
|
68
|
+
case "<":
|
|
69
|
+
return cmp < 0;
|
|
70
|
+
case "=":
|
|
71
|
+
return cmp === 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function compare(a, b) {
|
|
75
|
+
if (a.major !== b.major) {
|
|
76
|
+
return a.major - b.major;
|
|
77
|
+
}
|
|
78
|
+
if (a.minor !== b.minor) {
|
|
79
|
+
return a.minor - b.minor;
|
|
80
|
+
}
|
|
81
|
+
return a.patch - b.patch;
|
|
82
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CommandResult, OutputFormat } from "../../../../shared/domain/CliOutput.js";
|
|
2
|
+
export interface DoctorRequest {
|
|
3
|
+
rulesPath: string;
|
|
4
|
+
ruleVersion: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface DoctorCommand {
|
|
7
|
+
execute(cwd: string, req: DoctorRequest, format: Exclude<OutputFormat, "yaml">): Promise<CommandResult>;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type RegistryFile = {
|
|
2
|
+
kind: "found";
|
|
3
|
+
path: string;
|
|
4
|
+
content: string;
|
|
5
|
+
} | {
|
|
6
|
+
kind: "not-found";
|
|
7
|
+
path: string;
|
|
8
|
+
};
|
|
9
|
+
export interface RegistryReader {
|
|
10
|
+
readRegistry(rulesPath: string): Promise<RegistryFile>;
|
|
11
|
+
cliVersion(): Promise<string>;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type CommandResult, type OutputFormat } from "../../../../shared/domain/CliOutput.js";
|
|
2
|
+
import { type RunFinalizePorts } from "../../application/RunFinalize.js";
|
|
3
|
+
import type { FinalizeCommand, FinalizeRequest } from "../../ports/inbound/FinalizeCommand.js";
|
|
4
|
+
export declare class CliFinalizeHandler implements FinalizeCommand {
|
|
5
|
+
private readonly ports;
|
|
6
|
+
constructor(ports: RunFinalizePorts);
|
|
7
|
+
execute(cwd: string, req: FinalizeRequest, format: Exclude<OutputFormat, "yaml">): Promise<CommandResult>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ok, } from "../../../../shared/domain/CliOutput.js";
|
|
2
|
+
import { runFinalize, } from "../../application/RunFinalize.js";
|
|
3
|
+
export class CliFinalizeHandler {
|
|
4
|
+
ports;
|
|
5
|
+
constructor(ports) {
|
|
6
|
+
this.ports = ports;
|
|
7
|
+
}
|
|
8
|
+
async execute(cwd, req, format) {
|
|
9
|
+
const outcome = await runFinalize(cwd, req.planId, this.ports);
|
|
10
|
+
return render(outcome, format);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function render(outcome, format) {
|
|
14
|
+
if (outcome.kind === "finalized") {
|
|
15
|
+
return renderFinalized(outcome, format);
|
|
16
|
+
}
|
|
17
|
+
return renderRefusal(outcome, format);
|
|
18
|
+
}
|
|
19
|
+
function renderFinalized(outcome, format) {
|
|
20
|
+
if (format === "json") {
|
|
21
|
+
return ok(JSON.stringify({
|
|
22
|
+
format_version: 1,
|
|
23
|
+
ok: true,
|
|
24
|
+
plan_id: outcome.planId,
|
|
25
|
+
finalized_ids: outcome.finalizedIds,
|
|
26
|
+
files_changed: outcome.filesChanged,
|
|
27
|
+
archived_path: outcome.archivedPath,
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
const lines = [];
|
|
31
|
+
lines.push(`finalize: ${outcome.finalizedIds.length} record(s) flipped in ${outcome.filesChanged.length} file(s).`);
|
|
32
|
+
for (const id of outcome.finalizedIds) {
|
|
33
|
+
lines.push(` - ${id}`);
|
|
34
|
+
}
|
|
35
|
+
lines.push("");
|
|
36
|
+
lines.push(`finalize: plan archived at ${outcome.archivedPath}`);
|
|
37
|
+
return ok(lines.join("\n"));
|
|
38
|
+
}
|
|
39
|
+
function renderRefusal(outcome, format) {
|
|
40
|
+
if (outcome.kind === "no-active-plan") {
|
|
41
|
+
return refusal({ format_version: 1, ok: false, kind: "no-active-plan" }, "no active plan", 2, format);
|
|
42
|
+
}
|
|
43
|
+
if (outcome.kind === "invalid-plan") {
|
|
44
|
+
return refusal({
|
|
45
|
+
format_version: 1,
|
|
46
|
+
ok: false,
|
|
47
|
+
kind: "invalid-plan-file",
|
|
48
|
+
plan_id: outcome.planId,
|
|
49
|
+
source: outcome.sourcePath,
|
|
50
|
+
reason: outcome.reason,
|
|
51
|
+
}, `invalid plan file at ${outcome.sourcePath}: ${outcome.reason}`, 2, format);
|
|
52
|
+
}
|
|
53
|
+
if (outcome.kind === "graph-violation") {
|
|
54
|
+
return refusal({
|
|
55
|
+
format_version: 1,
|
|
56
|
+
ok: false,
|
|
57
|
+
reason: "proposed-references",
|
|
58
|
+
plan_id: outcome.planId,
|
|
59
|
+
offending: outcome.violations.map((v) => ({
|
|
60
|
+
id: v.flippedId,
|
|
61
|
+
references_id: v.referencesId,
|
|
62
|
+
references_status: v.referencesStatus,
|
|
63
|
+
via: v.via,
|
|
64
|
+
})),
|
|
65
|
+
}, `${outcome.violations.length} proposed-reference violation(s) — re-run after the referenced IDs are approved or added to the plan`, 1, format);
|
|
66
|
+
}
|
|
67
|
+
return refusal({
|
|
68
|
+
format_version: 1,
|
|
69
|
+
ok: false,
|
|
70
|
+
kind: "no-id-match",
|
|
71
|
+
plan_id: outcome.planId,
|
|
72
|
+
missing_ids: outcome.missingIds,
|
|
73
|
+
}, `${outcome.missingIds.length} attestation id(s) did not match any spec record`, 1, format);
|
|
74
|
+
}
|
|
75
|
+
function refusal(envelope, human, exitCode, format) {
|
|
76
|
+
if (format === "json") {
|
|
77
|
+
return { exitCode, stdout: `${JSON.stringify(envelope)}\n`, stderr: "" };
|
|
78
|
+
}
|
|
79
|
+
return { exitCode, stdout: "", stderr: `finalize: ${human}\n` };
|
|
80
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type SddConfig } from "../../../../shared/domain/Config.js";
|
|
2
|
+
import type { FinalizeConfigPort } from "../../ports/outbound/FinalizeConfigPort.js";
|
|
3
|
+
import type { FinalizeFileSystem, SpecFileEntry } from "../../ports/outbound/FinalizeFileSystem.js";
|
|
4
|
+
export declare class NodeFinalizeFileSystem implements FinalizeConfigPort, FinalizeFileSystem {
|
|
5
|
+
config(repoRoot: string): Promise<SddConfig>;
|
|
6
|
+
resolveSpecFiles(repoRoot: string, patterns: readonly string[]): Promise<SpecFileEntry[]>;
|
|
7
|
+
writeBatch(repoRoot: string, entries: ReadonlyArray<{
|
|
8
|
+
path: string;
|
|
9
|
+
content: string;
|
|
10
|
+
}>): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { readFile, readdir, stat, writeFile, rename, unlink, } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { configFromJson, } from "../../../../shared/domain/Config.js";
|
|
4
|
+
import { configFailure, errorMessage, } from "../../../../shared/domain/Errors.js";
|
|
5
|
+
export class NodeFinalizeFileSystem {
|
|
6
|
+
async config(repoRoot) {
|
|
7
|
+
const configPath = join(repoRoot, ".sdd", "config.json");
|
|
8
|
+
let text;
|
|
9
|
+
try {
|
|
10
|
+
text = await readFile(configPath, "utf8");
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
throw configFailure("config-invalid", `cannot read .sdd/config.json: ${errorMessage(e)}`, undefined, configPath);
|
|
14
|
+
}
|
|
15
|
+
let parsed;
|
|
16
|
+
try {
|
|
17
|
+
parsed = JSON.parse(text);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
throw configFailure("config-invalid", `JSON parse error: ${errorMessage(e)}`, undefined, configPath);
|
|
21
|
+
}
|
|
22
|
+
return configFromJson(parsed, configPath);
|
|
23
|
+
}
|
|
24
|
+
async resolveSpecFiles(repoRoot, patterns) {
|
|
25
|
+
const matched = new Set();
|
|
26
|
+
for (const pattern of patterns) {
|
|
27
|
+
for (const abs of await expandGlob(repoRoot, pattern)) {
|
|
28
|
+
matched.add(abs);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const list = [...matched].sort();
|
|
32
|
+
const out = [];
|
|
33
|
+
for (const abs of list) {
|
|
34
|
+
const text = await readFile(abs, "utf8");
|
|
35
|
+
const rel = relative(repoRoot, abs).split("\\").join("/");
|
|
36
|
+
out.push({ path: rel, content: text });
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
async writeBatch(repoRoot, entries) {
|
|
41
|
+
if (entries.length === 0) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const tmps = [];
|
|
45
|
+
try {
|
|
46
|
+
for (const e of entries) {
|
|
47
|
+
const finalAbs = resolve(repoRoot, e.path);
|
|
48
|
+
const tmp = `${finalAbs}.finalize.tmp.${process.pid}.${Date.now()}.${tmps.length}`;
|
|
49
|
+
await writeFile(tmp, e.content, "utf8");
|
|
50
|
+
tmps.push({ tmp, final: finalAbs });
|
|
51
|
+
}
|
|
52
|
+
for (const { tmp, final } of tmps) {
|
|
53
|
+
await rename(tmp, final);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
/* Best-effort cleanup of any tmp files left behind. */
|
|
58
|
+
for (const { tmp } of tmps) {
|
|
59
|
+
try {
|
|
60
|
+
await unlink(tmp);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* tmp may already be gone; original error is rethrown */
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
throw e;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function expandGlob(repoRoot, pattern) {
|
|
71
|
+
const normalised = pattern.split("\\").join("/");
|
|
72
|
+
if (!hasGlob(normalised)) {
|
|
73
|
+
const abs = resolve(repoRoot, normalised);
|
|
74
|
+
return (await isFile(abs)) ? [abs] : [];
|
|
75
|
+
}
|
|
76
|
+
const segments = normalised.split("/");
|
|
77
|
+
const literal = [];
|
|
78
|
+
let firstGlobIndex = -1;
|
|
79
|
+
for (let i = 0; i < segments.length; i++) {
|
|
80
|
+
if (hasGlob(segments[i])) {
|
|
81
|
+
firstGlobIndex = i;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
literal.push(segments[i]);
|
|
85
|
+
}
|
|
86
|
+
const baseDir = resolve(repoRoot, ...literal);
|
|
87
|
+
const remainder = segments.slice(firstGlobIndex);
|
|
88
|
+
return walk(baseDir, remainder);
|
|
89
|
+
}
|
|
90
|
+
function hasGlob(s) {
|
|
91
|
+
return s.includes("*") || s.includes("?");
|
|
92
|
+
}
|
|
93
|
+
async function walk(baseDir, segments) {
|
|
94
|
+
if (segments.length === 0) {
|
|
95
|
+
return (await isFile(baseDir)) ? [baseDir] : [];
|
|
96
|
+
}
|
|
97
|
+
const [head, ...rest] = segments;
|
|
98
|
+
if (head === "**") {
|
|
99
|
+
const out = [];
|
|
100
|
+
out.push(...(await walk(baseDir, rest)));
|
|
101
|
+
let entries;
|
|
102
|
+
try {
|
|
103
|
+
entries = await readdir(baseDir, { withFileTypes: true });
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
for (const e of entries) {
|
|
109
|
+
if (!e.isDirectory()) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
out.push(...(await walk(join(baseDir, e.name), segments)));
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
const re = segmentToRegExp(head);
|
|
117
|
+
let entries;
|
|
118
|
+
try {
|
|
119
|
+
entries = await readdir(baseDir, { withFileTypes: true });
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
const out = [];
|
|
125
|
+
for (const e of entries) {
|
|
126
|
+
if (!re.test(e.name)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const next = join(baseDir, e.name);
|
|
130
|
+
if (rest.length === 0) {
|
|
131
|
+
if (await isFile(next)) {
|
|
132
|
+
out.push(next);
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (e.isDirectory()) {
|
|
137
|
+
out.push(...(await walk(next, rest)));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
function segmentToRegExp(seg) {
|
|
143
|
+
let body = "^";
|
|
144
|
+
for (const ch of seg) {
|
|
145
|
+
if (ch === "*") {
|
|
146
|
+
body += "[^/]*";
|
|
147
|
+
}
|
|
148
|
+
else if (ch === "?") {
|
|
149
|
+
body += "[^/]";
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
body += ch.replace(/[-./\\^$+?()|[\]{}]/g, "\\$&");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
body += "$";
|
|
156
|
+
return new RegExp(body);
|
|
157
|
+
}
|
|
158
|
+
async function isFile(abs) {
|
|
159
|
+
try {
|
|
160
|
+
const st = await stat(abs);
|
|
161
|
+
return st.isFile();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
void dirname; /* reserved */
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PlanLoad, PlanRepo } from "../../ports/outbound/PlanRepo.js";
|
|
2
|
+
export declare class NodePlanRepo implements PlanRepo {
|
|
3
|
+
load(repoRoot: string, plansDir: string, planId: string | undefined): Promise<PlanLoad>;
|
|
4
|
+
archive(repoRoot: string, plansDir: string, planId: string): Promise<{
|
|
5
|
+
archivedPath: string;
|
|
6
|
+
}>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { parse as yamlParse } from "yaml";
|
|
4
|
+
import { errorMessage } from "../../../../shared/domain/Errors.js";
|
|
5
|
+
import { parsePlanFile } from "../../../../shared/domain/PlanFile.js";
|
|
6
|
+
export class NodePlanRepo {
|
|
7
|
+
async load(repoRoot, plansDir, planId) {
|
|
8
|
+
const dirAbs = resolve(repoRoot, plansDir);
|
|
9
|
+
const id = planId ?? (await readActive(dirAbs));
|
|
10
|
+
if (id === null) {
|
|
11
|
+
return { kind: "no-active-plan" };
|
|
12
|
+
}
|
|
13
|
+
const sourceRel = join(plansDir, `${id}.yaml`).split("\\").join("/");
|
|
14
|
+
const sourceAbs = resolve(repoRoot, sourceRel);
|
|
15
|
+
let raw;
|
|
16
|
+
try {
|
|
17
|
+
raw = await fs.readFile(sourceAbs, "utf8");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {
|
|
21
|
+
kind: "invalid-plan-file",
|
|
22
|
+
planId: id,
|
|
23
|
+
sourcePath: sourceRel,
|
|
24
|
+
reason: "plan file not found",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = yamlParse(raw);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
return {
|
|
33
|
+
kind: "invalid-plan-file",
|
|
34
|
+
planId: id,
|
|
35
|
+
sourcePath: sourceRel,
|
|
36
|
+
reason: `YAML parse error: ${errorMessage(e)}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const plan = parsePlanFile(parsed);
|
|
41
|
+
return { kind: "found", plan, sourcePath: sourceRel };
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
return {
|
|
45
|
+
kind: "invalid-plan-file",
|
|
46
|
+
planId: id,
|
|
47
|
+
sourcePath: sourceRel,
|
|
48
|
+
reason: errorMessage(e),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async archive(repoRoot, plansDir, planId) {
|
|
53
|
+
const dirAbs = resolve(repoRoot, plansDir);
|
|
54
|
+
const finalizedDirAbs = join(dirAbs, "finalized");
|
|
55
|
+
await fs.mkdir(finalizedDirAbs, { recursive: true });
|
|
56
|
+
const fromAbs = join(dirAbs, `${planId}.yaml`);
|
|
57
|
+
const toAbs = join(finalizedDirAbs, `${planId}.yaml`);
|
|
58
|
+
await fs.rename(fromAbs, toAbs);
|
|
59
|
+
const active = await readActive(dirAbs);
|
|
60
|
+
if (active === planId) {
|
|
61
|
+
try {
|
|
62
|
+
await fs.unlink(join(dirAbs, ".active"));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
/* missing is fine */
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const rel = relative(repoRoot, toAbs).split("\\").join("/");
|
|
69
|
+
return { archivedPath: rel };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function readActive(dirAbs) {
|
|
73
|
+
try {
|
|
74
|
+
const raw = await fs.readFile(join(dirAbs, ".active"), "utf8");
|
|
75
|
+
const id = raw.trim();
|
|
76
|
+
return id.length > 0 ? id : null;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
void dirname; /* reserved */
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type GraphViolation } from "../domain/ValidateFinalizeGraph.js";
|
|
2
|
+
import type { FinalizeClock } from "../ports/outbound/FinalizeClock.js";
|
|
3
|
+
import type { FinalizeConfigPort } from "../ports/outbound/FinalizeConfigPort.js";
|
|
4
|
+
import type { FinalizeFileSystem } from "../ports/outbound/FinalizeFileSystem.js";
|
|
5
|
+
import type { PlanRepo } from "../ports/outbound/PlanRepo.js";
|
|
6
|
+
export interface RunFinalizePorts {
|
|
7
|
+
clock: FinalizeClock;
|
|
8
|
+
config: FinalizeConfigPort;
|
|
9
|
+
files: FinalizeFileSystem;
|
|
10
|
+
plans: PlanRepo;
|
|
11
|
+
}
|
|
12
|
+
export type RunFinalizeOutcome = {
|
|
13
|
+
kind: "no-active-plan";
|
|
14
|
+
} | {
|
|
15
|
+
kind: "invalid-plan";
|
|
16
|
+
planId: string;
|
|
17
|
+
sourcePath: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
} | {
|
|
20
|
+
kind: "graph-violation";
|
|
21
|
+
planId: string;
|
|
22
|
+
violations: GraphViolation[];
|
|
23
|
+
} | {
|
|
24
|
+
kind: "no-id-match";
|
|
25
|
+
planId: string;
|
|
26
|
+
missingIds: string[];
|
|
27
|
+
} | {
|
|
28
|
+
kind: "finalized";
|
|
29
|
+
planId: string;
|
|
30
|
+
finalizedIds: string[];
|
|
31
|
+
filesChanged: string[];
|
|
32
|
+
archivedPath: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function runFinalize(cwd: string, planId: string | undefined, ports: RunFinalizePorts): Promise<RunFinalizeOutcome>;
|