doc-detective 4.0.2-dev.1 → 4.0.2-dev.11

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 (97) hide show
  1. package/dist/agents/adapters/claude-code.d.ts +77 -0
  2. package/dist/agents/adapters/claude-code.d.ts.map +1 -0
  3. package/dist/agents/adapters/claude-code.js +461 -0
  4. package/dist/agents/adapters/claude-code.js.map +1 -0
  5. package/dist/agents/adapters/codex.d.ts +64 -0
  6. package/dist/agents/adapters/codex.d.ts.map +1 -0
  7. package/dist/agents/adapters/codex.js +299 -0
  8. package/dist/agents/adapters/codex.js.map +1 -0
  9. package/dist/agents/adapters/copilot-cli.d.ts +29 -0
  10. package/dist/agents/adapters/copilot-cli.d.ts.map +1 -0
  11. package/dist/agents/adapters/copilot-cli.js +195 -0
  12. package/dist/agents/adapters/copilot-cli.js.map +1 -0
  13. package/dist/agents/adapters/gemini-cli.d.ts +29 -0
  14. package/dist/agents/adapters/gemini-cli.d.ts.map +1 -0
  15. package/dist/agents/adapters/gemini-cli.js +207 -0
  16. package/dist/agents/adapters/gemini-cli.js.map +1 -0
  17. package/dist/agents/adapters/opencode.d.ts +67 -0
  18. package/dist/agents/adapters/opencode.d.ts.map +1 -0
  19. package/dist/agents/adapters/opencode.js +341 -0
  20. package/dist/agents/adapters/opencode.js.map +1 -0
  21. package/dist/agents/adapters/qwen-code.d.ts +30 -0
  22. package/dist/agents/adapters/qwen-code.d.ts.map +1 -0
  23. package/dist/agents/adapters/qwen-code.js +212 -0
  24. package/dist/agents/adapters/qwen-code.js.map +1 -0
  25. package/dist/agents/command.d.ts +11 -0
  26. package/dist/agents/command.d.ts.map +1 -0
  27. package/dist/agents/command.js +41 -0
  28. package/dist/agents/command.js.map +1 -0
  29. package/dist/agents/fetcher.d.ts +30 -0
  30. package/dist/agents/fetcher.d.ts.map +1 -0
  31. package/dist/agents/fetcher.js +112 -0
  32. package/dist/agents/fetcher.js.map +1 -0
  33. package/dist/agents/prompts.d.ts +24 -0
  34. package/dist/agents/prompts.d.ts.map +1 -0
  35. package/dist/agents/prompts.js +74 -0
  36. package/dist/agents/prompts.js.map +1 -0
  37. package/dist/agents/registry.d.ts +4 -0
  38. package/dist/agents/registry.d.ts.map +1 -0
  39. package/dist/agents/registry.js +25 -0
  40. package/dist/agents/registry.js.map +1 -0
  41. package/dist/agents/runner.d.ts +13 -0
  42. package/dist/agents/runner.d.ts.map +1 -0
  43. package/dist/agents/runner.js +155 -0
  44. package/dist/agents/runner.js.map +1 -0
  45. package/dist/agents/spawn-helper.d.ts +33 -0
  46. package/dist/agents/spawn-helper.d.ts.map +1 -0
  47. package/dist/agents/spawn-helper.js +98 -0
  48. package/dist/agents/spawn-helper.js.map +1 -0
  49. package/dist/agents/types.d.ts +41 -0
  50. package/dist/agents/types.d.ts.map +1 -0
  51. package/dist/agents/types.js +2 -0
  52. package/dist/agents/types.js.map +1 -0
  53. package/dist/cli.js +42 -10
  54. package/dist/cli.js.map +1 -1
  55. package/dist/common/src/detectTests.d.ts.map +1 -1
  56. package/dist/common/src/detectTests.js +43 -12
  57. package/dist/common/src/detectTests.js.map +1 -1
  58. package/dist/common/src/fileTypes.d.ts.map +1 -1
  59. package/dist/common/src/fileTypes.js +10 -0
  60. package/dist/common/src/fileTypes.js.map +1 -1
  61. package/dist/common/src/schemas/schemas.json +594 -0
  62. package/dist/common/src/types/generated/checkLink_v3.d.ts +15 -0
  63. package/dist/common/src/types/generated/checkLink_v3.d.ts.map +1 -1
  64. package/dist/common/src/types/generated/config_v3.d.ts +4 -0
  65. package/dist/common/src/types/generated/config_v3.d.ts.map +1 -1
  66. package/dist/common/src/types/generated/resolvedTests_v3.d.ts +4 -0
  67. package/dist/common/src/types/generated/resolvedTests_v3.d.ts.map +1 -1
  68. package/dist/common/src/types/generated/step_v3.d.ts +15 -0
  69. package/dist/common/src/types/generated/step_v3.d.ts.map +1 -1
  70. package/dist/common/src/types/generated/test_v3.d.ts +30 -0
  71. package/dist/common/src/types/generated/test_v3.d.ts.map +1 -1
  72. package/dist/core/config.d.ts.map +1 -1
  73. package/dist/core/config.js +10 -0
  74. package/dist/core/config.js.map +1 -1
  75. package/dist/core/integrations/heretto.d.ts.map +1 -1
  76. package/dist/core/integrations/heretto.js +11 -3
  77. package/dist/core/integrations/heretto.js.map +1 -1
  78. package/dist/core/tests/checkLink.d.ts.map +1 -1
  79. package/dist/core/tests/checkLink.js +136 -29
  80. package/dist/core/tests/checkLink.js.map +1 -1
  81. package/dist/core/tests/loadCookie.d.ts.map +1 -1
  82. package/dist/core/tests/loadCookie.js +12 -2
  83. package/dist/core/tests/loadCookie.js.map +1 -1
  84. package/dist/core/tests/saveScreenshot.d.ts +15 -1
  85. package/dist/core/tests/saveScreenshot.d.ts.map +1 -1
  86. package/dist/core/tests/saveScreenshot.js +41 -17
  87. package/dist/core/tests/saveScreenshot.js.map +1 -1
  88. package/dist/index.cjs +816 -59
  89. package/dist/reporters/htmlReporter.d.ts +2 -0
  90. package/dist/reporters/htmlReporter.d.ts.map +1 -0
  91. package/dist/reporters/htmlReporter.js +1589 -0
  92. package/dist/reporters/htmlReporter.js.map +1 -0
  93. package/dist/utils.d.ts +2 -1
  94. package/dist/utils.d.ts.map +1 -1
  95. package/dist/utils.js +43 -10
  96. package/dist/utils.js.map +1 -1
  97. package/package.json +3 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agents/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAE/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE;QACR,UAAU,EAAE,CAAC,SAAS,EAAE,YAAY,EAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7D,SAAS,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;KACnD,CAAC;IACF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAC;IACrD,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC;CACvB;AAOD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,iBAAiB,EACvB,IAAI,GAAE,UAAe,GACpB,OAAO,CAAC,aAAa,EAAE,CAAC,CAmE1B"}
@@ -0,0 +1,155 @@
1
+ import { listAdapters } from "./registry.js";
2
+ const defaultLogger = (msg, level = "info") => {
3
+ if (level === "error")
4
+ console.error(msg);
5
+ else
6
+ console.log(msg);
7
+ };
8
+ export async function runInstallAgents(argv, deps = {}) {
9
+ const adapters = deps.adapters ?? listAdapters();
10
+ const logger = deps.logger ?? defaultLogger;
11
+ const isTTY = deps.isTTY ?? (() => Boolean(process.stdin.isTTY));
12
+ const dryRun = argv["dry-run"] === true;
13
+ // --yes requires explicit --agent and --scope; no prompts.
14
+ if (argv.yes) {
15
+ if (!argv.agent || argv.agent.length === 0) {
16
+ throw new Error("--yes requires at least one --agent (for example, --agent claude).");
17
+ }
18
+ if (!argv.scope) {
19
+ throw new Error("--yes requires --scope project|global.");
20
+ }
21
+ }
22
+ // Resolve which agents to target.
23
+ const targeted = await resolveTargetAgents(argv, adapters, {
24
+ isTTY: isTTY(),
25
+ prompts: deps.prompts,
26
+ logger,
27
+ });
28
+ if (targeted.length === 0) {
29
+ return [];
30
+ }
31
+ // Resolve scope.
32
+ const scope = await resolveScope(argv, targeted, {
33
+ isTTY: isTTY(),
34
+ prompts: deps.prompts,
35
+ });
36
+ // Install each in order; collect reports. When an adapter doesn't support
37
+ // the requested scope, degrade to its nearest supported scope and attach a
38
+ // note to the report so callers see the divergence.
39
+ const reports = [];
40
+ for (const adapter of targeted) {
41
+ const effective = effectiveScopeFor(adapter, scope);
42
+ if (effective.degraded) {
43
+ logger(`⚠ ${adapter.displayName} does not support ${scope} scope — installing as ${effective.scope}.`, "warn");
44
+ }
45
+ logger(`\n→ ${adapter.displayName} (${adapter.id}) — scope: ${effective.scope}`, "info");
46
+ const report = await adapter.install({
47
+ scope: effective.scope,
48
+ force: Boolean(argv.force),
49
+ dryRun,
50
+ logger,
51
+ });
52
+ const finalReport = effective.degraded
53
+ ? {
54
+ ...report,
55
+ notes: [
56
+ ...(report.notes ?? []),
57
+ `Requested scope '${scope}' is not supported by ${adapter.displayName}; installed as '${effective.scope}' instead.`,
58
+ ],
59
+ }
60
+ : report;
61
+ reports.push(finalReport);
62
+ logger(summarizeReport(finalReport), "info");
63
+ }
64
+ return reports;
65
+ }
66
+ /**
67
+ * Resolve the effective scope an adapter will actually receive. If the desired
68
+ * scope is supported, pass it through. Otherwise, degrade to the adapter's
69
+ * first supported scope and flag that we did.
70
+ */
71
+ function effectiveScopeFor(adapter, desired) {
72
+ const supported = adapter.supportsScopes();
73
+ if (supported.includes(desired))
74
+ return { scope: desired, degraded: false };
75
+ return { scope: supported[0], degraded: true };
76
+ }
77
+ async function resolveTargetAgents(argv, adapters, ctx) {
78
+ if (argv.agent && argv.agent.length > 0) {
79
+ const chosen = [];
80
+ const seen = new Set();
81
+ for (const id of argv.agent) {
82
+ // Preserve first-seen order but drop duplicates — `--agent codex --agent codex`
83
+ // should run one install, not two.
84
+ if (seen.has(id))
85
+ continue;
86
+ seen.add(id);
87
+ const match = adapters.find((a) => a.id === id);
88
+ if (!match) {
89
+ const known = adapters.map((a) => a.id).join(", ");
90
+ throw new Error(`Unknown agent '${id}'. Known agents: ${known}`);
91
+ }
92
+ chosen.push(match);
93
+ }
94
+ return chosen;
95
+ }
96
+ // No --agent: run detection across all adapters and prompt the user with the
97
+ // ones that reported present.
98
+ const detections = await Promise.all(adapters.map(async (a) => ({ adapter: a, result: await a.detect() })));
99
+ const detected = detections.filter((d) => d.result.present).map((d) => d.adapter);
100
+ if (detected.length === 0) {
101
+ ctx.logger("No supported coding agents detected on this machine. Install one (e.g., Claude Code) and re-run.", "info");
102
+ return [];
103
+ }
104
+ if (!ctx.isTTY) {
105
+ throw new Error("Cannot prompt for agents in a non-TTY environment. Pass --agent explicitly (repeatable).");
106
+ }
107
+ if (!ctx.prompts) {
108
+ throw new Error("No prompts implementation available; pass --agent explicitly.");
109
+ }
110
+ const selectedIds = await ctx.prompts.pickAgents(detected);
111
+ return detected.filter((a) => selectedIds.includes(a.id));
112
+ }
113
+ async function resolveScope(argv, targeted, ctx) {
114
+ if (argv.scope === "project" || argv.scope === "global") {
115
+ return argv.scope;
116
+ }
117
+ // Intersect the supported-scope sets across all chosen adapters. Seed with
118
+ // the first adapter's supported scopes (not `[]`) so that an empty running
119
+ // intersection stays empty — otherwise the next adapter would re-seed it.
120
+ const [first, ...rest] = targeted;
121
+ const intersection = rest.reduce((acc, a) => acc.filter((s) => a.supportsScopes().includes(s)), first.supportsScopes());
122
+ // If no scope is common to every chosen adapter, pick "global" as a
123
+ // sensible default and let each adapter degrade via effectiveScopeFor().
124
+ if (intersection.length === 0)
125
+ return "global";
126
+ if (intersection.length === 1)
127
+ return intersection[0];
128
+ if (!ctx.isTTY) {
129
+ throw new Error("Cannot prompt for scope in a non-TTY environment. Pass --scope project|global.");
130
+ }
131
+ if (!ctx.prompts) {
132
+ throw new Error("No prompts implementation available; pass --scope project|global.");
133
+ }
134
+ return ctx.prompts.pickScope(intersection);
135
+ }
136
+ function summarizeReport(r) {
137
+ const ver = r.installedVersion ? ` @ ${r.installedVersion}` : "";
138
+ switch (r.action) {
139
+ case "installed":
140
+ return ` installed${ver}`;
141
+ case "updated":
142
+ return ` updated${ver}`;
143
+ case "already-up-to-date":
144
+ return ` already up to date${ver}`;
145
+ case "forced":
146
+ return ` forced reinstall${ver}`;
147
+ case "dry-run":
148
+ return ` dry-run (no changes)`;
149
+ case "fallback":
150
+ return ` wrote settings.json fallback${ver}`;
151
+ default:
152
+ return ` ${r.action}${ver}`;
153
+ }
154
+ }
155
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/agents/runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ export interface SpawnResult {
2
+ stdout: string;
3
+ stderr: string;
4
+ exitCode: number;
5
+ }
6
+ /**
7
+ * Build a single cmd.exe-safe command line from (cmd, args). Follows the
8
+ * canonical `CommandLineToArgvW` escaping rules so the child process sees
9
+ * each arg verbatim:
10
+ * 1. Double any run of backslashes that directly precedes a `"` or the
11
+ * end of the string (so the closing `"` we add below isn't escaped).
12
+ * 2. Escape each embedded `"` as `\"`.
13
+ * 3. Wrap the whole arg in `"..."`.
14
+ * Naive `s.replace(/"/g, '\\"')` is wrong for args like `a\"b` or `a\\`
15
+ * because the preceding backslashes turn the intended escape into a real
16
+ * backslash + unescaped quote, breaking out of the quoted context.
17
+ *
18
+ * Chose this shape because Node 22+'s DEP0190 deprecates
19
+ * `spawn(cmd, args[], {shell:true})` (joining args without escaping), but
20
+ * `spawn(<single string>, [], {shell:true})` stays supported. Inside
21
+ * double quotes cmd.exe treats `&|<>^` literally, so no caret escaping is
22
+ * needed for our hardcoded args.
23
+ */
24
+ export declare function winCommandLine(cmd: string, args: string[]): string;
25
+ /**
26
+ * Spawn a command and resolve with its stdout/stderr/exit code. Unlike the
27
+ * shared `spawnCommand` in src/utils.ts, this helper attaches an `error`
28
+ * listener so that ENOENT (binary not on PATH) rejects the promise cleanly
29
+ * instead of crashing the process. That matters for adapters that probe for
30
+ * optional binaries (`copilot`, `gemini`, `codex`).
31
+ */
32
+ export declare function safeSpawn(cmd: string, args?: string[]): Promise<SpawnResult>;
33
+ //# sourceMappingURL=spawn-helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn-helper.d.ts","sourceRoot":"","sources":["../../src/agents/spawn-helper.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAQlE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CA6DhF"}
@@ -0,0 +1,98 @@
1
+ import { spawn } from "node:child_process";
2
+ /**
3
+ * Build a single cmd.exe-safe command line from (cmd, args). Follows the
4
+ * canonical `CommandLineToArgvW` escaping rules so the child process sees
5
+ * each arg verbatim:
6
+ * 1. Double any run of backslashes that directly precedes a `"` or the
7
+ * end of the string (so the closing `"` we add below isn't escaped).
8
+ * 2. Escape each embedded `"` as `\"`.
9
+ * 3. Wrap the whole arg in `"..."`.
10
+ * Naive `s.replace(/"/g, '\\"')` is wrong for args like `a\"b` or `a\\`
11
+ * because the preceding backslashes turn the intended escape into a real
12
+ * backslash + unescaped quote, breaking out of the quoted context.
13
+ *
14
+ * Chose this shape because Node 22+'s DEP0190 deprecates
15
+ * `spawn(cmd, args[], {shell:true})` (joining args without escaping), but
16
+ * `spawn(<single string>, [], {shell:true})` stays supported. Inside
17
+ * double quotes cmd.exe treats `&|<>^` literally, so no caret escaping is
18
+ * needed for our hardcoded args.
19
+ */
20
+ export function winCommandLine(cmd, args) {
21
+ const quote = (s) => {
22
+ const escaped = s.replace(/(\\*)("|$)/g, (_, slashes, end) => slashes + slashes + (end === '"' ? '\\"' : ""));
23
+ return `"${escaped}"`;
24
+ };
25
+ return [cmd, ...args].map(quote).join(" ");
26
+ }
27
+ /**
28
+ * Spawn a command and resolve with its stdout/stderr/exit code. Unlike the
29
+ * shared `spawnCommand` in src/utils.ts, this helper attaches an `error`
30
+ * listener so that ENOENT (binary not on PATH) rejects the promise cleanly
31
+ * instead of crashing the process. That matters for adapters that probe for
32
+ * optional binaries (`copilot`, `gemini`, `codex`).
33
+ */
34
+ export function safeSpawn(cmd, args = []) {
35
+ return new Promise((resolve, reject) => {
36
+ let stdout = "";
37
+ let stderr = "";
38
+ let settled = false;
39
+ const finish = (err, result) => {
40
+ if (settled)
41
+ return;
42
+ settled = true;
43
+ if (err)
44
+ reject(err);
45
+ else
46
+ resolve(result);
47
+ };
48
+ try {
49
+ // Close the child's stdin with `ignore` so any interactive prompt in
50
+ // the target tool (for example, `qwen extensions install` asking for
51
+ // consent despite `--consent`) fails fast with EOF instead of hanging
52
+ // indefinitely in CI / non-TTY contexts. We capture stdout/stderr via
53
+ // pipes so the caller can still surface output/errors.
54
+ //
55
+ // On Windows, npm installs globally-linked binaries as `.cmd` / `.ps1`
56
+ // shims (so `qwen` on disk is actually `qwen.cmd`). Without
57
+ // `shell: true`, Node's spawn resolves only exact filenames — it can't
58
+ // see `.cmd` extensions and ENOENTs even when the binary works fine
59
+ // from PowerShell / cmd. We therefore enable the shell on win32 so
60
+ // cmd.exe resolves PATHEXT extensions the same way the user's
61
+ // terminal does. Node 22+ deprecates `spawn(cmd, args, {shell: true})`
62
+ // (DEP0190) because args are joined without escaping, so on win32 we
63
+ // instead build a single pre-quoted command line and pass it as the
64
+ // `cmd` argument with `shell: true`. All callers pass hardcoded
65
+ // strings, so shell-injection isn't a risk.
66
+ const onWindows = process.platform === "win32";
67
+ const spawnCmd = onWindows ? winCommandLine(cmd, args) : cmd;
68
+ const spawnArgs = onWindows ? [] : args;
69
+ const child = spawn(spawnCmd, spawnArgs, {
70
+ env: process.env,
71
+ stdio: ["ignore", "pipe", "pipe"],
72
+ shell: onWindows,
73
+ });
74
+ child.on("error", (err) => finish(err));
75
+ child.stdout?.on("data", (chunk) => { stdout += chunk.toString(); });
76
+ child.stderr?.on("data", (chunk) => { stderr += chunk.toString(); });
77
+ child.on("close", (code, signal) => {
78
+ // `code === null` means the child was killed by a signal. Surface that
79
+ // as a non-zero exit code (and annotate stderr) so callers that only
80
+ // inspect exitCode still detect failure.
81
+ const exitCode = code !== null ? code : 1;
82
+ const signalNote = signal ? `\n[terminated by signal ${signal}]` : "";
83
+ finish(null, {
84
+ // Strip `\r?\n` so Windows CLIs that emit CRLF don't leave a
85
+ // stray `\r` in captured output (breaks log comparisons and
86
+ // produces mystery `^M` glyphs in terminal output).
87
+ stdout: stdout.replace(/\r?\n$/, ""),
88
+ stderr: (stderr + signalNote).replace(/\r?\n$/, ""),
89
+ exitCode,
90
+ });
91
+ });
92
+ }
93
+ catch (err) {
94
+ finish(err instanceof Error ? err : new Error(String(err)));
95
+ }
96
+ });
97
+ }
98
+ //# sourceMappingURL=spawn-helper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn-helper.js","sourceRoot":"","sources":["../../src/agents/spawn-helper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ export type Scope = "global" | "project";
2
+ export interface DetectionResult {
3
+ present: boolean;
4
+ onPath: boolean;
5
+ version?: string;
6
+ configPaths: {
7
+ global?: string;
8
+ project?: string;
9
+ };
10
+ notes?: string[];
11
+ }
12
+ export interface InstallState {
13
+ installed: boolean;
14
+ installedVersion?: string;
15
+ latestVersion?: string;
16
+ upToDate?: boolean;
17
+ }
18
+ export type LogLevel = "info" | "warn" | "error" | "debug";
19
+ export interface InstallOptions {
20
+ scope: Scope;
21
+ force: boolean;
22
+ dryRun: boolean;
23
+ logger: (message: string, level?: LogLevel) => void;
24
+ }
25
+ export type InstallAction = "installed" | "updated" | "already-up-to-date" | "forced" | "dry-run" | "fallback";
26
+ export interface InstallReport {
27
+ adapterId: string;
28
+ scope: Scope;
29
+ action: InstallAction;
30
+ installedVersion?: string;
31
+ notes?: string[];
32
+ }
33
+ export interface AgentAdapter {
34
+ id: string;
35
+ displayName: string;
36
+ detect(): Promise<DetectionResult>;
37
+ supportsScopes(): Scope[];
38
+ getInstallState(scope: Scope): Promise<InstallState>;
39
+ install(opts: InstallOptions): Promise<InstallReport>;
40
+ }
41
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEzC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,MAAM,aAAa,GACrB,WAAW,GACX,SAAS,GACT,oBAAoB,GACpB,QAAQ,GACR,SAAS,GACT,UAAU,CAAC;AAEf,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACnC,cAAc,IAAI,KAAK,EAAE,CAAC;IAC1B,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACrD,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACvD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":""}
package/dist/cli.js CHANGED
@@ -1,22 +1,53 @@
1
1
  import { runTests } from "./core/index.js";
2
- import { setArgs, setConfig, outputResults, setMeta, getVersionData, log, getResolvedTestsFromEnv, reportResults, } from "./utils.js";
3
- import { argv } from "node:process";
2
+ import { buildYargs, setConfig, outputResults, setMeta, getVersionData, log, getResolvedTestsFromEnv, reportResults, } from "./utils.js";
3
+ import { installAgentsCommand } from "./agents/command.js";
4
+ import { argv as processArgv } from "node:process";
4
5
  import path from "node:path";
5
6
  import fs from "node:fs";
6
7
  // Run
7
8
  setMeta();
8
- main(argv);
9
- // Run
9
+ main(processArgv).catch((err) => {
10
+ // yargs' .fail handler prints usage + message; this catches anything that
11
+ // escapes (including our rethrown errors) so the process exits non-zero.
12
+ console.error(err?.message || err);
13
+ process.exit(1);
14
+ });
10
15
  async function main(argv) {
11
- // Find index of `doc-detective` or `run` in argv
12
- const index = argv.findIndex((arg) => arg.endsWith("doc-detective") || arg.endsWith("index.js"));
13
- // Set args
14
- const args = setArgs(argv);
16
+ await buildYargs(argv)
17
+ .command({
18
+ command: "$0",
19
+ describe: "Run Doc Detective tests (default).",
20
+ handler: runTestsHandler,
21
+ })
22
+ .command({
23
+ // Preserved for back-compat with the `runTests` npm script and any
24
+ // existing CI invocations. Runs the same handler as the default.
25
+ command: "runTests",
26
+ describe: false,
27
+ handler: runTestsHandler,
28
+ })
29
+ .command(installAgentsCommand)
30
+ .strict()
31
+ .demandCommand(0)
32
+ // Suppress yargs' default help-dump on failure; surface the concrete error
33
+ // message instead. The top-level .catch() prints it and sets exit code.
34
+ .fail((msg, err) => {
35
+ if (err)
36
+ throw err;
37
+ throw new Error(msg);
38
+ })
39
+ .parseAsync();
40
+ }
41
+ // Legacy "run tests" flow — unchanged behavior when no subcommand is given.
42
+ async function runTestsHandler(args) {
15
43
  // Get .doc-detective JSON or YAML config, if it exists, preferring a config arg if provided
16
44
  const configPathJSON = path.resolve(process.cwd(), ".doc-detective.json");
17
45
  const configPathYAML = path.resolve(process.cwd(), ".doc-detective.yaml");
18
46
  const configPathYML = path.resolve(process.cwd(), ".doc-detective.yml");
19
- const configPath = fs.existsSync(args.config)
47
+ // Guard `args.config` so the handler falls back to the defaults cleanly
48
+ // when --config is omitted (args.config is undefined in that case).
49
+ const hasExplicitConfig = typeof args.config === "string" && args.config.length > 0 && fs.existsSync(args.config);
50
+ const configPath = hasExplicitConfig
20
51
  ? args.config
21
52
  : fs.existsSync(configPathJSON)
22
53
  ? configPathJSON
@@ -42,7 +73,8 @@ async function main(argv) {
42
73
  await reportResults({ apiConfig, results });
43
74
  }
44
75
  else {
45
- // Output results
76
+ // Output results — config.reporters (populated from args.reporters by
77
+ // setConfig) is the source of truth for which reporters run.
46
78
  await outputResults(config, output, results, { command: "runTests" });
47
79
  }
48
80
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EACL,UAAU,EACV,SAAS,EACT,aAAa,EACb,OAAO,EACP,cAAc,EACd,GAAG,EACH,uBAAuB,EACvB,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM;AACN,OAAO,EAAE,CAAC;AACV,IAAI,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAC9B,0EAA0E;IAC1E,yEAAyE;IACzE,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,IAAI,GAAG,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI,CAAC,IAAc;IAChC,MAAM,UAAU,CAAC,IAAI,CAAC;SACnB,OAAO,CAAC;QACP,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,oCAAoC;QAC9C,OAAO,EAAE,eAAe;KACzB,CAAC;SACD,OAAO,CAAC;QACP,mEAAmE;QACnE,iEAAiE;QACjE,OAAO,EAAE,UAAU;QACnB,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,eAAe;KACzB,CAAC;SACD,OAAO,CAAC,oBAAoB,CAAC;SAC7B,MAAM,EAAE;SACR,aAAa,CAAC,CAAC,CAAC;QACjB,2EAA2E;QAC3E,wEAAwE;SACvE,IAAI,CAAC,CAAC,GAAW,EAAE,GAAsB,EAAE,EAAE;QAC5C,IAAI,GAAG;YAAE,MAAM,GAAG,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC,CAAC;SACD,UAAU,EAAE,CAAC;AAClB,CAAC;AAED,4EAA4E;AAC5E,KAAK,UAAU,eAAe,CAAC,IAAS;IACtC,4FAA4F;IAC5F,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,qBAAqB,CAAC,CAAC;IAC1E,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,qBAAqB,CAAC,CAAC;IAC1E,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,oBAAoB,CAAC,CAAC;IACxE,wEAAwE;IACxE,oEAAoE;IACpE,MAAM,iBAAiB,GACrB,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1F,MAAM,UAAU,GAAG,iBAAiB;QAClC,CAAC,CAAC,IAAI,CAAC,MAAM;QACb,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC;YAC/B,CAAC,CAAC,cAAc;YAChB,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC;gBAC/B,CAAC,CAAC,cAAc;gBAChB,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC;oBAC9B,CAAC,CAAC,aAAa;oBACf,CAAC,CAAC,IAAI,CAAC;IAET,aAAa;IACb,MAAM,MAAM,GAAQ,MAAM,SAAS,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAE5E,GAAG,CACD,sBAAsB,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EACjE,OAAO,EACP,MAAM,CACP,CAAC;IACF,GAAG,CAAC,gBAAgB,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAExE,mDAAmD;IACnD,MAAM,GAAG,GAAG,MAAM,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,aAAa,GAAG,GAAG,EAAE,aAAa,IAAI,IAAI,CAAC;IACjD,MAAM,SAAS,GAAG,GAAG,EAAE,SAAS,IAAI,IAAI,CAAC;IAEzC,YAAY;IACZ,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7B,MAAM,OAAO,GAAG,aAAa;QAC3B,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,EAAE,aAAa,EAAE,CAAC;QAC3C,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE3B,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,aAAa,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9C,CAAC;SAAM,CAAC;QACN,sEAAsE;QACtE,6DAA6D;QAC7D,MAAM,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;IACxE,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"detectTests.d.ts","sourceRoot":"","sources":["../../../src/common/src/detectTests.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAA+C,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAoCvF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAe3F;AAED,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAOlF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,EAAE;IAAE,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAoEnH;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,iBAAiB,EAAE,EAAE;IAAE,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAkD5G;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,oBAAoB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAClD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CA2DrC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAAC,EACjC,MAAW,EACX,OAAO,EACP,QAAa,EACb,QAAQ,GACT,EAAE;IACD,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAqU1B;AAsBD;;GAEG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAqBhF"}
1
+ {"version":3,"file":"detectTests.d.ts","sourceRoot":"","sources":["../../../src/common/src/detectTests.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAA+C,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAoCvF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAe3F;AAED,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAOlF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,EAAE;IAAE,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAoEnH;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,iBAAiB,EAAE,EAAE;IAAE,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAgE5G;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,oBAAoB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAClD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CA2DrC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAAC,EACjC,MAAW,EACX,OAAO,EACP,QAAa,EACb,QAAQ,GACT,EAAE;IACD,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAmU1B;AAkDD;;GAEG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAqBhF"}
@@ -183,6 +183,19 @@ export function parseXmlAttributes({ stringifiedObject }) {
183
183
  */
184
184
  export function parseObject({ stringifiedObject }) {
185
185
  if (typeof stringifiedObject === "string") {
186
+ // Decode XML/HTML entities (e.g., &#34; → " and &#39; → ') that may be
187
+ // introduced by DITA OT or other XML processors when normalizing attribute
188
+ // values. &amp; must be decoded last so author-escaped sequences like
189
+ // "&amp;lt;" (meant to render as the literal text "&lt;") aren't double-decoded
190
+ // into "<".
191
+ if (/&(?:#\d+|#x[0-9a-fA-F]+|amp|lt|gt|quot|apos);/.test(stringifiedObject)) {
192
+ stringifiedObject = stringifiedObject
193
+ .replace(/&#39;|&apos;/g, "'")
194
+ .replace(/&#34;|&quot;/g, '"')
195
+ .replace(/&lt;/g, "<")
196
+ .replace(/&gt;/g, ">")
197
+ .replace(/&amp;/g, "&");
198
+ }
186
199
  // First, try to parse as XML attributes
187
200
  const xmlAttrs = parseXmlAttributes({ stringifiedObject });
188
201
  if (xmlAttrs !== null) {
@@ -488,18 +501,7 @@ export async function parseContent({ config = {}, content, filePath = "", fileTy
488
501
  if (step.screenshot && config._herettoPathMapping) {
489
502
  const herettoIntegration = findHerettoIntegration(config, filePath);
490
503
  if (herettoIntegration) {
491
- if (typeof step.screenshot === "string") {
492
- step.screenshot = { path: step.screenshot };
493
- }
494
- else if (typeof step.screenshot === "boolean") {
495
- step.screenshot = {};
496
- }
497
- step.screenshot.sourceIntegration = {
498
- type: "heretto",
499
- integrationName: herettoIntegration,
500
- filePath: step.screenshot.path || "",
501
- contentPath: filePath,
502
- };
504
+ attachHerettoScreenshotSourceIntegration(step, herettoIntegration, filePath);
503
505
  }
504
506
  }
505
507
  }
@@ -576,6 +578,13 @@ export async function parseContent({ config = {}, content, filePath = "", fileTy
576
578
  endIndex: statement._endIndex,
577
579
  };
578
580
  }
581
+ // Attach sourceIntegration for Heretto on screenshot steps
582
+ if (step.screenshot && config._herettoPathMapping) {
583
+ const herettoIntegration = findHerettoIntegration(config, filePath);
584
+ if (herettoIntegration) {
585
+ attachHerettoScreenshotSourceIntegration(step, herettoIntegration, filePath);
586
+ }
587
+ }
579
588
  const validation = validate({
580
589
  schemaKey: "step_v3",
581
590
  object: step,
@@ -618,6 +627,28 @@ export async function parseContent({ config = {}, content, filePath = "", fileTy
618
627
  });
619
628
  return validatedTests;
620
629
  }
630
+ /**
631
+ * Attaches a Heretto sourceIntegration descriptor to a screenshot step in place.
632
+ * Normalizes non-object screenshot values (string paths, booleans, other primitives)
633
+ * to an object shape before assigning sourceIntegration so downstream validation
634
+ * has a predictable structure to reject or accept.
635
+ */
636
+ function attachHerettoScreenshotSourceIntegration(step, integrationName, contentPath) {
637
+ if (typeof step.screenshot === "string") {
638
+ step.screenshot = { path: step.screenshot };
639
+ }
640
+ else if (step.screenshot === null ||
641
+ typeof step.screenshot !== "object" ||
642
+ Array.isArray(step.screenshot)) {
643
+ step.screenshot = {};
644
+ }
645
+ step.screenshot.sourceIntegration = {
646
+ type: "heretto",
647
+ integrationName,
648
+ filePath: step.screenshot.path || "",
649
+ contentPath,
650
+ };
651
+ }
621
652
  /**
622
653
  * Helper function to find which Heretto integration a file belongs to.
623
654
  */