executable-stories-formatters 0.15.0 → 0.16.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 (42) hide show
  1. package/dist/cli.js +238 -64
  2. package/dist/cli.js.map +1 -1
  3. package/dist/index.cjs +197 -55
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +11 -1
  6. package/dist/index.d.ts +11 -1
  7. package/dist/index.js +197 -55
  8. package/dist/index.js.map +1 -1
  9. package/package.json +3 -2
  10. package/schemas/README.md +1 -1
  11. package/templates/astro-starlight/astro.config.mjs +57 -0
  12. package/templates/astro-starlight/gitignore +14 -0
  13. package/templates/astro-starlight/package.json +20 -0
  14. package/templates/astro-starlight/public/stories/assets/.gitkeep +0 -0
  15. package/templates/astro-starlight/public/stories/notes-index.json +4 -0
  16. package/templates/astro-starlight/public/stories/story-report.json +17 -0
  17. package/templates/astro-starlight/src/components/ApiOperations.astro +366 -0
  18. package/templates/astro-starlight/src/components/Checklist.astro +15 -0
  19. package/templates/astro-starlight/src/components/HealthDashboard.astro +171 -0
  20. package/templates/astro-starlight/src/components/PageTitle.astro +53 -0
  21. package/templates/astro-starlight/src/components/VerifiedBy.astro +281 -0
  22. package/templates/astro-starlight/src/components/VerifiedStep.astro +91 -0
  23. package/templates/astro-starlight/src/content/docs/examples/example-adr.mdx +45 -0
  24. package/templates/astro-starlight/src/content/docs/guides/behavior-portal.mdx +41 -0
  25. package/templates/astro-starlight/src/content/docs/guides/writing-docs.mdx +49 -0
  26. package/templates/astro-starlight/src/content/docs/index.mdx +49 -0
  27. package/templates/astro-starlight/src/content/docs/stories/.gitkeep +0 -0
  28. package/templates/astro-starlight/src/content.config.ts +18 -0
  29. package/templates/astro-starlight/src/lib/config.ts +50 -0
  30. package/templates/astro-starlight/src/lib/render-doc-entry.ts +154 -0
  31. package/templates/astro-starlight/src/lib/report-health.ts +61 -0
  32. package/templates/astro-starlight/src/lib/verification.ts +247 -0
  33. package/templates/astro-starlight/src/pages/explorer/explorer.css +729 -0
  34. package/templates/astro-starlight/src/pages/explorer/index.astro +404 -0
  35. package/templates/astro-starlight/src/styles/global.css +293 -0
  36. package/templates/astro-starlight/src/styles/themes/corporate.css +83 -0
  37. package/templates/astro-starlight/src/styles/themes/dashboard.css +76 -0
  38. package/templates/astro-starlight/src/styles/themes/default.css +86 -0
  39. package/templates/astro-starlight/src/styles/themes/minimal.css +87 -0
  40. package/templates/astro-starlight/src/styles/themes/playful.css +77 -0
  41. package/templates/astro-starlight/src/styles/themes/terminal.css +77 -0
  42. package/templates/astro-starlight/tsconfig.json +13 -0
package/dist/cli.js CHANGED
@@ -702,6 +702,41 @@ function generateFeatureId(uri) {
702
702
  function generateScenarioId(featureId, scenarioName) {
703
703
  return `${featureId};${slugify(scenarioName)}`;
704
704
  }
705
+ function normalizeText(text2) {
706
+ return text2.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
707
+ }
708
+ function tokenize(text2) {
709
+ const normalized = normalizeText(text2);
710
+ return normalized.length === 0 ? [] : normalized.split(" ");
711
+ }
712
+ function behaviourFingerprint(input) {
713
+ const steps = input.steps.map((step) => `${step.keyword.toLowerCase()}:${normalizeText(step.text)}`).join("\n");
714
+ const covers = [...input.covers ?? []].map((path18) => path18.trim()).filter(Boolean).sort().join(",");
715
+ if (steps.length === 0 && covers.length === 0) return "";
716
+ return createHash("sha1").update(`${steps}\0${covers}`).digest("hex").slice(0, 16);
717
+ }
718
+ function jaccard(a, b) {
719
+ if (a.size === 0 && b.size === 0) return 1;
720
+ if (a.size === 0 || b.size === 0) return 0;
721
+ let intersection = 0;
722
+ for (const value of a) if (b.has(value)) intersection += 1;
723
+ return intersection / (a.size + b.size - intersection);
724
+ }
725
+ function stepTokens(steps) {
726
+ const tokens = /* @__PURE__ */ new Set();
727
+ for (const step of steps) {
728
+ for (const token of tokenize(step.text)) tokens.add(token);
729
+ }
730
+ return tokens;
731
+ }
732
+ function behaviourSimilarity(a, b) {
733
+ const aSteps = stepTokens(a.steps);
734
+ const bSteps = stepTokens(b.steps);
735
+ if (aSteps.size === 0 || bSteps.size === 0) return 0;
736
+ const stepScore = jaccard(aSteps, bSteps);
737
+ const titleScore = jaccard(new Set(tokenize(a.scenario)), new Set(tokenize(b.scenario)));
738
+ return stepScore * 0.7 + titleScore * 0.3;
739
+ }
705
740
 
706
741
  // src/converters/acl/steps.ts
707
742
  function deriveStepResults(steps, scenarioStatus, error) {
@@ -17401,6 +17436,10 @@ function titleFor(kind) {
17401
17436
  return "Added";
17402
17437
  case "removed":
17403
17438
  return "Removed";
17439
+ case "renamed":
17440
+ return "Renamed";
17441
+ case "moved":
17442
+ return "Moved";
17404
17443
  case "changed":
17405
17444
  return "Changed";
17406
17445
  default:
@@ -17449,6 +17488,8 @@ function createPrCommentSummary(diff, maxScenarios = 10) {
17449
17488
  addSection(lines, diff, "fixed", maxScenarios);
17450
17489
  addSection(lines, diff, "added", maxScenarios);
17451
17490
  addSection(lines, diff, "removed", maxScenarios);
17491
+ addSection(lines, diff, "renamed", maxScenarios);
17492
+ addSection(lines, diff, "moved", maxScenarios);
17452
17493
  addSection(lines, diff, "changed", maxScenarios);
17453
17494
  return lines.join("\n").trimEnd();
17454
17495
  }
@@ -17525,8 +17566,10 @@ function sortDiffs(scenarios) {
17525
17566
  fixed: 1,
17526
17567
  added: 2,
17527
17568
  removed: 3,
17528
- changed: 4,
17529
- unchanged: 5
17569
+ renamed: 4,
17570
+ moved: 5,
17571
+ changed: 6,
17572
+ unchanged: 7
17530
17573
  };
17531
17574
  return [...scenarios].sort((a, b) => {
17532
17575
  if (rank[a.kind] !== rank[b.kind]) {
@@ -17541,69 +17584,108 @@ function sortDiffs(scenarios) {
17541
17584
  return a.scenario.localeCompare(b.scenario);
17542
17585
  });
17543
17586
  }
17587
+ var SIMILARITY_THRESHOLD = 0.75;
17588
+ function identityInput(tc) {
17589
+ return {
17590
+ scenario: tc.story.scenario,
17591
+ sourceFile: tc.sourceFile,
17592
+ steps: tc.story.steps,
17593
+ covers: tc.story.covers
17594
+ };
17595
+ }
17596
+ function changedFieldsOf(flags) {
17597
+ return Object.entries(flags).filter(([, changed]) => changed).map(([field]) => field);
17598
+ }
17599
+ function allChangedFlags(errorMessage) {
17600
+ return {
17601
+ status: true,
17602
+ steps: true,
17603
+ docs: true,
17604
+ tags: true,
17605
+ tickets: true,
17606
+ source: true,
17607
+ duration: true,
17608
+ attachments: true,
17609
+ error: Boolean(errorMessage),
17610
+ titlePath: true
17611
+ };
17612
+ }
17613
+ function matchIdentities(removed, added) {
17614
+ const pairs = [];
17615
+ const remainingRemoved = new Set(removed);
17616
+ const remainingAdded = new Set(added);
17617
+ const removedByFp = /* @__PURE__ */ new Map();
17618
+ const addedByFp = /* @__PURE__ */ new Map();
17619
+ for (const tc of remainingRemoved) {
17620
+ const fp = behaviourFingerprint(identityInput(tc));
17621
+ if (fp === "") continue;
17622
+ (removedByFp.get(fp) ?? removedByFp.set(fp, []).get(fp)).push(tc);
17623
+ }
17624
+ for (const tc of remainingAdded) {
17625
+ const fp = behaviourFingerprint(identityInput(tc));
17626
+ if (fp === "") continue;
17627
+ (addedByFp.get(fp) ?? addedByFp.set(fp, []).get(fp)).push(tc);
17628
+ }
17629
+ for (const [fp, removedGroup] of removedByFp) {
17630
+ const addedGroup = addedByFp.get(fp);
17631
+ if (removedGroup.length === 1 && addedGroup && addedGroup.length === 1) {
17632
+ pairs.push({ before: removedGroup[0], after: addedGroup[0], confidence: 1, matchedBy: "fingerprint" });
17633
+ remainingRemoved.delete(removedGroup[0]);
17634
+ remainingAdded.delete(addedGroup[0]);
17635
+ }
17636
+ }
17637
+ for (const before of [...remainingRemoved]) {
17638
+ let best;
17639
+ let bestScore = 0;
17640
+ let tied = false;
17641
+ for (const after of remainingAdded) {
17642
+ const score = behaviourSimilarity(identityInput(before), identityInput(after));
17643
+ if (score > bestScore) {
17644
+ bestScore = score;
17645
+ best = after;
17646
+ tied = false;
17647
+ } else if (score === bestScore) {
17648
+ tied = true;
17649
+ }
17650
+ }
17651
+ if (best && !tied && bestScore >= SIMILARITY_THRESHOLD) {
17652
+ pairs.push({ before, after: best, confidence: Math.round(bestScore * 100) / 100, matchedBy: "similarity" });
17653
+ remainingRemoved.delete(before);
17654
+ remainingAdded.delete(best);
17655
+ }
17656
+ }
17657
+ return {
17658
+ pairs,
17659
+ unmatchedRemoved: [...remainingRemoved],
17660
+ unmatchedAdded: [...remainingAdded]
17661
+ };
17662
+ }
17663
+ function identityKind(before, after) {
17664
+ const titleChanged = before.story.scenario !== after.story.scenario;
17665
+ const fileChanged = before.sourceFile !== after.sourceFile;
17666
+ return fileChanged && !titleChanged ? "moved" : "renamed";
17667
+ }
17544
17668
  function diffRuns(baseline, current) {
17545
17669
  const baselineById = new Map(baseline.testCases.map((tc) => [tc.id, tc]));
17546
17670
  const currentById = new Map(current.testCases.map((tc) => [tc.id, tc]));
17547
17671
  const ids = /* @__PURE__ */ new Set([...baselineById.keys(), ...currentById.keys()]);
17548
17672
  const scenarios = [];
17673
+ const removedCases = [];
17674
+ const addedCases = [];
17549
17675
  for (const id of ids) {
17550
17676
  const before = baselineById.get(id);
17551
17677
  const after = currentById.get(id);
17552
17678
  if (!before && after) {
17553
- const flags2 = {
17554
- status: true,
17555
- steps: true,
17556
- docs: true,
17557
- tags: true,
17558
- tickets: true,
17559
- source: true,
17560
- duration: true,
17561
- attachments: true,
17562
- error: Boolean(after.errorMessage),
17563
- titlePath: true
17564
- };
17565
- scenarios.push({
17566
- kind: "added",
17567
- id,
17568
- scenario: after.story.scenario,
17569
- sourceFile: after.sourceFile,
17570
- sourceLine: after.sourceLine,
17571
- current: toScenarioSnapshot(after),
17572
- flags: flags2,
17573
- changedFields: Object.entries(flags2).filter(([, changed]) => changed).map(([field]) => field)
17574
- });
17679
+ addedCases.push(after);
17575
17680
  continue;
17576
17681
  }
17577
17682
  if (before && !after) {
17578
- const flags2 = {
17579
- status: true,
17580
- steps: true,
17581
- docs: true,
17582
- tags: true,
17583
- tickets: true,
17584
- source: true,
17585
- duration: true,
17586
- attachments: true,
17587
- error: Boolean(before.errorMessage),
17588
- titlePath: true
17589
- };
17590
- scenarios.push({
17591
- kind: "removed",
17592
- id,
17593
- scenario: before.story.scenario,
17594
- sourceFile: before.sourceFile,
17595
- sourceLine: before.sourceLine,
17596
- baseline: toScenarioSnapshot(before),
17597
- flags: flags2,
17598
- changedFields: Object.entries(flags2).filter(([, changed]) => changed).map(([field]) => field)
17599
- });
17600
- continue;
17601
- }
17602
- if (!before || !after) {
17683
+ removedCases.push(before);
17603
17684
  continue;
17604
17685
  }
17686
+ if (!before || !after) continue;
17605
17687
  const flags = buildFlags(before, after);
17606
- const changedFields = Object.entries(flags).filter(([, changed]) => changed).map(([field]) => field);
17688
+ const changedFields = changedFieldsOf(flags);
17607
17689
  const kind = getPrimaryKind(before, after, changedFields.length > 0);
17608
17690
  scenarios.push({
17609
17691
  kind,
@@ -17618,12 +17700,59 @@ function diffRuns(baseline, current) {
17618
17700
  durationDeltaMs: after.durationMs - before.durationMs
17619
17701
  });
17620
17702
  }
17703
+ const { pairs, unmatchedRemoved, unmatchedAdded } = matchIdentities(removedCases, addedCases);
17704
+ for (const { before, after, confidence, matchedBy } of pairs) {
17705
+ const flags = buildFlags(before, after);
17706
+ scenarios.push({
17707
+ kind: identityKind(before, after),
17708
+ id: after.id,
17709
+ previousId: before.id,
17710
+ scenario: after.story.scenario,
17711
+ sourceFile: after.sourceFile,
17712
+ sourceLine: after.sourceLine,
17713
+ baseline: toScenarioSnapshot(before),
17714
+ current: toScenarioSnapshot(after),
17715
+ flags,
17716
+ changedFields: changedFieldsOf(flags),
17717
+ durationDeltaMs: after.durationMs - before.durationMs,
17718
+ matchConfidence: confidence,
17719
+ matchedBy
17720
+ });
17721
+ }
17722
+ for (const after of unmatchedAdded) {
17723
+ const flags = allChangedFlags(after.errorMessage);
17724
+ scenarios.push({
17725
+ kind: "added",
17726
+ id: after.id,
17727
+ scenario: after.story.scenario,
17728
+ sourceFile: after.sourceFile,
17729
+ sourceLine: after.sourceLine,
17730
+ current: toScenarioSnapshot(after),
17731
+ flags,
17732
+ changedFields: changedFieldsOf(flags)
17733
+ });
17734
+ }
17735
+ for (const before of unmatchedRemoved) {
17736
+ const flags = allChangedFlags(before.errorMessage);
17737
+ scenarios.push({
17738
+ kind: "removed",
17739
+ id: before.id,
17740
+ scenario: before.story.scenario,
17741
+ sourceFile: before.sourceFile,
17742
+ sourceLine: before.sourceLine,
17743
+ baseline: toScenarioSnapshot(before),
17744
+ flags,
17745
+ changedFields: changedFieldsOf(flags)
17746
+ });
17747
+ }
17621
17748
  const sorted = sortDiffs(scenarios);
17622
17749
  const summary = {
17623
17750
  totalBaseline: baseline.testCases.length,
17624
17751
  totalCurrent: current.testCases.length,
17625
17752
  added: sorted.filter((s) => s.kind === "added").length,
17626
17753
  removed: sorted.filter((s) => s.kind === "removed").length,
17754
+ renamed: sorted.filter((s) => s.kind === "renamed").length,
17755
+ moved: sorted.filter((s) => s.kind === "moved").length,
17627
17756
  changed: sorted.filter((s) => s.kind === "changed").length,
17628
17757
  regressed: sorted.filter((s) => s.kind === "regressed").length,
17629
17758
  fixed: sorted.filter((s) => s.kind === "fixed").length,
@@ -17651,6 +17780,10 @@ function statusLabel(kind) {
17651
17780
  return "Added";
17652
17781
  case "removed":
17653
17782
  return "Removed";
17783
+ case "renamed":
17784
+ return "Renamed";
17785
+ case "moved":
17786
+ return "Moved";
17654
17787
  case "changed":
17655
17788
  return "Changed";
17656
17789
  default:
@@ -17976,6 +18109,8 @@ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', fun
17976
18109
  <div class="summary-card"><strong>${diff.summary.fixed}</strong><span>Fixed</span></div>
17977
18110
  <div class="summary-card"><strong>${diff.summary.added}</strong><span>Added</span></div>
17978
18111
  <div class="summary-card"><strong>${diff.summary.removed}</strong><span>Removed</span></div>
18112
+ <div class="summary-card"><strong>${diff.summary.renamed}</strong><span>Renamed</span></div>
18113
+ <div class="summary-card"><strong>${diff.summary.moved}</strong><span>Moved</span></div>
17979
18114
  <div class="summary-card"><strong>${diff.summary.changed}</strong><span>Changed</span></div>
17980
18115
  <div class="summary-card"><strong>${diff.summary.unchanged}</strong><span>Unchanged</span></div>
17981
18116
  </section>
@@ -18036,6 +18171,10 @@ function formatStatus(kind) {
18036
18171
  return "Added";
18037
18172
  case "removed":
18038
18173
  return "Removed";
18174
+ case "renamed":
18175
+ return "Renamed";
18176
+ case "moved":
18177
+ return "Moved";
18039
18178
  case "changed":
18040
18179
  return "Changed";
18041
18180
  default:
@@ -18188,13 +18327,13 @@ var RunDiffMarkdownFormatter = class {
18188
18327
  lines.push("No regressions or fixes detected. Remaining changes are neutral.");
18189
18328
  }
18190
18329
  lines.push("");
18191
- lines.push("| Added | Removed | Regressed | Fixed | Changed | Unchanged |");
18192
- lines.push("| ---: | ---: | ---: | ---: | ---: | ---: |");
18330
+ lines.push("| Added | Removed | Renamed | Moved | Regressed | Fixed | Changed | Unchanged |");
18331
+ lines.push("| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |");
18193
18332
  lines.push(
18194
- `| ${diff.summary.added} | ${diff.summary.removed} | ${diff.summary.regressed} | ${diff.summary.fixed} | ${diff.summary.changed} | ${diff.summary.unchanged} |`
18333
+ `| ${diff.summary.added} | ${diff.summary.removed} | ${diff.summary.renamed} | ${diff.summary.moved} | ${diff.summary.regressed} | ${diff.summary.fixed} | ${diff.summary.changed} | ${diff.summary.unchanged} |`
18195
18334
  );
18196
18335
  lines.push("");
18197
- for (const kind of ["regressed", "fixed", "added", "removed", "changed"]) {
18336
+ for (const kind of ["regressed", "fixed", "added", "removed", "renamed", "moved", "changed"]) {
18198
18337
  const scenarios = diff.scenarios.filter((scenario) => scenario.kind === kind);
18199
18338
  if (scenarios.length === 0) continue;
18200
18339
  lines.push(`## ${formatStatus(kind)} (${scenarios.length})`);
@@ -19143,7 +19282,10 @@ function diffStoryReports(baseline, current) {
19143
19282
  };
19144
19283
  });
19145
19284
  const summary = { added: 0, removed: 0, regressed: 0, fixed: 0, changed: 0, unchanged: 0 };
19146
- for (const s of scenarios) summary[s.kind] += 1;
19285
+ for (const s of scenarios) {
19286
+ if (s.kind === "renamed" || s.kind === "moved") continue;
19287
+ summary[s.kind] += 1;
19288
+ }
19147
19289
  return { schemaVersion: "1.0", summary, scenarios };
19148
19290
  }
19149
19291
 
@@ -21987,6 +22129,9 @@ import { fileURLToPath } from "url";
21987
22129
  var __dirname = path10.dirname(fileURLToPath(import.meta.url));
21988
22130
  var FRAMEWORK_DIRS = ["src/components", "src/lib", "src/styles", "src/pages"];
21989
22131
  var FRAMEWORK_FILES = ["tsconfig.json"];
22132
+ function isScaffoldedAstroSite(dir) {
22133
+ return fs9.existsSync(path10.join(dir, "astro.config.mjs"));
22134
+ }
21990
22135
  function initAstro(options = {}) {
21991
22136
  const targetDir = options.targetDir ?? "./story-docs";
21992
22137
  const force = options.force ?? false;
@@ -22012,7 +22157,7 @@ function initAstro(options = {}) {
22012
22157
  return { targetDir };
22013
22158
  }
22014
22159
  function updateFrameworkFiles(templateDir, targetDir) {
22015
- if (!fs9.existsSync(targetDir) || !fs9.existsSync(path10.join(targetDir, "astro.config.mjs"))) {
22160
+ if (!isScaffoldedAstroSite(targetDir)) {
22016
22161
  throw new Error(
22017
22162
  `"${targetDir}" does not look like a scaffolded docs site. Run init-astro (without --update) first.`
22018
22163
  );
@@ -22057,7 +22202,8 @@ function copyDirRecursive(src, dest, onFile, baseSrc = src) {
22057
22202
  const entries = fs9.readdirSync(src, { withFileTypes: true });
22058
22203
  for (const entry of entries) {
22059
22204
  const srcPath = path10.join(src, entry.name);
22060
- const destPath = path10.join(dest, entry.name);
22205
+ const destName = entry.name === "gitignore" ? ".gitignore" : entry.name;
22206
+ const destPath = path10.join(dest, destName);
22061
22207
  if (entry.isDirectory()) {
22062
22208
  copyDirRecursive(srcPath, destPath, onFile, baseSrc);
22063
22209
  } else {
@@ -23019,6 +23165,12 @@ ${schemaResult.errors.map((e) => ` ${e}`).join("\n")}`,
23019
23165
  }
23020
23166
  async function buildDocs(options) {
23021
23167
  const siteDir = path16.resolve(options.siteDir);
23168
+ if (!isScaffoldedAstroSite(siteDir)) {
23169
+ throw new BuildDocsError(
23170
+ `"${siteDir}" is not a scaffolded Astro docs site (no astro.config.mjs). Run "executable-stories init-astro <dir>" first, then pass it with --site-dir <dir>.`,
23171
+ "usage"
23172
+ );
23173
+ }
23022
23174
  const storiesPublicDir = path16.join(siteDir, "public", "stories");
23023
23175
  const assetsDir = path16.join(storiesPublicDir, "assets");
23024
23176
  const storyPagesDir = path16.join(siteDir, "src", "content", "docs", "stories");
@@ -23152,6 +23304,7 @@ executable-stories \u2014 Generate reports from test results JSON.
23152
23304
  USAGE
23153
23305
  executable-stories format <file> [options]
23154
23306
  executable-stories format --stdin [options]
23307
+ executable-stories watch <raw-run.json> [options]
23155
23308
  executable-stories compare <baseline-file> <current-file> [options]
23156
23309
  executable-stories gate-release <dev-run.json> <rc-run.json> [options]
23157
23310
  executable-stories review <file> --changed-files <path> [options]
@@ -23162,6 +23315,7 @@ USAGE
23162
23315
  executable-stories validate <file>
23163
23316
  executable-stories validate --stdin
23164
23317
  executable-stories init-astro [directory]
23318
+ executable-stories build-docs <raw-run.json> [--site-dir <dir>] [options]
23165
23319
  executable-stories new <template> "<name>" [options]
23166
23320
  executable-stories check-links <dir> [options]
23167
23321
  executable-stories import-openapi <spec> [options]
@@ -23183,6 +23337,7 @@ SUBCOMMANDS
23183
23337
  triage Discovery worklist for agent loops: failing scenarios, regressions first, each with the code it covers
23184
23338
  validate Validate a JSON file against the schema (no output generated)
23185
23339
  init-astro Scaffold an Astro docs site for story output (Starlight with themed CSS)
23340
+ build-docs Build the living-docs site: one page per story file + Explorer data (auto-pickup, prunes deleted stories)
23186
23341
  new Scaffold a docs page from a template (adr, runbook, decision-log, incident, scenario-note)
23187
23342
  check-links Scan docs for broken internal/external links (CI-friendly exit code)
23188
23343
  import-openapi Generate API doc pages from an OpenAPI spec, linked to verifying stories
@@ -23192,7 +23347,7 @@ SUBCOMMANDS
23192
23347
 
23193
23348
  OPTIONS
23194
23349
  --format <formats> Comma-separated formats: html, markdown, release-manifest, traceability-matrix, junit, cucumber-json, cucumber-messages, cucumber-html, astro, confluence, story-report-json, scenario-index-json, behavior-manifest-json, or custom names from config (default: html)
23195
- astro Themed Markdown (for Astro docs sites with matching CSS)
23350
+ astro Themed Markdown primitive (single aggregated page; for a full site use "build-docs")
23196
23351
  confluence Atlassian Document Format (ADF) JSON for Confluence / Jira
23197
23352
  behavior-manifest-json Agent-readable behavior manifest and debugger warnings
23198
23353
  html Custom HTML report (accessible, dark mode, mermaid)
@@ -23313,6 +23468,20 @@ DEPLOY
23313
23468
  INIT-ASTRO
23314
23469
  executable-stories init-astro [directory] Scaffold into directory (default: ./story-docs)
23315
23470
  --force Overwrite existing directory
23471
+ --update Refresh framework files only (keeps your content + config)
23472
+
23473
+ BUILD-DOCS
23474
+ Build the multi-page living-docs site from a raw run: one Astro page per story
23475
+ file plus Explorer data (scenario-links.json, story-report.json). Auto-pickup \u2014
23476
+ a new *.story.test.ts becomes a new page on the next run; deleting a story
23477
+ prunes its page. This is the headline living-docs flow; "format --format astro"
23478
+ is a low-level primitive that emits a single aggregated page, not a site.
23479
+
23480
+ executable-stories build-docs <raw-run.json> [--site-dir <dir>]
23481
+ --site-dir <dir> Target site dir (default: a scaffolded init-astro site)
23482
+ --openapi <spec> Link generated API pages to verifying stories
23483
+ --baseline <prev-report> Diff against a prior story-report.json for change markers
23484
+ --audience-split Split pages by audience (business vs technical)
23316
23485
 
23317
23486
  PUBLISH-CONFLUENCE
23318
23487
  executable-stories publish-confluence <file.adf.json> [options]
@@ -23419,12 +23588,17 @@ async function parseCliArgs(argv) {
23419
23588
  console.log("To change theme, edit astro.config.mjs customCss array.");
23420
23589
  console.log("");
23421
23590
  console.log("Next steps:");
23422
- console.log(` cd ${result.targetDir}`);
23423
- console.log(" pnpm install # or npm install");
23424
- console.log(" pnpm dev # start the dev server");
23425
- console.log("");
23426
- console.log("Generate everything (story pages, explorer data, API pages) in one step:");
23427
- console.log(` executable-stories build-docs run.json --site-dir ${result.targetDir} [--openapi spec.json]`);
23591
+ console.log(` 1. cd ${result.targetDir} && pnpm install # or npm install`);
23592
+ console.log(" 2. In your TEST project, add the StoryReporter with a rawRunPath, e.g.");
23593
+ console.log(" StoryReporter({ rawRunPath: 'reports/raw-run.json' })");
23594
+ console.log(" (this is what writes the raw run that build-docs reads)");
23595
+ console.log(" 3. Run your tests to produce reports/raw-run.json:");
23596
+ console.log(" pnpm test");
23597
+ console.log(" 4. Build the living-docs site (story pages, explorer data, API pages):");
23598
+ console.log(
23599
+ ` executable-stories build-docs reports/raw-run.json --site-dir ${result.targetDir} [--openapi spec.json]`
23600
+ );
23601
+ console.log(` 5. Preview it: cd ${result.targetDir} && pnpm dev`);
23428
23602
  console.log("");
23429
23603
  console.log("Later, pull template/design improvements without losing your content:");
23430
23604
  console.log(` executable-stories init-astro ${result.targetDir} --update`);