@anselmdk/feature-spec-md 0.1.0 → 0.1.1-rc.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.
@@ -0,0 +1,154 @@
1
+ import { html } from "./html.js";
2
+ import { screenshotKey } from "./screenshots.js";
3
+ /** Render a complete self-contained feature spec report as HTML. */
4
+ export function renderHtmlReport(specs, options = {}) {
5
+ const title = options.title ?? "Feature Spec Report";
6
+ const issues = options.validationIssues ?? [];
7
+ const screenshots = options.screenshots ?? [];
8
+ return `<!doctype html>
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="utf-8">
12
+ <meta name="viewport" content="width=device-width,initial-scale=1">
13
+ <title>${html(title)}</title>
14
+ <style>
15
+ body{font-family:system-ui,sans-serif;max-width:1180px;margin:0 auto;padding:40px 24px;color:#1f2328}
16
+ .panel{border:1px solid #d0d7de;border-radius:8px;padding:20px;margin:18px 0}
17
+ .ok{color:#1a7f37}.missing,.error{color:#cf222e}.warning{color:#9a6700}
18
+ .badge{border:1px solid #d0d7de;border-radius:999px;padding:2px 8px;font-size:12px;white-space:nowrap}
19
+ .feature-header{display:flex;gap:12px;align-items:center;justify-content:space-between}
20
+ .scenario{border:1px solid #d0d7de;border-radius:8px;margin:12px 0;background:#fff}
21
+ .scenario summary{cursor:pointer;padding:14px 16px;font-weight:600}
22
+ .scenario-body{padding:0 16px 16px}
23
+ .step{border-left:3px solid #d0d7de;margin:12px 0;padding:2px 0 2px 12px}
24
+ .step p{margin:8px 0}
25
+ .screenshots{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;margin:10px 0 14px}
26
+ .screenshot{border:1px solid #d0d7de;border-radius:8px;overflow:hidden;background:#f6f8fa}
27
+ .screenshot img{display:block;width:100%;height:auto}
28
+ .screenshot figcaption{font-size:12px;padding:8px;color:#57606a}
29
+ </style>
30
+ </head>
31
+ <body>
32
+ <h1>${html(title)}</h1>
33
+ <p>Generated ${html(options.generatedAt ?? new Date().toISOString())}.</p>
34
+ ${renderIssues(issues)}
35
+ ${renderModels(options.models ?? [], options.coverage)}
36
+ ${specs.map((spec) => renderSpec(spec, options.coverage, screenshots)).join("\n")}
37
+ </body>
38
+ </html>`;
39
+ }
40
+ function renderIssues(issues) {
41
+ if (!issues.length) {
42
+ return `<section class="panel"><h2>Validation</h2><p class="ok">No validation issues found.</p></section>`;
43
+ }
44
+ return `<section class="panel"><h2>Validation</h2><ul>${issues
45
+ .map((issue) => `<li class="${issue.severity}"><code>${html(`${issue.filePath ?? ""}${issue.line ? `:${issue.line}` : ""}`)}</code> ${html(issue.message)}</li>`)
46
+ .join("")}</ul></section>`;
47
+ }
48
+ function renderModels(models, coverage) {
49
+ if (!models.length)
50
+ return "";
51
+ const modelCoverage = coverage?.modelCoverage ?? [];
52
+ return `<section class="panel">
53
+ <h2>Models</h2>
54
+ ${models
55
+ .map((model) => `<section>
56
+ <div class="feature-header">
57
+ <h3>${html(model.frontmatter.id)} ${html(model.title)}</h3>
58
+ <span class="badge">${html(model.frontmatter.status ?? "draft")}</span>
59
+ </div>
60
+ <p>${html(model.purpose)}</p>
61
+ <h4>Model</h4>
62
+ <ul>${model.modelItems
63
+ .map((item) => {
64
+ const coverageItem = modelCoverage.find((candidate) => candidate.id === item.id);
65
+ return `<li><code>${html(item.id)}</code>: ${html(item.title)} ${coverageBadge(coverageItem?.covered)}</li>`;
66
+ })
67
+ .join("")}</ul>
68
+ ${model.rules.length
69
+ ? `<h4>Rules</h4><ul>${model.rules
70
+ .map((rule) => `<li><code>${html(rule.id)}</code>: ${html(rule.text)}</li>`)
71
+ .join("")}</ul>`
72
+ : ""}
73
+ </section>`)
74
+ .join("\n")}
75
+ </section>`;
76
+ }
77
+ function renderSpec(spec, coverage, screenshots = []) {
78
+ const screenshotsByLine = groupScreenshotsByLine(screenshots);
79
+ const ruleCoverage = coverage?.ruleCoverage ?? [];
80
+ const scenarioCoverage = coverage?.scenarioCoverage ?? [];
81
+ return `<section class="panel">
82
+ <div class="feature-header">
83
+ <h2>${html(spec.frontmatter.id)} ${html(spec.title)}</h2>
84
+ <span class="badge">${html(spec.frontmatter.status ?? "draft")}</span>
85
+ </div>
86
+ <p>${html(spec.purpose)}</p>
87
+ <h3>Rules</h3>
88
+ <ul>${spec.rules
89
+ .map((rule) => {
90
+ const item = ruleCoverage.find((coverageItem) => coverageItem.id === rule.id);
91
+ return `<li><code>${html(rule.id)}</code>: ${html(rule.text)} ${coverageBadge(item?.covered, ruleScenarioShortIds(item, scenarioCoverage))}</li>`;
92
+ })
93
+ .join("")}</ul>
94
+ <h3>Scenarios</h3>
95
+ ${spec.scenarios
96
+ .map((scenario) => {
97
+ const screenshotCount = scenario.steps.reduce((count, step) => count +
98
+ (screenshotsByLine.get(screenshotKey(spec.filePath, step.line))
99
+ ?.length ?? 0), 0);
100
+ return `<details class="scenario">
101
+ <summary><code>${html(scenario.id)}</code>: ${html(scenario.title)} ${coverageBadge(scenarioCoverage.find((item) => item.id === scenario.id)?.covered)} <span class="badge">${screenshotCount} screenshot${screenshotCount === 1 ? "" : "s"}</span></summary>
102
+ <div class="scenario-body">${scenario.steps.map((step) => renderStep(spec, step, screenshotsByLine)).join("")}</div>
103
+ </details>`;
104
+ })
105
+ .join("\n")}
106
+ </section>`;
107
+ }
108
+ function renderStep(spec, step, screenshotsByLine) {
109
+ const screenshots = screenshotsByLine.get(screenshotKey(spec.filePath, step.line)) ?? [];
110
+ const evidenceBadge = screenshots.length
111
+ ? `<span class="badge ok">${screenshots.length} screenshot${screenshots.length === 1 ? "" : "s"}</span>`
112
+ : `<span class="badge missing">missing screenshot</span>`;
113
+ return `<div class="step"><p><strong>${html(step.keyword)}</strong> ${html(step.text)} <span class="badge">line ${step.line}</span> ${evidenceBadge}</p>${renderScreenshots(screenshots)}</div>`;
114
+ }
115
+ function renderScreenshots(screenshots) {
116
+ if (!screenshots.length)
117
+ return "";
118
+ return `<div class="screenshots">${screenshots
119
+ .map((screenshot) => `<figure class="screenshot"><img src="${html(screenshot.path)}" alt="${html(screenshot.title ?? `Screenshot for ${screenshot.specPath}:${screenshot.line}`)}"><figcaption>${html(screenshot.title ?? `${screenshot.specPath}:${screenshot.line}`)}</figcaption></figure>`)
120
+ .join("")}</div>`;
121
+ }
122
+ function coverageBadge(covered, suffixes = []) {
123
+ return covered === undefined
124
+ ? ""
125
+ : covered
126
+ ? `<span class="badge ok">covered${suffixes.length ? ` ${suffixes.map(html).join(" ")}` : ""}</span>`
127
+ : `<span class="badge missing">missing coverage</span>`;
128
+ }
129
+ function ruleScenarioShortIds(ruleCoverage, scenarioCoverage) {
130
+ if (!ruleCoverage?.covered)
131
+ return [];
132
+ const scenarioIds = new Set();
133
+ for (const ruleReference of ruleCoverage.references) {
134
+ for (const scenario of scenarioCoverage) {
135
+ const coversRule = scenario.references.some((scenarioReference) => scenarioReference.filePath === ruleReference.filePath &&
136
+ scenarioReference.line === ruleReference.line);
137
+ if (coversRule)
138
+ scenarioIds.add(shortScenarioId(scenario.id));
139
+ }
140
+ }
141
+ return Array.from(scenarioIds).sort();
142
+ }
143
+ function shortScenarioId(id) {
144
+ return id.match(/-(S\d{3})$/)?.[1] ?? id;
145
+ }
146
+ function groupScreenshotsByLine(screenshots) {
147
+ const grouped = new Map();
148
+ for (const screenshot of screenshots) {
149
+ const key = screenshotKey(screenshot.specPath, screenshot.line);
150
+ grouped.set(key, [...(grouped.get(key) ?? []), screenshot]);
151
+ }
152
+ return grouped;
153
+ }
154
+ //# sourceMappingURL=reportTemplate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportTemplate.js","sourceRoot":"","sources":["../src/reportTemplate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { SpecScreenshot } from "./types.js";
2
+ /** Load screenshot evidence manifests and normalize entries for report rendering. */
3
+ export declare function collectSpecScreenshots(patterns: string[]): Promise<SpecScreenshot[]>;
4
+ export declare function screenshotKey(filePath: string, line: number): string;
5
+ export declare function normalizeFilePath(filePath: string): string;
6
+ //# sourceMappingURL=screenshots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshots.d.ts","sourceRoot":"","sources":["../src/screenshots.ts"],"names":[],"mappings":"AAEA,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"}
@@ -0,0 +1,49 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { expandArtifactPatterns } from "./filePatterns.js";
3
+ /** Load screenshot evidence manifests and normalize entries for report rendering. */
4
+ export async function collectSpecScreenshots(patterns) {
5
+ const screenshots = [];
6
+ for (const file of await expandArtifactPatterns(patterns)) {
7
+ const parsed = JSON.parse(await readFile(file, "utf8"));
8
+ const entries = Array.isArray(parsed)
9
+ ? parsed
10
+ : isRecord(parsed) && Array.isArray(parsed.screenshots)
11
+ ? parsed.screenshots
12
+ : [];
13
+ for (const entry of entries) {
14
+ const screenshot = normalizeScreenshot(entry);
15
+ if (screenshot)
16
+ screenshots.push(screenshot);
17
+ }
18
+ }
19
+ return screenshots;
20
+ }
21
+ export function screenshotKey(filePath, line) {
22
+ return `${normalizeFilePath(filePath)}:${line}`;
23
+ }
24
+ export function normalizeFilePath(filePath) {
25
+ return filePath.replace(/\\/g, "/").replace(/^\.\//, "");
26
+ }
27
+ function normalizeScreenshot(value) {
28
+ if (!isRecord(value))
29
+ return null;
30
+ const specPath = value.specPath;
31
+ const line = value.line;
32
+ const imagePath = value.path ?? value.imagePath;
33
+ if (typeof specPath !== "string" ||
34
+ typeof line !== "number" ||
35
+ typeof imagePath !== "string") {
36
+ return null;
37
+ }
38
+ return {
39
+ specPath: normalizeFilePath(specPath),
40
+ line,
41
+ path: imagePath,
42
+ title: typeof value.title === "string" ? value.title : undefined,
43
+ testPath: typeof value.testPath === "string" ? value.testPath : undefined,
44
+ };
45
+ }
46
+ function isRecord(value) {
47
+ return typeof value === "object" && value !== null;
48
+ }
49
+ //# sourceMappingURL=screenshots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshots.js","sourceRoot":"","sources":["../src/screenshots.ts"],"names":[],"mappings":"AAAA,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"}
@@ -0,0 +1,72 @@
1
+ import type { DesignSpec, FeatureSpec, ModelSpec, SpecDocument, StackSpec, TestReference, ValidationIssue } from "./types.js";
2
+ type SpecKind = "model" | "feature" | "stack" | "design";
3
+ export declare function parseSpecDocument(source: string, options?: {
4
+ filePath?: string;
5
+ kind?: SpecKind;
6
+ }): SpecDocument;
7
+ export declare function parseModelSpec(source: string, options?: {
8
+ filePath?: string;
9
+ }): ModelSpec;
10
+ export declare function parseStackSpec(source: string, options?: {
11
+ filePath?: string;
12
+ }): StackSpec;
13
+ export declare function parseDesignSpec(source: string, options?: {
14
+ filePath?: string;
15
+ }): DesignSpec;
16
+ export declare function loadSpecDocuments(patterns: string[]): Promise<SpecDocument[]>;
17
+ export declare function parseSpecTestReferences(source: string, filePath?: string): TestReference[];
18
+ export declare function collectSpecTestReferences(patterns: string[]): Promise<TestReference[]>;
19
+ export declare function validateModelSpec(spec: ModelSpec): ValidationIssue[];
20
+ export declare function validateStackSpec(spec: StackSpec): ValidationIssue[];
21
+ export declare function validateDesignSpec(spec: DesignSpec): ValidationIssue[];
22
+ export declare function validateSpecDocument(spec: SpecDocument): ValidationIssue[];
23
+ export declare function validateSpecGraph(documents: SpecDocument[]): ValidationIssue[];
24
+ export declare function buildSpecCoverageSummary(documents: SpecDocument[], references: TestReference[]): {
25
+ modelCoverage: {
26
+ id: string;
27
+ title: string;
28
+ filePath: string;
29
+ line: number;
30
+ references: TestReference[];
31
+ covered: boolean;
32
+ }[];
33
+ orphanModelReferences: TestReference[];
34
+ ruleCoverage: import("./types.js").CoverageItem[];
35
+ scenarioCoverage: import("./types.js").CoverageItem[];
36
+ orphanRuleReferences: TestReference[];
37
+ orphanScenarioReferences: TestReference[];
38
+ };
39
+ export declare function checkSpecDocuments(options: {
40
+ specs: string[];
41
+ tests?: string[];
42
+ requireModelCoverage?: boolean;
43
+ requireRuleCoverage?: boolean;
44
+ requireScenarioCoverage?: boolean;
45
+ }): Promise<{
46
+ documents: SpecDocument[];
47
+ models: ModelSpec[];
48
+ features: FeatureSpec[];
49
+ stacks: StackSpec[];
50
+ designs: DesignSpec[];
51
+ specs: FeatureSpec[];
52
+ validationIssues: ValidationIssue[];
53
+ coverage: {
54
+ modelCoverage: {
55
+ id: string;
56
+ title: string;
57
+ filePath: string;
58
+ line: number;
59
+ references: TestReference[];
60
+ covered: boolean;
61
+ }[];
62
+ orphanModelReferences: TestReference[];
63
+ ruleCoverage: import("./types.js").CoverageItem[];
64
+ scenarioCoverage: import("./types.js").CoverageItem[];
65
+ orphanRuleReferences: TestReference[];
66
+ orphanScenarioReferences: TestReference[];
67
+ } | undefined;
68
+ coverageIssues: ValidationIssue[];
69
+ ok: boolean;
70
+ }>;
71
+ export {};
72
+ //# sourceMappingURL=specDocuments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specDocuments.d.ts","sourceRoot":"","sources":["../src/specDocuments.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EAEX,SAAS,EACT,YAAY,EAEZ,SAAS,EACT,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AAIpB,KAAK,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEzD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,QAAQ,CAAA;CAAO,GACnD,YAAY,CASd;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,SAAS,CAaX;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,SAAS,CAgBX;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,UAAU,CAiBZ;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,2BAMzD;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,QAAQ,SAAa,GACpB,aAAa,EAAE,CAUjB;AAED,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,4BAMjE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,EAAE,CA8BpE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,EAAE,CAWpE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,EAAE,CAgBtE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,eAAe,EAAE,CAK1E;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,eAAe,EAAE,CAmC9E;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,YAAY,EAAE,EACzB,UAAU,EAAE,aAAa,EAAE;;;;;;;;;;;;;;EAsB5B;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBA"}