vouchington-tooling 0.1.4 → 0.1.5

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/README.md CHANGED
@@ -20,6 +20,7 @@ vouchington gha-runtime-audit --pr-workflow CI --push-workflow '/^Main CI \\(.+\
20
20
  vouchington gha-output name
21
21
  vouchington gha-needs-results
22
22
  vouchington download-with-diagnostics <url> <destination>
23
+ vouchington download-optional-run-artifacts --pattern 'coverage-*' --dir ./coverage-fallback
23
24
  vouchington host-pressure-diagnostics
24
25
  vouchington allocate-browser-safe-ports 2 --policy ./policy.json --forbidden-ports ./ports.json
25
26
  vouchington diagnose-port-collision --ports "2200 2216"
@@ -46,6 +47,11 @@ vouchington post-review
46
47
  vouchington stage-review-payload optional|required <source> <destination>
47
48
  ```
48
49
 
50
+ `download-optional-run-artifacts` uses the current Actions run and host. Pattern mode discovers
51
+ non-expired artifacts across the run, keeps the first result for each name (matching `gh run
52
+ download`), and extracts each selected name into its own directory. Ordinary absence is reported as
53
+ `availability=unavailable`; invalid names and cancellation remain hard failures.
54
+
49
55
  `retrospective-transcript` discovers Codex and Claude transcripts by default. It also reads a
50
56
  Claude-compatible transcript when `CURSOR_SESSION_ID` is set, and Grok's `updates.jsonl` session
51
57
  layout when `GROK_SESSION_ID` is set. Use `--grok-sessions-dir` to point discovery at a nondefault
@@ -26,6 +26,10 @@ const SCRIPT_PATHS = {
26
26
  command: 'bash',
27
27
  path: 'scripts/gha/download-with-diagnostics.sh',
28
28
  },
29
+ 'download-optional-run-artifacts': {
30
+ command: 'bash',
31
+ path: 'scripts/gha/download-optional-run-artifacts.sh',
32
+ },
29
33
  'host-pressure-diagnostics': {
30
34
  command: 'bash',
31
35
  path: 'scripts/gha/host-pressure-diagnostics.sh',
@@ -44,5 +44,5 @@ export type ParsedCli = {
44
44
  kind: 'retrospective-transcript';
45
45
  args: string[];
46
46
  } | ParsedGhaRuntimeAudit | ParsedGhaArtifactsCleanup;
47
- export type ScriptCommand = 'gha-output' | 'gha-needs-results' | 'download-with-diagnostics' | 'host-pressure-diagnostics' | 'allocate-browser-safe-ports' | 'diagnose-port-collision' | 'prepare-trivy-db' | 'check-cache-size' | 'make-shard-matrix' | 'load-runner-env' | 'clean-workspace' | 'install-github-release' | 'run-with-timeout' | 'lint-links' | 'materialize-pr-context' | 'wait-for-apt-locks' | 'install-playwright-chromium-arm64' | 'ghcr-package-retention';
47
+ export type ScriptCommand = 'gha-output' | 'gha-needs-results' | 'download-with-diagnostics' | 'download-optional-run-artifacts' | 'host-pressure-diagnostics' | 'allocate-browser-safe-ports' | 'diagnose-port-collision' | 'prepare-trivy-db' | 'check-cache-size' | 'make-shard-matrix' | 'load-runner-env' | 'clean-workspace' | 'install-github-release' | 'run-with-timeout' | 'lint-links' | 'materialize-pr-context' | 'wait-for-apt-locks' | 'install-playwright-chromium-arm64' | 'ghcr-package-retention';
48
48
  export declare function parseCli(argv: readonly string[]): ParsedCli;
@@ -4,6 +4,7 @@ const SCRIPT_COMMANDS = new Set([
4
4
  'gha-output',
5
5
  'gha-needs-results',
6
6
  'download-with-diagnostics',
7
+ 'download-optional-run-artifacts',
7
8
  'host-pressure-diagnostics',
8
9
  'allocate-browser-safe-ports',
9
10
  'diagnose-port-collision',
@@ -1,2 +1,2 @@
1
- export declare const USAGE = "Usage: vouchington <command> [options]\n\nCommands:\n runner-port-policy Print or validate a runner port policy\n with-host-lock Run a command under a host-wide lock\n gha-runtime-audit Audit successful GitHub Actions job runtimes\n gha-output Write a collision-safe multiline GITHUB_OUTPUT record\n gha-needs-results Fail if required GitHub Actions job results failed\n download-with-diagnostics Download a URL and report HTTP status on failure\n host-pressure-diagnostics Print a bounded host memory/OOM/PSI snapshot\n allocate-browser-safe-ports Allocate Fetch-safe localhost ports\n diagnose-port-collision Capture bounded localhost port diagnostics\n prepare-trivy-db Download the Trivy vulnerability database\n gha-artifacts-cleanup Delete classified GitHub Actions artifacts\n http-origin Validate an optional HTTP(S) origin\n vitest-blob-manifest Stamp a vitest-blob-manifest:v1 identity file\n pnpm-install Install a pnpm workspace with retry and release-age fail-fast\n check-cache-size Measure a path and decide whether to save a GHA cache\n make-shard-matrix Emit a [1..N] GitHub Actions shard matrix\n load-runner-env Overlay a runner env file onto GITHUB_ENV with injection guards\n clean-workspace Reset a persistent-runner workspace with a fork-PR trust gate\n install-github-release Download a checksum-verified GitHub Release binary\n run-with-timeout Run a command with GNU timeout or a Perl fallback\n lint-links Two-pass lychee: internal links fail, external warn\n materialize-pr-context Dump PR title/body/files/diff/comments and #N crawl\n wait-for-apt-locks Wait until apt/dpkg lock files are free\n install-playwright-chromium-arm64 Install Playwright Chromium from browsers.json\n ghcr-package-retention Delete old GHCR package versions past KEEP_MIN\n nuget-central-version Validate a Directory.Packages.props PackageVersion delta\n swift-semantic-equal Compare Swift sources ignoring comments and whitespace\n post-review Post one COMMENT review from a staged payload file\n stage-review-payload Validate a review payload file into a staging directory\n retrospective-transcript Format facts from Claude-compatible, Codex, or Grok transcripts\n\nOptions:\n -h, --help Show this help\n -v, --version Print the package version\n\nrunner-port-policy\n (no args) Print the shipped policy as JSON\n --file <path> Validate and print a policy file\n --reserved <port> Print true if the port is reserved\n\nwith-host-lock\n --name <family>\n [--slots <n>]\n --timeout-seconds <n>\n [--command-timeout-seconds <n>]\n [--failure-diagnostics <absolute-script>]\n [--on-acquire-timeout fail|run-unlocked]\n -- <command> [args...]\n\ngha-runtime-audit\n [--repository owner/name] Default GITHUB_REPOSITORY\n [--branch main]\n --pr-workflow <name|/regex/> Repeatable\n --push-workflow <name|/regex/> Repeatable\n\ngha-output <name>\ngha-needs-results [label]\ndownload-with-diagnostics <url> <destination> [-- curl-args...]\nhost-pressure-diagnostics\nallocate-browser-safe-ports [count] [--policy path] [--forbidden-ports path]\ndiagnose-port-collision [--ports \"2200 2216\"] [--output-dir PATH]\nprepare-trivy-db\ngha-artifacts-cleanup run --run-id <id> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\ngha-artifacts-cleanup sweep --older-than-hours <n> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\nhttp-origin [--field NAME] [value]\nvitest-blob-manifest <suite> [reports-directory]\npnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false\ncheck-cache-size <path> <max-bytes> <label>\nmake-shard-matrix <total>\nload-runner-env\nclean-workspace\ninstall-github-release --repo owner/name --version X --asset 'name-{platform}.tar.gz' --bin name [--tag-prefix PREFIX] [--no-checksum] [--checksums-asset NAME] [--version-flag FLAG] [--bin-dir DIR]\nrun-with-timeout <timeout-seconds> <kill-after-seconds> <command...>\nlint-links [--offline] [--config PATH] [--glob PATTERN] [files...]\nmaterialize-pr-context\nwait-for-apt-locks\ninstall-playwright-chromium-arm64 [name:archive...]\nghcr-package-retention <url-encoded-package>...\nnuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>\nswift-semantic-equal <base> <head> <file.swift>\npost-review\nstage-review-payload optional|required <source> <destination>\nretrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH] [--codex-sessions-dir PATH] [--grok-sessions-dir PATH]\n";
1
+ export declare const USAGE = "Usage: vouchington <command> [options]\n\nCommands:\n runner-port-policy Print or validate a runner port policy\n with-host-lock Run a command under a host-wide lock\n gha-runtime-audit Audit successful GitHub Actions job runtimes\n gha-output Write a collision-safe multiline GITHUB_OUTPUT record\n gha-needs-results Fail if required GitHub Actions job results failed\n download-with-diagnostics Download a URL and report HTTP status on failure\n download-optional-run-artifacts Download optional artifacts from the current run\n host-pressure-diagnostics Print a bounded host memory/OOM/PSI snapshot\n allocate-browser-safe-ports Allocate Fetch-safe localhost ports\n diagnose-port-collision Capture bounded localhost port diagnostics\n prepare-trivy-db Download the Trivy vulnerability database\n gha-artifacts-cleanup Delete classified GitHub Actions artifacts\n http-origin Validate an optional HTTP(S) origin\n vitest-blob-manifest Stamp a vitest-blob-manifest:v1 identity file\n pnpm-install Install a pnpm workspace with retry and release-age fail-fast\n check-cache-size Measure a path and decide whether to save a GHA cache\n make-shard-matrix Emit a [1..N] GitHub Actions shard matrix\n load-runner-env Overlay a runner env file onto GITHUB_ENV with injection guards\n clean-workspace Reset a persistent-runner workspace with a fork-PR trust gate\n install-github-release Download a checksum-verified GitHub Release binary\n run-with-timeout Run a command with GNU timeout or a Perl fallback\n lint-links Two-pass lychee: internal links fail, external warn\n materialize-pr-context Dump PR title/body/files/diff/comments and #N crawl\n wait-for-apt-locks Wait until apt/dpkg lock files are free\n install-playwright-chromium-arm64 Install Playwright Chromium from browsers.json\n ghcr-package-retention Delete old GHCR package versions past KEEP_MIN\n nuget-central-version Validate a Directory.Packages.props PackageVersion delta\n swift-semantic-equal Compare Swift sources ignoring comments and whitespace\n post-review Post one COMMENT review from a staged payload file\n stage-review-payload Validate a review payload file into a staging directory\n retrospective-transcript Format facts from Claude-compatible, Codex, or Grok transcripts\n\nOptions:\n -h, --help Show this help\n -v, --version Print the package version\n\nrunner-port-policy\n (no args) Print the shipped policy as JSON\n --file <path> Validate and print a policy file\n --reserved <port> Print true if the port is reserved\n\nwith-host-lock\n --name <family>\n [--slots <n>]\n --timeout-seconds <n>\n [--command-timeout-seconds <n>]\n [--failure-diagnostics <absolute-script>]\n [--on-acquire-timeout fail|run-unlocked]\n -- <command> [args...]\n\ngha-runtime-audit\n [--repository owner/name] Default GITHUB_REPOSITORY\n [--branch main]\n --pr-workflow <name|/regex/> Repeatable\n --push-workflow <name|/regex/> Repeatable\n\ngha-output <name>\ngha-needs-results [label]\ndownload-with-diagnostics <url> <destination> [-- curl-args...]\ndownload-optional-run-artifacts (--name <name> | --pattern <pattern>) --dir <directory>\nhost-pressure-diagnostics\nallocate-browser-safe-ports [count] [--policy path] [--forbidden-ports path]\ndiagnose-port-collision [--ports \"2200 2216\"] [--output-dir PATH]\nprepare-trivy-db\ngha-artifacts-cleanup run --run-id <id> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\ngha-artifacts-cleanup sweep --older-than-hours <n> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\nhttp-origin [--field NAME] [value]\nvitest-blob-manifest <suite> [reports-directory]\npnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false\ncheck-cache-size <path> <max-bytes> <label>\nmake-shard-matrix <total>\nload-runner-env\nclean-workspace\ninstall-github-release --repo owner/name --version X --asset 'name-{platform}.tar.gz' --bin name [--tag-prefix PREFIX] [--expected-sha256 SHA256] [--no-checksum] [--checksums-asset NAME] [--version-flag FLAG] [--bin-dir DIR]\nrun-with-timeout <timeout-seconds> <kill-after-seconds> <command...>\nlint-links [--offline] [--config PATH] [--glob PATTERN] [files...]\nmaterialize-pr-context\nwait-for-apt-locks\ninstall-playwright-chromium-arm64 [name:archive...]\nghcr-package-retention <url-encoded-package>...\nnuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>\nswift-semantic-equal <base> <head> <file.swift>\npost-review\nstage-review-payload optional|required <source> <destination>\nretrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH] [--codex-sessions-dir PATH] [--grok-sessions-dir PATH]\n";
2
2
  export declare function printUsage(stream?: NodeJS.WritableStream): void;
@@ -7,6 +7,7 @@ Commands:
7
7
  gha-output Write a collision-safe multiline GITHUB_OUTPUT record
8
8
  gha-needs-results Fail if required GitHub Actions job results failed
9
9
  download-with-diagnostics Download a URL and report HTTP status on failure
10
+ download-optional-run-artifacts Download optional artifacts from the current run
10
11
  host-pressure-diagnostics Print a bounded host memory/OOM/PSI snapshot
11
12
  allocate-browser-safe-ports Allocate Fetch-safe localhost ports
12
13
  diagnose-port-collision Capture bounded localhost port diagnostics
@@ -59,6 +60,7 @@ gha-runtime-audit
59
60
  gha-output <name>
60
61
  gha-needs-results [label]
61
62
  download-with-diagnostics <url> <destination> [-- curl-args...]
63
+ download-optional-run-artifacts (--name <name> | --pattern <pattern>) --dir <directory>
62
64
  host-pressure-diagnostics
63
65
  allocate-browser-safe-ports [count] [--policy path] [--forbidden-ports path]
64
66
  diagnose-port-collision [--ports "2200 2216"] [--output-dir PATH]
@@ -72,7 +74,7 @@ check-cache-size <path> <max-bytes> <label>
72
74
  make-shard-matrix <total>
73
75
  load-runner-env
74
76
  clean-workspace
75
- install-github-release --repo owner/name --version X --asset 'name-{platform}.tar.gz' --bin name [--tag-prefix PREFIX] [--no-checksum] [--checksums-asset NAME] [--version-flag FLAG] [--bin-dir DIR]
77
+ install-github-release --repo owner/name --version X --asset 'name-{platform}.tar.gz' --bin name [--tag-prefix PREFIX] [--expected-sha256 SHA256] [--no-checksum] [--checksums-asset NAME] [--version-flag FLAG] [--bin-dir DIR]
76
78
  run-with-timeout <timeout-seconds> <kill-after-seconds> <command...>
77
79
  lint-links [--offline] [--config PATH] [--glob PATTERN] [files...]
78
80
  materialize-pr-context
@@ -1,4 +1,4 @@
1
- import { ReviewPayloadError, bodyOnlyReviewFallback, indexReviewFiles, parseReviewPayload, remapReviewComments, } from '../gha-review-payload/index.mjs';
1
+ import { ReviewPayloadError, indexReviewFiles, parseReviewPayload, remapReviewComments, } from '../gha-review-payload/index.mjs';
2
2
  export { MAX_REVIEW_COMMENTS as MAX_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES as MAX_PAYLOAD_BYTES, ReviewPayloadError as PostReviewError, } from '../gha-review-payload/index.mjs';
3
3
  export function runPostReview(payloadPath, io) {
4
4
  try {
@@ -12,14 +12,7 @@ export function runPostReview(payloadPath, io) {
12
12
  const first = io.postReview(review);
13
13
  if (first.ok)
14
14
  return { posted: true };
15
- if (first.status !== 422) {
16
- throw new ReviewPayloadError(`GitHub review POST failed (HTTP ${first.status}).`);
17
- }
18
- const retry = io.postReview(bodyOnlyReviewFallback(review, first.status));
19
- if (!retry.ok) {
20
- throw new ReviewPayloadError(`GitHub review POST retry failed (HTTP ${retry.status}).`);
21
- }
22
- return { posted: true };
15
+ throw new ReviewPayloadError(`GitHub review POST failed (HTTP ${first.status}).`);
23
16
  }
24
17
  finally {
25
18
  io.removeFile(payloadPath);
@@ -1,4 +1,4 @@
1
- export { MAX_REVIEW_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES, ReviewPayloadError, bodyOnlyReviewFallback, parseReviewPayload, reviewCommentSubject, } from './payload.mts';
1
+ export { MAX_REVIEW_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES, ReviewPayloadError, parseReviewPayload, reviewCommentSubject, } from './payload.mts';
2
2
  export type { ReviewComment, ReviewSide, SanitizedReview } from './payload.mts';
3
3
  export { indexReviewFiles, parsePatchCommentable, parseReviewFilesJson } from './diff.mts';
4
4
  export type { CommentableIndex, CommentableLine, LineKind, ReviewFile } from './diff.mts';
@@ -1,4 +1,4 @@
1
- export { MAX_REVIEW_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES, ReviewPayloadError, bodyOnlyReviewFallback, parseReviewPayload, reviewCommentSubject, } from './payload.mjs';
1
+ export { MAX_REVIEW_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES, ReviewPayloadError, parseReviewPayload, reviewCommentSubject, } from './payload.mjs';
2
2
  export { indexReviewFiles, parsePatchCommentable, parseReviewFilesJson } from './diff.mjs';
3
3
  export { nearestReviewLine, remapReviewComments, rewriteSnappedSuggestion, snapReviewNote, } from './remap.mjs';
4
4
  export { readRegularReviewPayload, stageReviewPayload, writeStagedOutput } from './file.mjs';
@@ -23,8 +23,6 @@ export type SanitizedReview = {
23
23
  export declare function reviewCommentSubject(comment: ReviewComment): string;
24
24
  /**
25
25
  * Parses untrusted JSON into the exact review wire shape accepted by the caller's poster.
26
- * Unknown fields and malformed comments are dropped; the supplied commit id always wins.
26
+ * Unknown fields are ignored, malformed findings fail closed, and the supplied commit id wins.
27
27
  */
28
28
  export declare function parseReviewPayload(bytes: Buffer, commitId: string): SanitizedReview;
29
- /** Builds the body-only fallback used when inline review placement is rejected. */
30
- export declare function bodyOnlyReviewFallback(review: SanitizedReview, status: number): SanitizedReview;
@@ -49,7 +49,7 @@ export function reviewCommentSubject(comment) {
49
49
  }
50
50
  /**
51
51
  * Parses untrusted JSON into the exact review wire shape accepted by the caller's poster.
52
- * Unknown fields and malformed comments are dropped; the supplied commit id always wins.
52
+ * Unknown fields are ignored, malformed findings fail closed, and the supplied commit id wins.
53
53
  */
54
54
  export function parseReviewPayload(bytes, commitId) {
55
55
  if (!isCommitId(commitId)) {
@@ -70,42 +70,26 @@ export function parseReviewPayload(bytes, commitId) {
70
70
  }
71
71
  const record = parsed;
72
72
  const body = typeof record.body === 'string' ? record.body : '';
73
- const rawComments = Array.isArray(record.comments) ? record.comments : [];
74
- const valid = rawComments.flatMap((comment) => {
73
+ if (record.comments !== undefined && !Array.isArray(record.comments)) {
74
+ throw new ReviewPayloadError('comments must be an array when provided.');
75
+ }
76
+ const rawComments = record.comments ?? [];
77
+ if (rawComments.length > MAX_REVIEW_COMMENTS) {
78
+ throw new ReviewPayloadError(`Payload has more than ${MAX_REVIEW_COMMENTS} inline comments.`);
79
+ }
80
+ const comments = rawComments.map((comment) => {
75
81
  const sanitized = sanitizeComment(comment);
76
- return sanitized ? [sanitized] : [];
82
+ if (!sanitized)
83
+ throw new ReviewPayloadError('Every finding must be a valid inline comment.');
84
+ return sanitized;
77
85
  });
78
- const kept = valid.slice(0, MAX_REVIEW_COMMENTS);
79
- const overflow = valid.slice(MAX_REVIEW_COMMENTS);
80
- const parts = [body];
81
- if (overflow.length > 0) {
82
- parts.push(`## Comments over the ${MAX_REVIEW_COMMENTS}-comment cap`, ...overflow.map((comment) => `- ${reviewCommentSubject(comment)}`));
83
- }
84
- const reviewBody = parts.filter((part) => part.length > 0).join('\n\n');
85
- if (reviewBody.length === 0 && kept.length === 0) {
86
+ if (body.length === 0 && comments.length === 0) {
86
87
  throw new ReviewPayloadError('Payload has no review body and no valid comments.');
87
88
  }
88
89
  return {
89
90
  event: 'COMMENT',
90
91
  commit_id: commitId,
91
- body: reviewBody.length > 0 ? reviewBody : 'Inline findings only.',
92
- comments: kept,
93
- };
94
- }
95
- /** Builds the body-only fallback used when inline review placement is rejected. */
96
- export function bodyOnlyReviewFallback(review, status) {
97
- const listed = review.comments.length === 0
98
- ? []
99
- : [
100
- '## Inline findings not posted',
101
- `The inline comments were rejected (HTTP ${status}). The findings were:`,
102
- ...review.comments.map((comment) => `- ${reviewCommentSubject(comment)}`),
103
- ];
104
- const body = [review.body, ...listed].filter((part) => part.length > 0).join('\n\n');
105
- return {
106
- event: 'COMMENT',
107
- commit_id: review.commit_id,
108
- body: body.length > 0 ? body : `Inline findings not posted (HTTP ${status}).`,
109
- comments: [],
92
+ body: body.length > 0 ? body : 'Inline findings only.',
93
+ comments,
110
94
  };
111
95
  }
@@ -9,5 +9,5 @@ export declare function nearestReviewLine(candidates: Array<{
9
9
  line: number;
10
10
  kind: LineKind;
11
11
  } | undefined;
12
- /** Places comments on commentable diff lines, remapping renames and recording dropped findings. */
12
+ /** Places every finding on a commentable diff line and fails if any finding cannot be placed. */
13
13
  export declare function remapReviewComments(review: SanitizedReview, index: CommentableIndex): SanitizedReview;
@@ -1,4 +1,4 @@
1
- import { reviewCommentSubject } from './payload.mjs';
1
+ import { ReviewPayloadError } from './payload.mjs';
2
2
  export function snapReviewNote(path, line) {
3
3
  return `_Regarding \`${path}:${line}\` (not in the diff hunk; posted on the nearest commentable line)._`;
4
4
  }
@@ -62,28 +62,19 @@ function placeComment(comment, index) {
62
62
  return withSnap({ ...target, side: alt, line: nearAlt.line }, originalPath, originalLine);
63
63
  return null;
64
64
  }
65
- /** Places comments on commentable diff lines, remapping renames and recording dropped findings. */
65
+ /** Places every finding on a commentable diff line and fails if any finding cannot be placed. */
66
66
  export function remapReviewComments(review, index) {
67
67
  const kept = [];
68
- const dropped = [];
69
68
  for (const comment of review.comments) {
70
69
  const placed = placeComment(comment, index);
71
- if (placed)
72
- kept.push(placed);
73
- else
74
- dropped.push(comment);
70
+ if (!placed) {
71
+ throw new ReviewPayloadError(`Inline finding cannot be placed on the pull request diff: ${comment.path}:${comment.line}.`);
72
+ }
73
+ kept.push(placed);
75
74
  }
76
- const extras = dropped.length === 0
77
- ? []
78
- : [
79
- '## Inline findings not posted',
80
- 'These comments could not be placed on a diff hunk:',
81
- ...dropped.map((comment) => `- ${reviewCommentSubject(comment)}`),
82
- ];
83
- const body = [review.body, ...extras].filter((part) => part.length > 0).join('\n\n');
84
75
  return {
85
76
  ...review,
86
- body: body.length > 0 ? body : 'Inline findings only.',
77
+ body: review.body.length > 0 ? review.body : 'Inline findings only.',
87
78
  comments: kept,
88
79
  };
89
80
  }
package/dist/index.d.mts CHANGED
@@ -35,7 +35,7 @@ export { MissingResponseBodyError, readResponseBody, readResponseBodyAsBuffer, R
35
35
  export type { ReadResponseBodyOptions } from './http-body/index.mts';
36
36
  export { parseAstGrepRuleArgs, runAstGrepRule } from './ast-grep-rule/index.mts';
37
37
  export type { AstGrepRuleInvocation, RunAstGrepRuleOptions } from './ast-grep-rule/index.mts';
38
- export { bodyOnlyReviewFallback, indexReviewFiles, MAX_REVIEW_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES, nearestReviewLine, parsePatchCommentable, parseReviewFilesJson, parseReviewPayload, readRegularReviewPayload, remapReviewComments, ReviewPayloadError, reviewCommentSubject, rewriteSnappedSuggestion, snapReviewNote, stageReviewPayload, writeStagedOutput, } from './gha-review-payload/index.mts';
38
+ export { indexReviewFiles, MAX_REVIEW_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES, nearestReviewLine, parsePatchCommentable, parseReviewFilesJson, parseReviewPayload, readRegularReviewPayload, remapReviewComments, ReviewPayloadError, reviewCommentSubject, rewriteSnappedSuggestion, snapReviewNote, stageReviewPayload, writeStagedOutput, } from './gha-review-payload/index.mts';
39
39
  export type { CommentableIndex, CommentableLine, LineKind, PayloadRequirement, ReviewComment, ReviewFile, ReviewSide, SanitizedReview, } from './gha-review-payload/index.mts';
40
40
  export { CLAUDE_OIDC_AUDIENCE, createActionsClaudeTokenIo, mintClaudeAppToken, PostReviewError, requireEnv, resolveReviewPostToken, revokeClaudeAppToken, runPostReview, runPostReviewCli, withClaudeAppToken, } from './gha-post-review/index.mts';
41
41
  export type { ClaudeTokenIo, PostResult, PostReviewIo, PullFile, ReviewPostToken, } from './gha-post-review/index.mts';
package/dist/index.mjs CHANGED
@@ -20,7 +20,7 @@ export { decide, deriveRetryAttempt } from './transient-retry/index.mjs';
20
20
  export { escapeSpreadsheetFormula, parseCsvRows, streamCsvRows, stripCsvBom } from './csv/index.mjs';
21
21
  export { MissingResponseBodyError, readResponseBody, readResponseBodyAsBuffer, ResponseBodyTooLargeError, } from './http-body/index.mjs';
22
22
  export { parseAstGrepRuleArgs, runAstGrepRule } from './ast-grep-rule/index.mjs';
23
- export { bodyOnlyReviewFallback, indexReviewFiles, MAX_REVIEW_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES, nearestReviewLine, parsePatchCommentable, parseReviewFilesJson, parseReviewPayload, readRegularReviewPayload, remapReviewComments, ReviewPayloadError, reviewCommentSubject, rewriteSnappedSuggestion, snapReviewNote, stageReviewPayload, writeStagedOutput, } from './gha-review-payload/index.mjs';
23
+ export { indexReviewFiles, MAX_REVIEW_COMMENTS, MAX_REVIEW_PAYLOAD_BYTES, nearestReviewLine, parsePatchCommentable, parseReviewFilesJson, parseReviewPayload, readRegularReviewPayload, remapReviewComments, ReviewPayloadError, reviewCommentSubject, rewriteSnappedSuggestion, snapReviewNote, stageReviewPayload, writeStagedOutput, } from './gha-review-payload/index.mjs';
24
24
  export { CLAUDE_OIDC_AUDIENCE, createActionsClaudeTokenIo, mintClaudeAppToken, PostReviewError, requireEnv, resolveReviewPostToken, revokeClaudeAppToken, runPostReview, runPostReviewCli, withClaudeAppToken, } from './gha-post-review/index.mjs';
25
25
  export { nextPageCursorFromLinkHeader, nextPageUrlFromLinkHeader, validatePaginationRequestUrl, } from './http-link-pagination/index.mjs';
26
26
  export { cmdDownloadCoverage, cmdDownloadVitestBlobs, cmdUpload, mintPresignedControl, transportObjectKeys, } from './coverage-transport/index.mjs';
@@ -0,0 +1,8 @@
1
+ import { type ParsedLine } from './shared.mts';
2
+ export declare class CodexMessageCounter {
3
+ private previous;
4
+ private userPrompts;
5
+ private assistantResponses;
6
+ add(record: ParsedLine, payload: ParsedLine | undefined): void;
7
+ totals(): [number, number];
8
+ }
@@ -0,0 +1,66 @@
1
+ import { asRecord } from './shared.mjs';
2
+ function isInjectedBlock(raw) {
3
+ const text = raw.trim();
4
+ // Hosted metadata occupies complete input blocks; inner fields can vary by runtime version.
5
+ const agents = /^# AGENTS\.md instructions for [^\r\n]+(?:\r?\n)+<INSTRUCTIONS(?:\s[^>]*)?>[\s\S]*<\/INSTRUCTIONS>$/.test(text);
6
+ const environment = /^<environment_context(?:\s[^>]*)?>[\s\S]*<\/environment_context>$/.test(text);
7
+ const skill = /^<skill(?:\s[^>]*)?>[\s\S]*<\/skill>$/.test(text);
8
+ return agents || environment || skill;
9
+ }
10
+ function isInjectedUser(payload) {
11
+ if (!Array.isArray(payload.content) || payload.content.length === 0)
12
+ return false;
13
+ return payload.content.map(asRecord).every((item) => {
14
+ return (item?.type === 'input_text' && typeof item.text === 'string' && isInjectedBlock(item.text));
15
+ });
16
+ }
17
+ function message(record, payload) {
18
+ if (record.type === 'response_item' && payload?.type === 'message') {
19
+ if (payload.role === 'user' || payload.role === 'assistant')
20
+ return { role: payload.role, schema: 'current' };
21
+ }
22
+ else if (record.type === 'event_msg') {
23
+ if (payload?.type === 'user_message')
24
+ return { role: 'user', schema: 'legacy' };
25
+ if (payload?.type === 'agent_message')
26
+ return { role: 'assistant', schema: 'legacy' };
27
+ }
28
+ return undefined;
29
+ }
30
+ function isDuplicate(previous, current) {
31
+ // Hosted rollouts currently emit user as current→legacy and assistant as legacy→current;
32
+ // keep directional to avoid merging inverse-order distinct turns (see pairing tests).
33
+ return ((current.role === 'user' && previous?.schema === 'current' && current.schema === 'legacy') ||
34
+ (current.role === 'assistant' && previous?.schema === 'legacy' && current.schema === 'current'));
35
+ }
36
+ export class CodexMessageCounter {
37
+ previous;
38
+ userPrompts = 0;
39
+ assistantResponses = 0;
40
+ add(record, payload) {
41
+ const current = message(record, payload);
42
+ const injected = current?.schema === 'current' &&
43
+ current.role === 'user' &&
44
+ payload !== undefined &&
45
+ isInjectedUser(payload);
46
+ if (!current) {
47
+ this.previous = undefined;
48
+ return;
49
+ }
50
+ // Injected records are transparent because hosted metadata can split a duplicate pair.
51
+ if (injected)
52
+ return;
53
+ if (this.previous?.role === current.role && isDuplicate(this.previous, current)) {
54
+ this.previous = undefined;
55
+ return;
56
+ }
57
+ if (current.role === 'user')
58
+ this.userPrompts++;
59
+ else
60
+ this.assistantResponses++;
61
+ this.previous = current;
62
+ }
63
+ totals() {
64
+ return [this.userPrompts, this.assistantResponses];
65
+ }
66
+ }
@@ -1,4 +1,5 @@
1
1
  import { applyCommand, asNumber, asRecord, emptyFacts, emptyTokens, parseLines, } from './shared.mjs';
2
+ import { CodexMessageCounter } from './codex-messages.mjs';
2
3
  import { customExecCommands } from './javascript-command.mjs';
3
4
  function usage(record) {
4
5
  const payload = asRecord(record.payload);
@@ -83,14 +84,13 @@ function applyRecords(records, facts, subagent, baseline = emptyTokens()) {
83
84
  let previous = baseline;
84
85
  const calls = new Set();
85
86
  const failed = new Set();
87
+ const messages = new CodexMessageCounter();
86
88
  let anonymousFailures = 0;
87
89
  let previousCompaction;
88
90
  for (const record of records) {
89
91
  const payload = asRecord(record.payload);
90
- if (!subagent && record.type === 'event_msg' && payload?.type === 'user_message')
91
- facts.userPrompts++;
92
- if (!subagent && record.type === 'event_msg' && payload?.type === 'agent_message')
93
- facts.assistantResponses++;
92
+ if (!subagent)
93
+ messages.add(record, payload);
94
94
  const compaction = record.type === 'compacted'
95
95
  ? 'top-level'
96
96
  : record.type === 'event_msg' && payload?.type === 'context_compacted'
@@ -135,6 +135,11 @@ function applyRecords(records, facts, subagent, baseline = emptyTokens()) {
135
135
  anonymousFailures++;
136
136
  }
137
137
  }
138
+ if (!subagent) {
139
+ const [userPrompts, assistantResponses] = messages.totals();
140
+ facts.userPrompts += userPrompts;
141
+ facts.assistantResponses += assistantResponses;
142
+ }
138
143
  facts.failedToolCalls += [...failed].filter((id) => calls.has(id)).length + anonymousFailures;
139
144
  }
140
145
  export function codexChildren(lines, ownerPath = '/root') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vouchington-tooling",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Vouchington CLI and extractable tooling libraries.",
5
5
  "homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
6
6
  "bugs": {
@@ -223,7 +223,7 @@
223
223
  },
224
224
  "dependencies": {
225
225
  "csv-parse": "7.0.2",
226
- "csv-stringify": "6.8.2",
226
+ "csv-stringify": "6.8.3",
227
227
  "dockerfile-ast": "0.7.1",
228
228
  "picomatch": "4.0.5",
229
229
  "yaml": "2.9.0"
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ echo 'usage: download-optional-run-artifacts.sh (--name <name> | --pattern <pattern>) --dir <directory>' >&2
6
+ exit 2
7
+ }
8
+
9
+ selector=''
10
+ selector_type=''
11
+ destination=''
12
+ while [ "$#" -gt 0 ]; do
13
+ case "$1" in
14
+ --name|--pattern)
15
+ [ -z "$selector_type" ] && [ "$#" -ge 2 ] || usage
16
+ selector_type="${1#--}"
17
+ selector="$2"
18
+ shift 2
19
+ ;;
20
+ --dir)
21
+ [ -z "$destination" ] && [ "$#" -ge 2 ] || usage
22
+ destination="$2"
23
+ shift 2
24
+ ;;
25
+ *) usage ;;
26
+ esac
27
+ done
28
+
29
+ [ -n "$selector_type" ] && [ -n "$selector" ] && [ -n "$destination" ] || usage
30
+ [ -n "${GITHUB_OUTPUT:-}" ] || { echo 'GITHUB_OUTPUT must be set' >&2; exit 2; }
31
+ [ -n "${GITHUB_REPOSITORY:-}" ] || { echo 'GITHUB_REPOSITORY must be set' >&2; exit 2; }
32
+ [ -n "${GITHUB_RUN_ID:-}" ] || { echo 'GITHUB_RUN_ID must be set' >&2; exit 2; }
33
+ github_host="${GH_HOST:-${GITHUB_SERVER_URL:-https://github.com}}"
34
+ github_host="${github_host#*://}"
35
+ github_host="${github_host%%/*}"
36
+ [ -n "$github_host" ] || { echo 'GitHub host must be set' >&2; exit 2; }
37
+ repository="$GITHUB_REPOSITORY"
38
+ case "$github_host" in
39
+ github.com|*.ghe.com) ;;
40
+ *)
41
+ if [ -z "${GH_ENTERPRISE_TOKEN:-${GITHUB_ENTERPRISE_TOKEN:-}}" ]; then
42
+ enterprise_token="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
43
+ [ -z "$enterprise_token" ] || export GH_ENTERPRISE_TOKEN="$enterprise_token"
44
+ fi
45
+ ;;
46
+ esac
47
+
48
+ validate_artifact_name() {
49
+ case "$1" in
50
+ ''|.|..|*/*|*\\*)
51
+ echo '[optional-run-artifacts] invalid artifact name' >&2
52
+ return 2
53
+ ;;
54
+ esac
55
+ }
56
+
57
+ download_exact() {
58
+ artifact="$1"
59
+ directory="$2"
60
+ validate_artifact_name "$artifact" || return $?
61
+ artifacts=$(list_artifact_names) || return $?
62
+ found=0
63
+ while IFS= read -r candidate; do
64
+ if [ "$candidate" = "$artifact" ]; then
65
+ found=1
66
+ break
67
+ fi
68
+ done < <(printf '%s\n' "$artifacts")
69
+ [ "$found" -eq 1 ] || return 3
70
+ echo "[optional-run-artifacts] attempt selector=$selector_type" >&2
71
+ GH_HOST="$github_host" gh run download "$GITHUB_RUN_ID" --repo "$repository" --name "$artifact" --dir "$directory"
72
+ }
73
+
74
+ list_artifact_names() {
75
+ GH_HOST="$github_host" gh api \
76
+ --paginate \
77
+ "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/artifacts?per_page=100" \
78
+ --jq '.artifacts[] | select(.expired | not) | .name'
79
+ }
80
+
81
+ download_pattern() {
82
+ artifacts=$(list_artifact_names | awk '!seen[$0]++') || return $?
83
+ selected_artifacts=()
84
+ while IFS= read -r artifact; do
85
+ [ -n "$artifact" ] || continue
86
+ # shellcheck disable=SC2254
87
+ # selector is the caller's intended glob pattern.
88
+ case "$artifact" in
89
+ $selector) ;;
90
+ *) continue ;;
91
+ esac
92
+ validate_artifact_name "$artifact" || return $?
93
+ selected_artifacts+=("$artifact")
94
+ echo "[optional-run-artifacts] selected artifact=$artifact" >&2
95
+ done < <(printf '%s\n' "$artifacts")
96
+ count=${#selected_artifacts[@]}
97
+ echo "[optional-run-artifacts] selection selector=pattern count=$count" >&2
98
+ [ "$count" -gt 0 ] || return 3
99
+ for artifact in "${selected_artifacts[@]}"; do
100
+ echo "[optional-run-artifacts] attempt artifact=$artifact" >&2
101
+ GH_HOST="$github_host" gh run download "$GITHUB_RUN_ID" --repo "$repository" --name "$artifact" --dir "$destination/$artifact" || return $?
102
+ done
103
+ }
104
+
105
+ if [ "$selector_type" = name ]; then
106
+ if download_exact "$selector" "$destination"; then status=0; else status=$?; fi
107
+ else
108
+ if download_pattern; then status=0; else status=$?; fi
109
+ fi
110
+ if [ "$status" -eq 0 ]; then
111
+ echo 'availability=available' >> "$GITHUB_OUTPUT"
112
+ echo "[optional-run-artifacts] result=available selector=$selector_type" >&2
113
+ else
114
+ case "$status" in
115
+ 130|143) exit "$status" ;;
116
+ 3)
117
+ echo 'availability=unavailable' >> "$GITHUB_OUTPUT"
118
+ echo "::warning::Optional same-run artifact unavailable; continuing with validated fallback (selector=$selector_type exit=$status)" >&2
119
+ echo "[optional-run-artifacts] result=unavailable selector=$selector_type exit=$status" >&2
120
+ ;;
121
+ *)
122
+ echo "[optional-run-artifacts] result=error selector=$selector_type exit=$status" >&2
123
+ exit "$status"
124
+ ;;
125
+ esac
126
+ fi
@@ -2,7 +2,7 @@
2
2
  set -euo pipefail
3
3
 
4
4
  usage() {
5
- echo "usage: install-github-release.sh --repo owner/name --version VERSION --asset TEMPLATE --bin NAME [--tag-prefix PREFIX] [--strip-components N] [--bin-dir DIR] [--version-flag FLAG] [--checksums-asset NAME] [--no-checksum]" >&2
5
+ echo "usage: install-github-release.sh --repo owner/name --version VERSION --asset TEMPLATE --bin NAME [--tag-prefix PREFIX] [--strip-components N] [--bin-dir DIR] [--version-flag FLAG] [--checksums-asset NAME] [--expected-sha256 SHA256] [--no-checksum]" >&2
6
6
  }
7
7
 
8
8
  REPO="${REPO:-}"
@@ -14,6 +14,7 @@ STRIP_COMPONENTS="${STRIP_COMPONENTS:-1}"
14
14
  BIN_DIR="${INSTALL_BIN_DIR:-}"
15
15
  VERSION_FLAG="${VERSION_FLAG:---version}"
16
16
  CHECKSUMS_ASSET="${CHECKSUMS_ASSET:-}"
17
+ EXPECTED_SHA256="${EXPECTED_SHA256:-}"
17
18
  NO_CHECKSUM="${NO_CHECKSUM:-0}"
18
19
 
19
20
  if [ -n "${RELEASE_OWNER:-}" ] && [ -n "${RELEASE_REPO:-}" ]; then
@@ -67,6 +68,11 @@ while [ "${#}" -gt 0 ]; do
67
68
  CHECKSUMS_ASSET="$2"
68
69
  shift 2
69
70
  ;;
71
+ --expected-sha256)
72
+ [ "${#}" -ge 2 ] || { usage; exit 2; }
73
+ EXPECTED_SHA256="$2"
74
+ shift 2
75
+ ;;
70
76
  --no-checksum)
71
77
  NO_CHECKSUM=1
72
78
  shift
@@ -97,6 +103,11 @@ if ! [[ "$STRIP_COMPONENTS" =~ ^[0-9]+$ ]]; then
97
103
  exit 2
98
104
  fi
99
105
 
106
+ if [ -n "$EXPECTED_SHA256" ] && ! [[ "$EXPECTED_SHA256" =~ ^[[:xdigit:]]{64}$ ]]; then
107
+ echo "install-github-release.sh: --expected-sha256 must be a 64-character hexadecimal SHA-256 digest" >&2
108
+ exit 2
109
+ fi
110
+
100
111
  BIN_DIR="${BIN_DIR:-${RUNNER_TEMP:-${HOME}/.local}/bin}"
101
112
  mkdir -p "$BIN_DIR"
102
113
  if [ -n "${GITHUB_PATH:-}" ]; then
@@ -150,10 +161,12 @@ extract_dir="${work}-extract"
150
161
  rm -rf "$extract_dir"
151
162
  mkdir -p "$extract_dir"
152
163
  curl -fsSL -o "$archive" "${base_url}/${asset_name}"
153
- if [ "$NO_CHECKSUM" != 1 ]; then
154
- if [ -n "$checksums_name" ]; then
164
+ if [ -n "$EXPECTED_SHA256" ] || [ "$NO_CHECKSUM" != 1 ]; then
165
+ if [ -n "$EXPECTED_SHA256" ]; then
166
+ expected="$EXPECTED_SHA256"
167
+ elif [ -n "$checksums_name" ]; then
155
168
  curl -fsSL -o "${archive}.checksums" "${base_url}/${checksums_name}"
156
- expected=$(grep -F "$asset_name" "${archive}.checksums" | awk '{print $1}')
169
+ expected=$(awk -v file="$asset_name" '$2 == file || $2 == "*" file { print $1 }' "${archive}.checksums")
157
170
  rm "${archive}.checksums"
158
171
  else
159
172
  curl -fsSL -o "${archive}.sha256" "${base_url}/${asset_name}.sha256"