vouchington-tooling 0.44.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -52
- package/dist/agent-blackboard/client.d.mts +6 -0
- package/dist/agent-blackboard/feedback-audit-source.d.mts +4 -10
- package/dist/agent-blackboard/feedback-audit-source.mjs +7 -18
- package/dist/agent-blackboard/feedback-compose-audit.mjs +6 -14
- package/dist/agent-blackboard/feedback-compose.d.mts +1 -4
- package/dist/agent-blackboard/feedback-compose.mjs +2 -5
- package/dist/agent-blackboard/feedback-fields.mjs +1 -1
- package/dist/agent-blackboard/feedback-identity.mjs +1 -1
- package/dist/agent-blackboard/feedback-journal-audit.d.mts +6 -8
- package/dist/agent-blackboard/feedback-journal-audit.mjs +9 -10
- package/dist/agent-blackboard/feedback-journal-sandbox.d.mts +2 -2
- package/dist/agent-blackboard/feedback-journal-sandbox.mjs +2 -2
- package/dist/agent-blackboard/feedback-outbox-rejected.mjs +1 -1
- package/dist/agent-blackboard/feedback-outbox.mjs +2 -2
- package/dist/agent-blackboard/index.d.mts +1 -1
- package/dist/audit-journal/ci-failures.d.mts +6 -0
- package/dist/{session-friction → audit-journal}/ci-failures.mjs +3 -9
- package/dist/{session-friction → audit-journal}/journal.d.mts +0 -1
- package/dist/{session-friction → audit-journal}/journal.mjs +1 -1
- package/dist/audit-journal/session-id.d.mts +1 -0
- package/dist/{session-friction → audit-journal}/session-id.mjs +0 -5
- package/dist/{session-friction → audit-journal}/text.d.mts +1 -4
- package/dist/{session-friction → audit-journal}/text.mjs +4 -22
- package/dist/audit-journal/types.d.mts +17 -0
- package/dist/cli/commands/gha-runtime-audit.mjs +2 -0
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +1 -1
- package/dist/gh-cli/env.d.mts +9 -0
- package/dist/gh-cli/env.mjs +12 -0
- package/dist/gh-cli/exec.d.mts +7 -4
- package/dist/gh-cli/exec.mjs +7 -5
- package/dist/gh-cli/index.d.mts +1 -0
- package/dist/gh-cli/index.mjs +1 -0
- package/dist/gha-post-review/github.mjs +2 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/mcp-server/context.d.mts +5 -0
- package/dist/mcp-server/dispatch.d.mts +5 -0
- package/dist/mcp-server/dispatch.mjs +8 -1
- package/dist/mcp-server/fake-blackboard.test-helpers.d.mts +2 -0
- package/dist/mcp-server/fake-blackboard.test-helpers.mjs +3 -0
- package/dist/mcp-server/handlers-pr.d.mts +1 -1
- package/dist/mcp-server/handlers-pr.mjs +6 -97
- package/dist/mcp-server/handlers-retrospective-save.d.mts +10 -0
- package/dist/mcp-server/handlers-retrospective-save.mjs +138 -0
- package/dist/mcp-server/handlers-retrospective.d.mts +1 -12
- package/dist/mcp-server/handlers-retrospective.mjs +1 -154
- package/dist/mcp-server/handlers-session.mjs +25 -6
- package/dist/mcp-server/handlers-snapshot-cleanup.d.mts +7 -0
- package/dist/mcp-server/handlers-snapshot-cleanup.mjs +36 -0
- package/dist/mcp-server/harness.test-helpers.d.mts +3 -0
- package/dist/mcp-server/harness.test-helpers.mjs +2 -0
- package/dist/mcp-server/policy-run.d.mts +1 -0
- package/dist/mcp-server/policy-run.mjs +1 -0
- package/dist/mcp-server/pr-agent.d.mts +12 -0
- package/dist/mcp-server/pr-agent.mjs +34 -0
- package/dist/mcp-server/pr-body.d.mts +6 -2
- package/dist/mcp-server/pr-body.mjs +16 -5
- package/dist/mcp-server/pr-defaults.d.mts +3 -0
- package/dist/mcp-server/pr-defaults.mjs +7 -2
- package/dist/mcp-server/pr-gh.mjs +2 -1
- package/dist/mcp-server/pr-input.d.mts +14 -6
- package/dist/mcp-server/pr-input.mjs +22 -5
- package/dist/mcp-server/pr-live.d.mts +4 -1
- package/dist/mcp-server/pr-live.mjs +8 -3
- package/dist/mcp-server/pr-mode.d.mts +7 -0
- package/dist/mcp-server/pr-mode.mjs +34 -0
- package/dist/mcp-server/pr-no-source.d.mts +9 -0
- package/dist/mcp-server/pr-no-source.mjs +22 -0
- package/dist/mcp-server/pr-prepare.d.mts +25 -0
- package/dist/mcp-server/pr-prepare.mjs +124 -0
- package/dist/mcp-server/preview-registry.d.mts +25 -0
- package/dist/mcp-server/preview-registry.mjs +38 -0
- package/dist/mcp-server/retrospective-check.d.mts +3 -0
- package/dist/mcp-server/retrospective-check.mjs +38 -0
- package/dist/mcp-server/retrospective-input.mjs +2 -2
- package/dist/mcp-server/retrospective-preview.d.mts +17 -0
- package/dist/mcp-server/retrospective-preview.mjs +60 -0
- package/dist/mcp-server/server.mjs +1 -0
- package/dist/mcp-server/snapshot-registry.d.mts +25 -0
- package/dist/mcp-server/snapshot-registry.mjs +32 -0
- package/dist/mcp-server/tools-pr.mjs +28 -16
- package/dist/mcp-server/tools-retrospective.mjs +25 -5
- package/dist/mcp-server/tools.mjs +6 -0
- package/dist/{session-friction → private-files}/lock.mjs +3 -2
- package/dist/private-files/log-file.d.mts +3 -0
- package/dist/private-files/log-file.mjs +53 -0
- package/dist/{session-friction → private-files}/utf8.mjs +1 -1
- package/dist/retrospective-facts/exec.mjs +2 -1
- package/docs/mcp-server.md +112 -26
- package/package.json +3 -8
- package/skills/retrospective/SKILL.md +3 -1
- package/skills/retrospective-distill/SKILL.md +5 -0
- package/dist/session-friction/ci-failures.d.mts +0 -13
- package/dist/session-friction/classify.d.mts +0 -2
- package/dist/session-friction/classify.mjs +0 -98
- package/dist/session-friction/command-segments.d.mts +0 -3
- package/dist/session-friction/command-segments.mjs +0 -94
- package/dist/session-friction/coverage-ledger.d.mts +0 -10
- package/dist/session-friction/coverage-ledger.mjs +0 -75
- package/dist/session-friction/directory.d.mts +0 -1
- package/dist/session-friction/directory.mjs +0 -71
- package/dist/session-friction/index.d.mts +0 -6
- package/dist/session-friction/index.mjs +0 -5
- package/dist/session-friction/log-file.d.mts +0 -7
- package/dist/session-friction/log-file.mjs +0 -95
- package/dist/session-friction/log.d.mts +0 -8
- package/dist/session-friction/log.mjs +0 -163
- package/dist/session-friction/normalize.d.mts +0 -1
- package/dist/session-friction/normalize.mjs +0 -81
- package/dist/session-friction/report.d.mts +0 -16
- package/dist/session-friction/report.mjs +0 -91
- package/dist/session-friction/sandbox.d.mts +0 -3
- package/dist/session-friction/sandbox.mjs +0 -43
- package/dist/session-friction/session-id.d.mts +0 -2
- package/dist/session-friction/types.d.mts +0 -80
- /package/dist/{session-friction → audit-journal}/audit-block.d.mts +0 -0
- /package/dist/{session-friction → audit-journal}/audit-block.mjs +0 -0
- /package/dist/{session-friction → audit-journal}/audit-fit.d.mts +0 -0
- /package/dist/{session-friction → audit-journal}/audit-fit.mjs +0 -0
- /package/dist/{session-friction → audit-journal}/types.mjs +0 -0
- /package/dist/{session-friction → private-files}/lock.d.mts +0 -0
- /package/dist/{session-friction → private-files}/utf8.d.mts +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { closeSync, constants, fstatSync, openSync, readSync, writeSync } from 'node:fs';
|
|
2
|
+
import { decodeUtf8 } from "./utf8.mjs";
|
|
3
|
+
export function openLogFile(path, flags) {
|
|
4
|
+
let descriptor;
|
|
5
|
+
try {
|
|
6
|
+
descriptor = openSync(path, flags | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600);
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
if (error &&
|
|
10
|
+
typeof error === 'object' &&
|
|
11
|
+
'code' in error &&
|
|
12
|
+
(error.code === 'ELOOP' || error.code === 'EISDIR' || error.code === 'ENOTDIR'))
|
|
13
|
+
throw new Error('log file must be a regular file');
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const status = fstatSync(descriptor);
|
|
18
|
+
if (!status.isFile() || status.nlink !== 1)
|
|
19
|
+
throw new Error('log file must be a regular file');
|
|
20
|
+
return descriptor;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
closeSync(descriptor);
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function readLogContent(descriptor, maximumBytes) {
|
|
28
|
+
if (fstatSync(descriptor).size > maximumBytes)
|
|
29
|
+
throw new Error('log file is too large');
|
|
30
|
+
const buffer = Buffer.alloc(maximumBytes + 1);
|
|
31
|
+
let length = 0;
|
|
32
|
+
while (length < buffer.length) {
|
|
33
|
+
const bytes = readSync(descriptor, buffer, length, buffer.length - length, length);
|
|
34
|
+
if (bytes === 0)
|
|
35
|
+
break;
|
|
36
|
+
length += bytes;
|
|
37
|
+
}
|
|
38
|
+
/* v8 ignore next -- detects external growth after the descriptor size check. */
|
|
39
|
+
if (length > maximumBytes)
|
|
40
|
+
throw new Error('log file is too large');
|
|
41
|
+
return decodeUtf8(buffer.subarray(0, length));
|
|
42
|
+
}
|
|
43
|
+
export function writeAll(descriptor, value) {
|
|
44
|
+
const buffer = Buffer.from(value);
|
|
45
|
+
let offset = 0;
|
|
46
|
+
while (offset < buffer.length) {
|
|
47
|
+
const written = writeSync(descriptor, buffer, offset, buffer.length - offset);
|
|
48
|
+
/* v8 ignore next -- a zero-byte synchronous file write is an external I/O failure. */
|
|
49
|
+
if (written === 0)
|
|
50
|
+
throw new Error('log file write made no progress');
|
|
51
|
+
offset += written;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { ghSpawnEnv } from "../gh-cli/env.mjs";
|
|
2
3
|
export const shell = (command, args) => new Promise((resolve) => {
|
|
3
|
-
const child = spawn(command, args);
|
|
4
|
+
const child = command === 'gh' ? spawn(command, args, { env: ghSpawnEnv() }) : spawn(command, args);
|
|
4
5
|
let stdout = '';
|
|
5
6
|
let stderr = '';
|
|
6
7
|
child.stdout.setEncoding('utf8');
|
package/docs/mcp-server.md
CHANGED
|
@@ -82,17 +82,18 @@ Every tool takes an explicit `sessionId` (`^[A-Za-z0-9._:-]+$`, at most 256 char
|
|
|
82
82
|
server never infers, and an optional `worktree`. Unknown arguments are rejected. Failures come back
|
|
83
83
|
as MCP tool errors (`isError: true`) with an actionable message rather than crashing the server.
|
|
84
84
|
|
|
85
|
-
| Tool | Arguments besides `sessionId` and `worktree`
|
|
86
|
-
| --------------------- |
|
|
87
|
-
| `journal_append` | `parentSessionId`, `agent`, `version`, `mode` (`interactive` or `autonomous`), `markdown`, `sourceEventId`, `workOutcome`, `repositories`, `feedbackCoverage`, optional `category`
|
|
88
|
-
| `journal_entries` | none
|
|
89
|
-
| `outbox_status` | none
|
|
90
|
-
| `outbox_flush` | none
|
|
91
|
-
| `session_ensure` | `parentSessionId`, `agent`, `version`
|
|
92
|
-
| `retrospective_check` | optional `sourceEventId`
|
|
93
|
-
| `retrospective_save` | `parentSessionId`, `agent`, `version`, `mode`, `workOutcome`, `feedbackCoverage`, exactly one of `markdown` or `compose`, optional `repositories`, `sourceEventId`, `category`, `dryRun` | as `journal_append` plus `policy.warnings`; with `dryRun`, `markdown` and `policy.warnings`
|
|
94
|
-
| `snapshot_export` | optional `agent`, `version`, `parentSessionId`, `data`, `dataArrayContains`, `inactiveForHours`
|
|
95
|
-
| `
|
|
85
|
+
| Tool | Arguments besides `sessionId` and `worktree` | Returns |
|
|
86
|
+
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
87
|
+
| `journal_append` | `parentSessionId`, `agent`, `version`, `mode` (`interactive` or `autonomous`), `markdown`, `sourceEventId`, `workOutcome`, `repositories`, `feedbackCoverage`, optional `category` | `sessionId`, `timestamp`, delivery `status` (`delivered` with a verified receipt, or `pending`), `pendingCount`, `worktreePendingCount` |
|
|
88
|
+
| `journal_entries` | none | `sessionId` and `entries`, every entry oldest first |
|
|
89
|
+
| `outbox_status` | none | `sessionId`, `status`, `pendingCount`, `worktreePendingCount`, `rejectedCount`, `worktreeRejectedCount` |
|
|
90
|
+
| `outbox_flush` | none | `sessionId`, `status`, `pendingCount`, `worktreePendingCount`, `rejectedCount`, `worktreeRejectedCount`, `deliveredCount`, `rejected` (when any) |
|
|
91
|
+
| `session_ensure` | `parentSessionId`, `agent`, `version` | `sessionId`, `status` (`created` or `exists`), `archived` |
|
|
92
|
+
| `retrospective_check` | optional `sourceEventId` | `sessionId`, `saved`, `entries` (`sourceEventId`, `timestamp`, `issues`, `prs` of each retrospective, oldest first) |
|
|
93
|
+
| `retrospective_save` | `parentSessionId`, `agent`, `version`, `mode`, `workOutcome`, `feedbackCoverage`, exactly one of `markdown` or `compose`, optional `repositories`, `sourceEventId`, `category`, `dryRun`, `knownSensitiveValues` (markdown only); or `previewToken` with identity, `mode` and `sourceEventId` | as `journal_append` plus `policy.warnings`; with `dryRun`, `markdown`, `previewToken` and `policy.warnings` |
|
|
94
|
+
| `snapshot_export` | optional `agent`, `version`, `parentSessionId`, `data`, `dataArrayContains`, `inactiveForHours` | `sessionId`, `path`, `counts`, `checksum`, `manifest`, `cleanupToken` |
|
|
95
|
+
| `snapshot_cleanup` | `cleanupToken` | `sessionId`, `cleaned: true` |
|
|
96
|
+
| `session_archive` | none | `sessionId`, `archived: true` |
|
|
96
97
|
|
|
97
98
|
`journal_append` validates the whole feedback envelope with `validateFeedbackEnvelope` before
|
|
98
99
|
anything is written, so a missing required field writes nothing. It returns the verified read-back
|
|
@@ -146,6 +147,35 @@ outbox does not create the directory. `snapshot_export` never accepts a destinat
|
|
|
146
147
|
`agent-blackboard` client chooses a private file and returns its path. `session_archive` is the one
|
|
147
148
|
destructive tool; archived metadata is immutable, while entries stay appendable.
|
|
148
149
|
|
|
150
|
+
### Snapshot cleanup
|
|
151
|
+
|
|
152
|
+
`snapshot_export` writes a private file in the OS temp directory, and `snapshot_cleanup` is the only
|
|
153
|
+
way to delete it through the server. It was chosen over cleanup inside `session_archive` because
|
|
154
|
+
the exporting session and the archived sessions differ during distillation, and cleanup must run
|
|
155
|
+
whether or not archiving happened. Call it as a finally step after processing, on success and on
|
|
156
|
+
failure.
|
|
157
|
+
|
|
158
|
+
- **No caller-chosen path.** The tool takes only `cleanupToken` (`path`, `directory`, and `receipt`
|
|
159
|
+
are unsupported arguments). The server records the export's file path itself, plus a partition
|
|
160
|
+
directory and its signed receipt when the client reports both, and cleans exactly that record.
|
|
161
|
+
When the client's export carried its own cleanup capability, the server keeps it (never
|
|
162
|
+
returning it) and cleans through the client's `cleanupSnapshotPartitions`, so the client's
|
|
163
|
+
ownership marker goes with the file; otherwise it uses the in-tree `cleanupSnapshotPartitions`.
|
|
164
|
+
- **Server-minted, bound token.** `snapshot_export` replaces the client's `cleanupToken` with 32
|
|
165
|
+
random bytes the server generates, recorded with the caller's `sessionId` and resolved `worktree`.
|
|
166
|
+
The binding is checked before any filesystem call.
|
|
167
|
+
- **Single use.** A successful cleanup spends the token; a repeat fails as unknown and deletes
|
|
168
|
+
nothing. The record is taken synchronously, so concurrent calls cannot both clean.
|
|
169
|
+
- **Failure modes.** An unknown, forged (including a path used as a token), spent, other-session, or
|
|
170
|
+
other-worktree token fails with one message and deletes nothing; a mismatched caller does not spend
|
|
171
|
+
the owner's token. If the cleanup itself fails, the token stays valid and the error carries the
|
|
172
|
+
reason, including the retained tombstone path when a partition directory was half removed, so the
|
|
173
|
+
same call can retry. A file that is already gone counts as cleaned.
|
|
174
|
+
- **Server restart.** Bindings are in memory only. After a restart every earlier token is unknown, so
|
|
175
|
+
the file is left for the operating system to reap from its temp directory; run the distill pass
|
|
176
|
+
again to export a fresh snapshot. The registry also remembers at most 256 exports and forgets the
|
|
177
|
+
oldest first.
|
|
178
|
+
|
|
149
179
|
The outbox lives at `<worktree>/.local/blackboard-outbox` and is used only in interactive mode.
|
|
150
180
|
Autonomous mode forbids a filesystem outbox, so a failed delivery is an error.
|
|
151
181
|
|
|
@@ -168,7 +198,9 @@ instructions shows it to the agent.
|
|
|
168
198
|
## Retrospective tools and the policy contract
|
|
169
199
|
|
|
170
200
|
`retrospective_check` reads the session through the same client as `journal_entries` and reports the
|
|
171
|
-
entries whose envelope `type` is `retrospective`. A
|
|
201
|
+
entries whose envelope `type` is `retrospective`. A schema-versioned entry counts only when its
|
|
202
|
+
envelope passes `validateFeedbackEnvelope`; a malformed one is not saved, while an unversioned legacy
|
|
203
|
+
entry is still accepted. A session that was never created (the blackboard
|
|
172
204
|
answers 404) reports `saved: false`.
|
|
173
205
|
|
|
174
206
|
`retrospective_save` builds a `retrospective` envelope and writes it exactly as `journal_append`
|
|
@@ -189,7 +221,20 @@ are killed after 120 seconds. `compose.facts` takes `pr`, `branch`, `noPr`, or `
|
|
|
189
221
|
worktree's. Facts run `git` and `gh` in the worktree as the server's user, honoring its
|
|
190
222
|
repository configuration (which can select helpers that run code), so the worktree, including
|
|
191
223
|
`.git/config`, must be as trusted as the policy module. `dryRun`
|
|
192
|
-
runs every check and returns the document without writing
|
|
224
|
+
runs every check and returns the document without writing, plus a `previewToken`.
|
|
225
|
+
|
|
226
|
+
`knownSensitiveValues` (each at least 4 characters) is also accepted beside `markdown`, and
|
|
227
|
+
redacts the markdown, `category` and `feedbackCoverage` just as `compose.knownSensitiveValues`
|
|
228
|
+
does; with `compose`, pass it inside `compose`. The `previewToken` is 32 random bytes (base64url),
|
|
229
|
+
opaque, single use, and held only in server memory: a restart drops every token, and the registry
|
|
230
|
+
keeps the 32 most recent. It is bound to the `sessionId`, `parentSessionId`, `mode`, the resolved worktree, the `sourceEventId`
|
|
231
|
+
and the exact redacted content. Save it by calling `retrospective_save` again with the identity,
|
|
232
|
+
the same `parentSessionId`, `mode`, `sourceEventId` and `previewToken`, and none of `markdown`, `compose`,
|
|
233
|
+
`workOutcome`, `feedbackCoverage`, `repositories`, `category`, `knownSensitiveValues` or `dryRun`.
|
|
234
|
+
The server stores exactly the previewed content (so a sensitive value never has to be sent
|
|
235
|
+
again), mints a new timestamp, and does not run the policy again. An unknown, spent, restarted or
|
|
236
|
+
mismatched token is one error and the dry run must be repeated; a mismatch does not spend the
|
|
237
|
+
token, and neither does a failed write.
|
|
193
238
|
|
|
194
239
|
The document must open with the front matter `composeRetrospective` writes: lines of
|
|
195
240
|
`key: <JSON value>` between two `---` lines. Recognized keys are `date`, `issues`, `prs` (required),
|
|
@@ -229,7 +274,7 @@ whole process group is killed, so nothing it started outlives the call. The runn
|
|
|
229
274
|
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ------- |
|
|
230
275
|
| `validateRetrospective` | `sessionId`, `parentSessionId`, `mode`, `workOutcome`, `feedbackCoverage`, `repositories`, `markdown`, `sourceEventId`, `metadata` (`date`, `issues`, `prs`, `description`) | `{ errors: string[], warnings?: string[] }` | 30 s |
|
|
231
276
|
| `pullRequestDefaults` | `{}` from `retrospective_save`; `{ title, base, relatedIssues }` from `pr_create` | `{ repositories?, labels?, milestone?, project? }` (an object) | 120 s |
|
|
232
|
-
| `validatePullRequest` | `title`, `body`, `base`, `head`, `mode`, and `pr`
|
|
277
|
+
| `validatePullRequest` | `title`, `body`, `base`, `head`, the trusted `mode`, `noSourceIssue` when given, and `pr` when set | `{ errors: string[], warnings?: string[] }` | 120 s |
|
|
233
278
|
|
|
234
279
|
Any `errors` reject the save and write nothing. A non-zero exit, a timeout (reported as
|
|
235
280
|
`policy <export> timed out after <N>s`), output over 1 MB, malformed JSON, or a result of the wrong
|
|
@@ -241,9 +286,45 @@ shape is an error, never a pass.
|
|
|
241
286
|
retrospective tools. All three take `sessionId` (required) and `worktree` (optional), need a server
|
|
242
287
|
launched inside a repository worktree (they run the checkout policy), and share this structured
|
|
243
288
|
input: `title` (single line, at most 256 characters), `summary`, `impact`, `relatedIssues`
|
|
244
|
-
(`[{ number, relation: "closes" | "refs", reason }]`, may be empty), `
|
|
245
|
-
`
|
|
246
|
-
`
|
|
289
|
+
(`[{ number, relation: "closes" | "refs", reason }]`, may be empty), optional `noSourceIssue`
|
|
290
|
+
(`{ reason }`, see below), `details` (string array), `base` (default `main`), optional `agent`, optional
|
|
291
|
+
`model`, optional `mode` (`interactive` | `autonomous`) and `workspaceSetup`. `pr_update` also
|
|
292
|
+
requires `pr`; `pr_validate` accepts an optional `pr`.
|
|
293
|
+
|
|
294
|
+
### Trusted mode
|
|
295
|
+
|
|
296
|
+
The mode the policy sees is derived by the server from its own launch environment, never from the
|
|
297
|
+
caller. It fails closed: when any automation marker is set to something other than an empty
|
|
298
|
+
string, `0` or `false` (`CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI`,
|
|
299
|
+
`JENKINS_URL`, `TF_BUILD`, `CODEBUILD_BUILD_ID`, `VOUCHINGTON_AUTOMATION`), the mode is
|
|
300
|
+
`autonomous` whatever the caller passes. `CLAUDE_CODE_SESSION_ATTENDED` set to exactly `0` (Claude
|
|
301
|
+
Code's marker for an unattended `claude -p` run, inherited by MCP servers) also means `autonomous`;
|
|
302
|
+
a missing value or `1` never does. This list is a superset of the downstream check it replaces,
|
|
303
|
+
which was exactly `GITHUB_ACTIONS === 'true' || CI === 'true'`. Otherwise the mode is `interactive`. The optional `mode`
|
|
304
|
+
argument can only narrow `interactive` to `autonomous` (it never widens), so an autonomous run
|
|
305
|
+
cannot unlock an interactive-only policy exception. The policy child receives no env markers, so it
|
|
306
|
+
relies on this value.
|
|
307
|
+
|
|
308
|
+
### Agent provenance
|
|
309
|
+
|
|
310
|
+
The `Agent:` line is derived by the server, never taken from the caller. The agent is the harness
|
|
311
|
+
that launched the server: `CODEX_THREAD_ID` (codex), `CLAUDECODE=1` or `CLAUDE_CODE_SESSION_ID`
|
|
312
|
+
(claude), `CURSOR_AGENT` or `CURSOR_SESSION_ID` (cursor), `GROK_AGENT`, `GROK_HOOK_EVENT` or
|
|
313
|
+
`GROK_SESSION_ID` (grok), in that order; failing those, the MCP client's `initialize`
|
|
314
|
+
`clientInfo.name` when it contains one of those names. The server has no source for the model.
|
|
315
|
+
`agent` is optional: a value naming the derived agent is ignored, any other value is rendered as
|
|
316
|
+
`(caller-claimed agent: <value>)`, and `model` as `(caller-claimed model: <value>)`. When no agent
|
|
317
|
+
is derivable the line says `unknown`. Making `agent` optional is compatible with existing callers,
|
|
318
|
+
which may keep sending it.
|
|
319
|
+
|
|
320
|
+
### Pull requests with no source issue
|
|
321
|
+
|
|
322
|
+
Pass `noSourceIssue: { reason }` (a single line) with an empty `relatedIssues`; a non-empty
|
|
323
|
+
`relatedIssues` beside it is rejected. The Related issues section then holds the generic statement
|
|
324
|
+
`- No source issue — <reason>` instead of `- None`. A policy may replace the statement by
|
|
325
|
+
exporting `renderNoSourceIssue({ reason })`, returning a non-empty string (30 s). The field also
|
|
326
|
+
reaches `validatePullRequest` (with the trusted `mode`, so the policy can apply an interactive-only
|
|
327
|
+
rule) and `pullRequestDefaults`.
|
|
247
328
|
|
|
248
329
|
### Rendering
|
|
249
330
|
|
|
@@ -258,35 +339,40 @@ input: `title` (single line, at most 256 characters), `summary`, `impact`, `rela
|
|
|
258
339
|
|
|
259
340
|
## Related issues
|
|
260
341
|
|
|
261
|
-
- Closes #N — <reason> (or "Refs #N"; "- None" when empty
|
|
342
|
+
- Closes #N — <reason> (or "Refs #N"; "- None" when empty;
|
|
343
|
+
"- No source issue — <reason>" for noSourceIssue)
|
|
262
344
|
|
|
263
345
|
## Details
|
|
264
346
|
|
|
265
347
|
<details joined by blank lines>
|
|
266
348
|
|
|
267
349
|
Workspace setup: <workspaceSetup>
|
|
268
|
-
Agent: <agent>[ (
|
|
350
|
+
Agent: <derived agent | unknown>[ (caller-claimed ...)] session <sessionId>
|
|
269
351
|
Device: <os user>@<lowercased hostname>
|
|
270
352
|
Worktree: <path after "/worktrees/", "main" for the main worktree, else the directory name>
|
|
271
353
|
```
|
|
272
354
|
|
|
273
|
-
The head branch is the worktree's current branch
|
|
274
|
-
rejected. The body must stay within GitHub's 65536 characters. `pr_create` sees no stack membership,
|
|
355
|
+
The head branch is the worktree's current branch (for `pr_validate` with `pr`, the live pull
|
|
356
|
+
request's head, which may equal `base` for a fork); otherwise a detached HEAD, or a head equal to `base`, is rejected. The body must stay within GitHub's 65536 characters. `pr_create` sees no stack membership,
|
|
275
357
|
so a consumer policy may reject any `base` other than the default branch; create stacked layers
|
|
276
358
|
with the repository's stack tooling instead.
|
|
277
359
|
|
|
278
360
|
### Tools
|
|
279
361
|
|
|
280
362
|
- `pr_validate` (read-only): renders the body, runs the policy `validatePullRequest({ title, body,
|
|
281
|
-
base, head, mode, pr? })` export and returns `{ body, errors, warnings }`. With
|
|
282
|
-
body is read (never written) so its Shepherd Journal is preserved as in
|
|
363
|
+
base, head, mode, noSourceIssue?, pr? })` export and returns `{ body, errors, warnings }`. With
|
|
364
|
+
`pr`, the live body is read (never written) so its Shepherd Journal is preserved as in
|
|
365
|
+
`pr_update`. The liveness checks of `pr_update` do not apply: any state (merged or closed
|
|
366
|
+
included) and any head are accepted, and the policy sees the pull request's own head and base.
|
|
283
367
|
- `pr_create` (additive): renders and validates (errors block), then calls the policy
|
|
284
|
-
`pullRequestDefaults({ title, base, relatedIssues })` export for optional `labels`, `milestone`
|
|
368
|
+
`pullRequestDefaults({ title, base, relatedIssues, noSourceIssue? })` export for optional `labels`, `milestone`
|
|
285
369
|
and `project`. It checks the head is pushed and current, writes the body to a mode 0600 temp file
|
|
286
370
|
(removed afterwards) and runs `gh pr create --draft` with the labels, `--milestone` and
|
|
287
371
|
`--project`. Returns `{ number, url, warnings }`.
|
|
288
|
-
- `pr_update` (mutating): reads `gh pr view <pr> --json body,headRefName,baseRefName,state`,
|
|
289
|
-
rejects a pull request that is not `OPEN` or whose head is
|
|
372
|
+
- `pr_update` (mutating): reads `gh pr view <pr> --json body,headRefName,headRefOid,baseRefName,state`,
|
|
373
|
+
rejects a pull request that is not `OPEN` or whose head is neither the worktree branch nor the
|
|
374
|
+
worktree `HEAD` commit (an alias branch at the live head SHA is accepted and the live head name is
|
|
375
|
+
used), and validates
|
|
290
376
|
against the live base (an explicit `base` must equal it). It renders, appends the live Shepherd
|
|
291
377
|
Journal (a `<details><summary>Shepherd Journal</summary>` block, or a legacy `## Shepherd Journal`
|
|
292
378
|
section to the next H2 or the end; examples in fenced code blocks are ignored), validates with
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vouchington-tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
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": {
|
|
@@ -71,11 +71,6 @@
|
|
|
71
71
|
"default": "./dist/agent-blackboard/index.mjs"
|
|
72
72
|
},
|
|
73
73
|
"./mcp-server/bin": "./bin/vouchington-mcp.mjs",
|
|
74
|
-
"./session-friction": {
|
|
75
|
-
"types": "./dist/session-friction/index.d.mts",
|
|
76
|
-
"import": "./dist/session-friction/index.mjs",
|
|
77
|
-
"default": "./dist/session-friction/index.mjs"
|
|
78
|
-
},
|
|
79
74
|
"./sql-ast": {
|
|
80
75
|
"types": "./dist/sql-ast/index.d.mts",
|
|
81
76
|
"import": "./dist/sql-ast/index.mjs",
|
|
@@ -391,13 +386,13 @@
|
|
|
391
386
|
"yaml": "2.9.1"
|
|
392
387
|
},
|
|
393
388
|
"devDependencies": {
|
|
394
|
-
"@ast-grep/cli": "0.
|
|
389
|
+
"@ast-grep/cli": "0.50.0",
|
|
395
390
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
396
391
|
"@types/picomatch": "^4.0.3",
|
|
397
392
|
"@types/spdx-expression-parse": "4.0.0",
|
|
398
393
|
"agent-blackboard": "^0.6.1",
|
|
399
394
|
"no-mistakes": "0.83.0",
|
|
400
|
-
"smol-toml": "1.9.
|
|
395
|
+
"smol-toml": "1.9.1",
|
|
401
396
|
"typescript5": "npm:typescript@5.9.3"
|
|
402
397
|
},
|
|
403
398
|
"peerDependencies": {
|
|
@@ -15,7 +15,9 @@ local `AGENTS.md`, and journal guidance first.
|
|
|
15
15
|
1. Read the agent's own journal oldest-first and check for an existing retrospective. Capture a
|
|
16
16
|
material new observation before filing a follow-up issue. Preserve entry-level repository tags.
|
|
17
17
|
2. Generate routine repository and transcript facts with the approved composer: pass `compose` to
|
|
18
|
-
the `retrospective_save` MCP tool and preview the composition with `dryRun`, never a CLI.
|
|
18
|
+
the `retrospective_save` MCP tool and preview the composition with `dryRun`, never a CLI. Save the preview by passing its
|
|
19
|
+
`previewToken` to `retrospective_save` (no markdown); put sensitive values only in
|
|
20
|
+
`compose.knownSensitiveValues`, or `knownSensitiveValues` for a markdown save. Keep exact factual
|
|
19
21
|
section markers generated, preserve unavailable counts, and never replace missing evidence with
|
|
20
22
|
invented zero statistics. Do not paste raw collectors' output or manually reconstruct facts.
|
|
21
23
|
3. Record work outcome, feedback coverage, and delivery independently. Success, failure,
|
|
@@ -126,6 +126,11 @@ difference is a conflicting payload and stays quarantined.
|
|
|
126
126
|
sessions into retrospective and stale (checkpoint-only, journal-only, zero-entry child, and
|
|
127
127
|
zero-entry root).
|
|
128
128
|
|
|
129
|
+
Cleanup, as a finally step: whether processing succeeded or failed, call `snapshot_cleanup` with
|
|
130
|
+
the `cleanupToken` from `snapshot_export`, with the same `sessionId` and `worktree`, once the
|
|
131
|
+
snapshot is no longer needed. The token is single-use and server-restart-scoped; if cleanup fails,
|
|
132
|
+
retry the same call.
|
|
133
|
+
|
|
129
134
|
Use source records only for local verification and leave them in the repository's approved journal
|
|
130
135
|
or retention system. Public issue bodies contain only the minimum bounded facts or redacted
|
|
131
136
|
summaries needed to establish the problem, proposed work, relevant areas, and validation. Never
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { FrictionLogReadResult, JournalEntry } from './types.mts';
|
|
2
|
-
import { type AuditRedactor } from './text.mts';
|
|
3
|
-
export declare function isConformingCiFailureBlock(markdown: string): boolean;
|
|
4
|
-
export declare function getConformingGroups(entries: Iterable<JournalEntry>, redact?: AuditRedactor, maxBytes?: number): string[];
|
|
5
|
-
export declare function incompleteCiSection(markdownBlocks: string[]): string;
|
|
6
|
-
export declare function buildCiFailuresSection(sessionId: string, journal: {
|
|
7
|
-
status: 'ok';
|
|
8
|
-
markdownBlocks: string[];
|
|
9
|
-
truncated: boolean;
|
|
10
|
-
} | {
|
|
11
|
-
status: 'unreachable';
|
|
12
|
-
diagnostic: string;
|
|
13
|
-
}, frictionStatus: FrictionLogReadResult['status'] | 'journal-only'): string;
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { normalizeCommandPrefix } from "./normalize.mjs";
|
|
2
|
-
import { boundedText, isWellFormedUnicode, normalizeAuditText } from "./text.mjs";
|
|
3
|
-
const FAILURE_TOKENS = [
|
|
4
|
-
['Operation not permitted', /Operation not permitted/i],
|
|
5
|
-
['E2BIG', /\bE2BIG\b/i],
|
|
6
|
-
['EPERM', /\bEPERM\b/i],
|
|
7
|
-
];
|
|
8
|
-
const CONNECTION_REFUSED = /\bECONNREFUSED\b/i;
|
|
9
|
-
const URL_USERINFO = /([a-z][a-z0-9+.-]{0,31}:\/\/)[^\s/?#@]*@/gi;
|
|
10
|
-
const STDERR_INSPECTION_MAX_LENGTH = 100_000;
|
|
11
|
-
const COMMAND_INSPECTION_MAX_LENGTH = 10_000;
|
|
12
|
-
const IPV4_OCTET = '(?:25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)';
|
|
13
|
-
const LOOPBACK_ADDRESSES = [
|
|
14
|
-
new RegExp(String.raw `(?<![0-9a-z_.:-])(?:127\.${IPV4_OCTET}(?:\.${IPV4_OCTET}){2}|localhost)(?![0-9a-z_.-])`, 'i'),
|
|
15
|
-
/(?<![0-9a-z_.-])\[(?:::1|0:0:0:0:0:0:0:1)(?:%[a-z0-9_.-]+)?\](?![0-9a-z_.-])/i,
|
|
16
|
-
/(?<![0-9a-z_.:\x5b\x5d-])(?:::1|0:0:0:0:0:0:0:1)(?:%[a-z0-9_.-]+)?(?::\d{1,5})?(?![0-9a-z_.:\x5b\x5d-])/i,
|
|
17
|
-
];
|
|
18
|
-
const DETAIL_MAX_LENGTH = 1_000;
|
|
19
|
-
function boundedTail(value, maximum) {
|
|
20
|
-
let start = Math.max(0, value.length - maximum);
|
|
21
|
-
const first = value.charCodeAt(start);
|
|
22
|
-
const previous = value.charCodeAt(start - 1);
|
|
23
|
-
if (first >= 0xdc00 && first <= 0xdfff && previous >= 0xd800 && previous <= 0xdbff)
|
|
24
|
-
start++;
|
|
25
|
-
return value.slice(start);
|
|
26
|
-
}
|
|
27
|
-
function boundedStderr(value) {
|
|
28
|
-
if (value.length <= STDERR_INSPECTION_MAX_LENGTH)
|
|
29
|
-
return value;
|
|
30
|
-
const headLength = Math.floor((STDERR_INSPECTION_MAX_LENGTH - 1) / 2);
|
|
31
|
-
const head = boundedText(value, headLength);
|
|
32
|
-
const tail = boundedTail(value, STDERR_INSPECTION_MAX_LENGTH - 1 - head.length);
|
|
33
|
-
return `${head}\n${tail}`;
|
|
34
|
-
}
|
|
35
|
-
function withoutUrlUserinfo(line) {
|
|
36
|
-
return line.includes('://') && line.includes('@') ? line.replace(URL_USERINFO, '$1') : line;
|
|
37
|
-
}
|
|
38
|
-
export function classifyFrictionObservation(observation) {
|
|
39
|
-
if (!observation || typeof observation !== 'object')
|
|
40
|
-
return null;
|
|
41
|
-
if (typeof observation.command !== 'string')
|
|
42
|
-
return null;
|
|
43
|
-
const command = boundedText(observation.command, COMMAND_INSPECTION_MAX_LENGTH);
|
|
44
|
-
if (!isWellFormedUnicode(command) || normalizeAuditText(command) === '')
|
|
45
|
-
return null;
|
|
46
|
-
const commandPrefix = normalizeCommandPrefix(command, observation.commandWrappers);
|
|
47
|
-
if (commandPrefix === '' || !isWellFormedUnicode(commandPrefix))
|
|
48
|
-
return null;
|
|
49
|
-
if (observation.type === 'permission-request') {
|
|
50
|
-
return {
|
|
51
|
-
kind: 'sandbox-escalation',
|
|
52
|
-
commandPrefix,
|
|
53
|
-
detail: 'permission-request',
|
|
54
|
-
outcome: 'requested',
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
if (observation.type !== 'tool-result')
|
|
58
|
-
return null;
|
|
59
|
-
if (observation.escalationDetail !== undefined &&
|
|
60
|
-
typeof observation.escalationDetail !== 'string')
|
|
61
|
-
return null;
|
|
62
|
-
const escalationDetail = normalizeAuditText(boundedText(observation.escalationDetail ?? '', DETAIL_MAX_LENGTH));
|
|
63
|
-
if (!isWellFormedUnicode(escalationDetail))
|
|
64
|
-
return null;
|
|
65
|
-
if (observation.permissionOutcome !== undefined &&
|
|
66
|
-
!['approved', 'denied'].includes(observation.permissionOutcome))
|
|
67
|
-
return null;
|
|
68
|
-
const permission = escalationDetail || observation.permissionOutcome
|
|
69
|
-
? {
|
|
70
|
-
kind: 'sandbox-escalation',
|
|
71
|
-
commandPrefix,
|
|
72
|
-
detail: escalationDetail || `permission-${observation.permissionOutcome}`,
|
|
73
|
-
outcome: observation.permissionOutcome ?? 'unknown',
|
|
74
|
-
}
|
|
75
|
-
: null;
|
|
76
|
-
const rawStderr = observation.structuredStderr;
|
|
77
|
-
if (rawStderr !== undefined && (typeof rawStderr !== 'string' || !isWellFormedUnicode(rawStderr)))
|
|
78
|
-
return null;
|
|
79
|
-
const stderr = rawStderr ? boundedStderr(rawStderr) : rawStderr;
|
|
80
|
-
if (stderr === undefined)
|
|
81
|
-
return permission;
|
|
82
|
-
const token = FAILURE_TOKENS.find(([, pattern]) => pattern.test(stderr))?.[0];
|
|
83
|
-
if (token) {
|
|
84
|
-
const failure = { kind: 'sandbox-failure', detail: `stderr matched "${token}"` };
|
|
85
|
-
return permission ? { ...permission, failure } : { ...failure, commandPrefix };
|
|
86
|
-
}
|
|
87
|
-
if (stderr.split(/\r\n|[\r\n]/).some((rawLine) => {
|
|
88
|
-
const line = withoutUrlUserinfo(rawLine);
|
|
89
|
-
return (CONNECTION_REFUSED.test(line) && LOOPBACK_ADDRESSES.some((pattern) => pattern.test(line)));
|
|
90
|
-
})) {
|
|
91
|
-
const failure = {
|
|
92
|
-
kind: 'ambiguous-failure',
|
|
93
|
-
detail: 'localhost connection refused; sandbox cause unverified',
|
|
94
|
-
};
|
|
95
|
-
return permission ? { ...permission, failure } : { ...failure, commandPrefix };
|
|
96
|
-
}
|
|
97
|
-
return permission;
|
|
98
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
export const REDACTED_TOKEN = '[REDACTED]';
|
|
2
|
-
const ENV_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
3
|
-
const ENV_OPTIONS_WITH_ARGS = new Set(['-C', '--chdir', '-S', '--split-string', '-u', '--unset']);
|
|
4
|
-
const ENV_OPTIONS_WITHOUT_ARGS = new Set([
|
|
5
|
-
'-i',
|
|
6
|
-
'--ignore-environment',
|
|
7
|
-
'-0',
|
|
8
|
-
'--null',
|
|
9
|
-
'-v',
|
|
10
|
-
'--debug',
|
|
11
|
-
]);
|
|
12
|
-
export function splitCommand(command) {
|
|
13
|
-
const result = [];
|
|
14
|
-
let segment = [];
|
|
15
|
-
let word = '';
|
|
16
|
-
let quote;
|
|
17
|
-
let escaped = false;
|
|
18
|
-
const flushWord = () => {
|
|
19
|
-
if (word)
|
|
20
|
-
segment.push(word);
|
|
21
|
-
word = '';
|
|
22
|
-
};
|
|
23
|
-
const flushSegment = () => {
|
|
24
|
-
flushWord();
|
|
25
|
-
if (segment.length)
|
|
26
|
-
result.push(segment);
|
|
27
|
-
segment = [];
|
|
28
|
-
};
|
|
29
|
-
for (const char of command) {
|
|
30
|
-
if (escaped) {
|
|
31
|
-
word += char;
|
|
32
|
-
escaped = false;
|
|
33
|
-
}
|
|
34
|
-
else if (char === '\\' && quote !== "'") {
|
|
35
|
-
escaped = true;
|
|
36
|
-
}
|
|
37
|
-
else if (quote) {
|
|
38
|
-
if (char === quote)
|
|
39
|
-
quote = undefined;
|
|
40
|
-
else
|
|
41
|
-
word += char;
|
|
42
|
-
}
|
|
43
|
-
else if (char === '"' || char === "'")
|
|
44
|
-
quote = char;
|
|
45
|
-
else if (';&|<>\n\r'.includes(char))
|
|
46
|
-
flushSegment();
|
|
47
|
-
else if (/\s/.test(char))
|
|
48
|
-
flushWord();
|
|
49
|
-
else
|
|
50
|
-
word += char;
|
|
51
|
-
}
|
|
52
|
-
if (escaped)
|
|
53
|
-
word += '\\';
|
|
54
|
-
flushSegment();
|
|
55
|
-
return result;
|
|
56
|
-
}
|
|
57
|
-
export function stripAssignments(tokens) {
|
|
58
|
-
let index = 0;
|
|
59
|
-
while (index < tokens.length && ENV_ASSIGNMENT.test(tokens[index]))
|
|
60
|
-
index++;
|
|
61
|
-
const wrapper = tokens[index];
|
|
62
|
-
// Assignments without an executable are represented by a safe sentinel.
|
|
63
|
-
if (wrapper !== 'env' && !wrapper?.endsWith('/env'))
|
|
64
|
-
return index < tokens.length ? tokens.slice(index) : index ? [REDACTED_TOKEN] : tokens;
|
|
65
|
-
index++;
|
|
66
|
-
while (index < tokens.length) {
|
|
67
|
-
const option = tokens[index];
|
|
68
|
-
if (option === '--') {
|
|
69
|
-
index++;
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
const optionName = option.split('=', 1)[0];
|
|
73
|
-
const splitStringOption = optionName === '-S' || (optionName.length >= 5 && '--split-string'.startsWith(optionName));
|
|
74
|
-
if (splitStringOption)
|
|
75
|
-
return [REDACTED_TOKEN];
|
|
76
|
-
const abbreviatedArgument = optionName.length >= 5 && '--chdir'.startsWith(optionName);
|
|
77
|
-
if (ENV_OPTIONS_WITH_ARGS.has(optionName) || abbreviatedArgument) {
|
|
78
|
-
index += option.includes('=') ? 1 : 2;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (ENV_OPTIONS_WITHOUT_ARGS.has(option)) {
|
|
82
|
-
index++;
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (option.startsWith('-'))
|
|
86
|
-
return [REDACTED_TOKEN];
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
while (index < tokens.length && ENV_ASSIGNMENT.test(tokens[index]))
|
|
90
|
-
index++;
|
|
91
|
-
if (index < tokens.length)
|
|
92
|
-
return tokens.slice(index);
|
|
93
|
-
return tokens.some((token) => ENV_ASSIGNMENT.test(token)) ? [REDACTED_TOKEN] : [];
|
|
94
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export type CoverageLedger = {
|
|
2
|
-
schemaVersion: 1;
|
|
3
|
-
retainedEvents: number;
|
|
4
|
-
logBytes: number;
|
|
5
|
-
droppedCount: number;
|
|
6
|
-
};
|
|
7
|
-
/** Caller holds the event log lock; incomplete atomic updates must never imply complete capture. */
|
|
8
|
-
export declare function readCoverageLedger(path: string): CoverageLedger | undefined;
|
|
9
|
-
/** The fixed-size atomic sidecar keeps saturation work independent of the accumulated drop count. */
|
|
10
|
-
export declare function writeCoverageLedger(path: string, ledger: CoverageLedger): void;
|