patdown 0.3.1 → 0.4.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/README.md +10 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/patdown-judge.d.ts +6 -1
- package/dist/patdown-judge.d.ts.map +1 -1
- package/dist/patdown-judge.js +8 -2
- package/dist/patdown-lint.d.ts +4 -1
- package/dist/patdown-lint.d.ts.map +1 -1
- package/dist/patdown-lint.js +11 -5
- package/dist/patdown-output.d.ts +4 -3
- package/dist/patdown-output.d.ts.map +1 -1
- package/dist/patdown-output.js +16 -8
- package/dist/patdown-probability-bar.d.ts +3 -0
- package/dist/patdown-probability-bar.d.ts.map +1 -0
- package/dist/patdown-probability-bar.js +29 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ pnpm -w patdown -- ask "Is this urgent?" --input-text "ASAP" --verbose
|
|
|
28
28
|
pnpm -w patdown -- --yes-threshold 0.9
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Default command lints from the current directory. `rules` prints what it loaded. `ask` answers a yes/no question, no files involved. It prints `yes` or `no`; `--verbose` also shows
|
|
31
|
+
Default command lints from the current directory. `rules` prints what it loaded. `ask` answers a yes/no question, no files involved. It prints `yes` or `no`; `--verbose` also shows a P(yes) shade bar, the cutoff, and how long the judge call took. The old `--noul` and `--state` flags have been replaced by a positional question and `--input-text`.
|
|
32
32
|
|
|
33
33
|
To judge piped output, use `--stdin`. For example, after building with `pnpm -w build` or running any `pnpm -w patdown` command:
|
|
34
34
|
|
|
@@ -119,7 +119,12 @@ FAIL README.md: No title case
|
|
|
119
119
|
patdown: failed
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
Exit 1 on a violation, a missing rules file, a read error, an invalid cutoff, or a judge error. Add `--verbose` to show
|
|
122
|
+
Exit 1 on a violation, a missing rules file, a read error, an invalid cutoff, or a judge error. Add `--verbose` to show a P(yes) shade bar, the numeric probability, the cutoff, and elapsed judge time. Lint totals that time across every rule/file pair.
|
|
123
|
+
|
|
124
|
+
````
|
|
125
|
+
FAIL service.ts: explicit-actor (▓▓▓▓▓▓▓▓▒░ estimated P(yes): 0.86; cutoff: >0.85; elapsed: 312ms)
|
|
126
|
+
patdown: failed (elapsed: 1840ms)
|
|
127
|
+
``` Patdown counts estimated P(yes) strictly above the cutoff as yes; for lint, yes means violation. Default cutoff is 0.85. Override it with `--yes-threshold`, package.json `patdown.yesThreshold`, or a per-rule `yes-threshold:` line. The flag wins over package.json; a per-rule value wins for that rule only. `1` is rejected because nothing can exceed it. This cutoff belongs to patdown, not the provider.
|
|
123
128
|
|
|
124
129
|
See [judge providers](docs/judge-providers.md) for custom layers and the planned Effect Decision integration.
|
|
125
130
|
|
|
@@ -162,7 +167,7 @@ await Effect.runPromise(
|
|
|
162
167
|
JsonOutputLive,
|
|
163
168
|
),
|
|
164
169
|
)
|
|
165
|
-
|
|
170
|
+
````
|
|
166
171
|
|
|
167
172
|
The output service receives structured judgments and lint results, including probabilities even when `--verbose` is off. Your layer decides what to print, collect, or omit. Formatting does not change the cutoff or exit status. The example emits one JSON object per output event, not a single JSON document for the entire run.
|
|
168
173
|
|
|
@@ -196,6 +201,8 @@ Inspired by [pi-warden](https://github.com/DevMortimer/pi-warden). Same idea, in
|
|
|
196
201
|
|
|
197
202
|
[Abide](https://github.com/coldteadotai/abide) is a similar Jev-backed checker. It hooks into coding agents, reads project instruction files, and asks Jev whether each edit or turn broke a rule.
|
|
198
203
|
|
|
204
|
+
[Jev Review](https://github.com/devagrawal09/jev-review) is a Jev-backed diff/codebase reviewer with a local dashboard of those judgments. Patdown stays in the terminal; `--verbose` draws a P(yes) shade bar instead of a GUI.
|
|
205
|
+
|
|
199
206
|
Name inspired by It's Always Sunny in Philadelphia
|
|
200
207
|
|
|
201
208
|
<img width="511" height="415" alt="image" src="https://github.com/user-attachments/assets/f7c73138-3914-4fbd-9e6b-7a37d161334a" />
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EAKjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAU,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACxD,OAAO,EAAY,OAAO,EAAQ,MAAM,qBAAqB,CAAA;AAE7D,OAAO,EAAE,YAAY,EAAuC,MAAM,oBAAoB,CAAA;AAEtF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EAKjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAU,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACxD,OAAO,EAAY,OAAO,EAAQ,MAAM,qBAAqB,CAAA;AAE7D,OAAO,EAAE,YAAY,EAAuC,MAAM,oBAAoB,CAAA;AAEtF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AA8BnD,KAAK,mBAAmB,GACrB,UAAU,CAAC,UAAU,GACrB,KAAK,CAAC,KAAK,GACX,YAAY,GACZ,IAAI,CAAC,IAAI,GACT,aAAa,GACb,iBAAiB,CAAA;AAuBpB,4EAA4E;AAC5E,wBAAgB,kBAAkB,CACjC,gBAAgB,GAAE,OAAc,GAC9B,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,CAAC,CA6GvE;AAED,uEAAuE;AACvE,eAAO,MAAM,cAAc,uEAAuB,CAAA"}
|
package/dist/cli.js
CHANGED
|
@@ -12,20 +12,20 @@ const failPatdown = (message) => Effect.sync(() => {
|
|
|
12
12
|
process.stderr.write(`${message}\n`);
|
|
13
13
|
});
|
|
14
14
|
const rulesFileFlag = Flag.optional(Flag.string('rules')).pipe(Flag.withDescription('Path passed to the rule source. Markdown skips the AGENTS.PATDOWN.md walk'));
|
|
15
|
-
const verboseFlag = Flag.boolean('verbose').pipe(Flag.withDefault(false), Flag.withDescription('Show estimated yes probabilities
|
|
15
|
+
const verboseFlag = Flag.boolean('verbose').pipe(Flag.withDefault(false), Flag.withDescription('Show estimated yes probabilities, the decision cutoff, and elapsed judge time'));
|
|
16
16
|
const adapterFlag = Flag.optional(Flag.string('adapter')).pipe(Flag.withDescription('Module exporting PatdownRuleSourceLive, replacingkdown rule parsing'));
|
|
17
17
|
const yesThresholdFlag = Flag.optional(Flag.float('yes-threshold')).pipe(Flag.withDescription('Minimum exclusive P(yes) for yes; default 0.85, overridable per rule'));
|
|
18
|
-
function finishPatdownLint(failed) {
|
|
18
|
+
function finishPatdownLint(failed, verbose, elapsedMs) {
|
|
19
19
|
return Effect.gen(function* () {
|
|
20
20
|
const output = yield* PatdownOutput;
|
|
21
21
|
if (failed) {
|
|
22
|
-
yield* output.writeLintFailed;
|
|
22
|
+
yield* output.writeLintFailed(verbose ? elapsedMs : undefined);
|
|
23
23
|
yield* Effect.sync(() => {
|
|
24
24
|
process.exitCode = 1;
|
|
25
25
|
});
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
-
yield* output.writeLintOk;
|
|
28
|
+
yield* output.writeLintOk(verbose ? elapsedMs : undefined);
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
/** Builds commands with optional adapter discovery for embedded callers. */
|
|
@@ -53,8 +53,8 @@ export function makePatdownCommand(discoverAdapters = true) {
|
|
|
53
53
|
const output = yield* PatdownOutput;
|
|
54
54
|
const text = yield* readPatdownQuestionInput(inputText, stdin);
|
|
55
55
|
const cutoff = yield* resolvePatdownYesThreshold(yesThreshold);
|
|
56
|
-
const
|
|
57
|
-
yield* output.writeAnswer(
|
|
56
|
+
const timed = yield* askPatdownJudge(question, text);
|
|
57
|
+
yield* output.writeAnswer(timed.judgment, verbose, cutoff, timed.elapsedMs);
|
|
58
58
|
}).pipe(Effect.catchTags({
|
|
59
59
|
PatdownJudgeFailed: (error) => failPatdown(error.message),
|
|
60
60
|
PatdownYesThresholdInvalid: (error) => failPatdown(error.message),
|
|
@@ -71,8 +71,8 @@ export function makePatdownCommand(discoverAdapters = true) {
|
|
|
71
71
|
const document = yield* discoverAdapters
|
|
72
72
|
? loadConfiguredPatdownRules(adapter, rules)
|
|
73
73
|
: patdownRuleSource.loadPatdownRules(rules);
|
|
74
|
-
const
|
|
75
|
-
yield* finishPatdownLint(failed);
|
|
74
|
+
const linted = yield* runPatdownLint(document, verbose, cutoff);
|
|
75
|
+
yield* finishPatdownLint(linted.failed, verbose, linted.elapsedMs);
|
|
76
76
|
}).pipe(Effect.catchTags({
|
|
77
77
|
PatdownRulesLoadFailed: (error) => failPatdown(error.message),
|
|
78
78
|
PatdownJudgeFailed: (error) => failPatdown(error.message),
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { patdownCommand, makePatdownCommand } from '#src/cli';
|
|
|
2
2
|
export { PatdownOutput, PatdownOutputLive, type PatdownLintResult } from '#src/patdown-output';
|
|
3
3
|
export type { PatdownRuleSourceLayer } from '#src/patdown-rule-source-adapter';
|
|
4
4
|
export { runPatdownCli } from '#src/run-patdown-cli';
|
|
5
|
-
export { PatdownJudge, PatdownJudgeFailed, PatdownJudgmentSchema, askPatdownJudge, patdownJudgmentIsYes, patdownYesThreshold, type PatdownJudgment, } from '#src/patdown-judge';
|
|
5
|
+
export { PatdownJudge, PatdownJudgeFailed, PatdownJudgmentSchema, askPatdownJudge, patdownJudgmentIsYes, patdownYesThreshold, type PatdownJudgment, type PatdownTimedJudgment, } from '#src/patdown-judge';
|
|
6
6
|
export { defaultPatdownYesThreshold, decodePatdownYesThreshold, PatdownYesThresholdInvalid, type PatdownYesThreshold, } from '@patdown/rules';
|
|
7
7
|
export { TypeSafeJudgeLive } from '#src/typesafe-judge';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAE7D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE9F,YAAY,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AAE9E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAE7D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE9F,YAAY,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AAE9E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,oBAAoB,GACzB,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EACN,0BAA0B,EAC1B,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,mBAAmB,GACxB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/patdown-judge.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ export declare const PatdownJudgmentSchema: Schema.Struct<{
|
|
|
6
6
|
}>;
|
|
7
7
|
/** A judge estimates the probability that the question is true of the supplied text. */
|
|
8
8
|
export type PatdownJudgment = typeof PatdownJudgmentSchema.Type;
|
|
9
|
+
/** A validated judgment plus how long the judge call took. */
|
|
10
|
+
export type PatdownTimedJudgment = {
|
|
11
|
+
readonly judgment: PatdownJudgment;
|
|
12
|
+
readonly elapsedMs: number;
|
|
13
|
+
};
|
|
9
14
|
declare const PatdownJudgeFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
10
15
|
readonly _tag: "PatdownJudgeFailed";
|
|
11
16
|
} & Readonly<A>;
|
|
@@ -25,6 +30,6 @@ export declare const patdownYesThreshold = 0.85;
|
|
|
25
30
|
/** Applies a cutoff to a validated probability. Equality is not yes. */
|
|
26
31
|
export declare function patdownJudgmentIsYes(judgment: PatdownJudgment, yesThreshold?: PatdownYesThreshold): boolean;
|
|
27
32
|
/** Validates custom judge responses at the service boundary before applying policy or printing. */
|
|
28
|
-
export declare function askPatdownJudge(question: string, inputText: string): Effect.Effect<
|
|
33
|
+
export declare function askPatdownJudge(question: string, inputText: string): Effect.Effect<PatdownTimedJudgment, PatdownJudgeFailed, PatdownJudge>;
|
|
29
34
|
export {};
|
|
30
35
|
//# sourceMappingURL=patdown-judge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"patdown-judge.d.ts","sourceRoot":"","sources":["../src/patdown-judge.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,mBAAmB,EACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,
|
|
1
|
+
{"version":3,"file":"patdown-judge.d.ts","sourceRoot":"","sources":["../src/patdown-judge.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,mBAAmB,EACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAS,OAAO,EAAQ,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE7D,0FAA0F;AAC1F,eAAO,MAAM,qBAAqB;;EAEhC,CAAA;AAEF,wFAAwF;AACxF,MAAM,MAAM,eAAe,GAAG,OAAO,qBAAqB,CAAC,IAAI,CAAA;AAE/D,8DAA8D;AAC9D,MAAM,MAAM,oBAAoB,GAAG;IAClC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAA;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC1B,CAAA;;;;AAED,2EAA2E;AAC3E,qBAAa,kBAAmB,SAAQ,wBAAuC;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACxB,CAAC;CAAG;;kBAMW,CACb,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,KACb,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC;;AAPzD,6FAA6F;AAC7F,qBAAa,YAAa,SAAQ,iBAQF;CAAG;AAEnC,uFAAuF;AACvF,eAAO,MAAM,mBAAmB,OAA6B,CAAA;AAE7D,wEAAwE;AACxE,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,eAAe,EACzB,YAAY,GAAE,mBAAgD,GAC5D,OAAO,CAET;AAED,mGAAmG;AACnG,wBAAgB,eAAe,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE,kBAAkB,EAAE,YAAY,CAAC,CAmBvE"}
|
package/dist/patdown-judge.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defaultPatdownYesThreshold, patdownJudgmentIsYes as comparePatdownYesProbability, } from '@patdown/rules';
|
|
2
|
-
import { Context, Data, Effect, Schema } from 'effect';
|
|
2
|
+
import { Clock, Context, Data, Effect, Schema } from 'effect';
|
|
3
3
|
/** Provider-neutral estimate. This is P(yes), not confidence in whichever answer wins. */
|
|
4
4
|
export const PatdownJudgmentSchema = Schema.Struct({
|
|
5
5
|
yesProbability: Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 1 })),
|
|
@@ -20,7 +20,13 @@ export function patdownJudgmentIsYes(judgment, yesThreshold = defaultPatdownYesT
|
|
|
20
20
|
export function askPatdownJudge(question, inputText) {
|
|
21
21
|
return Effect.gen(function* () {
|
|
22
22
|
const judge = yield* PatdownJudge;
|
|
23
|
+
const startedAt = yield* Clock.currentTimeMillis;
|
|
23
24
|
const answer = yield* judge.ask(question, inputText);
|
|
24
|
-
|
|
25
|
+
const finishedAt = yield* Clock.currentTimeMillis;
|
|
26
|
+
const judgment = yield* Schema.decodeEffect(PatdownJudgmentSchema)(answer).pipe(Effect.mapError(() => new PatdownJudgeFailed({ message: 'patdown: judge returned an invalid yes probability' })));
|
|
27
|
+
return {
|
|
28
|
+
judgment,
|
|
29
|
+
elapsedMs: Math.max(0, finishedAt - startedAt),
|
|
30
|
+
};
|
|
25
31
|
});
|
|
26
32
|
}
|
package/dist/patdown-lint.d.ts
CHANGED
|
@@ -3,5 +3,8 @@ import { Effect, FileSystem, Path } from 'effect';
|
|
|
3
3
|
import { PatdownJudge, PatdownJudgeFailed } from '#src/patdown-judge';
|
|
4
4
|
import { PatdownOutput } from '#src/patdown-output';
|
|
5
5
|
/** Lint files matched by each rule's globs. A yes judgment means a violation. */
|
|
6
|
-
export declare function runPatdownLint(document: PatdownRulesDocument, verbose?: boolean, yesThreshold?: PatdownYesThreshold): Effect.Effect<
|
|
6
|
+
export declare function runPatdownLint(document: PatdownRulesDocument, verbose?: boolean, yesThreshold?: PatdownYesThreshold): Effect.Effect<{
|
|
7
|
+
readonly failed: boolean;
|
|
8
|
+
readonly elapsedMs: number;
|
|
9
|
+
}, PatdownJudgeFailed | PatdownYesThresholdInvalid, FileSystem.FileSystem | PatdownJudge | Path.Path | PatdownOutput>;
|
|
7
10
|
//# sourceMappingURL=patdown-lint.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"patdown-lint.d.ts","sourceRoot":"","sources":["../src/patdown-lint.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,0BAA0B,EAE1B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,
|
|
1
|
+
{"version":3,"file":"patdown-lint.d.ts","sourceRoot":"","sources":["../src/patdown-lint.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,0BAA0B,EAE1B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAS,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAExD,OAAO,EACN,YAAY,EACZ,kBAAkB,EAGlB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AA2InD,iFAAiF;AACjF,wBAAgB,cAAc,CAC7B,QAAQ,EAAE,oBAAoB,EAC9B,OAAO,GAAE,OAAe,EACxB,YAAY,GAAE,mBAAgD,GAC5D,MAAM,CAAC,MAAM,CACf;IAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,EACxD,kBAAkB,GAAG,0BAA0B,EAC/C,UAAU,CAAC,UAAU,GAAG,YAAY,GAAG,IAAI,CAAC,IAAI,GAAG,aAAa,CAChE,CAmBA"}
|
package/dist/patdown-lint.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defaultPatdownYesThreshold, PatdownYesThresholdInvalid, } from '@patdown/rules';
|
|
2
|
-
import { Effect, FileSystem, Path } from 'effect';
|
|
2
|
+
import { Clock, Effect, FileSystem, Path } from 'effect';
|
|
3
3
|
import { PatdownJudge, PatdownJudgeFailed, askPatdownJudge, patdownJudgmentIsYes, } from '#src/patdown-judge';
|
|
4
4
|
import { PatdownOutput } from '#src/patdown-output';
|
|
5
5
|
import { decodePatdownRuleYesThreshold } from '#src/patdown-yes-threshold-config';
|
|
@@ -51,14 +51,15 @@ function lintPatdownRuleFile(rule, filePath, options) {
|
|
|
51
51
|
const contents = yield* fileSystem.readFileString(filePath).pipe(Effect.mapError(() => new PatdownJudgeFailed({
|
|
52
52
|
message: `patdown: failed to read ${relativePath}`,
|
|
53
53
|
})));
|
|
54
|
-
const
|
|
55
|
-
const failed = patdownJudgmentIsYes(
|
|
54
|
+
const timed = yield* askPatdownJudge(patdownViolationInstructions(rule), patdownFileState(relativePath, contents));
|
|
55
|
+
const failed = patdownJudgmentIsYes(timed.judgment, options.yesThreshold);
|
|
56
56
|
yield* output.writeLintResult({
|
|
57
57
|
violated: failed,
|
|
58
58
|
ruleTitle: rule.patdownRuleTitle,
|
|
59
59
|
filePath: relativePath,
|
|
60
|
-
violationProbability:
|
|
60
|
+
violationProbability: timed.judgment.yesProbability,
|
|
61
61
|
yesThreshold: options.yesThreshold,
|
|
62
|
+
elapsedMs: timed.elapsedMs,
|
|
62
63
|
}, options.verbose);
|
|
63
64
|
return failed;
|
|
64
65
|
});
|
|
@@ -83,7 +84,12 @@ export function runPatdownLint(document, verbose = false, yesThreshold = default
|
|
|
83
84
|
return Effect.gen(function* () {
|
|
84
85
|
const path = yield* Path.Path;
|
|
85
86
|
const cwd = path.resolve('.');
|
|
87
|
+
const startedAt = yield* Clock.currentTimeMillis;
|
|
86
88
|
const failures = yield* Effect.forEach(document.patdownRules, (rule) => lintPatdownRule(rule, cwd, verbose, yesThreshold), { concurrency: 1 });
|
|
87
|
-
|
|
89
|
+
const finishedAt = yield* Clock.currentTimeMillis;
|
|
90
|
+
return {
|
|
91
|
+
failed: failures.some((failed) => failed),
|
|
92
|
+
elapsedMs: Math.max(0, finishedAt - startedAt),
|
|
93
|
+
};
|
|
88
94
|
});
|
|
89
95
|
}
|
package/dist/patdown-output.d.ts
CHANGED
|
@@ -9,13 +9,14 @@ export type PatdownLintResult = {
|
|
|
9
9
|
readonly filePath: string;
|
|
10
10
|
readonly violationProbability: number;
|
|
11
11
|
readonly yesThreshold: PatdownYesThreshold;
|
|
12
|
+
readonly elapsedMs: number;
|
|
12
13
|
};
|
|
13
14
|
declare const PatdownOutput_base: Context.ServiceClass<PatdownOutput, "@patdown/cli/PatdownOutput", {
|
|
14
|
-
readonly writeLintFailed: Effect.Effect<void>;
|
|
15
|
-
readonly writeLintOk: Effect.Effect<void>;
|
|
15
|
+
readonly writeLintFailed: (elapsedMs?: number) => Effect.Effect<void>;
|
|
16
|
+
readonly writeLintOk: (elapsedMs?: number) => Effect.Effect<void>;
|
|
16
17
|
readonly writeLintResult: (result: PatdownLintResult, verbose: boolean) => Effect.Effect<void>;
|
|
17
18
|
readonly writeNoFilesMatched: (ruleTitle: string) => Effect.Effect<void>;
|
|
18
|
-
readonly writeAnswer: (judgment: PatdownJudgment, verbose: boolean, yesThreshold?: PatdownYesThreshold) => Effect.Effect<void>;
|
|
19
|
+
readonly writeAnswer: (judgment: PatdownJudgment, verbose: boolean, yesThreshold?: PatdownYesThreshold, elapsedMs?: number) => Effect.Effect<void>;
|
|
19
20
|
readonly writeRulesDocument: (document: PatdownRulesDocument) => Effect.Effect<void>;
|
|
20
21
|
}>;
|
|
21
22
|
/** Swappable output service. Probability details are opt-in. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"patdown-output.d.ts","sourceRoot":"","sources":["../src/patdown-output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAA8B,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACrF,OAAO,EAAW,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAExD,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"patdown-output.d.ts","sourceRoot":"","sources":["../src/patdown-output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAA8B,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACrF,OAAO,EAAW,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAExD,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAG/E,+FAA+F;AAC/F,MAAM,MAAM,iBAAiB,GAAG;IAC/B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;IACrC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAA;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC1B,CAAA;;8BA4D2B,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;0BAC/C,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;8BACvC,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;kCAChE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;0BAClD,CACrB,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,OAAO,EAChB,YAAY,CAAC,EAAE,mBAAmB,EAClC,SAAS,CAAC,EAAE,MAAM,KACd,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;iCACK,CAAC,QAAQ,EAAE,oBAAoB,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;;AAdtF,gEAAgE;AAChE,qBAAa,aAAc,SAAQ,kBAeF;CAAG;AAEpC,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,0CAkB5B,CAAA"}
|
package/dist/patdown-output.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { defaultPatdownYesThreshold } from '@patdown/rules';
|
|
2
2
|
import { Console, Context, Effect, Layer } from 'effect';
|
|
3
3
|
import { patdownJudgmentIsYes } from '#src/patdown-judge';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { formatPatdownProbabilityBar } from '#src/patdown-probability-bar';
|
|
5
|
+
function formatPatdownElapsedMs(elapsedMs) {
|
|
6
|
+
return `${String(elapsedMs)}ms`;
|
|
6
7
|
}
|
|
7
|
-
function
|
|
8
|
+
function formatPatdownProbability(probability, yesThreshold, elapsedMs) {
|
|
9
|
+
return `${formatPatdownProbabilityBar(probability)} estimated P(yes): ${String(probability)}; cutoff: >${String(yesThreshold)}; elapsed: ${formatPatdownElapsedMs(elapsedMs)}`;
|
|
10
|
+
}
|
|
11
|
+
function formatPatdownAnswer(judgment, verbose, yesThreshold, elapsedMs) {
|
|
8
12
|
const answer = patdownJudgmentIsYes(judgment, yesThreshold) ? 'yes' : 'no';
|
|
9
13
|
return verbose
|
|
10
|
-
? `${answer} (${formatPatdownProbability(judgment.yesProbability, yesThreshold)})`
|
|
14
|
+
? `${answer} (${formatPatdownProbability(judgment.yesProbability, yesThreshold, elapsedMs)})`
|
|
11
15
|
: answer;
|
|
12
16
|
}
|
|
13
17
|
function formatPatdownLintResult(result, verbose) {
|
|
14
18
|
const verdict = result.violated ? 'FAIL' : 'PASS';
|
|
15
19
|
const line = `${verdict} ${result.filePath}: ${result.ruleTitle}`;
|
|
16
20
|
return verbose
|
|
17
|
-
? `${line} (${formatPatdownProbability(result.violationProbability, result.yesThreshold)})`
|
|
21
|
+
? `${line} (${formatPatdownProbability(result.violationProbability, result.yesThreshold, result.elapsedMs)})`
|
|
18
22
|
: line;
|
|
19
23
|
}
|
|
20
24
|
function formatPatdownRulesDocument(document) {
|
|
@@ -36,10 +40,14 @@ export class PatdownOutput extends Context.Service()('@patdown/cli/PatdownOutput
|
|
|
36
40
|
}
|
|
37
41
|
/** Default human-readable output, without provider-specific vocabulary. */
|
|
38
42
|
export const PatdownOutputLive = Layer.succeed(PatdownOutput, {
|
|
39
|
-
writeLintFailed: Console.log(
|
|
40
|
-
|
|
43
|
+
writeLintFailed: (elapsedMs) => Console.log(elapsedMs === undefined
|
|
44
|
+
? 'patdown: failed'
|
|
45
|
+
: `patdown: failed (elapsed: ${formatPatdownElapsedMs(elapsedMs)})`),
|
|
46
|
+
writeLintOk: (elapsedMs) => Console.log(elapsedMs === undefined
|
|
47
|
+
? 'patdown: ok'
|
|
48
|
+
: `patdown: ok (elapsed: ${formatPatdownElapsedMs(elapsedMs)})`),
|
|
41
49
|
writeLintResult: (result, verbose) => Console.log(formatPatdownLintResult(result, verbose)),
|
|
42
50
|
writeNoFilesMatched: (title) => Console.log(`patdown: no files matched ${title}`),
|
|
43
|
-
writeAnswer: (judgment, verbose, yesThreshold = defaultPatdownYesThreshold) => Console.log(formatPatdownAnswer(judgment, verbose, yesThreshold)),
|
|
51
|
+
writeAnswer: (judgment, verbose, yesThreshold = defaultPatdownYesThreshold, elapsedMs = 0) => Console.log(formatPatdownAnswer(judgment, verbose, yesThreshold, elapsedMs)),
|
|
44
52
|
writeRulesDocument: (document) => Console.log(formatPatdownRulesDocument(document)),
|
|
45
53
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patdown-probability-bar.d.ts","sourceRoot":"","sources":["../src/patdown-probability-bar.ts"],"names":[],"mappings":"AAgBA,oFAAoF;AACpF,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAqBvE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const patdownProbabilityBarFilled = '▓';
|
|
2
|
+
const patdownProbabilityBarPartial = '▒';
|
|
3
|
+
const patdownProbabilityBarEmpty = '░';
|
|
4
|
+
const patdownProbabilityBarWidth = 10;
|
|
5
|
+
function clampPatdownProbability(probability) {
|
|
6
|
+
if (probability <= 0)
|
|
7
|
+
return 0;
|
|
8
|
+
if (probability >= 1)
|
|
9
|
+
return 1;
|
|
10
|
+
return probability;
|
|
11
|
+
}
|
|
12
|
+
/** Ten-cell shade bar for estimated P(yes). Search for ▓ ▒ ░ to find the glyphs. */
|
|
13
|
+
export function formatPatdownProbabilityBar(probability) {
|
|
14
|
+
const filledCells = clampPatdownProbability(probability) * patdownProbabilityBarWidth;
|
|
15
|
+
const cells = [];
|
|
16
|
+
for (let index = 0; index < patdownProbabilityBarWidth; index += 1) {
|
|
17
|
+
const cellFill = filledCells - index;
|
|
18
|
+
if (cellFill >= 1) {
|
|
19
|
+
cells.push(patdownProbabilityBarFilled);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (cellFill >= 0.5) {
|
|
23
|
+
cells.push(patdownProbabilityBarPartial);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
cells.push(patdownProbabilityBarEmpty);
|
|
27
|
+
}
|
|
28
|
+
return cells.join('');
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patdown",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Standalone CLI that lints a tree against fuzzy markdown rules",
|
|
5
5
|
"homepage": "https://github.com/tyler-dot-earth/patdown",
|
|
6
6
|
"bugs": {
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
49
49
|
"effect": "4.0.0-rc.112",
|
|
50
50
|
"import-meta-resolve": "4.2.0",
|
|
51
|
-
"@patdown/jev": "0.
|
|
52
|
-
"@patdown/rules": "0.
|
|
51
|
+
"@patdown/jev": "0.4.0",
|
|
52
|
+
"@patdown/rules": "0.4.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@effect/language-service": "0.87.2",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"oxlint-tsgolint": "^7.0.2001",
|
|
62
62
|
"typescript": "7.0.2",
|
|
63
63
|
"vitest": "^4.1.11",
|
|
64
|
-
"@patdown/
|
|
65
|
-
"@patdown/
|
|
64
|
+
"@patdown/tsconfig": "0.0.0",
|
|
65
|
+
"@patdown/oxlint-config": "0.0.0"
|
|
66
66
|
},
|
|
67
67
|
"engines": {
|
|
68
68
|
"node": ">=22.22.2"
|