@pcamarajr/scout 0.2.0 → 0.4.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.
package/dist/specs.js ADDED
@@ -0,0 +1,198 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import matter from "gray-matter";
4
+ import { SCOUT_DIR } from "./config.js";
5
+ /**
6
+ * Scenario specs live as markdown files under `.scout/specs/**\/*.scout.md`.
7
+ * One file per feature/component; each `## heading` is one scenario. The
8
+ * markdown is the human-authored source of truth — a *pure input* that is
9
+ * never written back to by a run (status/last-run derive from `.scout/runs/`).
10
+ *
11
+ * The format mirrors the Playwright Agents test-plan layout (feature file with
12
+ * scenario sections) and adds YAML frontmatter as a superset for scout-specific
13
+ * config (profile, tags). Per-scenario overrides may follow a heading as
14
+ * `key: value` lines before the prose.
15
+ */
16
+ export const SPECS_DIR = "specs";
17
+ const SPEC_EXT = ".scout.md";
18
+ /** Per-scenario override keys allowed under a heading. */
19
+ const OVERRIDE_KEYS = new Set(["profile", "notes", "tags"]);
20
+ export function slugify(name) {
21
+ return name
22
+ .toLowerCase()
23
+ .normalize("NFD")
24
+ .replace(/[̀-ͯ]/g, "")
25
+ .replace(/[^a-z0-9]+/g, "-")
26
+ .replace(/^-|-$/g, "")
27
+ .slice(0, 60);
28
+ }
29
+ /** A logical slug `<file>/<scenario>` → a single filesystem-safe token. */
30
+ export function slugToToken(slug) {
31
+ return slug.replace(/\//g, "__");
32
+ }
33
+ function specsRoot(cwd) {
34
+ return path.join(cwd, SCOUT_DIR, SPECS_DIR);
35
+ }
36
+ /** All `*.scout.md` under `.scout/specs`, recursively, sorted for stable order. */
37
+ function findSpecFiles(dir) {
38
+ if (!fs.existsSync(dir))
39
+ return [];
40
+ const out = [];
41
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
42
+ const full = path.join(dir, entry.name);
43
+ if (entry.isDirectory())
44
+ out.push(...findSpecFiles(full));
45
+ else if (entry.isFile() && entry.name.endsWith(SPEC_EXT))
46
+ out.push(full);
47
+ }
48
+ return out.sort();
49
+ }
50
+ /** `.scout/specs/auth/login.scout.md` → `auth/login` (each segment slugified). */
51
+ function fileSlugFromPath(specFile, root) {
52
+ const rel = path.relative(root, specFile).replace(new RegExp(`${SPEC_EXT.replace(".", "\\.")}$`), "");
53
+ return rel
54
+ .split(path.sep)
55
+ .map((seg) => slugify(seg))
56
+ .join("/");
57
+ }
58
+ function parseTags(raw) {
59
+ if (Array.isArray(raw))
60
+ return raw.map(String);
61
+ if (typeof raw === "string") {
62
+ const inner = raw.trim().replace(/^\[|\]$/g, "");
63
+ const parts = inner.split(",").map((t) => t.trim()).filter(Boolean);
64
+ return parts.length ? parts : undefined;
65
+ }
66
+ return undefined;
67
+ }
68
+ /**
69
+ * Split markdown body into `## ` sections (h1/h3+ and preamble are ignored).
70
+ * Fenced code blocks (``` ... ```) are skipped so a documented `## ` inside a
71
+ * fence is not mistaken for a real scenario heading.
72
+ */
73
+ function splitSections(content) {
74
+ const sections = [];
75
+ let cur = null;
76
+ let inFence = false;
77
+ for (const line of content.split("\n")) {
78
+ if (/^\s*```/.test(line))
79
+ inFence = !inFence;
80
+ const m = inFence ? null : /^##\s+(.+?)\s*$/.exec(line);
81
+ if (m) {
82
+ cur = { name: m[1].trim(), bodyLines: [] };
83
+ sections.push(cur);
84
+ }
85
+ else if (cur) {
86
+ cur.bodyLines.push(line);
87
+ }
88
+ }
89
+ return sections;
90
+ }
91
+ /** Parse a single `.scout.md` file into its scenarios. */
92
+ export function parseSpec(specFile, root, cwd) {
93
+ const fileSlug = fileSlugFromPath(specFile, root);
94
+ const raw = fs.readFileSync(specFile, "utf8");
95
+ const { data, content } = matter(raw);
96
+ const fileFeature = typeof data.feature === "string" ? data.feature : path.basename(specFile).replace(SPEC_EXT, "");
97
+ const fileProfile = typeof data.profile === "string" ? data.profile : undefined;
98
+ const fileTags = parseTags(data.tags);
99
+ const relFile = path.relative(cwd, specFile);
100
+ const sections = splitSections(content);
101
+ const scenarios = [];
102
+ const seen = new Set();
103
+ for (const section of sections) {
104
+ // Consume optional leading `key: value` overrides, then the prose.
105
+ const lines = section.bodyLines;
106
+ let i = 0;
107
+ while (i < lines.length && lines[i].trim() === "")
108
+ i++;
109
+ const overrides = {};
110
+ while (i < lines.length && lines[i].trim() !== "") {
111
+ const m = /^([a-zA-Z]+):\s*(.+)$/.exec(lines[i].trim());
112
+ if (!m || !OVERRIDE_KEYS.has(m[1]))
113
+ break;
114
+ overrides[m[1]] = m[2].trim();
115
+ i++;
116
+ }
117
+ while (i < lines.length && lines[i].trim() === "")
118
+ i++;
119
+ const text = lines.slice(i).join("\n").trim();
120
+ if (!text) {
121
+ throw new Error(`Scenario "${section.name}" in ${relFile} has no description text.`);
122
+ }
123
+ const scenarioSlug = slugify(section.name);
124
+ if (seen.has(scenarioSlug)) {
125
+ throw new Error(`Duplicate scenario "${section.name}" (slug "${scenarioSlug}") in ${relFile}.`);
126
+ }
127
+ seen.add(scenarioSlug);
128
+ const tags = parseTags(overrides.tags) ?? fileTags;
129
+ scenarios.push({
130
+ slug: `${fileSlug}/${scenarioSlug}`,
131
+ name: section.name,
132
+ scenario: text,
133
+ feature: fileFeature,
134
+ profile: overrides.profile ?? fileProfile,
135
+ notes: overrides.notes,
136
+ tags,
137
+ file: relFile,
138
+ });
139
+ }
140
+ return scenarios;
141
+ }
142
+ /** Load every scenario across all spec files, validating slug uniqueness. */
143
+ export function loadScenarios(cwd = process.cwd()) {
144
+ const root = specsRoot(cwd);
145
+ const all = [];
146
+ const bySlug = new Map();
147
+ for (const file of findSpecFiles(root)) {
148
+ for (const scenario of parseSpec(file, root, cwd)) {
149
+ const prev = bySlug.get(scenario.slug);
150
+ if (prev) {
151
+ throw new Error(`Duplicate scenario slug "${scenario.slug}" in ${scenario.file} (also in ${prev}).`);
152
+ }
153
+ bySlug.set(scenario.slug, scenario.file);
154
+ all.push(scenario);
155
+ }
156
+ }
157
+ return all;
158
+ }
159
+ /** Render a `## ` section (with optional overrides) for appending to a spec. */
160
+ function renderSection(input) {
161
+ const overrides = [];
162
+ if (input.profile)
163
+ overrides.push(`profile: ${input.profile}`);
164
+ if (input.notes)
165
+ overrides.push(`notes: ${input.notes}`);
166
+ if (input.tags?.length)
167
+ overrides.push(`tags: [${input.tags.join(", ")}]`);
168
+ const head = `## ${input.name}\n`;
169
+ const meta = overrides.length ? overrides.join("\n") + "\n\n" : "";
170
+ return `${head}${meta}${input.scenario.trim()}\n`;
171
+ }
172
+ /**
173
+ * Append a scenario to `.scout/specs/<feature-slug>.scout.md`, creating the
174
+ * file with frontmatter when it does not exist. Returns the logical slug.
175
+ */
176
+ export function addScenario(input, cwd = process.cwd()) {
177
+ const root = specsRoot(cwd);
178
+ const fileSlug = slugify(input.feature);
179
+ const specFile = path.join(root, `${fileSlug}${SPEC_EXT}`);
180
+ fs.mkdirSync(path.dirname(specFile), { recursive: true });
181
+ const section = renderSection(input);
182
+ if (!fs.existsSync(specFile)) {
183
+ const frontmatter = `---\nfeature: ${input.feature}\n---\n\n`;
184
+ fs.writeFileSync(specFile, frontmatter + section);
185
+ }
186
+ else {
187
+ const existing = fs.readFileSync(specFile, "utf8").replace(/\s*$/, "");
188
+ fs.writeFileSync(specFile, existing + "\n\n" + section);
189
+ }
190
+ const scenarioSlug = slugify(input.name);
191
+ const slug = `${fileSlug}/${scenarioSlug}`;
192
+ const found = parseSpec(specFile, root, cwd).find((s) => s.slug === slug);
193
+ if (!found) {
194
+ throw new Error(`Failed to round-trip scenario "${input.name}" into ${path.relative(cwd, specFile)}.`);
195
+ }
196
+ return found;
197
+ }
198
+ //# sourceMappingURL=specs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specs.js","sourceRoot":"","sources":["../src/specs.ts"],"names":[],"mappings":"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"}
package/dist/store.d.ts CHANGED
@@ -1,27 +1,19 @@
1
1
  import type { RunResult, Scenario, Step } from "./types.js";
2
2
  /**
3
3
  * Filesystem store inside the target project:
4
- * .scout/scenarios.json — committed (the suite definition)
5
- * .scout/scripts/<slug>.json — committed (cached deterministic steps)
6
- * .scout/runs/<runId>/ — gitignored (artifacts per run)
7
- * .scout/state/<profile>.json — gitignored (auth storageState)
4
+ * .scout/specs/<feature>.scout.md — committed (the suite; markdown source of truth)
5
+ * .scout/scripts/<slug>.json — committed (cached deterministic steps; `<slug>` is `<file>/<scenario>`)
6
+ * .scout/runs/<runId>/ — gitignored (artifacts per run)
7
+ * .scout/state/<profile>.json — gitignored (auth storageState)
8
8
  */
9
9
  export declare class Store {
10
10
  readonly root: string;
11
+ readonly cwd: string;
11
12
  constructor(cwd?: string);
12
13
  init(): void;
13
- get scenariosFile(): string;
14
14
  exists(): boolean;
15
15
  listScenarios(): Scenario[];
16
- saveScenarios(scenarios: Scenario[]): void;
17
- addScenario(input: {
18
- name: string;
19
- scenario: string;
20
- profile?: string;
21
- notes?: string;
22
- }): Scenario;
23
- getScenario(idOrSlug: number | string): Scenario | undefined;
24
- updateScenario(id: number, patch: Partial<Scenario>): void;
16
+ getScenario(slug: string): Scenario | undefined;
25
17
  scriptPath(slug: string): string;
26
18
  loadSteps(slug: string): Step[] | undefined;
27
19
  saveSteps(slug: string, steps: Step[]): void;
@@ -30,4 +22,3 @@ export declare class Store {
30
22
  /** Latest run result per scenario slug (for `scout report`) */
31
23
  latestRuns(): Map<string, RunResult>;
32
24
  }
33
- export declare function slugify(name: string): string;
package/dist/store.js CHANGED
@@ -1,19 +1,23 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { SCOUT_DIR } from "./config.js";
4
+ import { SPECS_DIR, loadScenarios, slugToToken } from "./specs.js";
4
5
  /**
5
6
  * Filesystem store inside the target project:
6
- * .scout/scenarios.json — committed (the suite definition)
7
- * .scout/scripts/<slug>.json — committed (cached deterministic steps)
8
- * .scout/runs/<runId>/ — gitignored (artifacts per run)
9
- * .scout/state/<profile>.json — gitignored (auth storageState)
7
+ * .scout/specs/<feature>.scout.md — committed (the suite; markdown source of truth)
8
+ * .scout/scripts/<slug>.json — committed (cached deterministic steps; `<slug>` is `<file>/<scenario>`)
9
+ * .scout/runs/<runId>/ — gitignored (artifacts per run)
10
+ * .scout/state/<profile>.json — gitignored (auth storageState)
10
11
  */
11
12
  export class Store {
12
13
  root;
14
+ cwd;
13
15
  constructor(cwd = process.cwd()) {
16
+ this.cwd = cwd;
14
17
  this.root = path.join(cwd, SCOUT_DIR);
15
18
  }
16
19
  init() {
20
+ fs.mkdirSync(path.join(this.root, SPECS_DIR), { recursive: true });
17
21
  fs.mkdirSync(path.join(this.root, "scripts"), { recursive: true });
18
22
  fs.mkdirSync(path.join(this.root, "runs"), { recursive: true });
19
23
  fs.mkdirSync(path.join(this.root, "state"), { recursive: true });
@@ -21,51 +25,19 @@ export class Store {
21
25
  if (!fs.existsSync(gitignore)) {
22
26
  fs.writeFileSync(gitignore, "runs/\nstate/\n");
23
27
  }
24
- if (!fs.existsSync(this.scenariosFile)) {
25
- fs.writeFileSync(this.scenariosFile, "[]\n");
28
+ const example = path.join(this.root, SPECS_DIR, "example.scout.md");
29
+ if (!fs.existsSync(example)) {
30
+ fs.writeFileSync(example, EXAMPLE_SPEC);
26
31
  }
27
32
  }
28
- get scenariosFile() {
29
- return path.join(this.root, "scenarios.json");
30
- }
31
33
  exists() {
32
- return fs.existsSync(this.scenariosFile);
34
+ return fs.existsSync(this.root);
33
35
  }
34
36
  listScenarios() {
35
- if (!this.exists())
36
- return [];
37
- return JSON.parse(fs.readFileSync(this.scenariosFile, "utf8"));
38
- }
39
- saveScenarios(scenarios) {
40
- fs.writeFileSync(this.scenariosFile, JSON.stringify(scenarios, null, 2) + "\n");
41
- }
42
- addScenario(input) {
43
- const scenarios = this.listScenarios();
44
- const id = scenarios.length ? Math.max(...scenarios.map((s) => s.id)) + 1 : 1;
45
- const scenario = {
46
- id,
47
- slug: slugify(input.name),
48
- name: input.name,
49
- scenario: input.scenario,
50
- profile: input.profile,
51
- notes: input.notes,
52
- status: "pending",
53
- createdAt: new Date().toISOString(),
54
- };
55
- scenarios.push(scenario);
56
- this.saveScenarios(scenarios);
57
- return scenario;
37
+ return loadScenarios(this.cwd);
58
38
  }
59
- getScenario(idOrSlug) {
60
- return this.listScenarios().find((s) => s.id === Number(idOrSlug) || s.slug === idOrSlug);
61
- }
62
- updateScenario(id, patch) {
63
- const scenarios = this.listScenarios();
64
- const idx = scenarios.findIndex((s) => s.id === id);
65
- if (idx === -1)
66
- throw new Error(`Scenario ${id} not found`);
67
- scenarios[idx] = { ...scenarios[idx], ...patch };
68
- this.saveScenarios(scenarios);
39
+ getScenario(slug) {
40
+ return this.listScenarios().find((s) => s.slug === slug);
69
41
  }
70
42
  // ---- cached deterministic scripts ----
71
43
  scriptPath(slug) {
@@ -78,12 +50,14 @@ export class Store {
78
50
  return JSON.parse(fs.readFileSync(file, "utf8"));
79
51
  }
80
52
  saveSteps(slug, steps) {
81
- fs.writeFileSync(this.scriptPath(slug), JSON.stringify(steps, null, 2) + "\n");
53
+ const file = this.scriptPath(slug);
54
+ fs.mkdirSync(path.dirname(file), { recursive: true });
55
+ fs.writeFileSync(file, JSON.stringify(steps, null, 2) + "\n");
82
56
  }
83
57
  // ---- runs ----
84
58
  newRunDir(slug) {
85
59
  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
86
- const dir = path.join(this.root, "runs", `${stamp}-${slug}`);
60
+ const dir = path.join(this.root, "runs", `${stamp}-${slugToToken(slug)}`);
87
61
  fs.mkdirSync(dir, { recursive: true });
88
62
  return dir;
89
63
  }
@@ -107,13 +81,35 @@ export class Store {
107
81
  return map;
108
82
  }
109
83
  }
110
- export function slugify(name) {
111
- return name
112
- .toLowerCase()
113
- .normalize("NFD")
114
- .replace(/[̀-ͯ]/g, "")
115
- .replace(/[^a-z0-9]+/g, "-")
116
- .replace(/^-|-$/g, "")
117
- .slice(0, 60);
118
- }
84
+ // The example sits inside a fenced block so it loads as ZERO scenarios — it
85
+ // documents the format without polluting the suite (or the `--check` gate).
86
+ const EXAMPLE_SPEC = `---
87
+ feature: Example
88
+ ---
89
+
90
+ # How to write a scout spec
91
+
92
+ One file per feature/component. File-level frontmatter sets defaults
93
+ (\`feature\`, \`profile\`, \`tags\`). Each \`##\` heading is one scenario; optional
94
+ \`profile\`/\`notes\`/\`tags\` override lines may follow a heading before the prose.
95
+ Write the flow + expected behavior in plain language — no selectors, no code.
96
+
97
+ Copy the block below into a new \`*.scout.md\` file (outside the fence) to start:
98
+
99
+ \`\`\`markdown
100
+ ---
101
+ feature: Paywall
102
+ profile: anon
103
+ tags: [monetization]
104
+ ---
105
+
106
+ ## Free user hits paywall on ep 3
107
+ Open ep 3 of series X without login; paywall appears with a signup CTA.
108
+
109
+ ## Subscriber bypasses paywall
110
+ profile: qa
111
+
112
+ Logged-in subscriber opens ep 3; plays with no paywall.
113
+ \`\`\`
114
+ `;
119
115
  //# sourceMappingURL=store.js.map
package/dist/store.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGnE;;;;;;GAMG;AACH,MAAM,OAAO,KAAK;IACP,IAAI,CAAS;IACb,GAAG,CAAS;IAErB,YAAY,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;QAC7B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACxC,CAAC;IAED,IAAI;QACF,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QACrD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,kBAAkB,CAAC,CAAC;QACpE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,MAAM;QACJ,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,aAAa;QACX,OAAO,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,WAAW,CAAC,IAAY;QACtB,OAAO,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC3D,CAAC;IAED,yCAAyC;IAEzC,UAAU,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;IACzD,CAAC;IAED,SAAS,CAAC,IAAY;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,SAAS,CAAC;QAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,SAAS,CAAC,IAAY,EAAE,KAAa;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACnC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAChE,CAAC;IAED,iBAAiB;IAEjB,SAAS,CAAC,IAAY;QACpB,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1E,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvC,OAAO,GAAG,CAAC;IACb,CAAC;IAED,aAAa,CAAC,MAAiB;QAC7B,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,EACvC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CACvC,CAAC;IACJ,CAAC;IAED,+DAA+D;IAC/D,UAAU;QACR,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAqB,CAAC;QACzC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,GAAG,CAAC;QACxC,MAAM,IAAI,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,mCAAmC;QAChF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,aAAa,CAAC,CAAC;YACpD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAS;YACnC,MAAM,MAAM,GAAc,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;YACpE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,2BAA2B;QAC3D,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF;AAED,4EAA4E;AAC5E,4EAA4E;AAC5E,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BpB,CAAC"}
package/dist/types.d.ts CHANGED
@@ -46,21 +46,30 @@ export type Step = {
46
46
  };
47
47
  export type Verdict = "verified" | "failed" | "partial" | "blocked";
48
48
  export type ScenarioStatus = Verdict | "pending";
49
+ /**
50
+ * A single verification scenario, parsed from a `.scout/specs/**\/*.scout.md`
51
+ * file. The markdown is the source of truth and a *pure input* — it is never
52
+ * mutated by a run. Status and last-run derive from `.scout/runs/` instead.
53
+ */
49
54
  export interface Scenario {
50
- id: number;
55
+ /** Logical identity, unique across the suite: `<file-slug>/<scenario-slug>` */
51
56
  slug: string;
57
+ /** Heading text of the scenario section */
52
58
  name: string;
53
59
  /** Natural-language description of the flow + expected behavior */
54
60
  scenario: string;
61
+ /** Feature/group this scenario belongs to (frontmatter `feature` or filename) */
62
+ feature: string;
55
63
  /** Auth profile name from scout.config.json (omit = anonymous) */
56
64
  profile?: string;
57
- status: ScenarioStatus;
58
- createdAt: string;
59
- lastRun?: string;
65
+ /** Extra context handed to the AI agent */
60
66
  notes?: string;
67
+ /** Free-form tags (file-level + per-scenario, merged) */
68
+ tags?: string[];
69
+ /** Source spec file, relative to the project root */
70
+ file: string;
61
71
  }
62
72
  export interface RunResult {
63
- scenarioId: number;
64
73
  slug: string;
65
74
  /** replay = cached deterministic steps; ai = agent-driven run */
66
75
  mode: "replay" | "ai";
@@ -75,4 +84,10 @@ export interface RunResult {
75
84
  trace?: string;
76
85
  /** true when this AI run replaced a broken cached script */
77
86
  healed?: boolean;
87
+ /**
88
+ * Set when the AI runner itself failed to obtain a verdict (infrastructure
89
+ * failure: turn budget, SDK error). The verdict is "blocked" by convention,
90
+ * but it is NOT a UI judgment — rerun instead of debugging the app.
91
+ */
92
+ runnerFailure?: string;
78
93
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pcamarajr/scout",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Self-healing browser QA: natural-language scenarios verified by an AI agent, replayed deterministically in CI",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,6 +49,7 @@
49
49
  "@anthropic-ai/claude-agent-sdk": "^0.3.0",
50
50
  "@modelcontextprotocol/sdk": "^1.12.0",
51
51
  "commander": "^13.1.0",
52
+ "gray-matter": "^4.0.3",
52
53
  "playwright": "^1.52.0",
53
54
  "zod": "^4.0.0"
54
55
  },