@0xcraft/powershot 1.1.0 → 1.1.2

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 CHANGED
@@ -20,13 +20,14 @@ PowerShot reviews the failure modes that plausible-looking generated code tends
20
20
  hide: invented APIs, undeclared dependencies, dropped guards, swallowed errors,
21
21
  tests that prove nothing, bent expectations, stale callers, and duplicated helpers.
22
22
 
23
- It asks compilers, parsers, manifests, reference graphs, and pre/post ASTs first.
24
- Optional model judges only handle questions that still require judgement.
23
+ It asks self-contained parsers, manifests, and pre/post ASTs first, then uses compiler
24
+ types and reference graphs when the environment can supply them. Optional model judges
25
+ only handle questions that still require judgement.
25
26
 
26
27
  <table>
27
28
  <tr>
28
29
  <td width="33%"><strong>Deterministic first</strong><br>Local checks need no model, key, tokens, or network calls.</td>
29
- <td width="33%"><strong>Honest completion</strong><br>A partial run cannot present itself as a clean verdict.</td>
30
+ <td width="33%"><strong>Honest coverage</strong><br>Portable, full, partial, and failed runs stay distinguishable.</td>
30
31
  <td width="33%"><strong>CI-native output</strong><br>One run emits terminal, Markdown, JSON, SARIF, manifest, and Code Quality reports.</td>
31
32
  </tr>
32
33
  </table>
@@ -89,7 +90,7 @@ flowchart LR
89
90
 
90
91
  1. **Snapshot** resolves the exact tree the review is about.
91
92
  2. **Ground** builds the available type, syntax, dependency, and reference oracles.
92
- 3. **Plan** assigns checks and missing capabilities to each file individually.
93
+ 3. **Plan** assigns baseline checks and enriched semantic capabilities to each file individually.
93
94
  4. **Verify** runs deterministic checks and records what actually executed.
94
95
  5. **Judge** optionally reviews bounded bundles of related files.
95
96
  6. **Manifest** decides whether the result is complete, partial, or failed.
@@ -108,8 +109,11 @@ Every finding says where it came from:
108
109
  | `verified` | `firm` | A deterministic heuristic fired; inspect the evidence |
109
110
  | `judged` | `firm` or `tentative` | A model supplied the judgement and provenance |
110
111
 
111
- PowerShot stays silent when a required oracle is unavailable. The missing capability
112
- is written to the manifest instead of being counted as a pass.
112
+ Portable coverage is the default: self-contained oracles run without bootstrapping the
113
+ reviewed repository, while unavailable compiler/reference depth stays visible in the
114
+ manifest and reports. Set `"coverage": "strict"`, or explicitly select a check with
115
+ `--checks`, when a missing semantic oracle must make the run partial. An unavailable
116
+ oracle is never counted as a pass in either profile.
113
117
 
114
118
  ## Deterministic checks
115
119
 
@@ -174,33 +178,38 @@ commands.
174
178
 
175
179
  ```mermaid
176
180
  flowchart LR
177
- START["Selected files and checks"] --> ACCOUNT{"Everything accounted for?"}
178
- ACCOUNT -- "yes" --> FINDINGS{"Findings?"}
181
+ START["Selected files and checks"] --> ACCOUNT{"Required work accounted for?"}
182
+ ACCOUNT -- "yes" --> DEPTH{"Enriched semantic depth available?"}
183
+ DEPTH -- "yes" --> FULL["full coverage"]
184
+ DEPTH -- "no · portable policy" --> PORTABLE["portable coverage · gaps named"]
185
+ FULL --> FINDINGS{"Findings?"}
186
+ PORTABLE --> FINDINGS
179
187
  FINDINGS -- "no" --> CLEAN["exit 0 · complete and clean"]
180
188
  FINDINGS -- "yes" --> FOUND["exit 1 · complete with findings"]
181
- ACCOUNT -- "capability or budget gap" --> PARTIAL["exit 3 · partial"]
189
+ ACCOUNT -- "required oracle or budget gap" --> PARTIAL["exit 3 · partial"]
182
190
  ACCOUNT -- "required stage failed" --> FAILED["exit 3 · failed"]
183
191
 
184
192
  classDef neutral fill:#172033,stroke:#57a6ff,color:#f0f6fc,stroke-width:2px
185
193
  classDef good fill:#17251f,stroke:#4ac58b,color:#f0f6fc,stroke-width:2px
186
194
  classDef warn fill:#2a2117,stroke:#f2b84b,color:#f0f6fc,stroke-width:2px
187
195
  classDef bad fill:#2a191b,stroke:#ff675c,color:#f0f6fc,stroke-width:2px
188
- class START,ACCOUNT,FINDINGS neutral
196
+ class START,ACCOUNT,DEPTH,FINDINGS neutral
189
197
  class CLEAN good
190
- class FOUND,PARTIAL warn
198
+ class FULL,PORTABLE,FOUND,PARTIAL warn
191
199
  class FAILED bad
192
200
  ```
193
201
 
194
202
  | Exit | Contract |
195
203
  |---:|---|
196
- | `0` | Review completed and found nothing at the selected severity |
197
- | `1` | Review completed and reported findings |
204
+ | `0` | Review completed in full or portable coverage and found nothing at the selected severity |
205
+ | `1` | Review completed in full or portable coverage and reported findings |
198
206
  | `2` | Command or Git input was invalid |
199
207
  | `3` | Review is incomplete; findings may be missing |
200
208
 
201
- “No findings and “PowerShot could not look” are intentionally different pipeline
202
- outcomes. Use `--format manifest` to inspect file dispositions, executed checks,
203
- skips, failures, judge units, and `notLookedAt`.
209
+ “No findings in portable coverage”, “No findings”, and “PowerShot could not look” are
210
+ intentionally different outcomes. Use `--format manifest` to inspect `coverage`, file
211
+ dispositions, executed and unavailable checks, failures, judge units, and
212
+ `notLookedAt`.
204
213
 
205
214
  ## CI integration
206
215
 
@@ -220,6 +229,18 @@ The composite action is the shortest setup for GitHub:
220
229
  fail-on-findings: 'true'
221
230
  ```
222
231
 
232
+ The default portable profile needs no install from the checked-out repository. That is
233
+ the safe default for private monorepos and fork pull requests: do not expose a package
234
+ registry credential merely to enrich a review of untrusted code. If a trusted job
235
+ already has dependencies, PowerShot uses their TypeScript declarations automatically.
236
+ Set `"coverage": "strict"` when missing compiler/reference oracles must block instead.
237
+
238
+ PowerShot discovers `tsconfig.json` and `tsconfig.*.json` along the ancestor chain of
239
+ each changed file. One review can use several independent package projects, skip
240
+ empty solution configs in favour of their leaf configs, and type-check test files
241
+ that a production config excludes. Discovery is change-scoped: unrelated packages
242
+ and configless source trees are not crawled just to build the TypeScript ground.
243
+
223
244
  `@v1` follows compatible `1.x` releases. Pin the action to a full commit SHA in a
224
245
  protected required workflow when immutable dependencies are required.
225
246
 
@@ -252,6 +273,7 @@ Anthropic, OpenAI, and Gemini providers are supported.
252
273
  "judges": {
253
274
  "enable": ["plausible-logic", "test-adequacy", "intent"]
254
275
  },
276
+ "coverage": "portable",
255
277
  "minSeverity": "low",
256
278
  "ignore": ["**/generated/**"],
257
279
  "promptCache": true
@@ -286,8 +308,11 @@ psh review --verify-only --absorb /tmp/powershot-findings.json
286
308
  | C | Syntax-backed checks that do not require exception semantics |
287
309
  | Solidity | Declared syntax-backed checks |
288
310
 
289
- PowerShot loads a bounded number of grammars per run. Unsupported or unavailable
290
- coverage is named in the manifest.
311
+ Every declared language is parsed in disposable, language-isolated workers. Sources
312
+ are sent in bounded batches, so a mixed-language monorepo does not accumulate every
313
+ compiled WASM grammar in one process. If a declared parser cannot run, the review
314
+ fails loudly; it is never silently waived. All eleven packs plus TypeScript and
315
+ JavaScript are exercised together by the integration suite.
291
316
 
292
317
  ## Project guide
293
318
 
@@ -1,4 +1,5 @@
1
1
  import { writeFileSync } from 'node:fs';
2
+ import { unavailableCoverage } from '#app/manifest.js';
2
3
  import { dim } from '#app/report/ansi.js';
3
4
  import { codeQuality } from '#app/report/codequality.js';
4
5
  import { compact } from '#app/report/compact.js';
@@ -44,6 +45,8 @@ export function renderReport(format, result, manifest, target) {
44
45
  ...result.stats,
45
46
  state: manifest.state,
46
47
  notLookedAt: manifest.notLookedAt,
48
+ coverage: manifest.coverage,
49
+ unavailableCoverage: unavailableCoverage(manifest),
47
50
  });
48
51
  }
49
52
  export function publishReports(options) {
@@ -5,7 +5,7 @@ import { loadConfig, policyChanged } from '#app/config.js';
5
5
  import { absorbDelegated, delegateBrief } from '#app/delegate.js';
6
6
  import { baseRefOf, checkRange, headSha, repoRoot, shaOf } from '#app/git.js';
7
7
  import { JUDGES } from '#app/judges/prompts.js';
8
- import { RunManifest, coverageProblems, hashOf, writeManifest } from '#app/manifest.js';
8
+ import { RunManifest, coverageProblems, hashOf, unavailableCoverage, writeManifest } from '#app/manifest.js';
9
9
  import { Trace } from '#app/otel.js';
10
10
  import { PACKAGE_VERSION } from '#app/package-meta.js';
11
11
  import { dim, yellow } from '#app/report/ansi.js';
@@ -225,6 +225,7 @@ export async function runReviewCommand(command, values, positionals) {
225
225
  },
226
226
  files: result.plan?.items() ?? [],
227
227
  skippedChecks: result.skippedChecks ?? [],
228
+ unavailableChecks: result.unavailableChecks ?? [],
228
229
  findings: {
229
230
  total: result.findings.length,
230
231
  verified: result.stats.verified,
@@ -246,7 +247,12 @@ export async function runReviewCommand(command, values, positionals) {
246
247
  record.notLookedAt.push(failure);
247
248
  process.stderr.write(yellow(' ◇ manifest') + dim(' ' + gaps.join('; ')) + '\n');
248
249
  }
249
- session?.saveReport(result.findings, { state: record.state, notLookedAt: record.notLookedAt });
250
+ session?.saveReport(result.findings, {
251
+ state: record.state,
252
+ notLookedAt: record.notLookedAt,
253
+ coverage: record.coverage,
254
+ unavailableCoverage: unavailableCoverage(record),
255
+ });
250
256
  writeManifest(root, record);
251
257
  publishReports({
252
258
  format: values.format,
@@ -42,6 +42,8 @@ export function runSessionCommand(positionals) {
42
42
  started: session.started,
43
43
  state: session.report.state ?? 'unknown',
44
44
  notLookedAt: session.report.notLookedAt ?? ['session predates verdict recording'],
45
+ coverage: session.report.coverage,
46
+ unavailableCoverage: session.report.unavailableCoverage,
45
47
  }));
46
48
  process.stdout.write(output + '\n');
47
49
  return 0;
package/dist/config.js CHANGED
@@ -9,6 +9,7 @@ const DEFAULTS = {
9
9
  // security may duplicate SAST; convention needs repository idioms in the diff
10
10
  judges: ['plausible-logic', 'test-adequacy', 'intent'],
11
11
  minSeverity: 'low',
12
+ coverage: 'portable',
12
13
  // findings in vendored trees are not decisions made by this repository
13
14
  ignore: [
14
15
  '**/node_modules/**', '**/dist/**', '**/build/**', '**/*.generated.*', '**/*.min.js',
@@ -33,6 +34,7 @@ export function policyChanged(root, baseRef) {
33
34
  }
34
35
  const KEYS = new Set([...Object.keys(DEFAULTS), 'checks']);
35
36
  const PROVIDERS = new Set(['anthropic', 'openai', 'gemini']);
37
+ const COVERAGE = new Set(['portable', 'strict']);
36
38
  /** A misspelled name in the config is the quietest way to get a clean review. */
37
39
  export function validateConfig(raw, known) {
38
40
  const problems = [];
@@ -50,6 +52,9 @@ export function validateConfig(raw, known) {
50
52
  if (raw.minSeverity !== undefined && !SEVERITIES.includes(raw.minSeverity)) {
51
53
  problems.push('minSeverity "' + String(raw.minSeverity) + '" is not one of: ' + SEVERITIES.join(', '));
52
54
  }
55
+ if (raw.coverage !== undefined && !COVERAGE.has(String(raw.coverage))) {
56
+ problems.push('coverage "' + String(raw.coverage) + '" is not one of: ' + [...COVERAGE].join(', '));
57
+ }
53
58
  for (const [field, names] of [['verifiers', known.verifiers], ['judges', known.judges]]) {
54
59
  const value = raw[field];
55
60
  if (value === undefined)