@sfqa/scar 0.0.0-stage → 0.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.
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +863 -2
- package/dist/capture.d.ts +20 -0
- package/dist/capture.js +42 -0
- package/dist/cli.d.ts +80 -0
- package/dist/cli.js +622 -0
- package/dist/harvest.d.ts +82 -0
- package/dist/harvest.js +511 -0
- package/dist/hash.d.ts +1 -0
- package/dist/hash.js +10 -0
- package/dist/ignore.d.ts +17 -0
- package/dist/ignore.js +38 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +14 -0
- package/dist/local.d.ts +17 -0
- package/dist/local.js +163 -0
- package/dist/new-rule.d.ts +39 -0
- package/dist/new-rule.js +209 -0
- package/dist/prove.d.ts +37 -0
- package/dist/prove.js +158 -0
- package/dist/redact.d.ts +3 -0
- package/dist/redact.js +20 -0
- package/dist/report.d.ts +23 -0
- package/dist/report.js +137 -0
- package/dist/rules.d.ts +38 -0
- package/dist/rules.js +166 -0
- package/dist/scan.d.ts +38 -0
- package/dist/scan.js +404 -0
- package/dist/types.d.ts +77 -0
- package/dist/types.js +11 -0
- package/package.json +59 -4
- package/prompts/extract-rules.md +80 -0
- package/prompts/turn-incident-into-rule.md +82 -0
- package/rules/generic.json +518 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ScarRule } from './types.js';
|
|
2
|
+
export declare const DEFAULT_CAPTURE_FILE = "scar-tissue.candidates.json";
|
|
3
|
+
export interface CaptureEntry {
|
|
4
|
+
/** Content fingerprint of the candidate that was captured. */
|
|
5
|
+
fingerprint: string;
|
|
6
|
+
capturedAt: string;
|
|
7
|
+
subject?: string;
|
|
8
|
+
files?: string[];
|
|
9
|
+
/** The draft a person still has to review and prove. */
|
|
10
|
+
draft?: ScarRule;
|
|
11
|
+
}
|
|
12
|
+
export interface CaptureQueue {
|
|
13
|
+
version: number;
|
|
14
|
+
candidates: CaptureEntry[];
|
|
15
|
+
}
|
|
16
|
+
export declare function emptyQueue(): CaptureQueue;
|
|
17
|
+
export declare function loadCaptureQueue(path: string): CaptureQueue;
|
|
18
|
+
export declare function saveCaptureQueue(path: string, queue: CaptureQueue): void;
|
|
19
|
+
export declare function findCapture(queue: CaptureQueue, entryFingerprint: string): CaptureEntry | undefined;
|
|
20
|
+
export declare function addCapture(queue: CaptureQueue, entry: CaptureEntry): void;
|
package/dist/capture.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// packages/scar/src/capture.ts
|
|
2
|
+
// -----------------------------------------------------------------------------
|
|
3
|
+
// The capture queue. A "yes" at commit time means "keep this incident, I will
|
|
4
|
+
// write the rule" - so the decision has to outlive the shell. The queue is
|
|
5
|
+
// committed data, not a cache: a reviewer can see every incident a person
|
|
6
|
+
// promised to turn into a rule, and delete an entry they disagree with.
|
|
7
|
+
//
|
|
8
|
+
// It stores the draft, not the rule. Nothing here is enforced until a person
|
|
9
|
+
// moves it into the rule set and proves it with fixtures.
|
|
10
|
+
// -----------------------------------------------------------------------------
|
|
11
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { dirname } from 'node:path';
|
|
13
|
+
export const DEFAULT_CAPTURE_FILE = 'scar-tissue.candidates.json';
|
|
14
|
+
export function emptyQueue() {
|
|
15
|
+
return { version: 1, candidates: [] };
|
|
16
|
+
}
|
|
17
|
+
export function loadCaptureQueue(path) {
|
|
18
|
+
try {
|
|
19
|
+
const raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
20
|
+
const candidates = Array.isArray(raw.candidates)
|
|
21
|
+
? raw.candidates.filter((entry) => Boolean(entry) && typeof entry.fingerprint === 'string')
|
|
22
|
+
: [];
|
|
23
|
+
return { version: 1, candidates };
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return emptyQueue();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function saveCaptureQueue(path, queue) {
|
|
30
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
31
|
+
writeFileSync(path, `${JSON.stringify(queue, null, 2)}\n`, 'utf8');
|
|
32
|
+
}
|
|
33
|
+
export function findCapture(queue, entryFingerprint) {
|
|
34
|
+
return queue.candidates.find((entry) => entry.fingerprint === entryFingerprint);
|
|
35
|
+
}
|
|
36
|
+
export function addCapture(queue, entry) {
|
|
37
|
+
const existing = queue.candidates.findIndex((item) => item.fingerprint === entry.fingerprint);
|
|
38
|
+
if (existing === -1)
|
|
39
|
+
queue.candidates.push(entry);
|
|
40
|
+
else
|
|
41
|
+
queue.candidates[existing] = { ...queue.candidates[existing], ...entry };
|
|
42
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import type { ReportFormat } from './local.js';
|
|
3
|
+
import { type FailOn } from './report.js';
|
|
4
|
+
import type { Severity } from './types.js';
|
|
5
|
+
export declare const DEFAULT_RULES: string;
|
|
6
|
+
export declare class CliError extends Error {
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
9
|
+
export interface CliOptions {
|
|
10
|
+
rules: string;
|
|
11
|
+
/** True when --rules was passed. Without it, a local scar-tissue.json wins. */
|
|
12
|
+
rulesExplicit: boolean;
|
|
13
|
+
paths: string[];
|
|
14
|
+
base?: string;
|
|
15
|
+
staged: boolean;
|
|
16
|
+
json: boolean;
|
|
17
|
+
markdown: boolean;
|
|
18
|
+
failOn: FailOn;
|
|
19
|
+
maxPerRule: number;
|
|
20
|
+
help: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare const USAGE = "sfqa-scar - review code against past-failure patterns\n\nThe four things you do\n sfqa-scar [options] [paths...] scan files or directories\n sfqa-scar new --rule <sentence> --scope <dir-or-glob>\n start a rule: writes the rule and both fixtures\n sfqa-scar prove [--id ST-020] prove rules against their fixtures\n sfqa-scar harvest --help find incidents that never became rules\n\nScan options\n --base <ref> Review files changed vs a git ref instead of paths\n --staged Review only staged files\n --rules <file> Rule set to load (default: scar-tissue.json if it exists,\n otherwise the packaged template)\n --fail-on <sev> Exit 1 at this severity or higher: high|med|low|any (default: high)\n --json Machine-readable JSON on stdout\n --markdown Markdown table with relative paths, for a PR or a write-up\n --max-per-rule <n> Max lines reported per rule per file (default: 5)\n -h, --help This message\n\nNew rule options (sfqa-scar new)\n --rule <sentence> The rule, imperative: what must not happen again\n --scope <dir|glob> Where the rule may fire; repeatable. A plain directory means\n \"everything under it\". The fixture is placed to match it.\n Required: a rule with no place cannot be proved\n --severity <sev> high|med|low (default: med - high is for lost data, a\n leaked secret, a wrong record or a false green)\n --id <id> Rule id (default: the next free one, e.g. ST-020)\n --fixtures <dir> Fixture root (default: scar-fixtures)\n\nProve options (sfqa-scar prove)\n --id <id> Prove only this rule; repeatable\n --rules <file> Rule set to prove (same default as scan)\n --fixtures <dir> Fixture root (default: scar-fixtures)\n\nRun sfqa-scar harvest --help for the history and report options.\n";
|
|
23
|
+
export declare const HARVEST_USAGE = "sfqa-scar harvest - propose rules for incidents no rule claims\n\n sfqa-scar harvest [options] read git history for incident-shaped commits\n sfqa-scar harvest --staged read one staged change instead\n\nOptions\n --since <date> Only read commits after this date (git --since)\n --limit <n> Max commits read (default: 300)\n --all Also show covered and dismissed incidents\n --write <file> Write candidates as JSON, for review or a PR comment\n --strict Exit 1 when any incident-shaped change has no rule\n --staged Inspect the staged change instead of history\n --message <text> Commit message for --staged\n --message-file <f> Read the commit message from a file (commit-msg hook)\n --force Treat a staged change as an incident without a keyword\n --report <path> Read a Playwright JSON / JUnit XML report (repeatable)\n --report-format <f> auto|playwright|junit (default: auto)\n --ask Prompt to capture or ignore each staged candidate\n --capture <id> Record a captured candidate in the capture queue\n --ignore <id> Record a dismissal in the ignore ledger\n --rules <file> Rule set to check candidates against\n --ignore-file <f> Ledger path (default: scar-tissue.ignore.json)\n --capture-file <f> Queue path (default: scar-tissue.candidates.json)\n -h, --help This message\n\nA candidate is a commit whose message says fix, revert, hotfix, flaky, timeout,\nrace, regression or credential, and that no rule in your rule set mentions.\n";
|
|
24
|
+
export declare function parseArgs(argv: readonly string[], defaultRules?: string): CliOptions;
|
|
25
|
+
export interface HarvestCliOptions {
|
|
26
|
+
rules: string;
|
|
27
|
+
rulesExplicit: boolean;
|
|
28
|
+
since?: string;
|
|
29
|
+
limit: number;
|
|
30
|
+
json: boolean;
|
|
31
|
+
all: boolean;
|
|
32
|
+
write?: string;
|
|
33
|
+
strict: boolean;
|
|
34
|
+
help: boolean;
|
|
35
|
+
staged: boolean;
|
|
36
|
+
message?: string;
|
|
37
|
+
messageFile?: string;
|
|
38
|
+
force: boolean;
|
|
39
|
+
reports: string[];
|
|
40
|
+
reportFormat: ReportFormat;
|
|
41
|
+
ignoreFile: string;
|
|
42
|
+
ignore: string[];
|
|
43
|
+
captureFile: string;
|
|
44
|
+
capture: string[];
|
|
45
|
+
ask: boolean;
|
|
46
|
+
}
|
|
47
|
+
export declare function parseHarvestArgs(argv: readonly string[], defaultRules?: string): HarvestCliOptions;
|
|
48
|
+
export type Ask = (question: string) => Promise<string>;
|
|
49
|
+
/**
|
|
50
|
+
* Advisory command. Exit 0 by default even when it finds gaps, because a gap is
|
|
51
|
+
* information, not a failed scan. `--strict` makes a gap fail CI.
|
|
52
|
+
*
|
|
53
|
+
* `--ask` prompts only for a staged change, because that is the one moment the
|
|
54
|
+
* person still remembers the incident. History is reviewed in bulk instead.
|
|
55
|
+
*/
|
|
56
|
+
export declare function runHarvest(options: HarvestCliOptions, cwd?: string, ask?: Ask): Promise<number>;
|
|
57
|
+
export interface NewCliOptions {
|
|
58
|
+
rule?: string;
|
|
59
|
+
scope: string[];
|
|
60
|
+
severity?: Severity;
|
|
61
|
+
id?: string;
|
|
62
|
+
rules?: string;
|
|
63
|
+
fixtures: string;
|
|
64
|
+
help: boolean;
|
|
65
|
+
}
|
|
66
|
+
export declare function parseNewArgs(argv: readonly string[]): NewCliOptions;
|
|
67
|
+
/** Write one rule and its two fixtures. Returns 0; a bad request is a usage error. */
|
|
68
|
+
export declare function runNew(options: NewCliOptions, cwd?: string): number;
|
|
69
|
+
export interface ProveCliOptions {
|
|
70
|
+
ids: string[];
|
|
71
|
+
rules?: string;
|
|
72
|
+
fixtures: string;
|
|
73
|
+
help: boolean;
|
|
74
|
+
}
|
|
75
|
+
export declare function parseProveArgs(argv: readonly string[]): ProveCliOptions;
|
|
76
|
+
/** Prove rules against their fixtures. 0 all proven, 1 a proof failed, 2 bad input. */
|
|
77
|
+
export declare function runProve(options: ProveCliOptions, cwd?: string): number;
|
|
78
|
+
/** Run the scanner. Returns a process exit code: 0 clean, 1 findings, 2 usage. */
|
|
79
|
+
export declare function run(options: CliOptions, cwd?: string): number;
|
|
80
|
+
export declare function main(argv?: readonly string[]): Promise<number>;
|