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,62 @@
|
|
|
1
|
+
import { failed, ok, } from "../../../../shared/domain/CliOutput.js";
|
|
2
|
+
import { CliFailure } from "../../../../shared/domain/Errors.js";
|
|
3
|
+
import { checkBaseline, } from "../../application/CheckBaseline.js";
|
|
4
|
+
export class CliCheckHandler {
|
|
5
|
+
ports;
|
|
6
|
+
constructor(ports) {
|
|
7
|
+
this.ports = ports;
|
|
8
|
+
}
|
|
9
|
+
async execute(cwd, format) {
|
|
10
|
+
try {
|
|
11
|
+
const outcome = await checkBaseline(cwd, this.ports);
|
|
12
|
+
if (outcome.kind === "dirty") {
|
|
13
|
+
return driftResult(format, "baseline-dirty", "", null, "", outcome.currentCommitSha, outcome.dirtyPaths);
|
|
14
|
+
}
|
|
15
|
+
if (outcome.kind === "stale") {
|
|
16
|
+
return driftResult(format, "baseline-stale", outcome.recordedToken, outcome.recomputedToken, outcome.baselineCommitSha, outcome.currentCommitSha, []);
|
|
17
|
+
}
|
|
18
|
+
if (format === "json") {
|
|
19
|
+
return ok(JSON.stringify({
|
|
20
|
+
format_version: 1,
|
|
21
|
+
ok: true,
|
|
22
|
+
recorded_token: outcome.recordedToken,
|
|
23
|
+
recomputed_token: outcome.recomputedToken,
|
|
24
|
+
baseline_commit_sha: outcome.baselineCommitSha,
|
|
25
|
+
current_commit_sha: outcome.currentCommitSha,
|
|
26
|
+
mechanism: outcome.mechanism,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
return ok(`ok true\n recorded_token: ${outcome.recordedToken}\n recomputed_token: ${outcome.recomputedToken}\n baseline_commit_sha: ${outcome.baselineCommitSha}\n current_commit_sha: ${outcome.currentCommitSha}`);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof CliFailure) {
|
|
33
|
+
return failed(error, format);
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function driftResult(format, reason, recordedToken, recomputedToken, baselineCommitSha, currentCommitSha, dirtyPaths) {
|
|
40
|
+
if (format === "json") {
|
|
41
|
+
return {
|
|
42
|
+
exitCode: 1,
|
|
43
|
+
stdout: `${JSON.stringify({
|
|
44
|
+
format_version: 1,
|
|
45
|
+
ok: false,
|
|
46
|
+
reason,
|
|
47
|
+
recorded_token: recordedToken,
|
|
48
|
+
recomputed_token: recomputedToken,
|
|
49
|
+
baseline_commit_sha: baselineCommitSha,
|
|
50
|
+
current_commit_sha: currentCommitSha,
|
|
51
|
+
dirty_paths: dirtyPaths,
|
|
52
|
+
})}\n`,
|
|
53
|
+
stderr: "",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const dirtyText = dirtyPaths.length > 0 ? `\n dirty_paths: ${dirtyPaths.join(", ")}` : "";
|
|
57
|
+
return {
|
|
58
|
+
exitCode: 1,
|
|
59
|
+
stdout: "",
|
|
60
|
+
stderr: `${reason}: token baseline is not current${dirtyText}\n`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CheckGitPort } from "../../ports/outbound/CheckGitPort.js";
|
|
2
|
+
export declare class ChildProcessCheckGit implements CheckGitPort {
|
|
3
|
+
repoRoot(cwd: string): Promise<string>;
|
|
4
|
+
headSha(repoRoot: string): Promise<string>;
|
|
5
|
+
treeBytes(repoRoot: string, scope: readonly string[]): Promise<Uint8Array>;
|
|
6
|
+
treePaths(repoRoot: string, scope: readonly string[]): Promise<string[]>;
|
|
7
|
+
dirtyPaths(repoRoot: string, scope: readonly string[]): Promise<string[]>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { environmentFailure } from "../../../../shared/domain/Errors.js";
|
|
5
|
+
export class ChildProcessCheckGit {
|
|
6
|
+
async repoRoot(cwd) {
|
|
7
|
+
const result = await runGit(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
8
|
+
if (result.code !== 0 || result.stdout.toString("utf8").trim() !== "true") {
|
|
9
|
+
throw environmentFailure("not-a-git-repo", "cwd is not inside a git working tree", cwd);
|
|
10
|
+
}
|
|
11
|
+
return findRepoRoot(cwd);
|
|
12
|
+
}
|
|
13
|
+
async headSha(repoRoot) {
|
|
14
|
+
const result = await runGit(repoRoot, ["rev-parse", "HEAD"]);
|
|
15
|
+
if (result.code === 0) {
|
|
16
|
+
return result.stdout.toString("utf8").trim();
|
|
17
|
+
}
|
|
18
|
+
throw environmentFailure("head-unborn", "HEAD does not resolve to a commit", result.stderr.toString("utf8").trim());
|
|
19
|
+
}
|
|
20
|
+
async treeBytes(repoRoot, scope) {
|
|
21
|
+
const result = await runGit(repoRoot, ["ls-tree", "HEAD", "--", ...scope]);
|
|
22
|
+
if (result.code !== 0) {
|
|
23
|
+
throw environmentFailure("head-unborn", "git ls-tree failed", result.stderr.toString("utf8").trim());
|
|
24
|
+
}
|
|
25
|
+
return result.stdout;
|
|
26
|
+
}
|
|
27
|
+
async treePaths(repoRoot, scope) {
|
|
28
|
+
const result = await runGit(repoRoot, [
|
|
29
|
+
"ls-tree",
|
|
30
|
+
"-r",
|
|
31
|
+
"--name-only",
|
|
32
|
+
"HEAD",
|
|
33
|
+
"--",
|
|
34
|
+
...scope,
|
|
35
|
+
]);
|
|
36
|
+
if (result.code !== 0) {
|
|
37
|
+
throw environmentFailure("head-unborn", "git ls-tree failed", result.stderr.toString("utf8").trim());
|
|
38
|
+
}
|
|
39
|
+
return nonEmptyLines(result.stdout.toString("utf8"));
|
|
40
|
+
}
|
|
41
|
+
async dirtyPaths(repoRoot, scope) {
|
|
42
|
+
const diffResult = await runGit(repoRoot, [
|
|
43
|
+
"diff",
|
|
44
|
+
"--quiet",
|
|
45
|
+
"HEAD",
|
|
46
|
+
"--",
|
|
47
|
+
...scope,
|
|
48
|
+
]);
|
|
49
|
+
if (diffResult.code !== 0 && diffResult.code !== 1) {
|
|
50
|
+
throw environmentFailure("head-unborn", "git diff --quiet failed", diffResult.stderr.toString("utf8").trim());
|
|
51
|
+
}
|
|
52
|
+
const result = await runGit(repoRoot, [
|
|
53
|
+
"status",
|
|
54
|
+
"--porcelain",
|
|
55
|
+
"--",
|
|
56
|
+
...scope,
|
|
57
|
+
]);
|
|
58
|
+
if (result.code !== 0) {
|
|
59
|
+
throw environmentFailure("not-a-git-repo", "git status failed", result.stderr.toString("utf8").trim());
|
|
60
|
+
}
|
|
61
|
+
return nonEmptyLines(result.stdout.toString("utf8"))
|
|
62
|
+
.map((line) => porcelainPath(line))
|
|
63
|
+
.sort();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function runGit(cwd, args) {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const child = spawn("git", [...args], {
|
|
69
|
+
cwd,
|
|
70
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
71
|
+
});
|
|
72
|
+
const stdout = [];
|
|
73
|
+
const stderr = [];
|
|
74
|
+
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
75
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
76
|
+
child.on("error", (error) => {
|
|
77
|
+
if (error.code === "ENOENT") {
|
|
78
|
+
reject(environmentFailure("git-not-on-path", "git binary is not on PATH"));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
reject(error);
|
|
82
|
+
});
|
|
83
|
+
child.on("close", (code) => {
|
|
84
|
+
resolve({
|
|
85
|
+
code: code ?? 1,
|
|
86
|
+
stdout: Buffer.concat(stdout),
|
|
87
|
+
stderr: Buffer.concat(stderr),
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function nonEmptyLines(value) {
|
|
93
|
+
return value.split(/\r?\n/).filter((line) => line.length > 0);
|
|
94
|
+
}
|
|
95
|
+
function porcelainPath(line) {
|
|
96
|
+
const raw = line.slice(3);
|
|
97
|
+
const parts = raw.split(" -> ");
|
|
98
|
+
return parts[parts.length - 1];
|
|
99
|
+
}
|
|
100
|
+
function findRepoRoot(cwd) {
|
|
101
|
+
let current = resolve(cwd);
|
|
102
|
+
while (true) {
|
|
103
|
+
if (existsSync(join(current, ".git"))) {
|
|
104
|
+
return current;
|
|
105
|
+
}
|
|
106
|
+
const parent = dirname(current);
|
|
107
|
+
if (parent === current) {
|
|
108
|
+
throw environmentFailure("not-a-git-repo", "cwd is not inside a git working tree", cwd);
|
|
109
|
+
}
|
|
110
|
+
current = parent;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type SddConfig } from "../../../../shared/domain/Config.js";
|
|
2
|
+
import type { CheckConfigPort } from "../../ports/outbound/CheckConfigPort.js";
|
|
3
|
+
import type { CheckSpec, CheckSpecPort } from "../../ports/outbound/CheckSpecPort.js";
|
|
4
|
+
export declare class NodeCheckFileReader implements CheckConfigPort, CheckSpecPort {
|
|
5
|
+
config(repoRoot: string): Promise<SddConfig>;
|
|
6
|
+
spec(repoRoot: string, config: SddConfig): Promise<CheckSpec>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { configFromJson, } from "../../../../shared/domain/Config.js";
|
|
4
|
+
import { configFailure } from "../../../../shared/domain/Errors.js";
|
|
5
|
+
import { specBlocks } from "../../../../shared/domain/SpecBlocks.js";
|
|
6
|
+
export class NodeCheckFileReader {
|
|
7
|
+
async config(repoRoot) {
|
|
8
|
+
const configPath = join(repoRoot, ".sdd", "config.json");
|
|
9
|
+
const text = await readConfig(configPath);
|
|
10
|
+
const config = configFromJson(parseConfigJson(text, configPath), configPath);
|
|
11
|
+
await this.spec(repoRoot, config);
|
|
12
|
+
return config;
|
|
13
|
+
}
|
|
14
|
+
async spec(repoRoot, config) {
|
|
15
|
+
const path = join(repoRoot, config.specFile);
|
|
16
|
+
let text;
|
|
17
|
+
try {
|
|
18
|
+
text = await readFile(path, "utf8");
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw configFailure("config-invalid", `spec_file is not readable: ${config.specFile}`, errorMessage(error), path);
|
|
22
|
+
}
|
|
23
|
+
return { path, blocks: specBlocks(text) };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function readConfig(path) {
|
|
27
|
+
try {
|
|
28
|
+
return await readFile(path, "utf8");
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
throw configFailure("config-missing", ".sdd/config.json is missing or unreadable", errorMessage(error), path);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function parseConfigJson(text, path) {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(text);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
throw configFailure("config-invalid", ".sdd/config.json is not valid JSON", errorMessage(error), path);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function errorMessage(error) {
|
|
43
|
+
return error instanceof Error ? error.message : String(error);
|
|
44
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { TOKEN_MECHANISM } from "../../../shared/domain/Token.js";
|
|
2
|
+
import type { CheckConfigPort } from "../ports/outbound/CheckConfigPort.js";
|
|
3
|
+
import type { CheckGitPort } from "../ports/outbound/CheckGitPort.js";
|
|
4
|
+
import type { CheckSpecPort } from "../ports/outbound/CheckSpecPort.js";
|
|
5
|
+
export type CheckOutcome = {
|
|
6
|
+
kind: "match";
|
|
7
|
+
recordedToken: string;
|
|
8
|
+
recomputedToken: string;
|
|
9
|
+
baselineCommitSha: string;
|
|
10
|
+
currentCommitSha: string;
|
|
11
|
+
mechanism: typeof TOKEN_MECHANISM;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "dirty";
|
|
14
|
+
currentCommitSha: string;
|
|
15
|
+
dirtyPaths: string[];
|
|
16
|
+
} | {
|
|
17
|
+
kind: "stale";
|
|
18
|
+
recordedToken: string;
|
|
19
|
+
recomputedToken: string;
|
|
20
|
+
baselineCommitSha: string;
|
|
21
|
+
currentCommitSha: string;
|
|
22
|
+
};
|
|
23
|
+
export interface CheckBaselinePorts {
|
|
24
|
+
config: CheckConfigPort;
|
|
25
|
+
git: CheckGitPort;
|
|
26
|
+
spec: CheckSpecPort;
|
|
27
|
+
}
|
|
28
|
+
export declare function checkBaseline(cwd: string, ports: CheckBaselinePorts): Promise<CheckOutcome>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { configFailure } from "../../../shared/domain/Errors.js";
|
|
2
|
+
import { stringValue, typedBlock, } from "../../../shared/domain/SpecBlocks.js";
|
|
3
|
+
import { TOKEN_MECHANISM, token } from "../../../shared/domain/Token.js";
|
|
4
|
+
import { baselineComparison } from "../domain/BaselineComparison.js";
|
|
5
|
+
export async function checkBaseline(cwd, ports) {
|
|
6
|
+
const repoRoot = await ports.git.repoRoot(cwd);
|
|
7
|
+
const currentCommitSha = await ports.git.headSha(repoRoot);
|
|
8
|
+
const config = await ports.config.config(repoRoot);
|
|
9
|
+
await assertGlobMatches(ports.git, repoRoot, config.discoveryScope);
|
|
10
|
+
const dirtyPaths = await ports.git.dirtyPaths(repoRoot, config.discoveryScope);
|
|
11
|
+
if (dirtyPaths.length > 0) {
|
|
12
|
+
return {
|
|
13
|
+
kind: "dirty",
|
|
14
|
+
currentCommitSha,
|
|
15
|
+
dirtyPaths,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const loadedSpec = await ports.spec.spec(repoRoot, config);
|
|
19
|
+
const recorded = baseline(loadedSpec.path, loadedSpec.blocks, config.baselineId);
|
|
20
|
+
const recomputedToken = token(await ports.git.treeBytes(repoRoot, config.discoveryScope));
|
|
21
|
+
const comparison = baselineComparison(recorded.freshnessToken, recomputedToken);
|
|
22
|
+
if (comparison.kind === "stale") {
|
|
23
|
+
return {
|
|
24
|
+
kind: "stale",
|
|
25
|
+
recordedToken: comparison.recordedToken,
|
|
26
|
+
recomputedToken: comparison.recomputedToken,
|
|
27
|
+
baselineCommitSha: recorded.baselineCommitSha,
|
|
28
|
+
currentCommitSha,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
kind: "match",
|
|
33
|
+
recordedToken: comparison.recordedToken,
|
|
34
|
+
recomputedToken: comparison.recomputedToken,
|
|
35
|
+
baselineCommitSha: recorded.baselineCommitSha,
|
|
36
|
+
currentCommitSha,
|
|
37
|
+
mechanism: TOKEN_MECHANISM,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function baseline(specPath, blocks, baselineId) {
|
|
41
|
+
const block = typedBlock(blocks, baselineId, "BrownfieldBaseline", specPath);
|
|
42
|
+
return {
|
|
43
|
+
freshnessToken: stringValue(block, "freshness_token", specPath),
|
|
44
|
+
baselineCommitSha: stringValue(block, "baseline_commit_sha", specPath),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function assertGlobMatches(git, repoRoot, scope) {
|
|
48
|
+
for (const entry of scope.filter((value) => /[*?[]/.test(value))) {
|
|
49
|
+
const paths = await git.treePaths(repoRoot, [entry]);
|
|
50
|
+
if (paths.length === 0) {
|
|
51
|
+
throw configFailure("config-invalid", `discovery_scope glob matched zero files: ${entry}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { baselineComparison, type BaselineComparison, } from "../../../shared/domain/CheckOutcome.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface CheckGitPort {
|
|
2
|
+
repoRoot(cwd: string): Promise<string>;
|
|
3
|
+
headSha(repoRoot: string): Promise<string>;
|
|
4
|
+
treeBytes(repoRoot: string, scope: readonly string[]): Promise<Uint8Array>;
|
|
5
|
+
treePaths(repoRoot: string, scope: readonly string[]): Promise<string[]>;
|
|
6
|
+
dirtyPaths(repoRoot: string, scope: readonly string[]): Promise<string[]>;
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SddConfig } from "../../../../shared/domain/Config.js";
|
|
2
|
+
import type { SpecBlock } from "../../../../shared/domain/SpecBlocks.js";
|
|
3
|
+
export interface CheckSpec {
|
|
4
|
+
path: string;
|
|
5
|
+
blocks: SpecBlock[];
|
|
6
|
+
}
|
|
7
|
+
export interface CheckSpecPort {
|
|
8
|
+
spec(repoRoot: string, config: SddConfig): Promise<CheckSpec>;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type CommandResult, type OutputFormat } from "../../../../shared/domain/CliOutput.js";
|
|
2
|
+
import { type RunDoctorPorts } from "../../application/RunDoctor.js";
|
|
3
|
+
import type { DoctorCommand, DoctorRequest } from "../../ports/inbound/DoctorCommand.js";
|
|
4
|
+
export declare class CliDoctorHandler implements DoctorCommand {
|
|
5
|
+
private readonly ports;
|
|
6
|
+
constructor(ports: RunDoctorPorts);
|
|
7
|
+
execute(_cwd: string, req: DoctorRequest, format: Exclude<OutputFormat, "yaml">): Promise<CommandResult>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ok, } from "../../../../shared/domain/CliOutput.js";
|
|
2
|
+
import { runDoctor, } from "../../application/RunDoctor.js";
|
|
3
|
+
export class CliDoctorHandler {
|
|
4
|
+
ports;
|
|
5
|
+
constructor(ports) {
|
|
6
|
+
this.ports = ports;
|
|
7
|
+
}
|
|
8
|
+
async execute(_cwd, req, format) {
|
|
9
|
+
if (!req.ruleVersion) {
|
|
10
|
+
const env = { format_version: 1, ok: false, kind: "no-mode-selected" };
|
|
11
|
+
return refusal(env, "doctor requires --rule-version (no other modes implemented in v0.4)", 2, format);
|
|
12
|
+
}
|
|
13
|
+
const outcome = await runDoctor(req.rulesPath, this.ports);
|
|
14
|
+
return render(outcome, format);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function render(outcome, format) {
|
|
18
|
+
if (outcome.kind === "ok") {
|
|
19
|
+
if (format === "json") {
|
|
20
|
+
return ok(JSON.stringify({
|
|
21
|
+
format_version: 1,
|
|
22
|
+
ok: true,
|
|
23
|
+
rule_version: outcome.ruleVersion,
|
|
24
|
+
cli_version: outcome.cliVersion,
|
|
25
|
+
compatible_range: outcome.compatibleRange,
|
|
26
|
+
drift: [],
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
return ok([
|
|
30
|
+
`doctor: ok (rule_version=${outcome.ruleVersion}, cli_version=${outcome.cliVersion}, compatible_range=${outcome.compatibleRange})`,
|
|
31
|
+
`doctor: 0 drift entries.`,
|
|
32
|
+
].join("\n"));
|
|
33
|
+
}
|
|
34
|
+
if (outcome.kind === "drift") {
|
|
35
|
+
if (format === "json") {
|
|
36
|
+
return {
|
|
37
|
+
exitCode: 1,
|
|
38
|
+
stdout: `${JSON.stringify({
|
|
39
|
+
format_version: 1,
|
|
40
|
+
ok: false,
|
|
41
|
+
rule_version: outcome.ruleVersion,
|
|
42
|
+
cli_version: outcome.cliVersion,
|
|
43
|
+
compatible_range: outcome.compatibleRange,
|
|
44
|
+
drift: outcome.drift,
|
|
45
|
+
})}\n`,
|
|
46
|
+
stderr: "",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const lines = [];
|
|
50
|
+
lines.push(`doctor: drift detected (rule_version=${outcome.ruleVersion}, cli_version=${outcome.cliVersion}, compatible_range=${outcome.compatibleRange})`);
|
|
51
|
+
for (const d of outcome.drift) {
|
|
52
|
+
lines.push(` - [${d.kind}] ${d.id}: ${d.remediation}`);
|
|
53
|
+
}
|
|
54
|
+
return { exitCode: 1, stdout: "", stderr: `${lines.join("\n")}\n` };
|
|
55
|
+
}
|
|
56
|
+
if (outcome.kind === "registry-not-found") {
|
|
57
|
+
return refusal({
|
|
58
|
+
format_version: 1,
|
|
59
|
+
ok: false,
|
|
60
|
+
kind: "registry-not-found",
|
|
61
|
+
path: outcome.path,
|
|
62
|
+
}, `enforcement registry not found at ${outcome.path}`, 2, format);
|
|
63
|
+
}
|
|
64
|
+
return refusal({
|
|
65
|
+
format_version: 1,
|
|
66
|
+
ok: false,
|
|
67
|
+
kind: "invalid-registry",
|
|
68
|
+
path: outcome.path,
|
|
69
|
+
reason: outcome.reason,
|
|
70
|
+
}, `enforcement registry at ${outcome.path} could not be parsed: ${outcome.reason}`, 2, format);
|
|
71
|
+
}
|
|
72
|
+
function refusal(envelope, human, exitCode, format) {
|
|
73
|
+
if (format === "json") {
|
|
74
|
+
return { exitCode, stdout: `${JSON.stringify(envelope)}\n`, stderr: "" };
|
|
75
|
+
}
|
|
76
|
+
return { exitCode, stdout: "", stderr: `doctor: ${human}\n` };
|
|
77
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { isAbsolute, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
export class NodeRegistryReader {
|
|
6
|
+
async readRegistry(rulesPath) {
|
|
7
|
+
const resolved = resolveRulesPath(rulesPath);
|
|
8
|
+
try {
|
|
9
|
+
const content = await readFile(resolved, "utf8");
|
|
10
|
+
return { kind: "found", path: resolved, content };
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return { kind: "not-found", path: resolved };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async cliVersion() {
|
|
17
|
+
const packagePath = resolve(fileURLToPath(import.meta.url), "..", "..", "..", "..", "..", "..", "package.json");
|
|
18
|
+
const text = await readFile(packagePath, "utf8");
|
|
19
|
+
const parsed = JSON.parse(text);
|
|
20
|
+
if (!isRecord(parsed) || typeof parsed.version !== "string") {
|
|
21
|
+
throw new Error("package.json#version missing");
|
|
22
|
+
}
|
|
23
|
+
return parsed.version;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function isRecord(value) {
|
|
27
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
function resolveRulesPath(p) {
|
|
30
|
+
if (p.startsWith("~/")) {
|
|
31
|
+
return resolve(homedir(), p.slice(2));
|
|
32
|
+
}
|
|
33
|
+
if (p === "~") {
|
|
34
|
+
return homedir();
|
|
35
|
+
}
|
|
36
|
+
if (isAbsolute(p)) {
|
|
37
|
+
return p;
|
|
38
|
+
}
|
|
39
|
+
return resolve(process.cwd(), p);
|
|
40
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { RegistryReader } from "../ports/outbound/RegistryReader.js";
|
|
2
|
+
export interface RunDoctorPorts {
|
|
3
|
+
registry: RegistryReader;
|
|
4
|
+
}
|
|
5
|
+
export type DriftKind = "version_mismatch" | "missing_diagnostic" | "stale_diagnostic";
|
|
6
|
+
export interface DriftEntry {
|
|
7
|
+
kind: DriftKind;
|
|
8
|
+
id: string;
|
|
9
|
+
remediation: string;
|
|
10
|
+
}
|
|
11
|
+
export type DoctorOutcome = {
|
|
12
|
+
kind: "ok";
|
|
13
|
+
ruleVersion: string;
|
|
14
|
+
cliVersion: string;
|
|
15
|
+
compatibleRange: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "drift";
|
|
18
|
+
ruleVersion: string;
|
|
19
|
+
cliVersion: string;
|
|
20
|
+
compatibleRange: string;
|
|
21
|
+
drift: DriftEntry[];
|
|
22
|
+
} | {
|
|
23
|
+
kind: "registry-not-found";
|
|
24
|
+
path: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "invalid-registry";
|
|
27
|
+
path: string;
|
|
28
|
+
reason: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function runDoctor(rulesPath: string, ports: RunDoctorPorts): Promise<DoctorOutcome>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { DOCTOR_DRIFT_KINDS, LINT_DIAGNOSTIC_IDS, READY_VIOLATION_KINDS, } from "../../../shared/domain/DiagnosticRegistry.js";
|
|
2
|
+
import { parseRegistry } from "../domain/RegistryRow.js";
|
|
3
|
+
import { rangeIncludes } from "../domain/SemverRange.js";
|
|
4
|
+
export async function runDoctor(rulesPath, ports) {
|
|
5
|
+
const file = await ports.registry.readRegistry(rulesPath);
|
|
6
|
+
if (file.kind === "not-found") {
|
|
7
|
+
return { kind: "registry-not-found", path: file.path };
|
|
8
|
+
}
|
|
9
|
+
const parsed = parseRegistry(file.content);
|
|
10
|
+
if (!parsed.ok) {
|
|
11
|
+
return {
|
|
12
|
+
kind: "invalid-registry",
|
|
13
|
+
path: file.path,
|
|
14
|
+
reason: parsed.error.reason,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const cliVersion = await ports.registry.cliVersion();
|
|
18
|
+
const drift = computeDrift(parsed.doc, cliVersion);
|
|
19
|
+
const ruleVersion = parsed.doc.compatibleSddCli;
|
|
20
|
+
if (drift.length === 0) {
|
|
21
|
+
return {
|
|
22
|
+
kind: "ok",
|
|
23
|
+
ruleVersion,
|
|
24
|
+
cliVersion,
|
|
25
|
+
compatibleRange: parsed.doc.compatibleSddCli,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
kind: "drift",
|
|
30
|
+
ruleVersion,
|
|
31
|
+
cliVersion,
|
|
32
|
+
compatibleRange: parsed.doc.compatibleSddCli,
|
|
33
|
+
drift,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function computeDrift(doc, cliVersion) {
|
|
37
|
+
const out = [];
|
|
38
|
+
if (!rangeIncludes(doc.compatibleSddCli, cliVersion)) {
|
|
39
|
+
out.push({
|
|
40
|
+
kind: "version_mismatch",
|
|
41
|
+
id: cliVersion,
|
|
42
|
+
remediation: `cli version ${cliVersion} is outside the registry range "${doc.compatibleSddCli}" — bump the registry's compatible_sdd_cli or downgrade the CLI`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const declared = new Set();
|
|
46
|
+
for (const row of doc.rows) {
|
|
47
|
+
if (row.maturity !== "implemented") {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
for (const id of row.diagnosticIds) {
|
|
51
|
+
declared.add(id);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const known = new Set([
|
|
55
|
+
...LINT_DIAGNOSTIC_IDS,
|
|
56
|
+
...READY_VIOLATION_KINDS,
|
|
57
|
+
...DOCTOR_DRIFT_KINDS,
|
|
58
|
+
]);
|
|
59
|
+
for (const id of declared) {
|
|
60
|
+
if (!known.has(id)) {
|
|
61
|
+
out.push({
|
|
62
|
+
kind: "missing_diagnostic",
|
|
63
|
+
id,
|
|
64
|
+
remediation: `registry declares "${id}" as implemented but the running CLI's DiagnosticRegistry does not contain it — add it to LINT_DIAGNOSTIC_IDS or READY_VIOLATION_KINDS, or change registry maturity to planned`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const id of known) {
|
|
69
|
+
if (!declared.has(id)) {
|
|
70
|
+
out.push({
|
|
71
|
+
kind: "stale_diagnostic",
|
|
72
|
+
id,
|
|
73
|
+
remediation: `DiagnosticRegistry contains "${id}" but no registry row claims it as implemented — add a registry row or remove the constant`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type RegistryMaturity = "planned" | "implemented" | "deprecated" | "out_of_scope";
|
|
2
|
+
export interface RegistryRow {
|
|
3
|
+
enfId: string;
|
|
4
|
+
ruleName: string;
|
|
5
|
+
maturity: RegistryMaturity;
|
|
6
|
+
diagnosticIds: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface RegistryDocument {
|
|
9
|
+
compatibleSddCli: string;
|
|
10
|
+
rows: RegistryRow[];
|
|
11
|
+
}
|
|
12
|
+
export interface RegistryParseError {
|
|
13
|
+
reason: string;
|
|
14
|
+
}
|
|
15
|
+
/** Parse a methodology enforcement-registry markdown blob into a typed
|
|
16
|
+
* RegistryDocument. Returns a discriminated outcome — never throws. */
|
|
17
|
+
export declare function parseRegistry(markdown: string): {
|
|
18
|
+
ok: true;
|
|
19
|
+
doc: RegistryDocument;
|
|
20
|
+
} | {
|
|
21
|
+
ok: false;
|
|
22
|
+
error: RegistryParseError;
|
|
23
|
+
};
|