review-council 3.0.0 → 3.2.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 +150 -1
- package/dist/config/harness.d.ts +9 -0
- package/dist/config/harness.js +3 -2
- package/dist/config/harness.js.map +1 -1
- package/dist/evidence/format.d.ts +19 -0
- package/dist/evidence/format.js +109 -0
- package/dist/evidence/format.js.map +1 -0
- package/dist/evidence/reads.d.ts +11 -0
- package/dist/evidence/reads.js +16 -0
- package/dist/evidence/reads.js.map +1 -0
- package/dist/evidence/show.d.ts +10 -0
- package/dist/evidence/show.js +41 -0
- package/dist/evidence/show.js.map +1 -0
- package/dist/evidence/status.d.ts +37 -0
- package/dist/evidence/status.js +72 -0
- package/dist/evidence/status.js.map +1 -0
- package/dist/evidence/target.d.ts +17 -0
- package/dist/evidence/target.js +74 -0
- package/dist/evidence/target.js.map +1 -0
- package/dist/evidence/types.d.ts +152 -0
- package/dist/evidence/types.js +94 -0
- package/dist/evidence/types.js.map +1 -0
- package/dist/index.js +272 -32
- package/dist/index.js.map +1 -1
- package/dist/models/server-stats.d.ts +80 -0
- package/dist/models/server-stats.js +109 -0
- package/dist/models/server-stats.js.map +1 -0
- package/dist/report/run-header.d.ts +2 -0
- package/dist/report/run-header.js.map +1 -1
- package/dist/report/uuid.d.ts +9 -0
- package/dist/report/uuid.js +19 -1
- package/dist/report/uuid.js.map +1 -1
- package/dist/resolver/github.d.ts +11 -0
- package/dist/resolver/github.js +21 -0
- package/dist/resolver/github.js.map +1 -1
- package/dist/resolver/target.d.ts +8 -0
- package/dist/resolver/target.js +45 -0
- package/dist/resolver/target.js.map +1 -1
- package/dist/telemetry/attest.d.ts +79 -0
- package/dist/telemetry/attest.js +269 -0
- package/dist/telemetry/attest.js.map +1 -0
- package/dist/telemetry/backfill.d.ts +86 -0
- package/dist/telemetry/backfill.js +446 -0
- package/dist/telemetry/backfill.js.map +1 -0
- package/dist/telemetry/credentials.d.ts +2 -1
- package/dist/telemetry/credentials.js.map +1 -1
- package/dist/telemetry/deliver.d.ts +15 -2
- package/dist/telemetry/deliver.js +32 -4
- package/dist/telemetry/deliver.js.map +1 -1
- package/dist/telemetry/events.d.ts +25 -0
- package/dist/telemetry/events.js +26 -0
- package/dist/telemetry/events.js.map +1 -1
- package/dist/telemetry/read-sink.d.ts +27 -0
- package/dist/telemetry/read-sink.js +23 -0
- package/dist/telemetry/read-sink.js.map +1 -0
- package/dist/telemetry/sink.d.ts +21 -2
- package/dist/telemetry/sink.js +19 -4
- package/dist/telemetry/sink.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,6 +91,7 @@ Review a PR, a local diff, or uncommitted work.
|
|
|
91
91
|
| `--expect-head-sha <sha>` | Fail fast unless the resolved head commit equals this SHA |
|
|
92
92
|
| `--spec-source <source>` | Where `--spec` came from: `flag`, `repo_file`, or `harness_issue:<ID>` |
|
|
93
93
|
| `--converge-target <key>` / `--round <n>` / `--attempt <n>` | Converge context recorded in the report (or `RCL_CONVERGE_TARGET` / `_ROUND` / `_ATTEMPT`) |
|
|
94
|
+
| `--attest` | GitHub Actions gate workflow only: exchange the job's OIDC token for a run-bound Harness credential and record the review as attested (see below) |
|
|
94
95
|
| `--config <path>` | Path to a config file |
|
|
95
96
|
|
|
96
97
|
`--role`, `--roles`, and `--reviewer` are mutually exclusive. So are a positional target, `--staged`, and `--working-tree` — pick exactly one review source. Untracked files are invisible to `git diff` and therefore not reviewed.
|
|
@@ -292,6 +293,116 @@ harness:
|
|
|
292
293
|
|
|
293
294
|
---
|
|
294
295
|
|
|
296
|
+
### `--attest`: attested reviews from the gate workflow
|
|
297
|
+
|
|
298
|
+
Only evidence recorded from the organization's own gate workflow on GitHub
|
|
299
|
+
Actions counts for the enforced gate (epic IO-12475, section 4.1). Inside
|
|
300
|
+
such a job — one that grants `id-token: write` — `rcl review owner/repo#N
|
|
301
|
+
--attest` asks the runner for the job's OIDC token with the Harness origin as
|
|
302
|
+
audience, exchanges it at `POST /api/v1/reviews/attest` for a **run-bound
|
|
303
|
+
credential** (`rbc_…`, thirty minutes, one rcl run id, valid while the
|
|
304
|
+
Actions run is in progress), and records the review under it: the envelope,
|
|
305
|
+
its artifacts, the model keys and the model stats all travel with that
|
|
306
|
+
credential and nothing else. Harness verifies the token, requires the
|
|
307
|
+
workflow file to be on the organization's gate allow-list at its default
|
|
308
|
+
branch, re-reads the pull request through its GitHub App and stores the run
|
|
309
|
+
only if the reviewed head is the pull request's current head and the PR is
|
|
310
|
+
not from a fork — the run is then `credential_kind: attested`.
|
|
311
|
+
|
|
312
|
+
`--attest` fails loudly, before any token is requested or any reviewer is
|
|
313
|
+
paid: outside Actions (no `ACTIONS_ID_TOKEN_REQUEST_URL` / `_TOKEN`), without
|
|
314
|
+
`HARNESS_API_URL`, off a pull request target, with a telemetry level other
|
|
315
|
+
than `full` (from `RCL_TELEMETRY` or the project config — an attested run
|
|
316
|
+
carries its full report), or when Harness refuses the exchange (the refusal
|
|
317
|
+
names the reason: `workflow_not_allowed`, `reviews_disabled`,
|
|
318
|
+
`run_not_in_progress`, …). It never falls back to `HARNESS_API_TOKEN` or the
|
|
319
|
+
stored login, it implies `--evidence-required`, and nothing recorded under the
|
|
320
|
+
run-bound credential is ever spooled — the credential does not outlive the
|
|
321
|
+
workflow run. A review that outlasts most of the credential's thirty minutes
|
|
322
|
+
mints it again for the same run id before delivery. Pair it with
|
|
323
|
+
`--expect-head-sha` so a moved pull request fails fast instead of being
|
|
324
|
+
refused at ingest.
|
|
325
|
+
|
|
326
|
+
```yaml
|
|
327
|
+
# .github/workflows/review_gate.yml (dispatched by Harness for one pull request)
|
|
328
|
+
permissions:
|
|
329
|
+
id-token: write
|
|
330
|
+
contents: read
|
|
331
|
+
jobs:
|
|
332
|
+
review:
|
|
333
|
+
runs-on: ubuntu-latest
|
|
334
|
+
env:
|
|
335
|
+
HARNESS_API_URL: https://harness.infra.one
|
|
336
|
+
steps:
|
|
337
|
+
- run: npm i -g review-council
|
|
338
|
+
# Inputs reach the shell through the environment, never by expression
|
|
339
|
+
# interpolation into the command line.
|
|
340
|
+
- env:
|
|
341
|
+
REPO: ${{ inputs.repo }}
|
|
342
|
+
PR: ${{ inputs.pr }}
|
|
343
|
+
HEAD_SHA: ${{ inputs.head_sha }}
|
|
344
|
+
run: rcl review "$REPO#$PR" --attest --expect-head-sha "$HEAD_SHA" --ci
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
### `rcl evidence status` and `rcl evidence show`
|
|
350
|
+
|
|
351
|
+
What Harness holds — never the client's own claim. `rcl evidence status`
|
|
352
|
+
prints the gate status Harness computed for a pull request: its known head,
|
|
353
|
+
the advisory and enforced projections (status, the rounds behind them, the
|
|
354
|
+
actionable findings still open) and the merge decision once it merged. The
|
|
355
|
+
exit code is the contract the skills gate on:
|
|
356
|
+
|
|
357
|
+
| exit | meaning |
|
|
358
|
+
| --- | --- |
|
|
359
|
+
| 0 | the judged projection (advisory by default, `--enforced` on request) is `converged` |
|
|
360
|
+
| 1 | any other status: `none`, `stale`, `unverified`, `inconclusive`, `fixes_pending`, `unresolved` |
|
|
361
|
+
| 2 | the pull request could not be named |
|
|
362
|
+
| 3 | the read could not be answered: no credential, evidence off for the organization, unknown pull request, refused credential, unreachable host — never reported as "not converged" |
|
|
363
|
+
|
|
364
|
+
`rcl evidence show <run id>` prints one recorded run: header and verification,
|
|
365
|
+
credential tier and runner, reviewer health, artifact state, and every finding
|
|
366
|
+
with its identity, gating reason and triage verdict (`—` until the server
|
|
367
|
+
joins the verdict onto the finding).
|
|
368
|
+
|
|
369
|
+
The reads use the same credential rules as delivery — the stored `harness
|
|
370
|
+
login`, or `HARNESS_API_TOKEN` + `HARNESS_API_URL` in CI, the token sent to
|
|
371
|
+
its own host only — and need `reviews:read`. They do not depend on the
|
|
372
|
+
telemetry level: switching delivery off does not blind them.
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
rcl evidence status # error: name the pull request
|
|
376
|
+
rcl evidence status 8524 # against the current checkout's origin remote
|
|
377
|
+
rcl evidence status '#8524' --enforced
|
|
378
|
+
rcl evidence status allocator-one/rcl#42 --json
|
|
379
|
+
rcl evidence status https://github.com/allocator-one/rcl/pull/42
|
|
380
|
+
rcl evidence show 01a08032-0838-76db-ade3-1990f6e54072
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### `--for-pr` on a patch-file review
|
|
384
|
+
|
|
385
|
+
A patch-file review (`rcl review changes.patch`) carries no repository or pull
|
|
386
|
+
request, so Harness records it as a `patch` run it cannot verify or count for
|
|
387
|
+
any gate. `--for-pr owner/repo#N` (or a pull request URL) names the pull
|
|
388
|
+
request the patch was taken from (`RCL_FOR_PR` in the environment does the
|
|
389
|
+
same for patch files): the run is bound to that pull request and its
|
|
390
|
+
`--head-sha` — required with the flag — is verified against the pull
|
|
391
|
+
request's head. Owner and repository are lower-cased, as GitHub reads them. Counting the round for that pull request's
|
|
392
|
+
gate is the server half (IO-12585); until it lands, `rcl evidence status`
|
|
393
|
+
still reads `stale`/`none` for patch-file loops. The flag is refused on PR and
|
|
394
|
+
git-mode targets, which name their own pull request or checkout. `rcl-converge` passes `--converge-target`, `--round`
|
|
395
|
+
and `--attempt` on every round and adds `--for-pr` when a pull request loop
|
|
396
|
+
reviews a patch file taken from the pull request (a pull request target names
|
|
397
|
+
its own). A converge
|
|
398
|
+
target of the `owner/repo#N` form attributes the run the same way; a slug such
|
|
399
|
+
as `rcl-7` does not.
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
rcl review round-3.patch --head-sha "$HEAD" --base-sha "$BASE" --for-pr allocator-one/rcl#42 \
|
|
403
|
+
--converge-target allocator-one/rcl#42 --round 3 --attempt 3 --evidence-required
|
|
404
|
+
```
|
|
405
|
+
|
|
295
406
|
### `rcl models`
|
|
296
407
|
|
|
297
408
|
The tool's own memory of which reviewers earn their seat. Every reviewer call
|
|
@@ -316,6 +427,43 @@ rcl models seed --from ~/recovered-rcl-artifacts # backfill from reports + con
|
|
|
316
427
|
|
|
317
428
|
---
|
|
318
429
|
|
|
430
|
+
Since 3.1 the table merges the organization's window from Harness
|
|
431
|
+
(`GET /api/v1/reviews/model-stats`, the server-side `rcl models` over every run
|
|
432
|
+
the org recorded, backfilled history included) with this machine's store: for a
|
|
433
|
+
model the server holds at least 20 outcomes for, the server's weight is used
|
|
434
|
+
(`source: server`); below that the local store decides (`local`); a model
|
|
435
|
+
neither knows enough about keeps the neutral weight (`neutral`). Reviews weight
|
|
436
|
+
consensus the same way, asking the server with a three-second bound and falling
|
|
437
|
+
back to the local store when it cannot answer. `--local` shows this machine's
|
|
438
|
+
view alone; `--json` carries `server` (host, window, rows) and `weights` with
|
|
439
|
+
their `source`.
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
rcl models # org-wide where Harness has enough history, local otherwise
|
|
443
|
+
rcl models show --local # this machine's store only
|
|
444
|
+
rcl models show --window 30 --json
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### `rcl telemetry backfill`
|
|
448
|
+
|
|
449
|
+
Recovered history becomes day-one evidence on Harness. `rcl telemetry backfill
|
|
450
|
+
--from <dir> --repo <owner/repo>` reads the pre-3.0 `rcl-report-*.json`
|
|
451
|
+
reports and `rcl-converge-*-ledger.md` ledgers in a directory (the same layout
|
|
452
|
+
`rcl models seed` reads) and posts each report as a run with `provenance:
|
|
453
|
+
backfill`: a synthesized header bound to the named repository (target `patch`,
|
|
454
|
+
the report bytes as the digest, a runner claim naming this command), the
|
|
455
|
+
report's findings with their stable identities, its reviewer calls, and the
|
|
456
|
+
report files as artifacts. Ledger bullets matched to a round's findings become
|
|
457
|
+
`verdicts_recorded` events. Run ids are UUIDv5 of `(host, repo, sha256 of the
|
|
458
|
+
report)` and event ids derive from them, so running the backfill twice reports
|
|
459
|
+
the second run as `0 new` — nothing is duplicated. Backfilled runs count for
|
|
460
|
+
model stats and analytics and never enter a gate decision.
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
rcl telemetry backfill --from ~/recovered-rcl-artifacts --repo allocator-one/allocator-one --dry-run
|
|
464
|
+
rcl telemetry backfill --from ~/recovered-rcl-artifacts --repo allocator-one/allocator-one
|
|
465
|
+
```
|
|
466
|
+
|
|
319
467
|
## Config File
|
|
320
468
|
|
|
321
469
|
Place `.review-council.yml` in your project root (or any parent directory). All fields are optional.
|
|
@@ -446,7 +594,8 @@ For the full algorithm, see [CONSENSUS_V2_SPEC.md](./CONSENSUS_V2_SPEC.md).
|
|
|
446
594
|
| `RCL_NO_HARNESS_KEYS` | Set to any value to disable Harness key distribution (below) |
|
|
447
595
|
| `RCL_TELEMETRY` | `off` keeps every review on the machine (see `rcl telemetry`) |
|
|
448
596
|
| `HARNESS_API_TOKEN` | CI credential for evidence delivery; requires `HARNESS_API_URL` — never pairs with the stored login host |
|
|
449
|
-
| `HARNESS_API_URL` | The Harness host `HARNESS_API_TOKEN` was minted by |
|
|
597
|
+
| `HARNESS_API_URL` | The Harness host `HARNESS_API_TOKEN` was minted by; under `--attest` the host attested to (no token needed) |
|
|
598
|
+
| `ACTIONS_ID_TOKEN_REQUEST_URL` / `ACTIONS_ID_TOKEN_REQUEST_TOKEN` | Set by the GitHub Actions runner for jobs with `id-token: write`; `--attest` reads them and refuses to run without them |
|
|
450
599
|
|
|
451
600
|
The default blocking council is direct-API only (Anthropic, OpenAI, Google) —
|
|
452
601
|
no default review round ever waits on an OpenRouter-routed call. The default
|
package/dist/config/harness.d.ts
CHANGED
|
@@ -10,6 +10,15 @@ export interface HarnessKeyOptions {
|
|
|
10
10
|
env?: Record<string, string | undefined>;
|
|
11
11
|
credentialsPath?: string;
|
|
12
12
|
fetchImpl?: typeof fetch;
|
|
13
|
+
/**
|
|
14
|
+
* A credential already in hand — the run-bound one `rcl review --attest`
|
|
15
|
+
* minted (RCL-40). Used instead of the stored login, and the attestation
|
|
16
|
+
* stands in for the repository's `.harness-cli/config.json`.
|
|
17
|
+
*/
|
|
18
|
+
credential?: {
|
|
19
|
+
url: string;
|
|
20
|
+
token: string;
|
|
21
|
+
};
|
|
13
22
|
}
|
|
14
23
|
/** Walk up from `startDir` looking for `.harness-cli/config.json`. */
|
|
15
24
|
export declare function findHarnessRepoConfig(startDir: string): string | null;
|
package/dist/config/harness.js
CHANGED
|
@@ -121,9 +121,10 @@ export async function applyHarnessModelKeys(options = {}) {
|
|
|
121
121
|
const missing = missingProviders(env);
|
|
122
122
|
if (missing.length === 0)
|
|
123
123
|
return { injected: [] };
|
|
124
|
-
if (findHarnessRepoConfig(options.cwd ?? process.cwd()) === null)
|
|
124
|
+
if (options.credential === undefined && findHarnessRepoConfig(options.cwd ?? process.cwd()) === null)
|
|
125
125
|
return { injected: [] };
|
|
126
|
-
const credentials =
|
|
126
|
+
const credentials = options.credential ??
|
|
127
|
+
(await readStoredCredentials(options.credentialsPath ?? defaultCredentialsPath(env)));
|
|
127
128
|
if (credentials === null) {
|
|
128
129
|
return {
|
|
129
130
|
injected: [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/config/harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAExD;;;;;;;;;;;;;;;;;GAiBG;AAEH,6EAA6E;AAC7E,MAAM,iBAAiB,GAA2B;IAChD,SAAS,EAAE,mBAAmB;IAC9B,MAAM,EAAE,gBAAgB;IACxB,MAAM,EAAE,gBAAgB;IACxB,UAAU,EAAE,oBAAoB;CACjC,CAAC;AAEF,+EAA+E;AAC/E,MAAM,gBAAgB,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/config/harness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { GateStatus, RunDetail } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Terminal rendering of what Harness holds; one line per entry, no colour,
|
|
4
|
+
* safe to grep. Every server-supplied string — repository names, titles,
|
|
5
|
+
* paths, model names, errors, URLs — can trace back to reviewed code or model
|
|
6
|
+
* output, so it reaches the terminal only through `text()`: control and
|
|
7
|
+
* escape characters (C0, DEL, C1) become spaces, secrets are scrubbed, and
|
|
8
|
+
* the length is bounded, which also keeps every entry on its one line.
|
|
9
|
+
*/
|
|
10
|
+
export declare function text(value: unknown, limit?: number): string;
|
|
11
|
+
/**
|
|
12
|
+
* `JSON.stringify` escapes C0 controls but writes DEL and the C1 range
|
|
13
|
+
* (U+007F–U+009F — 8-bit CSI and OSC introducers) literally; a title carrying
|
|
14
|
+
* one would reach the terminal or a CI log intact. They become `\\u00xx`
|
|
15
|
+
* escapes, which parse back to the same string.
|
|
16
|
+
*/
|
|
17
|
+
export declare function safeJson(value: unknown): string;
|
|
18
|
+
export declare function formatGateStatus(status: GateStatus, judged: 'advisory' | 'enforced'): string[];
|
|
19
|
+
export declare function formatRun(run: RunDetail): string[];
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { scrubText } from '../telemetry/scrub.js';
|
|
2
|
+
/**
|
|
3
|
+
* Terminal rendering of what Harness holds; one line per entry, no colour,
|
|
4
|
+
* safe to grep. Every server-supplied string — repository names, titles,
|
|
5
|
+
* paths, model names, errors, URLs — can trace back to reviewed code or model
|
|
6
|
+
* output, so it reaches the terminal only through `text()`: control and
|
|
7
|
+
* escape characters (C0, DEL, C1) become spaces, secrets are scrubbed, and
|
|
8
|
+
* the length is bounded, which also keeps every entry on its one line.
|
|
9
|
+
*/
|
|
10
|
+
export function text(value, limit = 300) {
|
|
11
|
+
if (value === null || value === undefined)
|
|
12
|
+
return '—';
|
|
13
|
+
return scrubText(String(value).replace(/[\u0000-\u001f\u007f-\u009f]/g, ' '), limit);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* `JSON.stringify` escapes C0 controls but writes DEL and the C1 range
|
|
17
|
+
* (U+007F–U+009F — 8-bit CSI and OSC introducers) literally; a title carrying
|
|
18
|
+
* one would reach the terminal or a CI log intact. They become `\\u00xx`
|
|
19
|
+
* escapes, which parse back to the same string.
|
|
20
|
+
*/
|
|
21
|
+
export function safeJson(value) {
|
|
22
|
+
return JSON.stringify(value, null, 2).replace(/[\u007f-\u009f]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
23
|
+
}
|
|
24
|
+
function short(sha) {
|
|
25
|
+
return typeof sha === 'string' && sha.length > 0 ? text(sha.slice(0, 10), 10) : '—';
|
|
26
|
+
}
|
|
27
|
+
function where(file, line) {
|
|
28
|
+
if (!file)
|
|
29
|
+
return '—';
|
|
30
|
+
return `${text(file)}${typeof line === 'number' ? `:${line}` : ''}`;
|
|
31
|
+
}
|
|
32
|
+
function projectionLines(name, projection, judged) {
|
|
33
|
+
const lines = [
|
|
34
|
+
`${name}: ${text(projection.status, 40)}${projection.conclusive === false ? ' (inconclusive)' : ''}${judged ? ' ← judged' : ''}` +
|
|
35
|
+
(projection.run_id ? ` — run ${short(projection.run_id)}${projection.run_url ? ` ${text(projection.run_url)}` : ''}` : ''),
|
|
36
|
+
];
|
|
37
|
+
for (const round of projection.rounds) {
|
|
38
|
+
lines.push(` round ${typeof round.converge_round === 'number' ? round.converge_round : '·'}: ${short(round.id)} head ${short(round.head_sha)} ${text(round.tier, 40)}` +
|
|
39
|
+
`${round.received_at ? ` ${text(round.received_at, 40)}` : ''}${round.url ? ` ${text(round.url)}` : ''}`);
|
|
40
|
+
}
|
|
41
|
+
for (const finding of projection.actionable) {
|
|
42
|
+
lines.push(` actionable ${text(finding.identity_key ?? finding.ref ?? '?', 64)} ${text(finding.severity, 20)}/${text(finding.gating_reason, 20)} ` +
|
|
43
|
+
`${where(finding.file, finding.start_line)} ${text(finding.title, 200)}`);
|
|
44
|
+
}
|
|
45
|
+
return lines;
|
|
46
|
+
}
|
|
47
|
+
export function formatGateStatus(status, judged) {
|
|
48
|
+
const head = status.head;
|
|
49
|
+
const name = `${text(status.repo, 200)}#${text(status.pr_number, 20)}`;
|
|
50
|
+
const headLine = !head
|
|
51
|
+
? `${name} — no head known to Harness`
|
|
52
|
+
: `${name} — head ${short(head.sha)}${head.source ? ` (${text(head.source, 40)})` : ''}` +
|
|
53
|
+
`${head.merged ? ` — merged${head.merge_commit_sha ? ` as ${short(head.merge_commit_sha)}` : ''}` : ''}` +
|
|
54
|
+
`${head.is_cross_repository ? ' — fork' : ''}`;
|
|
55
|
+
const decision = status.decision;
|
|
56
|
+
return [
|
|
57
|
+
headLine,
|
|
58
|
+
...projectionLines('advisory', status.advisory, judged === 'advisory'),
|
|
59
|
+
...projectionLines('enforced', status.enforced, judged === 'enforced'),
|
|
60
|
+
decision
|
|
61
|
+
? `decision: ${text(decision.decision, 40)} at ${short(decision.reviewed_head_sha)} (${text(decision.source, 40)}` +
|
|
62
|
+
`${decision.merged_at ? `, merged ${text(decision.merged_at, 40)}` : ''})`
|
|
63
|
+
: 'decision: —',
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
function reviewerHealth(run) {
|
|
67
|
+
const stats = run.stats ?? {};
|
|
68
|
+
const total = stats['totalReviews'];
|
|
69
|
+
const ok = stats['successfulReviews'];
|
|
70
|
+
if (typeof total === 'number' && typeof ok === 'number')
|
|
71
|
+
return `${ok}/${total}`;
|
|
72
|
+
return `${run.calls.filter((call) => call.status === 'success').length}/${run.calls.length}`;
|
|
73
|
+
}
|
|
74
|
+
export function formatRun(run) {
|
|
75
|
+
const runner = run.runner ?? {};
|
|
76
|
+
const runnerKind = typeof runner['kind'] === 'string' ? text(runner['kind'], 40) : '—';
|
|
77
|
+
const ciRunId = typeof runner['ci_run_id'] === 'string' ? ` ${text(runner['ci_run_id'], 40)}` : '';
|
|
78
|
+
const target = run.target;
|
|
79
|
+
const named = target.repo && target.pr_number ? `${text(target.repo, 200)}#${text(target.pr_number, 20)}` : text(target.kind, 40);
|
|
80
|
+
const converge = run.converge;
|
|
81
|
+
const lines = [
|
|
82
|
+
`run ${text(run.id, 64)}${run.url ? ` ${text(run.url)}` : ''}`,
|
|
83
|
+
`target ${named} @ ${short(target.head_sha)} (head ${text(run.head_verified ?? 'unknown', 20)}${run.repo_verified ? ', repo verified' : ''}` +
|
|
84
|
+
`${run.is_cross_repository ? ', fork' : ''})`,
|
|
85
|
+
`credential ${text(run.credential_kind, 40)} (${text(run.tier ?? 'asserted', 40)}) — runner ${runnerKind}${ciRunId} — rcl ${text(run.rcl_version, 40)}` +
|
|
86
|
+
(run.provenance && run.provenance !== 'live' ? ` — ${text(run.provenance, 40)}` : ''),
|
|
87
|
+
`reviewers ${reviewerHealth(run)} ok` +
|
|
88
|
+
(converge ? ` — converge ${text(converge.target, 200)}${typeof converge.round === 'number' ? ` round ${converge.round}` : ''}` : '') +
|
|
89
|
+
(run.received_at ? ` — received ${text(run.received_at, 40)}` : ''),
|
|
90
|
+
];
|
|
91
|
+
for (const artifact of run.artifacts ?? []) {
|
|
92
|
+
lines.push(`artifact ${text(artifact.kind, 40)} ${artifact.stored ? 'stored' : 'missing'}${artifact.url ? ` ${text(artifact.url)}` : ''}`);
|
|
93
|
+
}
|
|
94
|
+
lines.push(`findings (${run.findings.length}): identity severity gating file:line title — verdict`);
|
|
95
|
+
for (const finding of run.findings) {
|
|
96
|
+
const verdict = finding.verdict
|
|
97
|
+
? `${text(finding.verdict.verdict, 40)}${typeof finding.verdict.round === 'number' ? ` (round ${finding.verdict.round})` : ''}`
|
|
98
|
+
: '—';
|
|
99
|
+
lines.push(` ${text(finding.identity_key ?? finding.ref ?? '?', 64)} ${text(finding.severity, 20)} ${text(finding.gating_reason ?? 'none', 20)} ` +
|
|
100
|
+
`${where(finding.file, finding.start_line)} ${text(finding.title, 200)} — ${verdict}`);
|
|
101
|
+
}
|
|
102
|
+
lines.push(`calls (${run.calls.length}):`);
|
|
103
|
+
for (const call of run.calls) {
|
|
104
|
+
const duration = typeof call.duration_ms === 'number' ? ` ${call.duration_ms}ms` : '';
|
|
105
|
+
lines.push(` call ${text(call.model, 80)}${call.role ? ` ${text(call.role, 40)}` : ''} ${text(call.status, 20)}${duration}${call.error ? ` — ${text(call.error, 200)}` : ''}`);
|
|
106
|
+
}
|
|
107
|
+
return lines;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/evidence/format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HarnessSink, RequestOptions, SinkOutcome } from '../telemetry/sink.js';
|
|
2
|
+
import { type GateStatus, type RunDetail } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The two evidence reads (epic IO-12475, section 9) on top of the sink's
|
|
5
|
+
* generic GET: each names what it asked for, so an answer about another pull
|
|
6
|
+
* request or run — or one shaped wrong — is refused, never trusted.
|
|
7
|
+
*/
|
|
8
|
+
/** `GET /api/v1/reviews/prs/:owner/:repo/:number` — the gate status Harness computed for a pull request. */
|
|
9
|
+
export declare function getGateStatus(sink: HarnessSink, owner: string, repo: string, number: number, options?: RequestOptions): Promise<SinkOutcome<GateStatus>>;
|
|
10
|
+
/** `GET /api/v1/reviews/runs/:id` — one recorded run with its findings, calls and artifact state. */
|
|
11
|
+
export declare function getRun(sink: HarnessSink, id: string, options?: RequestOptions): Promise<SinkOutcome<RunDetail>>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { isGateStatus, isRunDetail } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The two evidence reads (epic IO-12475, section 9) on top of the sink's
|
|
4
|
+
* generic GET: each names what it asked for, so an answer about another pull
|
|
5
|
+
* request or run — or one shaped wrong — is refused, never trusted.
|
|
6
|
+
*/
|
|
7
|
+
/** `GET /api/v1/reviews/prs/:owner/:repo/:number` — the gate status Harness computed for a pull request. */
|
|
8
|
+
export function getGateStatus(sink, owner, repo, number, options = {}) {
|
|
9
|
+
const path = `/api/v1/reviews/prs/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${encodeURIComponent(String(number))}`;
|
|
10
|
+
return sink.getJson(path, (data) => (isGateStatus(data, `${owner}/${repo}`, number) ? data : null), options);
|
|
11
|
+
}
|
|
12
|
+
/** `GET /api/v1/reviews/runs/:id` — one recorded run with its findings, calls and artifact state. */
|
|
13
|
+
export function getRun(sink, id, options = {}) {
|
|
14
|
+
return sink.getJson(`/api/v1/reviews/runs/${encodeURIComponent(id)}`, (data) => (isRunDetail(data, id) ? data : null), options);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=reads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reads.js","sourceRoot":"","sources":["../../src/evidence/reads.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAmC,MAAM,YAAY,CAAC;AAExF;;;;GAIG;AAEH,4GAA4G;AAC5G,MAAM,UAAU,aAAa,CAC3B,IAAiB,EACjB,KAAa,EACb,IAAY,EACZ,MAAc,EACd,UAA0B,EAAE;IAE5B,MAAM,IAAI,GAAG,uBAAuB,kBAAkB,CAAC,KAAK,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;IAClI,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AAC/G,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,MAAM,CAAC,IAAiB,EAAE,EAAU,EAAE,UAA0B,EAAE;IAChF,OAAO,IAAI,CAAC,OAAO,CAAC,wBAAwB,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AAClI,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type EvidenceDeps } from './status.js';
|
|
2
|
+
/**
|
|
3
|
+
* `rcl evidence show <run-id>` (RCL-41): one recorded run as Harness holds
|
|
4
|
+
* it — header, verification, reviewer health, artifact state, findings with
|
|
5
|
+
* their identity, gating reason and (once the server joins it) verdict.
|
|
6
|
+
* Exit 0 when the run was read (`EVIDENCE_EXIT.ok`), 2 without a run id, 3 when it could not be.
|
|
7
|
+
*/
|
|
8
|
+
export declare function runEvidenceShow(runId: string, options: {
|
|
9
|
+
json?: boolean;
|
|
10
|
+
}, deps: EvidenceDeps): Promise<number>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describeOutcome } from '../telemetry/sink.js';
|
|
2
|
+
import { formatRun, safeJson, text } from './format.js';
|
|
3
|
+
import { getRun } from './reads.js';
|
|
4
|
+
// Run ids are UUIDs (v7 live, v5 backfill); anything else never reaches the network or the terminal raw.
|
|
5
|
+
const RUN_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6
|
+
import { EVIDENCE_EXIT, openSink } from './status.js';
|
|
7
|
+
/**
|
|
8
|
+
* `rcl evidence show <run-id>` (RCL-41): one recorded run as Harness holds
|
|
9
|
+
* it — header, verification, reviewer health, artifact state, findings with
|
|
10
|
+
* their identity, gating reason and (once the server joins it) verdict.
|
|
11
|
+
* Exit 0 when the run was read (`EVIDENCE_EXIT.ok`), 2 without a run id, 3 when it could not be.
|
|
12
|
+
*/
|
|
13
|
+
export async function runEvidenceShow(runId, options, deps) {
|
|
14
|
+
// UUIDs are case-insensitive text; the server echoes the canonical lowercase form.
|
|
15
|
+
const id = (runId ?? '').trim().toLowerCase();
|
|
16
|
+
if (id === '') {
|
|
17
|
+
deps.stderr('Name the run id: `run.id` in a report, or the id in an `Evidence recorded:` URL.');
|
|
18
|
+
return EVIDENCE_EXIT.usage;
|
|
19
|
+
}
|
|
20
|
+
if (!RUN_ID.test(id)) {
|
|
21
|
+
deps.stderr(`Not a run id (a UUID): ${text(id, 80)}`);
|
|
22
|
+
return EVIDENCE_EXIT.usage;
|
|
23
|
+
}
|
|
24
|
+
const sink = await openSink(deps);
|
|
25
|
+
if (!sink)
|
|
26
|
+
return EVIDENCE_EXIT.unanswered;
|
|
27
|
+
const outcome = await getRun(sink, id);
|
|
28
|
+
if (outcome.kind !== 'ok') {
|
|
29
|
+
deps.stderr(`Cannot read run ${id}: ${describeOutcome(outcome)}`);
|
|
30
|
+
return EVIDENCE_EXIT.unanswered;
|
|
31
|
+
}
|
|
32
|
+
if (options.json) {
|
|
33
|
+
deps.stdout(safeJson(outcome.value));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
for (const line of formatRun(outcome.value))
|
|
37
|
+
deps.stdout(line);
|
|
38
|
+
}
|
|
39
|
+
return EVIDENCE_EXIT.ok;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=show.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/evidence/show.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,yGAAyG;AACzG,MAAM,MAAM,GAAG,iEAAiE,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAa,EAAE,OAA2B,EAAE,IAAkB;IAClG,mFAAmF;IACnF,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC9C,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACd,IAAI,CAAC,MAAM,CAAC,kFAAkF,CAAC,CAAC;QAChG,OAAO,aAAa,CAAC,KAAK,CAAC;IAC7B,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,0BAA0B,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QACtD,OAAO,aAAa,CAAC,KAAK,CAAC;IAC7B,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI;QAAE,OAAO,aAAa,CAAC,UAAU,CAAC;IAE3C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACvC,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,mBAAmB,EAAE,KAAK,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAClE,OAAO,aAAa,CAAC,UAAU,CAAC;IAClC,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,aAAa,CAAC,EAAE,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type HarnessSink } from '../telemetry/sink.js';
|
|
2
|
+
import { type RepoRef } from './target.js';
|
|
3
|
+
/**
|
|
4
|
+
* `rcl evidence status [<pr>]` (RCL-41): the gate status Harness computed for
|
|
5
|
+
* a pull request — never the client's own claim. The exit code is the
|
|
6
|
+
* contract the skills gate on: 0 only when the judged projection (advisory by
|
|
7
|
+
* default, `--enforced` on request) is `converged`; 1 for every other
|
|
8
|
+
* status; 2 when the pull request cannot be named; 3 when the read could not
|
|
9
|
+
* be answered (no credential, evidence off for the organization, unknown
|
|
10
|
+
* pull request, refused credential, unreachable host) — an unanswered read is
|
|
11
|
+
* never "not converged".
|
|
12
|
+
*/
|
|
13
|
+
export declare const EVIDENCE_EXIT: {
|
|
14
|
+
readonly converged: 0;
|
|
15
|
+
readonly ok: 0;
|
|
16
|
+
readonly notConverged: 1;
|
|
17
|
+
readonly usage: 2;
|
|
18
|
+
readonly unanswered: 3;
|
|
19
|
+
};
|
|
20
|
+
export interface EvidenceDeps {
|
|
21
|
+
rclVersion: string;
|
|
22
|
+
fetchImpl?: typeof fetch;
|
|
23
|
+
env?: Record<string, string | undefined>;
|
|
24
|
+
cwd?: string;
|
|
25
|
+
credentialsPath?: string;
|
|
26
|
+
/** The repository the checkout's `origin` names; injected by tests. */
|
|
27
|
+
remoteRepo?: () => Promise<RepoRef | null>;
|
|
28
|
+
stdout: (line: string) => void;
|
|
29
|
+
stderr: (line: string) => void;
|
|
30
|
+
}
|
|
31
|
+
export interface StatusOptions {
|
|
32
|
+
json?: boolean;
|
|
33
|
+
enforced?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** The read sink, or `null` after telling the user why there is none. */
|
|
36
|
+
export declare function openSink(deps: EvidenceDeps): Promise<HarnessSink | null>;
|
|
37
|
+
export declare function runEvidenceStatus(prArg: string | undefined, options: StatusOptions, deps: EvidenceDeps): Promise<number>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { openReadSink } from '../telemetry/read-sink.js';
|
|
2
|
+
import { describeOutcome } from '../telemetry/sink.js';
|
|
3
|
+
import { formatGateStatus, safeJson, text } from './format.js';
|
|
4
|
+
import { getGateStatus } from './reads.js';
|
|
5
|
+
import { needsRemote, parsePullRequestArg, resolveRemoteRepo } from './target.js';
|
|
6
|
+
/**
|
|
7
|
+
* `rcl evidence status [<pr>]` (RCL-41): the gate status Harness computed for
|
|
8
|
+
* a pull request — never the client's own claim. The exit code is the
|
|
9
|
+
* contract the skills gate on: 0 only when the judged projection (advisory by
|
|
10
|
+
* default, `--enforced` on request) is `converged`; 1 for every other
|
|
11
|
+
* status; 2 when the pull request cannot be named; 3 when the read could not
|
|
12
|
+
* be answered (no credential, evidence off for the organization, unknown
|
|
13
|
+
* pull request, refused credential, unreachable host) — an unanswered read is
|
|
14
|
+
* never "not converged".
|
|
15
|
+
*/
|
|
16
|
+
export const EVIDENCE_EXIT = { converged: 0, ok: 0, notConverged: 1, usage: 2, unanswered: 3 };
|
|
17
|
+
/** The read sink, or `null` after telling the user why there is none. */
|
|
18
|
+
export async function openSink(deps) {
|
|
19
|
+
const opened = await openReadSink({
|
|
20
|
+
rclVersion: deps.rclVersion,
|
|
21
|
+
...(deps.env !== undefined ? { env: deps.env } : {}),
|
|
22
|
+
...(deps.cwd !== undefined ? { cwd: deps.cwd } : {}),
|
|
23
|
+
...(deps.credentialsPath !== undefined ? { credentialsPath: deps.credentialsPath } : {}),
|
|
24
|
+
...(deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}),
|
|
25
|
+
});
|
|
26
|
+
if (!opened.sink) {
|
|
27
|
+
deps.stderr(`Cannot read evidence: ${opened.note}`);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return opened.sink;
|
|
31
|
+
}
|
|
32
|
+
export async function runEvidenceStatus(prArg, options, deps) {
|
|
33
|
+
if (prArg === undefined || prArg.trim() === '') {
|
|
34
|
+
deps.stderr('Name the pull request: N or #N (against the current git remote), owner/repo#N, or its URL.');
|
|
35
|
+
return EVIDENCE_EXIT.usage;
|
|
36
|
+
}
|
|
37
|
+
let target;
|
|
38
|
+
try {
|
|
39
|
+
const remote = needsRemote(prArg)
|
|
40
|
+
? await (deps.remoteRepo ?? (() => resolveRemoteRepo(deps.cwd ?? process.cwd())))()
|
|
41
|
+
: null;
|
|
42
|
+
target = parsePullRequestArg(prArg, remote);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
// The argument or the remote may be the reason; neither reaches the terminal raw.
|
|
46
|
+
deps.stderr(text(err instanceof Error ? err.message : String(err), 400));
|
|
47
|
+
return EVIDENCE_EXIT.usage;
|
|
48
|
+
}
|
|
49
|
+
const sink = await openSink(deps);
|
|
50
|
+
if (!sink)
|
|
51
|
+
return EVIDENCE_EXIT.unanswered;
|
|
52
|
+
const outcome = await getGateStatus(sink, target.owner, target.repo, target.number);
|
|
53
|
+
const name = text(`${target.owner}/${target.repo}#${target.number}`, 200);
|
|
54
|
+
if (outcome.kind !== 'ok') {
|
|
55
|
+
deps.stderr(`Cannot read the gate status of ${name}: ${describeOutcome(outcome)}`);
|
|
56
|
+
return EVIDENCE_EXIT.unanswered;
|
|
57
|
+
}
|
|
58
|
+
const status = outcome.value;
|
|
59
|
+
const judged = options.enforced ? 'enforced' : 'advisory';
|
|
60
|
+
if (options.json) {
|
|
61
|
+
deps.stdout(safeJson(status));
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
for (const line of formatGateStatus(status, judged))
|
|
65
|
+
deps.stdout(line);
|
|
66
|
+
}
|
|
67
|
+
// Converged is only ever conclusive on the server; a payload saying
|
|
68
|
+
// otherwise does not open the gate.
|
|
69
|
+
const projection = status[judged];
|
|
70
|
+
return projection.status === 'converged' && projection.conclusive ? EVIDENCE_EXIT.converged : EVIDENCE_EXIT.notConverged;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/evidence/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,eAAe,EAAoB,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,iBAAiB,EAAgB,MAAM,aAAa,CAAC;AAEhG;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAW,CAAC;AAmBxG,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAkB;IAC/C,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC;QAChC,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,GAAG,CAAC,IAAI,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxF,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzD,CAAC,CAAC;IACH,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,IAAI,CAAC,MAAM,CAAC,yBAAyB,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAyB,EACzB,OAAsB,EACtB,IAAkB;IAElB,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,4FAA4F,CAAC,CAAC;QAC1G,OAAO,aAAa,CAAC,KAAK,CAAC;IAC7B,CAAC;IACD,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC;YAC/B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE;YACnF,CAAC,CAAC,IAAI,CAAC;QACT,MAAM,GAAG,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,kFAAkF;QAClF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACzE,OAAO,aAAa,CAAC,KAAK,CAAC;IAC7B,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI;QAAE,OAAO,aAAa,CAAC,UAAU,CAAC;IAE3C,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACpF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC;IAC1E,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,kCAAkC,IAAI,KAAK,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACnF,OAAO,aAAa,CAAC,UAAU,CAAC;IAClC,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC;IAC1D,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAChC,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,IAAI,IAAI,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC;IACD,oEAAoE;IACpE,oCAAoC;IACpC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,UAAU,CAAC,MAAM,KAAK,WAAW,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC;AAC3H,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { parseRepoName, type RepoRef } from '../resolver/github.js';
|
|
2
|
+
export { parseRepoName, type RepoRef };
|
|
3
|
+
/**
|
|
4
|
+
* Which pull request `rcl evidence status` asks about: `owner/repo#N` or a
|
|
5
|
+
* pull request URL stand alone; a bare `N` or `#N` is read against the
|
|
6
|
+
* repository the current checkout's `origin` remote points at.
|
|
7
|
+
*/
|
|
8
|
+
export interface PullRequestRef extends RepoRef {
|
|
9
|
+
number: number;
|
|
10
|
+
}
|
|
11
|
+
/** The GitHub repository a remote URL names, or `null` for anything else. */
|
|
12
|
+
export declare function parseRemoteUrl(url: string): RepoRef | null;
|
|
13
|
+
/** The repository `origin` points at, or `null` outside a checkout with a GitHub remote. Never throws. */
|
|
14
|
+
export declare function resolveRemoteRepo(cwd?: string): Promise<RepoRef | null>;
|
|
15
|
+
/** Whether an argument is a bare number or `#N`, which needs the remote to become a pull request. */
|
|
16
|
+
export declare function needsRemote(arg: string): boolean;
|
|
17
|
+
export declare function parsePullRequestArg(arg: string, remote: RepoRef | null): PullRequestRef;
|