@wix/pathgrade 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/bin/pathgrade.js +2 -0
  2. package/dist/affected/anchor.d.ts +18 -0
  3. package/dist/affected/anchor.js +42 -0
  4. package/dist/affected/config.d.ts +25 -0
  5. package/dist/affected/config.js +98 -0
  6. package/dist/affected/format.d.ts +45 -0
  7. package/dist/affected/format.js +87 -0
  8. package/dist/affected/git.d.ts +33 -0
  9. package/dist/affected/git.js +68 -0
  10. package/dist/affected/glob.d.ts +17 -0
  11. package/dist/affected/glob.js +25 -0
  12. package/dist/affected/meta.d.ts +26 -0
  13. package/dist/affected/meta.js +158 -0
  14. package/dist/affected/select.d.ts +30 -0
  15. package/dist/affected/select.js +101 -0
  16. package/dist/affected/sidecar.d.ts +32 -0
  17. package/dist/affected/sidecar.js +86 -0
  18. package/dist/affected/types.d.ts +43 -0
  19. package/dist/affected/types.js +7 -0
  20. package/dist/agents/claude/ask-user-answer-store.d.ts +28 -0
  21. package/dist/agents/claude/ask-user-answer-store.js +29 -0
  22. package/dist/agents/claude/ask-user-bridge.d.ts +56 -0
  23. package/dist/agents/claude/ask-user-bridge.js +162 -0
  24. package/dist/agents/claude/sdk-message-projector.d.ts +43 -0
  25. package/dist/agents/claude/sdk-message-projector.js +208 -0
  26. package/dist/agents/claude/sdk-options.d.ts +52 -0
  27. package/dist/agents/claude/sdk-options.js +79 -0
  28. package/dist/agents/claude.d.ts +56 -0
  29. package/dist/agents/claude.js +143 -0
  30. package/dist/agents/codex-app-server/agent.d.ts +35 -0
  31. package/dist/agents/codex-app-server/agent.js +388 -0
  32. package/dist/agents/codex-app-server/fixtures/app-server-client.d.ts +19 -0
  33. package/dist/agents/codex-app-server/fixtures/app-server-client.js +104 -0
  34. package/dist/agents/codex-app-server/fixtures/run-gate.d.ts +25 -0
  35. package/dist/agents/codex-app-server/fixtures/run-gate.js +48 -0
  36. package/dist/agents/codex-app-server/protocol/ClientRequest.d.ts +27 -0
  37. package/dist/agents/codex-app-server/protocol/ClientRequest.js +7 -0
  38. package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.d.ts +9 -0
  39. package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.js +4 -0
  40. package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.d.ts +6 -0
  41. package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.js +7 -0
  42. package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.d.ts +23 -0
  43. package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.js +8 -0
  44. package/dist/agents/codex-app-server/protocol/Op.d.ts +11 -0
  45. package/dist/agents/codex-app-server/protocol/Op.js +5 -0
  46. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.d.ts +10 -0
  47. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.js +6 -0
  48. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.d.ts +15 -0
  49. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.js +4 -0
  50. package/dist/agents/codex-app-server/protocol/SandboxMode.d.ts +1 -0
  51. package/dist/agents/codex-app-server/protocol/SandboxMode.js +4 -0
  52. package/dist/agents/codex-app-server/protocol/ServerRequest.d.ts +42 -0
  53. package/dist/agents/codex-app-server/protocol/ServerRequest.js +7 -0
  54. package/dist/agents/codex-app-server/protocol/ThreadStartParams.d.ts +45 -0
  55. package/dist/agents/codex-app-server/protocol/ThreadStartParams.js +10 -0
  56. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.d.ts +6 -0
  57. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.js +4 -0
  58. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.d.ts +7 -0
  59. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.js +4 -0
  60. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.d.ts +10 -0
  61. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.js +4 -0
  62. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.d.ts +12 -0
  63. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.js +4 -0
  64. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.d.ts +9 -0
  65. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.js +4 -0
  66. package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.d.ts +5 -0
  67. package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.js +4 -0
  68. package/dist/agents/codex-app-server/protocol/index.d.ts +16 -0
  69. package/dist/agents/codex-app-server/protocol/index.js +11 -0
  70. package/dist/agents/codex-app-server/transport.d.ts +84 -0
  71. package/dist/agents/codex-app-server/transport.js +246 -0
  72. package/dist/agents/codex-app-server/wire-translators.d.ts +19 -0
  73. package/dist/agents/codex-app-server/wire-translators.js +52 -0
  74. package/dist/agents/codex.d.ts +7 -0
  75. package/dist/agents/codex.js +164 -0
  76. package/dist/agents/cursor.d.ts +39 -0
  77. package/dist/agents/cursor.js +233 -0
  78. package/dist/agents/registry.d.ts +17 -0
  79. package/dist/agents/registry.js +27 -0
  80. package/dist/agents/transcript-agent.d.ts +14 -0
  81. package/dist/agents/transcript-agent.js +59 -0
  82. package/dist/analytics/engine.d.ts +18 -0
  83. package/dist/analytics/engine.js +66 -0
  84. package/dist/commands/affected.d.ts +31 -0
  85. package/dist/commands/affected.js +150 -0
  86. package/dist/commands/analyze.d.ts +21 -0
  87. package/dist/commands/analyze.js +167 -0
  88. package/dist/commands/init.d.ts +3 -0
  89. package/dist/commands/init.js +242 -0
  90. package/dist/commands/preview-reactions.d.ts +10 -0
  91. package/dist/commands/preview-reactions.js +88 -0
  92. package/dist/commands/preview.d.ts +5 -0
  93. package/dist/commands/preview.js +17 -0
  94. package/dist/commands/report.d.ts +39 -0
  95. package/dist/commands/report.js +108 -0
  96. package/dist/commands/run-args.d.ts +22 -0
  97. package/dist/commands/run-args.js +69 -0
  98. package/dist/commands/run-changed.d.ts +26 -0
  99. package/dist/commands/run-changed.js +138 -0
  100. package/dist/commands/validate.d.ts +13 -0
  101. package/dist/commands/validate.js +233 -0
  102. package/dist/core/mcp-mock.d.ts +3 -0
  103. package/dist/core/mcp-mock.js +23 -0
  104. package/dist/core/mcp-mock.types.d.ts +15 -0
  105. package/dist/core/mcp-mock.types.js +1 -0
  106. package/dist/core/skills.d.ts +15 -0
  107. package/dist/core/skills.js +76 -0
  108. package/dist/evals/discovery.d.ts +7 -0
  109. package/dist/evals/discovery.js +69 -0
  110. package/dist/mcp-mock-server.d.ts +1 -0
  111. package/dist/mcp-mock-server.js +116 -0
  112. package/dist/pathgrade.d.ts +10 -0
  113. package/dist/pathgrade.js +243 -0
  114. package/dist/plugin/index.d.ts +9 -0
  115. package/dist/plugin/index.js +81 -0
  116. package/dist/plugin/lifecycle.d.ts +45 -0
  117. package/dist/plugin/lifecycle.js +146 -0
  118. package/dist/plugin/reporter.d.ts +36 -0
  119. package/dist/plugin/reporter.js +275 -0
  120. package/dist/plugin/setup.d.ts +1 -0
  121. package/dist/plugin/setup.js +3 -0
  122. package/dist/providers/copy-filter.d.ts +18 -0
  123. package/dist/providers/copy-filter.js +66 -0
  124. package/dist/providers/credentials.d.ts +36 -0
  125. package/dist/providers/credentials.js +202 -0
  126. package/dist/providers/mcp-config.d.ts +34 -0
  127. package/dist/providers/mcp-config.js +55 -0
  128. package/dist/providers/sandbox-exec.d.ts +8 -0
  129. package/dist/providers/sandbox-exec.js +82 -0
  130. package/dist/providers/sandbox.d.ts +22 -0
  131. package/dist/providers/sandbox.js +158 -0
  132. package/dist/providers/sandboxed-claude-spawn.d.ts +24 -0
  133. package/dist/providers/sandboxed-claude-spawn.js +57 -0
  134. package/dist/providers/workspace.d.ts +15 -0
  135. package/dist/providers/workspace.js +73 -0
  136. package/dist/reporters/browser.d.ts +2 -0
  137. package/dist/reporters/browser.js +85 -0
  138. package/dist/reporters/cli.d.ts +2 -0
  139. package/dist/reporters/cli.js +131 -0
  140. package/dist/reporters/diagnostics.d.ts +46 -0
  141. package/dist/reporters/diagnostics.js +147 -0
  142. package/dist/reporters/github-comment.d.ts +67 -0
  143. package/dist/reporters/github-comment.js +252 -0
  144. package/dist/reporters/loader.d.ts +8 -0
  145. package/dist/reporters/loader.js +36 -0
  146. package/dist/reporters/results-path.d.ts +2 -0
  147. package/dist/reporters/results-path.js +10 -0
  148. package/dist/reporters/verbose-emitter.d.ts +69 -0
  149. package/dist/reporters/verbose-emitter.js +103 -0
  150. package/dist/sdk/agent-crash.d.ts +19 -0
  151. package/dist/sdk/agent-crash.js +20 -0
  152. package/dist/sdk/agent-resolution.d.ts +12 -0
  153. package/dist/sdk/agent-resolution.js +21 -0
  154. package/dist/sdk/agent-result-log.d.ts +19 -0
  155. package/dist/sdk/agent-result-log.js +45 -0
  156. package/dist/sdk/agent.d.ts +4 -0
  157. package/dist/sdk/agent.js +354 -0
  158. package/dist/sdk/ask-bus/bus.d.ts +21 -0
  159. package/dist/sdk/ask-bus/bus.js +129 -0
  160. package/dist/sdk/ask-bus/handler.d.ts +31 -0
  161. package/dist/sdk/ask-bus/handler.js +244 -0
  162. package/dist/sdk/ask-bus/parsers.d.ts +9 -0
  163. package/dist/sdk/ask-bus/parsers.js +30 -0
  164. package/dist/sdk/ask-bus/projection.d.ts +26 -0
  165. package/dist/sdk/ask-bus/projection.js +45 -0
  166. package/dist/sdk/ask-bus/types.d.ts +69 -0
  167. package/dist/sdk/ask-bus/types.js +1 -0
  168. package/dist/sdk/chat.d.ts +29 -0
  169. package/dist/sdk/chat.js +93 -0
  170. package/dist/sdk/conversation-window.d.ts +14 -0
  171. package/dist/sdk/conversation-window.js +38 -0
  172. package/dist/sdk/converse.d.ts +42 -0
  173. package/dist/sdk/converse.js +329 -0
  174. package/dist/sdk/eval-runtime.d.ts +10 -0
  175. package/dist/sdk/eval-runtime.js +17 -0
  176. package/dist/sdk/evaluate.d.ts +13 -0
  177. package/dist/sdk/evaluate.js +325 -0
  178. package/dist/sdk/index.d.ts +31 -0
  179. package/dist/sdk/index.js +20 -0
  180. package/dist/sdk/judge-pipeline.d.ts +7 -0
  181. package/dist/sdk/judge-pipeline.js +178 -0
  182. package/dist/sdk/judge-prompt-builder.d.ts +7 -0
  183. package/dist/sdk/judge-prompt-builder.js +76 -0
  184. package/dist/sdk/judge-tool-runner.d.ts +19 -0
  185. package/dist/sdk/judge-tool-runner.js +51 -0
  186. package/dist/sdk/judge-tool-session.d.ts +47 -0
  187. package/dist/sdk/judge-tool-session.js +213 -0
  188. package/dist/sdk/judge-tools.d.ts +29 -0
  189. package/dist/sdk/judge-tools.js +283 -0
  190. package/dist/sdk/managed-session.d.ts +50 -0
  191. package/dist/sdk/managed-session.js +110 -0
  192. package/dist/sdk/persona.d.ts +5 -0
  193. package/dist/sdk/persona.js +33 -0
  194. package/dist/sdk/reaction-loader.d.ts +4 -0
  195. package/dist/sdk/reaction-loader.js +119 -0
  196. package/dist/sdk/reaction-preview.d.ts +6 -0
  197. package/dist/sdk/reaction-preview.js +94 -0
  198. package/dist/sdk/run-scorer.d.ts +10 -0
  199. package/dist/sdk/run-scorer.js +124 -0
  200. package/dist/sdk/runtime-policy.d.ts +21 -0
  201. package/dist/sdk/runtime-policy.js +89 -0
  202. package/dist/sdk/scorer-utils.d.ts +6 -0
  203. package/dist/sdk/scorer-utils.js +39 -0
  204. package/dist/sdk/scorers.d.ts +64 -0
  205. package/dist/sdk/scorers.js +80 -0
  206. package/dist/sdk/snapshots.d.ts +41 -0
  207. package/dist/sdk/snapshots.js +148 -0
  208. package/dist/sdk/types.d.ts +416 -0
  209. package/dist/sdk/types.js +17 -0
  210. package/dist/sdk/visible-turn.d.ts +4 -0
  211. package/dist/sdk/visible-turn.js +23 -0
  212. package/dist/tool-events.d.ts +45 -0
  213. package/dist/tool-events.js +167 -0
  214. package/dist/types.d.ts +372 -0
  215. package/dist/types.js +37 -0
  216. package/dist/utils/cli.d.ts +55 -0
  217. package/dist/utils/cli.js +131 -0
  218. package/dist/utils/env.d.ts +8 -0
  219. package/dist/utils/env.js +26 -0
  220. package/dist/utils/llm-mocks.d.ts +43 -0
  221. package/dist/utils/llm-mocks.js +108 -0
  222. package/dist/utils/llm-providers/anthropic.d.ts +2 -0
  223. package/dist/utils/llm-providers/anthropic.js +132 -0
  224. package/dist/utils/llm-providers/cli.d.ts +20 -0
  225. package/dist/utils/llm-providers/cli.js +162 -0
  226. package/dist/utils/llm-providers/openai.d.ts +2 -0
  227. package/dist/utils/llm-providers/openai.js +56 -0
  228. package/dist/utils/llm-types.d.ts +107 -0
  229. package/dist/utils/llm-types.js +1 -0
  230. package/dist/utils/llm.d.ts +36 -0
  231. package/dist/utils/llm.js +209 -0
  232. package/dist/utils/shutdown.d.ts +15 -0
  233. package/dist/utils/shutdown.js +41 -0
  234. package/dist/utils/timeout.d.ts +8 -0
  235. package/dist/utils/timeout.js +32 -0
  236. package/dist/viewer.html +1178 -0
  237. package/package.json +88 -0
  238. package/templates/eval.ts.template +28 -0
@@ -0,0 +1,88 @@
1
+ import * as path from 'path';
2
+ import { loadReactionSnapshotMessages, loadReactionsFromFile } from '../sdk/reaction-loader.js';
3
+ import { previewReactions } from '../sdk/reaction-preview.js';
4
+ function readFlag(args, flag) {
5
+ const exactIndex = args.findIndex((arg) => arg === flag);
6
+ if (exactIndex >= 0) {
7
+ return args[exactIndex + 1];
8
+ }
9
+ const inline = args.find((arg) => arg.startsWith(`${flag}=`));
10
+ return inline ? inline.slice(flag.length + 1) : undefined;
11
+ }
12
+ function truncate(value, limit) {
13
+ if (value.length <= limit)
14
+ return value;
15
+ return `${value.slice(0, Math.max(0, limit - 3))}...`;
16
+ }
17
+ function getStatus(result) {
18
+ if (result.status)
19
+ return result.status;
20
+ if (result.fired)
21
+ return 'fired';
22
+ if (result.kind === 'text' && result.unlessMatched)
23
+ return 'vetoed';
24
+ return 'no-match';
25
+ }
26
+ function formatCli(result) {
27
+ if (result.turns.length === 0) {
28
+ return 'No agent turns found in snapshot.\n';
29
+ }
30
+ const lines = [];
31
+ for (const turn of result.turns) {
32
+ lines.push(`Turn ${turn.turn}: ${truncate(turn.agentMessage, 120)}`);
33
+ if (turn.reactions.length === 0) {
34
+ lines.push(' (no reactions)');
35
+ continue;
36
+ }
37
+ for (const reaction of turn.reactions) {
38
+ const reply = reaction.kind === 'text' ? reaction.reply : undefined;
39
+ const replySuffix = reply ? ` -> ${truncate(reply, 80)}` : '';
40
+ lines.push(` [${reaction.reactionIndex}] ${getStatus(reaction)}${replySuffix}`);
41
+ }
42
+ }
43
+ return `${lines.join('\n')}\n`;
44
+ }
45
+ function parseArgs(args) {
46
+ const snapshot = readFlag(args, '--snapshot');
47
+ if (!snapshot) {
48
+ throw new Error('Missing required --snapshot <path> flag for preview-reactions.');
49
+ }
50
+ const reactions = readFlag(args, '--reactions');
51
+ if (!reactions) {
52
+ throw new Error('Missing required --reactions <path> flag for preview-reactions.');
53
+ }
54
+ const format = (readFlag(args, '--format') ?? 'cli');
55
+ if (format !== 'cli' && format !== 'json') {
56
+ throw new Error('Invalid --format value. Expected "cli" or "json".');
57
+ }
58
+ return { snapshot, reactions, format };
59
+ }
60
+ async function buildPreview(opts) {
61
+ const cwd = opts.cwd ?? process.cwd();
62
+ const snapshotPath = path.resolve(cwd, opts.snapshot);
63
+ const reactionsPath = path.resolve(cwd, opts.reactions);
64
+ const messages = await loadReactionSnapshotMessages(snapshotPath);
65
+ const reactions = await loadReactionsFromFile(reactionsPath);
66
+ return previewReactions(messages, reactions);
67
+ }
68
+ export async function runPreviewReactions(input) {
69
+ const opts = Array.isArray(input) ? parseArgs(input) : input;
70
+ const result = await buildPreview(opts);
71
+ if (Array.isArray(input)) {
72
+ if ((opts.format ?? 'cli') === 'json') {
73
+ console.log(JSON.stringify(result, null, 2));
74
+ }
75
+ else {
76
+ console.log(formatCli(result).trimEnd());
77
+ }
78
+ return 0;
79
+ }
80
+ const write = opts.write ?? ((chunk) => process.stdout.write(chunk));
81
+ if ((opts.format ?? 'cli') === 'json') {
82
+ write(`${JSON.stringify(result, null, 2)}\n`);
83
+ }
84
+ else {
85
+ write(formatCli(result));
86
+ }
87
+ return 0;
88
+ }
@@ -0,0 +1,5 @@
1
+ export interface PreviewOptions {
2
+ last?: number;
3
+ filter?: string;
4
+ }
5
+ export declare function runPreview(dir: string, mode: 'cli' | 'browser', outputDir?: string, opts?: PreviewOptions): Promise<void>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `pathgrade preview` command.
3
+ *
4
+ * Opens the CLI or browser results viewer.
5
+ */
6
+ import { runCliPreview } from '../reporters/cli.js';
7
+ import { runBrowserPreview } from '../reporters/browser.js';
8
+ import { getResultsDir } from '../reporters/results-path.js';
9
+ export async function runPreview(dir, mode, outputDir, opts) {
10
+ const resultsDir = getResultsDir(dir, outputDir);
11
+ if (mode === 'browser') {
12
+ await runBrowserPreview(resultsDir);
13
+ }
14
+ else {
15
+ await runCliPreview(resultsDir, opts);
16
+ }
17
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * `pathgrade report` command.
3
+ *
4
+ * Reads the consolidated `.pathgrade/results.json` and formats a markdown
5
+ * report via `src/reporters/github-comment.ts`. Two observable behaviors:
6
+ *
7
+ * - Posting path: in GitHub Actions (`GITHUB_ACTIONS === 'true'`) with a
8
+ * resolvable PR context and a `GITHUB_TOKEN`, posts/updates a PR
9
+ * comment and prints only the pass rate to stdout.
10
+ * - Local/fallback path: prints the markdown, a blank line, then the
11
+ * pass rate. Used for local runs, `--no-comment`, or any CI run
12
+ * missing token/PR context.
13
+ *
14
+ * The final line of stdout is always the pass rate as a plain number so
15
+ * downstream workflow steps can capture it with `$(...)` or `>> $GITHUB_OUTPUT`.
16
+ *
17
+ * Display-only: the command never sets a failing exit code. Threshold
18
+ * enforcement stays in `PathgradeReporter` via `ci.threshold`.
19
+ */
20
+ export interface ReportOptions {
21
+ /** Path to the consolidated results JSON, resolved against `cwd`. Defaults to `.pathgrade/results.json`. */
22
+ resultsPath?: string;
23
+ /** Skip PR comment posting; print markdown to stdout instead. */
24
+ noComment?: boolean;
25
+ /** Override the comment-id used in the dedup marker. See `resolveCommentId`. */
26
+ commentId?: string;
27
+ }
28
+ /**
29
+ * Resolve the comment-id used for the dedup marker.
30
+ *
31
+ * Precedence: explicit `commentId` → `$GITHUB_WORKFLOW:$GITHUB_JOB` →
32
+ * `$GITHUB_WORKFLOW` → hard-coded literal `"default"`.
33
+ */
34
+ export declare function resolveCommentId(explicit?: string): string;
35
+ /**
36
+ * Entry point for `pathgrade report`. Never throws and never sets a
37
+ * failing exit code — even on malformed inputs or network errors.
38
+ */
39
+ export declare function runReport(cwd: string, opts?: ReportOptions): Promise<void>;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * `pathgrade report` command.
3
+ *
4
+ * Reads the consolidated `.pathgrade/results.json` and formats a markdown
5
+ * report via `src/reporters/github-comment.ts`. Two observable behaviors:
6
+ *
7
+ * - Posting path: in GitHub Actions (`GITHUB_ACTIONS === 'true'`) with a
8
+ * resolvable PR context and a `GITHUB_TOKEN`, posts/updates a PR
9
+ * comment and prints only the pass rate to stdout.
10
+ * - Local/fallback path: prints the markdown, a blank line, then the
11
+ * pass rate. Used for local runs, `--no-comment`, or any CI run
12
+ * missing token/PR context.
13
+ *
14
+ * The final line of stdout is always the pass rate as a plain number so
15
+ * downstream workflow steps can capture it with `$(...)` or `>> $GITHUB_OUTPUT`.
16
+ *
17
+ * Display-only: the command never sets a failing exit code. Threshold
18
+ * enforcement stays in `PathgradeReporter` via `ci.threshold`.
19
+ */
20
+ import * as path from 'path';
21
+ import fs from 'fs-extra';
22
+ import { formatReportMarkdown, MISSING_RESULTS_BODY, postOrUpdateComment, resolvePrContext, } from '../reporters/github-comment.js';
23
+ const DEFAULT_RESULTS_PATH = path.join('.pathgrade', 'results.json');
24
+ /**
25
+ * Resolve the comment-id used for the dedup marker.
26
+ *
27
+ * Precedence: explicit `commentId` → `$GITHUB_WORKFLOW:$GITHUB_JOB` →
28
+ * `$GITHUB_WORKFLOW` → hard-coded literal `"default"`.
29
+ */
30
+ export function resolveCommentId(explicit) {
31
+ if (explicit && explicit.length > 0)
32
+ return explicit;
33
+ const workflow = process.env.GITHUB_WORKFLOW;
34
+ const job = process.env.GITHUB_JOB;
35
+ if (workflow && job)
36
+ return `${workflow}:${job}`;
37
+ if (workflow)
38
+ return workflow;
39
+ return 'default';
40
+ }
41
+ function isPathgradeReport(value) {
42
+ if (!value || typeof value !== 'object')
43
+ return false;
44
+ const v = value;
45
+ return (v.version === 1 &&
46
+ typeof v.overall_pass_rate === 'number' &&
47
+ (v.status === 'pass' || v.status === 'fail') &&
48
+ Array.isArray(v.groups));
49
+ }
50
+ async function loadReport(resolvedPath) {
51
+ if (!(await fs.pathExists(resolvedPath))) {
52
+ throw new Error(`results file not found at ${resolvedPath}`);
53
+ }
54
+ const raw = await fs.readJSON(resolvedPath);
55
+ if (!isPathgradeReport(raw)) {
56
+ throw new Error(`results file at ${resolvedPath} is not a valid pathgrade report`);
57
+ }
58
+ return raw;
59
+ }
60
+ function printMarkdownAndPassRate(markdown, passRate) {
61
+ // Strip trailing newlines so we control spacing precisely:
62
+ // <markdown>\n\n<pass rate>\n
63
+ console.log(markdown.replace(/\n+$/, ''));
64
+ console.log('');
65
+ console.log(String(passRate));
66
+ }
67
+ /**
68
+ * Entry point for `pathgrade report`. Never throws and never sets a
69
+ * failing exit code — even on malformed inputs or network errors.
70
+ */
71
+ export async function runReport(cwd, opts = {}) {
72
+ const resolvedPath = path.resolve(cwd, opts.resultsPath ?? DEFAULT_RESULTS_PATH);
73
+ const commentId = resolveCommentId(opts.commentId);
74
+ const inCI = process.env.GITHUB_ACTIONS === 'true';
75
+ const noComment = opts.noComment ?? false;
76
+ // Branch point: when true, post the comment and print only the pass rate.
77
+ const shouldPost = inCI && !noComment;
78
+ const prContext = shouldPost ? resolvePrContext(process.env) : null;
79
+ let report = null;
80
+ let loadError = null;
81
+ try {
82
+ report = await loadReport(resolvedPath);
83
+ }
84
+ catch (err) {
85
+ loadError = err instanceof Error ? err.message : String(err);
86
+ }
87
+ if (loadError) {
88
+ console.error(`pathgrade report: ${loadError}`);
89
+ if (prContext) {
90
+ await postOrUpdateComment(prContext, {
91
+ commentId,
92
+ body: MISSING_RESULTS_BODY,
93
+ });
94
+ }
95
+ // Always emit a pass rate line so downstream capture doesn't explode.
96
+ console.log('0');
97
+ return;
98
+ }
99
+ const markdown = formatReportMarkdown(report, { commentId });
100
+ if (prContext) {
101
+ await postOrUpdateComment(prContext, { commentId, body: markdown });
102
+ // Only the pass rate goes to stdout — the markdown lives on the PR.
103
+ console.log(String(report.overall_pass_rate));
104
+ return;
105
+ }
106
+ // Local / no-comment / missing CI context — print markdown + blank + pass rate.
107
+ printMarkdownAndPassRate(markdown, report.overall_pass_rate);
108
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Parse `pathgrade run` arguments.
3
+ *
4
+ * Recognized pathgrade-specific flags are consumed here; everything else
5
+ * passes through to vitest. After a literal `--` separator, all remaining
6
+ * args are forwarded verbatim (vitest uses `--` the same way).
7
+ */
8
+ export interface PathgradeRunArgs {
9
+ vitestArgs: string[];
10
+ forceDiagnostics: boolean;
11
+ forceVerbose: boolean;
12
+ changed: boolean;
13
+ quiet: boolean;
14
+ since?: string;
15
+ changedFilesPath?: string;
16
+ /**
17
+ * Non-fatal argument-shape warnings (e.g. `--since` without `--changed`).
18
+ * Callers should surface these to the user on stderr before dispatch.
19
+ */
20
+ warnings?: string[];
21
+ }
22
+ export declare function parsePathgradeRunArgs(args: string[]): PathgradeRunArgs;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Parse `pathgrade run` arguments.
3
+ *
4
+ * Recognized pathgrade-specific flags are consumed here; everything else
5
+ * passes through to vitest. After a literal `--` separator, all remaining
6
+ * args are forwarded verbatim (vitest uses `--` the same way).
7
+ */
8
+ export function parsePathgradeRunArgs(args) {
9
+ const vitestArgs = [];
10
+ let forceDiagnostics = false;
11
+ let forceVerbose = false;
12
+ let changed = false;
13
+ let quiet = false;
14
+ let since;
15
+ let changedFilesPath;
16
+ let passthrough = false;
17
+ for (const arg of args) {
18
+ if (passthrough) {
19
+ vitestArgs.push(arg);
20
+ continue;
21
+ }
22
+ if (arg === '--') {
23
+ passthrough = true;
24
+ continue;
25
+ }
26
+ if (arg === '--diagnostics') {
27
+ forceDiagnostics = true;
28
+ continue;
29
+ }
30
+ if (arg === '--verbose' || arg === '-v') {
31
+ forceVerbose = true;
32
+ continue;
33
+ }
34
+ if (arg === '--changed') {
35
+ changed = true;
36
+ continue;
37
+ }
38
+ if (arg === '--quiet') {
39
+ quiet = true;
40
+ continue;
41
+ }
42
+ if (arg.startsWith('--since=')) {
43
+ since = arg.slice('--since='.length);
44
+ continue;
45
+ }
46
+ if (arg.startsWith('--changed-files=')) {
47
+ changedFilesPath = arg.slice('--changed-files='.length);
48
+ continue;
49
+ }
50
+ vitestArgs.push(arg);
51
+ }
52
+ const warnings = [];
53
+ if (!changed && since !== undefined) {
54
+ warnings.push('--since has no effect without --changed; the flag is being ignored.');
55
+ }
56
+ if (!changed && changedFilesPath !== undefined) {
57
+ warnings.push('--changed-files has no effect without --changed; the flag is being ignored.');
58
+ }
59
+ const base = {
60
+ vitestArgs,
61
+ forceDiagnostics,
62
+ forceVerbose,
63
+ changed,
64
+ quiet,
65
+ since,
66
+ changedFilesPath,
67
+ };
68
+ return warnings.length > 0 ? { ...base, warnings } : base;
69
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `pathgrade run --changed` — orchestration.
3
+ *
4
+ * Happy-path wrapper: computes the affected-eval selection, formats the
5
+ * run-start summary, and spawns `vitest run <files> <extra-args>` with the
6
+ * selected file list as positional args.
7
+ *
8
+ * Empty selection short-circuits: print "no affected evals" and exit 0
9
+ * without invoking vitest (which would silently run the full suite with
10
+ * zero positional args — a correctness bug we must not introduce).
11
+ *
12
+ * Git-resolution failures exit non-zero. The selection pipeline is the
13
+ * only producer of the file list here.
14
+ */
15
+ import type { PathgradeRunArgs } from './run-args.js';
16
+ export interface SpawnVitestRequest {
17
+ argv: string[];
18
+ }
19
+ export type SpawnVitest = (req: SpawnVitestRequest) => Promise<number> | number;
20
+ export interface RunChangedOptions {
21
+ cwd: string;
22
+ parsed: PathgradeRunArgs;
23
+ /** Override for tests; default spawns `npx vitest` inheriting stdio. */
24
+ spawnVitest?: SpawnVitest;
25
+ }
26
+ export declare function runChanged(opts: RunChangedOptions): Promise<number>;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * `pathgrade run --changed` — orchestration.
3
+ *
4
+ * Happy-path wrapper: computes the affected-eval selection, formats the
5
+ * run-start summary, and spawns `vitest run <files> <extra-args>` with the
6
+ * selected file list as positional args.
7
+ *
8
+ * Empty selection short-circuits: print "no affected evals" and exit 0
9
+ * without invoking vitest (which would silently run the full suite with
10
+ * zero positional args — a correctness bug we must not introduce).
11
+ *
12
+ * Git-resolution failures exit non-zero. The selection pipeline is the
13
+ * only producer of the file list here.
14
+ */
15
+ import * as fs from 'fs';
16
+ import { selectAffected } from '../affected/select.js';
17
+ import { resolveBaseRef, computeChangedFiles } from '../affected/git.js';
18
+ import { loadAffectedConfig } from '../affected/config.js';
19
+ import { writeSidecar } from '../affected/sidecar.js';
20
+ import { discoverEvalFiles } from './affected.js';
21
+ export async function runChanged(opts) {
22
+ const { cwd, parsed } = opts;
23
+ const spawnVitest = opts.spawnVitest ?? defaultSpawnVitest;
24
+ // 1. Change-set
25
+ let baseRef;
26
+ let changedFiles;
27
+ let baseRefLine;
28
+ if (parsed.changedFilesPath) {
29
+ try {
30
+ changedFiles = readChangedFilesList(parsed.changedFilesPath);
31
+ }
32
+ catch (err) {
33
+ process.stderr.write(`pathgrade run: could not read --changed-files: ${errMsg(err)}\n`);
34
+ return 1;
35
+ }
36
+ baseRef = 'explicit';
37
+ }
38
+ else if (parsed.since) {
39
+ changedFiles = computeChangedFiles(parsed.since);
40
+ baseRef = parsed.since;
41
+ baseRefLine = `pathgrade: base = ${parsed.since} (--since)`;
42
+ }
43
+ else {
44
+ const resolution = resolveBaseRef();
45
+ if ('error' in resolution) {
46
+ process.stderr.write(`${resolution.error}\n`);
47
+ return 1;
48
+ }
49
+ const shortSha = resolution.sha.slice(0, 7);
50
+ changedFiles = computeChangedFiles(resolution.sha);
51
+ baseRef = `${resolution.base}@${shortSha}`;
52
+ baseRefLine = `pathgrade: base = ${baseRef} (merge-base with HEAD)`;
53
+ }
54
+ // 2. Selection
55
+ const evalFiles = discoverEvalFiles(cwd);
56
+ const config = await loadAffectedConfig(cwd, {
57
+ onWarning: w => {
58
+ if (!parsed.quiet)
59
+ process.stderr.write(`${w}\n`);
60
+ },
61
+ });
62
+ let result;
63
+ try {
64
+ result = selectAffected({
65
+ evalFiles,
66
+ changedFiles,
67
+ repoRoot: cwd,
68
+ baseRef,
69
+ global: config.global,
70
+ });
71
+ }
72
+ catch (err) {
73
+ process.stderr.write(`pathgrade run: ${errMsg(err)}\n`);
74
+ return 1;
75
+ }
76
+ // 3. Summary + spawn
77
+ if (!parsed.quiet) {
78
+ printRunStartSummary({
79
+ baseRefLine,
80
+ totalEvals: evalFiles.length,
81
+ changedCount: changedFiles.length,
82
+ result,
83
+ });
84
+ }
85
+ // Persist the sidecar immediately — even on empty selection, so the
86
+ // reporter (if invoked later by another workflow step) has a coherent
87
+ // record. A subsequent plain `pathgrade run` clears it.
88
+ await writeSidecar(cwd, result);
89
+ if (result.selected.length === 0) {
90
+ if (!parsed.quiet) {
91
+ process.stderr.write(`pathgrade: no affected evals; nothing to run.\n`);
92
+ }
93
+ return 0;
94
+ }
95
+ const selectedFiles = result.selected.map(s => s.file);
96
+ const argv = ['run', ...selectedFiles, ...parsed.vitestArgs];
97
+ if (!parsed.quiet) {
98
+ process.stderr.write(`→ vitest run ${selectedFiles.join(' ')}\n`);
99
+ }
100
+ return await spawnVitest({ argv });
101
+ }
102
+ function printRunStartSummary(input) {
103
+ const { baseRefLine, totalEvals, changedCount, result } = input;
104
+ if (baseRefLine) {
105
+ process.stderr.write(`${baseRefLine}\n`);
106
+ }
107
+ process.stderr.write(` changed files: ${changedCount}\n`);
108
+ const globalLabel = result.globalMatch ? `\`${result.globalMatch}\`` : 'none';
109
+ process.stderr.write(` global matches: ${globalLabel}\n`);
110
+ process.stderr.write(` selected: ${result.selected.length} / ${totalEvals} evals\n`);
111
+ for (const entry of result.selected) {
112
+ process.stderr.write(` ${entry.file}\n`);
113
+ }
114
+ process.stderr.write(` skipped: ${result.skipped.length}\n`);
115
+ for (const w of result.warnings) {
116
+ process.stderr.write(` warning: ${w}\n`);
117
+ }
118
+ process.stderr.write(`\n`);
119
+ }
120
+ function readChangedFilesList(filePath) {
121
+ return fs.readFileSync(filePath, 'utf-8')
122
+ .split('\n')
123
+ .map(l => l.trim())
124
+ .filter(l => l.length > 0);
125
+ }
126
+ function errMsg(err) {
127
+ return err instanceof Error ? err.message : String(err);
128
+ }
129
+ async function defaultSpawnVitest(req) {
130
+ const { spawn } = await import('child_process');
131
+ return await new Promise(resolve => {
132
+ const child = spawn('npx', ['vitest', ...req.argv], {
133
+ stdio: 'inherit',
134
+ shell: true,
135
+ });
136
+ child.on('close', code => resolve(code ?? 0));
137
+ });
138
+ }
@@ -0,0 +1,13 @@
1
+ export interface ValidateOptions {
2
+ /** Skip tsc subprocess check (useful in tests without full TS project setup). */
3
+ skipTsc?: boolean;
4
+ }
5
+ /**
6
+ * Run the validate command. Returns exit code (0 = valid, 1 = errors).
7
+ */
8
+ export declare function runValidate(filePath: string, opts?: ValidateOptions): Promise<number>;
9
+ /**
10
+ * Strict affected-selection validator. Exits 1 if any eval is missing
11
+ * both a SKILL.md ancestor and `__pathgradeMeta`, or has malformed meta.
12
+ */
13
+ export declare function runValidateAffected(cwd: string): Promise<number>;