vouchington-tooling 0.1.4 → 0.1.6
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 +36 -0
- package/dist/cli/index.mjs +4 -0
- package/dist/cli/parse.d.mts +1 -1
- package/dist/cli/parse.mjs +1 -0
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +3 -1
- package/dist/gha-post-review/post.mjs +2 -9
- package/dist/gha-review-payload/index.d.mts +1 -1
- package/dist/gha-review-payload/index.mjs +1 -1
- package/dist/gha-review-payload/payload.d.mts +1 -3
- package/dist/gha-review-payload/payload.mjs +15 -31
- package/dist/gha-review-payload/remap.d.mts +1 -1
- package/dist/gha-review-payload/remap.mjs +7 -16
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +2 -1
- package/dist/retrospective-transcript/codex-messages.d.mts +8 -0
- package/dist/retrospective-transcript/codex-messages.mjs +66 -0
- package/dist/retrospective-transcript/codex.mjs +9 -4
- package/dist/session-friction/ci-failures.d.mts +11 -0
- package/dist/session-friction/ci-failures.mjs +78 -0
- package/dist/session-friction/classify.d.mts +2 -0
- package/dist/session-friction/classify.mjs +87 -0
- package/dist/session-friction/directory.d.mts +1 -0
- package/dist/session-friction/directory.mjs +71 -0
- package/dist/session-friction/index.d.mts +6 -0
- package/dist/session-friction/index.mjs +5 -0
- package/dist/session-friction/lock.d.mts +1 -0
- package/dist/session-friction/lock.mjs +193 -0
- package/dist/session-friction/log.d.mts +8 -0
- package/dist/session-friction/log.mjs +195 -0
- package/dist/session-friction/normalize.d.mts +1 -0
- package/dist/session-friction/normalize.mjs +174 -0
- package/dist/session-friction/report.d.mts +2 -0
- package/dist/session-friction/report.mjs +113 -0
- package/dist/session-friction/sandbox.d.mts +2 -0
- package/dist/session-friction/sandbox.mjs +15 -0
- package/dist/session-friction/session-id.d.mts +2 -0
- package/dist/session-friction/session-id.mjs +17 -0
- package/dist/session-friction/text.d.mts +5 -0
- package/dist/session-friction/text.mjs +45 -0
- package/dist/session-friction/types.d.mts +52 -0
- package/dist/session-friction/types.mjs +1 -0
- package/dist/session-friction/utf8.d.mts +1 -0
- package/dist/session-friction/utf8.mjs +8 -0
- package/package.json +7 -2
- package/scripts/gha/download-optional-run-artifacts.sh +126 -0
- package/scripts/gha/install-github-release.sh +17 -4
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
|
|
@@ -128,8 +134,38 @@ import {
|
|
|
128
134
|
readDiagnosticReportSummaries,
|
|
129
135
|
} from 'vouchington-tooling/vitest-diagnostics'
|
|
130
136
|
import { runRetrospectiveTranscript } from 'vouchington-tooling/retrospective-transcript'
|
|
137
|
+
import { buildSessionFrictionReport, recordFriction } from 'vouchington-tooling/session-friction'
|
|
131
138
|
```
|
|
132
139
|
|
|
140
|
+
`session-friction` is an opt-in capture and reporting library. Callers supply the session id,
|
|
141
|
+
absolute log directory, host-independent observation, and journal loader; it does not inspect host
|
|
142
|
+
environment variables, install hooks, or connect to a journal service by itself. Invoking
|
|
143
|
+
`recordFriction` touches the session log even when no event is classified, preserving the
|
|
144
|
+
difference between an observed clean session and missing evidence. Report markdown keeps backend
|
|
145
|
+
diagnostics separate from its paste-safe output. Capture stores at most 500 events per session,
|
|
146
|
+
truncates event detail to 1,000 characters, and consumes up to 500 entries from the journal loader
|
|
147
|
+
when building a report, stopping earlier when its aggregate 1 MB inspected-byte budget is reached.
|
|
148
|
+
Bounded journal scans that stop before exhaustion are reported as incomplete rather than clean.
|
|
149
|
+
Report liveness inherits the caller-supplied journal loader, which must bound its own I/O and yields.
|
|
150
|
+
Log reads are capped at 2 MB, journal Markdown at 10,000 bytes per entry,
|
|
151
|
+
and rendered audit fields at 120 escaped characters. The supplied log directory must be dedicated
|
|
152
|
+
to session-friction; existing directories must already be owner-only, while newly created
|
|
153
|
+
directories and log files are enforced as owner-only when recording. Reads use a fixed bounded
|
|
154
|
+
buffer that can detect growth one byte beyond the documented 2 MB cap.
|
|
155
|
+
Ownership checks require POSIX effective-user IDs (Linux and macOS); session-friction throws on
|
|
156
|
+
Windows and other platforms where those IDs are unavailable.
|
|
157
|
+
Root-owned system symlink ancestors are supported for paths such as macOS `/var`; callers must not
|
|
158
|
+
allow the directory chain to be mutated while it is being validated.
|
|
159
|
+
Command-prefix normalization recognizes simple shell
|
|
160
|
+
segments with single or double quotes; it does not evaluate substitutions or implement a full shell
|
|
161
|
+
grammar. Normalization attempts limited redaction of obvious credential patterns but is not a secret
|
|
162
|
+
scrubber; callers must ensure credentials are never included in captured commands.
|
|
163
|
+
Failure classification inspects at most 100,000 structured-stderr characters, split evenly between
|
|
164
|
+
the beginning and end when input exceeds that bound.
|
|
165
|
+
Cooperating log readers and writers are serialized, including the initial clean-session
|
|
166
|
+
touch. Recording and report log reads are synchronous: on contention they block the caller's
|
|
167
|
+
event loop for up to one second before failing explicitly. Avoid these APIs on hot request paths.
|
|
168
|
+
|
|
133
169
|
The artifact, review-payload, HTTP body, and pagination APIs validate untrusted inputs at their
|
|
134
170
|
boundaries. Review posting lives in `gha-post-review` and talks to GitHub only through caller-supplied
|
|
135
171
|
credentials (job token or a minted Claude GitHub App token).
|
package/dist/cli/index.mjs
CHANGED
|
@@ -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',
|
package/dist/cli/parse.d.mts
CHANGED
|
@@ -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;
|
package/dist/cli/parse.mjs
CHANGED
package/dist/cli/usage.d.mts
CHANGED
|
@@ -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;
|
package/dist/cli/usage.mjs
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
82
|
+
if (!sanitized)
|
|
83
|
+
throw new ReviewPayloadError('Every finding must be a valid inline comment.');
|
|
84
|
+
return sanitized;
|
|
77
85
|
});
|
|
78
|
-
|
|
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:
|
|
92
|
-
comments
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { codexChildren, codexIdentity, computeTranscriptFacts, formatTranscriptFacts, formatUnavailable, resolveTranscriptFile, runRetrospectiveTranscript, } from './retrospective-transcript/index.mts';
|
|
2
2
|
export type { ResolveOptions, TokenTotals, TranscriptFacts, } from './retrospective-transcript/index.mts';
|
|
3
|
+
export { buildSessionFrictionReport, classifyFrictionObservation, FRICTION_LOG_MAX_EVENTS, isConformingCiFailureBlock, normalizeCommandPrefix, readFrictionLog, recordFriction, } from './session-friction/index.mts';
|
|
4
|
+
export type { FrictionEvent, FrictionEventKind, FrictionLogOptions, FrictionLogReadResult, FrictionObservation, JournalEntry, JournalLoader, JournalLoadResult, PermissionRequestObservation, SessionFrictionReport, SessionFrictionReportOptions, ToolResultObservation, } from './session-friction/index.mts';
|
|
3
5
|
export { EphemeralListenerAttemptsExhaustedError, isRunnerReservedPort, listenOnRunnerUnreservedEphemeralPort, loadRunnerPortPolicy, runnerPortPolicy, validateRunnerPortPolicy, } from './runner-port-policy/index.mts';
|
|
4
6
|
export type { EphemeralListenerOptions, RunnerPortPolicy } from './runner-port-policy/index.mts';
|
|
5
7
|
export { extractAlterTableAddColumnLocations, extractCreateIndexMetadata, extractCreateTableMetadata, extractDefaultFunction, extractDropIndexMetadata, extractFuncCallArgColumnNames, extractMigrationConstraintMetadata, initSqlAst, lineOfUtf8ByteOffset, MissingSqlAstParserError, parseSql, } from './sql-ast/index.mts';
|
|
@@ -35,7 +37,7 @@ export { MissingResponseBodyError, readResponseBody, readResponseBodyAsBuffer, R
|
|
|
35
37
|
export type { ReadResponseBodyOptions } from './http-body/index.mts';
|
|
36
38
|
export { parseAstGrepRuleArgs, runAstGrepRule } from './ast-grep-rule/index.mts';
|
|
37
39
|
export type { AstGrepRuleInvocation, RunAstGrepRuleOptions } from './ast-grep-rule/index.mts';
|
|
38
|
-
export {
|
|
40
|
+
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
41
|
export type { CommentableIndex, CommentableLine, LineKind, PayloadRequirement, ReviewComment, ReviewFile, ReviewSide, SanitizedReview, } from './gha-review-payload/index.mts';
|
|
40
42
|
export { CLAUDE_OIDC_AUDIENCE, createActionsClaudeTokenIo, mintClaudeAppToken, PostReviewError, requireEnv, resolveReviewPostToken, revokeClaudeAppToken, runPostReview, runPostReviewCli, withClaudeAppToken, } from './gha-post-review/index.mts';
|
|
41
43
|
export type { ClaudeTokenIo, PostResult, PostReviewIo, PullFile, ReviewPostToken, } from './gha-post-review/index.mts';
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable max-lines -- package entry point enumerates the supported public API. */
|
|
2
2
|
export { codexChildren, codexIdentity, computeTranscriptFacts, formatTranscriptFacts, formatUnavailable, resolveTranscriptFile, runRetrospectiveTranscript, } from './retrospective-transcript/index.mjs';
|
|
3
|
+
export { buildSessionFrictionReport, classifyFrictionObservation, FRICTION_LOG_MAX_EVENTS, isConformingCiFailureBlock, normalizeCommandPrefix, readFrictionLog, recordFriction, } from './session-friction/index.mjs';
|
|
3
4
|
export { EphemeralListenerAttemptsExhaustedError, isRunnerReservedPort, listenOnRunnerUnreservedEphemeralPort, loadRunnerPortPolicy, runnerPortPolicy, validateRunnerPortPolicy, } from './runner-port-policy/index.mjs';
|
|
4
5
|
export { extractAlterTableAddColumnLocations, extractCreateIndexMetadata, extractCreateTableMetadata, extractDefaultFunction, extractDropIndexMetadata, extractFuncCallArgColumnNames, extractMigrationConstraintMetadata, initSqlAst, lineOfUtf8ByteOffset, MissingSqlAstParserError, parseSql, } from './sql-ast/index.mjs';
|
|
5
6
|
export { dollarQuoteEnd, lineOf, maskSqlQuotedText, readDollarQuoteDelimiter, readStringLiteral, splitSqlStatements, sqlFragments, stripSqlComments, } from './sql-scanner/index.mjs';
|
|
@@ -20,7 +21,7 @@ export { decide, deriveRetryAttempt } from './transient-retry/index.mjs';
|
|
|
20
21
|
export { escapeSpreadsheetFormula, parseCsvRows, streamCsvRows, stripCsvBom } from './csv/index.mjs';
|
|
21
22
|
export { MissingResponseBodyError, readResponseBody, readResponseBodyAsBuffer, ResponseBodyTooLargeError, } from './http-body/index.mjs';
|
|
22
23
|
export { parseAstGrepRuleArgs, runAstGrepRule } from './ast-grep-rule/index.mjs';
|
|
23
|
-
export {
|
|
24
|
+
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
25
|
export { CLAUDE_OIDC_AUDIENCE, createActionsClaudeTokenIo, mintClaudeAppToken, PostReviewError, requireEnv, resolveReviewPostToken, revokeClaudeAppToken, runPostReview, runPostReviewCli, withClaudeAppToken, } from './gha-post-review/index.mjs';
|
|
25
26
|
export { nextPageCursorFromLinkHeader, nextPageUrlFromLinkHeader, validatePaginationRequestUrl, } from './http-link-pagination/index.mjs';
|
|
26
27
|
export { cmdDownloadCoverage, cmdDownloadVitestBlobs, cmdUpload, mintPresignedControl, transportObjectKeys, } from './coverage-transport/index.mjs';
|
|
@@ -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
|
|
91
|
-
|
|
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') {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FrictionLogReadResult, JournalEntry } from './types.mts';
|
|
2
|
+
export declare function isConformingCiFailureBlock(markdown: string): boolean;
|
|
3
|
+
export declare function getConformingGroups(entries: Iterable<JournalEntry>): string[];
|
|
4
|
+
export declare function buildCiFailuresSection(sessionId: string, journal: {
|
|
5
|
+
status: 'ok';
|
|
6
|
+
markdownBlocks: string[];
|
|
7
|
+
truncated: boolean;
|
|
8
|
+
} | {
|
|
9
|
+
status: 'unreachable';
|
|
10
|
+
diagnostic: string;
|
|
11
|
+
}, frictionStatus: FrictionLogReadResult['status']): string;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { isWellFormedUnicode, markdownAuditText } from './text.mjs';
|
|
2
|
+
const GROUP_HEADER = /^- `(recurring|one-off)` — `GitHub Actions` — .*[^\s]$/;
|
|
3
|
+
const EVIDENCE = /^ {2}- Evidence: .*[^\s]$/;
|
|
4
|
+
const ROOT_DIAGNOSTIC = /^ {2}- Root diagnostic: .*[^\s]$/;
|
|
5
|
+
const DISPOSITION = /^ {2}- Disposition: .*[^\s]$/;
|
|
6
|
+
const BLANK = /^\s*$/;
|
|
7
|
+
const CI_FAILURES_HEADER = '## CI Failures';
|
|
8
|
+
const CI_FAILURE_BLOCK_MAX_BYTES = 10_000;
|
|
9
|
+
const FIELD_PREFIXES = [
|
|
10
|
+
'- `recurring` — `GitHub Actions` — ',
|
|
11
|
+
'- `one-off` — `GitHub Actions` — ',
|
|
12
|
+
' - Evidence: ',
|
|
13
|
+
' - Root diagnostic: ',
|
|
14
|
+
' - Disposition: ',
|
|
15
|
+
];
|
|
16
|
+
function safeField(line) {
|
|
17
|
+
const prefix = FIELD_PREFIXES.find((value) => line.startsWith(value));
|
|
18
|
+
/* v8 ignore next -- matchBlock currently passes only fields with a known prefix. */
|
|
19
|
+
if (!prefix)
|
|
20
|
+
return markdownAuditText(line) || null;
|
|
21
|
+
const content = markdownAuditText(line.slice(prefix.length));
|
|
22
|
+
return content ? `${prefix}${content}` : null;
|
|
23
|
+
}
|
|
24
|
+
function matchBlock(markdown) {
|
|
25
|
+
if (!isWellFormedUnicode(markdown))
|
|
26
|
+
return null;
|
|
27
|
+
const lines = markdown.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
|
|
28
|
+
let index = 0;
|
|
29
|
+
const consume = (pattern) => {
|
|
30
|
+
while (index < lines.length && BLANK.test(lines[index]))
|
|
31
|
+
index++;
|
|
32
|
+
const line = lines[index];
|
|
33
|
+
if (line === undefined || !pattern.test(line))
|
|
34
|
+
return null;
|
|
35
|
+
index++;
|
|
36
|
+
return line;
|
|
37
|
+
};
|
|
38
|
+
const fields = [
|
|
39
|
+
consume(GROUP_HEADER),
|
|
40
|
+
consume(EVIDENCE),
|
|
41
|
+
consume(ROOT_DIAGNOSTIC),
|
|
42
|
+
consume(DISPOSITION),
|
|
43
|
+
];
|
|
44
|
+
if (fields.some((field) => field === null))
|
|
45
|
+
return null;
|
|
46
|
+
if (!lines.slice(index).every((line) => BLANK.test(line)))
|
|
47
|
+
return null;
|
|
48
|
+
const safeFields = fields.map((field) => safeField(field));
|
|
49
|
+
if (safeFields.some((field) => field === null))
|
|
50
|
+
return null;
|
|
51
|
+
return safeFields.join('\n');
|
|
52
|
+
}
|
|
53
|
+
export function isConformingCiFailureBlock(markdown) {
|
|
54
|
+
if (markdown.length > CI_FAILURE_BLOCK_MAX_BYTES ||
|
|
55
|
+
Buffer.byteLength(markdown) > CI_FAILURE_BLOCK_MAX_BYTES)
|
|
56
|
+
return false;
|
|
57
|
+
return matchBlock(markdown) !== null;
|
|
58
|
+
}
|
|
59
|
+
function journalMarkdown(entries) {
|
|
60
|
+
return [...entries].flatMap((entry) => {
|
|
61
|
+
const data = entry?.data;
|
|
62
|
+
return data?.type === 'journal' && typeof data.markdown === 'string' ? [data.markdown] : [];
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export function getConformingGroups(entries) {
|
|
66
|
+
return journalMarkdown(entries)
|
|
67
|
+
.map(matchBlock)
|
|
68
|
+
.filter((block) => block !== null);
|
|
69
|
+
}
|
|
70
|
+
export function buildCiFailuresSection(sessionId, journal, frictionStatus) {
|
|
71
|
+
if (journal.status === 'unreachable')
|
|
72
|
+
return `${CI_FAILURES_HEADER}\nStatus: unavailable (blackboard unreachable)`;
|
|
73
|
+
if (journal.markdownBlocks.length === 0 && frictionStatus === 'absent')
|
|
74
|
+
return `${CI_FAILURES_HEADER}\nStatus: unavailable (no friction log for session ${markdownAuditText(sessionId)})`;
|
|
75
|
+
if (journal.markdownBlocks.length === 0)
|
|
76
|
+
return `${CI_FAILURES_HEADER}\nStatus: none observed`;
|
|
77
|
+
return `${CI_FAILURES_HEADER}\nStatus: failures observed\n\n${journal.markdownBlocks.join('\n\n')}`;
|
|
78
|
+
}
|