@rsaglobaltech/specgate 0.20.0 → 0.22.0

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 (273) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/README.md +31 -31
  3. package/bin/specgate.ts +25 -19
  4. package/dist/bin/specgate.js +15 -10
  5. package/dist/bin/specgate.js.map +1 -1
  6. package/dist/packages/core/src/application/AddRequirementUseCase.js +11 -1
  7. package/dist/packages/core/src/application/AddRequirementUseCase.js.map +1 -1
  8. package/dist/packages/core/src/application/ArchiveChangeUseCase.js +7 -2
  9. package/dist/packages/core/src/application/ArchiveChangeUseCase.js.map +1 -1
  10. package/dist/packages/core/src/application/ValidateProjectUseCase.js +9 -3
  11. package/dist/packages/core/src/application/ValidateProjectUseCase.js.map +1 -1
  12. package/dist/packages/core/src/domain/ArchivePlan.js.map +1 -1
  13. package/dist/packages/core/src/domain/BriefCards.js +320 -0
  14. package/dist/packages/core/src/domain/BriefCards.js.map +1 -0
  15. package/dist/packages/core/src/domain/ChangeTemplates.js +19 -0
  16. package/dist/packages/core/src/domain/ChangeTemplates.js.map +1 -1
  17. package/dist/packages/core/src/domain/DeltaSpec.js +14 -7
  18. package/dist/packages/core/src/domain/DeltaSpec.js.map +1 -1
  19. package/dist/packages/core/src/domain/DerivedMatrix.js +2 -2
  20. package/dist/packages/core/src/domain/DerivedMatrix.js.map +1 -1
  21. package/dist/packages/core/src/domain/Diagnostic.js.map +1 -1
  22. package/dist/packages/core/src/domain/DoneVerification.js +3 -3
  23. package/dist/packages/core/src/domain/DoneVerification.js.map +1 -1
  24. package/dist/packages/core/src/domain/DraftChecklist.js +6 -2
  25. package/dist/packages/core/src/domain/DraftChecklist.js.map +1 -1
  26. package/dist/packages/core/src/domain/GherkinQuality.js +5 -2
  27. package/dist/packages/core/src/domain/GherkinQuality.js.map +1 -1
  28. package/dist/packages/core/src/domain/HarnessEvents.js +129 -0
  29. package/dist/packages/core/src/domain/HarnessEvents.js.map +1 -0
  30. package/dist/packages/core/src/domain/HarnessRun.js.map +1 -1
  31. package/dist/packages/core/src/domain/Language.js +5 -2
  32. package/dist/packages/core/src/domain/Language.js.map +1 -1
  33. package/dist/packages/core/src/domain/PackContribution.js.map +1 -1
  34. package/dist/packages/core/src/domain/PackDelta.js +5 -2
  35. package/dist/packages/core/src/domain/PackDelta.js.map +1 -1
  36. package/dist/packages/core/src/domain/PackSpec.js +16 -2
  37. package/dist/packages/core/src/domain/PackSpec.js.map +1 -1
  38. package/dist/packages/core/src/domain/QuotedText.js +41 -0
  39. package/dist/packages/core/src/domain/QuotedText.js.map +1 -0
  40. package/dist/packages/core/src/domain/RequirementPlan.js.map +1 -1
  41. package/dist/packages/core/src/domain/SpecParser.js +2 -9
  42. package/dist/packages/core/src/domain/SpecParser.js.map +1 -1
  43. package/dist/packages/core/src/domain/TicketText.js +337 -0
  44. package/dist/packages/core/src/domain/TicketText.js.map +1 -0
  45. package/dist/packages/core/src/domain/TraceabilityFormat.js.map +1 -1
  46. package/dist/packages/core/src/domain/TraceabilityMatrix.js +3 -3
  47. package/dist/packages/core/src/domain/TraceabilityMatrix.js.map +1 -1
  48. package/dist/packages/core/src/domain/YamlLite.js +3 -0
  49. package/dist/packages/core/src/domain/YamlLite.js.map +1 -1
  50. package/dist/packages/core/src/infrastructure/ChangeWorkspace.js +7 -3
  51. package/dist/packages/core/src/infrastructure/ChangeWorkspace.js.map +1 -1
  52. package/dist/packages/core/src/infrastructure/ConsoleReporter.js.map +1 -1
  53. package/dist/packages/core/src/infrastructure/DiskLanguageRepository.js +1 -0
  54. package/dist/packages/core/src/infrastructure/DiskLanguageRepository.js.map +1 -1
  55. package/dist/packages/core/src/infrastructure/DiskPackDeltaRepository.js +3 -1
  56. package/dist/packages/core/src/infrastructure/DiskPackDeltaRepository.js.map +1 -1
  57. package/dist/packages/core/src/infrastructure/DiskPackRepository.js +0 -2
  58. package/dist/packages/core/src/infrastructure/DiskPackRepository.js.map +1 -1
  59. package/dist/packages/core/src/infrastructure/DiskRequirementGraphRepository.js.map +1 -1
  60. package/dist/packages/core/src/infrastructure/GitContributionStager.js +0 -4
  61. package/dist/packages/core/src/infrastructure/GitContributionStager.js.map +1 -1
  62. package/dist/packages/core/src/infrastructure/GitMergeDriver.js +0 -3
  63. package/dist/packages/core/src/infrastructure/GitMergeDriver.js.map +1 -1
  64. package/dist/packages/core/src/infrastructure/HarnessConfigFile.js +7 -13
  65. package/dist/packages/core/src/infrastructure/HarnessConfigFile.js.map +1 -1
  66. package/dist/packages/core/src/infrastructure/IdsAcrossBranches.js +48 -0
  67. package/dist/packages/core/src/infrastructure/IdsAcrossBranches.js.map +1 -0
  68. package/dist/packages/core/src/infrastructure/PackChangeDeposit.js.map +1 -1
  69. package/dist/packages/core/src/infrastructure/RemotePackResolver.js +0 -7
  70. package/dist/packages/core/src/infrastructure/RemotePackResolver.js.map +1 -1
  71. package/dist/packages/core/src/infrastructure/SpecopsConfigFile.js.map +1 -1
  72. package/dist/packages/core/test/unit/declared-artifacts.test.js +6 -2
  73. package/dist/packages/core/test/unit/declared-artifacts.test.js.map +1 -1
  74. package/dist/packages/core/test/unit/derived-matrix.test.js +3 -0
  75. package/dist/packages/core/test/unit/derived-matrix.test.js.map +1 -1
  76. package/dist/packages/core/test/unit/done-verification.test.js +3 -0
  77. package/dist/packages/core/test/unit/done-verification.test.js.map +1 -1
  78. package/dist/packages/core/test/unit/matrix-dependencies.test.js.map +1 -1
  79. package/dist/packages/core/test/unit/reconciliation.test.js.map +1 -1
  80. package/dist/packages/core/test/unit/requirement-readiness.test.js +2 -2
  81. package/dist/packages/core/test/unit/requirement-readiness.test.js.map +1 -1
  82. package/dist/packages/core/test/unit/resume-state.test.js +4 -1
  83. package/dist/packages/core/test/unit/resume-state.test.js.map +1 -1
  84. package/dist/packages/core/test/unit/run-budget.test.js +3 -0
  85. package/dist/packages/core/test/unit/run-budget.test.js.map +1 -1
  86. package/dist/packages/core/test/unit/untouched-delta.test.js +52 -0
  87. package/dist/packages/core/test/unit/untouched-delta.test.js.map +1 -0
  88. package/dist/packages/core/test/unit/validate-project.test.js +6 -2
  89. package/dist/packages/core/test/unit/validate-project.test.js.map +1 -1
  90. package/dist/packages/mcp-spec-driven/src/server.js +4 -3
  91. package/dist/packages/mcp-spec-driven/src/server.js.map +1 -1
  92. package/dist/packages/mcp-spec-driven/src/tools.js.map +1 -1
  93. package/dist/scripts/agents/commands.js +52 -38
  94. package/dist/scripts/agents/commands.js.map +1 -1
  95. package/dist/scripts/agents/contract-file.js +44 -0
  96. package/dist/scripts/agents/contract-file.js.map +1 -1
  97. package/dist/scripts/agents/detect.js.map +1 -1
  98. package/dist/scripts/agents/init.js.map +1 -1
  99. package/dist/scripts/alm/cli.js +16 -5
  100. package/dist/scripts/alm/cli.js.map +1 -1
  101. package/dist/scripts/alm/core.js +1 -0
  102. package/dist/scripts/alm/core.js.map +1 -1
  103. package/dist/scripts/alm/port.js +1 -1
  104. package/dist/scripts/alm/port.js.map +1 -1
  105. package/dist/scripts/alm/providers/http-client.js +46 -3
  106. package/dist/scripts/alm/providers/http-client.js.map +1 -1
  107. package/dist/scripts/alm/providers/jira.js +108 -32
  108. package/dist/scripts/alm/providers/jira.js.map +1 -1
  109. package/dist/scripts/apply.js +9 -0
  110. package/dist/scripts/apply.js.map +1 -0
  111. package/dist/scripts/archive.js +9 -0
  112. package/dist/scripts/archive.js.map +1 -0
  113. package/dist/scripts/change/archive.js.map +1 -1
  114. package/dist/scripts/change/artifacts.js.map +1 -1
  115. package/dist/scripts/change/delta.js +0 -8
  116. package/dist/scripts/change/delta.js.map +1 -1
  117. package/dist/scripts/change/instructions.js +9 -11
  118. package/dist/scripts/change/instructions.js.map +1 -1
  119. package/dist/scripts/cli/commands/alm/PrCommand.js +198 -0
  120. package/dist/scripts/cli/commands/alm/PrCommand.js.map +1 -0
  121. package/dist/scripts/cli/commands/alm/PullCommand.js +4 -2
  122. package/dist/scripts/cli/commands/alm/PullCommand.js.map +1 -1
  123. package/dist/scripts/cli/commands/change/ChangeCommand.js +119 -19
  124. package/dist/scripts/cli/commands/change/ChangeCommand.js.map +1 -1
  125. package/dist/scripts/cli/commands/change/TicketCommand.js +657 -0
  126. package/dist/scripts/cli/commands/change/TicketCommand.js.map +1 -0
  127. package/dist/scripts/cli/commands/config/ConfigSetCommand.js +1 -1
  128. package/dist/scripts/cli/commands/config/ConfigSetCommand.js.map +1 -1
  129. package/dist/scripts/cli/commands/harness/RunCommand.js +225 -35
  130. package/dist/scripts/cli/commands/harness/RunCommand.js.map +1 -1
  131. package/dist/scripts/cli/commands/mcp/McpInstallCommand.js +1 -0
  132. package/dist/scripts/cli/commands/mcp/McpInstallCommand.js.map +1 -1
  133. package/dist/scripts/cli/commands/pack/BundlePackCommand.js +2 -1
  134. package/dist/scripts/cli/commands/pack/BundlePackCommand.js.map +1 -1
  135. package/dist/scripts/cli/commands/pack/ExpandDomainPackCommand.js +32 -17
  136. package/dist/scripts/cli/commands/pack/ExpandDomainPackCommand.js.map +1 -1
  137. package/dist/scripts/cli/commands/pack/InferPackCommand.js +8 -5
  138. package/dist/scripts/cli/commands/pack/InferPackCommand.js.map +1 -1
  139. package/dist/scripts/cli/commands/pack/LintPackCommand.js +36 -44
  140. package/dist/scripts/cli/commands/pack/LintPackCommand.js.map +1 -1
  141. package/dist/scripts/cli/commands/pack/pack-args.js.map +1 -1
  142. package/dist/scripts/cli/commands/project/AddCommand.js.map +1 -1
  143. package/dist/scripts/cli/commands/project/AdoptCommand.js +62 -2
  144. package/dist/scripts/cli/commands/project/AdoptCommand.js.map +1 -1
  145. package/dist/scripts/cli/commands/project/InitFromPackCommand.js.map +1 -1
  146. package/dist/scripts/cli/commands/project/InitProjectCommand.js +2 -1
  147. package/dist/scripts/cli/commands/project/InitProjectCommand.js.map +1 -1
  148. package/dist/scripts/cli/commands/project/OnboardCommand.js +40 -4
  149. package/dist/scripts/cli/commands/project/OnboardCommand.js.map +1 -1
  150. package/dist/scripts/cli/commands/project/UpdateCommand.js +4 -0
  151. package/dist/scripts/cli/commands/project/UpdateCommand.js.map +1 -1
  152. package/dist/scripts/cli/commands/project/WizardCommand.js.map +1 -1
  153. package/dist/scripts/cli/commands/quality/CheckCommand.js +86 -10
  154. package/dist/scripts/cli/commands/quality/CheckCommand.js.map +1 -1
  155. package/dist/scripts/cli/commands/quality/DoctorCommand.js +8 -4
  156. package/dist/scripts/cli/commands/quality/DoctorCommand.js.map +1 -1
  157. package/dist/scripts/cli/commands/quality/FixCommand.js.map +1 -1
  158. package/dist/scripts/cli/commands/quality/LicenseCheckCommand.js +3 -1
  159. package/dist/scripts/cli/commands/quality/LicenseCheckCommand.js.map +1 -1
  160. package/dist/scripts/cli/commands/quality/ReportCommand.js +8 -3
  161. package/dist/scripts/cli/commands/quality/ReportCommand.js.map +1 -1
  162. package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js +39 -30
  163. package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js.map +1 -1
  164. package/dist/scripts/cli/commands/quality/VerifyCommand.js +3 -2
  165. package/dist/scripts/cli/commands/quality/VerifyCommand.js.map +1 -1
  166. package/dist/scripts/cli/commands/spec/DoneCommand.js +182 -77
  167. package/dist/scripts/cli/commands/spec/DoneCommand.js.map +1 -1
  168. package/dist/scripts/cli/commands/spec/DraftCommand.js +292 -56
  169. package/dist/scripts/cli/commands/spec/DraftCommand.js.map +1 -1
  170. package/dist/scripts/cli/commands/spec/ExploreCommand.js +224 -0
  171. package/dist/scripts/cli/commands/spec/ExploreCommand.js.map +1 -0
  172. package/dist/scripts/cli/commands/spec/MatrixCommand.js +35 -5
  173. package/dist/scripts/cli/commands/spec/MatrixCommand.js.map +1 -1
  174. package/dist/scripts/cli/commands/spec/NewCommand.js +4 -4
  175. package/dist/scripts/cli/commands/spec/NewCommand.js.map +1 -1
  176. package/dist/scripts/cli/commands/spec/PetCommand.js.map +1 -1
  177. package/dist/scripts/cli/commands/spec/PlanCommand.js +8 -3
  178. package/dist/scripts/cli/commands/spec/PlanCommand.js.map +1 -1
  179. package/dist/scripts/cli/commands/spec/ReqCommand.js +8 -9
  180. package/dist/scripts/cli/commands/spec/ReqCommand.js.map +1 -1
  181. package/dist/scripts/cli/commands/spec/StatusCommand.js +105 -26
  182. package/dist/scripts/cli/commands/spec/StatusCommand.js.map +1 -1
  183. package/dist/scripts/cli/commands/spec/StudioCommand.js +76 -4
  184. package/dist/scripts/cli/commands/spec/StudioCommand.js.map +1 -1
  185. package/dist/scripts/cli/commands/specops/ContributeCommand.js +6 -2
  186. package/dist/scripts/cli/commands/specops/ContributeCommand.js.map +1 -1
  187. package/dist/scripts/cli/commands/specops/DiffCommand.js +19 -5
  188. package/dist/scripts/cli/commands/specops/DiffCommand.js.map +1 -1
  189. package/dist/scripts/cli/commands/specops/SyncCommand.js +4 -2
  190. package/dist/scripts/cli/commands/specops/SyncCommand.js.map +1 -1
  191. package/dist/scripts/docs/build-site.js.map +1 -1
  192. package/dist/scripts/docs/icons.js +0 -6
  193. package/dist/scripts/docs/icons.js.map +1 -1
  194. package/dist/scripts/docs/journey-storyboard.js +56 -47
  195. package/dist/scripts/docs/journey-storyboard.js.map +1 -1
  196. package/dist/scripts/docs/nav.js +5 -0
  197. package/dist/scripts/docs/nav.js.map +1 -1
  198. package/dist/scripts/docs/record-journey.js +93 -30
  199. package/dist/scripts/docs/record-journey.js.map +1 -1
  200. package/dist/scripts/docs/record-terminal.js +1 -1
  201. package/dist/scripts/docs/record-terminal.js.map +1 -1
  202. package/dist/scripts/docs/terminal-storyboard.js +24 -21
  203. package/dist/scripts/docs/terminal-storyboard.js.map +1 -1
  204. package/dist/scripts/draft/brief-check.js +34 -0
  205. package/dist/scripts/draft/brief-check.js.map +1 -0
  206. package/dist/scripts/draft/brief-setup.js +190 -24
  207. package/dist/scripts/draft/brief-setup.js.map +1 -1
  208. package/dist/scripts/draft/confined-agent.js +1 -0
  209. package/dist/scripts/draft/confined-agent.js.map +1 -1
  210. package/dist/scripts/draft/drafting-guide.js +49 -6
  211. package/dist/scripts/draft/drafting-guide.js.map +1 -1
  212. package/dist/scripts/draft/ticket-guide.js +129 -0
  213. package/dist/scripts/draft/ticket-guide.js.map +1 -0
  214. package/dist/scripts/explore.js +10 -0
  215. package/dist/scripts/explore.js.map +1 -0
  216. package/dist/scripts/gen_mcp_tools.js +15 -7
  217. package/dist/scripts/gen_mcp_tools.js.map +1 -1
  218. package/dist/scripts/harness/events.js +202 -0
  219. package/dist/scripts/harness/events.js.map +1 -0
  220. package/dist/scripts/harness/init.js +6 -2
  221. package/dist/scripts/harness/init.js.map +1 -1
  222. package/dist/scripts/harness/run.js +2 -1
  223. package/dist/scripts/harness/run.js.map +1 -1
  224. package/dist/scripts/lib/agent.js.map +1 -1
  225. package/dist/scripts/lib/base-ids.js +179 -0
  226. package/dist/scripts/lib/base-ids.js.map +1 -0
  227. package/dist/scripts/lib/capability-specs.js.map +1 -1
  228. package/dist/scripts/lib/diagnostics.js +0 -10
  229. package/dist/scripts/lib/diagnostics.js.map +1 -1
  230. package/dist/scripts/lib/harness-events.js +74 -0
  231. package/dist/scripts/lib/harness-events.js.map +1 -0
  232. package/dist/scripts/lib/in-flight.js +191 -0
  233. package/dist/scripts/lib/in-flight.js.map +1 -0
  234. package/dist/scripts/lib/live-model.js +145 -0
  235. package/dist/scripts/lib/live-model.js.map +1 -0
  236. package/dist/scripts/lib/live-page.js +89 -0
  237. package/dist/scripts/lib/live-page.js.map +1 -0
  238. package/dist/scripts/lib/monorepo-fanout.js.map +1 -1
  239. package/dist/scripts/lib/placeholders.js.map +1 -1
  240. package/dist/scripts/lib/project-root.js.map +1 -1
  241. package/dist/scripts/lib/run-with-deadline.js +6 -1
  242. package/dist/scripts/lib/run-with-deadline.js.map +1 -1
  243. package/dist/scripts/lib/surface.js +95 -30
  244. package/dist/scripts/lib/surface.js.map +1 -1
  245. package/dist/scripts/lib/ticket-keys.js +140 -0
  246. package/dist/scripts/lib/ticket-keys.js.map +1 -0
  247. package/dist/scripts/lib/tour.js +149 -0
  248. package/dist/scripts/lib/tour.js.map +1 -0
  249. package/dist/scripts/propose.js +27 -0
  250. package/dist/scripts/propose.js.map +1 -0
  251. package/dist/scripts/schema/cli.js +1 -1
  252. package/dist/scripts/schema/cli.js.map +1 -1
  253. package/dist/scripts/schema/registry.js +1 -0
  254. package/dist/scripts/schema/registry.js.map +1 -1
  255. package/dist/scripts/specops/add.js +2 -1
  256. package/dist/scripts/specops/add.js.map +1 -1
  257. package/dist/scripts/specops/against_lock.js +22 -25
  258. package/dist/scripts/specops/against_lock.js.map +1 -1
  259. package/dist/scripts/specops/as_change.js.map +1 -1
  260. package/dist/scripts/specops/lock.js +0 -5
  261. package/dist/scripts/specops/lock.js.map +1 -1
  262. package/dist/scripts/specops/manifest.js +0 -8
  263. package/dist/scripts/specops/manifest.js.map +1 -1
  264. package/dist/scripts/specops/remove.js +2 -1
  265. package/dist/scripts/specops/remove.js.map +1 -1
  266. package/dist/scripts/specops/verify.js.map +1 -1
  267. package/dist/scripts/vcs/github-pr.js +88 -0
  268. package/dist/scripts/vcs/github-pr.js.map +1 -0
  269. package/package.json +1 -1
  270. package/templates/adopt/spec.md.tpl +1 -1
  271. package/templates/adopt/traceability.md.tpl +1 -1
  272. package/templates/base/AGENTS.md.tpl +14 -7
  273. package/templates/harness/prompt-prefix.md.tpl +1 -1
@@ -57,6 +57,9 @@ exports.draftReport = draftReport;
57
57
  exports.moduleStatus = moduleStatus;
58
58
  exports.briefProgress = briefProgress;
59
59
  exports.draftNextCommand = draftNextCommand;
60
+ exports.openFindings = openFindings;
61
+ exports.nextScenario = nextScenario;
62
+ exports.reserveScenarioRange = reserveScenarioRange;
60
63
  exports.pasteLine = pasteLine;
61
64
  const fs = __importStar(require("node:fs"));
62
65
  const path = __importStar(require("node:path"));
@@ -69,10 +72,13 @@ const project_root_1 = require("../../../lib/project-root");
69
72
  const DraftChecklist_1 = require("../../../../packages/core/src/domain/DraftChecklist");
70
73
  const ChangeWorkspace_1 = require("../../../../packages/core/src/infrastructure/ChangeWorkspace");
71
74
  const ChangeTemplates_1 = require("../../../../packages/core/src/domain/ChangeTemplates");
75
+ const IdsAcrossBranches_1 = require("../../../../packages/core/src/infrastructure/IdsAcrossBranches");
72
76
  const DiskLanguageRepository_1 = require("../../../../packages/core/src/infrastructure/DiskLanguageRepository");
73
77
  const drafting_guide_1 = require("../../../draft/drafting-guide");
74
78
  const confined_agent_1 = require("../../../draft/confined-agent");
75
79
  const brief_setup_1 = require("../../../draft/brief-setup");
80
+ const brief_check_1 = require("../../../draft/brief-check");
81
+ const BriefCards_1 = require("../../../../packages/core/src/domain/BriefCards");
76
82
  const GherkinTags_1 = require("../../../../packages/core/src/domain/GherkinTags");
77
83
  const DerivedMatrixSources_1 = require("../../../../packages/core/src/infrastructure/DerivedMatrixSources");
78
84
  const CHANGES = path.join("docs", "specs", "changes");
@@ -84,11 +90,13 @@ function draftModuleOf(changeId) {
84
90
  return changeId.startsWith("draft-") ? changeId.slice("draft-".length) : `--check ${changeId}`;
85
91
  }
86
92
  function usage() {
87
- return ("Usage:\n" +
93
+ return ("Same as specgate propose --from-brief: a module of your brief becomes a draft change.\n\n" +
94
+ "Usage:\n" +
88
95
  " specgate draft your brief's modules, and the one to do next\n" +
89
96
  " specgate draft <module> open that module's draft — or check it, once it exists\n" +
90
97
  ' specgate draft <module> --agent "claude -p"\n' +
91
98
  " have your agent write it, then check it\n" +
99
+ " specgate draft --check the brief rules (B1–B11) on the brief, before drafting\n" +
92
100
  " specgate draft --check <change-id> the checklist on any change\n" +
93
101
  " specgate draft --new-brief write docs/brief.md to fill in, when you have none\n" +
94
102
  ' specgate draft --agent "claude -p" have your agent propose the brief\'s front matter\n\n' +
@@ -107,11 +115,20 @@ function usage() {
107
115
  " D9 every scenario passes the harness gate's rules\n" +
108
116
  " D10 no id the project already uses\n" +
109
117
  " D12 an actor can get the ids it acts on\n\n" +
110
- "Without a brief, D3 and D6 cannot run, and the report says so.\n");
118
+ "Without a brief, D3 and D6 cannot run, and the report says so.\n\n" +
119
+ "The brief rules (ADR-0038) — errors stop a draft, warnings are for you:\n" +
120
+ " B1 front matter fields in the shape Specgate reads B5 a vague word in a rule\n" +
121
+ " B2 the brief, and each module section, says something B6 a promised quantity with no number\n" +
122
+ " B3 a section per module, a module per section B7 a framework or database in a rule\n" +
123
+ " B4 every module has rules, and examples B8 more than 7 rules or 3 questions\n" +
124
+ " B9 a declared actor never mentioned\n" +
125
+ " B10 a rule that names a non-goal\n" +
126
+ " B11 a condition after the rule (EARS)\n");
111
127
  }
112
128
  function parseArgs(argv) {
113
129
  const o = {
114
130
  check: "",
131
+ checkBrief: false,
115
132
  module: "",
116
133
  brief: "",
117
134
  maxRequirements: 25,
@@ -129,8 +146,14 @@ function parseArgs(argv) {
129
146
  throw new Error(`${a} needs a value`);
130
147
  return v;
131
148
  };
132
- if (a === "--check")
133
- o.check = value();
149
+ if (a === "--check") {
150
+ // `--check <change-id>` checks a draft; `--check` alone, the brief.
151
+ const next = argv[i + 1];
152
+ if (next === undefined || next.startsWith("-"))
153
+ o.checkBrief = true;
154
+ else
155
+ o.check = value();
156
+ }
134
157
  else if (a === "--brief")
135
158
  o.brief = value();
136
159
  else if (a === "--max-requirements")
@@ -157,6 +180,9 @@ function parseArgs(argv) {
157
180
  if (o.check && o.module) {
158
181
  throw new Error("give a module or --check <change-id>, not both");
159
182
  }
183
+ if (o.checkBrief && (o.module || o.agent || o.newBrief)) {
184
+ throw new Error("--check only checks the brief: run it on its own");
185
+ }
160
186
  if (o.module && !ChangeWorkspace_1.CHANGE_ID_RE.test(o.module)) {
161
187
  throw new Error(`"${o.module}" is not a module name: use lowercase words and dashes, e.g. bookings`);
162
188
  }
@@ -340,11 +366,17 @@ function briefProgress(projectDir) {
340
366
  const modules = (0, DraftChecklist_1.briefModules)(text).map((m) => moduleStatus(projectDir, m));
341
367
  if (modules.length === 0)
342
368
  return null;
343
- return { brief: exports.DEFAULT_BRIEF, modules, next: modules.find((m) => m.state !== "archived") };
369
+ // A ready draft is reviewed and then applied (ADR-0035): the next module to
370
+ // draft is the first that is neither archived nor ready.
371
+ return {
372
+ brief: exports.DEFAULT_BRIEF,
373
+ modules,
374
+ next: modules.find((m) => m.state !== "archived" && m.state !== "ready"),
375
+ };
344
376
  }
345
377
  /** What to run for this module, given where it stands. */
346
378
  function draftNextCommand(s) {
347
- return s.state === "ready" ? `specgate change archive ${s.change}` : `specgate draft ${s.module}`;
379
+ return s.state === "ready" ? "specgate apply" : `specgate propose --from-brief ${s.module}`;
348
380
  }
349
381
  /** `➜ claude -p … is drafting catalogo…`, and why a flag was added. */
350
382
  function agentLine(command, doing, added) {
@@ -421,7 +453,8 @@ function nextScenario(projectDir) {
421
453
  const full = path.join(dir, e.name);
422
454
  if (e.isDirectory())
423
455
  walk(full);
424
- else if (e.name.endsWith(".md") || e.name.endsWith(".feature")) {
456
+ // change.yaml holds a change's reserved scenario range (REQ-157).
457
+ else if (e.name.endsWith(".md") || e.name.endsWith(".feature") || e.name === "change.yaml") {
425
458
  for (const m of fs.readFileSync(full, "utf8").matchAll(/\bSCN-(\d+)\b/g)) {
426
459
  max = Math.max(max, Number(m[1]));
427
460
  }
@@ -430,11 +463,19 @@ function nextScenario(projectDir) {
430
463
  };
431
464
  walk(path.join(projectDir, "docs", "specs"));
432
465
  walk(path.join(projectDir, "features"));
466
+ max = Math.max(max, (0, IdsAcrossBranches_1.highestOnOtherBranches)(projectDir, "SCN"));
433
467
  for (const m of (readIf(path.join(projectDir, "spec.md")) || "").matchAll(/\bSCN-(\d+)\b/g)) {
434
468
  max = Math.max(max, Number(m[1]));
435
469
  }
436
470
  return `SCN-${String(max + 1).padStart(3, "0")}`;
437
471
  }
472
+ /** Three scenario ids per requirement id, from the next free one (REQ-157). */
473
+ function reserveScenarioRange(projectDir, reqRange) {
474
+ const first = nextScenario(projectDir);
475
+ const reqs = Number(reqRange[1].slice(4)) - Number(reqRange[0].slice(4)) + 1;
476
+ const last = Number(first.slice(4)) + Math.max(1, reqs) * 3 - 1;
477
+ return [first, `SCN-${String(last).padStart(3, "0")}`];
478
+ }
438
479
  /** The one line to paste into any agent. */
439
480
  function pasteLine(module) {
440
481
  return `Draft the ${module} module: follow ${CHANGES.split(path.sep).join("/")}/draft-${module}/DRAFTING.md`;
@@ -456,11 +497,23 @@ function proposalFor(module, changeId, brief) {
456
497
  "",
457
498
  ].join("\n");
458
499
  }
500
+ /** The module's section of the brief, for `DRAFTING.md`; undefined when it has none. */
501
+ function moduleCards(briefText, module) {
502
+ const section = (0, BriefCards_1.moduleSection)(briefText, module);
503
+ if (!section)
504
+ return undefined;
505
+ return {
506
+ source: section.source,
507
+ values: (0, BriefCards_1.cardValues)(section),
508
+ questions: section.questions.map((q) => q.text),
509
+ };
510
+ }
459
511
  /** Open `draft-<module>`: the change, linked to the brief, with its guide. */
460
512
  function openDraft(projectDir, module, brief, o) {
461
513
  const changeId = `draft-${module}`;
462
514
  const dir = path.join(projectDir, CHANGES, changeId);
463
515
  const reqRange = (0, ChangeWorkspace_1.reserveReqRange)(projectDir, o.reserve);
516
+ const scnRange = reserveScenarioRange(projectDir, reqRange);
464
517
  const config = {
465
518
  ...ChangeWorkspace_1.DEFAULT_CONFIG,
466
519
  created: new Date().toISOString().slice(0, 10),
@@ -480,12 +533,14 @@ function openDraft(projectDir, module, brief, o) {
480
533
  changeId,
481
534
  brief,
482
535
  reqRange,
483
- firstScenario: nextScenario(projectDir),
536
+ firstScenario: scnRange[0],
537
+ lastScenario: scnRange[1],
484
538
  maxRequirements: o.maxRequirements,
485
539
  actors: (0, DraftChecklist_1.actorSurfaces)(briefText),
540
+ cards: moduleCards(briefText, module),
486
541
  }));
487
542
  write(path.join("specs", module, "spec.md"), (0, ChangeTemplates_1.templateDelta)(module, reqRange[0], (0, DiskLanguageRepository_1.phrases)(projectDir)));
488
- write("change.yaml", `${(0, ChangeWorkspace_1.renderConfig)(config).trimEnd()}\nbrief: ${brief}\n`);
543
+ write("change.yaml", `${(0, ChangeWorkspace_1.renderConfig)(config).trimEnd()}\nscn_range: [${scnRange[0]}, ${scnRange[1]}]\nbrief: ${brief}\n`);
489
544
  write("proposal.md", proposalFor(module, changeId, brief));
490
545
  return { changeId, reqRange, created };
491
546
  }
@@ -512,6 +567,12 @@ function nextFor(s) {
512
567
  return s ? draftNextCommand(s) : "specgate status";
513
568
  }
514
569
  class DraftCommand extends command_1.BaseCommand {
570
+ /** How the person called it, for the header: `draft`, or `propose --from-brief`. */
571
+ verb;
572
+ constructor(args = [], verb = "draft") {
573
+ super(args);
574
+ this.verb = verb;
575
+ }
515
576
  execute() {
516
577
  const io0 = (0, agent_1.agentIo)((0, agent_1.wantsJson)(this.args));
517
578
  let o;
@@ -519,11 +580,14 @@ class DraftCommand extends command_1.BaseCommand {
519
580
  o = parseArgs(this.args);
520
581
  }
521
582
  catch (err) {
583
+ // `parseArgs` throws Errors.
522
584
  io0.usage({ draft: null }, [
523
585
  (0, diagnostics_1.error)("draft_usage", err.message, { fix: "specgate draft --help" }),
524
586
  ]);
525
587
  }
526
588
  const opts = o;
589
+ if (opts.checkBrief)
590
+ return this.checkBrief(opts);
527
591
  if (opts.check)
528
592
  return this.check(opts);
529
593
  if (opts.module)
@@ -539,7 +603,7 @@ class DraftCommand extends command_1.BaseCommand {
539
603
  const io = (0, agent_1.agentIo)(opts.json);
540
604
  io.fail({ draft: null }, [
541
605
  (0, diagnostics_1.error)("draft_no_brief", `No plan or brief at ${rel}.`, {
542
- fix: `Save your plan or brief as ${exports.DEFAULT_BRIEF} — any markdown — then run \`specgate draft\`. ` +
606
+ fix: `Save your plan or brief as ${exports.DEFAULT_BRIEF} — any markdown — then run \`specgate propose --from-brief\`. ` +
543
607
  "Somewhere else: --brief <file>.",
544
608
  }),
545
609
  ]);
@@ -556,14 +620,14 @@ class DraftCommand extends command_1.BaseCommand {
556
620
  // (a) No brief: what one is, and how to get one.
557
621
  if (text === undefined) {
558
622
  if (!opts.json) {
559
- process.stdout.write(ui.header("draft") + `${(0, brief_setup_1.noBriefExplanation)(rel)}\n`);
623
+ process.stdout.write(ui.header(this.verb) + `${(0, brief_setup_1.noBriefExplanation)(rel)}\n`);
560
624
  process.exitCode = 1;
561
625
  return;
562
626
  }
563
627
  io.fail({ draft: null }, [
564
628
  (0, diagnostics_1.error)("draft_no_brief", `No plan or brief at ${rel}.`, {
565
- fix: `Save your plan or brief as ${rel} — any markdown — then run \`specgate draft\`. ` +
566
- "No plan yet: `specgate draft --new-brief` writes one to fill in. Somewhere else: --brief <file>.",
629
+ fix: `Save your plan or brief as ${rel} — any markdown — then run \`specgate propose --from-brief\`. ` +
630
+ "No plan yet: `specgate propose --from-brief --new-brief` writes one to fill in. Somewhere else: --brief <file>.",
567
631
  }),
568
632
  ]);
569
633
  }
@@ -579,49 +643,75 @@ class DraftCommand extends command_1.BaseCommand {
579
643
  frontMatter: null,
580
644
  guide: brief_setup_1.BRIEF_SETUP_FILE,
581
645
  prompt: brief_setup_1.BRIEF_SETUP_PASTE,
582
- next: 'specgate draft --agent "claude -p"',
646
+ next: 'specgate propose --from-brief --agent "claude -p"',
583
647
  },
584
- }, () => process.stdout.write(ui.header("draft", rel) +
648
+ }, () => process.stdout.write(ui.header(this.verb, rel) +
585
649
  (0, brief_setup_1.frontMatterExplanation)(rel) +
586
650
  "\n" +
587
651
  ui.section("Or let your agent propose it") +
588
652
  " Paste this into Claude Code, Cursor, Codex…\n\n" +
589
653
  ui.quote(brief_setup_1.BRIEF_SETUP_PASTE) +
590
- ui.next('specgate draft --agent "claude -p"', "Specgate calls your agent for you — then review the four fields and run specgate draft again")));
654
+ ui.next('specgate propose --from-brief --agent "claude -p"', "Specgate calls your agent for you — then review the four fields and run specgate propose --from-brief again")));
591
655
  return;
592
656
  }
593
657
  // (c) A front matter with problems: each in plain words, with its fix.
594
658
  const problems = (0, brief_setup_1.checkFrontMatter)(brief.text);
595
659
  if (problems.length > 0) {
596
660
  io.emit({
597
- draft: { brief: rel, frontMatter: "invalid", next: `edit ${rel}, then specgate draft` },
661
+ draft: {
662
+ brief: rel,
663
+ frontMatter: "invalid",
664
+ next: `edit ${rel}, then specgate propose --from-brief`,
665
+ },
598
666
  status: problems.map((p) => (0, diagnostics_1.error)(p.code, p.message, { fix: p.fix })),
599
667
  }, () => {
600
668
  const out = process.stdout;
601
- out.write(ui.header("draft", rel));
669
+ out.write(ui.header(this.verb, rel));
602
670
  out.write(` The front matter at the top of ${rel} needs ${problems.length} fix(es):\n\n`);
603
671
  for (const p of problems)
604
672
  out.write(ui.fail(p.message) + ui.fix(p.fix));
605
- out.write(ui.next("specgate draft", `once they are fixed in ${rel}`));
673
+ out.write(ui.next("specgate propose --from-brief", `once they are fixed in ${rel}`));
606
674
  });
607
675
  process.exitCode = 1;
608
676
  return;
609
677
  }
678
+ // (c') The body: an empty brief, a module without its section or cards.
679
+ const findings = (0, brief_check_1.checkBrief)(brief.text, rel);
680
+ const errors = findings.filter((d) => d.severity === "error");
681
+ if (errors.length > 0) {
682
+ io.emit({
683
+ draft: { brief: rel, ready: false, next: "specgate propose --from-brief --check" },
684
+ status: findings,
685
+ }, () => {
686
+ const out = process.stdout;
687
+ out.write(ui.header(this.verb, rel));
688
+ out.write(` ${rel} needs ${errors.length} fix(es) before a module is drafted:\n\n`);
689
+ writeBriefFindings(errors);
690
+ out.write(ui.next("specgate propose --from-brief --check", `once they are fixed in ${rel}`));
691
+ });
692
+ process.exitCode = 1;
693
+ return;
694
+ }
695
+ const warnings = findings.filter((d) => d.severity === "warning").length;
610
696
  // (d) Ready: the modules, where each stands, what is next.
611
697
  const modules = (0, DraftChecklist_1.briefModules)(brief.text);
612
698
  const states = modules.map((m) => moduleStatus(projectDir, m, rel));
613
699
  const pending = states.find((s) => s.state !== "archived");
614
700
  const next = nextFor(pending);
615
701
  const product = /^product:\s*(.+)$/m.exec((0, brief_setup_1.frontMatter)(brief.text) || "");
616
- io.emit({ draft: { brief: rel, modules: states, next } }, () => {
702
+ io.emit({ draft: { brief: rel, modules: states, next }, status: findings }, () => {
617
703
  const out = process.stdout;
618
- out.write(ui.header("draft", rel));
704
+ out.write(ui.header(this.verb, rel));
619
705
  if (product)
620
706
  out.write(ui.kv("Product", ui.bold(product[1].trim()), 10));
621
707
  const archived = states.filter((s) => s.state === "archived").length;
622
708
  out.write(ui.kv("Progress", ui.progress(archived, states.length, "archived"), 10));
623
709
  out.write("\n" + ui.section("Modules"));
624
710
  out.write(ui.table(states.map((s) => [`${ICON[s.state]()} ${s.module}`, ui.muted(describe(s))])));
711
+ if (warnings > 0) {
712
+ out.write("\n" +
713
+ ui.info(`${warnings} warning(s) on the brief's wording ${ui.muted("— specgate propose --from-brief --check")}`));
714
+ }
625
715
  out.write(pending
626
716
  ? ui.next(next)
627
717
  : "\n" +
@@ -629,29 +719,60 @@ class DraftCommand extends command_1.BaseCommand {
629
719
  ui.next(next, "what to build first"));
630
720
  });
631
721
  }
722
+ /** `--check`: the brief rules on the brief, writing nothing (ADR-0038). */
723
+ checkBrief(opts) {
724
+ const io = (0, agent_1.agentIo)(opts.json);
725
+ const projectDir = (0, project_root_1.resolveProjectDir)(opts.projectDir);
726
+ const brief = this.brief(projectDir, opts);
727
+ const findings = (0, brief_check_1.checkBrief)(brief.text, brief.rel);
728
+ const errors = findings.filter((d) => d.severity === "error").length;
729
+ const warnings = findings.filter((d) => d.severity === "warning").length;
730
+ const first = (0, DraftChecklist_1.briefModules)(brief.text).find((m) => moduleStatus(projectDir, m, brief.rel).state !== "archived");
731
+ const next = errors > 0
732
+ ? "specgate propose --from-brief --check"
733
+ : first
734
+ ? `specgate propose --from-brief ${first}`
735
+ : "specgate propose --from-brief";
736
+ io.emit({
737
+ draft: { brief: brief.rel, ready: errors === 0, errors, warnings, next },
738
+ status: findings,
739
+ }, () => {
740
+ const out = process.stdout;
741
+ out.write(ui.header(`${this.verb} --check`, brief.rel));
742
+ writeBriefFindings(findings);
743
+ out.write(errors > 0
744
+ ? (findings.length ? "\n" : "") +
745
+ ui.fail(`${ui.bold(`${errors} error(s) to fix before drafting`)} ${ui.muted("— each says how")}`)
746
+ : (findings.length ? "\n" : "") +
747
+ ui.ok(ui.bold("Ready to draft") +
748
+ (warnings ? ui.muted(` — ${warnings} warning(s) for you to judge`) : "")));
749
+ out.write(ui.next(next, errors > 0 ? `once they are fixed in ${brief.rel}` : "the first module"));
750
+ });
751
+ process.exitCode = errors > 0 ? 1 : 0;
752
+ }
632
753
  /** `--new-brief`: the template, never over a brief that exists. */
633
754
  newBrief(opts, file, rel) {
634
755
  const io = (0, agent_1.agentIo)(opts.json);
635
756
  if (fs.existsSync(file)) {
636
757
  io.fail({ draft: null }, [
637
758
  (0, diagnostics_1.error)("draft_brief_exists", `${rel} already exists — nothing was written.`, {
638
- fix: "Edit it, then run `specgate draft`; it says what the top of the brief still needs.",
759
+ fix: "Edit it, then run `specgate propose --from-brief`; it says what the top of the brief still needs.",
639
760
  }),
640
761
  ]);
641
762
  }
642
763
  fs.mkdirSync(path.dirname(file), { recursive: true });
643
764
  fs.writeFileSync(file, brief_setup_1.BRIEF_TEMPLATE, "utf8");
644
- io.emit({ draft: { brief: rel, written: true, next: "specgate draft" } }, () => process.stdout.write(ui.header("draft --new-brief") +
765
+ io.emit({ draft: { brief: rel, written: true, next: "specgate propose --from-brief --check" } }, () => process.stdout.write(ui.header(`${this.verb} --new-brief`) +
645
766
  ui.ok(`Wrote ${rel} — a brief to fill in.`) +
646
767
  "\n Fill it in, in your own words:\n" +
647
768
  " 1. The four fields at the top — product, surfaces, actors, modules.\n" +
648
769
  " Each has an example in the comment above it.\n" +
649
- " 2. The sections below — what the product is, who uses it and how, what\n" +
650
- " each one must be able to do, the rules and numbers you already know,\n" +
651
- " what you don't know yet, and what is out of scope.\n\n" +
652
- " Have a plan or an email already? Paste it under the four fields instead\n" +
653
- " of the sections — Specgate reads it as it is.\n" +
654
- ui.next("specgate draft", "it checks the four fields and says what is next")));
770
+ " 2. Why — the problem, and a success you can measure.\n" +
771
+ " 3. Per module, a section: its Rules, an Example per rule, and the\n" +
772
+ " Questions you cannot answer yet.\n\n" +
773
+ " Have a plan or an email already? Paste it under Notes — your agent can\n" +
774
+ ' turn it into the module sections: specgate propose --from-brief --agent "claude -p"\n' +
775
+ ui.next("specgate propose --from-brief --check", "it checks the brief and says what is missing")));
655
776
  }
656
777
  /**
657
778
  * `draft --agent` without a module: the agent proposes the front matter.
@@ -662,10 +783,20 @@ class DraftCommand extends command_1.BaseCommand {
662
783
  frontMatterWithAgent(opts, projectDir, brief) {
663
784
  const io = (0, agent_1.agentIo)(opts.json);
664
785
  if ((0, brief_setup_1.frontMatter)(brief.text) !== null && (0, brief_setup_1.checkFrontMatter)(brief.text).length === 0) {
665
- const first = (0, DraftChecklist_1.briefModules)(brief.text)[0];
666
- io.emit({ draft: { brief: brief.rel, frontMatter: "ok", next: `specgate draft ${first}` } }, () => process.stdout.write(ui.header("draft --agent", brief.rel) +
786
+ const modules = (0, DraftChecklist_1.briefModules)(brief.text);
787
+ if (modules.length > 0 && (0, BriefCards_1.readBriefBody)(brief.text).modules.length === 0) {
788
+ return this.cardsWithAgent(opts, projectDir, brief, modules);
789
+ }
790
+ const first = modules[0];
791
+ io.emit({
792
+ draft: {
793
+ brief: brief.rel,
794
+ frontMatter: "ok",
795
+ next: `specgate propose --from-brief ${first}`,
796
+ },
797
+ }, () => process.stdout.write(ui.header(`${this.verb} --agent`, brief.rel) +
667
798
  ui.ok(`The front matter of ${brief.rel} is already there and complete — nothing for the agent to do.`) +
668
- ui.next(`specgate draft ${first}`)));
799
+ ui.next(`specgate propose --from-brief ${first}`)));
669
800
  return;
670
801
  }
671
802
  writeSetupGuide(projectDir, brief.rel);
@@ -675,7 +806,7 @@ class DraftCommand extends command_1.BaseCommand {
675
806
  .join("/");
676
807
  const agent = (0, confined_agent_1.withWritePermission)(opts.agent);
677
808
  if (!opts.json) {
678
- process.stdout.write(ui.header("draft --agent", brief.rel) +
809
+ process.stdout.write(ui.header(`${this.verb} --agent`, brief.rel) +
679
810
  agentLine(agent.command, `is proposing the front matter of ${brief.rel}`, agent.added));
680
811
  }
681
812
  const run = (0, confined_agent_1.runConfinedAgent)({
@@ -703,7 +834,7 @@ class DraftCommand extends command_1.BaseCommand {
703
834
  fs.writeFileSync(path.resolve(projectDir, brief.rel), brief.text, "utf8");
704
835
  io.fail({ draft: null }, [
705
836
  (0, diagnostics_1.error)("draft_brief_body_changed", "The agent changed the brief itself, not only its front matter — undone.", {
706
- fix: `${brief.rel} is back as it was. Try again, or write the four fields yourself (\`specgate draft\` shows them).`,
837
+ fix: `${brief.rel} is back as it was. Try again, or write the four fields yourself (\`specgate propose --from-brief\` shows them).`,
707
838
  }),
708
839
  ]);
709
840
  }
@@ -718,13 +849,13 @@ class DraftCommand extends command_1.BaseCommand {
718
849
  if (proposed === null) {
719
850
  io.fail({ draft: null }, [
720
851
  (0, diagnostics_1.error)("draft_no_front_matter", "The agent wrote no front matter.", {
721
- fix: `${confined_agent_1.NO_WRITE_HINT} Or run \`specgate draft\` to see the four fields, and write them yourself.`,
852
+ fix: `${confined_agent_1.NO_WRITE_HINT} Or run \`specgate propose --from-brief\` to see the four fields, and write them yourself.`,
722
853
  }),
723
854
  ]);
724
855
  }
725
856
  const problems = (0, brief_setup_1.checkFrontMatter)(after);
726
857
  io.emit({
727
- draft: { brief: brief.rel, frontMatter: proposed, next: "specgate draft" },
858
+ draft: { brief: brief.rel, frontMatter: proposed, next: "specgate propose --from-brief" },
728
859
  status: problems.map((p) => (0, diagnostics_1.error)(p.code, p.message, { fix: p.fix })),
729
860
  }, () => {
730
861
  const out = process.stdout;
@@ -737,7 +868,81 @@ class DraftCommand extends command_1.BaseCommand {
737
868
  for (const p of problems)
738
869
  out.write(ui.fail(p.message) + ui.fix(p.fix));
739
870
  }
740
- out.write(ui.next("specgate draft", "review it — change anything — then run it"));
871
+ out.write(ui.next("specgate propose --from-brief", "review it — change anything — then run it"));
872
+ });
873
+ }
874
+ /**
875
+ * `draft --agent` on a brief with its front matter and no module sections:
876
+ * the agent proposes one section per module from the brief's own text. It
877
+ * may write only the brief, and must keep every line already there.
878
+ */
879
+ cardsWithAgent(opts, projectDir, brief, modules) {
880
+ const io = (0, agent_1.agentIo)(opts.json);
881
+ const guide = (0, brief_setup_1.briefCardsGuide)(brief.rel, modules);
882
+ const guideFile = path.join(projectDir, brief_setup_1.BRIEF_CARDS_FILE);
883
+ fs.mkdirSync(path.dirname(guideFile), { recursive: true });
884
+ fs.writeFileSync(guideFile, guide, "utf8");
885
+ const own = path
886
+ .relative(projectDir, path.resolve(projectDir, brief.rel))
887
+ .split(path.sep)
888
+ .join("/");
889
+ const agent = (0, confined_agent_1.withWritePermission)(opts.agent);
890
+ if (!opts.json) {
891
+ process.stdout.write(ui.header(`${this.verb} --agent`, brief.rel) +
892
+ agentLine(agent.command, `is proposing the module sections of ${brief.rel}`, agent.added));
893
+ }
894
+ const run = (0, confined_agent_1.runConfinedAgent)({
895
+ projectDir,
896
+ changeId: "brief",
897
+ allowed: (rel) => rel === own,
898
+ command: agent.command,
899
+ prompt: (0, brief_setup_1.briefCardsPrompt)(guide),
900
+ showOutput: !opts.json,
901
+ });
902
+ if (run.problem === "no_git") {
903
+ io.fail({ draft: null }, [
904
+ (0, diagnostics_1.error)("draft_agent_needs_git", "--agent needs a git repository.", {
905
+ fix: `Run \`git init\`, then try again — or paste this into your agent yourself: ${brief_setup_1.BRIEF_CARDS_PASTE}`,
906
+ }),
907
+ ]);
908
+ }
909
+ if (run.reverted.length > 0 && !opts.json) {
910
+ process.stdout.write("\n" +
911
+ ui.warn(`${ui_1.sym.undo} Undone — the agent wrote outside the brief: ${run.reverted.join(", ")}`));
912
+ }
913
+ const file = path.resolve(projectDir, brief.rel);
914
+ const after = readIf(file) ?? "";
915
+ if (!(0, brief_setup_1.keepsEveryLine)(brief.text, after)) {
916
+ fs.writeFileSync(file, brief.text, "utf8");
917
+ io.fail({ draft: null }, [
918
+ (0, diagnostics_1.error)("draft_brief_text_lost", "The agent changed or removed a line of the brief, not only added sections — undone.", {
919
+ fix: `${brief.rel} is back as it was. Try again, or write the sections yourself: \`## Module: ${modules[0]}\` with Rules, Examples and Questions.`,
920
+ }),
921
+ ]);
922
+ }
923
+ if (!run.ok) {
924
+ io.fail({ draft: null }, [
925
+ (0, diagnostics_1.error)("draft_agent_failed", `The agent ${run.detail}.`, {
926
+ fix: `Check the command runs on its own, or paste this into your agent: ${brief_setup_1.BRIEF_CARDS_PASTE}`,
927
+ }),
928
+ ]);
929
+ }
930
+ const added = (0, BriefCards_1.readBriefBody)(after).modules.map((m) => m.name);
931
+ if (added.length === 0) {
932
+ io.fail({ draft: null }, [
933
+ (0, diagnostics_1.error)("draft_no_cards", "The agent added no module section.", {
934
+ fix: `${confined_agent_1.NO_WRITE_HINT} Or write them yourself: \`## Module: ${modules[0]}\` with Rules, Examples and Questions.`,
935
+ }),
936
+ ]);
937
+ }
938
+ const findings = (0, brief_check_1.checkBrief)(after, brief.rel);
939
+ const next = "specgate propose --from-brief --check";
940
+ io.emit({ draft: { brief: brief.rel, sections: added, next }, status: findings }, () => {
941
+ const out = process.stdout;
942
+ out.write(ui.rule() + "\n");
943
+ out.write(ui.ok(`The agent proposed ${added.length} module section(s) in ${brief.rel}: ${added.join(", ")}`) + "\n");
944
+ out.write(ui.info("Every line you wrote is still there."));
945
+ out.write(ui.next(next, "review the sections — change anything — then check them"));
741
946
  });
742
947
  }
743
948
  // ── specgate draft <module> ─────────────────────────────────────────────────
@@ -752,28 +957,41 @@ class DraftCommand extends command_1.BaseCommand {
752
957
  module: opts.module,
753
958
  change: changeId,
754
959
  state: "archived",
755
- next: "specgate draft",
960
+ next: "specgate propose --from-brief",
756
961
  },
757
- }, () => process.stdout.write(ui.header(`draft ${opts.module}`) +
962
+ }, () => process.stdout.write(ui.header(`${this.verb} ${opts.module}`) +
758
963
  ui.ok(`${opts.module} is already drafted and archived ${ui.muted(`(${status.archivedAs})`)}`) +
759
- ui.next("specgate draft", "the next module of the brief, or what to build first")));
964
+ ui.next("specgate propose --from-brief", "the next module of the brief, or what to build first")));
760
965
  return;
761
966
  }
762
967
  if (status.state === "not started") {
763
968
  if (!fs.existsSync(path.join(projectDir, "spec.md"))) {
764
969
  io.fail({ draft: null }, [
765
970
  (0, diagnostics_1.error)("draft_no_project", "This folder is not a Specgate project yet.", {
766
- fix: "Run `specgate init` first, then `specgate draft " + opts.module + "`.",
971
+ fix: "Run `specgate init` first, then `specgate propose --from-brief " +
972
+ opts.module +
973
+ "`.",
767
974
  }),
768
975
  ]);
769
976
  }
770
977
  const brief = this.brief(projectDir, opts);
978
+ // A brief that is plain markdown, with no front matter, still drafts:
979
+ // only the rules that apply to what it does say stop it.
980
+ const blocking = (0, brief_check_1.checkBrief)(brief.text, brief.rel).filter((d) => d.code !== "brief_no_front_matter");
981
+ if (!(0, brief_check_1.briefReady)(blocking)) {
982
+ io.fail({ draft: null }, [
983
+ (0, diagnostics_1.error)("brief_not_ready", `${brief.rel} is not ready to draft ${opts.module} from.`, {
984
+ fix: "Fix what `specgate propose --from-brief --check` reports, then run this again.",
985
+ }),
986
+ ...blocking.filter((d) => d.severity === "error"),
987
+ ]);
988
+ }
771
989
  const listed = (0, DraftChecklist_1.briefModules)(brief.text);
772
990
  const opened = openDraft(projectDir, opts.module, brief.rel, opts);
773
991
  const notes = listed.length > 0 && !listed.includes(opts.module)
774
992
  ? [
775
993
  (0, diagnostics_1.info)("draft_module_not_in_brief", `${opts.module} is not among the brief's modules (${listed.join(", ")}).`, {
776
- fix: "Add it to `modules:` in the brief's front matter, so `specgate draft` lists it.",
994
+ fix: "Add it to `modules:` in the brief's front matter, so `specgate propose --from-brief` lists it.",
777
995
  }),
778
996
  ]
779
997
  : [];
@@ -788,7 +1006,7 @@ class DraftCommand extends command_1.BaseCommand {
788
1006
  reserved: opened.reqRange,
789
1007
  created: opened.created,
790
1008
  prompt: pasteLine(opts.module),
791
- next: `specgate draft ${opts.module}`,
1009
+ next: `specgate propose --from-brief ${opts.module}`,
792
1010
  },
793
1011
  status: notes,
794
1012
  }, () => {
@@ -800,7 +1018,7 @@ class DraftCommand extends command_1.BaseCommand {
800
1018
  [`${dir}/change.yaml, proposal.md`, ""],
801
1019
  ];
802
1020
  const w = Math.max(...files.map((r) => r[0].length));
803
- out.write(ui.header(`draft ${opts.module}`, brief.rel) +
1021
+ out.write(ui.header(`${this.verb} ${opts.module}`, brief.rel) +
804
1022
  ui.ok(`Draft ${ui.bold(opened.changeId)} opened ` +
805
1023
  ui.muted(`· REQ ids ${opened.reqRange[0]}…${opened.reqRange[1]} reserved`)) +
806
1024
  "\n" +
@@ -809,7 +1027,8 @@ class DraftCommand extends command_1.BaseCommand {
809
1027
  ui.section("Have your agent write it") +
810
1028
  " Paste this into Claude Code, Cursor, Codex…\n\n" +
811
1029
  ui.quote(pasteLine(opts.module)) +
812
- ui.next(`specgate draft ${opts.module} --agent "claude -p"`, `or let Specgate call it — then run specgate draft ${opts.module} until it says Ready to review`));
1030
+ ui.next(`specgate propose --from-brief ${opts.module} --agent "claude -p"`, `or let Specgate call it — then run specgate propose --from-brief ${opts.module} until it says Ready to review`));
1031
+ // Every note above is built with a `fix`.
813
1032
  for (const n of notes)
814
1033
  out.write(ui.info(n.message) + ui.fix(n.fix) + "\n");
815
1034
  });
@@ -827,7 +1046,7 @@ class DraftCommand extends command_1.BaseCommand {
827
1046
  const guide = readIf(path.join(projectDir, CHANGES, changeId, "DRAFTING.md")) || "";
828
1047
  const agent = (0, confined_agent_1.withWritePermission)(opts.agent);
829
1048
  if (!opts.json) {
830
- process.stdout.write(ui.header(`draft ${opts.module}`, brief) +
1049
+ process.stdout.write(ui.header(`${this.verb} ${opts.module}`, brief) +
831
1050
  (created.length ? ui.ok(`Draft ${changeId} opened, linked to ${brief}.`) : "") +
832
1051
  agentLine(agent.command, `is drafting ${opts.module}`, agent.added));
833
1052
  }
@@ -842,7 +1061,7 @@ class DraftCommand extends command_1.BaseCommand {
842
1061
  io.fail({ draft: null }, [
843
1062
  (0, diagnostics_1.error)("draft_agent_needs_git", "--agent needs a git repository.", {
844
1063
  fix: "Git is how Specgate finds what the agent wrote and undoes anything outside the draft. " +
845
- "Run `git init`, then `specgate draft " +
1064
+ "Run `git init`, then `specgate propose --from-brief " +
846
1065
  opts.module +
847
1066
  ' --agent "…"` again — or paste the prompt into your agent yourself.',
848
1067
  }),
@@ -878,16 +1097,16 @@ class DraftCommand extends command_1.BaseCommand {
878
1097
  state: status.state,
879
1098
  brief,
880
1099
  prompt: pasteLine(opts.module),
881
- next: `specgate draft ${opts.module}`,
1100
+ next: `specgate propose --from-brief ${opts.module}`,
882
1101
  },
883
- }, () => process.stdout.write((afterAgent ? "\n" : ui.header(`draft ${opts.module}`, brief)) +
1102
+ }, () => process.stdout.write((afterAgent ? "\n" : ui.header(`${this.verb} ${opts.module}`, brief)) +
884
1103
  (afterAgent
885
1104
  ? ui.fail("The agent ran, but the draft is still the placeholder.") +
886
1105
  ui.fix(confined_agent_1.NO_WRITE_HINT) +
887
1106
  "\n"
888
1107
  : " The draft is still the placeholder. Have your agent write it:\n\n" +
889
1108
  ui.quote(pasteLine(opts.module)) +
890
- ui.next(`specgate draft ${opts.module} --agent "claude -p"`, "or let Specgate call it"))));
1109
+ ui.next(`specgate propose --from-brief ${opts.module} --agent "claude -p"`, "or let Specgate call it"))));
891
1110
  process.exitCode = 1;
892
1111
  return;
893
1112
  }
@@ -899,7 +1118,9 @@ class DraftCommand extends command_1.BaseCommand {
899
1118
  const report = result.report;
900
1119
  const diags = [...report.status, ...skippedNotes(report)];
901
1120
  const errors = report.status.length;
902
- const next = errors === 0 ? `specgate change archive ${changeId}` : `specgate draft ${opts.module}`;
1121
+ // Ready: reviewed, then its requirements are applied one by one, and the
1122
+ // change is archived once they are all delivered (ADR-0035).
1123
+ const next = errors === 0 ? "specgate apply" : `specgate propose --from-brief ${opts.module}`;
903
1124
  io.emit({
904
1125
  draft: {
905
1126
  module: opts.module,
@@ -915,7 +1136,7 @@ class DraftCommand extends command_1.BaseCommand {
915
1136
  status: diags,
916
1137
  }, () => {
917
1138
  const out = process.stdout;
918
- out.write(afterAgent ? "\n" : ui.header(`draft ${opts.module}`, brief));
1139
+ out.write(afterAgent ? "\n" : ui.header(`${this.verb} ${opts.module}`, brief));
919
1140
  writeFindings(report, diags);
920
1141
  const dir = `${CHANGES.split(path.sep).join("/")}/${changeId}`;
921
1142
  const steps = [];
@@ -923,17 +1144,17 @@ class DraftCommand extends command_1.BaseCommand {
923
1144
  out.write(`${diags.length ? "\n" : ""}` + ui.ok(ui.bold("Ready to review")));
924
1145
  steps.push(`Read ${dir}/specs/ with whoever knows the business`, fs.existsSync(path.join(projectDir, CHANGES, changeId, "questions.md"))
925
1146
  ? `Answer what you can in ${dir}/questions.md`
926
- : `Check ${dir}/assumptions.md, if the agent wrote one`, next);
1147
+ : `Check ${dir}/assumptions.md, if the agent wrote one`, [next, "its requirements, one at a time; then specgate archive " + changeId]);
927
1148
  const after = nextModuleAfter(projectDir, brief, opts.module);
928
1149
  steps.push(after
929
- ? [`specgate draft ${after}`, "the next module of the brief"]
1150
+ ? [`specgate propose --from-brief ${after}`, "the next module of the brief"]
930
1151
  : ["specgate status", "every module drafted: what to build first"]);
931
1152
  }
932
1153
  else {
933
1154
  out.write("\n" +
934
1155
  ui.fail(`${ui.bold(`${errors} finding(s) to fix`)} ${ui.muted("— each says how; DRAFTING.md has the formats")}`));
935
1156
  steps.push([
936
- `specgate draft ${opts.module} --agent "claude -p"`,
1157
+ `specgate propose --from-brief ${opts.module} --agent "claude -p"`,
937
1158
  "your agent fixes them — or fix them yourself",
938
1159
  ], [next, "again, until it says Ready to review"]);
939
1160
  }
@@ -966,7 +1187,7 @@ class DraftCommand extends command_1.BaseCommand {
966
1187
  status,
967
1188
  }, () => {
968
1189
  const out = process.stdout;
969
- out.write(ui.header(`draft --check ${opts.check}`));
1190
+ out.write(ui.header(`${this.verb} --check ${opts.check}`));
970
1191
  writeFindings(report, status);
971
1192
  const skipped = report.skipped.length;
972
1193
  out.write(errors === 0
@@ -993,4 +1214,19 @@ function writeFindings(report, status) {
993
1214
  out.write(ui.fix(d.fix));
994
1215
  }
995
1216
  }
1217
+ /** The brief rules' findings: code, the module they are about, message, fix. */
1218
+ function writeBriefFindings(findings) {
1219
+ const out = process.stdout;
1220
+ for (const d of findings) {
1221
+ const where = d.target ? `${ui.bold(d.target)} ` : "";
1222
+ const line = `${ui.muted(d.code)} ${where}${d.message}`;
1223
+ out.write(d.severity === "error"
1224
+ ? ui.fail(line)
1225
+ : d.severity === "warning"
1226
+ ? ui.warn(line)
1227
+ : ui.info(line));
1228
+ if (d.fix)
1229
+ out.write(ui.fix(d.fix));
1230
+ }
1231
+ }
996
1232
  //# sourceMappingURL=DraftCommand.js.map