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,17 @@
|
|
|
1
|
+
import { type CommandResult, type OutputFormat } from "../../../../shared/domain/CliOutput.js";
|
|
2
|
+
import { type ApplyApprovalPorts } from "../../application/ApplyApproval.js";
|
|
3
|
+
import { type WriteAttestationPorts } from "../../application/WriteAttestation.js";
|
|
4
|
+
import type { ApproveCommand, ApproveRequest } from "../../ports/inbound/ApproveCommand.js";
|
|
5
|
+
export interface ApproveCliPorts extends ApplyApprovalPorts, WriteAttestationPorts {
|
|
6
|
+
}
|
|
7
|
+
export interface ApproveExecutionMode {
|
|
8
|
+
inline: boolean;
|
|
9
|
+
planId?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class CliApproveHandler implements ApproveCommand {
|
|
12
|
+
private readonly ports;
|
|
13
|
+
constructor(ports: ApproveCliPorts);
|
|
14
|
+
execute(cwd: string, req: ApproveRequest, format: Exclude<OutputFormat, "yaml">, mode?: ApproveExecutionMode): Promise<CommandResult>;
|
|
15
|
+
private executeInline;
|
|
16
|
+
private executePlan;
|
|
17
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { failed, ok, } from "../../../../shared/domain/CliOutput.js";
|
|
2
|
+
import { CliFailure } from "../../../../shared/domain/Errors.js";
|
|
3
|
+
import { applyApproval, } from "../../application/ApplyApproval.js";
|
|
4
|
+
import { writeAttestation, } from "../../application/WriteAttestation.js";
|
|
5
|
+
export class CliApproveHandler {
|
|
6
|
+
ports;
|
|
7
|
+
constructor(ports) {
|
|
8
|
+
this.ports = ports;
|
|
9
|
+
}
|
|
10
|
+
async execute(cwd, req, format, mode) {
|
|
11
|
+
const inline = mode?.inline === true;
|
|
12
|
+
try {
|
|
13
|
+
if (inline) {
|
|
14
|
+
return await this.executeInline(cwd, req, format);
|
|
15
|
+
}
|
|
16
|
+
return await this.executePlan(cwd, req, mode?.planId, format);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error instanceof CliFailure) {
|
|
20
|
+
return failed(error, format);
|
|
21
|
+
}
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async executeInline(cwd, req, format) {
|
|
26
|
+
const outcome = await applyApproval(cwd, req, this.ports);
|
|
27
|
+
if (outcome.kind === "refused") {
|
|
28
|
+
return refusalResult(outcome.refusal, format);
|
|
29
|
+
}
|
|
30
|
+
const stderr = `DEPRECATED: --inline will be removed in v1.1.0; use \`sdd approve\` + \`sdd finalize\` instead.\n`;
|
|
31
|
+
if (format === "json") {
|
|
32
|
+
return {
|
|
33
|
+
exitCode: 0,
|
|
34
|
+
stdout: `${JSON.stringify({
|
|
35
|
+
format_version: 1,
|
|
36
|
+
ok: true,
|
|
37
|
+
mode: "inline",
|
|
38
|
+
matched_ids: outcome.matchedIds,
|
|
39
|
+
files_changed: outcome.filesChanged,
|
|
40
|
+
})}\n`,
|
|
41
|
+
stderr,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const lines = [];
|
|
45
|
+
for (const id of outcome.matchedIds) {
|
|
46
|
+
lines.push(`approve: ${id} → ${req.targetStatus} (approver=${req.approver})`);
|
|
47
|
+
}
|
|
48
|
+
lines.push("");
|
|
49
|
+
lines.push(`approve --inline: rewrote ${outcome.matchedIds.length} record(s) in ${outcome.filesChanged.length} file(s).`);
|
|
50
|
+
lines.push(`approve: re-run \`sdd lint\` to verify spec-valid.`);
|
|
51
|
+
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, stderr };
|
|
52
|
+
}
|
|
53
|
+
async executePlan(cwd, req, planId, format) {
|
|
54
|
+
const outcome = await writeAttestation(cwd, req, planId, this.ports);
|
|
55
|
+
if (outcome.kind === "refused") {
|
|
56
|
+
return refusalResult(outcome.refusal, format);
|
|
57
|
+
}
|
|
58
|
+
if (format === "json") {
|
|
59
|
+
return ok(JSON.stringify({
|
|
60
|
+
format_version: 1,
|
|
61
|
+
ok: true,
|
|
62
|
+
mode: "plan",
|
|
63
|
+
plan_id: outcome.result.planId,
|
|
64
|
+
plan_path: outcome.result.planPath,
|
|
65
|
+
is_new_plan: outcome.result.isNewPlan,
|
|
66
|
+
attestation: {
|
|
67
|
+
id: outcome.attestation.id,
|
|
68
|
+
owner_role: outcome.attestation.ownerRole,
|
|
69
|
+
approver_identity: outcome.attestation.approverIdentity,
|
|
70
|
+
timestamp: outcome.attestation.timestamp,
|
|
71
|
+
target_status: outcome.attestation.targetStatus,
|
|
72
|
+
},
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
const lines = [];
|
|
76
|
+
lines.push(`approve: ${req.id} → ${req.targetStatus} (approver=${req.approver})`);
|
|
77
|
+
lines.push("");
|
|
78
|
+
lines.push(`approve: queued attestation in ${outcome.result.planPath}`);
|
|
79
|
+
lines.push(` plan_id=${outcome.result.planId} (${outcome.result.isNewPlan ? "new plan" : "existing plan"})`);
|
|
80
|
+
lines.push(`approve: run \`sdd plan show\` to review, \`sdd finalize\` to apply.`);
|
|
81
|
+
return ok(lines.join("\n"));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function refusalResult(refusal, format) {
|
|
85
|
+
const message = refusalMessage(refusal);
|
|
86
|
+
if (format === "json") {
|
|
87
|
+
return {
|
|
88
|
+
exitCode: 1,
|
|
89
|
+
stdout: `${JSON.stringify({
|
|
90
|
+
format_version: 1,
|
|
91
|
+
ok: false,
|
|
92
|
+
reason: refusal.kind,
|
|
93
|
+
detail: message,
|
|
94
|
+
})}\n`,
|
|
95
|
+
stderr: "",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return { exitCode: 1, stdout: "", stderr: `approve: REFUSED — ${message}\n` };
|
|
99
|
+
}
|
|
100
|
+
function refusalMessage(refusal) {
|
|
101
|
+
if (refusal.kind === "agent-approver") {
|
|
102
|
+
return `approver "${refusal.approver}" is in the agent blocklist (SDD §7.5: self-approval forbidden). See spec/APPROVAL.md for the human-owned workflow.`;
|
|
103
|
+
}
|
|
104
|
+
if (refusal.kind === "invalid-owner-role") {
|
|
105
|
+
return `owner-role "${refusal.ownerRole}" not in valid set: tech-lead, architect, security-owner, platform-runtime-lead, product-owner, compliance.`;
|
|
106
|
+
}
|
|
107
|
+
return `no normative ID records matched "${refusal.id}".`;
|
|
108
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type SddConfig } from "../../../../shared/domain/Config.js";
|
|
2
|
+
import type { ApproveConfigPort } from "../../ports/outbound/ApproveConfigPort.js";
|
|
3
|
+
import type { ApproveFileSystem, SpecFileEntry } from "../../ports/outbound/ApproveFileSystem.js";
|
|
4
|
+
export declare class NodeApproveFileSystem implements ApproveConfigPort, ApproveFileSystem {
|
|
5
|
+
config(repoRoot: string): Promise<SddConfig>;
|
|
6
|
+
resolveSpecFiles(repoRoot: string, patterns: readonly string[]): Promise<SpecFileEntry[]>;
|
|
7
|
+
writeSpecFile(repoRoot: string, relativePath: string, content: string): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, relative, resolve } from "node:path";
|
|
3
|
+
import { configFromJson, } from "../../../../shared/domain/Config.js";
|
|
4
|
+
import { configFailure } from "../../../../shared/domain/Errors.js";
|
|
5
|
+
export class NodeApproveFileSystem {
|
|
6
|
+
async config(repoRoot) {
|
|
7
|
+
const configPath = join(repoRoot, ".sdd", "config.json");
|
|
8
|
+
const text = await readConfig(configPath);
|
|
9
|
+
return configFromJson(parseConfigJson(text, configPath), configPath);
|
|
10
|
+
}
|
|
11
|
+
async resolveSpecFiles(repoRoot, patterns) {
|
|
12
|
+
const matched = new Set();
|
|
13
|
+
for (const pattern of patterns) {
|
|
14
|
+
for (const abs of await expandGlob(repoRoot, pattern)) {
|
|
15
|
+
matched.add(abs);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const list = [...matched].sort();
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const abs of list) {
|
|
21
|
+
const text = await readFile(abs, "utf8");
|
|
22
|
+
const rel = relative(repoRoot, abs).split("\\").join("/");
|
|
23
|
+
out.push({ path: rel, content: text });
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
async writeSpecFile(repoRoot, relativePath, content) {
|
|
28
|
+
const abs = resolve(repoRoot, relativePath);
|
|
29
|
+
await writeFile(abs, content, "utf8");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function expandGlob(repoRoot, pattern) {
|
|
33
|
+
const normalised = pattern.split("\\").join("/");
|
|
34
|
+
if (!hasGlob(normalised)) {
|
|
35
|
+
const abs = resolve(repoRoot, normalised);
|
|
36
|
+
return (await isFile(abs)) ? [abs] : [];
|
|
37
|
+
}
|
|
38
|
+
const segments = normalised.split("/");
|
|
39
|
+
const literal = [];
|
|
40
|
+
let firstGlobIndex = -1;
|
|
41
|
+
for (let i = 0; i < segments.length; i++) {
|
|
42
|
+
if (hasGlob(segments[i])) {
|
|
43
|
+
firstGlobIndex = i;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
literal.push(segments[i]);
|
|
47
|
+
}
|
|
48
|
+
const baseDir = resolve(repoRoot, ...literal);
|
|
49
|
+
const remaining = segments.slice(firstGlobIndex);
|
|
50
|
+
const out = [];
|
|
51
|
+
await walk(baseDir, remaining, out);
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
async function walk(dir, remaining, acc) {
|
|
55
|
+
if (remaining.length === 0) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const head = remaining[0];
|
|
59
|
+
const rest = remaining.slice(1);
|
|
60
|
+
let entries;
|
|
61
|
+
try {
|
|
62
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (head === "**") {
|
|
68
|
+
if (rest.length === 0) {
|
|
69
|
+
for (const e of entries) {
|
|
70
|
+
const abs = join(dir, e.name);
|
|
71
|
+
if (e.isDirectory()) {
|
|
72
|
+
await walk(abs, ["**"], acc);
|
|
73
|
+
}
|
|
74
|
+
else if (e.isFile()) {
|
|
75
|
+
acc.push(abs);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
for (const e of entries) {
|
|
81
|
+
if (e.isDirectory()) {
|
|
82
|
+
await walk(join(dir, e.name), remaining, acc);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
await walk(dir, rest, acc);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
for (const e of entries) {
|
|
89
|
+
if (!matchSegment(head, e.name)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const abs = join(dir, e.name);
|
|
93
|
+
if (rest.length === 0) {
|
|
94
|
+
if (e.isFile()) {
|
|
95
|
+
acc.push(abs);
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (e.isDirectory()) {
|
|
100
|
+
await walk(abs, rest, acc);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function matchSegment(pattern, name) {
|
|
105
|
+
if (!hasGlob(pattern)) {
|
|
106
|
+
return pattern === name;
|
|
107
|
+
}
|
|
108
|
+
const re = new RegExp(`^${pattern
|
|
109
|
+
.split("")
|
|
110
|
+
.map((c) => c === "*"
|
|
111
|
+
? "[^/]*"
|
|
112
|
+
: c === "?"
|
|
113
|
+
? "[^/]"
|
|
114
|
+
: c.replace(/[.+^${}()|[\]\\]/g, "\\$&"))
|
|
115
|
+
.join("")}$`);
|
|
116
|
+
return re.test(name);
|
|
117
|
+
}
|
|
118
|
+
function hasGlob(value) {
|
|
119
|
+
return /[*?[\]]/.test(value);
|
|
120
|
+
}
|
|
121
|
+
async function isFile(abs) {
|
|
122
|
+
try {
|
|
123
|
+
return (await stat(abs)).isFile();
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function readConfig(path) {
|
|
130
|
+
try {
|
|
131
|
+
return await readFile(path, "utf8");
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
throw configFailure("config-missing", ".sdd/config.json is missing or unreadable", errorMessage(error), path);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function parseConfigJson(text, path) {
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(text);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw configFailure("config-invalid", ".sdd/config.json is not valid JSON", errorMessage(error), path);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function errorMessage(error) {
|
|
146
|
+
return error instanceof Error ? error.message : String(error);
|
|
147
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type PendingAttestation, type PlanFileShape } from "../../../../shared/domain/PlanFile.js";
|
|
2
|
+
import type { AppendAttestationResult, PlanFileWriter } from "../../ports/outbound/PlanFileWriter.js";
|
|
3
|
+
export declare class NodePlanFileWriter implements PlanFileWriter {
|
|
4
|
+
appendAttestation(repoRoot: string, plansDir: string, planId: string | undefined, attestation: PendingAttestation): Promise<AppendAttestationResult>;
|
|
5
|
+
readPlan(repoRoot: string, plansDir: string, planId: string): Promise<PlanFileShape | null>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
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 { generatePlanId, isoBasicUtc, parsePlanFile, serialisePlanFile, } from "../../../../shared/domain/PlanFile.js";
|
|
5
|
+
export class NodePlanFileWriter {
|
|
6
|
+
async appendAttestation(repoRoot, plansDir, planId, attestation) {
|
|
7
|
+
const dirAbs = resolve(repoRoot, plansDir);
|
|
8
|
+
await fs.mkdir(dirAbs, { recursive: true });
|
|
9
|
+
const resolvedPlanId = await resolveOrMintPlanId(dirAbs, planId);
|
|
10
|
+
const planPathAbs = join(dirAbs, `${resolvedPlanId.id}.yaml`);
|
|
11
|
+
const planPathRel = relative(repoRoot, planPathAbs).split("\\").join("/");
|
|
12
|
+
let plan;
|
|
13
|
+
let isNewPlan = false;
|
|
14
|
+
if (await pathExists(planPathAbs)) {
|
|
15
|
+
const raw = await fs.readFile(planPathAbs, "utf8");
|
|
16
|
+
plan = parsePlanFile(yamlParse(raw));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
plan = {
|
|
20
|
+
planId: resolvedPlanId.id,
|
|
21
|
+
createdAt: new Date().toISOString(),
|
|
22
|
+
pendingAttestations: [],
|
|
23
|
+
};
|
|
24
|
+
isNewPlan = true;
|
|
25
|
+
}
|
|
26
|
+
plan.pendingAttestations.push(attestation);
|
|
27
|
+
await writeAtomic(planPathAbs, serialisePlanFile(plan));
|
|
28
|
+
if (isNewPlan && resolvedPlanId.shouldUpdateActive) {
|
|
29
|
+
await fs.writeFile(join(dirAbs, ".active"), `${plan.planId}\n`, "utf8");
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
planId: plan.planId,
|
|
33
|
+
planPath: planPathRel,
|
|
34
|
+
isNewPlan,
|
|
35
|
+
pendingAfter: plan.pendingAttestations.length,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
async readPlan(repoRoot, plansDir, planId) {
|
|
39
|
+
const planPathAbs = resolve(repoRoot, plansDir, `${planId}.yaml`);
|
|
40
|
+
if (!(await pathExists(planPathAbs))) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const raw = await fs.readFile(planPathAbs, "utf8");
|
|
44
|
+
return parsePlanFile(yamlParse(raw));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function resolveOrMintPlanId(dirAbs, explicit) {
|
|
48
|
+
if (explicit !== undefined) {
|
|
49
|
+
return { id: explicit, shouldUpdateActive: false };
|
|
50
|
+
}
|
|
51
|
+
const active = await readActive(dirAbs);
|
|
52
|
+
if (active !== null) {
|
|
53
|
+
return { id: active, shouldUpdateActive: false };
|
|
54
|
+
}
|
|
55
|
+
/* Mint: ensure no collision against existing plan files. */
|
|
56
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
57
|
+
const id = generatePlanId(new Date());
|
|
58
|
+
const planPath = join(dirAbs, `${id}.yaml`);
|
|
59
|
+
if (!(await pathExists(planPath))) {
|
|
60
|
+
return { id, shouldUpdateActive: true };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/* Fallback — extremely unlikely; suffix with a counter to break the tie. */
|
|
64
|
+
const id = `${generatePlanId(new Date())}-fallback`;
|
|
65
|
+
void isoBasicUtc; /* keep import */
|
|
66
|
+
return { id, shouldUpdateActive: true };
|
|
67
|
+
}
|
|
68
|
+
async function readActive(dirAbs) {
|
|
69
|
+
try {
|
|
70
|
+
const raw = await fs.readFile(join(dirAbs, ".active"), "utf8");
|
|
71
|
+
const id = raw.trim();
|
|
72
|
+
return id.length > 0 ? id : null;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function writeAtomic(path, content) {
|
|
79
|
+
const tmp = `${path}.tmp.${process.pid}.${Date.now()}`;
|
|
80
|
+
await fs.mkdir(dirname(path), { recursive: true });
|
|
81
|
+
await fs.writeFile(tmp, content, "utf8");
|
|
82
|
+
await fs.rename(tmp, path);
|
|
83
|
+
}
|
|
84
|
+
async function pathExists(p) {
|
|
85
|
+
try {
|
|
86
|
+
await fs.access(p);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ApproveRefusal, type ApproveRequest } from "../domain/ApproveRequest.js";
|
|
2
|
+
export type { ApproveRefusal };
|
|
3
|
+
import type { ApproveClock } from "../ports/outbound/ApproveClock.js";
|
|
4
|
+
import type { ApproveConfigPort } from "../ports/outbound/ApproveConfigPort.js";
|
|
5
|
+
import type { ApproveFileSystem } from "../ports/outbound/ApproveFileSystem.js";
|
|
6
|
+
export interface ApplyApprovalPorts {
|
|
7
|
+
clock: ApproveClock;
|
|
8
|
+
config: ApproveConfigPort;
|
|
9
|
+
files: ApproveFileSystem;
|
|
10
|
+
}
|
|
11
|
+
export type ApplyApprovalOutcome = {
|
|
12
|
+
kind: "refused";
|
|
13
|
+
refusal: ApproveRefusal;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "applied";
|
|
16
|
+
matchedIds: string[];
|
|
17
|
+
filesChanged: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare function applyApproval(cwd: string, req: ApproveRequest, ports: ApplyApprovalPorts): Promise<ApplyApprovalOutcome>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { classifyRefusal, } from "../domain/ApproveRequest.js";
|
|
2
|
+
import { rewriteApproval } from "../domain/Rewrite.js";
|
|
3
|
+
export async function applyApproval(cwd, req, ports) {
|
|
4
|
+
const config = await ports.config.config(cwd);
|
|
5
|
+
const refusal = classifyRefusal(req, config.lint.approverBlocklist);
|
|
6
|
+
if (refusal !== null) {
|
|
7
|
+
return { kind: "refused", refusal };
|
|
8
|
+
}
|
|
9
|
+
const entries = await ports.files.resolveSpecFiles(cwd, config.lint.specFiles);
|
|
10
|
+
const matchedIds = [];
|
|
11
|
+
const filesChanged = [];
|
|
12
|
+
const when = ports.clock.now();
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const result = rewriteApproval(entry.content, req, when);
|
|
15
|
+
if (result.matched.length === 0) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (result.newContent !== entry.content) {
|
|
19
|
+
await ports.files.writeSpecFile(cwd, entry.path, result.newContent);
|
|
20
|
+
filesChanged.push(entry.path);
|
|
21
|
+
}
|
|
22
|
+
for (const m of result.matched) {
|
|
23
|
+
matchedIds.push(m.id);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (matchedIds.length === 0) {
|
|
27
|
+
return { kind: "refused", refusal: { kind: "no-id-match", id: req.id } };
|
|
28
|
+
}
|
|
29
|
+
return { kind: "applied", matchedIds, filesChanged };
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PendingAttestation } from "../../../shared/domain/PlanFile.js";
|
|
2
|
+
import { type ApproveRefusal, type ApproveRequest } from "../domain/ApproveRequest.js";
|
|
3
|
+
import type { ApproveClock } from "../ports/outbound/ApproveClock.js";
|
|
4
|
+
import type { ApproveConfigPort } from "../ports/outbound/ApproveConfigPort.js";
|
|
5
|
+
import type { AppendAttestationResult, PlanFileWriter } from "../ports/outbound/PlanFileWriter.js";
|
|
6
|
+
export interface WriteAttestationPorts {
|
|
7
|
+
clock: ApproveClock;
|
|
8
|
+
config: ApproveConfigPort;
|
|
9
|
+
plans: PlanFileWriter;
|
|
10
|
+
}
|
|
11
|
+
export type WriteAttestationOutcome = {
|
|
12
|
+
kind: "refused";
|
|
13
|
+
refusal: ApproveRefusal;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "appended";
|
|
16
|
+
result: AppendAttestationResult;
|
|
17
|
+
attestation: PendingAttestation;
|
|
18
|
+
};
|
|
19
|
+
export declare function writeAttestation(cwd: string, req: ApproveRequest, planId: string | undefined, ports: WriteAttestationPorts): Promise<WriteAttestationOutcome>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { classifyRefusal, } from "../domain/ApproveRequest.js";
|
|
2
|
+
export async function writeAttestation(cwd, req, planId, ports) {
|
|
3
|
+
const config = await ports.config.config(cwd);
|
|
4
|
+
const refusal = classifyRefusal(req, config.lint.approverBlocklist);
|
|
5
|
+
if (refusal !== null) {
|
|
6
|
+
return { kind: "refused", refusal };
|
|
7
|
+
}
|
|
8
|
+
const when = ports.clock.now();
|
|
9
|
+
const attestation = {
|
|
10
|
+
id: req.id,
|
|
11
|
+
ownerRole: req.ownerRole,
|
|
12
|
+
approverIdentity: req.approver,
|
|
13
|
+
timestamp: when.toISOString(),
|
|
14
|
+
changeRequest: req.changeRequest,
|
|
15
|
+
scope: req.scope,
|
|
16
|
+
targetStatus: req.targetStatus,
|
|
17
|
+
...(req.reviewedTestOracle !== null
|
|
18
|
+
? { reviewedTestOracle: req.reviewedTestOracle }
|
|
19
|
+
: {}),
|
|
20
|
+
};
|
|
21
|
+
const result = await ports.plans.appendAttestation(cwd, config.plansDir, planId, attestation);
|
|
22
|
+
return { kind: "appended", result, attestation };
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type TargetStatus = "approved" | "deprecated" | "removed";
|
|
2
|
+
export declare const VALID_TARGET_STATUS: ReadonlySet<TargetStatus>;
|
|
3
|
+
export declare const VALID_OWNER_ROLES: ReadonlySet<string>;
|
|
4
|
+
export { BUILTIN_AGENT_BLOCKLIST } from "../../../shared/domain/AgentBlocklist.js";
|
|
5
|
+
export interface ApproveRequest {
|
|
6
|
+
id: string;
|
|
7
|
+
approver: string;
|
|
8
|
+
ownerRole: string;
|
|
9
|
+
changeRequest: string;
|
|
10
|
+
scope: string;
|
|
11
|
+
targetStatus: TargetStatus;
|
|
12
|
+
reviewedTestOracle: string | null;
|
|
13
|
+
}
|
|
14
|
+
export type ApproveRefusal = {
|
|
15
|
+
kind: "agent-approver";
|
|
16
|
+
approver: string;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "invalid-owner-role";
|
|
19
|
+
ownerRole: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "no-id-match";
|
|
22
|
+
id: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function classifyRefusal(req: ApproveRequest, extraBlocklist: readonly string[]): ApproveRefusal | null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isBlockedApprover } from "../../../shared/domain/AgentBlocklist.js";
|
|
2
|
+
export const VALID_TARGET_STATUS = new Set(["approved", "deprecated", "removed"]);
|
|
3
|
+
export const VALID_OWNER_ROLES = new Set([
|
|
4
|
+
"tech-lead",
|
|
5
|
+
"architect",
|
|
6
|
+
"security-owner",
|
|
7
|
+
"platform-runtime-lead",
|
|
8
|
+
"product-owner",
|
|
9
|
+
"compliance",
|
|
10
|
+
]);
|
|
11
|
+
/*
|
|
12
|
+
* ENF-010: re-export of the built-in agent-approver blocklist (SDD §7.5)
|
|
13
|
+
* so approve and lint share one list; config extends it. See spec record.
|
|
14
|
+
*/
|
|
15
|
+
export { BUILTIN_AGENT_BLOCKLIST } from "../../../shared/domain/AgentBlocklist.js";
|
|
16
|
+
export function classifyRefusal(req, extraBlocklist) {
|
|
17
|
+
if (isBlockedApprover(req.approver, extraBlocklist)) {
|
|
18
|
+
return { kind: "agent-approver", approver: req.approver };
|
|
19
|
+
}
|
|
20
|
+
if (!VALID_OWNER_ROLES.has(req.ownerRole)) {
|
|
21
|
+
return { kind: "invalid-owner-role", ownerRole: req.ownerRole };
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { matchId, rewriteApproval, type IdMatch, type RewriteResult, type ApprovalAttestation, type ApprovalTargetStatus, } from "../../../shared/domain/SpecApprovalRewrite.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Re-export shim — content lives in src/shared/domain/SpecApprovalRewrite.ts
|
|
3
|
+
* so both approve (inline) and finalize (plan materialisation) can use it
|
|
4
|
+
* without crossing feature boundaries.
|
|
5
|
+
*/
|
|
6
|
+
export { matchId, rewriteApproval, } from "../../../shared/domain/SpecApprovalRewrite.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CommandResult, OutputFormat } from "../../../../shared/domain/CliOutput.js";
|
|
2
|
+
import type { ApproveRequest } from "../../domain/ApproveRequest.js";
|
|
3
|
+
export type { ApproveRequest, TargetStatus, } from "../../domain/ApproveRequest.js";
|
|
4
|
+
export interface ApproveExecutionMode {
|
|
5
|
+
inline: boolean;
|
|
6
|
+
planId?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ApproveCommand {
|
|
9
|
+
execute(cwd: string, req: ApproveRequest, format: Exclude<OutputFormat, "yaml">, mode?: ApproveExecutionMode): Promise<CommandResult>;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface SpecFileEntry {
|
|
2
|
+
path: string;
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ApproveFileSystem {
|
|
6
|
+
resolveSpecFiles(repoRoot: string, patterns: readonly string[]): Promise<SpecFileEntry[]>;
|
|
7
|
+
writeSpecFile(repoRoot: string, relativePath: string, content: string): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PendingAttestation, PlanFileShape } from "../../../../shared/domain/PlanFile.js";
|
|
2
|
+
export interface AppendAttestationResult {
|
|
3
|
+
planId: string;
|
|
4
|
+
planPath: string;
|
|
5
|
+
isNewPlan: boolean;
|
|
6
|
+
pendingAfter: number;
|
|
7
|
+
}
|
|
8
|
+
export interface PlanFileWriter {
|
|
9
|
+
appendAttestation(repoRoot: string, plansDir: string, planId: string | undefined, attestation: PendingAttestation): Promise<AppendAttestationResult>;
|
|
10
|
+
/** Read-only helper used by `sdd plan show`-style consumers and by tests.
|
|
11
|
+
* Returns null when the plan does not exist. */
|
|
12
|
+
readPlan(repoRoot: string, plansDir: string, planId: string): Promise<PlanFileShape | null>;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type CommandResult, type OutputFormat } from "../../../../shared/domain/CliOutput.js";
|
|
2
|
+
import { type CheckBaselinePorts } from "../../application/CheckBaseline.js";
|
|
3
|
+
import type { CheckCommand } from "../../ports/inbound/CheckCommand.js";
|
|
4
|
+
export declare class CliCheckHandler implements CheckCommand {
|
|
5
|
+
private readonly ports;
|
|
6
|
+
constructor(ports: CheckBaselinePorts);
|
|
7
|
+
execute(cwd: string, format: Exclude<OutputFormat, "yaml">): Promise<CommandResult>;
|
|
8
|
+
}
|