@rsaglobaltech/specgate 0.17.9 → 0.18.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 (53) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +5 -5
  3. package/bin/specgate.ts +1 -0
  4. package/dist/bin/specgate.js +1 -0
  5. package/dist/bin/specgate.js.map +1 -1
  6. package/dist/packages/core/src/application/ArchiveChangeUseCase.js +5 -1
  7. package/dist/packages/core/src/application/ArchiveChangeUseCase.js.map +1 -1
  8. package/dist/packages/core/src/domain/DerivedMatrix.js +32 -0
  9. package/dist/packages/core/src/domain/DerivedMatrix.js.map +1 -1
  10. package/dist/packages/core/src/domain/DraftChecklist.js +101 -24
  11. package/dist/packages/core/src/domain/DraftChecklist.js.map +1 -1
  12. package/dist/packages/core/src/domain/PackSpec.js +21 -2
  13. package/dist/packages/core/src/domain/PackSpec.js.map +1 -1
  14. package/dist/scripts/agents/commands.js +18 -0
  15. package/dist/scripts/agents/commands.js.map +1 -1
  16. package/dist/scripts/agents/init.js +9 -1
  17. package/dist/scripts/agents/init.js.map +1 -1
  18. package/dist/scripts/cli/commands/change/ChangeCommand.js +23 -3
  19. package/dist/scripts/cli/commands/change/ChangeCommand.js.map +1 -1
  20. package/dist/scripts/cli/commands/project/AdoptCommand.js +14 -1
  21. package/dist/scripts/cli/commands/project/AdoptCommand.js.map +1 -1
  22. package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js +20 -0
  23. package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js.map +1 -1
  24. package/dist/scripts/cli/commands/spec/DoneCommand.js +152 -4
  25. package/dist/scripts/cli/commands/spec/DoneCommand.js.map +1 -1
  26. package/dist/scripts/cli/commands/spec/DraftCommand.js +702 -57
  27. package/dist/scripts/cli/commands/spec/DraftCommand.js.map +1 -1
  28. package/dist/scripts/cli/commands/spec/StatusCommand.js +41 -2
  29. package/dist/scripts/cli/commands/spec/StatusCommand.js.map +1 -1
  30. package/dist/scripts/docs/build-site.js +1 -1
  31. package/dist/scripts/docs/journey-storyboard.js +358 -0
  32. package/dist/scripts/docs/journey-storyboard.js.map +1 -0
  33. package/dist/scripts/docs/nav.js +117 -5
  34. package/dist/scripts/docs/nav.js.map +1 -1
  35. package/dist/scripts/docs/record-journey.js +376 -0
  36. package/dist/scripts/docs/record-journey.js.map +1 -0
  37. package/dist/scripts/docs/record-terminal.js +252 -35
  38. package/dist/scripts/docs/record-terminal.js.map +1 -1
  39. package/dist/scripts/docs/terminal-storyboard.js +151 -2
  40. package/dist/scripts/docs/terminal-storyboard.js.map +1 -1
  41. package/dist/scripts/draft/brief-setup.js +363 -0
  42. package/dist/scripts/draft/brief-setup.js.map +1 -0
  43. package/dist/scripts/draft/confined-agent.js +175 -0
  44. package/dist/scripts/draft/confined-agent.js.map +1 -0
  45. package/dist/scripts/draft/drafting-guide.js +145 -0
  46. package/dist/scripts/draft/drafting-guide.js.map +1 -0
  47. package/dist/scripts/harness/init.js +33 -0
  48. package/dist/scripts/harness/init.js.map +1 -1
  49. package/dist/scripts/lib/surface.js +8 -1
  50. package/dist/scripts/lib/surface.js.map +1 -1
  51. package/package.json +3 -1
  52. package/templates/base/AGENTS.md.tpl +6 -0
  53. package/templates/harness/prompt-prefix.md.tpl +1 -1
@@ -1,11 +1,18 @@
1
1
  "use strict";
2
2
  /**
3
- * `specgate draft --check <change-id>` — is a draft complete enough to
4
- * review? (ADR-0029, docs/specs/draft-from-brief.md §4–5)
3
+ * `specgate draft` — from a plan or brief to a reviewed specification, one
4
+ * module at a time. (ADR-0029, docs/specs/draft-from-brief.md)
5
5
  *
6
- * Phase 1 of `draft`: the checklist, runnable on a change written by any
7
- * agent or person. Drafting it with the project's agent comes later; this is
8
- * what holds that draft — and any hand-written change — to the rules.
6
+ * specgate draft the brief's modules, where each stands, what next
7
+ * specgate draft <module> open the module's draft, or check it
8
+ * specgate draft <module> --agent "claude -p" have an agent write it, then check it
9
+ * specgate draft --check <id> the checklist on any change, as before
10
+ *
11
+ * Most projects start from a plan, and the path from that plan to a draft
12
+ * used to take four commands, a hand edit of `change.yaml` and a prompt the
13
+ * user had to write (#93, #94). `draft <module>` is the one command a person
14
+ * repeats: the first run opens the draft and leaves `DRAFTING.md` for
15
+ * whoever writes it; every run after that checks it and says what is next.
9
16
  */
10
17
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
18
  if (k2 === undefined) k2 = k;
@@ -41,8 +48,16 @@ var __importStar = (this && this.__importStar) || (function () {
41
48
  };
42
49
  })();
43
50
  Object.defineProperty(exports, "__esModule", { value: true });
44
- exports.DraftCommand = void 0;
51
+ exports.DraftCommand = exports.DEFAULT_BRIEF = void 0;
52
+ exports.draftModuleOf = draftModuleOf;
45
53
  exports.parseArgs = parseArgs;
54
+ exports.archivedAssumptions = archivedAssumptions;
55
+ exports.briefOf = briefOf;
56
+ exports.draftReport = draftReport;
57
+ exports.moduleStatus = moduleStatus;
58
+ exports.briefProgress = briefProgress;
59
+ exports.draftNextCommand = draftNextCommand;
60
+ exports.pasteLine = pasteLine;
46
61
  const fs = __importStar(require("node:fs"));
47
62
  const path = __importStar(require("node:path"));
48
63
  const command_1 = require("../../../lib/command");
@@ -50,27 +65,57 @@ const agent_1 = require("../../../lib/agent");
50
65
  const diagnostics_1 = require("../../../lib/diagnostics");
51
66
  const project_root_1 = require("../../../lib/project-root");
52
67
  const DraftChecklist_1 = require("../../../../packages/core/src/domain/DraftChecklist");
68
+ const ChangeWorkspace_1 = require("../../../../packages/core/src/infrastructure/ChangeWorkspace");
69
+ const ChangeTemplates_1 = require("../../../../packages/core/src/domain/ChangeTemplates");
70
+ const DiskLanguageRepository_1 = require("../../../../packages/core/src/infrastructure/DiskLanguageRepository");
71
+ const drafting_guide_1 = require("../../../draft/drafting-guide");
72
+ const confined_agent_1 = require("../../../draft/confined-agent");
73
+ const brief_setup_1 = require("../../../draft/brief-setup");
53
74
  const GherkinTags_1 = require("../../../../packages/core/src/domain/GherkinTags");
54
75
  const DerivedMatrixSources_1 = require("../../../../packages/core/src/infrastructure/DerivedMatrixSources");
55
76
  const CHANGES = path.join("docs", "specs", "changes");
77
+ /** Where `specgate draft` looks for the plan when `--brief` is not given. */
78
+ exports.DEFAULT_BRIEF = "docs/brief.md";
79
+ const DEFAULT_RESERVE = 10;
80
+ /** `draft-bookings` → `bookings`; any other change id → `--check <id>`. */
81
+ function draftModuleOf(changeId) {
82
+ return changeId.startsWith("draft-") ? changeId.slice("draft-".length) : `--check ${changeId}`;
83
+ }
56
84
  function usage() {
57
85
  return ("Usage:\n" +
58
- " specgate draft --check <change-id> [--brief <file>] [--max-requirements <n>] [--project-dir <dir>] [--json]\n\n" +
59
- "Checks a draft change against the checklist (ADR-0029):\n" +
86
+ " specgate draft your brief's modules, and the one to do next\n" +
87
+ " specgate draft <module> open that module's draft — or check it, once it exists\n" +
88
+ ' specgate draft <module> --agent "claude -p"\n' +
89
+ " have your agent write it, then check it\n" +
90
+ " specgate draft --check <change-id> the checklist on any change\n" +
91
+ " specgate draft --new-brief write docs/brief.md to fill in, when you have none\n" +
92
+ ' specgate draft --agent "claude -p" have your agent propose the brief\'s front matter\n\n' +
93
+ " --brief <file> Your plan or brief (default: docs/brief.md). Any markdown; a front\n" +
94
+ " matter with modules, actors and surfaces lets the checklist do more.\n" +
95
+ " --reserve <n> Requirement ids a new draft reserves (default 10, at most the --max-requirements).\n" +
96
+ " --agent <cmd> Your agent's command. It gets the drafting guide on standard input,\n" +
97
+ " or in a file if the command contains {prompt_file}. Whatever it writes\n" +
98
+ " outside the draft is undone.\n" +
99
+ " [--max-requirements <n>] [--project-dir <dir>] [--json]\n\n" +
100
+ "The checklist (ADR-0029):\n" +
60
101
  " D1 every requirement has a scenario D5 a cited law or standard names its source\n" +
61
102
  " D2 every requirement has a kind D6 a stated value is in the brief or an assumption\n" +
62
103
  " D3 a scenario per surface each actor uses D7 an open question names what it blocks\n" +
63
104
  " D4 a non-functional requirement is measured D8 the draft is small enough to review\n" +
64
105
  " D9 every scenario passes the harness gate's rules\n" +
65
- " D10 no id the project already uses\n\n" +
66
- " --brief <file> The brief the draft came from. Without it D3 and D6 cannot\n" +
67
- " run, and the report says so. Also read from `brief:` in change.yaml.\n");
106
+ " D10 no id the project already uses\n" +
107
+ " D12 an actor can get the ids it acts on\n\n" +
108
+ "Without a brief, D3 and D6 cannot run, and the report says so.\n");
68
109
  }
69
110
  function parseArgs(argv) {
70
111
  const o = {
71
112
  check: "",
113
+ module: "",
72
114
  brief: "",
73
115
  maxRequirements: 25,
116
+ reserve: DEFAULT_RESERVE,
117
+ agent: "",
118
+ newBrief: false,
74
119
  projectDir: ".",
75
120
  json: (0, agent_1.wantsJson)(argv),
76
121
  };
@@ -88,6 +133,12 @@ function parseArgs(argv) {
88
133
  o.brief = value();
89
134
  else if (a === "--max-requirements")
90
135
  o.maxRequirements = Number(value());
136
+ else if (a === "--reserve")
137
+ o.reserve = Number(value());
138
+ else if (a === "--agent")
139
+ o.agent = value();
140
+ else if (a === "--new-brief")
141
+ o.newBrief = true;
91
142
  else if (a === "--project-dir")
92
143
  o.projectDir = value();
93
144
  else if (a === "--json")
@@ -96,15 +147,29 @@ function parseArgs(argv) {
96
147
  process.stdout.write(usage());
97
148
  process.exit(0);
98
149
  }
150
+ else if (!a.startsWith("-") && !o.module)
151
+ o.module = a;
99
152
  else
100
153
  throw new Error(`unknown argument ${a}`);
101
154
  }
102
- if (!o.check) {
103
- throw new Error("--check <change-id> is required (drafting with an agent is not available yet)");
155
+ if (o.check && o.module) {
156
+ throw new Error("give a module or --check <change-id>, not both");
157
+ }
158
+ if (o.module && !ChangeWorkspace_1.CHANGE_ID_RE.test(o.module)) {
159
+ throw new Error(`"${o.module}" is not a module name: use lowercase words and dashes, e.g. bookings`);
104
160
  }
105
161
  if (!Number.isInteger(o.maxRequirements) || o.maxRequirements < 1) {
106
162
  throw new Error("--max-requirements needs a positive integer");
107
163
  }
164
+ if (!Number.isInteger(o.reserve) || o.reserve < 1 || o.reserve > o.maxRequirements) {
165
+ throw new Error(`--reserve needs a whole number from 1 to ${o.maxRequirements}`);
166
+ }
167
+ if (o.newBrief && (o.module || o.check || o.agent)) {
168
+ throw new Error("--new-brief only writes the brief: run it on its own");
169
+ }
170
+ if (o.agent && o.check) {
171
+ throw new Error("--agent drafts a module, or proposes the brief's front matter — not --check");
172
+ }
108
173
  return o;
109
174
  }
110
175
  function specFiles(dir, rel = "") {
@@ -170,6 +235,231 @@ function projectIds(projectDir) {
170
235
  return owners;
171
236
  }
172
237
  const readIf = (file) => (fs.existsSync(file) ? fs.readFileSync(file, "utf8") : undefined);
238
+ /** `assumptions.md` of every archived change, for MODIFIED requirements (#81). */
239
+ function archivedAssumptions(projectDir) {
240
+ const archive = path.join(projectDir, CHANGES, "archive");
241
+ let dirs = [];
242
+ try {
243
+ dirs = fs.readdirSync(archive).sort();
244
+ }
245
+ catch {
246
+ return "";
247
+ }
248
+ return dirs
249
+ .map((d) => readIf(path.join(archive, d, "assumptions.md")) || "")
250
+ .filter(Boolean)
251
+ .join("\n");
252
+ }
253
+ /** Whether a change was opened from a brief (`brief:` in change.yaml): a draft. */
254
+ function briefOf(projectDir, changeId) {
255
+ const yaml = readIf(path.join(projectDir, CHANGES, changeId, "change.yaml")) || "";
256
+ const m = /^brief:\s*["']?([^"'\n]+)["']?\s*$/m.exec(yaml);
257
+ return m ? m[1].trim() : undefined;
258
+ }
259
+ /**
260
+ * The checklist on one change, as `draft --check` runs it. Shared with
261
+ * `change archive`, which refuses a draft that does not pass it (#57).
262
+ */
263
+ function draftReport(projectDir, changeId, briefOption = "", maxRequirements = 25) {
264
+ const changeDir = path.join(projectDir, CHANGES, changeId);
265
+ if (!fs.existsSync(changeDir)) {
266
+ return {
267
+ failure: (0, diagnostics_1.error)("draft_no_change", `No change '${changeId}' under ${CHANGES}/.`, {
268
+ fix: "specgate change list — or open one with `specgate change new <id>`.",
269
+ }),
270
+ };
271
+ }
272
+ const specs = path.join(changeDir, "specs");
273
+ const deltas = specFiles(specs).map((rel) => ({
274
+ path: path.join(CHANGES, changeId, "specs", rel).split(path.sep).join("/"),
275
+ source: fs.readFileSync(path.join(specs, rel), "utf8"),
276
+ }));
277
+ if (deltas.length === 0) {
278
+ return {
279
+ failure: (0, diagnostics_1.error)("draft_no_specs", `Change '${changeId}' has no specs/<capability>/spec.md to check.`),
280
+ };
281
+ }
282
+ // The brief: --brief, else `brief: <path>` in change.yaml.
283
+ const briefPath = briefOption || briefOf(projectDir, changeId);
284
+ let brief;
285
+ if (briefPath) {
286
+ brief = readIf(path.resolve(projectDir, briefPath));
287
+ if (brief === undefined) {
288
+ return { failure: (0, diagnostics_1.error)("draft_no_brief", `The brief ${briefPath} does not exist.`) };
289
+ }
290
+ }
291
+ return {
292
+ report: (0, DraftChecklist_1.checkDraft)({
293
+ deltas,
294
+ brief,
295
+ assumptions: readIf(path.join(changeDir, "assumptions.md")),
296
+ archivedAssumptions: archivedAssumptions(projectDir),
297
+ specText: projectSpecText(projectDir),
298
+ idsInUse: projectIds(projectDir),
299
+ specs: projectSpecs(projectDir),
300
+ questions: readIf(path.join(changeDir, "questions.md")),
301
+ maxRequirements,
302
+ }),
303
+ };
304
+ }
305
+ /** The placeholder `templateDelta` writes: a draft nobody has written yet. */
306
+ const PLACEHOLDER = "feature=features/<area>/<name>.feature";
307
+ function deltaSources(projectDir, changeId) {
308
+ const specs = path.join(projectDir, CHANGES, changeId, "specs");
309
+ return specFiles(specs).map((rel) => fs.readFileSync(path.join(specs, rel), "utf8"));
310
+ }
311
+ function moduleStatus(projectDir, module, brief = "") {
312
+ const change = `draft-${module}`;
313
+ const archived = (0, ChangeWorkspace_1.listArchivedIds)(projectDir).find((id) => id === change || id.endsWith(`-${change}`));
314
+ const base = { module, change, findings: 0 };
315
+ if (!fs.existsSync(path.join(projectDir, CHANGES, change))) {
316
+ return archived
317
+ ? { ...base, state: "archived", archivedAs: archived }
318
+ : { ...base, state: "not started" };
319
+ }
320
+ const deltas = deltaSources(projectDir, change);
321
+ if (deltas.length === 0 || deltas.every((d) => d.includes(PLACEHOLDER))) {
322
+ return { ...base, state: "not written" };
323
+ }
324
+ const result = draftReport(projectDir, change, brief);
325
+ if ("failure" in result)
326
+ return { ...base, state: "drafting", findings: 1 };
327
+ const findings = result.report.status.length;
328
+ return { ...base, state: findings === 0 ? "ready" : "drafting", findings };
329
+ }
330
+ /**
331
+ * Where the brief stands, for `status`: its modules, and the first one not
332
+ * yet archived. Null when there is no brief, or it lists no modules.
333
+ */
334
+ function briefProgress(projectDir) {
335
+ const text = readIf(path.join(projectDir, exports.DEFAULT_BRIEF));
336
+ if (text === undefined)
337
+ return null;
338
+ const modules = (0, DraftChecklist_1.briefModules)(text).map((m) => moduleStatus(projectDir, m));
339
+ if (modules.length === 0)
340
+ return null;
341
+ return { brief: exports.DEFAULT_BRIEF, modules, next: modules.find((m) => m.state !== "archived") };
342
+ }
343
+ /** What to run for this module, given where it stands. */
344
+ function draftNextCommand(s) {
345
+ return s.state === "ready" ? `specgate change archive ${s.change}` : `specgate draft ${s.module}`;
346
+ }
347
+ /** `docs/specs/BRIEF-SETUP.md`: the guide an agent follows to propose the front matter. */
348
+ function writeSetupGuide(projectDir, brief) {
349
+ const file = path.join(projectDir, brief_setup_1.BRIEF_SETUP_FILE);
350
+ fs.mkdirSync(path.dirname(file), { recursive: true });
351
+ fs.writeFileSync(file, (0, brief_setup_1.briefSetupGuide)(brief), "utf8");
352
+ }
353
+ /** The next free SCN number, past every scenario the project or a change uses. */
354
+ function nextScenario(projectDir) {
355
+ let max = 0;
356
+ for (const id of projectIds(projectDir).keys()) {
357
+ const m = /^SCN-(\d+)$/.exec(id);
358
+ if (m)
359
+ max = Math.max(max, Number(m[1]));
360
+ }
361
+ const walk = (dir) => {
362
+ let entries = [];
363
+ try {
364
+ entries = fs.readdirSync(dir, { withFileTypes: true });
365
+ }
366
+ catch {
367
+ return;
368
+ }
369
+ for (const e of entries) {
370
+ const full = path.join(dir, e.name);
371
+ if (e.isDirectory())
372
+ walk(full);
373
+ else if (e.name.endsWith(".md") || e.name.endsWith(".feature")) {
374
+ for (const m of fs.readFileSync(full, "utf8").matchAll(/\bSCN-(\d+)\b/g)) {
375
+ max = Math.max(max, Number(m[1]));
376
+ }
377
+ }
378
+ }
379
+ };
380
+ walk(path.join(projectDir, "docs", "specs"));
381
+ walk(path.join(projectDir, "features"));
382
+ for (const m of (readIf(path.join(projectDir, "spec.md")) || "").matchAll(/\bSCN-(\d+)\b/g)) {
383
+ max = Math.max(max, Number(m[1]));
384
+ }
385
+ return `SCN-${String(max + 1).padStart(3, "0")}`;
386
+ }
387
+ /** The one line to paste into any agent. */
388
+ function pasteLine(module) {
389
+ return `Draft the ${module} module: follow ${CHANGES.split(path.sep).join("/")}/draft-${module}/DRAFTING.md`;
390
+ }
391
+ function proposalFor(module, changeId, brief) {
392
+ return [
393
+ `# Proposal: ${changeId}`,
394
+ "",
395
+ "## Intent",
396
+ "",
397
+ `The ${module} module of the brief (\`${brief}\`), written down as requirements and`,
398
+ "scenarios a person can review before anything is built.",
399
+ "",
400
+ "## Scope",
401
+ "",
402
+ `In scope: what the brief says about ${module}.`,
403
+ "",
404
+ "Out of scope: every other module of the brief.",
405
+ "",
406
+ ].join("\n");
407
+ }
408
+ /** Open `draft-<module>`: the change, linked to the brief, with its guide. */
409
+ function openDraft(projectDir, module, brief, o) {
410
+ const changeId = `draft-${module}`;
411
+ const dir = path.join(projectDir, CHANGES, changeId);
412
+ const reqRange = (0, ChangeWorkspace_1.reserveReqRange)(projectDir, o.reserve);
413
+ const config = {
414
+ ...ChangeWorkspace_1.DEFAULT_CONFIG,
415
+ created: new Date().toISOString().slice(0, 10),
416
+ rigor: "lite",
417
+ req_range: reqRange,
418
+ };
419
+ const briefText = fs.readFileSync(path.resolve(projectDir, brief), "utf8");
420
+ const created = [];
421
+ const write = (rel, contents) => {
422
+ const full = path.join(dir, rel);
423
+ fs.mkdirSync(path.dirname(full), { recursive: true });
424
+ fs.writeFileSync(full, contents, "utf8");
425
+ created.push(path.join(CHANGES, changeId, rel).split(path.sep).join("/"));
426
+ };
427
+ write("DRAFTING.md", (0, drafting_guide_1.draftingGuide)({
428
+ module,
429
+ changeId,
430
+ brief,
431
+ reqRange,
432
+ firstScenario: nextScenario(projectDir),
433
+ maxRequirements: o.maxRequirements,
434
+ actors: (0, DraftChecklist_1.actorSurfaces)(briefText),
435
+ }));
436
+ write(path.join("specs", module, "spec.md"), (0, ChangeTemplates_1.templateDelta)(module, reqRange[0], (0, DiskLanguageRepository_1.phrases)(projectDir)));
437
+ write("change.yaml", `${(0, ChangeWorkspace_1.renderConfig)(config).trimEnd()}\nbrief: ${brief}\n`);
438
+ write("proposal.md", proposalFor(module, changeId, brief));
439
+ return { changeId, reqRange, created };
440
+ }
441
+ const ICON = {
442
+ "not started": "○",
443
+ "not written": "○",
444
+ drafting: "✎",
445
+ ready: "✔",
446
+ archived: "✔",
447
+ };
448
+ function describe(s) {
449
+ if (s.state === "drafting")
450
+ return `drafting — ${s.findings} finding(s) to fix`;
451
+ if (s.state === "ready")
452
+ return "ready to review";
453
+ if (s.state === "archived")
454
+ return `archived (${s.archivedAs})`;
455
+ if (s.state === "not written")
456
+ return "opened, not written yet";
457
+ return "not started";
458
+ }
459
+ /** What to run after this module, given where it stands. */
460
+ function nextFor(s) {
461
+ return s ? draftNextCommand(s) : "specgate status";
462
+ }
173
463
  class DraftCommand extends command_1.BaseCommand {
174
464
  execute() {
175
465
  const io0 = (0, agent_1.agentIo)((0, agent_1.wantsJson)(this.args));
@@ -183,59 +473,405 @@ class DraftCommand extends command_1.BaseCommand {
183
473
  ]);
184
474
  }
185
475
  const opts = o;
476
+ if (opts.check)
477
+ return this.check(opts);
478
+ if (opts.module)
479
+ return this.module(opts);
480
+ return this.overview(opts);
481
+ }
482
+ /** The brief's path and text, or the failure that says where to put it. */
483
+ brief(projectDir, opts, linked) {
484
+ const rel = opts.brief || linked || exports.DEFAULT_BRIEF;
485
+ const text = readIf(path.resolve(projectDir, rel));
486
+ if (text !== undefined)
487
+ return { rel, text };
488
+ const io = (0, agent_1.agentIo)(opts.json);
489
+ io.fail({ draft: null }, [
490
+ (0, diagnostics_1.error)("draft_no_brief", `No plan or brief at ${rel}.`, {
491
+ fix: `Save your plan or brief as ${exports.DEFAULT_BRIEF} — any markdown — then run \`specgate draft\`. ` +
492
+ "Somewhere else: --brief <file>.",
493
+ }),
494
+ ]);
495
+ }
496
+ // ── specgate draft ──────────────────────────────────────────────────────────
497
+ overview(opts) {
186
498
  const io = (0, agent_1.agentIo)(opts.json);
187
499
  const projectDir = (0, project_root_1.resolveProjectDir)(opts.projectDir);
188
- const changeDir = path.join(projectDir, CHANGES, opts.check);
189
- if (!fs.existsSync(changeDir)) {
500
+ const rel = opts.brief || exports.DEFAULT_BRIEF;
501
+ const file = path.resolve(projectDir, rel);
502
+ if (opts.newBrief)
503
+ return this.newBrief(opts, file, rel);
504
+ const text = readIf(file);
505
+ // (a) No brief: what one is, and how to get one.
506
+ if (text === undefined) {
507
+ if (!opts.json) {
508
+ process.stdout.write(`\n${(0, brief_setup_1.noBriefExplanation)(rel)}\n`);
509
+ process.exitCode = 1;
510
+ return;
511
+ }
512
+ io.fail({ draft: null }, [
513
+ (0, diagnostics_1.error)("draft_no_brief", `No plan or brief at ${rel}.`, {
514
+ fix: `Save your plan or brief as ${rel} — any markdown — then run \`specgate draft\`. ` +
515
+ "No plan yet: `specgate draft --new-brief` writes one to fill in. Somewhere else: --brief <file>.",
516
+ }),
517
+ ]);
518
+ }
519
+ const brief = { rel, text: text };
520
+ if (opts.agent)
521
+ return this.frontMatterWithAgent(opts, projectDir, brief);
522
+ // (b) No front matter: every field, explained, and the agent to propose it.
523
+ if ((0, brief_setup_1.frontMatter)(brief.text) === null) {
524
+ writeSetupGuide(projectDir, rel);
525
+ io.emit({
526
+ draft: {
527
+ brief: rel,
528
+ frontMatter: null,
529
+ guide: brief_setup_1.BRIEF_SETUP_FILE,
530
+ prompt: brief_setup_1.BRIEF_SETUP_PASTE,
531
+ next: 'specgate draft --agent "claude -p"',
532
+ },
533
+ }, () => process.stdout.write(`\n specgate draft · ${rel}\n\n` +
534
+ (0, brief_setup_1.frontMatterExplanation)(rel) +
535
+ "\n Or let your agent propose it from your brief. Paste this into Claude Code,\n" +
536
+ " Cursor, Codex…:\n\n" +
537
+ ` ${brief_setup_1.BRIEF_SETUP_PASTE}\n\n` +
538
+ ' or let Specgate call it: specgate draft --agent "claude -p"\n\n' +
539
+ " Either way, review the four fields, then run specgate draft again.\n\n"));
540
+ return;
541
+ }
542
+ // (c) A front matter with problems: each in plain words, with its fix.
543
+ const problems = (0, brief_setup_1.checkFrontMatter)(brief.text);
544
+ if (problems.length > 0) {
545
+ io.emit({
546
+ draft: { brief: rel, frontMatter: "invalid", next: `edit ${rel}, then specgate draft` },
547
+ status: problems.map((p) => (0, diagnostics_1.error)(p.code, p.message, { fix: p.fix })),
548
+ }, () => {
549
+ const out = process.stdout;
550
+ out.write(`\n specgate draft · ${rel}\n\n`);
551
+ out.write(` The front matter at the top of ${rel} needs ${problems.length} fix(es):\n\n`);
552
+ for (const p of problems)
553
+ out.write(` ✖ ${p.message}\n ${p.fix}\n`);
554
+ out.write(`\n Next fix them in ${rel}, then specgate draft\n\n`);
555
+ });
556
+ process.exitCode = 1;
557
+ return;
558
+ }
559
+ // (d) Ready: the modules, where each stands, what is next.
560
+ const modules = (0, DraftChecklist_1.briefModules)(brief.text);
561
+ const states = modules.map((m) => moduleStatus(projectDir, m, rel));
562
+ const pending = states.find((s) => s.state !== "archived");
563
+ const next = nextFor(pending);
564
+ const product = /^product:\s*(.+)$/m.exec((0, brief_setup_1.frontMatter)(brief.text) || "");
565
+ io.emit({ draft: { brief: rel, modules: states, next } }, () => {
566
+ const out = process.stdout;
567
+ out.write(`\n specgate draft · ${rel}\n\n`);
568
+ if (product)
569
+ out.write(` Product ${product[1].trim()}\n\n`);
570
+ const width = Math.max(...modules.map((m) => m.length));
571
+ out.write(" Modules\n");
572
+ for (const s of states) {
573
+ out.write(` ${ICON[s.state]} ${s.module.padEnd(width)} ${describe(s)}\n`);
574
+ }
575
+ out.write(pending
576
+ ? `\n Next ${next}\n\n`
577
+ : `\n Every module is drafted and archived.\n Next ${next} — what to build first\n\n`);
578
+ });
579
+ }
580
+ /** `--new-brief`: the template, never over a brief that exists. */
581
+ newBrief(opts, file, rel) {
582
+ const io = (0, agent_1.agentIo)(opts.json);
583
+ if (fs.existsSync(file)) {
190
584
  io.fail({ draft: null }, [
191
- (0, diagnostics_1.error)("draft_no_change", `No change '${opts.check}' under ${CHANGES}/.`, {
192
- fix: "specgate change list — or open one with `specgate change new <id>`.",
585
+ (0, diagnostics_1.error)("draft_brief_exists", `${rel} already exists — nothing was written.`, {
586
+ fix: "Edit it, then run `specgate draft`; it says what the top of the brief still needs.",
193
587
  }),
194
588
  ]);
195
589
  }
196
- const specs = path.join(changeDir, "specs");
197
- const deltas = specFiles(specs).map((rel) => ({
198
- path: path.join(CHANGES, opts.check, "specs", rel).split(path.sep).join("/"),
199
- source: fs.readFileSync(path.join(specs, rel), "utf8"),
200
- }));
201
- if (deltas.length === 0) {
590
+ fs.mkdirSync(path.dirname(file), { recursive: true });
591
+ fs.writeFileSync(file, brief_setup_1.BRIEF_TEMPLATE, "utf8");
592
+ io.emit({ draft: { brief: rel, written: true, next: "specgate draft" } }, () => process.stdout.write(`\n ✔ Wrote ${rel} — a brief to fill in.\n\n` +
593
+ " Fill it in, in your own words:\n" +
594
+ " 1. The four fields at the top — product, surfaces, actors, modules.\n" +
595
+ " Each has an example in the comment above it.\n" +
596
+ " 2. The sections below — what the product is, who uses it and how, what\n" +
597
+ " each one must be able to do, the rules and numbers you already know,\n" +
598
+ " what you don't know yet, and what is out of scope.\n\n" +
599
+ " Have a plan or an email already? Paste it under the four fields instead\n" +
600
+ " of the sections — Specgate reads it as it is.\n\n" +
601
+ " Next specgate draft — it checks the four fields and says what is next\n\n"));
602
+ }
603
+ /**
604
+ * `draft --agent` without a module: the agent proposes the front matter.
605
+ * It may write only the brief, and the brief below the front matter must
606
+ * come back byte for byte — it is the client's words, and every draft is
607
+ * checked against them.
608
+ */
609
+ frontMatterWithAgent(opts, projectDir, brief) {
610
+ const io = (0, agent_1.agentIo)(opts.json);
611
+ if ((0, brief_setup_1.frontMatter)(brief.text) !== null && (0, brief_setup_1.checkFrontMatter)(brief.text).length === 0) {
612
+ const first = (0, DraftChecklist_1.briefModules)(brief.text)[0];
613
+ io.emit({ draft: { brief: brief.rel, frontMatter: "ok", next: `specgate draft ${first}` } }, () => process.stdout.write(`\n The front matter of ${brief.rel} is already there and complete — nothing for the agent to do.\n` +
614
+ ` Next specgate draft ${first}\n\n`));
615
+ return;
616
+ }
617
+ writeSetupGuide(projectDir, brief.rel);
618
+ const own = path
619
+ .relative(projectDir, path.resolve(projectDir, brief.rel))
620
+ .split(path.sep)
621
+ .join("/");
622
+ if (!opts.json) {
623
+ process.stdout.write(`\n → ${opts.agent} is proposing the front matter of ${brief.rel}…\n\n`);
624
+ }
625
+ const run = (0, confined_agent_1.runConfinedAgent)({
626
+ projectDir,
627
+ changeId: "brief",
628
+ allowed: (rel) => rel === own,
629
+ command: opts.agent,
630
+ prompt: (0, brief_setup_1.briefSetupPrompt)((0, brief_setup_1.briefSetupGuide)(brief.rel)),
631
+ showOutput: !opts.json,
632
+ });
633
+ if (run.problem === "no_git") {
634
+ io.fail({ draft: null }, [
635
+ (0, diagnostics_1.error)("draft_agent_needs_git", "--agent needs a git repository.", {
636
+ fix: "Git is how Specgate finds what the agent wrote and undoes anything else. Run `git init`, " +
637
+ `then try again — or paste this into your agent yourself: ${brief_setup_1.BRIEF_SETUP_PASTE}`,
638
+ }),
639
+ ]);
640
+ }
641
+ if (run.reverted.length > 0 && !opts.json) {
642
+ process.stdout.write(`\n ↺ Undone — the agent wrote outside the brief: ${run.reverted.join(", ")}\n`);
643
+ }
644
+ const after = readIf(path.resolve(projectDir, brief.rel)) ?? "";
645
+ if ((0, brief_setup_1.briefBody)(after) !== (0, brief_setup_1.briefBody)(brief.text)) {
646
+ fs.writeFileSync(path.resolve(projectDir, brief.rel), brief.text, "utf8");
647
+ io.fail({ draft: null }, [
648
+ (0, diagnostics_1.error)("draft_brief_body_changed", "The agent changed the brief itself, not only its front matter — undone.", {
649
+ fix: `${brief.rel} is back as it was. Try again, or write the four fields yourself (\`specgate draft\` shows them).`,
650
+ }),
651
+ ]);
652
+ }
653
+ if (!run.ok) {
654
+ io.fail({ draft: null }, [
655
+ (0, diagnostics_1.error)("draft_agent_failed", `The agent ${run.detail}.`, {
656
+ fix: `Check the command runs on its own, or paste this into your agent: ${brief_setup_1.BRIEF_SETUP_PASTE}`,
657
+ }),
658
+ ]);
659
+ }
660
+ const proposed = (0, brief_setup_1.frontMatter)(after);
661
+ if (proposed === null) {
202
662
  io.fail({ draft: null }, [
203
- (0, diagnostics_1.error)("draft_no_specs", `Change '${opts.check}' has no specs/<capability>/spec.md to check.`),
663
+ (0, diagnostics_1.error)("draft_no_front_matter", "The agent wrote no front matter.", {
664
+ fix: "Run `specgate draft` to see the four fields, and write them yourself.",
665
+ }),
204
666
  ]);
205
667
  }
206
- // The brief: --brief, else `brief: <path>` in change.yaml.
207
- let briefPath = opts.brief;
208
- if (!briefPath) {
209
- const yaml = readIf(path.join(changeDir, "change.yaml")) || "";
210
- const m = /^brief:\s*["']?([^"'\n]+)["']?\s*$/m.exec(yaml);
211
- if (m)
212
- briefPath = m[1].trim();
668
+ const problems = (0, brief_setup_1.checkFrontMatter)(after);
669
+ io.emit({
670
+ draft: { brief: brief.rel, frontMatter: proposed, next: "specgate draft" },
671
+ status: problems.map((p) => (0, diagnostics_1.error)(p.code, p.message, { fix: p.fix })),
672
+ }, () => {
673
+ const out = process.stdout;
674
+ out.write(`\n The agent proposed this, at the top of ${brief.rel}:\n\n`);
675
+ for (const l of ["---", ...proposed.split("\n"), "---"])
676
+ out.write(` ${l}\n`);
677
+ out.write("\n Your brief below it is unchanged.\n");
678
+ if (problems.length > 0) {
679
+ out.write(`\n It still needs ${problems.length} fix(es):\n`);
680
+ for (const p of problems)
681
+ out.write(` ✖ ${p.message}\n ${p.fix}\n`);
682
+ }
683
+ out.write("\n Review it — change anything — then specgate draft\n\n");
684
+ });
685
+ }
686
+ // ── specgate draft <module> ─────────────────────────────────────────────────
687
+ module(opts) {
688
+ const io = (0, agent_1.agentIo)(opts.json);
689
+ const projectDir = (0, project_root_1.resolveProjectDir)(opts.projectDir);
690
+ const changeId = `draft-${opts.module}`;
691
+ const status = moduleStatus(projectDir, opts.module);
692
+ if (status.state === "archived") {
693
+ io.emit({
694
+ draft: {
695
+ module: opts.module,
696
+ change: changeId,
697
+ state: "archived",
698
+ next: "specgate draft",
699
+ },
700
+ }, () => process.stdout.write(`\n ✔ ${opts.module} is already drafted and archived (${status.archivedAs}).\n\n` +
701
+ " Next specgate draft — the next module of the brief, or what to build first\n\n"));
702
+ return;
213
703
  }
214
- let brief;
215
- if (briefPath) {
216
- brief = readIf(path.resolve(projectDir, briefPath));
217
- if (brief === undefined) {
704
+ if (status.state === "not started") {
705
+ if (!fs.existsSync(path.join(projectDir, "spec.md"))) {
218
706
  io.fail({ draft: null }, [
219
- (0, diagnostics_1.error)("draft_no_brief", `The brief ${briefPath} does not exist.`),
707
+ (0, diagnostics_1.error)("draft_no_project", "This folder is not a Specgate project yet.", {
708
+ fix: "Run `specgate init` first, then `specgate draft " + opts.module + "`.",
709
+ }),
220
710
  ]);
221
711
  }
712
+ const brief = this.brief(projectDir, opts);
713
+ const listed = (0, DraftChecklist_1.briefModules)(brief.text);
714
+ const opened = openDraft(projectDir, opts.module, brief.rel, opts);
715
+ const notes = listed.length > 0 && !listed.includes(opts.module)
716
+ ? [
717
+ (0, diagnostics_1.info)("draft_module_not_in_brief", `${opts.module} is not among the brief's modules (${listed.join(", ")}).`, {
718
+ fix: "Add it to `modules:` in the brief's front matter, so `specgate draft` lists it.",
719
+ }),
720
+ ]
721
+ : [];
722
+ if (opts.agent)
723
+ return this.withAgent(opts, projectDir, brief.rel, opened.created);
724
+ io.emit({
725
+ draft: {
726
+ module: opts.module,
727
+ change: opened.changeId,
728
+ state: "not written",
729
+ brief: brief.rel,
730
+ reserved: opened.reqRange,
731
+ created: opened.created,
732
+ prompt: pasteLine(opts.module),
733
+ next: `specgate draft ${opts.module}`,
734
+ },
735
+ status: notes,
736
+ }, () => {
737
+ const out = process.stdout;
738
+ const dir = `${CHANGES.split(path.sep).join("/")}/${opened.changeId}`;
739
+ out.write(`\n ✔ Draft ${opened.changeId} opened, linked to ${brief.rel} ` +
740
+ `(REQ ids ${opened.reqRange[0]}…${opened.reqRange[1]} reserved)\n\n` +
741
+ [
742
+ [`${dir}/DRAFTING.md`, "how to write it"],
743
+ [`${dir}/specs/${opts.module}/spec.md`, "the requirements go here"],
744
+ [`${dir}/change.yaml, proposal.md`, ""],
745
+ ]
746
+ .map(([f, why], _i, all) => {
747
+ const w = Math.max(...all.map((r) => r[0].length));
748
+ return ` + ${why ? f.padEnd(w) + " " + why : f}\n`;
749
+ })
750
+ .join("") +
751
+ "\n" +
752
+ " Have your agent write it. Paste this into Claude Code, Cursor, Codex…:\n\n" +
753
+ ` ${pasteLine(opts.module)}\n\n` +
754
+ ` or let Specgate call it: specgate draft ${opts.module} --agent "claude -p"\n\n` +
755
+ ` Then run specgate draft ${opts.module} until it says Ready to review.\n\n`);
756
+ for (const n of notes)
757
+ out.write(` ℹ ${n.message}\n ${n.fix}\n\n`);
758
+ });
759
+ return;
222
760
  }
223
- const report = (0, DraftChecklist_1.checkDraft)({
224
- deltas,
225
- brief,
226
- assumptions: readIf(path.join(changeDir, "assumptions.md")),
227
- specText: projectSpecText(projectDir),
228
- idsInUse: projectIds(projectDir),
229
- specs: projectSpecs(projectDir),
230
- questions: readIf(path.join(changeDir, "questions.md")),
231
- maxRequirements: opts.maxRequirements,
761
+ const brief = this.brief(projectDir, opts, briefOf(projectDir, changeId));
762
+ if (opts.agent)
763
+ return this.withAgent(opts, projectDir, brief.rel, []);
764
+ this.report(opts, projectDir, brief.rel);
765
+ }
766
+ /** Hand the guide to the agent, undo what it writes elsewhere, then check. */
767
+ withAgent(opts, projectDir, brief, created) {
768
+ const io = (0, agent_1.agentIo)(opts.json);
769
+ const changeId = `draft-${opts.module}`;
770
+ const guide = readIf(path.join(projectDir, CHANGES, changeId, "DRAFTING.md")) || "";
771
+ if (!opts.json) {
772
+ process.stdout.write(`\n ${created.length ? `✔ Draft ${changeId} opened, linked to ${brief}.\n ` : ""}` +
773
+ `→ ${opts.agent} is drafting ${opts.module}…\n\n`);
774
+ }
775
+ const run = (0, confined_agent_1.runConfinedAgent)({
776
+ projectDir,
777
+ changeId,
778
+ command: opts.agent,
779
+ prompt: (0, drafting_guide_1.draftingPrompt)(opts.module, changeId, guide),
780
+ showOutput: !opts.json,
232
781
  });
233
- const status = [
234
- ...report.status,
235
- ...report.skipped.map((s) => (0, diagnostics_1.info)("draft_rule_skipped", `${s.rule} did not run: ${s.why}.`, {
236
- fix: "Pass --brief <file>, with actors and their surfaces in its front matter for D3.",
237
- })),
238
- ];
782
+ if (run.problem === "no_git") {
783
+ io.fail({ draft: null }, [
784
+ (0, diagnostics_1.error)("draft_agent_needs_git", "--agent needs a git repository.", {
785
+ fix: "Git is how Specgate finds what the agent wrote and undoes anything outside the draft. " +
786
+ "Run `git init`, then `specgate draft " +
787
+ opts.module +
788
+ ' --agent "…"` again — or paste the prompt into your agent yourself.',
789
+ }),
790
+ ]);
791
+ }
792
+ if (run.reverted.length > 0 && !opts.json) {
793
+ process.stdout.write(`\n ↺ Undone — the agent wrote outside the draft: ${run.reverted.join(", ")}\n`);
794
+ }
795
+ if (!run.ok) {
796
+ io.fail({ draft: null }, [
797
+ (0, diagnostics_1.error)("draft_agent_failed", `The agent ${run.detail}.`, {
798
+ fix: `Check the command runs on its own, or paste this into your agent: ${pasteLine(opts.module)}`,
799
+ }),
800
+ ]);
801
+ }
802
+ this.report(opts, projectDir, brief, run.reverted);
803
+ }
804
+ /** The checklist on `draft-<module>`, and what comes next. */
805
+ report(opts, projectDir, brief, reverted = []) {
806
+ const io = (0, agent_1.agentIo)(opts.json);
807
+ const changeId = `draft-${opts.module}`;
808
+ const status = moduleStatus(projectDir, opts.module, brief);
809
+ if (status.state === "not written") {
810
+ io.emit({
811
+ draft: {
812
+ module: opts.module,
813
+ change: changeId,
814
+ state: status.state,
815
+ brief,
816
+ prompt: pasteLine(opts.module),
817
+ next: `specgate draft ${opts.module}`,
818
+ },
819
+ }, () => process.stdout.write(`\n specgate draft ${opts.module} · ${brief}\n\n` +
820
+ " The draft is still the placeholder. Have your agent write it:\n\n" +
821
+ ` ${pasteLine(opts.module)}\n\n` +
822
+ ` or: specgate draft ${opts.module} --agent "claude -p"\n\n`));
823
+ process.exitCode = 1;
824
+ return;
825
+ }
826
+ const result = draftReport(projectDir, changeId, brief, opts.maxRequirements);
827
+ if ("failure" in result) {
828
+ io.fail({ draft: null }, [result.failure]);
829
+ return;
830
+ }
831
+ const report = result.report;
832
+ const diags = [...report.status, ...skippedNotes(report)];
833
+ const errors = report.status.length;
834
+ const next = errors === 0 ? `specgate change archive ${changeId}` : `specgate draft ${opts.module}`;
835
+ io.emit({
836
+ draft: {
837
+ module: opts.module,
838
+ change: changeId,
839
+ state: errors === 0 ? "ready" : "drafting",
840
+ brief,
841
+ requirements: report.requirements,
842
+ scenarios: report.scenarios,
843
+ skipped: report.skipped,
844
+ reverted,
845
+ next,
846
+ },
847
+ status: diags,
848
+ }, () => {
849
+ const out = process.stdout;
850
+ out.write(`\n specgate draft ${opts.module} · ${brief}\n\n`);
851
+ writeFindings(report, diags);
852
+ if (errors === 0) {
853
+ out.write(`${diags.length ? "\n" : ""} ✔ Ready to review.\n\n` +
854
+ ` Next read it with whoever knows the business, answer what you can in\n` +
855
+ ` questions.md, then ${next}\n\n`);
856
+ }
857
+ else {
858
+ out.write(`\n ✖ ${errors} finding(s) to fix. Each says how; DRAFTING.md has the formats.\n` +
859
+ ` Next fix them (or ask your agent to), then ${next}\n\n`);
860
+ }
861
+ });
862
+ process.exitCode = errors === 0 ? 0 : 1;
863
+ }
864
+ // ── specgate draft --check <id> ─────────────────────────────────────────────
865
+ check(opts) {
866
+ const io = (0, agent_1.agentIo)(opts.json);
867
+ const projectDir = (0, project_root_1.resolveProjectDir)(opts.projectDir);
868
+ const result = draftReport(projectDir, opts.check, opts.brief, opts.maxRequirements);
869
+ if ("failure" in result) {
870
+ io.fail({ draft: null }, [result.failure]);
871
+ return;
872
+ }
873
+ const report = result.report;
874
+ const status = [...report.status, ...skippedNotes(report)];
239
875
  const errors = report.status.length;
240
876
  io.emit({
241
877
  draft: {
@@ -248,12 +884,7 @@ class DraftCommand extends command_1.BaseCommand {
248
884
  }, () => {
249
885
  const out = process.stdout;
250
886
  out.write(`\n specgate draft --check ${opts.check}\n\n`);
251
- out.write(` ${report.requirements} requirement(s), ${report.scenarios} scenario(s)\n\n`);
252
- for (const d of status) {
253
- out.write(` ${d.severity === "error" ? "✖" : "ℹ"} ${d.code} ${d.message}\n`);
254
- if (d.fix && d.severity === "error")
255
- out.write(` fix: ${d.fix}\n`);
256
- }
887
+ writeFindings(report, status);
257
888
  const skipped = report.skipped.length;
258
889
  out.write(errors === 0
259
890
  ? `\n ✔ Ready to review${skipped ? ` (${skipped} rule(s) did not run — see above)` : ""}.\n`
@@ -263,4 +894,18 @@ class DraftCommand extends command_1.BaseCommand {
263
894
  }
264
895
  }
265
896
  exports.DraftCommand = DraftCommand;
897
+ function skippedNotes(report) {
898
+ return report.skipped.map((s) => (0, diagnostics_1.info)("draft_rule_skipped", `${s.rule} did not run: ${s.why}.`, {
899
+ fix: "Pass --brief <file>, with actors and their surfaces in its front matter for D3.",
900
+ }));
901
+ }
902
+ function writeFindings(report, status) {
903
+ const out = process.stdout;
904
+ out.write(` ${report.requirements} requirement(s), ${report.scenarios} scenario(s)\n\n`);
905
+ for (const d of status) {
906
+ out.write(` ${d.severity === "error" ? "✖" : "ℹ"} ${d.code} ${d.message}\n`);
907
+ if (d.fix && d.severity === "error")
908
+ out.write(` fix: ${d.fix}\n`);
909
+ }
910
+ }
266
911
  //# sourceMappingURL=DraftCommand.js.map