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.
Files changed (304) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1028 -0
  3. package/README.ru.md +1046 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.js +867 -0
  6. package/dist/features/approve/adapters/inbound/CliApproveHandler.d.ts +17 -0
  7. package/dist/features/approve/adapters/inbound/CliApproveHandler.js +108 -0
  8. package/dist/features/approve/adapters/outbound/NodeApproveFileSystem.d.ts +8 -0
  9. package/dist/features/approve/adapters/outbound/NodeApproveFileSystem.js +147 -0
  10. package/dist/features/approve/adapters/outbound/NodePlanFileWriter.d.ts +6 -0
  11. package/dist/features/approve/adapters/outbound/NodePlanFileWriter.js +92 -0
  12. package/dist/features/approve/adapters/outbound/SystemApproveClock.d.ts +4 -0
  13. package/dist/features/approve/adapters/outbound/SystemApproveClock.js +5 -0
  14. package/dist/features/approve/application/ApplyApproval.d.ts +19 -0
  15. package/dist/features/approve/application/ApplyApproval.js +30 -0
  16. package/dist/features/approve/application/WriteAttestation.d.ts +19 -0
  17. package/dist/features/approve/application/WriteAttestation.js +23 -0
  18. package/dist/features/approve/domain/ApproveRequest.d.ts +24 -0
  19. package/dist/features/approve/domain/ApproveRequest.js +24 -0
  20. package/dist/features/approve/domain/Rewrite.d.ts +1 -0
  21. package/dist/features/approve/domain/Rewrite.js +6 -0
  22. package/dist/features/approve/ports/inbound/ApproveCommand.d.ts +10 -0
  23. package/dist/features/approve/ports/inbound/ApproveCommand.js +1 -0
  24. package/dist/features/approve/ports/outbound/ApproveClock.d.ts +3 -0
  25. package/dist/features/approve/ports/outbound/ApproveClock.js +1 -0
  26. package/dist/features/approve/ports/outbound/ApproveConfigPort.d.ts +4 -0
  27. package/dist/features/approve/ports/outbound/ApproveConfigPort.js +1 -0
  28. package/dist/features/approve/ports/outbound/ApproveFileSystem.d.ts +8 -0
  29. package/dist/features/approve/ports/outbound/ApproveFileSystem.js +1 -0
  30. package/dist/features/approve/ports/outbound/PlanFileWriter.d.ts +13 -0
  31. package/dist/features/approve/ports/outbound/PlanFileWriter.js +1 -0
  32. package/dist/features/check/adapters/inbound/CliCheckHandler.d.ts +8 -0
  33. package/dist/features/check/adapters/inbound/CliCheckHandler.js +62 -0
  34. package/dist/features/check/adapters/outbound/ChildProcessCheckGit.d.ts +8 -0
  35. package/dist/features/check/adapters/outbound/ChildProcessCheckGit.js +112 -0
  36. package/dist/features/check/adapters/outbound/NodeCheckFileReader.d.ts +7 -0
  37. package/dist/features/check/adapters/outbound/NodeCheckFileReader.js +44 -0
  38. package/dist/features/check/application/CheckBaseline.d.ts +28 -0
  39. package/dist/features/check/application/CheckBaseline.js +54 -0
  40. package/dist/features/check/domain/BaselineComparison.d.ts +1 -0
  41. package/dist/features/check/domain/BaselineComparison.js +2 -0
  42. package/dist/features/check/ports/inbound/CheckCommand.d.ts +4 -0
  43. package/dist/features/check/ports/inbound/CheckCommand.js +1 -0
  44. package/dist/features/check/ports/outbound/CheckConfigPort.d.ts +4 -0
  45. package/dist/features/check/ports/outbound/CheckConfigPort.js +1 -0
  46. package/dist/features/check/ports/outbound/CheckGitPort.d.ts +7 -0
  47. package/dist/features/check/ports/outbound/CheckGitPort.js +1 -0
  48. package/dist/features/check/ports/outbound/CheckSpecPort.d.ts +9 -0
  49. package/dist/features/check/ports/outbound/CheckSpecPort.js +1 -0
  50. package/dist/features/doctor/adapters/inbound/CliDoctorHandler.d.ts +8 -0
  51. package/dist/features/doctor/adapters/inbound/CliDoctorHandler.js +77 -0
  52. package/dist/features/doctor/adapters/outbound/NodeRegistryReader.d.ts +5 -0
  53. package/dist/features/doctor/adapters/outbound/NodeRegistryReader.js +40 -0
  54. package/dist/features/doctor/application/RunDoctor.d.ts +30 -0
  55. package/dist/features/doctor/application/RunDoctor.js +78 -0
  56. package/dist/features/doctor/domain/RegistryRow.d.ts +23 -0
  57. package/dist/features/doctor/domain/RegistryRow.js +114 -0
  58. package/dist/features/doctor/domain/SemverRange.d.ts +7 -0
  59. package/dist/features/doctor/domain/SemverRange.js +82 -0
  60. package/dist/features/doctor/ports/inbound/DoctorCommand.d.ts +8 -0
  61. package/dist/features/doctor/ports/inbound/DoctorCommand.js +1 -0
  62. package/dist/features/doctor/ports/outbound/RegistryReader.d.ts +12 -0
  63. package/dist/features/doctor/ports/outbound/RegistryReader.js +1 -0
  64. package/dist/features/finalize/adapters/inbound/CliFinalizeHandler.d.ts +8 -0
  65. package/dist/features/finalize/adapters/inbound/CliFinalizeHandler.js +80 -0
  66. package/dist/features/finalize/adapters/outbound/NodeFinalizeFileSystem.d.ts +11 -0
  67. package/dist/features/finalize/adapters/outbound/NodeFinalizeFileSystem.js +167 -0
  68. package/dist/features/finalize/adapters/outbound/NodePlanRepo.d.ts +7 -0
  69. package/dist/features/finalize/adapters/outbound/NodePlanRepo.js +82 -0
  70. package/dist/features/finalize/adapters/outbound/SystemFinalizeClock.d.ts +4 -0
  71. package/dist/features/finalize/adapters/outbound/SystemFinalizeClock.js +5 -0
  72. package/dist/features/finalize/application/RunFinalize.d.ts +34 -0
  73. package/dist/features/finalize/application/RunFinalize.js +98 -0
  74. package/dist/features/finalize/domain/ValidateFinalizeGraph.d.ts +9 -0
  75. package/dist/features/finalize/domain/ValidateFinalizeGraph.js +86 -0
  76. package/dist/features/finalize/ports/inbound/FinalizeCommand.d.ts +7 -0
  77. package/dist/features/finalize/ports/inbound/FinalizeCommand.js +1 -0
  78. package/dist/features/finalize/ports/outbound/FinalizeClock.d.ts +3 -0
  79. package/dist/features/finalize/ports/outbound/FinalizeClock.js +1 -0
  80. package/dist/features/finalize/ports/outbound/FinalizeConfigPort.d.ts +4 -0
  81. package/dist/features/finalize/ports/outbound/FinalizeConfigPort.js +1 -0
  82. package/dist/features/finalize/ports/outbound/FinalizeFileSystem.d.ts +14 -0
  83. package/dist/features/finalize/ports/outbound/FinalizeFileSystem.js +1 -0
  84. package/dist/features/finalize/ports/outbound/PlanRepo.d.ts +21 -0
  85. package/dist/features/finalize/ports/outbound/PlanRepo.js +1 -0
  86. package/dist/features/install/adapters/inbound/CliInstallHandler.d.ts +8 -0
  87. package/dist/features/install/adapters/inbound/CliInstallHandler.js +54 -0
  88. package/dist/features/install/adapters/outbound/NodeInstallSource.d.ts +7 -0
  89. package/dist/features/install/adapters/outbound/NodeInstallSource.js +24 -0
  90. package/dist/features/install/adapters/outbound/NodeInstallTargetFs.d.ts +7 -0
  91. package/dist/features/install/adapters/outbound/NodeInstallTargetFs.js +30 -0
  92. package/dist/features/install/application/InstallRules.d.ts +10 -0
  93. package/dist/features/install/application/InstallRules.js +73 -0
  94. package/dist/features/install/domain/InstallPlan.d.ts +27 -0
  95. package/dist/features/install/domain/InstallPlan.js +168 -0
  96. package/dist/features/install/domain/InstallResult.d.ts +23 -0
  97. package/dist/features/install/domain/InstallResult.js +1 -0
  98. package/dist/features/install/domain/InstallTarget.d.ts +6 -0
  99. package/dist/features/install/domain/InstallTarget.js +7 -0
  100. package/dist/features/install/domain/ManagedBlock.d.ts +3 -0
  101. package/dist/features/install/domain/ManagedBlock.js +20 -0
  102. package/dist/features/install/domain/RuleManifest.d.ts +17 -0
  103. package/dist/features/install/domain/RuleManifest.js +69 -0
  104. package/dist/features/install/domain/SettingsMerge.d.ts +5 -0
  105. package/dist/features/install/domain/SettingsMerge.js +43 -0
  106. package/dist/features/install/ports/inbound/InstallCommand.d.ts +10 -0
  107. package/dist/features/install/ports/inbound/InstallCommand.js +1 -0
  108. package/dist/features/install/ports/outbound/InstallSource.d.ts +4 -0
  109. package/dist/features/install/ports/outbound/InstallSource.js +1 -0
  110. package/dist/features/install/ports/outbound/InstallTargetFs.d.ts +6 -0
  111. package/dist/features/install/ports/outbound/InstallTargetFs.js +1 -0
  112. package/dist/features/lint/adapters/inbound/CliLintHandler.d.ts +8 -0
  113. package/dist/features/lint/adapters/inbound/CliLintHandler.js +61 -0
  114. package/dist/features/lint/adapters/outbound/NodeLintFileReader.d.ts +7 -0
  115. package/dist/features/lint/adapters/outbound/NodeLintFileReader.js +165 -0
  116. package/dist/features/lint/application/RunLint.d.ts +10 -0
  117. package/dist/features/lint/application/RunLint.js +100 -0
  118. package/dist/features/lint/domain/Diagnostic.d.ts +1 -0
  119. package/dist/features/lint/domain/Diagnostic.js +2 -0
  120. package/dist/features/lint/domain/Record.d.ts +1 -0
  121. package/dist/features/lint/domain/Record.js +5 -0
  122. package/dist/features/lint/domain/Rules.d.ts +1 -0
  123. package/dist/features/lint/domain/Rules.js +2 -0
  124. package/dist/features/lint/domain/SpecParser.d.ts +1 -0
  125. package/dist/features/lint/domain/SpecParser.js +2 -0
  126. package/dist/features/lint/ports/inbound/LintCommand.d.ts +4 -0
  127. package/dist/features/lint/ports/inbound/LintCommand.js +1 -0
  128. package/dist/features/lint/ports/outbound/LintConfigPort.d.ts +4 -0
  129. package/dist/features/lint/ports/outbound/LintConfigPort.js +1 -0
  130. package/dist/features/lint/ports/outbound/LintFileReader.d.ts +10 -0
  131. package/dist/features/lint/ports/outbound/LintFileReader.js +1 -0
  132. package/dist/features/plan/adapters/inbound/CliPlanShowHandler.d.ts +8 -0
  133. package/dist/features/plan/adapters/inbound/CliPlanShowHandler.js +73 -0
  134. package/dist/features/plan/adapters/outbound/NodePlanReader.d.ts +7 -0
  135. package/dist/features/plan/adapters/outbound/NodePlanReader.js +68 -0
  136. package/dist/features/plan/application/ShowPlan.d.ts +7 -0
  137. package/dist/features/plan/application/ShowPlan.js +4 -0
  138. package/dist/features/plan/ports/inbound/PlanShowCommand.d.ts +7 -0
  139. package/dist/features/plan/ports/inbound/PlanShowCommand.js +1 -0
  140. package/dist/features/plan/ports/outbound/PlanConfigPort.d.ts +4 -0
  141. package/dist/features/plan/ports/outbound/PlanConfigPort.js +1 -0
  142. package/dist/features/plan/ports/outbound/PlanReader.d.ts +19 -0
  143. package/dist/features/plan/ports/outbound/PlanReader.js +1 -0
  144. package/dist/features/ready/adapters/inbound/CliReadyHandler.d.ts +8 -0
  145. package/dist/features/ready/adapters/inbound/CliReadyHandler.js +79 -0
  146. package/dist/features/ready/adapters/outbound/ChildProcessReadyGit.d.ts +9 -0
  147. package/dist/features/ready/adapters/outbound/ChildProcessReadyGit.js +113 -0
  148. package/dist/features/ready/adapters/outbound/NodeReadyFileSystem.d.ts +8 -0
  149. package/dist/features/ready/adapters/outbound/NodeReadyFileSystem.js +159 -0
  150. package/dist/features/ready/application/RunReady.d.ts +16 -0
  151. package/dist/features/ready/application/RunReady.js +572 -0
  152. package/dist/features/ready/domain/AggregatedRules.d.ts +16 -0
  153. package/dist/features/ready/domain/AggregatedRules.js +42 -0
  154. package/dist/features/ready/domain/MarkerParser.d.ts +17 -0
  155. package/dist/features/ready/domain/MarkerParser.js +108 -0
  156. package/dist/features/ready/domain/PartitionResolver.d.ts +1 -0
  157. package/dist/features/ready/domain/PartitionResolver.js +5 -0
  158. package/dist/features/ready/domain/ReadyInput.d.ts +6 -0
  159. package/dist/features/ready/domain/ReadyInput.js +1 -0
  160. package/dist/features/ready/domain/ReadyViolation.d.ts +38 -0
  161. package/dist/features/ready/domain/ReadyViolation.js +19 -0
  162. package/dist/features/ready/domain/Rules.d.ts +22 -0
  163. package/dist/features/ready/domain/Rules.js +243 -0
  164. package/dist/features/ready/domain/SpecDiff.d.ts +33 -0
  165. package/dist/features/ready/domain/SpecDiff.js +321 -0
  166. package/dist/features/ready/ports/inbound/ReadyCommand.d.ts +4 -0
  167. package/dist/features/ready/ports/inbound/ReadyCommand.js +1 -0
  168. package/dist/features/ready/ports/outbound/ReadyConfigPort.d.ts +4 -0
  169. package/dist/features/ready/ports/outbound/ReadyConfigPort.js +1 -0
  170. package/dist/features/ready/ports/outbound/ReadyFileReader.d.ts +12 -0
  171. package/dist/features/ready/ports/outbound/ReadyFileReader.js +1 -0
  172. package/dist/features/ready/ports/outbound/ReadyGitPort.d.ts +10 -0
  173. package/dist/features/ready/ports/outbound/ReadyGitPort.js +5 -0
  174. package/dist/features/record/adapters/inbound/CliRecordHandler.d.ts +10 -0
  175. package/dist/features/record/adapters/inbound/CliRecordHandler.js +111 -0
  176. package/dist/features/record/adapters/outbound/NodeRecordFileSystem.d.ts +9 -0
  177. package/dist/features/record/adapters/outbound/NodeRecordFileSystem.js +152 -0
  178. package/dist/features/record/application/AddRecord.d.ts +11 -0
  179. package/dist/features/record/application/AddRecord.js +84 -0
  180. package/dist/features/record/application/GetRecord.d.ts +8 -0
  181. package/dist/features/record/application/GetRecord.js +22 -0
  182. package/dist/features/record/application/ListRecords.d.ts +9 -0
  183. package/dist/features/record/application/ListRecords.js +24 -0
  184. package/dist/features/record/application/SetRecord.d.ts +11 -0
  185. package/dist/features/record/application/SetRecord.js +68 -0
  186. package/dist/features/record/domain/RecordBody.d.ts +12 -0
  187. package/dist/features/record/domain/RecordBody.js +66 -0
  188. package/dist/features/record/domain/RecordPartition.d.ts +1 -0
  189. package/dist/features/record/domain/RecordPartition.js +7 -0
  190. package/dist/features/record/domain/RecordSlice.d.ts +7 -0
  191. package/dist/features/record/domain/RecordSlice.js +1 -0
  192. package/dist/features/record/domain/RecordSummary.d.ts +11 -0
  193. package/dist/features/record/domain/RecordSummary.js +13 -0
  194. package/dist/features/record/domain/RecordWrite.d.ts +14 -0
  195. package/dist/features/record/domain/RecordWrite.js +8 -0
  196. package/dist/features/record/ports/inbound/RecordCommand.d.ts +19 -0
  197. package/dist/features/record/ports/inbound/RecordCommand.js +1 -0
  198. package/dist/features/record/ports/outbound/RecordConfigPort.d.ts +4 -0
  199. package/dist/features/record/ports/outbound/RecordConfigPort.js +1 -0
  200. package/dist/features/record/ports/outbound/RecordFileReader.d.ts +10 -0
  201. package/dist/features/record/ports/outbound/RecordFileReader.js +1 -0
  202. package/dist/features/record/ports/outbound/RecordFileWriter.d.ts +6 -0
  203. package/dist/features/record/ports/outbound/RecordFileWriter.js +1 -0
  204. package/dist/features/refresh/adapters/inbound/CliRefreshHandler.d.ts +8 -0
  205. package/dist/features/refresh/adapters/inbound/CliRefreshHandler.js +24 -0
  206. package/dist/features/refresh/adapters/outbound/ChildProcessRefreshGit.d.ts +8 -0
  207. package/dist/features/refresh/adapters/outbound/ChildProcessRefreshGit.js +118 -0
  208. package/dist/features/refresh/adapters/outbound/NodeRefreshFileReader.d.ts +7 -0
  209. package/dist/features/refresh/adapters/outbound/NodeRefreshFileReader.js +44 -0
  210. package/dist/features/refresh/adapters/outbound/SystemRefreshClock.d.ts +4 -0
  211. package/dist/features/refresh/adapters/outbound/SystemRefreshClock.js +5 -0
  212. package/dist/features/refresh/application/BuildRefreshStubs.d.ts +25 -0
  213. package/dist/features/refresh/application/BuildRefreshStubs.js +43 -0
  214. package/dist/features/refresh/domain/DiffStubs.d.ts +24 -0
  215. package/dist/features/refresh/domain/DiffStubs.js +81 -0
  216. package/dist/features/refresh/domain/Footprint.d.ts +14 -0
  217. package/dist/features/refresh/domain/Footprint.js +45 -0
  218. package/dist/features/refresh/ports/inbound/RefreshCommand.d.ts +4 -0
  219. package/dist/features/refresh/ports/inbound/RefreshCommand.js +1 -0
  220. package/dist/features/refresh/ports/outbound/RefreshClockPort.d.ts +3 -0
  221. package/dist/features/refresh/ports/outbound/RefreshClockPort.js +1 -0
  222. package/dist/features/refresh/ports/outbound/RefreshConfigPort.d.ts +4 -0
  223. package/dist/features/refresh/ports/outbound/RefreshConfigPort.js +1 -0
  224. package/dist/features/refresh/ports/outbound/RefreshGitPort.d.ts +7 -0
  225. package/dist/features/refresh/ports/outbound/RefreshGitPort.js +1 -0
  226. package/dist/features/refresh/ports/outbound/RefreshSpecPort.d.ts +9 -0
  227. package/dist/features/refresh/ports/outbound/RefreshSpecPort.js +1 -0
  228. package/dist/features/report/adapters/inbound/CliReportHandler.d.ts +8 -0
  229. package/dist/features/report/adapters/inbound/CliReportHandler.js +35 -0
  230. package/dist/features/report/adapters/outbound/NodeReportFileSystem.d.ts +7 -0
  231. package/dist/features/report/adapters/outbound/NodeReportFileSystem.js +128 -0
  232. package/dist/features/report/application/RunReport.d.ts +19 -0
  233. package/dist/features/report/application/RunReport.js +161 -0
  234. package/dist/features/report/ports/inbound/ReportCommand.d.ts +8 -0
  235. package/dist/features/report/ports/inbound/ReportCommand.js +1 -0
  236. package/dist/features/report/ports/outbound/ReportConfigPort.d.ts +4 -0
  237. package/dist/features/report/ports/outbound/ReportConfigPort.js +1 -0
  238. package/dist/features/report/ports/outbound/ReportFileReader.d.ts +7 -0
  239. package/dist/features/report/ports/outbound/ReportFileReader.js +1 -0
  240. package/dist/features/token/adapters/inbound/CliTokenHandler.d.ts +8 -0
  241. package/dist/features/token/adapters/inbound/CliTokenHandler.js +53 -0
  242. package/dist/features/token/adapters/outbound/ChildProcessTokenGit.d.ts +8 -0
  243. package/dist/features/token/adapters/outbound/ChildProcessTokenGit.js +112 -0
  244. package/dist/features/token/adapters/outbound/NodeTokenConfigReader.d.ts +5 -0
  245. package/dist/features/token/adapters/outbound/NodeTokenConfigReader.js +41 -0
  246. package/dist/features/token/application/ComputeToken.d.ts +18 -0
  247. package/dist/features/token/application/ComputeToken.js +27 -0
  248. package/dist/features/token/ports/inbound/TokenCommand.d.ts +4 -0
  249. package/dist/features/token/ports/inbound/TokenCommand.js +1 -0
  250. package/dist/features/token/ports/outbound/TokenConfigPort.d.ts +4 -0
  251. package/dist/features/token/ports/outbound/TokenConfigPort.js +1 -0
  252. package/dist/features/token/ports/outbound/TokenGitPort.d.ts +7 -0
  253. package/dist/features/token/ports/outbound/TokenGitPort.js +1 -0
  254. package/dist/shared/domain/AgentBlocklist.d.ts +5 -0
  255. package/dist/shared/domain/AgentBlocklist.js +28 -0
  256. package/dist/shared/domain/BoundaryReachability.d.ts +5 -0
  257. package/dist/shared/domain/BoundaryReachability.js +71 -0
  258. package/dist/shared/domain/CheckOutcome.d.ts +10 -0
  259. package/dist/shared/domain/CheckOutcome.js +7 -0
  260. package/dist/shared/domain/CliOutput.d.ts +10 -0
  261. package/dist/shared/domain/CliOutput.js +29 -0
  262. package/dist/shared/domain/Config.d.ts +29 -0
  263. package/dist/shared/domain/Config.js +201 -0
  264. package/dist/shared/domain/DiagnosticRegistry.d.ts +8 -0
  265. package/dist/shared/domain/DiagnosticRegistry.js +71 -0
  266. package/dist/shared/domain/Errors.d.ts +12 -0
  267. package/dist/shared/domain/Errors.js +23 -0
  268. package/dist/shared/domain/GlobMatch.d.ts +2 -0
  269. package/dist/shared/domain/GlobMatch.js +58 -0
  270. package/dist/shared/domain/LintReport.d.ts +16 -0
  271. package/dist/shared/domain/LintReport.js +11 -0
  272. package/dist/shared/domain/LintRules.d.ts +67 -0
  273. package/dist/shared/domain/LintRules.js +956 -0
  274. package/dist/shared/domain/PartitionGrammar.d.ts +4 -0
  275. package/dist/shared/domain/PartitionGrammar.js +16 -0
  276. package/dist/shared/domain/PlanFile.d.ts +28 -0
  277. package/dist/shared/domain/PlanFile.js +112 -0
  278. package/dist/shared/domain/Scope.d.ts +1 -0
  279. package/dist/shared/domain/Scope.js +3 -0
  280. package/dist/shared/domain/SpecApprovalRewrite.d.ts +23 -0
  281. package/dist/shared/domain/SpecApprovalRewrite.js +254 -0
  282. package/dist/shared/domain/SpecBlocks.d.ts +12 -0
  283. package/dist/shared/domain/SpecBlocks.js +96 -0
  284. package/dist/shared/domain/SpecRecord.d.ts +17 -0
  285. package/dist/shared/domain/SpecRecord.js +208 -0
  286. package/dist/shared/domain/TemplateFieldMetadata.d.ts +2 -0
  287. package/dist/shared/domain/TemplateFieldMetadata.js +177 -0
  288. package/dist/shared/domain/Token.d.ts +2 -0
  289. package/dist/shared/domain/Token.js +5 -0
  290. package/dist/shared/domain/WeaselWords.d.ts +3 -0
  291. package/dist/shared/domain/WeaselWords.js +32 -0
  292. package/package.json +71 -0
  293. package/rules/enforcement_registry.md +126 -0
  294. package/rules/hooks/sdd-lint-reminder.sh +33 -0
  295. package/rules/hooks/sdd-spec-read-guard.sh +73 -0
  296. package/rules/manifest.json +15 -0
  297. package/rules/review-sdd.md +9 -0
  298. package/rules/sdd-cli-usage.md +91 -0
  299. package/rules/skills/spec-driven-development/SKILL.md +554 -0
  300. package/rules/skills/spec-driven-development/data/weasel-words.json +22 -0
  301. package/rules/spec-driven-development.md +69 -0
  302. package/rules/tdd-sdd.md +127 -0
  303. package/rules/workflow-sdd.md +56 -0
  304. package/schema/sdd.config.schema.json +104 -0
package/README.md ADDED
@@ -0,0 +1,1028 @@
1
+ # `agent-sdd`
2
+
3
+ [![CI](https://github.com/cyberash-dev/sdd-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/cyberash-dev/sdd-cli/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ [![Node ≥ 20](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](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.