promptwheel 0.7.5 → 0.7.6
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/INTEGRATE.md +3 -1
- package/bin/promptwheel.d.ts +306 -0
- package/bin/promptwheel.mjs +14 -1
- package/package.json +6 -2
- package/packs/security/README.md +14 -4
package/INTEGRATE.md
CHANGED
|
@@ -103,7 +103,9 @@ measurement runs in a throwaway git worktree.
|
|
|
103
103
|
|
|
104
104
|
Zero dependencies, no build step, the whole engine is one file. `check()` runs the gate in
|
|
105
105
|
process and returns the Section 2 object. It never prints and never calls `process.exit`; it
|
|
106
|
-
throws a plain `Error` on a precondition failure.
|
|
106
|
+
throws a plain `Error` on a precondition failure. Types ship in the package
|
|
107
|
+
(`bin/promptwheel.d.ts`, hand-written, no build step): `import type { Report, CheckOptions, Config } from 'promptwheel'`
|
|
108
|
+
works in TypeScript, and editors complete the options and the report in plain JavaScript.
|
|
107
109
|
|
|
108
110
|
```js
|
|
109
111
|
import { check, verdictExitCode, conformsToContract, VERDICTS, CONTRACT_VERSION } from 'promptwheel';
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// Type declarations for bin/promptwheel.mjs. Hand-written: there is no build step and no
|
|
2
|
+
// TypeScript dependency in this package. Every runtime export of the engine is declared here
|
|
3
|
+
// (test/promptwheel.test.mjs checks the list stays complete). The report shape is the
|
|
4
|
+
// Verified-Outcome Contract's Section 2 verdict object; see INTEGRATE.md for the integration
|
|
5
|
+
// surface and CONTRACT.md for the protocol.
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Section 2 vocabularies
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
/** The verdict of a check. */
|
|
12
|
+
export type Verdict = 'pass' | 'fail' | 'gamed' | 'inconclusive';
|
|
13
|
+
|
|
14
|
+
/** The status of one metric. A delta inside the measured noise band is `inconclusive`, never a regression. */
|
|
15
|
+
export type MetricStatus = 'improved' | 'regressed' | 'unchanged' | 'inconclusive' | 'unmeasurable';
|
|
16
|
+
|
|
17
|
+
/** The direction that counts as better. `pass` is a 0/1 gate (1 = the command exited 0). */
|
|
18
|
+
export type Direction = 'up' | 'down' | 'pass';
|
|
19
|
+
|
|
20
|
+
/** How much a delta can be trusted. `none` only on an unmeasurable metric; `unverified` is a single read with no noise sample. */
|
|
21
|
+
export type Confidence = 'none' | 'unverified' | 'low' | 'medium' | 'high';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* How a metric's value is read from its command: the last number in stdout (`number`, the
|
|
25
|
+
* default), the count of non-empty lines (`lines`), the exit code as 1/0 (`exit`), or the
|
|
26
|
+
* first capture group of a regex. A command that exits nonzero reads `null` (unmeasurable)
|
|
27
|
+
* unless the metric sets `allowNonzero`; `exit` mode consumes the exit code by design.
|
|
28
|
+
*/
|
|
29
|
+
export type ExtractMode = 'number' | 'lines' | 'exit' | { regex: string };
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Config (promptwheel.config.json, or the `config` option of check())
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
export interface MetricConfig {
|
|
36
|
+
name: string;
|
|
37
|
+
/** Shell command run in a throwaway worktree at each ref. */
|
|
38
|
+
cmd: string;
|
|
39
|
+
/** Default `'number'`. */
|
|
40
|
+
extract?: ExtractMode;
|
|
41
|
+
/** Default `'up'`. */
|
|
42
|
+
direction?: Direction;
|
|
43
|
+
/** A guarded metric fails the gate on a trusted regression; an unguarded one is tracked only. */
|
|
44
|
+
guard?: boolean;
|
|
45
|
+
/** Read a value even when `cmd` exits nonzero (never needed for `extract: 'exit'`). */
|
|
46
|
+
allowNonzero?: boolean;
|
|
47
|
+
/** Per-command timeout, default 300. */
|
|
48
|
+
timeoutSec?: number;
|
|
49
|
+
/** `false` skips the source-only re-proof for this metric (tripwires whose gain legitimately lives in test files). */
|
|
50
|
+
gamingCheck?: boolean;
|
|
51
|
+
/** Per-metric override of `Config.gamingThreshold`. */
|
|
52
|
+
gamingThreshold?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A named hard rule: any change touching one of `paths` fails the gate before any metric is read. */
|
|
56
|
+
export interface Mandate {
|
|
57
|
+
name: string;
|
|
58
|
+
/** Globs: a bare name matches that basename at any depth, a pattern with `/` is anchored at the repo root, `**` crosses directories. */
|
|
59
|
+
paths: string[];
|
|
60
|
+
reason?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface Config {
|
|
64
|
+
/** Path (or paths) to a shared base config whose metrics and mandates this repo inherits; resolved relative to the config file. */
|
|
65
|
+
extends?: string | string[];
|
|
66
|
+
metrics: MetricConfig[];
|
|
67
|
+
mandates?: Mandate[];
|
|
68
|
+
/** Measurement repeats per metric, for the noise band. Default 1. */
|
|
69
|
+
repeat?: number;
|
|
70
|
+
/** Back-compat for `linkDirs`: `false` links nothing. */
|
|
71
|
+
linkNodeModules?: boolean;
|
|
72
|
+
/** Directories outside git to symlink into each worktree (default `['node_modules']`). */
|
|
73
|
+
linkDirs?: string[];
|
|
74
|
+
/** Extra environment for the metric commands; `{wt}` expands to the worktree path. */
|
|
75
|
+
env?: Record<string, string>;
|
|
76
|
+
/** A per-ref build or install, run in the worktree before measuring (a failed setup leaves the metric inert, never a false green). */
|
|
77
|
+
setup?: string;
|
|
78
|
+
/** `false` never appends to `.promptwheel/outcomes.jsonl`. */
|
|
79
|
+
record?: boolean;
|
|
80
|
+
/** Fraction of a gain that must survive the source-only re-run; below it the win is `gamed`. Default 0.5. */
|
|
81
|
+
gamingThreshold?: number;
|
|
82
|
+
/** Extra files that define the gate (a threshold, a budget); a change to one alongside a win is gamed by declaration. Same glob rules as `Mandate.paths`. */
|
|
83
|
+
evaluatorPaths?: string[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// check()
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
export interface CheckOptions {
|
|
91
|
+
/** Repo path. Default: discovered from `process.cwd()`. */
|
|
92
|
+
repo?: string;
|
|
93
|
+
/** Base ref. Default: the merge-base with `origin/main` (then `origin/master`, `main`, `master`); on the trunk itself, `HEAD~1`. */
|
|
94
|
+
base?: string;
|
|
95
|
+
/** Head ref. Default `HEAD`. */
|
|
96
|
+
head?: string;
|
|
97
|
+
/** Gate uncommitted changes (tracked and untracked) instead of `base..head`. Default `false`. */
|
|
98
|
+
working?: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* A resolved config supplied in process, used as given (an `extends` chain is resolved only
|
|
101
|
+
* when the config is loaded from `promptwheel.config.json`). Omit to load the file, or to
|
|
102
|
+
* auto-detect the test command when there is none.
|
|
103
|
+
*/
|
|
104
|
+
config?: Config;
|
|
105
|
+
/** Allow the zero-config auto-detect fallback when there is no config file and no `config` option. Default `true`; `false` throws instead. */
|
|
106
|
+
auto?: boolean;
|
|
107
|
+
/** Measurement repeats for the noise band. Default `config.repeat ?? 1`. */
|
|
108
|
+
repeat?: number;
|
|
109
|
+
/** Run the Section 3 source-only re-proof. Default `true`. */
|
|
110
|
+
detectGaming?: boolean;
|
|
111
|
+
/** A declared override reason (the same as `--allow` or a `Promptwheel-Allow:` commit trailer): a would-be `gamed` reads `pass`, recorded in `report.allowed`. */
|
|
112
|
+
allow?: string;
|
|
113
|
+
/** Append the run to `.promptwheel/outcomes.jsonl`. Default `false`. */
|
|
114
|
+
record?: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// The report (Section 2 verdict object)
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
/** What `evaluate()` returns for one metric: the before/after medians and the trust call on their delta. */
|
|
122
|
+
export interface Evaluation {
|
|
123
|
+
before: number | null;
|
|
124
|
+
after: number | null;
|
|
125
|
+
/** `null` when either side is unmeasurable. */
|
|
126
|
+
delta: number | null;
|
|
127
|
+
status: MetricStatus;
|
|
128
|
+
/** `false` only for a guarded metric with a trusted regression. */
|
|
129
|
+
ok: boolean;
|
|
130
|
+
confidence: Confidence;
|
|
131
|
+
/** The measured jitter band (max spread across repeats); `null` when unmeasurable. */
|
|
132
|
+
noise: number | null;
|
|
133
|
+
/** A guarded pass/fail metric stuck at 0 at both refs: it protects nothing. */
|
|
134
|
+
inert?: true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface MetricResult extends Evaluation {
|
|
138
|
+
name: string;
|
|
139
|
+
direction: Direction;
|
|
140
|
+
guard: boolean;
|
|
141
|
+
/** `true` when the win did not survive the source-only re-run; `null` when the source-only patch did not apply. Absent without gaming detection or when the metric did not improve. */
|
|
142
|
+
gamed?: boolean | null;
|
|
143
|
+
/** The metric's value with only the source edits applied. */
|
|
144
|
+
sourceOnly?: number | null;
|
|
145
|
+
/** The fraction of the gain that survived source-only (0..1). */
|
|
146
|
+
retained?: number | null;
|
|
147
|
+
gamingReason?: string;
|
|
148
|
+
/** Set when a declared override cleared a `gamed` on this metric. */
|
|
149
|
+
allowed?: true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface MandateViolation {
|
|
153
|
+
name: string;
|
|
154
|
+
reason: string;
|
|
155
|
+
/** The protected paths this change touched (at most 20). */
|
|
156
|
+
files: string[];
|
|
157
|
+
/** The mandate was deleted in this same change; it stays in force for the change that removes it. */
|
|
158
|
+
removed?: true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface GateWeakening {
|
|
162
|
+
name: string;
|
|
163
|
+
change: 'guard-removed' | 'guard-disabled';
|
|
164
|
+
/** What the dropped guard would have read at head under its base definition. */
|
|
165
|
+
status: MetricStatus;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface EvaluatorChange {
|
|
169
|
+
path: string;
|
|
170
|
+
/** Truncated SHA-256 of the file at each ref; `null` where the file is absent. */
|
|
171
|
+
base: string | null;
|
|
172
|
+
head: string | null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The gate-integrity record, present when gaming detection ran. */
|
|
176
|
+
export interface GateRecord {
|
|
177
|
+
/** Truncated SHA-256 of the gate definition (metrics, guards, evaluatorPaths, gamingThreshold, mandates) at each ref; `base` is `null` when there was no config at base. */
|
|
178
|
+
fingerprint: { base: string | null; head: string };
|
|
179
|
+
/** The gate definition differs between base and head. */
|
|
180
|
+
changed?: true;
|
|
181
|
+
/** Guards the change dropped that would have failed at head; non-empty forces `gamed` unless allowed. */
|
|
182
|
+
weakened?: GateWeakening[];
|
|
183
|
+
/** Declared `evaluatorPaths` files whose content changed in this diff. */
|
|
184
|
+
evaluatorChanged?: EvaluatorChange[];
|
|
185
|
+
/** A declared override cleared the weakening. */
|
|
186
|
+
allowed?: true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface AllowedRecord {
|
|
190
|
+
reason: string;
|
|
191
|
+
/** The metric names (and `'gate'`) the override cleared. */
|
|
192
|
+
cleared: string[];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Tamper-evident receipt: re-run `(base, head, config)` and `verdictHash` must match. Additive; never changes the verdict. */
|
|
196
|
+
export interface Provenance {
|
|
197
|
+
tool: 'promptwheel';
|
|
198
|
+
toolVersion: string | null;
|
|
199
|
+
baseSha: string | null;
|
|
200
|
+
headSha: string | null;
|
|
201
|
+
configHash: string;
|
|
202
|
+
verdictHash: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface Report {
|
|
206
|
+
/** Short SHA (or the ref as given when it cannot be resolved). */
|
|
207
|
+
base: string;
|
|
208
|
+
head: string;
|
|
209
|
+
mode: 'refs' | 'working';
|
|
210
|
+
repeat: number;
|
|
211
|
+
verdict: Verdict;
|
|
212
|
+
metrics: MetricResult[];
|
|
213
|
+
/** Present only when a mandate was violated (the verdict is then `fail`). */
|
|
214
|
+
mandates?: MandateViolation[];
|
|
215
|
+
gate?: GateRecord;
|
|
216
|
+
allowed?: AllowedRecord;
|
|
217
|
+
provenance: Provenance;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Public library API (stable across 0.x minors)
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
/** The Contract revision this build implements; bumps only when the verdict schema or the exit codes change. */
|
|
225
|
+
export declare const CONTRACT_VERSION: '1';
|
|
226
|
+
export declare const VERDICTS: readonly ['pass', 'fail', 'gamed', 'inconclusive'];
|
|
227
|
+
export declare const METRIC_STATUSES: readonly ['improved', 'regressed', 'unchanged', 'inconclusive', 'unmeasurable'];
|
|
228
|
+
export declare const DIRECTIONS: readonly ['up', 'down', 'pass'];
|
|
229
|
+
|
|
230
|
+
/** Section 1: `pass` 0, `fail` 1, `gamed` 2, `inconclusive` 3 (any other string maps to 1). The CLI exits with this. */
|
|
231
|
+
export declare function verdictExitCode(verdict: Verdict | string): 0 | 1 | 2 | 3;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Run the gate in process and return the Section 2 verdict object (identical to `run --json`).
|
|
235
|
+
* Measures only in throwaway git worktrees, never the caller's working tree. Never prints and
|
|
236
|
+
* never calls `process.exit`; throws a plain `Error` on a precondition failure (not a git
|
|
237
|
+
* repository, `auto: false` with no config, a `config` without a non-empty `metrics` array).
|
|
238
|
+
*/
|
|
239
|
+
export declare function check(options?: CheckOptions): Report;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Structurally validate any verdict object against Section 2 (shapes, the fixed vocabularies,
|
|
243
|
+
* and the honest-verdict rules). Zero dependency, so a third-party implementation can self-check
|
|
244
|
+
* the artifact it emits. `errors` is empty exactly when `ok` is `true`.
|
|
245
|
+
*/
|
|
246
|
+
export declare function conformsToContract(report: unknown): { ok: boolean; errors: string[] };
|
|
247
|
+
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// Pure helpers (exported for unit tests and for consumers that want the arithmetic)
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
/** Read a metric value from a command's stdout and exit code. */
|
|
253
|
+
export declare function extract(stdout: string, code: number, mode?: ExtractMode, allowNonzero?: boolean): number | null;
|
|
254
|
+
|
|
255
|
+
/** Median of the non-null samples; `null` when there are none. */
|
|
256
|
+
export declare function median(samples: ReadonlyArray<number | null | undefined>): number | null;
|
|
257
|
+
|
|
258
|
+
/** `max - min` of the non-null samples; `0` when there are none. */
|
|
259
|
+
export declare function spread(samples: ReadonlyArray<number | null | undefined>): number;
|
|
260
|
+
|
|
261
|
+
/** The credibility core: compare before/after samples and trust the delta only if it clears the observed noise band. */
|
|
262
|
+
export declare function evaluate(
|
|
263
|
+
metric: Pick<MetricConfig, 'direction' | 'guard' | 'extract'>,
|
|
264
|
+
beforeSamples: ReadonlyArray<number | null>,
|
|
265
|
+
afterSamples: ReadonlyArray<number | null>,
|
|
266
|
+
repeat: number,
|
|
267
|
+
): Evaluation;
|
|
268
|
+
|
|
269
|
+
/** Render a report as the PR-comment markdown the GitHub Action posts. */
|
|
270
|
+
export declare function renderMarkdown(report: Report): string;
|
|
271
|
+
|
|
272
|
+
/** The built-in classifier: is this repo-relative path test / config / grader / golden (not production source)? */
|
|
273
|
+
export declare function isNonSource(path: string): boolean;
|
|
274
|
+
|
|
275
|
+
/** Compile a config's `evaluatorPaths` globs into a matcher over repo-relative paths (always `false` when none are declared). */
|
|
276
|
+
export declare function evaluatorMatcher(config?: { evaluatorPaths?: string[] } | null): (path: string) => boolean;
|
|
277
|
+
|
|
278
|
+
/** Directional improvement of `value` over `before` (positive = better); `null` when either is missing. */
|
|
279
|
+
export declare function gain(metric: { direction?: Direction }, before: number | null, value: number | null): number | null;
|
|
280
|
+
|
|
281
|
+
/** The samples measured with only the source slice of the change applied. */
|
|
282
|
+
export interface SourceOnlyResult {
|
|
283
|
+
samples: ReadonlyArray<number | null>;
|
|
284
|
+
/** `false` when the change touched zero production-source files. */
|
|
285
|
+
hadSourceChange: boolean;
|
|
286
|
+
/** The source-only patch did not apply cleanly, so the judgement is inconclusive. */
|
|
287
|
+
applyFailed?: boolean;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export interface GamingJudgement {
|
|
291
|
+
/** `null` when the source-only patch did not apply. */
|
|
292
|
+
gamed: boolean | null;
|
|
293
|
+
sourceOnly: number | null;
|
|
294
|
+
retained: number | null;
|
|
295
|
+
reason: string;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Did a metric's full-diff win survive on the source edits alone? `gamed` when less than `threshold` (default 0.5) of the gain is retained. */
|
|
299
|
+
export declare function judgeGaming(
|
|
300
|
+
metric: { direction?: Direction; before: number | null; after: number | null },
|
|
301
|
+
sourceOnly: SourceOnlyResult,
|
|
302
|
+
threshold?: number,
|
|
303
|
+
): GamingJudgement;
|
|
304
|
+
|
|
305
|
+
/** Truncated SHA-256 of the verdict content (everything but `provenance`), the reproducible half of the receipt. */
|
|
306
|
+
export declare function verdictFingerprint(report: Pick<Report, 'verdict'> & Partial<Pick<Report, 'metrics' | 'gate'>>): string;
|
package/bin/promptwheel.mjs
CHANGED
|
@@ -884,6 +884,13 @@ function hasEslint(repo) {
|
|
|
884
884
|
} catch { return false; }
|
|
885
885
|
}
|
|
886
886
|
|
|
887
|
+
// does git ignore this repo-relative path? `check-ignore -q` exits 0 when it does; 1 when it does
|
|
888
|
+
// not; anything else (not a git repo) counts as not ignored, so init never fails on the check.
|
|
889
|
+
function gitIgnores(repo, relPath) {
|
|
890
|
+
try { execFileSync('git', ['check-ignore', '-q', relPath], { cwd: repo, stdio: 'ignore' }); return true; }
|
|
891
|
+
catch { return false; }
|
|
892
|
+
}
|
|
893
|
+
|
|
887
894
|
function init(argv) {
|
|
888
895
|
if (argv.includes('--list')) {
|
|
889
896
|
console.log('PromptWheel presets (promptwheel init --preset <name>):\n');
|
|
@@ -911,8 +918,14 @@ function init(argv) {
|
|
|
911
918
|
const src = fileURLToPath(new URL(`../${pk[2]}/`, import.meta.url));
|
|
912
919
|
const dst = join(repo, '.promptwheel', pk[2]);
|
|
913
920
|
mkdirSync(dst, { recursive: true });
|
|
914
|
-
|
|
921
|
+
const files = readdirSync(src);
|
|
922
|
+
for (const f of files) copyFileSync(join(src, f), join(dst, f));
|
|
915
923
|
console.log(` ✓ installed ${pk[2]} → .promptwheel/${pk[2]} (commit it with the config)`);
|
|
924
|
+
// the gate measures in clean worktrees, so a pack that git ignores (common: the outcome
|
|
925
|
+
// record lives in .promptwheel/ too) is absent at both refs and the metric reads
|
|
926
|
+
// unmeasurable, failing the guard with no hint why. Say so now, while it is one edit.
|
|
927
|
+
if (files.length && gitIgnores(repo, `.promptwheel/${pk[2]}/${files[0]}`))
|
|
928
|
+
console.log(` ⚠ .promptwheel/${pk[2]} is gitignored: the gate measures in clean worktrees, so an ignored pack is absent at both refs and ${m.name} reads unmeasurable (the guard fails). Replace ".promptwheel/" in .gitignore with ".promptwheel/*" and "!.promptwheel/packs/", then commit the pack`);
|
|
916
929
|
return { ...m, cmd: m.cmd.replaceAll(pk[2] + '/', `.promptwheel/${pk[2]}/`) };
|
|
917
930
|
});
|
|
918
931
|
note = presetName;
|
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "promptwheel",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
4
4
|
"description": "A deterministic, zero-LLM check for your coding loop. It proves every change earned its win by re-running the result from the agent's source edits alone, so a green that came from editing the test reads GAMED. Runs in CI or a Claude Code hook, or import { check } in process.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"promptwheel": "bin/promptwheel.mjs"
|
|
8
8
|
},
|
|
9
|
+
"types": "./bin/promptwheel.d.ts",
|
|
9
10
|
"exports": {
|
|
10
|
-
".":
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./bin/promptwheel.d.ts",
|
|
13
|
+
"default": "./bin/promptwheel.mjs"
|
|
14
|
+
},
|
|
11
15
|
"./bin/promptwheel.mjs": "./bin/promptwheel.mjs",
|
|
12
16
|
"./package.json": "./package.json"
|
|
13
17
|
},
|
package/packs/security/README.md
CHANGED
|
@@ -9,18 +9,28 @@ lower the count by editing the pattern file or a config, only by fixing the code
|
|
|
9
9
|
|
|
10
10
|
## Use it
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
The pack ships inside the `promptwheel` package, not in your repo, so install it with the
|
|
13
|
+
preset:
|
|
13
14
|
|
|
14
|
-
```
|
|
15
|
-
|
|
15
|
+
```bash
|
|
16
|
+
promptwheel init --preset security # or --preset security-antihack, which adds the tripwires
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
`init` copies the pack into `.promptwheel/packs/security/` and writes a `security_findings`
|
|
20
|
+
metric that runs it from there. Commit the pack with the config: the gate measures in clean
|
|
21
|
+
git worktrees, so a pack that is not in git is absent at both refs and the metric reads
|
|
22
|
+
`unmeasurable` (init warns when `.promptwheel/` is gitignored; change that rule to
|
|
23
|
+
`.promptwheel/*` plus `!.promptwheel/packs/`, since git cannot re-include a file under an
|
|
24
|
+
ignored directory). Then gate as usual:
|
|
19
25
|
|
|
20
26
|
```bash
|
|
21
27
|
promptwheel run --working # fails if security_findings increased
|
|
22
28
|
```
|
|
23
29
|
|
|
30
|
+
`pack.json` is the same metric as a config fragment for `extends`
|
|
31
|
+
(`{ "extends": ["packs/security/pack.json"] }`); its command is relative to a repo that
|
|
32
|
+
holds the pack at `packs/security/`, such as a vendored copy or this repository.
|
|
33
|
+
|
|
24
34
|
A finding-count that drops because you fixed the code is a `PASS`; a "fix" that
|
|
25
35
|
only survives with test/config/pattern edits reverted is `GAMED`.
|
|
26
36
|
|