@0xcraft/powershot 1.1.2 → 1.1.3

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.
@@ -1,4 +1,4 @@
1
- import { unavailableCoverage } from '#app/manifest.js';
1
+ import { modeNote, noFindingsLabel, scopeLine, } from './summary.js';
2
2
  const MARK = { verified: '▣', judged: '▚' };
3
3
  /** Untrusted prose encoded as literal CommonMark text. */
4
4
  function text(s) {
@@ -51,6 +51,26 @@ function group(findings) {
51
51
  list.sort((a, b) => a.line - b.line);
52
52
  return out;
53
53
  }
54
+ function runSummary(run) {
55
+ const out = [];
56
+ const scope = scopeLine(run);
57
+ const mode = modeNote(run, '`verify-only`');
58
+ const details = run.state === 'complete'
59
+ ? run.scopeDetails ?? []
60
+ : [...run.notLookedAt, ...(run.scopeDetails ?? [])];
61
+ if (scope)
62
+ out.push(scope, '');
63
+ if (mode)
64
+ out.push(mode, '');
65
+ if (details.length > 0) {
66
+ out.push('<details>', '<summary>' +
67
+ (run.state !== 'complete'
68
+ ? 'Why this is not a verdict'
69
+ : run.coverage === 'portable' ? 'Coverage details' : 'Review scope') +
70
+ '</summary>', '', ...details.map((detail) => '- ' + text(detail)), '', '</details>', '');
71
+ }
72
+ return out;
73
+ }
54
74
  export function markdown(findings, run) {
55
75
  // "No findings" from a run that could not look is the one thing this must never
56
76
  // say on its own — the reader takes a comment at face value, and a red job beside
@@ -59,33 +79,25 @@ export function markdown(findings, run) {
59
79
  const banner = incomplete
60
80
  ? [
61
81
  '> [!WARNING]',
62
- '> **This review is ' + text(run.state) + ' — not a verdict.** Something was not looked at:',
63
- ...run.notLookedAt.slice(0, 8).map((f) => '> - ' + text(f)),
64
- '',
65
- ]
66
- : [];
67
- const portable = run?.state === 'complete' && run.coverage === 'portable';
68
- const coverage = portable
69
- ? [
70
- '> [!NOTE]',
71
- '> **Portable coverage.** Self-contained oracles ran; enriched semantic depth was unavailable:',
72
- ...unavailableCoverage(run).map((reason) => '> - ' + text(reason)),
82
+ '> **This review is ' + text(run.state) + ' — not a verdict.** Some files or checks were not reviewed.',
73
83
  '',
74
84
  ]
75
85
  : [];
86
+ const summary = run ? runSummary(run) : [];
76
87
  if (findings.length === 0) {
77
88
  return [
78
- '## PowerShot', '', ...banner, ...coverage,
89
+ '## PowerShot', '', ...banner,
79
90
  incomplete
80
91
  ? 'No findings *from what it managed to review*.'
81
- : portable ? 'No findings in portable coverage.' : 'No findings.',
82
- '',
92
+ : run ? ' **' + noFindingsLabel(run) + '**' : 'No findings.',
93
+ '', ...summary,
83
94
  ].join('\n');
84
95
  }
85
96
  const verified = findings.filter((f) => f.class === 'verified').length;
86
97
  const judged = findings.length - verified;
87
- const out = ['## PowerShot', '', ...banner, ...coverage];
98
+ const out = ['## PowerShot', '', ...banner];
88
99
  out.push('**' + verified + ' verified** (deterministic, 0 tokens) · **' + judged + ' judged** (agent)', '');
100
+ out.push(...summary);
89
101
  for (const [file, list] of group(findings)) {
90
102
  out.push('### `' + path(file) + '`', '');
91
103
  for (const f of list) {
@@ -0,0 +1,103 @@
1
+ function plural(count, singular, pluralForm = singular + 's') {
2
+ return count + ' ' + (count === 1 ? singular : pluralForm);
3
+ }
4
+ function capabilityName(capability) {
5
+ return {
6
+ types: 'type information',
7
+ references: 'a reference graph',
8
+ 'python-types': 'Python type information',
9
+ base: 'a base revision',
10
+ syntax: 'a syntax tree',
11
+ }[capability] ?? capability;
12
+ }
13
+ function naturalList(values, conjunction = 'and') {
14
+ if (values.length < 2)
15
+ return values[0] ?? '';
16
+ if (values.length === 2)
17
+ return values[0] + ' ' + conjunction + ' ' + values[1];
18
+ return values.slice(0, -1).join(', ') + ', ' + conjunction + ' ' + values.at(-1);
19
+ }
20
+ function scopeDetails(record) {
21
+ const details = [];
22
+ const selected = (record.files ?? []).filter((file) => file.disposition === 'selected');
23
+ const unavailableGroups = new Map();
24
+ for (const file of selected) {
25
+ if (!file.unavailable?.length)
26
+ continue;
27
+ const order = ['types', 'references', 'python-types', 'base', 'syntax'];
28
+ const capabilities = [...new Set(file.unavailable)].sort((left, right) => {
29
+ const leftRank = order.indexOf(left);
30
+ const rightRank = order.indexOf(right);
31
+ return (leftRank === -1 ? order.length : leftRank) - (rightRank === -1 ? order.length : rightRank) ||
32
+ left.localeCompare(right);
33
+ });
34
+ const key = capabilities.join('\0');
35
+ const group = unavailableGroups.get(key) ?? { capabilities, count: 0 };
36
+ group.count++;
37
+ unavailableGroups.set(key, group);
38
+ }
39
+ for (const group of unavailableGroups.values()) {
40
+ const capabilities = naturalList(group.capabilities.map(capabilityName));
41
+ details.push(plural(group.count, 'reviewed file') + ' lacked ' + capabilities + '.');
42
+ }
43
+ const unavailableChecks = record.checks?.unavailable ?? [];
44
+ if (unavailableChecks.length > 0) {
45
+ const shown = unavailableChecks.slice(0, 8).map((check) => check.check);
46
+ const requirements = [...new Set(unavailableChecks.flatMap((check) => check.missing.split(/,\s*/)))]
47
+ .map(capabilityName);
48
+ details.push(plural(unavailableChecks.length, 'check') + ' requiring ' + naturalList(requirements, 'or') +
49
+ ' did not run: ' + shown.join(', ') +
50
+ (unavailableChecks.length > shown.length ? ', and ' + (unavailableChecks.length - shown.length) + ' more' : '') + '.');
51
+ }
52
+ const waived = new Map();
53
+ for (const file of record.files ?? []) {
54
+ if (file.disposition !== 'waived')
55
+ continue;
56
+ const reason = file.reason ?? 'unspecified reason';
57
+ waived.set(reason, (waived.get(reason) ?? 0) + 1);
58
+ }
59
+ for (const [reason, count] of waived) {
60
+ details.push(plural(count, 'changed file') + ' not reviewed: ' + reason + '.');
61
+ }
62
+ return details;
63
+ }
64
+ export function summarizeRun(record) {
65
+ const hasFiles = record.files !== undefined;
66
+ const hasChecks = record.checks?.ran !== undefined;
67
+ return {
68
+ state: record.state,
69
+ notLookedAt: [...record.notLookedAt],
70
+ coverage: record.coverage,
71
+ verifyOnly: record.engine?.verifyOnly,
72
+ minSeverity: record.engine?.minSeverity,
73
+ filesReviewed: hasFiles
74
+ ? record.files.filter((file) => file.disposition === 'selected').length
75
+ : undefined,
76
+ deterministicChecks: hasChecks ? new Set(record.checks.ran).size : undefined,
77
+ scopeDetails: scopeDetails(record),
78
+ };
79
+ }
80
+ export function noFindingsLabel(summary) {
81
+ const threshold = summary.minSeverity === undefined || summary.minSeverity === 'info'
82
+ ? ''
83
+ : summary.minSeverity === 'critical'
84
+ ? 'critical '
85
+ : summary.minSeverity + '-or-higher ';
86
+ const origin = summary.verifyOnly === true ? 'deterministic ' : '';
87
+ return 'No ' + threshold + origin + 'findings';
88
+ }
89
+ export function scopeLine(summary) {
90
+ const parts = [];
91
+ if (summary.filesReviewed !== undefined)
92
+ parts.push(plural(summary.filesReviewed, 'file') + ' reviewed');
93
+ if (summary.deterministicChecks !== undefined) {
94
+ parts.push(plural(summary.deterministicChecks, 'deterministic check'));
95
+ }
96
+ if (summary.coverage !== undefined)
97
+ parts.push(summary.coverage + ' coverage');
98
+ return parts.length > 0 ? parts.join(' · ') : undefined;
99
+ }
100
+ export function modeNote(summary, verifyOnly = 'verify-only') {
101
+ return summary.verifyOnly === true ? 'Model review was disabled (' + verifyOnly + ').' : undefined;
102
+ }
103
+ //# sourceMappingURL=summary.js.map
@@ -1,5 +1,6 @@
1
1
  import { bold, brightRed, dim, gray, red, steel, yellow } from './ansi.js';
2
2
  import { highlight, isJsx } from './highlight.js';
3
+ import { modeNote, noFindingsLabel, scopeLine } from './summary.js';
3
4
  const SEVERITY_COLOR = {
4
5
  critical: brightRed,
5
6
  high: red,
@@ -106,17 +107,22 @@ export function terminal(findings, opts) {
106
107
  out.push(' ' + bold('PowerShot') + dim(' · ' + opts.subtitle));
107
108
  out.push(rule);
108
109
  const incomplete = opts.state !== 'complete';
109
- const portable = !incomplete && opts.coverage === 'portable';
110
+ const scope = scopeLine(opts);
111
+ const mode = modeNote(opts);
110
112
  if (findings.length === 0) {
111
113
  out.push('', incomplete
112
114
  ? ' ' + yellow('!') + ' No findings — but this review is ' + opts.state + ', not a verdict.'
113
- : ' ' + steel('✔') + (portable ? ' No findings in portable coverage.' : ' No findings.'));
115
+ : ' ' + steel('✔') + ' ' + noFindingsLabel(opts) + '.');
114
116
  for (const reason of opts.notLookedAt)
115
117
  out.push(dim(' ' + reason));
116
- if (portable) {
117
- out.push(dim(' Portable coverage: self-contained oracles ran; enriched semantic depth was unavailable.'));
118
- for (const reason of opts.unavailableCoverage ?? [])
119
- out.push(dim(' ' + reason));
118
+ if (scope)
119
+ out.push(dim(' ' + scope));
120
+ if (mode)
121
+ out.push(dim(' ' + mode));
122
+ if ((opts.scopeDetails?.length ?? 0) > 0) {
123
+ out.push(dim(' ' + (opts.coverage === 'portable' ? 'Coverage details:' : 'Review scope:')));
124
+ for (const detail of opts.scopeDetails)
125
+ out.push(dim(' - ' + detail));
120
126
  }
121
127
  out.push('');
122
128
  return out.join('\n');
@@ -152,11 +158,12 @@ export function terminal(findings, opts) {
152
158
  for (const reason of opts.notLookedAt)
153
159
  out.push(dim(' ' + reason));
154
160
  }
155
- else if (portable) {
156
- out.push(' ' + steel('◇ portable coverage') + dim(' · enriched semantic depth was unavailable'));
157
- for (const reason of opts.unavailableCoverage ?? [])
158
- out.push(dim(' ' + reason));
159
- }
161
+ if (scope)
162
+ out.push(' ' + steel('◇ ') + dim(scope));
163
+ if (mode)
164
+ out.push(dim(' ' + mode));
165
+ for (const detail of opts.scopeDetails ?? [])
166
+ out.push(dim(' ' + detail));
160
167
  out.push('');
161
168
  return out.join('\n');
162
169
  }
@@ -1,20 +1,30 @@
1
+ import { modeNote, noFindingsLabel, scopeLine } from './summary.js';
1
2
  const escape = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
2
3
  export function viewer(findings, meta) {
3
4
  const verified = findings.filter((f) => f.class === 'verified').length;
4
5
  const incomplete = meta.state !== 'complete';
5
- const portable = !incomplete && meta.coverage === 'portable';
6
+ const scope = scopeLine(meta);
7
+ const mode = modeNote(meta);
6
8
  const warning = incomplete
7
9
  ? '<div class="warning"><strong>This review is ' + escape(meta.state) + ' — not a verdict.</strong>' +
8
10
  (meta.notLookedAt.length > 0
9
11
  ? '<ul>' + meta.notLookedAt.map((reason) => '<li>' + escape(reason) + '</li>').join('') + '</ul>'
10
12
  : '') + '</div>'
11
13
  : '';
12
- const coverage = portable
13
- ? '<div class="coverage"><strong>Portable coverage.</strong> Self-contained oracles ran; enriched semantic depth was unavailable.' +
14
- ((meta.unavailableCoverage?.length ?? 0) > 0
15
- ? '<ul>' + meta.unavailableCoverage.map((reason) => '<li>' + escape(reason) + '</li>').join('') + '</ul>'
14
+ const context = scope || mode || (meta.scopeDetails?.length ?? 0) > 0
15
+ ? '<div class="coverage">' +
16
+ (scope ? '<strong>' + escape(scope) + '</strong>' : '') +
17
+ (mode ? '<p>' + escape(mode) + '</p>' : '') +
18
+ ((meta.scopeDetails?.length ?? 0) > 0
19
+ ? '<details><summary>' + (meta.coverage === 'portable' ? 'Coverage details' : 'Review scope') + '</summary><ul>' +
20
+ meta.scopeDetails.map((detail) => '<li>' + escape(detail) + '</li>').join('') +
21
+ '</ul></details>'
16
22
  : '') + '</div>'
17
23
  : '';
24
+ const verdict = findings.length === 0
25
+ ? '<p class="none">' +
26
+ (incomplete ? 'No findings from what completed.' : escape(noFindingsLabel(meta)) + '.') + '</p>'
27
+ : '';
18
28
  const rows = findings
19
29
  .map((f) => {
20
30
  const frame = f.frame
@@ -91,16 +101,15 @@ export function viewer(findings, meta) {
91
101
  <p class="meta">${escape(meta.target)} · ${escape(meta.started.slice(0, 19).replace('T', ' '))} · session ${escape(meta.id)}<br>
92
102
  ${findings.length} finding(s) — ${verified} verified, ${findings.length - verified} judged</p>
93
103
  ${warning}
94
- ${coverage}
104
+ ${verdict}
105
+ ${context}
95
106
  <div class="bar">
96
107
  <button data-filter="all" aria-pressed="true">all</button>
97
108
  <button data-filter="verified" aria-pressed="false">verified</button>
98
109
  <button data-filter="judged" aria-pressed="false">judged</button>
99
110
  <button id="show-away" aria-pressed="false">show put away</button>
100
111
  </div>
101
- ${findings.length === 0
102
- ? '<p class="none">' + (incomplete ? 'No findings from what completed.' : portable ? 'No findings in portable coverage.' : 'No findings.') + '</p>'
103
- : rows}
112
+ ${findings.length === 0 ? '' : rows}
104
113
  </div>
105
114
  <script>
106
115
  // Putting a finding away is per-reader and per-browser on purpose: this page is a