@anselmdk/feature-spec-md 0.5.0 → 0.6.1

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 (76) hide show
  1. package/README.md +59 -78
  2. package/SPEC_FORMAT.md +38 -78
  3. package/dist/cli.js +194 -86
  4. package/dist/cli.js.map +1 -1
  5. package/dist/featureSpecs.d.ts +1 -1
  6. package/dist/featureSpecs.d.ts.map +1 -1
  7. package/dist/featureSpecs.js +57 -10
  8. package/dist/featureSpecs.js.map +1 -1
  9. package/dist/githubActionDiffReport.d.ts +61 -0
  10. package/dist/githubActionDiffReport.d.ts.map +1 -1
  11. package/dist/githubActionDiffReport.js +410 -120
  12. package/dist/githubActionDiffReport.js.map +1 -1
  13. package/dist/githubActionReport.d.ts.map +1 -1
  14. package/dist/githubActionReport.js +27 -22
  15. package/dist/githubActionReport.js.map +1 -1
  16. package/dist/index.d.ts +8 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +4 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/mockReports.d.ts +36 -0
  21. package/dist/mockReports.d.ts.map +1 -0
  22. package/dist/mockReports.js +280 -0
  23. package/dist/mockReports.js.map +1 -0
  24. package/dist/mocks/current/app/supportDesk.d.ts +12 -0
  25. package/dist/mocks/current/app/supportDesk.d.ts.map +1 -0
  26. package/dist/mocks/current/app/supportDesk.js +19 -0
  27. package/dist/mocks/current/app/supportDesk.js.map +1 -0
  28. package/dist/mocks/current/tests/supportDesk.spec.d.ts +3 -0
  29. package/dist/mocks/current/tests/supportDesk.spec.d.ts.map +1 -0
  30. package/dist/mocks/current/tests/supportDesk.spec.js +15 -0
  31. package/dist/mocks/current/tests/supportDesk.spec.js.map +1 -0
  32. package/dist/mocks/previous/app/supportDesk.d.ts +10 -0
  33. package/dist/mocks/previous/app/supportDesk.d.ts.map +1 -0
  34. package/dist/mocks/previous/app/supportDesk.js +9 -0
  35. package/dist/mocks/previous/app/supportDesk.js.map +1 -0
  36. package/dist/mocks/previous/tests/supportDesk.spec.d.ts +3 -0
  37. package/dist/mocks/previous/tests/supportDesk.spec.d.ts.map +1 -0
  38. package/dist/mocks/previous/tests/supportDesk.spec.js +15 -0
  39. package/dist/mocks/previous/tests/supportDesk.spec.js.map +1 -0
  40. package/dist/reportArtifacts.d.ts +7 -0
  41. package/dist/reportArtifacts.d.ts.map +1 -0
  42. package/dist/reportArtifacts.js +26 -0
  43. package/dist/reportArtifacts.js.map +1 -0
  44. package/dist/reportHtml.d.ts +9 -0
  45. package/dist/reportHtml.d.ts.map +1 -0
  46. package/dist/reportHtml.js +36 -0
  47. package/dist/reportHtml.js.map +1 -0
  48. package/dist/reportMetadata.d.ts +9 -0
  49. package/dist/reportMetadata.d.ts.map +1 -0
  50. package/dist/reportMetadata.js +28 -0
  51. package/dist/reportMetadata.js.map +1 -0
  52. package/dist/screenshots.d.ts +3 -1
  53. package/dist/screenshots.d.ts.map +1 -1
  54. package/dist/screenshots.js +23 -0
  55. package/dist/screenshots.js.map +1 -1
  56. package/dist/types.d.ts +11 -1
  57. package/dist/types.d.ts.map +1 -1
  58. package/docs/evidence-policy.md +63 -0
  59. package/docs/report-development.md +102 -0
  60. package/docs/spec-driven-flow.md +67 -8
  61. package/package.json +3 -1
  62. package/src/mocks/current/app/supportDesk.ts +27 -0
  63. package/src/mocks/current/screenshots/SUPPORT-INBOX-S001-line-27-current.svg +10 -0
  64. package/src/mocks/current/screenshots/SUPPORT-REPLY-S001-line-27-current.svg +10 -0
  65. package/src/mocks/current/screenshots/screenshots.json +18 -0
  66. package/src/mocks/current/specs/support-desk.model.md +29 -0
  67. package/src/mocks/current/specs/ticket-inbox.feature.md +27 -0
  68. package/src/mocks/current/specs/ticket-reply.feature.md +33 -0
  69. package/src/mocks/current/tests/supportDesk.spec.ts +17 -0
  70. package/src/mocks/previous/app/supportDesk.ts +17 -0
  71. package/src/mocks/previous/screenshots/SUPPORT-INBOX-S001-line-27-previous.svg +10 -0
  72. package/src/mocks/previous/screenshots/screenshots.json +11 -0
  73. package/src/mocks/previous/specs/support-desk.model.md +29 -0
  74. package/src/mocks/previous/specs/ticket-inbox.feature.md +26 -0
  75. package/src/mocks/previous/specs/ticket-reply.feature.md +26 -0
  76. package/src/mocks/previous/tests/supportDesk.spec.ts +17 -0
@@ -0,0 +1,3 @@
1
+ export declare function verifiesTicketInboxMock(): ("High priority" | "Low priority" | "Normal priority")[];
2
+ export declare function verifiesReplyDraftMock(): string;
3
+ //# sourceMappingURL=supportDesk.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supportDesk.spec.d.ts","sourceRoot":"","sources":["../../../../src/mocks/current/tests/supportDesk.spec.ts"],"names":[],"mappings":"AAOA,wBAAgB,uBAAuB,6DAItC;AAED,wBAAgB,sBAAsB,WAGrC"}
@@ -0,0 +1,15 @@
1
+ import { draftReply, priorityBadge, visibleTickets } from "../app/supportDesk.js";
2
+ const tickets = [
3
+ { id: "T-2", title: "Newer", status: "open", priority: "high", createdAt: "2026-01-02T10:00:00Z" },
4
+ { id: "T-1", title: "Older", status: "open", priority: "normal", createdAt: "2026-01-01T10:00:00Z" },
5
+ ];
6
+ export function verifiesTicketInboxMock() {
7
+ // Covers SUPPORT-M001, SUPPORT-M002, SUPPORT-M003 and SUPPORT-M-R001.
8
+ // Covers SUPPORT-INBOX-R001, SUPPORT-INBOX-R002 and SUPPORT-INBOX-S001.
9
+ return visibleTickets(tickets).map((ticket) => priorityBadge(ticket));
10
+ }
11
+ export function verifiesReplyDraftMock() {
12
+ // Covers SUPPORT-REPLY-R001 and SUPPORT-REPLY-S001.
13
+ return draftReply("Existing draft", "Updated draft");
14
+ }
15
+ //# sourceMappingURL=supportDesk.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supportDesk.spec.js","sourceRoot":"","sources":["../../../../src/mocks/current/tests/supportDesk.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAElF,MAAM,OAAO,GAAG;IACd,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAe,EAAE,QAAQ,EAAE,MAAe,EAAE,SAAS,EAAE,sBAAsB,EAAE;IACpH,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAe,EAAE,QAAQ,EAAE,QAAiB,EAAE,SAAS,EAAE,sBAAsB,EAAE;CACvH,CAAC;AAEF,MAAM,UAAU,uBAAuB;IACrC,sEAAsE;IACtE,wEAAwE;IACxE,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,oDAAoD;IACpD,OAAO,UAAU,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,10 @@
1
+ export type Ticket = {
2
+ id: string;
3
+ title: string;
4
+ status: "open" | "closed";
5
+ priority: "low" | "normal" | "high";
6
+ createdAt: string;
7
+ };
8
+ export declare function visibleTickets(tickets: Ticket[]): Ticket[];
9
+ export declare function draftReply(existingDraft: string, nextText: string): string;
10
+ //# sourceMappingURL=supportDesk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supportDesk.d.ts","sourceRoot":"","sources":["../../../../src/mocks/previous/app/supportDesk.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,YAI/C;AAED,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAEjE"}
@@ -0,0 +1,9 @@
1
+ export function visibleTickets(tickets) {
2
+ return tickets
3
+ .filter((ticket) => ticket.status === "open")
4
+ .sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
5
+ }
6
+ export function draftReply(existingDraft, nextText) {
7
+ return nextText.trim() ? nextText : existingDraft;
8
+ }
9
+ //# sourceMappingURL=supportDesk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supportDesk.js","sourceRoot":"","sources":["../../../../src/mocks/previous/app/supportDesk.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,cAAc,CAAC,OAAiB;IAC9C,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC;SAC5C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,aAAqB,EAAE,QAAgB;IAChE,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC;AACpD,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function verifiesTicketInboxMock(): import("../app/supportDesk.js").Ticket[];
2
+ export declare function verifiesReplyDraftMock(): string;
3
+ //# sourceMappingURL=supportDesk.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supportDesk.spec.d.ts","sourceRoot":"","sources":["../../../../src/mocks/previous/tests/supportDesk.spec.ts"],"names":[],"mappings":"AAOA,wBAAgB,uBAAuB,6CAItC;AAED,wBAAgB,sBAAsB,WAGrC"}
@@ -0,0 +1,15 @@
1
+ import { draftReply, visibleTickets } from "../app/supportDesk.js";
2
+ const tickets = [
3
+ { id: "T-2", title: "Newer", status: "open", priority: "high", createdAt: "2026-01-02T10:00:00Z" },
4
+ { id: "T-1", title: "Older", status: "open", priority: "normal", createdAt: "2026-01-01T10:00:00Z" },
5
+ ];
6
+ export function verifiesTicketInboxMock() {
7
+ // Covers SUPPORT-M001, SUPPORT-M002, SUPPORT-M003 and SUPPORT-M-R001.
8
+ // Covers SUPPORT-INBOX-R001 and SUPPORT-INBOX-S001.
9
+ return visibleTickets(tickets);
10
+ }
11
+ export function verifiesReplyDraftMock() {
12
+ // Covers SUPPORT-REPLY-R001 and SUPPORT-REPLY-S001.
13
+ return draftReply("Existing draft", "Updated draft");
14
+ }
15
+ //# sourceMappingURL=supportDesk.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supportDesk.spec.js","sourceRoot":"","sources":["../../../../src/mocks/previous/tests/supportDesk.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEnE,MAAM,OAAO,GAAG;IACd,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAe,EAAE,QAAQ,EAAE,MAAe,EAAE,SAAS,EAAE,sBAAsB,EAAE;IACpH,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAe,EAAE,QAAQ,EAAE,QAAiB,EAAE,SAAS,EAAE,sBAAsB,EAAE;CACvH,CAAC;AAEF,MAAM,UAAU,uBAAuB;IACrC,sEAAsE;IACtE,oDAAoD;IACpD,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,oDAAoD;IACpD,OAAO,UAAU,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare const publishedSpecRoot = "__feature-spec-md/specs";
2
+ export type PublishedSpecManifestItem = {
3
+ filePath: string;
4
+ publishedPath: string;
5
+ };
6
+ export declare function writePublishedFeatureSpecFiles(reportDir: string, patterns: string[]): Promise<void>;
7
+ //# sourceMappingURL=reportArtifacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportArtifacts.d.ts","sourceRoot":"","sources":["../src/reportArtifacts.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,iBAAiB,4BAA4B,CAAC;AAE3D,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,wBAAsB,8BAA8B,CAClD,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAAE,iBAuBnB"}
@@ -0,0 +1,26 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { expandFilePatterns } from "./filePatterns.js";
4
+ export const publishedSpecRoot = "__feature-spec-md/specs";
5
+ export async function writePublishedFeatureSpecFiles(reportDir, patterns) {
6
+ const files = (await expandFilePatterns(patterns)).filter((file) => file.endsWith(".feature.md"));
7
+ const features = [];
8
+ for (const sourcePath of files) {
9
+ const filePath = safeRelativePath(sourcePath);
10
+ const publishedPath = `${publishedSpecRoot}/${filePath}`;
11
+ const target = join(reportDir, publishedPath);
12
+ await mkdir(dirname(target), { recursive: true });
13
+ await writeFile(target, await readFile(sourcePath, "utf8"), "utf8");
14
+ features.push({ filePath, publishedPath });
15
+ }
16
+ const manifestPath = join(reportDir, "__feature-spec-md", "manifest.json");
17
+ await mkdir(dirname(manifestPath), { recursive: true });
18
+ await writeFile(manifestPath, JSON.stringify({ generatedAt: new Date().toISOString(), features }, null, 2), "utf8");
19
+ }
20
+ function safeRelativePath(filePath) {
21
+ return filePath
22
+ .split(/[\\/]+/)
23
+ .filter((part) => part && part !== "." && part !== "..")
24
+ .join("/");
25
+ }
26
+ //# sourceMappingURL=reportArtifacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportArtifacts.js","sourceRoot":"","sources":["../src/reportArtifacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,CAAC,MAAM,iBAAiB,GAAG,yBAAyB,CAAC;AAO3D,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,SAAiB,EACjB,QAAkB;IAElB,MAAM,KAAK,GAAG,CAAC,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACjE,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,CAC7B,CAAC;IACF,MAAM,QAAQ,GAAgC,EAAE,CAAC;IAEjD,KAAK,MAAM,UAAU,IAAI,KAAK,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAC9C,MAAM,aAAa,GAAG,GAAG,iBAAiB,IAAI,QAAQ,EAAE,CAAC;QACzD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QAC9C,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,SAAS,CAAC,MAAM,EAAE,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;QACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,mBAAmB,EAAE,eAAe,CAAC,CAAC;IAC3E,MAAM,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,MAAM,SAAS,CACb,YAAY,EACZ,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAC5E,MAAM,CACP,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAgB;IACxC,OAAO,QAAQ;SACZ,KAAK,CAAC,QAAQ,CAAC;SACf,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC;SACvD,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,9 @@
1
+ export type HtmlPageOptions = {
2
+ title: string;
3
+ body: string;
4
+ styles?: string;
5
+ scripts?: string;
6
+ maxWidth?: string;
7
+ };
8
+ export declare function renderHtmlPage({ title, body, styles, scripts, maxWidth, }: HtmlPageOptions): string;
9
+ //# sourceMappingURL=reportHtml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportHtml.d.ts","sourceRoot":"","sources":["../src/reportHtml.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,IAAI,EACJ,MAAW,EACX,OAAY,EACZ,QAAmB,GACpB,EAAE,eAAe,UAyBjB"}
@@ -0,0 +1,36 @@
1
+ import { html } from "./html.js";
2
+ export function renderHtmlPage({ title, body, styles = "", scripts = "", maxWidth = "1180px", }) {
3
+ return `<!doctype html>
4
+ <html lang="en">
5
+ <head>
6
+ <meta charset="utf-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <title>${html(title)}</title>
9
+ <style>
10
+ body {
11
+ font-family: system-ui, sans-serif;
12
+ max-width: ${maxWidth};
13
+ margin: 0 auto;
14
+ padding: 40px 24px;
15
+ color: #1f2328;
16
+ line-height: 1.5;
17
+ }
18
+ ${indent(styles, 4)}
19
+ </style>
20
+ </head>
21
+ <body>
22
+ ${indent(body, 2)}
23
+ ${scripts ? indent(scripts, 2) : ""}
24
+ </body>
25
+ </html>
26
+ `;
27
+ }
28
+ function indent(value, spaces) {
29
+ const prefix = " ".repeat(spaces);
30
+ return value
31
+ .trim()
32
+ .split("\n")
33
+ .map((line) => (line ? `${prefix}${line}` : line))
34
+ .join("\n");
35
+ }
36
+ //# sourceMappingURL=reportHtml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportHtml.js","sourceRoot":"","sources":["../src/reportHtml.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAUjC,MAAM,UAAU,cAAc,CAAC,EAC7B,KAAK,EACL,IAAI,EACJ,MAAM,GAAG,EAAE,EACX,OAAO,GAAG,EAAE,EACZ,QAAQ,GAAG,QAAQ,GACH;IAChB,OAAO;;;;;WAKE,IAAI,CAAC,KAAK,CAAC;;;;mBAIH,QAAQ;;;;;;EAMzB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;;;;EAIjB,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;EACf,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;;;CAGlC,CAAC;AACF,CAAC;AAED,SAAS,MAAM,CAAC,KAAa,EAAE,MAAc;IAC3C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,KAAK;SACT,IAAI,EAAE;SACN,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;SACjD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
@@ -0,0 +1,9 @@
1
+ export type ReportMetadataItem = {
2
+ label: string;
3
+ value: string;
4
+ url?: string;
5
+ };
6
+ /** Insert report metadata directly below the first report title. */
7
+ export declare function insertReportMetadata(reportHtml: string, metadata?: ReportMetadataItem[]): string;
8
+ export declare function renderReportMetadata(metadata: ReportMetadataItem[]): string;
9
+ //# sourceMappingURL=reportMetadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportMetadata.d.ts","sourceRoot":"","sources":["../src/reportMetadata.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,QAAQ,GAAE,kBAAkB,EAAO,UAWpC;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,UAOlE"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Helpers for adding build/source metadata to generated HTML reports.
3
+ */
4
+ import { html } from "./html.js";
5
+ /** Insert report metadata directly below the first report title. */
6
+ export function insertReportMetadata(reportHtml, metadata = []) {
7
+ const visibleMetadata = metadata.filter((item) => item.label.trim() && item.value.trim());
8
+ if (!visibleMetadata.length)
9
+ return reportHtml;
10
+ const block = renderReportMetadata(visibleMetadata);
11
+ if (reportHtml.includes(block))
12
+ return reportHtml;
13
+ return reportHtml.replace(/(<h1[\s\S]*?<\/h1>)/, `$1\n ${block}`);
14
+ }
15
+ export function renderReportMetadata(metadata) {
16
+ const visibleMetadata = metadata.filter((item) => item.label.trim() && item.value.trim());
17
+ if (!visibleMetadata.length)
18
+ return "";
19
+ return `<p>${visibleMetadata.map(renderReportMetadataItem).join(" · ")}</p>`;
20
+ }
21
+ function renderReportMetadataItem(item) {
22
+ const label = `<strong>${html(item.label)}:</strong>`;
23
+ const value = item.url
24
+ ? `<a href="${html(item.url)}" target="_blank" rel="noopener noreferrer">${html(item.value)}</a>`
25
+ : html(item.value);
26
+ return `${label} ${value}`;
27
+ }
28
+ //# sourceMappingURL=reportMetadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportMetadata.js","sourceRoot":"","sources":["../src/reportMetadata.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQjC,oEAAoE;AACpE,MAAM,UAAU,oBAAoB,CAClC,UAAkB,EAClB,WAAiC,EAAE;IAEnC,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CACrC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CACjD,CAAC;IACF,IAAI,CAAC,eAAe,CAAC,MAAM;QAAE,OAAO,UAAU,CAAC;IAE/C,MAAM,KAAK,GAAG,oBAAoB,CAAC,eAAe,CAAC,CAAC;IACpD,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,UAAU,CAAC;IAElD,OAAO,UAAU,CAAC,OAAO,CAAC,qBAAqB,EAAE,WAAW,KAAK,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,QAA8B;IACjE,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CACrC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CACjD,CAAC;IACF,IAAI,CAAC,eAAe,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEvC,OAAO,MAAM,eAAe,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;AAC/E,CAAC;AAED,SAAS,wBAAwB,CAAC,IAAwB;IACxD,MAAM,KAAK,GAAG,WAAW,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;IACtD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG;QACpB,CAAC,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,+CAA+C,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM;QACjG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,GAAG,KAAK,IAAI,KAAK,EAAE,CAAC;AAC7B,CAAC"}
@@ -1,6 +1,8 @@
1
- import type { SpecScreenshot } from "./types.js";
1
+ import type { FeatureSpec, SpecScreenshot, ValidationIssue } from "./types.js";
2
2
  /** Load screenshot evidence manifests and normalize entries for report rendering. */
3
3
  export declare function collectSpecScreenshots(patterns: string[]): Promise<SpecScreenshot[]>;
4
+ /** Validate screenshot evidence manifests against scenario policies. */
5
+ export declare function validateScenarioScreenshots(specs: FeatureSpec[], screenshots: SpecScreenshot[]): ValidationIssue[];
4
6
  export declare function screenshotKey(filePath: string, line: number): string;
5
7
  export declare function normalizeFilePath(filePath: string): string;
6
8
  //# sourceMappingURL=screenshots.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"screenshots.d.ts","sourceRoot":"","sources":["../src/screenshots.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,qFAAqF;AACrF,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,6BAgB9D;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAE3D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,UAEjD"}
1
+ {"version":3,"file":"screenshots.d.ts","sourceRoot":"","sources":["../src/screenshots.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/E,qFAAqF;AACrF,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,6BAgB9D;AAED,wEAAwE;AACxE,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,WAAW,EAAE,EACpB,WAAW,EAAE,cAAc,EAAE,qBA0B9B;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAE3D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,UAEjD"}
@@ -22,6 +22,29 @@ export async function collectSpecScreenshots(patterns) {
22
22
  }
23
23
  return screenshots;
24
24
  }
25
+ /** Validate screenshot evidence manifests against scenario policies. */
26
+ export function validateScenarioScreenshots(specs, screenshots) {
27
+ const screenshotKeys = new Set(screenshots.map((screenshot) => screenshotKey(screenshot.specPath, screenshot.line)));
28
+ const issues = [];
29
+ for (const spec of specs) {
30
+ for (const scenario of spec.scenarios) {
31
+ if (scenario.evidence.screenshots === "required") {
32
+ for (const step of scenario.steps) {
33
+ if (!screenshotKeys.has(screenshotKey(spec.filePath, step.line))) {
34
+ issues.push({
35
+ code: "missing-screenshot-evidence",
36
+ severity: "error",
37
+ filePath: spec.filePath,
38
+ line: step.line,
39
+ message: `Screenshot evidence is required for ${scenario.id} ${step.keyword} ${step.text}`,
40
+ });
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ return issues;
47
+ }
25
48
  export function screenshotKey(filePath, line) {
26
49
  return `${normalizeFilePath(filePath)}:${line}`;
27
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"screenshots.js","sourceRoot":"","sources":["../src/screenshots.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,QAAkB;IAC7D,MAAM,WAAW,GAAqB,EAAE,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,MAAM,sBAAsB,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAY,CAAC;QACnE,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YACnC,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC;gBACrD,CAAC,CAAC,MAAM,CAAC,WAAW;gBACpB,CAAC,CAAC,EAAE,CAAC;QAET,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,UAAU,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;YAC9C,IAAI,UAAU;gBAAE,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,IAAY;IAC1D,OAAO,GAAG,iBAAiB,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,OAAO,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAc;IACzC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC;IAChD,IACE,OAAO,QAAQ,KAAK,QAAQ;QAC5B,OAAO,IAAI,KAAK,QAAQ;QACxB,OAAO,SAAS,KAAK,QAAQ,EAC7B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC;QACrC,IAAI;QACJ,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;QAChE,QAAQ,EAAE,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;KAC1E,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC"}
1
+ {"version":3,"file":"screenshots.js","sourceRoot":"","sources":["../src/screenshots.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -14,8 +14,17 @@ export type ModelReferenceFrontmatter = SpecFrontmatter & {
14
14
  model?: string;
15
15
  models?: string[] | string;
16
16
  };
17
+ export type ScenarioTestType = "unit" | "integration" | "playwright" | "manual" | "skip";
18
+ export type ScreenshotPolicy = "required" | "optional" | "skip";
19
+ export type ScenarioEvidencePolicy = {
20
+ test: ScenarioTestType;
21
+ screenshots: ScreenshotPolicy;
22
+ };
17
23
  /** Frontmatter fields supported by `*.feature.md` files. */
18
- export type FeatureFrontmatter = ModelReferenceFrontmatter;
24
+ export type FeatureFrontmatter = ModelReferenceFrontmatter & {
25
+ test?: ScenarioTestType;
26
+ screenshots?: ScreenshotPolicy;
27
+ };
19
28
  /** Frontmatter fields supported by `*.design.md` files. */
20
29
  export type DesignFrontmatter = ModelReferenceFrontmatter;
21
30
  /** Parsed contents of one `*.model.md` file. */
@@ -91,6 +100,7 @@ export type FeatureScenario = {
91
100
  id: string;
92
101
  title: string;
93
102
  line: number;
103
+ evidence: ScenarioEvidencePolicy;
94
104
  steps: FeatureStep[];
95
105
  };
96
106
  /** A single Given / When / Then style step inside a scenario. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ # Scenario Test And Evidence Policy
2
+
3
+ Feature specs can declare how scenarios are expected to be tested and whether screenshot evidence is required.
4
+
5
+ Use feature frontmatter to set defaults for all scenarios in a feature:
6
+
7
+ ```md
8
+ ---
9
+ id: KANBAN-CARD-AUTHORING
10
+ title: Card authoring
11
+ test: playwright
12
+ screenshots: required
13
+ ---
14
+ ```
15
+
16
+ Use scenario-level overrides directly below a scenario heading when one scenario needs different evidence:
17
+
18
+ ```md
19
+ ### KANBAN-CARD-AUTHORING-S002: Card title is normalized
20
+ Test: unit
21
+ Screenshots: skip
22
+
23
+ Given the raw card title contains leading whitespace
24
+ When the title is normalized
25
+ Then the stored title has no leading whitespace
26
+ ```
27
+
28
+ ## `test` values
29
+
30
+ - `unit`: behavior is expected to be covered by unit tests.
31
+ - `integration`: behavior is expected to be covered by integration tests.
32
+ - `playwright`: behavior is expected to be covered by Playwright or another UI/browser test.
33
+ - `manual`: behavior is intentionally verified manually.
34
+ - `skip`: no executable test is expected for this scenario.
35
+
36
+ ## `screenshots` values
37
+
38
+ - `required`: every scenario step should have screenshot evidence in the report manifest.
39
+ - `optional`: screenshot evidence may be included when useful, but missing screenshots do not fail validation.
40
+ - `skip`: screenshots are not expected and should not be treated as missing evidence.
41
+
42
+ `screenshots: none` is accepted as an alias for `screenshots: skip`.
43
+
44
+ ## Defaults
45
+
46
+ - If `test` is omitted, scenarios default to `unit`.
47
+ - If `screenshots` is omitted and `test` resolves to `playwright`, screenshots default to `required`.
48
+ - If `screenshots` is omitted and `test` resolves to `unit`, `integration`, `manual`, or `skip`, screenshots default to `skip`.
49
+
50
+ This lets UI scenarios fail CI when screenshot evidence is missing, while unit-only domain behavior does not need screenshots.
51
+
52
+ ## CI enforcement
53
+
54
+ Use report evidence enforcement in CI when declared screenshot evidence should block a build:
55
+
56
+ ```bash
57
+ npx feature-spec-md report \
58
+ --screenshots "test-results/spec-report/screenshots-*.json" \
59
+ --enforce-evidence \
60
+ --out test-results/spec-report/index.html
61
+ ```
62
+
63
+ The gate only fails for scenarios whose resolved screenshot policy is `required`.
@@ -0,0 +1,102 @@
1
+ # Report development mock data
2
+
3
+ The report UI is easier to develop when it can be rendered without a real app build, Playwright run, FTP server, or multiple published builds.
4
+
5
+ The mock setup keeps the maintainable source material in `src/mocks/`:
6
+
7
+ ```txt
8
+ src/mocks/
9
+ ├── current/
10
+ │ ├── app/
11
+ │ ├── specs/
12
+ │ ├── tests/
13
+ │ └── screenshots/
14
+ └── previous/
15
+ ├── app/
16
+ ├── specs/
17
+ ├── tests/
18
+ └── screenshots/
19
+ ```
20
+
21
+ The feature reports are generated by the normal library pipeline. The mock command loads the Markdown specs, scans the test files for coverage references, loads the screenshot manifests, and then renders the report HTML.
22
+
23
+ The diff report is generated by the same implementation used by the PR diff workflow. `src/mockReports.ts` prepares previous/current published-report directories, then calls `renderLocalDiffReport` from `src/githubActionDiffReport.ts`. That means the mock report exercises the real diff extraction, file comparison, screenshot grouping, and diff renderer instead of maintaining a separate mock-only diff renderer.
24
+
25
+ Run the library-owned mock setup from this package:
26
+
27
+ ```bash
28
+ npm run report:mock
29
+ ```
30
+
31
+ It writes a deterministic set of report fixtures to `test-results/mock-reports/`:
32
+
33
+ ```txt
34
+ test-results/mock-reports/
35
+ ├── feature-spec-report/index.html
36
+ ├── previous-feature-spec-report/index.html
37
+ └── diff-report/index.html
38
+ ```
39
+
40
+ The fixtures intentionally include the states that are otherwise cumbersome to reproduce by hand:
41
+
42
+ - covered and missing model/rule/scenario coverage
43
+ - current and previous screenshot evidence
44
+ - report metadata for branch, build, commit, and pull request
45
+ - a PR diff report with changed spec text, added spec text, changed screenshots, and added screenshots
46
+
47
+ Use `--out` to write the mock reports somewhere else:
48
+
49
+ ```bash
50
+ npm run report:mock -- --out /tmp/feature-spec-md-reports
51
+ ```
52
+
53
+ ## FTP publishing
54
+
55
+ The `.github/workflows/mock-reports.yml` workflow publishes the generated mock reports on every push and on manual workflow dispatch.
56
+
57
+ It uses the same publishing command that consuming projects use:
58
+
59
+ ```bash
60
+ node dist/cli.js github-report --publish ftp --report-dir test-results/mock-reports --name mock-feature-spec-reports
61
+ ```
62
+
63
+ Before publishing, the workflow adds a root `test-results/mock-reports/index.html` that links to:
64
+
65
+ - `feature-spec-report/`
66
+ - `previous-feature-spec-report/`
67
+ - `diff-report/`
68
+
69
+ The FTP publisher then uploads the whole `test-results/mock-reports` directory to `build/<github.run_number>/` and updates the build index.
70
+
71
+ Configure these repository settings before enabling the workflow:
72
+
73
+ | Name | Source |
74
+ | --- | --- |
75
+ | `FEATURE_SPEC_FTP_HOST` | Repository variable |
76
+ | `FEATURE_SPEC_FTP_USER` | Repository variable |
77
+ | `FEATURE_SPEC_FTP_PASSWORD` | Repository secret |
78
+ | `FEATURE_SPEC_REPORT_BASE_URL` | Repository variable |
79
+ | `FEATURE_SPEC_FTP_REMOTE_DIR` | Repository variable |
80
+
81
+ Optional settings:
82
+
83
+ | Name | Source |
84
+ | --- | --- |
85
+ | `FEATURE_SPEC_FTP_PORT` | Repository variable |
86
+ | `FEATURE_SPEC_FTP_SECURE` | Repository variable |
87
+
88
+ Use a mock-specific base URL and remote directory value so these reports do not overwrite the demo or any consumer report. For example, use a public base URL ending in `/mocks/` and an FTP remote directory ending in `/mocks`.
89
+
90
+ The same data is available from the library for custom dev servers or visual tests:
91
+
92
+ ```ts
93
+ import {
94
+ loadMockReportData,
95
+ renderLocalDiffReport,
96
+ renderMockDiffReport,
97
+ renderMockFeatureSpecReport,
98
+ writeMockReports,
99
+ } from "@anselmdk/feature-spec-md";
100
+ ```
101
+
102
+ Keep new reusable report states in `src/mocks/` where possible, and only put report-generation glue in `src/mockReports.ts`. That keeps the fixtures readable as normal specs, tests, manifests, and app code instead of duplicating parsed TypeScript objects.