review-council 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +94 -0
  2. package/dist/evidence/format.d.ts +19 -0
  3. package/dist/evidence/format.js +109 -0
  4. package/dist/evidence/format.js.map +1 -0
  5. package/dist/evidence/reads.d.ts +11 -0
  6. package/dist/evidence/reads.js +16 -0
  7. package/dist/evidence/reads.js.map +1 -0
  8. package/dist/evidence/show.d.ts +10 -0
  9. package/dist/evidence/show.js +41 -0
  10. package/dist/evidence/show.js.map +1 -0
  11. package/dist/evidence/status.d.ts +37 -0
  12. package/dist/evidence/status.js +72 -0
  13. package/dist/evidence/status.js.map +1 -0
  14. package/dist/evidence/target.d.ts +17 -0
  15. package/dist/evidence/target.js +74 -0
  16. package/dist/evidence/target.js.map +1 -0
  17. package/dist/evidence/types.d.ts +152 -0
  18. package/dist/evidence/types.js +94 -0
  19. package/dist/evidence/types.js.map +1 -0
  20. package/dist/index.js +182 -22
  21. package/dist/index.js.map +1 -1
  22. package/dist/models/server-stats.d.ts +80 -0
  23. package/dist/models/server-stats.js +109 -0
  24. package/dist/models/server-stats.js.map +1 -0
  25. package/dist/report/run-header.d.ts +2 -0
  26. package/dist/report/run-header.js.map +1 -1
  27. package/dist/report/uuid.d.ts +9 -0
  28. package/dist/report/uuid.js +19 -1
  29. package/dist/report/uuid.js.map +1 -1
  30. package/dist/resolver/github.d.ts +11 -0
  31. package/dist/resolver/github.js +21 -0
  32. package/dist/resolver/github.js.map +1 -1
  33. package/dist/resolver/target.d.ts +8 -0
  34. package/dist/resolver/target.js +45 -0
  35. package/dist/resolver/target.js.map +1 -1
  36. package/dist/telemetry/backfill.d.ts +86 -0
  37. package/dist/telemetry/backfill.js +446 -0
  38. package/dist/telemetry/backfill.js.map +1 -0
  39. package/dist/telemetry/read-sink.d.ts +24 -0
  40. package/dist/telemetry/read-sink.js +21 -0
  41. package/dist/telemetry/read-sink.js.map +1 -0
  42. package/dist/telemetry/sink.d.ts +15 -2
  43. package/dist/telemetry/sink.js +17 -3
  44. package/dist/telemetry/sink.js.map +1 -1
  45. package/package.json +1 -1
package/README.md CHANGED
@@ -292,6 +292,63 @@ harness:
292
292
 
293
293
  ---
294
294
 
295
+ ### `rcl evidence status` and `rcl evidence show`
296
+
297
+ What Harness holds — never the client's own claim. `rcl evidence status`
298
+ prints the gate status Harness computed for a pull request: its known head,
299
+ the advisory and enforced projections (status, the rounds behind them, the
300
+ actionable findings still open) and the merge decision once it merged. The
301
+ exit code is the contract the skills gate on:
302
+
303
+ | exit | meaning |
304
+ | --- | --- |
305
+ | 0 | the judged projection (advisory by default, `--enforced` on request) is `converged` |
306
+ | 1 | any other status: `none`, `stale`, `unverified`, `inconclusive`, `fixes_pending`, `unresolved` |
307
+ | 2 | the pull request could not be named |
308
+ | 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" |
309
+
310
+ `rcl evidence show <run id>` prints one recorded run: header and verification,
311
+ credential tier and runner, reviewer health, artifact state, and every finding
312
+ with its identity, gating reason and triage verdict (`—` until the server
313
+ joins the verdict onto the finding).
314
+
315
+ The reads use the same credential rules as delivery — the stored `harness
316
+ login`, or `HARNESS_API_TOKEN` + `HARNESS_API_URL` in CI, the token sent to
317
+ its own host only — and need `reviews:read`. They do not depend on the
318
+ telemetry level: switching delivery off does not blind them.
319
+
320
+ ```bash
321
+ rcl evidence status # error: name the pull request
322
+ rcl evidence status 8524 # against the current checkout's origin remote
323
+ rcl evidence status '#8524' --enforced
324
+ rcl evidence status allocator-one/rcl#42 --json
325
+ rcl evidence status https://github.com/allocator-one/rcl/pull/42
326
+ rcl evidence show 01a08032-0838-76db-ade3-1990f6e54072
327
+ ```
328
+
329
+ ### `--for-pr` on a patch-file review
330
+
331
+ A patch-file review (`rcl review changes.patch`) carries no repository or pull
332
+ request, so Harness records it as a `patch` run it cannot verify or count for
333
+ any gate. `--for-pr owner/repo#N` (or a pull request URL) names the pull
334
+ request the patch was taken from (`RCL_FOR_PR` in the environment does the
335
+ same for patch files): the run is bound to that pull request and its
336
+ `--head-sha` — required with the flag — is verified against the pull
337
+ request's head. Owner and repository are lower-cased, as GitHub reads them. Counting the round for that pull request's
338
+ gate is the server half (IO-12585); until it lands, `rcl evidence status`
339
+ still reads `stale`/`none` for patch-file loops. The flag is refused on PR and
340
+ git-mode targets, which name their own pull request or checkout. `rcl-converge` passes `--converge-target`, `--round`
341
+ and `--attempt` on every round and adds `--for-pr` when a pull request loop
342
+ reviews a patch file taken from the pull request (a pull request target names
343
+ its own). A converge
344
+ target of the `owner/repo#N` form attributes the run the same way; a slug such
345
+ as `rcl-7` does not.
346
+
347
+ ```bash
348
+ rcl review round-3.patch --head-sha "$HEAD" --base-sha "$BASE" --for-pr allocator-one/rcl#42 \
349
+ --converge-target allocator-one/rcl#42 --round 3 --attempt 3 --evidence-required
350
+ ```
351
+
295
352
  ### `rcl models`
296
353
 
297
354
  The tool's own memory of which reviewers earn their seat. Every reviewer call
@@ -316,6 +373,43 @@ rcl models seed --from ~/recovered-rcl-artifacts # backfill from reports + con
316
373
 
317
374
  ---
318
375
 
376
+ Since 3.1 the table merges the organization's window from Harness
377
+ (`GET /api/v1/reviews/model-stats`, the server-side `rcl models` over every run
378
+ the org recorded, backfilled history included) with this machine's store: for a
379
+ model the server holds at least 20 outcomes for, the server's weight is used
380
+ (`source: server`); below that the local store decides (`local`); a model
381
+ neither knows enough about keeps the neutral weight (`neutral`). Reviews weight
382
+ consensus the same way, asking the server with a three-second bound and falling
383
+ back to the local store when it cannot answer. `--local` shows this machine's
384
+ view alone; `--json` carries `server` (host, window, rows) and `weights` with
385
+ their `source`.
386
+
387
+ ```bash
388
+ rcl models # org-wide where Harness has enough history, local otherwise
389
+ rcl models show --local # this machine's store only
390
+ rcl models show --window 30 --json
391
+ ```
392
+
393
+ ### `rcl telemetry backfill`
394
+
395
+ Recovered history becomes day-one evidence on Harness. `rcl telemetry backfill
396
+ --from <dir> --repo <owner/repo>` reads the pre-3.0 `rcl-report-*.json`
397
+ reports and `rcl-converge-*-ledger.md` ledgers in a directory (the same layout
398
+ `rcl models seed` reads) and posts each report as a run with `provenance:
399
+ backfill`: a synthesized header bound to the named repository (target `patch`,
400
+ the report bytes as the digest, a runner claim naming this command), the
401
+ report's findings with their stable identities, its reviewer calls, and the
402
+ report files as artifacts. Ledger bullets matched to a round's findings become
403
+ `verdicts_recorded` events. Run ids are UUIDv5 of `(host, repo, sha256 of the
404
+ report)` and event ids derive from them, so running the backfill twice reports
405
+ the second run as `0 new` — nothing is duplicated. Backfilled runs count for
406
+ model stats and analytics and never enter a gate decision.
407
+
408
+ ```bash
409
+ rcl telemetry backfill --from ~/recovered-rcl-artifacts --repo allocator-one/allocator-one --dry-run
410
+ rcl telemetry backfill --from ~/recovered-rcl-artifacts --repo allocator-one/allocator-one
411
+ ```
412
+
319
413
  ## Config File
320
414
 
321
415
  Place `.review-council.yml` in your project root (or any parent directory). All fields are optional.
@@ -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":"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"}
@@ -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;
@@ -0,0 +1,74 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { GITHUB_OWNER_PATTERN as OWNER, GITHUB_REPO_PATTERN as REPO, isGitHubTarget, legalRepo, parseGitHubTarget, parseRepoName, } from '../resolver/github.js';
4
+ // The repository primitives live with the GitHub target parser; evidence
5
+ // callers keep reaching them from here.
6
+ export { parseRepoName };
7
+ const execFileAsync = promisify(execFile);
8
+ // The scp-like form has no scheme to hand to `URL`; the repository is matched
9
+ // lazily (`+?`) so an optional `.git` suffix is not swallowed into the name.
10
+ const SCP_REMOTE = new RegExp(`^git@github\\.com:(${OWNER})/(${REPO}?)(?:\\.git)?/?$`, 'i');
11
+ const REPO_PATH = new RegExp(`^/(${OWNER})/(${REPO}?)(?:\\.git)?/?$`, 'i');
12
+ /** The GitHub repository a remote URL names, or `null` for anything else. */
13
+ export function parseRemoteUrl(url) {
14
+ const trimmed = url.trim();
15
+ const scp = trimmed.match(SCP_REMOTE);
16
+ if (scp)
17
+ return legalRepo(scp[1], scp[2]);
18
+ // Scheme forms (ssh, https, git) go through `URL`, so user-info (a token,
19
+ // `user:password`) and a port never take part in the match.
20
+ let parsed;
21
+ try {
22
+ parsed = new URL(trimmed);
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ const host = parsed.hostname.toLowerCase();
28
+ if (!/^(?:ssh|https?|git):$/i.test(parsed.protocol) || (host !== 'github.com' && host !== 'www.github.com'))
29
+ return null;
30
+ const path = parsed.pathname.match(REPO_PATH);
31
+ return path ? legalRepo(path[1], path[2]) : null;
32
+ }
33
+ /** The repository `origin` points at, or `null` outside a checkout with a GitHub remote. Never throws. */
34
+ export async function resolveRemoteRepo(cwd = process.cwd()) {
35
+ try {
36
+ const { stdout } = await execFileAsync('git', ['remote', 'get-url', 'origin'], { cwd });
37
+ return parseRemoteUrl(stdout);
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ /** Whether an argument is a bare number or `#N`, which needs the remote to become a pull request. */
44
+ export function needsRemote(arg) {
45
+ return /^#?\d+$/.test(arg.trim());
46
+ }
47
+ export function parsePullRequestArg(arg, remote) {
48
+ const trimmed = arg.trim();
49
+ if (isGitHubTarget(trimmed)) {
50
+ const target = parseGitHubTarget(trimmed);
51
+ // The GitHub parser is shared with `rcl review`; the read side re-checks
52
+ // what it hands back so no other target shape passes as a pull request.
53
+ if (legalRepo(target.owner, target.repo) === null) {
54
+ throw new Error('The repository in the pull request target is not a GitHub owner/repository name.');
55
+ }
56
+ if (!Number.isSafeInteger(target.number) || target.number <= 0) {
57
+ throw new Error(`Cannot read a pull request number from "${trimmed}": use N, #N, owner/repo#N or a pull request URL.`);
58
+ }
59
+ return { owner: target.owner, repo: target.repo, number: target.number };
60
+ }
61
+ const numeric = trimmed.match(/^#?(\d+)$/);
62
+ if (numeric) {
63
+ const number = Number(numeric[1]);
64
+ if (!Number.isSafeInteger(number) || number <= 0) {
65
+ throw new Error(`The pull request number must be positive: "${trimmed}".`);
66
+ }
67
+ if (remote === null) {
68
+ throw new Error(`No GitHub remote to read pull request ${number} against — name it as owner/repo#${number} or as its URL.`);
69
+ }
70
+ return { ...remote, number };
71
+ }
72
+ throw new Error(`Cannot read a pull request from "${trimmed}": use N, #N, owner/repo#N or a pull request URL.`);
73
+ }
74
+ //# sourceMappingURL=target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target.js","sourceRoot":"","sources":["../../src/evidence/target.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EACL,oBAAoB,IAAI,KAAK,EAC7B,mBAAmB,IAAI,IAAI,EAC3B,cAAc,EACd,SAAS,EACT,iBAAiB,EACjB,aAAa,GAEd,MAAM,uBAAuB,CAAC;AAE/B,yEAAyE;AACzE,wCAAwC;AACxC,OAAO,EAAE,aAAa,EAAgB,CAAC;AAYvC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,8EAA8E;AAC9E,6EAA6E;AAC7E,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,sBAAsB,KAAK,MAAM,IAAI,kBAAkB,EAAE,GAAG,CAAC,CAAC;AAC5F,MAAM,SAAS,GAAG,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,IAAI,kBAAkB,EAAE,GAAG,CAAC,CAAC;AAE3E,6EAA6E;AAC7E,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACtC,IAAI,GAAG;QAAE,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAE,EAAE,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC;IAC5C,0EAA0E;IAC1E,4DAA4D;IAC5D,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IAC3C,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,gBAAgB,CAAC;QAAE,OAAO,IAAI,CAAC;IACzH,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9C,OAAO,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAE,EAAE,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACrD,CAAC;AAED,0GAA0G;AAC1G,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IACzD,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QACxF,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,OAAO,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,GAAW,EAAE,MAAsB;IACrE,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC1C,yEAAyE;QACzE,wEAAwE;QACxE,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CAAC,kFAAkF,CAAC,CAAC;QACtG,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YAC/D,MAAM,IAAI,KAAK,CAAC,2CAA2C,OAAO,mDAAmD,CAAC,CAAC;QACzH,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAC3E,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3C,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CAAC,8CAA8C,OAAO,IAAI,CAAC,CAAC;QAC7E,CAAC;QACD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,yCAAyC,MAAM,oCAAoC,MAAM,iBAAiB,CAC3G,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC;IAC/B,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,oCAAoC,OAAO,mDAAmD,CAAC,CAAC;AAClH,CAAC"}