lawspec 0.7.0 → 0.9.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/index.d.ts CHANGED
@@ -1,29 +1,248 @@
1
1
  // Generated from LawSpec.Gen. Do not edit.
2
- export type Target = 'java' | 'python' | 'javascript' | 'typescript' | 'go' | 'haskell' | 'kotlin';
3
- export interface Source { path: string; content: string }
4
- export interface Location { file: string; line: number; column: number }
5
- export interface Diagnostic { code: string; message: string; at: Location | null }
6
- export interface Artifact { path: string; content: string; ownership: 'user' | 'generated'; placement: 'source' | 'test' }
7
- export type Type = {tag: 'Named' | 'Variable'; contents: string} | {tag: 'Arrow'; contents: [Type, Type]} | {tag: 'Applied'; contents: ['Nullable' | 'Optional', Type]} | {tag:'Refined'; contents:[string, Type, Expr | null]} | {tag:'Qualified'; contents:[Constraint[],Type]} | {tag:'CheckedType'; contents:[Expr[],Type]} | {tag:'RefinementApp'; contents:[string,RefinementArgument[]]};
8
- export interface Constraint {tag:'Capability'; contents:['Eq'|'Integer'|'Ordered'|'Bounded',Type]}
9
- export type RefinementArgument = {tag:'TypeArgument'; contents:Type} | {tag:'ValueArgument'; contents:Expr};
10
- export interface Refinement {refinementName:string; refinementParameters:[string,Type][]; refinementRequirements:Constraint[]; refinementBody:Type}
11
- export interface Contract {contractName:string; contractArguments:[string,Type][]; contractResult:[string,Type]; contractPreconditions:Expr[]; contractPostconditions:Expr[]}
12
- export interface Generation {cases:number; maxAttempts:number; maxShrinks:number; exhaustiveLimit:number}
13
- export interface Input {inputName:string; inputId:string; inputType:Type; inputRefinements:Expr[]}
14
- export interface DomainPlan {domainInput:Input; domainBounds:[string,Expr][]}
15
- export type IntegerType = 'Int8' | 'Int16' | 'Int32' | 'Int64' | 'UInt8' | 'UInt16' | 'UInt32' | 'UInt64' | 'IntSize' | 'UIntSize' | 'UIntPtr' | 'Integer' | 'BigInt' | 'BigUInt';
16
- export type ScalarValue = {type: IntegerType; value: string} | {type: 'Bool'; value: boolean} | {type: 'Decimal'; coefficient: string; exponent: string} | {type: 'Rational'; numerator: string; denominator: string} | {type: 'Float32' | 'Float64'; bits: string} | {type: 'Complex64' | 'Complex128'; real: ScalarValue; imaginary: ScalarValue} | {type: 'Char' | 'CodePoint' | 'CodeUnit16'; value: number} | {type: 'Text' | 'CodePointText' | 'Utf16Text' | 'Bytes'; units: number[]} | {type: 'Symbol'; id: string; description: string} | {type: 'Unit' | 'Null' | 'Undefined'} | {type: 'Nullable' | 'Optional'; value: ScalarValue | null};
17
- export type Expr = {tag: 'Var' | 'StringLit'; contents: string} | {tag: 'Number'; contents: string} | {tag: 'DecimalNumber'; contents: [string, string]} | {tag: 'BoolLit'; contents: boolean} | {tag: 'Apply' | 'Compose'; contents: [Expr, Expr]} | {tag: 'ScalarLit'; contents: ScalarValue} | {tag: 'Annotate'; contents: [Expr, Type]} | {tag: 'Unary'; contents: [string, Expr]} | {tag: 'Binary'; contents: [string, Expr, Expr]} | {tag:'TypeBound'; contents:['min'|'max',Type]};
18
- export type Definition = {tag: 'Forall'; contents: [[string, Type][], Definition]} | {tag: 'Equal'; contents: [Expr, Expr]} | {tag: 'Holds'; contents: Expr} | {tag: 'Implies'; contents: [Expr, Definition]} | {tag: 'And'; contents: [Definition, Definition]} | {tag: 'Invoke'; contents: [string, Expr[]]};
19
- export interface Expectation { actual: Expr; expected: ScalarValue }
20
- export interface Example { exampleName: string; bindings: [string, ScalarValue][]; expectations: Expectation[] }
21
- export interface Law { lawName: string; parameters: [string, Type][]; requirements: Constraint[]; definition: Definition; description: string; rationale: string; examples: Example[]; references: string[]; location: Location }
22
- export type Assertion = {tag: 'AssertEqual'; contents: [Expr, Expr]} | {tag: 'AssertImplies'; contents: [Expr, Assertion]} | {tag: 'AssertAll'; contents: Assertion[]};
23
- export interface Expanded { owner: string; name: string; inputs: Input[]; left: Expr; right: Expr; guards: Expr[]; assertion: Assertion; trace: string[]; original: Law; typedExpressions: TypedExpr[]; propertyKind: 'law'|'contract'; generation:Generation; generationPlan:DomainPlan[] }
24
- export interface TypedExpr { expressionType: Type; expression: Expr; operands: TypedExpr[]; requiredConversion: Type | null }
25
- export interface CheckRequest { sources: Source[]; schemaVersion?: 2; machineBits?: 32 | 64; generation?:Partial<Generation> }
26
- export interface GenerationRequest extends CheckRequest { target: Target; sourceDir?: string; testDir?: string }
27
- export interface Result { schemaVersion: 2; machineBits?: 32 | 64; generation?:Generation; refinements?:{owner:string; declaration:Refinement}[]; contracts?:{owner:string; contract:Contract}[]; diagnostics: Diagnostic[]; laws?: Expanded[]; expansions?: string[]; files?: Artifact[] }
28
- export interface Compiler { check(input: CheckRequest): Promise<Result>; expand(input: CheckRequest): Promise<Result>; planGeneration(input: GenerationRequest): Promise<Result> }
2
+ export type Target =
3
+ 'java' |
4
+ 'python' |
5
+ 'javascript' |
6
+ 'typescript' |
7
+ 'go' |
8
+ 'haskell' |
9
+ 'kotlin' |
10
+ 'rust';
11
+
12
+ export interface Source {
13
+ path: string;
14
+ content: string;
15
+ }
16
+
17
+ export interface Location {
18
+ file: string;
19
+ line: number;
20
+ column: number;
21
+ }
22
+
23
+ export interface Span {
24
+ start: Location;
25
+ end: Location;
26
+ }
27
+
28
+ export interface Diagnostic {
29
+ code: string;
30
+ message: string;
31
+ at: Location | null;
32
+ }
33
+
34
+ export interface Artifact {
35
+ path: string;
36
+ content: string;
37
+ ownership: 'user' | 'generated';
38
+ placement: 'source' | 'test';
39
+ adapterReference?: string;
40
+ }
41
+
42
+ export type Type =
43
+ {kind: 'constructor'; name: string; arguments: TypeArgument[];} |
44
+ {kind: 'variable'; id: string;} |
45
+ {kind: 'function'; parameter: Type; result: Type;};
46
+
47
+ export type TypeArgument =
48
+ {kind: 'type'; type: Type;} |
49
+ {kind: 'natural'; value: string;} |
50
+ {kind: 'indexVariable'; id: string;};
51
+
52
+ export type Origin =
53
+ {kind: 'source'; span: Span;} | {kind: 'generated'; declaration: string;};
54
+
55
+ export type Evidence = {kind: 'numeric' | 'structural'; type: Type;};
56
+
57
+ export interface Expr {
58
+ type: Type;
59
+ origin: Origin;
60
+ text: string;
61
+ node: ExprNode;
62
+ }
63
+
64
+ export type ExprNode =
65
+ {kind: 'constant'; value: ScalarValue;} |
66
+ {kind: 'match'; value: Expr; cases: MatchCase[];} |
67
+ {kind: 'allElements'; value: Expr; binder: Binder; predicate: Expr;} |
68
+ {kind: 'allPayloads'; value: Expr; predicates: PayloadPredicate[];} |
69
+ {kind: 'construct'; constructor: string; arguments: Expr[];} |
70
+ {kind: 'local'; id: string;} |
71
+ {kind: 'call'; declaration: string; arguments: Expr[];} |
72
+ {
73
+ kind: 'binary';
74
+ operator: string;
75
+ evidence: Evidence;
76
+ left: Expr;
77
+ right: Expr;
78
+ } |
79
+ {kind: 'unary'; operator: '-' | '!'; argument: Expr;} |
80
+ {kind: 'shortCircuit'; operator: '&&' | '||'; left: Expr; right: Expr;} |
81
+ {kind: 'convert'; conversion: 'explicit' | 'checked'; argument: Expr;} |
82
+ {kind: 'helper'; name: string; arguments: Expr[];};
83
+
84
+ export type Assertion =
85
+ {kind: 'equal'; evidence: Evidence; left: Expr; right: Expr;} |
86
+ {kind: 'implies'; guard: Expr; body: Assertion;} |
87
+ {kind: 'all'; items: Assertion[];};
88
+
89
+ export interface Generation {
90
+ cases: number;
91
+ maxAttempts: number;
92
+ maxShrinks: number;
93
+ exhaustiveLimit: number;
94
+ }
95
+
96
+ export interface Binder {
97
+ id: string;
98
+ name: string;
99
+ type: Type;
100
+ }
101
+
102
+ export interface Definition {
103
+ owner: string;
104
+ id: string;
105
+ arguments: Binder[];
106
+ body: Expr;
107
+ }
108
+
109
+ export interface Input extends Binder {
110
+ predicates: Expr[];
111
+ bounds: {operator: string; value: Expr;}[];
112
+ }
113
+
114
+ export interface Example {
115
+ name: string;
116
+ bindings: (Binder & {value: DataValue;})[];
117
+ expectations: Assertion[];
118
+ }
119
+
120
+ export interface Law {
121
+ id: string;
122
+ owner: string;
123
+ name: string;
124
+ inputs: Input[];
125
+ assertion: Assertion;
126
+ examples: Example[];
127
+ description: string;
128
+ rationale: string;
129
+ references: string[];
130
+ location: Location;
131
+ trace: string[];
132
+ generation: Generation;
133
+ }
134
+
135
+ export interface Unit {
136
+ id: string;
137
+ declarations: (Binder & {origin: Origin;})[];
138
+ }
139
+
140
+ export interface DataTypeDeclaration {
141
+ id: string;
142
+ name: string;
143
+ parameters: string[];
144
+ origin: Origin;
145
+ constructors: {id: string; name: string; fields: Binder[]; origin: Origin;}[];
146
+ }
147
+
148
+ export interface Contract {
149
+ id: string;
150
+ name: string;
151
+ arguments: Binder[];
152
+ result: Binder;
153
+ preconditions: Expr[];
154
+ postconditions: Expr[];
155
+ }
156
+
157
+ export interface Refinement {
158
+ owner: string;
159
+ name: string;
160
+ parameters: {name: string; kind: 'type' | 'value'; type: string;}[];
161
+ requirements: {capability: string; type: string;}[];
162
+ definition: string;
163
+ }
164
+
165
+ export interface CheckRequest {
166
+ sources: Source[];
167
+ schemaVersion?: 3;
168
+ machineBits?: 32 | 64;
169
+ generation?: Partial<Generation>;
170
+ }
171
+
172
+ export interface GenerationRequest extends CheckRequest {
173
+ target: Target;
174
+ sourceDir?: string;
175
+ testDir?: string;
176
+ minify?: boolean;
177
+ }
178
+
179
+ export interface Result {
180
+ schemaVersion: 3;
181
+ machineBits?: 32 | 64;
182
+ generation?: Generation;
183
+ units?: Unit[];
184
+ dataTypes?: DataTypeDeclaration[];
185
+ definitions?: Definition[];
186
+ laws?: Law[];
187
+ contracts?: {owner: string; contract: Contract;}[];
188
+ refinements?: Refinement[];
189
+ diagnostics: Diagnostic[];
190
+ expansions?: string[];
191
+ files?: Artifact[];
192
+ }
193
+
194
+ export interface PayloadPredicate {
195
+ binder: Binder;
196
+ predicate: Expr;
197
+ }
198
+
199
+ export interface MatchCase {
200
+ constructor: string;
201
+ binders: Binder[];
202
+ body: Expr;
203
+ }
204
+
205
+ export type DataValue =
206
+ ScalarValue |
207
+ {kind: 'data'; type: Type; constructor: string; fields: DataValue[];};
208
+
209
+ export type IntegerType =
210
+ 'Int8' |
211
+ 'Int16' |
212
+ 'Int32' |
213
+ 'Int64' |
214
+ 'UInt8' |
215
+ 'UInt16' |
216
+ 'UInt32' |
217
+ 'UInt64' |
218
+ 'IntSize' |
219
+ 'UIntSize' |
220
+ 'UIntPtr' |
221
+ 'Integer' |
222
+ 'BigInt' |
223
+ 'BigUInt';
224
+
225
+ export type ScalarValue =
226
+ {type: IntegerType; value: string;} |
227
+ {type: 'Bool'; value: boolean;} |
228
+ {type: 'Decimal'; coefficient: string; exponent: string;} |
229
+ {type: 'Rational'; numerator: string; denominator: string;} |
230
+ {type: 'Float32' | 'Float64'; bits: string;} |
231
+ {
232
+ type: 'Complex64' | 'Complex128';
233
+ real: ScalarValue;
234
+ imaginary: ScalarValue;
235
+ } |
236
+ {type: 'Char' | 'CodePoint' | 'CodeUnit16'; value: number;} |
237
+ {type: 'Text' | 'CodePointText' | 'Utf16Text' | 'Bytes'; units: number[];} |
238
+ {type: 'Symbol'; id: string; description: string;} |
239
+ {type: 'Unit' | 'Null' | 'Undefined';} |
240
+ {type: 'Nullable' | 'Optional'; value: ScalarValue | null;};
241
+
242
+ export interface Compiler {
243
+ check(input: CheckRequest): Promise<Result>;
244
+ expand(input: CheckRequest): Promise<Result>;
245
+ planGeneration(input: GenerationRequest): Promise<Result>;
246
+ }
247
+
29
248
  export function createCompiler(): Promise<Compiler>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lawspec",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "State the law once. Check it everywhere.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,6 +8,6 @@
8
8
  "bin": { "lawspec": "bin/lawspec.mjs" },
9
9
  "exports": { ".": { "types": "./index.d.ts", "import": "./api.mjs" } },
10
10
  "types": "index.d.ts",
11
- "files": ["*.mjs", "*.json", "index.d.ts", "bin", "core.wasm", "core_jsffi.js", "README.md", "PRIMITIVES.md", "REFINEMENTS.md", "API-MIGRATION.md", "LICENSE", "starter.lawspec", "examples"],
11
+ "files": ["*.mjs", "*.json", "index.d.ts", "bin", "core.wasm", "core_jsffi.js", "README.md", "PRIMITIVES.md", "REFINEMENTS.md", "RUST.md", "JAVA.md", "PYTHON.md", "GO.md", "HASKELL.md", "KOTLIN.md", "WEB.md", "LANGUAGE.md", "API-MIGRATION.md", "RELEASE-0.9.md", "LICENSE", "starter.lawspec", "examples"],
12
12
  "scripts": { "test": "node --test test/*.test.mjs" }
13
13
  }
package/templates.mjs CHANGED
@@ -6,8 +6,10 @@ export const targets = [
6
6
  "go",
7
7
  "haskell",
8
8
  "kotlin",
9
+ "rust",
9
10
  ];
10
11
  export const commands = {
12
+ rust: "cargo test",
11
13
  java: "mvn test",
12
14
  python: "python -m pytest",
13
15
  javascript: "node --test test/*.test.mjs",
@@ -18,6 +20,7 @@ export const commands = {
18
20
  kotlin: "gradle test",
19
21
  };
20
22
  export const setup = {
23
+ rust: "Use Rust 1.85+ with edition 2024, Proptest 1.11.0, num-bigint 0.4.8, num-rational 0.4.2, num-complex 0.4.6, and num-traits 0.2.19. Run cargo test.",
21
24
  java: "Use JDK 25+, Maven, JetCheck 0.3.0, JUnit Jupiter 5.14.x, compiler plugin 3.14.1+, Surefire 3.5.x, and maven.compiler.release=25. Run mvn test-compile.",
22
25
  python:
23
26
  'Use Python 3.13+. Install pytest 8.4.x and Hypothesis 6.x into the selected environment: python -m pip install -e ".[test]". Configure pytest pythonpath=["src"] and testpaths=["tests"]. Set the target python field to the interpreter path if needed.',
@@ -31,8 +34,97 @@ export const setup = {
31
34
  kotlin:
32
35
  "Use JDK 25, Gradle 9.3.0, Kotlin plugin 2.3.21, JVM target 25, Kotest 5.9.1 (runner, assertions, property), and useJUnitPlatform(). Run gradle testClasses.",
33
36
  };
34
- const json = (value) => JSON.stringify(value, null, 2) + "\n";
35
- export function templates(target) {
37
+ // These render known scaffold structure; they never reformat user source.
38
+ const element = (name, value, attributes = {}) => ({ name, value, attributes });
39
+ const xmlEscape = (text) =>
40
+ String(text)
41
+ .replaceAll("&", "&amp;")
42
+ .replaceAll('"', "&quot;")
43
+ .replaceAll("<", "&lt;")
44
+ .replaceAll(">", "&gt;");
45
+ function renderXml(node, minify, depth = 0) {
46
+ const indent = minify ? "" : " ".repeat(depth);
47
+ const attributes = Object.entries(node.attributes).map(
48
+ ([key, value]) => `${key}="${xmlEscape(value)}"`,
49
+ );
50
+ const open = `<${node.name}${
51
+ attributes.length === 0
52
+ ? ""
53
+ : minify
54
+ ? " " + attributes.join(" ")
55
+ : "\n" + attributes.map((a) => indent + " " + a).join("\n")
56
+ }>`;
57
+ if (!Array.isArray(node.value)) {
58
+ return `${indent}${open}${xmlEscape(node.value)}</${node.name}>`;
59
+ }
60
+ const children = node.value.map((child) =>
61
+ renderXml(child, minify, depth + 1),
62
+ );
63
+ return minify
64
+ ? `${open}${children.join("")}</${node.name}>`
65
+ : `${indent}${open}\n${children.join("\n")}\n${indent}</${node.name}>`;
66
+ }
67
+ function mavenProject(minify) {
68
+ const fields = (values) =>
69
+ Object.entries(values).map(([name, value]) => element(name, value));
70
+ const dependency = (groupId, artifactId, version) =>
71
+ element(
72
+ "dependency",
73
+ fields({ groupId, artifactId, version, scope: "test" }),
74
+ );
75
+ const plugin = (artifactId, version) =>
76
+ element(
77
+ "plugin",
78
+ fields({ groupId: "org.apache.maven.plugins", artifactId, version }),
79
+ );
80
+ return (
81
+ renderXml(
82
+ element(
83
+ "project",
84
+ [
85
+ ...fields({
86
+ modelVersion: "4.0.0",
87
+ groupId: "example",
88
+ artifactId: "lawspec-example",
89
+ version: "0.1.0",
90
+ }),
91
+ element(
92
+ "properties",
93
+ fields({
94
+ "maven.compiler.release": "25",
95
+ "project.build.sourceEncoding": "UTF-8",
96
+ }),
97
+ ),
98
+ element("dependencies", [
99
+ dependency("org.jetbrains", "jetCheck", "0.3.0"),
100
+ dependency("org.junit.jupiter", "junit-jupiter", "5.14.0"),
101
+ ]),
102
+ element("build", [
103
+ element("plugins", [
104
+ plugin("maven-compiler-plugin", "3.14.1"),
105
+ plugin("maven-surefire-plugin", "3.5.4"),
106
+ ]),
107
+ ]),
108
+ ],
109
+ {
110
+ xmlns: "http://maven.apache.org/POM/4.0.0",
111
+ "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
112
+ "xsi:schemaLocation":
113
+ "http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd",
114
+ },
115
+ ),
116
+ minify,
117
+ ) + "\n"
118
+ );
119
+ }
120
+ export function templates(target, { minify = false } = {}) {
121
+ if (typeof minify !== "boolean") throw new Error("minify must be a boolean");
122
+ const json = (value) =>
123
+ JSON.stringify(value, null, minify ? undefined : 2) + "\n";
124
+ const kotlinBlock = (name, statements) =>
125
+ minify
126
+ ? `${name} { ${statements.join("; ")} }`
127
+ : `${name} {\n${statements.map((line) => " " + line).join("\n")}\n}`;
36
128
  if (!targets.includes(target)) throw new Error(`Unknown target: ${target}`);
37
129
  const commonPackage = {
38
130
  name: "lawspec-example",
@@ -41,6 +133,28 @@ export function templates(target) {
41
133
  type: "module",
42
134
  };
43
135
  switch (target) {
136
+ case "rust":
137
+ return {
138
+ "src/lib.rs":
139
+ '// Application library. LawSpec maintains the included module declarations.\ninclude!("lawspec_modules.rs");\n',
140
+ "Cargo.toml": `[package]
141
+ name = "lawspec-example"
142
+ version = "0.1.0"
143
+ edition = "2024"
144
+ rust-version = "1.85"
145
+ publish = false
146
+
147
+ [dependencies]
148
+ num-bigint = "=0.4.8"
149
+ num-rational = "=0.4.2"
150
+ num-complex = "=0.4.6"
151
+ num-traits = "=0.2.19"
152
+
153
+ [dev-dependencies]
154
+ proptest = "=1.11.0"
155
+ `,
156
+ };
157
+
44
158
  case "javascript":
45
159
  return {
46
160
  "package.json": json({
@@ -79,18 +193,7 @@ export function templates(target) {
79
193
  };
80
194
  case "java":
81
195
  return {
82
- "pom.xml": `<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
83
- <modelVersion>4.0.0</modelVersion><groupId>example</groupId><artifactId>lawspec-example</artifactId><version>0.1.0</version>
84
- <properties><maven.compiler.release>25</maven.compiler.release><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties>
85
- <dependencies>
86
- <dependency><groupId>org.jetbrains</groupId><artifactId>jetCheck</artifactId><version>0.3.0</version><scope>test</scope></dependency>
87
- <dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.14.0</version><scope>test</scope></dependency>
88
- </dependencies>
89
- <build><plugins>
90
- <plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.14.1</version></plugin>
91
- <plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.5.4</version></plugin>
92
- </plugins></build>
93
- </project>\n`,
196
+ "pom.xml": mavenProject(minify),
94
197
  };
95
198
  case "go":
96
199
  return {
@@ -100,13 +203,24 @@ export function templates(target) {
100
203
  case "haskell":
101
204
  return {
102
205
  "stack.yaml": "snapshot: lts-24.58\npackages: [.]\n",
103
- "package.yaml": `name: lawspec-example\nversion: 0.1.0\ndependencies: [base, text, bytestring]\nlibrary:\n source-dirs: src\ntests:\n laws:\n main: Spec.hs\n source-dirs: test\n dependencies: [lawspec-example, hspec, hedgehog, hspec-hedgehog]\n build-tools: [hspec-discover]\n`,
206
+ "package.yaml": `name: lawspec-example\nversion: 0.1.0\ndependencies: [base, text, bytestring]\nlibrary:\n source-dirs: src\ntests:\n laws:\n main: Spec.hs\n source-dirs: test\n dependencies: [lawspec-example, hspec, hedgehog, hspec-hedgehog, containers, mtl]\n build-tools: [hspec-discover]\n`,
104
207
  "test/Spec.hs": "{-# OPTIONS_GHC -F -pgmF hspec-discover #-}\n",
105
208
  };
106
209
  case "kotlin":
107
210
  return {
108
211
  "settings.gradle.kts": 'rootProject.name = "lawspec-example"\n',
109
- "build.gradle.kts": `plugins { kotlin("jvm") version "2.3.21" }\nrepositories { mavenCentral() }\nkotlin { jvmToolchain(25) }\ndependencies {\n testImplementation("io.kotest:kotest-runner-junit5:5.9.1")\n testImplementation("io.kotest:kotest-assertions-core:5.9.1")\n testImplementation("io.kotest:kotest-property:5.9.1")\n}\ntasks.test { useJUnitPlatform() }\n`,
212
+ "build.gradle.kts":
213
+ [
214
+ kotlinBlock("plugins", ['kotlin("jvm") version "2.3.21"']),
215
+ kotlinBlock("repositories", ["mavenCentral()"]),
216
+ kotlinBlock("kotlin", ["jvmToolchain(25)"]),
217
+ kotlinBlock("dependencies", [
218
+ 'testImplementation("io.kotest:kotest-runner-junit5:5.9.1")',
219
+ 'testImplementation("io.kotest:kotest-assertions-core:5.9.1")',
220
+ 'testImplementation("io.kotest:kotest-property:5.9.1")',
221
+ ]),
222
+ kotlinBlock("tasks.test", ["useJUnitPlatform()"]),
223
+ ].join(minify ? "\n" : "\n\n") + "\n",
110
224
  };
111
225
  }
112
226
  }