agent-simple-english 0.1.0 → 0.2.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +174 -78
- package/commands/ste.md +1 -1
- package/hooks/hooks.json +1 -1
- package/package.json +13 -2
- package/src/adapter/feedback.ts +2 -1
- package/src/adapter/rule-summary.ts +46 -5
- package/src/cli/hook.ts +148 -52
- package/src/cli/main.ts +81 -9
- package/src/cli/observation-log.ts +257 -0
- package/src/cli/session-command.ts +14 -6
- package/src/cli/session-state.ts +3 -8
- package/src/cli/state-directory.ts +9 -0
- package/src/config/schema.ts +23 -0
- package/src/dictionary/README.md +61 -7
- package/src/dictionary/bundled-rule-data.ts +13 -0
- package/src/dictionary/configured.ts +16 -0
- package/src/dictionary/data/adjectival-participles.json +15 -0
- package/src/dictionary/data/hedging.json +22 -0
- package/src/dictionary/data/marketing.json +40 -0
- package/src/dictionary/data/phrasal-verbs.json +55 -0
- package/src/dictionary/form.ts +2 -0
- package/src/dictionary/load.ts +84 -6
- package/src/dictionary/rule-data.ts +12 -0
- package/src/dictionary/schema.ts +18 -1
- package/src/engine/case-fold.ts +17 -0
- package/src/engine/comments.ts +273 -23
- package/src/engine/diff-match.ts +189 -0
- package/src/engine/kinds.ts +19 -2
- package/src/engine/lint.ts +209 -238
- package/src/engine/markdown-syntax.ts +171 -0
- package/src/engine/markdown.ts +591 -245
- package/src/engine/paragraphs.ts +47 -6
- package/src/engine/phrase-matcher.ts +62 -0
- package/src/engine/rules/dictionary.ts +38 -3
- package/src/engine/rules/hedging.ts +19 -16
- package/src/engine/rules/marketing.ts +122 -56
- package/src/engine/rules/paragraph-length.ts +6 -1
- package/src/engine/rules/phrasal-verb.ts +36 -41
- package/src/engine/rules/registry.ts +1 -0
- package/src/engine/rules/verb-form.ts +26 -3
- package/src/engine/sentences.ts +359 -20
- package/src/engine/suppression.ts +174 -0
- package/src/engine/tokens.ts +2 -2
- package/src/engine/types.ts +19 -5
- package/src/extension/index.ts +63 -35
package/src/cli/hook.ts
CHANGED
|
@@ -6,12 +6,18 @@ import { blankCommitMetadata, findCommitInvocations } from "../adapter/commit-me
|
|
|
6
6
|
import { formatViolations } from "../adapter/feedback.ts"
|
|
7
7
|
import { ruleSummary } from "../adapter/rule-summary.ts"
|
|
8
8
|
import { loadConfig } from "../config/load.ts"
|
|
9
|
-
import {
|
|
9
|
+
import { loadConfiguredDictionary } from "../dictionary/configured.ts"
|
|
10
|
+
import { loadRuleData } from "../dictionary/load.ts"
|
|
10
11
|
import { classifyPath } from "../engine/kinds.ts"
|
|
11
12
|
import { lint } from "../engine/lint.ts"
|
|
12
13
|
import type { Tagger } from "../engine/tagger.ts"
|
|
13
|
-
import type { LintOptions,
|
|
14
|
+
import type { LintKind, LintOptions, ReportViolation } from "../engine/types.ts"
|
|
14
15
|
import { TaggerService } from "../tagger/wink.ts"
|
|
16
|
+
import {
|
|
17
|
+
type ObservationDraft,
|
|
18
|
+
type ObservationEvent,
|
|
19
|
+
appendObservation,
|
|
20
|
+
} from "./observation-log.ts"
|
|
15
21
|
import {
|
|
16
22
|
consumePendingFeedback,
|
|
17
23
|
getSessionControl,
|
|
@@ -209,12 +215,12 @@ function addContext(
|
|
|
209
215
|
function nonBlockingError(message: string): HookError {
|
|
210
216
|
return {
|
|
211
217
|
continue: true,
|
|
212
|
-
systemMessage: `
|
|
218
|
+
systemMessage: `Writing-rule hook error: ${message}. The event is allowed.`,
|
|
213
219
|
}
|
|
214
220
|
}
|
|
215
221
|
|
|
216
222
|
function nonBlockingWarning(message: string): HookDecision {
|
|
217
|
-
return allow([`
|
|
223
|
+
return allow([`Writing-rule hook warning: ${message}. The event is allowed.`])
|
|
218
224
|
}
|
|
219
225
|
|
|
220
226
|
export function hookInternalFailure(cause: Cause.Cause<unknown>): HookOutput {
|
|
@@ -511,19 +517,24 @@ const readSessionControl = (sessionId: string) =>
|
|
|
511
517
|
catch: (cause) => new Error(`cannot read session state: ${cause}`),
|
|
512
518
|
})
|
|
513
519
|
|
|
514
|
-
const loadLintOptions = (cwd: string, tagger: Tagger) =>
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
dictionary
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
520
|
+
const loadLintOptions = (cwd: string, tagger: Tagger) =>
|
|
521
|
+
Effect.gen(function* () {
|
|
522
|
+
const config = yield* loadConfig(undefined, cwd)
|
|
523
|
+
const dictionaryPath = process.env.SIMPLE_ENGLISH_DICTIONARY
|
|
524
|
+
const { dictionary, ruleData } = yield* Effect.all({
|
|
525
|
+
dictionary: loadConfiguredDictionary(
|
|
526
|
+
config,
|
|
527
|
+
cwd,
|
|
528
|
+
dictionaryPath === undefined ? undefined : resolve(cwd, dictionaryPath),
|
|
529
|
+
),
|
|
530
|
+
ruleData: loadRuleData(config.ruleDataExtensions, cwd),
|
|
531
|
+
})
|
|
532
|
+
return { ...config, dictionary, ruleData, tagger } satisfies LintOptions
|
|
533
|
+
})
|
|
523
534
|
|
|
524
|
-
function splitViolations(violations: readonly
|
|
525
|
-
readonly hard:
|
|
526
|
-
readonly soft:
|
|
535
|
+
function splitViolations(violations: readonly ReportViolation[]): {
|
|
536
|
+
readonly hard: ReportViolation[]
|
|
537
|
+
readonly soft: ReportViolation[]
|
|
527
538
|
} {
|
|
528
539
|
return {
|
|
529
540
|
hard: violations.filter((violation) => violation.severity === "hard"),
|
|
@@ -531,13 +542,48 @@ function splitViolations(violations: readonly Violation[]): {
|
|
|
531
542
|
}
|
|
532
543
|
}
|
|
533
544
|
|
|
545
|
+
type EvaluationObservation = Omit<ObservationDraft, "sessionId" | "cwd">
|
|
546
|
+
|
|
547
|
+
interface HookEvaluation {
|
|
548
|
+
readonly output: HookOutput
|
|
549
|
+
readonly observation?: EvaluationObservation
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function evaluated(
|
|
553
|
+
output: HookOutput,
|
|
554
|
+
event: ObservationEvent,
|
|
555
|
+
kind: LintKind,
|
|
556
|
+
text: string,
|
|
557
|
+
violations: readonly ReportViolation[],
|
|
558
|
+
path?: string,
|
|
559
|
+
): HookEvaluation {
|
|
560
|
+
const preToolDenied =
|
|
561
|
+
"hookSpecificOutput" in output &&
|
|
562
|
+
output.hookSpecificOutput !== undefined &&
|
|
563
|
+
"permissionDecision" in output.hookSpecificOutput &&
|
|
564
|
+
output.hookSpecificOutput.permissionDecision === "deny"
|
|
565
|
+
const decision =
|
|
566
|
+
("decision" in output && output.decision === "block") || preToolDenied ? "deny" : "allow"
|
|
567
|
+
return {
|
|
568
|
+
output,
|
|
569
|
+
observation: {
|
|
570
|
+
event,
|
|
571
|
+
kind,
|
|
572
|
+
text,
|
|
573
|
+
violations,
|
|
574
|
+
decision,
|
|
575
|
+
...(path === undefined ? {} : { path }),
|
|
576
|
+
},
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
534
580
|
function textDecision(
|
|
535
581
|
operation: "write" | "edit",
|
|
536
582
|
path: string,
|
|
537
583
|
text: string,
|
|
538
584
|
options: LintOptions,
|
|
539
585
|
previousText?: string,
|
|
540
|
-
):
|
|
586
|
+
): HookEvaluation {
|
|
541
587
|
const classification = classifyPath(path)
|
|
542
588
|
const report = lint(classification.kind, text, {
|
|
543
589
|
...options,
|
|
@@ -545,68 +591,99 @@ function textDecision(
|
|
|
545
591
|
previousText,
|
|
546
592
|
})
|
|
547
593
|
const { hard, soft } = splitViolations(report.violations)
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
594
|
+
const output =
|
|
595
|
+
hard.length > 0
|
|
596
|
+
? deny(formatViolations(path, `Writing rules blocked ${operation} for`, hard))
|
|
597
|
+
: allow(soft.length === 0 ? [] : [formatViolations(path, "Writing-rule warnings for", soft)])
|
|
598
|
+
return evaluated(output, operation, classification.kind, text, report.violations, path)
|
|
552
599
|
}
|
|
553
600
|
|
|
554
|
-
function evaluateReply(event: StopEvent, tagger: Tagger): Effect.Effect<
|
|
601
|
+
function evaluateReply(event: StopEvent, tagger: Tagger): Effect.Effect<HookEvaluation, Error> {
|
|
555
602
|
return Effect.gen(function* () {
|
|
556
603
|
const reply = yield* event.lastAssistantMessage === undefined
|
|
557
604
|
? readAssistantReply(event.transcriptPath)
|
|
558
605
|
: readEventAssistantReply(event.lastAssistantMessage, event.transcriptPath)
|
|
559
606
|
if (yield* replyWasProcessed(event.sessionId, reply.identity)) {
|
|
560
|
-
return {} as Record<string, never>
|
|
607
|
+
return { output: {} as Record<string, never> }
|
|
561
608
|
}
|
|
562
609
|
const options = yield* loadLintOptions(event.cwd, tagger)
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
)
|
|
610
|
+
const violations = lint("prose-file", reply.text, options).violations
|
|
611
|
+
const hard = violations.filter((violation) => violation.severity === "hard")
|
|
566
612
|
const feedback =
|
|
567
613
|
hard.length === 0
|
|
568
614
|
? undefined
|
|
569
|
-
: formatViolations("assistant reply", "
|
|
615
|
+
: formatViolations("assistant reply", "Writing-rule reply feedback for", hard)
|
|
570
616
|
const currentControl = yield* updateReplyFeedback(event.sessionId, reply.identity, feedback)
|
|
571
|
-
|
|
617
|
+
const output: HookOutput =
|
|
572
618
|
currentControl === undefined ||
|
|
573
619
|
!currentControl.strict ||
|
|
574
620
|
event.stopHookActive ||
|
|
575
621
|
hard.length === 0
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
622
|
+
? ({} as Record<string, never>)
|
|
623
|
+
: {
|
|
624
|
+
decision: "block",
|
|
625
|
+
reason: formatViolations("assistant reply", "Writing rules blocked reply for", hard),
|
|
626
|
+
}
|
|
627
|
+
return evaluated(output, "reply", "prose-file", reply.text, violations)
|
|
628
|
+
})
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function restoreCommitSnippets(
|
|
632
|
+
message: string,
|
|
633
|
+
prepared: string,
|
|
634
|
+
violations: readonly ReportViolation[],
|
|
635
|
+
): ReportViolation[] {
|
|
636
|
+
return violations.map((violation) => {
|
|
637
|
+
const offset = prepared.indexOf(violation.snippet)
|
|
638
|
+
return offset === -1
|
|
639
|
+
? violation
|
|
640
|
+
: { ...violation, snippet: message.slice(offset, offset + violation.snippet.length) }
|
|
583
641
|
})
|
|
584
642
|
}
|
|
585
643
|
|
|
586
|
-
function evaluateEvent(
|
|
644
|
+
function evaluateEvent(
|
|
645
|
+
event: PreToolUseEvent,
|
|
646
|
+
tagger: Tagger,
|
|
647
|
+
): Effect.Effect<HookEvaluation, Error> {
|
|
587
648
|
if (event.toolName === "Bash") {
|
|
588
649
|
const invocations = findCommitInvocations(event.command)
|
|
589
|
-
if (invocations.length === 0) return Effect.succeed(allow())
|
|
650
|
+
if (invocations.length === 0) return Effect.succeed({ output: allow() })
|
|
590
651
|
if (invocations.some((invocation) => invocation.requiresExplicitMessage)) {
|
|
591
|
-
return Effect.succeed(
|
|
592
|
-
deny(
|
|
593
|
-
"
|
|
652
|
+
return Effect.succeed({
|
|
653
|
+
output: deny(
|
|
654
|
+
"Writing rules could not check the git commit message. Use git commit with a static -m or --message argument.",
|
|
594
655
|
),
|
|
595
|
-
)
|
|
656
|
+
})
|
|
596
657
|
}
|
|
597
658
|
return Effect.gen(function* () {
|
|
598
659
|
const options = yield* loadLintOptions(event.cwd, tagger)
|
|
599
|
-
const
|
|
600
|
-
invocation.requiresExplicitMessage
|
|
601
|
-
? []
|
|
602
|
-
: lint("commit-message", blankCommitMetadata(invocation.message), options).violations,
|
|
660
|
+
const messages = invocations.flatMap((invocation) =>
|
|
661
|
+
invocation.requiresExplicitMessage ? [] : [invocation.message],
|
|
603
662
|
)
|
|
663
|
+
const violations = messages.flatMap((message) => {
|
|
664
|
+
const prepared = blankCommitMetadata(message)
|
|
665
|
+
return restoreCommitSnippets(
|
|
666
|
+
message,
|
|
667
|
+
prepared,
|
|
668
|
+
lint("commit-message", prepared, options).violations,
|
|
669
|
+
)
|
|
670
|
+
})
|
|
604
671
|
const { hard, soft } = splitViolations(violations)
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
672
|
+
const output =
|
|
673
|
+
hard.length > 0
|
|
674
|
+
? deny(formatViolations("commit message", "Writing rules blocked commit for", hard))
|
|
675
|
+
: allow(
|
|
676
|
+
soft.length === 0
|
|
677
|
+
? []
|
|
678
|
+
: [formatViolations("commit message", "Writing-rule warnings for", soft)],
|
|
679
|
+
)
|
|
680
|
+
return evaluated(
|
|
681
|
+
output,
|
|
682
|
+
"commit-message",
|
|
683
|
+
"commit-message",
|
|
684
|
+
messages.join("\n\n"),
|
|
685
|
+
violations,
|
|
686
|
+
event.cwd,
|
|
610
687
|
)
|
|
611
688
|
})
|
|
612
689
|
}
|
|
@@ -623,6 +700,23 @@ function evaluateEvent(event: PreToolUseEvent, tagger: Tagger): Effect.Effect<Ho
|
|
|
623
700
|
})
|
|
624
701
|
}
|
|
625
702
|
|
|
703
|
+
function recordEvaluation(event: HookEvent, evaluation: HookEvaluation): Effect.Effect<HookOutput> {
|
|
704
|
+
const observation = evaluation.observation
|
|
705
|
+
if (observation === undefined) return Effect.succeed(evaluation.output)
|
|
706
|
+
return Effect.tryPromise({
|
|
707
|
+
try: () =>
|
|
708
|
+
appendObservation({
|
|
709
|
+
...observation,
|
|
710
|
+
sessionId: event.sessionId,
|
|
711
|
+
cwd: event.cwd,
|
|
712
|
+
}),
|
|
713
|
+
catch: () => undefined,
|
|
714
|
+
}).pipe(
|
|
715
|
+
Effect.catchAll(() => Effect.void),
|
|
716
|
+
Effect.as(evaluation.output),
|
|
717
|
+
)
|
|
718
|
+
}
|
|
719
|
+
|
|
626
720
|
export function runHookMode(raw: string): Effect.Effect<HookOutput, never, TaggerService> {
|
|
627
721
|
return Effect.try({
|
|
628
722
|
try: () => decodeEvent(raw),
|
|
@@ -653,12 +747,14 @@ export function runHookMode(raw: string): Effect.Effect<HookOutput, never, Tagge
|
|
|
653
747
|
if (event.hookEventName === "Stop") {
|
|
654
748
|
return Effect.gen(function* () {
|
|
655
749
|
const tagger = yield* TaggerService
|
|
656
|
-
|
|
750
|
+
const evaluation = yield* evaluateReply(event, tagger)
|
|
751
|
+
return yield* recordEvaluation(event, evaluation)
|
|
657
752
|
})
|
|
658
753
|
}
|
|
659
754
|
return Effect.gen(function* () {
|
|
660
755
|
const tagger = yield* TaggerService
|
|
661
|
-
|
|
756
|
+
const evaluation = yield* evaluateEvent(event, tagger)
|
|
757
|
+
return yield* recordEvaluation(event, evaluation)
|
|
662
758
|
})
|
|
663
759
|
}),
|
|
664
760
|
Effect.catchAll((error) =>
|
package/src/cli/main.ts
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { readFile } from "node:fs/promises"
|
|
3
3
|
import { Effect, Either } from "effect"
|
|
4
|
+
import packageManifest from "../../package.json" with { type: "json" }
|
|
4
5
|
import { loadConfig } from "../config/load.ts"
|
|
5
|
-
import {
|
|
6
|
+
import { loadConfiguredDictionary } from "../dictionary/configured.ts"
|
|
7
|
+
import { loadRuleData } from "../dictionary/load.ts"
|
|
6
8
|
import { classifyPath } from "../engine/kinds.ts"
|
|
7
9
|
import { lint } from "../engine/lint.ts"
|
|
8
10
|
import type { LintKind, LintReport } from "../engine/types.ts"
|
|
9
11
|
import { TaggerService, WinkTaggerLive } from "../tagger/wink.ts"
|
|
10
12
|
import { hookInternalFailure, runHookMode } from "./hook.ts"
|
|
13
|
+
import { observationStats, reviewObservations } from "./observation-log.ts"
|
|
11
14
|
import { runSessionCommand } from "./session-command.ts"
|
|
12
15
|
|
|
13
16
|
const KINDS: readonly LintKind[] = ["prose-file", "slash-source", "hash-source", "commit-message"]
|
|
14
17
|
|
|
18
|
+
const USAGE = `Usage: simple-english [options] [paths...]
|
|
19
|
+
simple-english observe review
|
|
20
|
+
simple-english observe stats
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--json Write a JSON report.
|
|
24
|
+
--config <path> Use one config file.
|
|
25
|
+
--kind <kind> Use one content kind.
|
|
26
|
+
--help Print this help.
|
|
27
|
+
--version Print the package version.`
|
|
28
|
+
|
|
15
29
|
interface CliArgs {
|
|
16
30
|
readonly json: boolean
|
|
17
31
|
readonly configPath: string | undefined
|
|
18
32
|
readonly kind: string | undefined
|
|
19
33
|
readonly kindMissingValue: boolean
|
|
34
|
+
readonly help: boolean
|
|
35
|
+
readonly version: boolean
|
|
20
36
|
readonly paths: readonly string[]
|
|
21
37
|
}
|
|
22
38
|
|
|
@@ -26,16 +42,20 @@ const parseArgs = (args: readonly string[]): Effect.Effect<CliArgs, Error> =>
|
|
|
26
42
|
let configPath: string | undefined
|
|
27
43
|
let kind: string | undefined
|
|
28
44
|
let kindMissingValue = false
|
|
45
|
+
let help = false
|
|
46
|
+
let version = false
|
|
29
47
|
const paths: string[] = []
|
|
30
48
|
for (let i = 0; i < args.length; i++) {
|
|
31
49
|
const arg = args[i] as string
|
|
32
50
|
if (arg === "--json") {
|
|
33
51
|
json = true
|
|
34
52
|
} else if (arg === "--config") {
|
|
35
|
-
|
|
36
|
-
if (
|
|
53
|
+
const value = args[i + 1]
|
|
54
|
+
if (value === undefined || value.startsWith("--")) {
|
|
37
55
|
yield* Effect.fail(new Error("--config requires a file path"))
|
|
38
56
|
}
|
|
57
|
+
configPath = value
|
|
58
|
+
i++
|
|
39
59
|
} else if (arg === "--kind") {
|
|
40
60
|
const value = args[i + 1]
|
|
41
61
|
if (value === undefined || value.startsWith("--")) {
|
|
@@ -46,13 +66,24 @@ const parseArgs = (args: readonly string[]): Effect.Effect<CliArgs, Error> =>
|
|
|
46
66
|
}
|
|
47
67
|
} else if (arg.startsWith("--kind=")) {
|
|
48
68
|
kind = arg.slice("--kind=".length)
|
|
69
|
+
} else if (arg === "--help") {
|
|
70
|
+
help = true
|
|
71
|
+
} else if (arg === "--version") {
|
|
72
|
+
version = true
|
|
73
|
+
} else if (arg.startsWith("--")) {
|
|
74
|
+
yield* Effect.fail(new Error(`unknown flag "${arg}"`))
|
|
49
75
|
} else {
|
|
50
76
|
paths.push(arg)
|
|
51
77
|
}
|
|
52
78
|
}
|
|
53
|
-
return { json, configPath, kind, kindMissingValue, paths }
|
|
79
|
+
return { json, configPath, kind, kindMissingValue, help, version, paths }
|
|
54
80
|
})
|
|
55
81
|
|
|
82
|
+
const rejectUnknownFlags = (args: readonly string[]): Effect.Effect<void, Error> => {
|
|
83
|
+
const flag = args.find((arg) => arg.startsWith("--"))
|
|
84
|
+
return flag === undefined ? Effect.void : Effect.fail(new Error(`unknown flag "${flag}"`))
|
|
85
|
+
}
|
|
86
|
+
|
|
56
87
|
const isLintKind = (value: string): value is LintKind =>
|
|
57
88
|
(KINDS as readonly string[]).includes(value)
|
|
58
89
|
|
|
@@ -61,6 +92,7 @@ interface FileViolation {
|
|
|
61
92
|
readonly ruleId: string
|
|
62
93
|
readonly severity: string
|
|
63
94
|
readonly message: string
|
|
95
|
+
readonly snippet: string
|
|
64
96
|
readonly suggestions?: readonly string[]
|
|
65
97
|
readonly line: number
|
|
66
98
|
readonly column: number
|
|
@@ -130,9 +162,38 @@ const sessionProgram = Effect.gen(function* () {
|
|
|
130
162
|
return 0
|
|
131
163
|
})
|
|
132
164
|
|
|
165
|
+
const observeProgram = Effect.gen(function* () {
|
|
166
|
+
const command = args[1]
|
|
167
|
+
if (args.length !== 2 || (command !== "review" && command !== "stats")) {
|
|
168
|
+
return yield* Effect.fail(new Error("Usage: simple-english observe <review|stats>"))
|
|
169
|
+
}
|
|
170
|
+
if (command === "review") {
|
|
171
|
+
yield* Effect.tryPromise({
|
|
172
|
+
try: () => reviewObservations(),
|
|
173
|
+
catch: (cause) => new Error(`cannot review observations: ${cause}`),
|
|
174
|
+
})
|
|
175
|
+
} else {
|
|
176
|
+
console.log(
|
|
177
|
+
yield* Effect.tryPromise({
|
|
178
|
+
try: () => observationStats(),
|
|
179
|
+
catch: (cause) => new Error(`cannot read observation stats: ${cause}`),
|
|
180
|
+
}),
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
return 0
|
|
184
|
+
})
|
|
185
|
+
|
|
133
186
|
const lintProgram = Effect.gen(function* () {
|
|
134
187
|
const tagger = yield* TaggerService
|
|
135
|
-
const { json, configPath, kind, kindMissingValue, paths } = yield* parseArgs(args)
|
|
188
|
+
const { json, configPath, kind, kindMissingValue, help, version, paths } = yield* parseArgs(args)
|
|
189
|
+
if (help) {
|
|
190
|
+
console.log(USAGE)
|
|
191
|
+
return 0
|
|
192
|
+
}
|
|
193
|
+
if (version) {
|
|
194
|
+
console.log(packageManifest.version)
|
|
195
|
+
return 0
|
|
196
|
+
}
|
|
136
197
|
if (kindMissingValue) {
|
|
137
198
|
return yield* Effect.fail(
|
|
138
199
|
new Error(`--kind requires a value; expected one of: ${KINDS.join(", ")}`),
|
|
@@ -145,12 +206,20 @@ const lintProgram = Effect.gen(function* () {
|
|
|
145
206
|
}
|
|
146
207
|
const config = yield* loadConfig(configPath)
|
|
147
208
|
const loadedDictionary = yield* Effect.either(
|
|
148
|
-
|
|
209
|
+
loadConfiguredDictionary(config, process.cwd(), process.env.SIMPLE_ENGLISH_DICTIONARY),
|
|
149
210
|
)
|
|
211
|
+
const loadedRuleData = yield* Effect.either(loadRuleData(config.ruleDataExtensions))
|
|
212
|
+
if (Either.isLeft(loadedDictionary) && config.approvedWordsPath !== undefined) {
|
|
213
|
+
return yield* Effect.fail(loadedDictionary.left)
|
|
214
|
+
}
|
|
150
215
|
const dictionary = Either.getOrUndefined(loadedDictionary)
|
|
216
|
+
const ruleData = Either.getOrUndefined(loadedRuleData)
|
|
151
217
|
if (Either.isLeft(loadedDictionary)) {
|
|
152
218
|
yield* Effect.sync(() => console.error(loadedDictionary.left.message))
|
|
153
219
|
}
|
|
220
|
+
if (Either.isLeft(loadedRuleData)) {
|
|
221
|
+
yield* Effect.sync(() => console.error(loadedRuleData.left.message))
|
|
222
|
+
}
|
|
154
223
|
const inputs =
|
|
155
224
|
paths.length === 0
|
|
156
225
|
? [{ path: "<stdin>", text: yield* readStdin }]
|
|
@@ -164,6 +233,7 @@ const lintProgram = Effect.gen(function* () {
|
|
|
164
233
|
report: lint(kind ?? classification.kind, text, {
|
|
165
234
|
...config,
|
|
166
235
|
dictionary,
|
|
236
|
+
ruleData,
|
|
167
237
|
tagger,
|
|
168
238
|
sourceDialect: classification.sourceDialect,
|
|
169
239
|
}),
|
|
@@ -180,10 +250,12 @@ const lintProgram = Effect.gen(function* () {
|
|
|
180
250
|
|
|
181
251
|
const program: Effect.Effect<number, Error> =
|
|
182
252
|
args[0] === "hook"
|
|
183
|
-
? hookProgram
|
|
253
|
+
? rejectUnknownFlags(args.slice(1)).pipe(Effect.andThen(hookProgram))
|
|
184
254
|
: args[0] === "session"
|
|
185
|
-
? sessionProgram
|
|
186
|
-
:
|
|
255
|
+
? rejectUnknownFlags(args.slice(1)).pipe(Effect.andThen(sessionProgram))
|
|
256
|
+
: args[0] === "observe"
|
|
257
|
+
? observeProgram
|
|
258
|
+
: lintProgram.pipe(Effect.provide(WinkTaggerLive))
|
|
187
259
|
|
|
188
260
|
const handled = program.pipe(
|
|
189
261
|
Effect.catchAll((error) =>
|