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
package/README.md
ADDED
|
@@ -0,0 +1,1028 @@
|
|
|
1
|
+
# `agent-sdd`
|
|
2
|
+
|
|
3
|
+
[](https://github.com/cyberash-dev/sdd-cli/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](package.json)
|
|
6
|
+
|
|
7
|
+
📖 Read this in other languages: [Русский](README.ru.md)
|
|
8
|
+
|
|
9
|
+
A standalone CLI helper for Spec-Driven Development (SDD). Computes a
|
|
10
|
+
deterministic `freshness_token` over a configurable Discovery scope of
|
|
11
|
+
your repository, compares the current state against the value recorded
|
|
12
|
+
in your spec's Brownfield-baseline block, emits machine-readable stubs
|
|
13
|
+
(`Delta` / `Open-Q`) describing scope drift since the recorded
|
|
14
|
+
baseline commit, runs SDD spec-lint rules over normative IDs, flips
|
|
15
|
+
`lifecycle.status` from `proposed` to `approved` with a typed
|
|
16
|
+
`approval_record` block via `sdd approve`, and gates merges with the
|
|
17
|
+
single `sdd ready` command — a strict superset of `sdd lint` and
|
|
18
|
+
`sdd check` plus marker-coverage / sandbox-isolation checks for the
|
|
19
|
+
SDD `implementation-valid` gate-3.
|
|
20
|
+
|
|
21
|
+
The CLI is **mostly read-only on the spec**: `sdd token`, `sdd check`,
|
|
22
|
+
`sdd refresh`, `sdd lint`, `sdd ready` never rewrite normative
|
|
23
|
+
content. The single exception is `sdd approve`, which atomically
|
|
24
|
+
writes `lifecycle.status` + `approval_record` and refuses agent
|
|
25
|
+
identities (SDD §7.5: self-approval is forbidden).
|
|
26
|
+
|
|
27
|
+
> **Status**: v1.0.0, governed by `spec/spec.md`. The full normative
|
|
28
|
+
> specification (Surfaces, Behaviors, Contracts, Invariants, Policies,
|
|
29
|
+
> Constraints, External dependencies, Migrations, Deltas,
|
|
30
|
+
> Implementation bindings) lives there. This README is the
|
|
31
|
+
> consumer-facing manual — for spec details, read `spec/spec.md`.
|
|
32
|
+
> Release notes: [CHANGELOG.md](CHANGELOG.md).
|
|
33
|
+
>
|
|
34
|
+
> **What's new in v1.0.0** — sync to SDD methodology Plan 2: two-step
|
|
35
|
+
> approval (`sdd approve` → `.sdd/plans/<plan_id>.yaml` attestation,
|
|
36
|
+
> then `sdd finalize` for the atomic flip with prospective graph
|
|
37
|
+
> validation), `sdd report --pr-summary`, debt-budget mechanics on
|
|
38
|
+
> `Partition` (`unmodeled_budget`), semver cascade in `sdd ready`
|
|
39
|
+
> (Policy / Invariant(contractual) → referencing Surface).
|
|
40
|
+
> Legacy direct-rewrite path survives one minor as
|
|
41
|
+
> `sdd approve --inline` (deprecated; removed in v1.1.0).
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Why `agent-sdd`?
|
|
46
|
+
|
|
47
|
+
SDD treats a project's specification as the single source of truth for
|
|
48
|
+
code generation. The Brownfield-baseline block in `spec.md` records:
|
|
49
|
+
|
|
50
|
+
- `freshness_token` — a hash over the repository's Discovery scope at
|
|
51
|
+
some commit;
|
|
52
|
+
- `baseline_commit_sha` — the commit at which the token was computed.
|
|
53
|
+
|
|
54
|
+
A `freshness_token` lets the SDD `baseline-valid` gate verify that the
|
|
55
|
+
spec's baseline still describes the actual repository. Without a
|
|
56
|
+
mechanical token, an agent has no way to detect that the source tree
|
|
57
|
+
drifted from the baseline since the last review.
|
|
58
|
+
|
|
59
|
+
`sdd-cli` provides these subcommands — the first six automate the
|
|
60
|
+
freshness/spec loop, `sdd record` navigates the spec, and `sdd install`
|
|
61
|
+
distributes the methodology rules into your agent config:
|
|
62
|
+
|
|
63
|
+
| Command | Purpose |
|
|
64
|
+
|---------------|--------------------------------------------------------------------|
|
|
65
|
+
| `sdd token` | Compute the current scope token at `HEAD` (no spec read). |
|
|
66
|
+
| `sdd check` | Compare the current token against the value recorded in `spec.md`. |
|
|
67
|
+
| `sdd refresh` | Diff scope state against the recorded baseline, emit stubs. |
|
|
68
|
+
| `sdd lint` | Run SDD spec-lint rules over your `lint.spec_files`; exit 1 on errors. |
|
|
69
|
+
| `sdd approve` | Promote a `proposed` ID to `approved` with a typed `approval_record`. Refuses agent identities (SDD §7.5). |
|
|
70
|
+
| `sdd ready` | The single CI gate-3 (`implementation-valid`) check: marker coverage, sandbox isolation, lint + check aggregation. |
|
|
71
|
+
| `sdd record` | Navigate/edit `spec.md` one record at a time (read-only `list`/`get`; atomic `set`/`add` for draft/proposed). |
|
|
72
|
+
| `sdd install` | Install the SDD methodology rules (+ Claude hooks) into the user-level agent config (`~/.claude`, `~/.codex`), or into the repo with `--scope project`. |
|
|
73
|
+
|
|
74
|
+
The mechanism is fixed (`git_tree_hash_v1`), but the tool is generic:
|
|
75
|
+
every SDD-following repo configures it through a small JSON file
|
|
76
|
+
(`.sdd/config.json`).
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Requirements
|
|
81
|
+
|
|
82
|
+
- **Node.js** ≥ 20
|
|
83
|
+
- **git** ≥ 2.30 on `PATH`
|
|
84
|
+
- a git repository — the CLI refuses to run outside one
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Installation
|
|
89
|
+
|
|
90
|
+
### Option 1 — npm registry (recommended)
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
npm install --save-dev agent-sdd
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
After install, `sdd` is on `node_modules/.bin/sdd` and runnable via
|
|
97
|
+
`npx sdd ...` or your preferred package script.
|
|
98
|
+
|
|
99
|
+
### Option 2 — local path
|
|
100
|
+
|
|
101
|
+
When developing `sdd-cli` itself alongside a consumer repo:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
npm install --save-dev "file:../sdd-cli"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`package.json` will reference `"agent-sdd": "file:../sdd-cli"`.
|
|
108
|
+
This is the recommended layout when both repos sit side by side, since
|
|
109
|
+
edits in `sdd-cli/` are picked up immediately after `npm run build`.
|
|
110
|
+
|
|
111
|
+
### Option 3 — `npm pack` tarball
|
|
112
|
+
|
|
113
|
+
For a frozen artefact without registry access:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
# inside ~/Projects/sdd-cli
|
|
117
|
+
npm run build
|
|
118
|
+
npm pack # produces agent-sdd-<version>.tgz
|
|
119
|
+
|
|
120
|
+
# inside the consumer repo
|
|
121
|
+
npm install --save-dev /path/to/agent-sdd-1.0.0.tgz
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Configuration — `.sdd/config.json`
|
|
127
|
+
|
|
128
|
+
Drop a single JSON file at `<repo_root>/.sdd/config.json`. Minimal
|
|
129
|
+
example:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"$schema": "https://github.com/cyberash-dev/sdd-cli/blob/main/schema/sdd.config.schema.json",
|
|
134
|
+
"spec_file": "spec/spec.md",
|
|
135
|
+
"baseline_id": "my-partition:BL-001",
|
|
136
|
+
"discovery_scope": [
|
|
137
|
+
"src",
|
|
138
|
+
"tests",
|
|
139
|
+
"package.json",
|
|
140
|
+
"tsconfig.json"
|
|
141
|
+
],
|
|
142
|
+
"mechanism": "git_tree_hash_v1"
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Field reference
|
|
147
|
+
|
|
148
|
+
| Field | Type | Required | Default | Meaning |
|
|
149
|
+
|-----------------------------|-----------|----------|------------------------|-------------------------------------------------------------------------|
|
|
150
|
+
| `spec_file` | string | yes | — | Path to the SDD spec file, relative to repo root. |
|
|
151
|
+
| `baseline_id` | string | yes | — | Full `<partition>:BL-<n>` of the BrownfieldBaseline block to read. |
|
|
152
|
+
| `discovery_scope` | string[] | yes | — | git pathspecs (dirs, files, globs) handed verbatim to `git ls-tree`. |
|
|
153
|
+
| `mechanism` | enum | yes | — | Currently only `"git_tree_hash_v1"`. |
|
|
154
|
+
| `footprint.binding_id_prefix` | string | no | `"IMP-"` | Neutral-id prefix scanned for footprint paths. |
|
|
155
|
+
| `footprint.binding_field` | string | no | `"binding"` | YAML key under which file paths live in IMP blocks. |
|
|
156
|
+
| `lint.spec_files` | string[] | no | `[spec_file]` | Glob patterns (posix) for spec files to scan with `sdd lint`/`sdd approve`. |
|
|
157
|
+
| `lint.approver_blocklist` | string[] | no | `[]` | Extra approver identities to refuse on top of the built-in agent list. |
|
|
158
|
+
| `partitions` | object | no | absent → flat shorthand | Multi-partition mode (CTR-015). Per-partition `spec_paths` (required), `test_paths`, `sandbox_paths`. Keys match the partition-name regex below. |
|
|
159
|
+
| `test_paths` (top-level) | string[] | no | `[]` | Shorthand applied to the synthesised single-partition fallback when `partitions` is absent. |
|
|
160
|
+
| `sandbox_paths` (top-level) | string[] | no | `[]` | Shorthand applied to the synthesised single-partition fallback when `partitions` is absent. |
|
|
161
|
+
|
|
162
|
+
`baseline_id` and `partitions.<name>` keys both match
|
|
163
|
+
`^[a-z][a-z0-9-]*(:[a-z][a-z0-9-]*)*$` (one or more lowercase tokens
|
|
164
|
+
joined by `:`; CST-007 / CTR-015 widening). Examples:
|
|
165
|
+
`pipeline-driver:BL-001`, `bridge:commands:CON-004`. Single-segment is
|
|
166
|
+
the v0.1.0/v0.2.0 default and is preserved unchanged. Unknown
|
|
167
|
+
top-level fields are rejected — see `schema/sdd.config.schema.json`
|
|
168
|
+
for the formal JSON Schema.
|
|
169
|
+
|
|
170
|
+
### Discovery scope tips
|
|
171
|
+
|
|
172
|
+
- A scope entry that resolves to **zero files** at HEAD is a hard
|
|
173
|
+
config error. This protects against typos like
|
|
174
|
+
`spec/0[0-9]-*.md` when no such files exist yet.
|
|
175
|
+
- Globs use git pathspec syntax (`*`, `?`, `[abc]`). They resolve
|
|
176
|
+
against `git ls-tree -r --name-only HEAD`.
|
|
177
|
+
- Order does not matter: `git ls-tree` canonicalises by name, so the
|
|
178
|
+
resulting token is stable across reorderings.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## The Brownfield-baseline block
|
|
183
|
+
|
|
184
|
+
`sdd-cli` looks up a single YAML block in `<spec_file>` whose `id`
|
|
185
|
+
equals `<config.baseline_id>` and whose `type` equals
|
|
186
|
+
`BrownfieldBaseline`. It reads two fields from that block:
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
---
|
|
190
|
+
id: my-partition:BL-001
|
|
191
|
+
type: BrownfieldBaseline
|
|
192
|
+
freshness_token: <64-char hex>
|
|
193
|
+
baseline_commit_sha: <40-char hex>
|
|
194
|
+
mechanism: git_tree_hash_v1
|
|
195
|
+
# ... lifecycle, discovery_scope, coverage_evidence, etc.
|
|
196
|
+
---
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The CLI treats duplicate baseline blocks (same `id` matching twice) as
|
|
200
|
+
a config error.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Commands
|
|
205
|
+
|
|
206
|
+
### `sdd token`
|
|
207
|
+
|
|
208
|
+
Compute and print the current scope token at `HEAD`.
|
|
209
|
+
|
|
210
|
+
```sh
|
|
211
|
+
sdd token # human format
|
|
212
|
+
sdd token --format=json # machine-readable
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**JSON output (success)**:
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"format_version": 1,
|
|
220
|
+
"ok": true,
|
|
221
|
+
"token": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
222
|
+
"commit_sha": "0b0f4d84e5c7a9182f15c7f3d4e0f6a8c0e1d2b7",
|
|
223
|
+
"mechanism": "git_tree_hash_v1",
|
|
224
|
+
"scope": ["src", "tests", "package.json"]
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**JSON output (scope-dirty)**:
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"format_version": 1,
|
|
233
|
+
"ok": false,
|
|
234
|
+
"reason": "baseline-dirty",
|
|
235
|
+
"dirty_paths": ["src/foo.ts"]
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
`sdd token` exits **1** when the working tree is dirty inside scope —
|
|
240
|
+
the CLI never computes a token over uncommitted changes. Untracked
|
|
241
|
+
files inside scope count as dirt.
|
|
242
|
+
|
|
243
|
+
### `sdd check`
|
|
244
|
+
|
|
245
|
+
Compare the freshly computed token against the value recorded in the
|
|
246
|
+
baseline block.
|
|
247
|
+
|
|
248
|
+
```sh
|
|
249
|
+
sdd check # human format
|
|
250
|
+
sdd check --format=json
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Outcomes**:
|
|
254
|
+
|
|
255
|
+
| Exit | Reason | Meaning |
|
|
256
|
+
|------|--------------------|--------------------------------------------------------------------|
|
|
257
|
+
| 0 | — | Recorded token matches the recomputed one; tree is scope-clean. |
|
|
258
|
+
| 1 | `baseline-stale` | Tree is clean, but the recorded token differs from the recomputed. |
|
|
259
|
+
| 1 | `baseline-dirty` | Working tree has uncommitted scope changes; check short-circuits. |
|
|
260
|
+
|
|
261
|
+
`sdd check` is the typical CI gate. If you put it in a pre-merge or
|
|
262
|
+
pre-deploy pipeline, exit code 1 stops the build until either the spec
|
|
263
|
+
is refreshed (with `sdd refresh` and human review) or the working tree
|
|
264
|
+
is cleaned up.
|
|
265
|
+
|
|
266
|
+
### `sdd refresh`
|
|
267
|
+
|
|
268
|
+
Diff the current scope state against the recorded `baseline_commit_sha`
|
|
269
|
+
and emit one stub per drifted path.
|
|
270
|
+
|
|
271
|
+
```sh
|
|
272
|
+
sdd refresh # default: --format=yaml
|
|
273
|
+
sdd refresh --format=json
|
|
274
|
+
sdd refresh --format=human
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Each changed path is bucketed:
|
|
278
|
+
|
|
279
|
+
- **Inside an `IMP-*` footprint** → `Delta` stub naming the IMP-id(s)
|
|
280
|
+
whose `binding` covers that path, plus the IMP's `target_ids`. A
|
|
281
|
+
human or downstream agent fills in `compatibility_action`,
|
|
282
|
+
`kind_of_change`, `tests_old_behavior`, `tests_new_behavior`.
|
|
283
|
+
- **Inside scope but outside every footprint** → `Open-Q` stub asking
|
|
284
|
+
whether the path should be bound to a normative ID.
|
|
285
|
+
|
|
286
|
+
**YAML stream** (default):
|
|
287
|
+
|
|
288
|
+
```yaml
|
|
289
|
+
---
|
|
290
|
+
kind: Delta
|
|
291
|
+
path: "src/foo.ts"
|
|
292
|
+
target_imp_ids:
|
|
293
|
+
- "my-partition:IMP-002"
|
|
294
|
+
target_ids:
|
|
295
|
+
- "my-partition:BEH-014"
|
|
296
|
+
emitted_at: "2026-04-29T15:37:35.000Z"
|
|
297
|
+
compatibility_action: TODO
|
|
298
|
+
kind_of_change: TODO
|
|
299
|
+
tests_old_behavior: TODO
|
|
300
|
+
tests_new_behavior: TODO
|
|
301
|
+
---
|
|
302
|
+
kind: Open-Q
|
|
303
|
+
path: "spec/notes.md"
|
|
304
|
+
question: "Should spec/notes.md be bound to a normative ID?"
|
|
305
|
+
options:
|
|
306
|
+
- "bind_to_existing_or_new_id"
|
|
307
|
+
- "leave_unmodeled"
|
|
308
|
+
blocking: TODO
|
|
309
|
+
emitted_at: "2026-04-29T15:37:35.000Z"
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Empty drift in JSON mode**:
|
|
313
|
+
|
|
314
|
+
```json
|
|
315
|
+
{ "format_version": 1, "stubs": [] }
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
`sdd refresh` exits **0** even when stubs are emitted — the command is
|
|
319
|
+
composable in scripts (`sdd refresh > stubs.yaml`). The drift signal is
|
|
320
|
+
`sdd check`, not `sdd refresh`.
|
|
321
|
+
|
|
322
|
+
### `sdd lint`
|
|
323
|
+
|
|
324
|
+
Run SDD spec-lint rules over every file matched by `lint.spec_files`
|
|
325
|
+
(falling back to the single `spec_file` when the `lint` block is
|
|
326
|
+
absent). Lint never modifies the spec.
|
|
327
|
+
|
|
328
|
+
```sh
|
|
329
|
+
sdd lint # human format (default)
|
|
330
|
+
sdd lint --format=json
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Each violating ID record produces one diagnostic. Rule ids (e.g.
|
|
334
|
+
`sdd:weasel-word`, `sdd:approval-record-required`,
|
|
335
|
+
`sdd:test-obligation-required`) are append-only — once published, a
|
|
336
|
+
rule id is never renamed or repurposed.
|
|
337
|
+
|
|
338
|
+
**JSON envelope**:
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"format_version": 1,
|
|
343
|
+
"ok": false,
|
|
344
|
+
"error_count": 3,
|
|
345
|
+
"warn_count": 0,
|
|
346
|
+
"diagnostics": [
|
|
347
|
+
{
|
|
348
|
+
"severity": "error",
|
|
349
|
+
"rule": "sdd:approval-record-required",
|
|
350
|
+
"file": "spec/spec.md",
|
|
351
|
+
"line": 141,
|
|
352
|
+
"message": "ID \"my:SUR-001\" has lifecycle.status=approved but no real approval_record (SDD §7.5)."
|
|
353
|
+
}
|
|
354
|
+
]
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
| Exit | Meaning |
|
|
359
|
+
|------|--------------------------------------------------------------------------------------|
|
|
360
|
+
| 0 | All errors resolved (warnings are allowed). |
|
|
361
|
+
| 1 | At least one **error**-severity diagnostic. `ok: false` in JSON. |
|
|
362
|
+
| 2 | argv error (unknown flag, invalid format value). |
|
|
363
|
+
| 3 | Environment error (e.g. `.sdd/config.json` missing). |
|
|
364
|
+
|
|
365
|
+
### `sdd approve`
|
|
366
|
+
|
|
367
|
+
Promote one or more normative IDs from `proposed` (with the
|
|
368
|
+
`not_applicable_for_proposed` placeholder) to `approved` (or
|
|
369
|
+
`deprecated` / `removed`), writing a typed `approval_record` block in
|
|
370
|
+
the same atomic edit. The CLI refuses to run when the `--approver` is
|
|
371
|
+
in the built-in agent blocklist (e.g. `claude`, `bot:*`,
|
|
372
|
+
`spec-author-bot`, `sdd-cli`) or appears in
|
|
373
|
+
`lint.approver_blocklist`.
|
|
374
|
+
|
|
375
|
+
```sh
|
|
376
|
+
sdd approve \
|
|
377
|
+
--id "my-partition:BEH-014" \
|
|
378
|
+
--approver alice \
|
|
379
|
+
--owner-role tech-lead \
|
|
380
|
+
--change-request "https://example.com/pr/42"
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Required flags**: `--id`, `--approver`, `--owner-role`,
|
|
384
|
+
`--change-request`.
|
|
385
|
+
|
|
386
|
+
**Optional flags**:
|
|
387
|
+
|
|
388
|
+
- `--scope <string>` (default: `first-time-approval`)
|
|
389
|
+
- `--target-status approved|deprecated|removed` (default: `approved`)
|
|
390
|
+
- `--reviewed-test-oracle <ref>` (recommended for major-bump Surfaces)
|
|
391
|
+
- `--format json|human` (default: `human`)
|
|
392
|
+
|
|
393
|
+
`--id` accepts an exact id or a glob with `*` (e.g. `pol:*`). All
|
|
394
|
+
matching records in every file under `lint.spec_files` are rewritten
|
|
395
|
+
in one batch. The written `approval_record` block looks like:
|
|
396
|
+
|
|
397
|
+
```yaml
|
|
398
|
+
approval_record:
|
|
399
|
+
owner_role: tech-lead
|
|
400
|
+
approver_identity: alice
|
|
401
|
+
timestamp: 2026-04-30T10:15:42.001Z
|
|
402
|
+
change_request: https://example.com/pr/42
|
|
403
|
+
scope: first-time-approval
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
| Exit | Reason | Meaning |
|
|
407
|
+
|------|-------------------------|---------------------------------------------------------------------------------|
|
|
408
|
+
| 0 | — | At least one record matched and was rewritten. |
|
|
409
|
+
| 1 | `agent-approver` | `--approver` is in the built-in agent blocklist or starts with `bot:` (SDD §7.5). |
|
|
410
|
+
| 1 | `invalid-owner-role` | `--owner-role` is not in the closed enum (six allowed roles). |
|
|
411
|
+
| 1 | `no-id-match` | `--id`/glob matched zero normative-ID records across all spec files. |
|
|
412
|
+
| 2 | — | argv error (missing required flag, unknown flag, invalid `--target-status`). |
|
|
413
|
+
|
|
414
|
+
**Owner-role enum** (closed): `tech-lead`, `architect`,
|
|
415
|
+
`security-owner`, `platform-runtime-lead`, `product-owner`,
|
|
416
|
+
`compliance`.
|
|
417
|
+
|
|
418
|
+
### `sdd ready`
|
|
419
|
+
|
|
420
|
+
Single, authoritative gate-3 check for CI. Strict superset of
|
|
421
|
+
`sdd lint` and `sdd check`: scans for `@covers <partition>:<id>`
|
|
422
|
+
markers in your test files, refuses `proposed`/`draft` IDs outside
|
|
423
|
+
`sandbox_paths`, demands a typed `compatibility_action=…` marker for
|
|
424
|
+
`removed` IDs, and re-runs lint/check semantics under one JSON
|
|
425
|
+
envelope. Adding `sdd ready` to your protected-branch policy is what
|
|
426
|
+
makes the SDD three-gate contract enforceable in practice.
|
|
427
|
+
|
|
428
|
+
```sh
|
|
429
|
+
sdd ready # default: all partitions, human output
|
|
430
|
+
sdd ready --format=json # stable JSON for CI / GitHub annotations
|
|
431
|
+
sdd ready --partition pipeline-driver # filter (and staged-rollout knob)
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
**Exit codes**:
|
|
435
|
+
|
|
436
|
+
| Exit | Meaning |
|
|
437
|
+
|------|--------------------------------------------------------------------------------------|
|
|
438
|
+
| 0 | Mergeable. No blockers found. |
|
|
439
|
+
| 1 | At least one merge blocker found (any of the seven rule kinds, or aggregated). |
|
|
440
|
+
| 2 | Could not evaluate (`config_invalid` / `spec_parse_failed` / `unreadable_test_paths`). |
|
|
441
|
+
|
|
442
|
+
**Marker grammar** (CST-007): `@covers <partition>:<id> [key=value ...]`
|
|
443
|
+
where `<partition>` matches
|
|
444
|
+
`^[a-z][a-z0-9-]*(:[a-z][a-z0-9-]*)*$` (one or more lowercase tokens
|
|
445
|
+
joined by `:` — single-segment `my-partition:BEH-001` and multi-segment
|
|
446
|
+
`bridge:commands:CON-004` both parse), `<id>` matches `^[A-Z]+-\d+$`,
|
|
447
|
+
and the only whitelisted tail key in v0.3.0 is
|
|
448
|
+
`compatibility_action=<value>`. Unknown tail keys are silently
|
|
449
|
+
ignored (forward-compat). The partition/id split is at the rightmost
|
|
450
|
+
`:` of the captured token (the id tail contains no `:`). Place
|
|
451
|
+
markers anywhere in your test files — typically as
|
|
452
|
+
`// @covers <id>` near the test that closes the obligation.
|
|
453
|
+
|
|
454
|
+
**Configuring partitions** (CTR-015): the v0.1.0/v0.2.0 flat
|
|
455
|
+
`.sdd/config.json` shape is preserved as a single-partition shorthand
|
|
456
|
+
when `partitions` is absent. For multi-partition repos, declare:
|
|
457
|
+
|
|
458
|
+
```json
|
|
459
|
+
{
|
|
460
|
+
"partitions": {
|
|
461
|
+
"my-partition": {
|
|
462
|
+
"spec_paths": ["spec/spec.md"],
|
|
463
|
+
"test_paths": ["tests/**/*.test.ts"],
|
|
464
|
+
"sandbox_paths": ["spike/**"]
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
A cross-partition test that legitimately covers IDs from both A and
|
|
471
|
+
B must appear in **both** partitions' `test_paths`. Implicit
|
|
472
|
+
cross-credit is not provided.
|
|
473
|
+
|
|
474
|
+
> `sdd ready` verifies traceability presence, not test fidelity.
|
|
475
|
+
> Major-bump correctness (oracle/assertion summary, input classes,
|
|
476
|
+
> negative oracle) is human review per SDD §three gates.
|
|
477
|
+
|
|
478
|
+
### `sdd record`
|
|
479
|
+
|
|
480
|
+
Navigate and edit a large `spec.md` one record at a time, without
|
|
481
|
+
reading or rewriting the whole file — designed for AI agents whose
|
|
482
|
+
context window is the scarce resource. Four subcommands:
|
|
483
|
+
|
|
484
|
+
```sh
|
|
485
|
+
sdd record list # compact index of every record
|
|
486
|
+
sdd record list --partition my-partition # filter to one partition
|
|
487
|
+
sdd record get my-partition:BEH-001 # one record, verbatim
|
|
488
|
+
sdd record set my-partition:BEH-001 --from-file body.yaml
|
|
489
|
+
sdd record set my-partition:BEH-001 --content "$BODY"
|
|
490
|
+
sdd record add --after my-partition:BEH-001 --from-file new.yaml
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
- **`list`** — one row per record: `id` · `type` · `lifecycle.status` ·
|
|
494
|
+
derived title (the record's `title`, else a Surface's `name`, else
|
|
495
|
+
blank). `--partition <name>` keeps only records whose partition
|
|
496
|
+
component (the id minus its trailing `:<ID-tail>`) equals `<name>`.
|
|
497
|
+
Read-only.
|
|
498
|
+
- **`get <id>`** — prints the record's exact source body (round-trips
|
|
499
|
+
straight back into `set`). `--format=json` adds `file`,
|
|
500
|
+
`start_line`, `end_line`. Exit 1 if the id is not found.
|
|
501
|
+
- **`set <id>`** — replaces the body of an existing **`draft`/`proposed`**
|
|
502
|
+
record in place; the surrounding fence and `---` markers are
|
|
503
|
+
preserved. The body comes from `--from-file <path>` or `--content
|
|
504
|
+
<string>`, supplied either bare (as `get` emits) or wrapped in a
|
|
505
|
+
```` ```yaml ```` fence — both are normalised.
|
|
506
|
+
- **`add --after <id>`** — inserts a new ```` ```yaml ````-fenced record
|
|
507
|
+
immediately after the anchor's fence. The body's `id` must be new and
|
|
508
|
+
its status `draft`/`proposed`.
|
|
509
|
+
|
|
510
|
+
`set`/`add` **refuse `approved`/`deprecated`/`removed` records** (exit
|
|
511
|
+
1) — changing a governed record is a `Delta` + `sdd approve`/`sdd
|
|
512
|
+
finalize` job. The write is atomic (temp-file + rename) and touches
|
|
513
|
+
only the `lint.spec_files` file that holds the record; everything else
|
|
514
|
+
in the file, plus `.sdd/config.json` and `.git/`, is left byte-identical
|
|
515
|
+
(`INV-015`). `list`/`get` never write at all (`INV-002`). Run `sdd lint`
|
|
516
|
+
after any `set`/`add` — the body is spliced verbatim, so lint remains
|
|
517
|
+
the structural gate.
|
|
518
|
+
|
|
519
|
+
**Exit codes**: 0 success · 1 `record-not-found` / `anchor-not-found` /
|
|
520
|
+
`duplicate-id` / `record-protected` / get-miss · 2 `invalid-body`
|
|
521
|
+
(no `id:`, unparseable, both/neither input flag, or set id≠body id).
|
|
522
|
+
|
|
523
|
+
### `sdd install`
|
|
524
|
+
|
|
525
|
+
Make `sdd-cli` the distribution point for the SDD methodology rules
|
|
526
|
+
(shipped under `rules/`). Installs them into the **user-level** agent
|
|
527
|
+
config so any project can follow the discipline.
|
|
528
|
+
|
|
529
|
+
```sh
|
|
530
|
+
sdd install all # both targets below
|
|
531
|
+
sdd install claude # ~/.claude
|
|
532
|
+
sdd install codex # ~/.codex
|
|
533
|
+
sdd install all --dry-run # print the planned file ops, write nothing
|
|
534
|
+
sdd install claude --format=json
|
|
535
|
+
sdd install all --scope project # write into THIS repo instead of the user home
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
`--scope user|project` (default `user`) selects the destination root:
|
|
539
|
+
|
|
540
|
+
- **`user`** (default) — byte-identical to before: writes under
|
|
541
|
+
`~/.claude` and `~/.codex` (`$SDD_INSTALL_HOME` overrides the home root).
|
|
542
|
+
- **`project`** — writes the agent config into the current working
|
|
543
|
+
directory so a repo can carry the SDD setup for the whole team:
|
|
544
|
+
`./CLAUDE.md` and `./AGENTS.md` at the repo root, with rules, skills,
|
|
545
|
+
and `settings.json` under `./.claude/**` and `./.codex/**`. Project-scope
|
|
546
|
+
`settings.json` hook commands use `$CLAUDE_PROJECT_DIR/.claude/sdd/...`
|
|
547
|
+
so a committed `settings.json` is portable across machines. Project
|
|
548
|
+
scope writes only that agent-config set — never `spec/*.md`,
|
|
549
|
+
`.sdd/config.json`, `.git`, or your source (`INV-016` / `POL-003` /
|
|
550
|
+
`POL-001`).
|
|
551
|
+
|
|
552
|
+
What lands per target:
|
|
553
|
+
|
|
554
|
+
- **`claude`** — the minimal TDD+SDD context rules are copied to
|
|
555
|
+
`~/.claude/sdd/` and `@import`-ed from a managed block in
|
|
556
|
+
`~/.claude/CLAUDE.md`; the full reference is installed as an on-demand
|
|
557
|
+
skill at `~/.claude/skills/spec-driven-development/SKILL.md`; and two
|
|
558
|
+
`PreToolUse` hooks are merged into `~/.claude/settings.json` — a lint
|
|
559
|
+
reminder and a **spec-read guard** that denies reading `spec/*.md` in
|
|
560
|
+
any project carrying `.sdd/config.json` (forcing `sdd record` instead).
|
|
561
|
+
- **`codex`** — every rule is copied to `~/.codex/sdd/` and listed in a
|
|
562
|
+
managed block in `~/.codex/AGENTS.md` (Codex has no `@import` / hook
|
|
563
|
+
host, so hooks are reported as skipped).
|
|
564
|
+
|
|
565
|
+
The artifact set is data-driven by `rules/manifest.json` (`CST-008`).
|
|
566
|
+
Install is **idempotent** (managed blocks are replaced in place, hook
|
|
567
|
+
entries deduped by matcher+command, pre-existing user hooks preserved)
|
|
568
|
+
and the only command that writes outside the repo: only under the agent
|
|
569
|
+
home roots, never inside `<repo_root>` (`INV-016` / `POL-003`).
|
|
570
|
+
`$SDD_INSTALL_HOME` overrides the home root.
|
|
571
|
+
|
|
572
|
+
| Exit | Reason | Meaning |
|
|
573
|
+
|------|---------------------|------------------------------------------------------|
|
|
574
|
+
| 0 | — | Install (or `--dry-run` plan) completed. |
|
|
575
|
+
| 1 | `manifest-missing` / `manifest-invalid` / `artifact-missing` | A packaged rule file or the manifest could not be read; nothing is written. |
|
|
576
|
+
| 2 | — | argv error (missing/unknown target, unknown flag). |
|
|
577
|
+
|
|
578
|
+
### Output formats summary
|
|
579
|
+
|
|
580
|
+
| Subcommand | `human` | `json` | `yaml` |
|
|
581
|
+
|---------------|----------------|--------|--------|
|
|
582
|
+
| `sdd token` | yes (default) | yes | — |
|
|
583
|
+
| `sdd check` | yes (default) | yes | — |
|
|
584
|
+
| `sdd refresh` | yes | yes | yes (default) |
|
|
585
|
+
| `sdd lint` | yes (default) | yes | — |
|
|
586
|
+
| `sdd approve` | yes (default) | yes | — |
|
|
587
|
+
| `sdd ready` | yes (default) | yes | — |
|
|
588
|
+
| `sdd record` | yes (default) | yes | — |
|
|
589
|
+
| `sdd install` | yes (default) | yes | — |
|
|
590
|
+
|
|
591
|
+
JSON outputs carry `format_version: 1` and are stable per the
|
|
592
|
+
contracts in `spec/spec.md` §7. Human-format output is a one-line
|
|
593
|
+
summary plus indented detail; it omits the `emitted_at` timestamp.
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## Exit code taxonomy
|
|
598
|
+
|
|
599
|
+
```
|
|
600
|
+
0 clean / success
|
|
601
|
+
1 drift (baseline-stale OR baseline-dirty); refresh-with-stubs is NOT 1
|
|
602
|
+
2 configuration error
|
|
603
|
+
3 environment error
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
| Code | Reason | Where it can come from |
|
|
607
|
+
|------|---------------------------------|----------------------------------------------------|
|
|
608
|
+
| 0 | — | Successful run. |
|
|
609
|
+
| 1 | `baseline-dirty` | Scope-touching uncommitted changes. |
|
|
610
|
+
| 1 | `baseline-stale` | Recorded token does not match recomputed. |
|
|
611
|
+
| 2 | `config-missing` | `.sdd/config.json` does not exist. |
|
|
612
|
+
| 2 | `config-invalid` | Schema violation, bad JSON, baseline_commit_sha unresolvable, zero-match scope glob, etc. |
|
|
613
|
+
| 2 | `baseline-block-missing` | Spec has no block with `id == config.baseline_id`. |
|
|
614
|
+
| 2 | `baseline-block-duplicate` | Spec has multiple blocks with the same `id`. |
|
|
615
|
+
| 3 | `git-not-on-path` | `git` binary not on `PATH`. |
|
|
616
|
+
| 3 | `not-a-git-repo` | cwd is not inside a git working tree. |
|
|
617
|
+
| 3 | `head-unborn` | Repo exists but `HEAD` does not resolve. |
|
|
618
|
+
| 1 | `agent-approver` | `sdd approve` refuses when `--approver` is an agent identity. |
|
|
619
|
+
| 1 | `invalid-owner-role` | `sdd approve` refuses an unknown `--owner-role`. |
|
|
620
|
+
| 1 | `no-id-match` | `sdd approve` refuses when `--id`/glob matched no records. |
|
|
621
|
+
|
|
622
|
+
Reasons are stable strings — downstream tooling can pin against them.
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## Token mechanism — `git_tree_hash_v1`
|
|
627
|
+
|
|
628
|
+
```
|
|
629
|
+
1. git diff --quiet HEAD -- <scope> # if non-zero -> baseline-dirty (exit 1)
|
|
630
|
+
2. git ls-tree HEAD -- <scope> # capture stdout bytes verbatim
|
|
631
|
+
3. token = hex(sha256(stdout_bytes))
|
|
632
|
+
4. commit_sha = trim(stdout of `git rev-parse HEAD`)
|
|
633
|
+
5. emit { token, commit_sha, mechanism, scope }
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Determinism comes from git's canonical `ls-tree` output: for a fixed
|
|
637
|
+
commit and pathspec set, the bytes are identical across invocations on
|
|
638
|
+
the same git version family. Reordering scope entries does not change
|
|
639
|
+
the token, because git canonicalises by name.
|
|
640
|
+
|
|
641
|
+
The set of git subcommands used by `sdd-cli` is a strict allowlist:
|
|
642
|
+
`diff --quiet HEAD`, `ls-tree HEAD`, `rev-parse HEAD`,
|
|
643
|
+
`rev-parse --is-inside-work-tree`, `diff --name-only baseline..HEAD`,
|
|
644
|
+
`status --porcelain`. No state-mutating subcommand is ever invoked
|
|
645
|
+
(see `spec/spec.md` POL-002).
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
## Workflow at a glance
|
|
650
|
+
|
|
651
|
+
Two views on the same loop: a flowchart for the full lifecycle, and a
|
|
652
|
+
lookup table for "I know my situation, just give me the command".
|
|
653
|
+
Detailed step-by-step scenarios live in [Typical workflows](#typical-workflows)
|
|
654
|
+
below.
|
|
655
|
+
|
|
656
|
+
### The SDD loop
|
|
657
|
+
|
|
658
|
+
```mermaid
|
|
659
|
+
flowchart TD
|
|
660
|
+
classDef cmd fill:#e3f2fd,stroke:#1565c0,color:#0d47a1
|
|
661
|
+
classDef human fill:#fff8e1,stroke:#f57f17,color:#5d4037
|
|
662
|
+
classDef ok fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20
|
|
663
|
+
classDef gate fill:#f3e5f5,stroke:#6a1b9a,color:#311b92
|
|
664
|
+
|
|
665
|
+
S([SDD repo]):::ok
|
|
666
|
+
|
|
667
|
+
S --> Q1{BL block has a real<br/>freshness_token?}
|
|
668
|
+
|
|
669
|
+
Q1 -- "no — fresh repo" --> B1["sdd token --format=json"]:::cmd
|
|
670
|
+
B1 --> B2[paste token + commit_sha<br/>into the BL block]:::human
|
|
671
|
+
B2 --> B3["sdd approve<br/>--id partition:BL-NNN ..."]:::cmd
|
|
672
|
+
B3 --> Q2
|
|
673
|
+
|
|
674
|
+
Q1 -- "yes" --> Q2{routine check<br/>or CI gate?}
|
|
675
|
+
|
|
676
|
+
Q2 -- "CI / pre-merge" --> RDY["sdd ready"]:::gate
|
|
677
|
+
Q2 -- "introspect" --> L["sdd lint"]:::cmd
|
|
678
|
+
Q2 -- "introspect" --> C["sdd check"]:::cmd
|
|
679
|
+
|
|
680
|
+
RDY -- "exit 0" --> OK([all green]):::ok
|
|
681
|
+
RDY -- "exit 1 — uncovered / unapproved /<br/>orphan_covers / unknown_partition_covers /<br/>aggregated_lint / aggregated_check / ..." --> RDYF[add `@covers <id>` next to a test /<br/>move proposed IDs into sandbox_paths /<br/>fix the upstream lint/check blocker]:::human
|
|
682
|
+
RDYF --> RDY
|
|
683
|
+
RDY -- "exit 2 — config_invalid /<br/>spec_parse_failed /<br/>unreadable_test_paths" --> RDYC[fix .sdd/config.json or<br/>spec.md syntax, retry]:::human
|
|
684
|
+
RDYC --> RDY
|
|
685
|
+
|
|
686
|
+
L -- "exit 1" --> LF[fix weasel words /<br/>missing approval_record /<br/>missing test_obligation / ...]:::human
|
|
687
|
+
LF --> L
|
|
688
|
+
L -- "exit 0" --> OK
|
|
689
|
+
|
|
690
|
+
C -- "exit 0" --> OK
|
|
691
|
+
C -- "exit 1 — baseline-dirty" --> CD[commit or stash<br/>scope-touching edits]:::human
|
|
692
|
+
CD --> C
|
|
693
|
+
C -- "exit 1 — baseline-stale" --> R["sdd refresh > stubs.yaml"]:::cmd
|
|
694
|
+
R --> RS[fill Delta / Open-Q stubs,<br/>edit spec.md, commit]:::human
|
|
695
|
+
RS --> RT["sdd token<br/>paste new token + sha<br/>into BL block"]:::cmd
|
|
696
|
+
RT --> C
|
|
697
|
+
|
|
698
|
+
OK --> Q3{proposed ID got<br/>human sign-off?}
|
|
699
|
+
Q3 -- "yes" --> A["sdd approve --id ...<br/>--approver alice<br/>--owner-role ...<br/>--change-request ..."]:::cmd
|
|
700
|
+
A --> RDY
|
|
701
|
+
Q3 -- "no" --> Q4{cutting a release?}
|
|
702
|
+
Q4 -- "yes" --> RE([release gate:<br/>sdd ready exit 0]):::ok
|
|
703
|
+
Q4 -- "no" --> END([continue work]):::ok
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
Read the chart in four layers:
|
|
707
|
+
|
|
708
|
+
1. **Bootstrap** (left branch off `Q1`) — one-time, when the
|
|
709
|
+
Brownfield-baseline block still has placeholder values. Compute the
|
|
710
|
+
token, paste it in, approve the BL record with a human identity,
|
|
711
|
+
confirm `sdd check` is green.
|
|
712
|
+
2. **CI / pre-merge** (`Q2 → RDY`) — `sdd ready` is the single
|
|
713
|
+
authoritative gate. It is a strict superset of `sdd lint` and
|
|
714
|
+
`sdd check`: re-runs both under one JSON envelope, plus enforces
|
|
715
|
+
marker coverage (`@covers <id>`), sandbox isolation for `proposed`
|
|
716
|
+
IDs, and `compatibility_action=…` markers for `removed` IDs. Add
|
|
717
|
+
`sdd ready` to your protected-branch policy and you do not need
|
|
718
|
+
to wire `sdd lint` and `sdd check` separately.
|
|
719
|
+
3. **Introspection** (`Q2 → L` / `Q2 → C`) — when you want a focused
|
|
720
|
+
answer to one question (just spec rules, just scope freshness),
|
|
721
|
+
the dedicated commands are still useful for narrowing diagnostics.
|
|
722
|
+
4. **Drift response** (right branch off `C`) — when `sdd check`
|
|
723
|
+
reports `baseline-stale`, `sdd refresh` emits one stub per drifted
|
|
724
|
+
path. After a human fills the stubs and updates the spec, recompute
|
|
725
|
+
the token with `sdd token` and re-record it in the BL block.
|
|
726
|
+
|
|
727
|
+
Approval (`A`) is human-only by design (SDD §7.5: `sdd approve`
|
|
728
|
+
refuses agent identities). It is a transition from `proposed` to
|
|
729
|
+
`approved` on a normative ID, never a way to bypass `sdd ready` (or
|
|
730
|
+
`sdd lint` / `sdd check` underneath).
|
|
731
|
+
|
|
732
|
+
### When to run which command
|
|
733
|
+
|
|
734
|
+
| Situation | Command(s) |
|
|
735
|
+
|-----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
736
|
+
| Fresh repo, BrownfieldBaseline still has placeholders | `sdd token` → paste token + commit_sha → `sdd approve --id <part>:BL-NNN ...` → `sdd check` |
|
|
737
|
+
| **Pre-merge / pre-deploy CI gate** | **`sdd ready`** (strict superset of `sdd lint` + `sdd check` — single command, one JSON envelope) |
|
|
738
|
+
| **Pre-release sanity check** | **`sdd ready`** |
|
|
739
|
+
| Introspect: does the spec follow SDD rules? | `sdd lint` |
|
|
740
|
+
| Introspect: did anything in scope drift since baseline? | `sdd check` |
|
|
741
|
+
| `sdd ready` flags `[uncovered]` | add `// @covers <partition>:<id>` next to a test that closes the obligation, or set `Test obligation: not_applicable + reason` in the spec |
|
|
742
|
+
| `sdd ready` flags `[unapproved]` | promote via `sdd approve …` (with a human identity), or move the proposed ID's spec file into `partitions[*].sandbox_paths` |
|
|
743
|
+
| `sdd ready` flags `[unknown_partition_covers]` | add the partition to `.sdd/config.json#partitions`, or fix the marker prefix on the offending line |
|
|
744
|
+
| `sdd check` reports `baseline-dirty` | `git commit` or `git stash` your scope-touching working-tree edits, then re-run `sdd check` (or `sdd ready`) |
|
|
745
|
+
| `sdd check` reports `baseline-stale` | `sdd refresh > stubs.yaml` → fill `Delta` / `Open-Q` stubs into the spec → commit → `sdd token` → paste fresh token + commit_sha → `sdd check` |
|
|
746
|
+
| Reviewer signed off on a `proposed` ID | `sdd approve --id ... --approver <human> --owner-role ... --change-request <url>` → `sdd ready` |
|
|
747
|
+
| Inspect the current scope token without touching the spec | `sdd token` (or `sdd token --format=json` for piping) |
|
|
748
|
+
|
|
749
|
+
> All commands are read-only on the spec **except `sdd approve`**,
|
|
750
|
+
> which atomically rewrites `lifecycle.status` + `approval_record`
|
|
751
|
+
> (INV-002 / INV-007). `sdd refresh` writes only to stdout — apply its
|
|
752
|
+
> stubs by hand.
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
## Typical workflows
|
|
757
|
+
|
|
758
|
+
### 1 — bootstrapping a new SDD baseline
|
|
759
|
+
|
|
760
|
+
You have a repo with a spec, but no `freshness_token` yet.
|
|
761
|
+
|
|
762
|
+
```sh
|
|
763
|
+
# 1. add config + an empty BrownfieldBaseline block in spec.md.
|
|
764
|
+
# leave freshness_token / baseline_commit_sha as placeholders.
|
|
765
|
+
|
|
766
|
+
# 2. compute the real token at the current HEAD.
|
|
767
|
+
sdd token --format=json
|
|
768
|
+
# {"token":"<TOKEN>","commit_sha":"<SHA>", ... }
|
|
769
|
+
|
|
770
|
+
# 3. paste TOKEN and SHA into the BL-001 block in spec.md.
|
|
771
|
+
# commit. add a non-agent approval_record to BL-001.
|
|
772
|
+
|
|
773
|
+
# 4. confirm the baseline is consistent.
|
|
774
|
+
sdd check
|
|
775
|
+
# exit 0
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
### 2 — daily / CI gate
|
|
779
|
+
|
|
780
|
+
Wire `sdd check` into the gate that decides whether code is allowed to
|
|
781
|
+
move from `spec-valid` to `implementation-valid`.
|
|
782
|
+
|
|
783
|
+
```yaml
|
|
784
|
+
# example GitHub Actions step
|
|
785
|
+
- run: npx sdd check
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
If `sdd check` exits 1, either:
|
|
789
|
+
|
|
790
|
+
- the working tree is dirty (commit your changes), or
|
|
791
|
+
- a scope-touching commit landed since the recorded baseline (run
|
|
792
|
+
`sdd refresh` and update the spec).
|
|
793
|
+
|
|
794
|
+
### 3 — a scope-touching change has landed
|
|
795
|
+
|
|
796
|
+
After committing a code change, `sdd check` reports
|
|
797
|
+
`baseline-stale`. You now know the spec needs an update — but the
|
|
798
|
+
spec is the source of truth, so you cannot just re-record the new
|
|
799
|
+
token. Instead:
|
|
800
|
+
|
|
801
|
+
```sh
|
|
802
|
+
sdd refresh > /tmp/stubs.yaml
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
For every changed path, the CLI emits exactly one stub:
|
|
806
|
+
|
|
807
|
+
- a `Delta` stub if the path lives inside an existing IMP footprint —
|
|
808
|
+
fill in `compatibility_action`, `kind_of_change`, and the test
|
|
809
|
+
references, then add the stub to your spec's `Deltas` section;
|
|
810
|
+
- an `Open-Q` stub if the path is in scope but no IMP claims it —
|
|
811
|
+
decide whether to bind it to an existing/new normative id, or to
|
|
812
|
+
leave it unmodeled.
|
|
813
|
+
|
|
814
|
+
After the spec edits land, recompute the token:
|
|
815
|
+
|
|
816
|
+
```sh
|
|
817
|
+
sdd token --format=json | jq -r .token # paste into BL-001.freshness_token
|
|
818
|
+
sdd token --format=json | jq -r .commit_sha # paste into BL-001.baseline_commit_sha
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
…and `sdd check` is green again.
|
|
822
|
+
|
|
823
|
+
### 4 — `sdd ready` as the single CI gate
|
|
824
|
+
|
|
825
|
+
`sdd ready` is the one command CI should call. It is a strict superset
|
|
826
|
+
of `sdd lint` and `sdd check`: re-runs both under a single JSON
|
|
827
|
+
envelope (kinds `aggregated_lint` / `aggregated_check`), and on top
|
|
828
|
+
adds gate-3 (`implementation-valid`) checks — every
|
|
829
|
+
`approved`/`deprecated` normative ID must have ≥ 1 test annotated
|
|
830
|
+
`@covers <partition>:<id>`, every `removed` ID must have a matching
|
|
831
|
+
`compatibility_action=…` marker, no `proposed`/`draft` ID may live
|
|
832
|
+
outside `partitions[*].sandbox_paths`, and orphan/unknown-partition
|
|
833
|
+
markers surface as `[orphan_covers]` / `[unknown_partition_covers]`.
|
|
834
|
+
|
|
835
|
+
```yaml
|
|
836
|
+
- run: npx sdd ready
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
Wiring `sdd ready` into a protected-branch policy is what makes the
|
|
840
|
+
SDD three-gate contract enforceable in practice. You no longer need
|
|
841
|
+
to wire `sdd lint` and `sdd check` separately — both run inside
|
|
842
|
+
`sdd ready`. They remain useful for narrowing diagnostics during
|
|
843
|
+
local development.
|
|
844
|
+
|
|
845
|
+
`sdd ready` exits 0 (mergeable), 1 (blocker — see the violation
|
|
846
|
+
list), or 2 (`config_invalid` / `spec_parse_failed` /
|
|
847
|
+
`unreadable_test_paths`, i.e. the gate could not even evaluate).
|
|
848
|
+
|
|
849
|
+
### 5 — promoting a `proposed` ID to `approved`
|
|
850
|
+
|
|
851
|
+
When a human reviewer signs off on an ID (a Behavior, Contract,
|
|
852
|
+
Invariant, Surface, etc.), they switch its lifecycle from `proposed`
|
|
853
|
+
to `approved` and stamp the typed `approval_record` block. `sdd
|
|
854
|
+
approve` does this in one atomic edit and refuses agent identities
|
|
855
|
+
(SDD §7.5: self-approval is forbidden).
|
|
856
|
+
|
|
857
|
+
```sh
|
|
858
|
+
sdd approve \
|
|
859
|
+
--id "my-partition:BEH-014" \
|
|
860
|
+
--approver alice \
|
|
861
|
+
--owner-role tech-lead \
|
|
862
|
+
--change-request "https://example.com/pr/42"
|
|
863
|
+
|
|
864
|
+
# `sdd approve` rewrites:
|
|
865
|
+
# lifecycle.status: approved
|
|
866
|
+
# approval_record:
|
|
867
|
+
# owner_role: tech-lead
|
|
868
|
+
# approver_identity: alice
|
|
869
|
+
# timestamp: 2026-04-30T10:15:42.001Z
|
|
870
|
+
# change_request: https://example.com/pr/42
|
|
871
|
+
# scope: first-time-approval
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
If `--approver` is in the built-in agent blocklist (e.g. `claude`,
|
|
875
|
+
`codex`, `bot:tg-1`, `sdd-cli` itself) the command exits 1 with reason
|
|
876
|
+
`agent-approver` and writes nothing.
|
|
877
|
+
|
|
878
|
+
After approval, run `sdd ready` to verify the record now passes
|
|
879
|
+
`sdd:approval-record-required` and that gate-3 (the test annotated
|
|
880
|
+
`@covers` for the just-approved ID) is in place.
|
|
881
|
+
|
|
882
|
+
### 6 — confirming a release
|
|
883
|
+
|
|
884
|
+
Right before tagging a release, `sdd ready` should be exit 0. That
|
|
885
|
+
single signal means: spec rules pass, the recorded baseline is fresh,
|
|
886
|
+
every approved ID has a `@covers` test, no `proposed`/`draft` ID
|
|
887
|
+
slipped outside `sandbox_paths`, and the working tree is clean.
|
|
888
|
+
Releases without that signal break SDD's invariant that "spec is the
|
|
889
|
+
source of truth".
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
## Architecture
|
|
894
|
+
|
|
895
|
+
`sdd-cli` follows Vertical Slice + Hexagonal architecture. Each
|
|
896
|
+
command (`token`, `check`, `refresh`, `lint`, `approve`, `ready`) owns
|
|
897
|
+
its own slice with local domain, application, ports, and adapters. The
|
|
898
|
+
composition root is `src/cli.ts`.
|
|
899
|
+
|
|
900
|
+
```
|
|
901
|
+
src/
|
|
902
|
+
cli.ts # argv router / DI
|
|
903
|
+
features/
|
|
904
|
+
token/
|
|
905
|
+
domain/ # —
|
|
906
|
+
application/ # ComputeToken
|
|
907
|
+
ports/{inbound,outbound}/
|
|
908
|
+
adapters/{inbound,outbound}/ # CliTokenHandler, ChildProcessTokenGit, NodeTokenConfigReader
|
|
909
|
+
check/
|
|
910
|
+
domain/ # BaselineComparison
|
|
911
|
+
application/ # CheckBaseline
|
|
912
|
+
ports/{inbound,outbound}/
|
|
913
|
+
adapters/{inbound,outbound}/
|
|
914
|
+
refresh/
|
|
915
|
+
domain/ # Footprint, DiffStubs
|
|
916
|
+
application/ # BuildRefreshStubs
|
|
917
|
+
ports/{inbound,outbound}/
|
|
918
|
+
adapters/{inbound,outbound}/
|
|
919
|
+
lint/
|
|
920
|
+
domain/ # Diagnostic, Record, SpecParser, Rules
|
|
921
|
+
application/ # RunLint
|
|
922
|
+
ports/{inbound,outbound}/
|
|
923
|
+
adapters/{inbound,outbound}/
|
|
924
|
+
approve/
|
|
925
|
+
domain/ # ApproveRequest (incl. BUILTIN_AGENT_BLOCKLIST), Rewrite
|
|
926
|
+
application/ # ApplyApproval
|
|
927
|
+
ports/{inbound,outbound}/
|
|
928
|
+
adapters/{inbound,outbound}/
|
|
929
|
+
ready/
|
|
930
|
+
domain/ # MarkerParser (CST-007), PartitionResolver, Rules (8 rule fns)
|
|
931
|
+
application/ # RunReady — strict superset of lint + check
|
|
932
|
+
ports/{inbound,outbound}/
|
|
933
|
+
adapters/{inbound,outbound}/
|
|
934
|
+
shared/
|
|
935
|
+
domain/ # Config (incl. LintConfig + partitions), Token, SpecBlocks,
|
|
936
|
+
# Scope, CliOutput, Errors, PartitionGrammar (CST-007 source of truth),
|
|
937
|
+
# SpecRecord, LintReport, LintRules, CheckOutcome
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
Cross-feature imports are forbidden and enforced by
|
|
941
|
+
`tests/unit/layer-imports.test.ts` (per `INV-004`). Shared primitives
|
|
942
|
+
live only under `src/shared/domain`.
|
|
943
|
+
|
|
944
|
+
---
|
|
945
|
+
|
|
946
|
+
## Development
|
|
947
|
+
|
|
948
|
+
```sh
|
|
949
|
+
git clone <repo>
|
|
950
|
+
cd sdd-cli
|
|
951
|
+
npm install
|
|
952
|
+
|
|
953
|
+
npm run tsc # type-check (no emit)
|
|
954
|
+
npm run test:unit
|
|
955
|
+
npm run test:integration
|
|
956
|
+
npm run build # tsc + chmod +x dist/cli.js
|
|
957
|
+
node dist/cli.js --help
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
The integration suite spins up tmp git repos and runs the built CLI.
|
|
961
|
+
`tests/integration/git-shim-allowlist.test.ts` enforces POL-002 (only
|
|
962
|
+
EXT-001-allowlisted git subcommands), and
|
|
963
|
+
`tests/integration/fs-readonly.test.ts` enforces INV-002 / POL-001
|
|
964
|
+
(spec, config, and git refs/objects unchanged after each run).
|
|
965
|
+
|
|
966
|
+
`tests/integration/package-bin.test.ts` runs `npm pack` end-to-end and
|
|
967
|
+
installs the tarball into a fresh consumer to verify the `bin` wiring
|
|
968
|
+
(CTR-007). Allow ~2 minutes for that test on the first run.
|
|
969
|
+
|
|
970
|
+
---
|
|
971
|
+
|
|
972
|
+
## Limits / out of scope (v0.3.0)
|
|
973
|
+
|
|
974
|
+
- npm-registry publication of `agent-sdd`.
|
|
975
|
+
- Other token mechanisms (`sha256_of_concat`, `git_tag_based`).
|
|
976
|
+
- A scaffolding command (`sdd init`).
|
|
977
|
+
- Auto-application of `sdd refresh` stubs back into `spec.md`
|
|
978
|
+
(forbidden by INV-002).
|
|
979
|
+
- Localised output / message catalogs.
|
|
980
|
+
- Lint/aggregated diagnostic on `@covers` near-misses (e.g. uppercase
|
|
981
|
+
in partition prefix). v0.3.0 silently skips them — see
|
|
982
|
+
[`OQ-017`](spec/spec.md) for the deferred decision.
|
|
983
|
+
|
|
984
|
+
`sdd lint` shipped in v0.2.0 and `sdd ready` shipped in v0.3.0 — both
|
|
985
|
+
are no longer out of scope. `sdd record` (read-only `list`/`get`, plus
|
|
986
|
+
`set`/`add` writing a single draft/proposed record) is the one
|
|
987
|
+
sanctioned writer of `spec.md` — the blanket "no auto-write" above is
|
|
988
|
+
specifically about `sdd refresh` stubs; record writes are governed by
|
|
989
|
+
`INV-015`. `sdd install` (`SUR-016`) is in scope and distinct from the
|
|
990
|
+
out-of-scope `sdd init` scaffolding: it writes the methodology rules and
|
|
991
|
+
Claude hooks into the user-level agent config, never into the repo
|
|
992
|
+
working tree (`INV-016` / `POL-003`).
|
|
993
|
+
|
|
994
|
+
---
|
|
995
|
+
|
|
996
|
+
## Documents in this repo
|
|
997
|
+
|
|
998
|
+
| File | Purpose |
|
|
999
|
+
|-------------------|--------------------------------------------------------------------------------|
|
|
1000
|
+
| `spec/spec.md` | The normative specification — single source of truth. |
|
|
1001
|
+
| `README.md` | Consumer manual (this file). |
|
|
1002
|
+
| `CHANGELOG.md` | Release notes per version, mapped to spec IDs. |
|
|
1003
|
+
| `RELEASING.md` | How to cut a release and publish to npm. |
|
|
1004
|
+
| `CLAUDE.md` | Project-specific instructions for Claude Code agents. |
|
|
1005
|
+
| `AGENTS.md` | Repo-rooted, agent-agnostic rules of the road for any AI coding agent. |
|
|
1006
|
+
| `LICENSE` | MIT. |
|
|
1007
|
+
| `schema/sdd.config.schema.json` | Published JSON Schema for `.sdd/config.json`. |
|
|
1008
|
+
|
|
1009
|
+
---
|
|
1010
|
+
|
|
1011
|
+
## Contributing
|
|
1012
|
+
|
|
1013
|
+
This is a personal tool published for reuse. PRs are welcome but the
|
|
1014
|
+
SDD discipline is enforced — every behavior change requires a
|
|
1015
|
+
corresponding spec update in the same PR, `sdd lint` must exit 0, and
|
|
1016
|
+
`sdd approve` is human-only (the CLI refuses agent identities). See
|
|
1017
|
+
`AGENTS.md` for the rules an AI coding agent must follow when working
|
|
1018
|
+
in this repo.
|
|
1019
|
+
|
|
1020
|
+
---
|
|
1021
|
+
|
|
1022
|
+
## Specification
|
|
1023
|
+
|
|
1024
|
+
The full normative specification — Surfaces, Behaviors, Contracts,
|
|
1025
|
+
Invariants, Policies, Constraints, External dependencies, Generated
|
|
1026
|
+
artefacts, Implementation bindings, Open questions, Assumptions — is
|
|
1027
|
+
in `spec/spec.md`. If a behavior surprises you, that file is the
|
|
1028
|
+
source of truth, and any divergence between code and spec is a bug.
|