invar-tools 1.7.1__py3-none-any.whl → 1.10.0__py3-none-any.whl
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.
- invar/__init__.py +8 -0
- invar/core/language.py +88 -0
- invar/core/models.py +106 -0
- invar/core/patterns/detector.py +6 -1
- invar/core/patterns/p0_exhaustive.py +15 -3
- invar/core/patterns/p0_literal.py +15 -3
- invar/core/patterns/p0_newtype.py +15 -3
- invar/core/patterns/p0_nonempty.py +15 -3
- invar/core/patterns/p0_validation.py +15 -3
- invar/core/patterns/registry.py +5 -1
- invar/core/patterns/types.py +5 -1
- invar/core/property_gen.py +4 -0
- invar/core/rules.py +84 -18
- invar/core/sync_helpers.py +27 -1
- invar/core/template_helpers.py +32 -0
- invar/core/ts_parsers.py +286 -0
- invar/core/ts_sig_parser.py +307 -0
- invar/node_tools/MANIFEST +7 -0
- invar/node_tools/__init__.py +51 -0
- invar/node_tools/fc-runner/cli.js +77 -0
- invar/node_tools/quick-check/cli.js +28 -0
- invar/node_tools/ts-analyzer/cli.js +480 -0
- invar/shell/claude_hooks.py +35 -12
- invar/shell/commands/guard.py +36 -1
- invar/shell/commands/init.py +133 -7
- invar/shell/commands/perception.py +157 -33
- invar/shell/commands/skill.py +187 -0
- invar/shell/commands/template_sync.py +65 -13
- invar/shell/commands/uninstall.py +77 -12
- invar/shell/commands/update.py +6 -14
- invar/shell/contract_coverage.py +1 -0
- invar/shell/fs.py +66 -13
- invar/shell/pi_hooks.py +213 -0
- invar/shell/prove/guard_ts.py +899 -0
- invar/shell/skill_manager.py +353 -0
- invar/shell/template_engine.py +28 -4
- invar/shell/templates.py +4 -4
- invar/templates/claude-md/python/critical-rules.md +33 -0
- invar/templates/claude-md/python/quick-reference.md +24 -0
- invar/templates/claude-md/typescript/critical-rules.md +40 -0
- invar/templates/claude-md/typescript/quick-reference.md +24 -0
- invar/templates/claude-md/universal/check-in.md +25 -0
- invar/templates/claude-md/universal/skills.md +73 -0
- invar/templates/claude-md/universal/workflow.md +55 -0
- invar/templates/commands/{audit.md → audit.md.jinja} +18 -1
- invar/templates/config/AGENT.md.jinja +256 -0
- invar/templates/config/CLAUDE.md.jinja +16 -209
- invar/templates/config/context.md.jinja +19 -0
- invar/templates/examples/{README.md → python/README.md} +2 -0
- invar/templates/examples/{conftest.py → python/conftest.py} +1 -1
- invar/templates/examples/{contracts.py → python/contracts.py} +81 -4
- invar/templates/examples/python/core_shell.py +227 -0
- invar/templates/examples/python/functional.py +613 -0
- invar/templates/examples/typescript/README.md +31 -0
- invar/templates/examples/typescript/contracts.ts +163 -0
- invar/templates/examples/typescript/core_shell.ts +374 -0
- invar/templates/examples/typescript/functional.ts +601 -0
- invar/templates/examples/typescript/workflow.md +95 -0
- invar/templates/hooks/PostToolUse.sh.jinja +10 -1
- invar/templates/hooks/PreToolUse.sh.jinja +38 -0
- invar/templates/hooks/Stop.sh.jinja +1 -1
- invar/templates/hooks/UserPromptSubmit.sh.jinja +7 -0
- invar/templates/hooks/pi/invar.ts.jinja +82 -0
- invar/templates/manifest.toml +8 -6
- invar/templates/onboard/assessment.md.jinja +214 -0
- invar/templates/onboard/patterns/python.md +347 -0
- invar/templates/onboard/patterns/typescript.md +452 -0
- invar/templates/onboard/roadmap.md.jinja +168 -0
- invar/templates/protocol/INVAR.md.jinja +51 -0
- invar/templates/protocol/python/architecture-examples.md +41 -0
- invar/templates/protocol/python/contracts-syntax.md +56 -0
- invar/templates/protocol/python/markers.md +44 -0
- invar/templates/protocol/python/tools.md +24 -0
- invar/templates/protocol/python/troubleshooting.md +38 -0
- invar/templates/protocol/typescript/architecture-examples.md +52 -0
- invar/templates/protocol/typescript/contracts-syntax.md +73 -0
- invar/templates/protocol/typescript/markers.md +48 -0
- invar/templates/protocol/typescript/tools.md +65 -0
- invar/templates/protocol/typescript/troubleshooting.md +104 -0
- invar/templates/protocol/universal/architecture.md +36 -0
- invar/templates/protocol/universal/completion.md +14 -0
- invar/templates/protocol/universal/contracts-concept.md +37 -0
- invar/templates/protocol/universal/header.md +17 -0
- invar/templates/protocol/universal/session.md +17 -0
- invar/templates/protocol/universal/six-laws.md +10 -0
- invar/templates/protocol/universal/usbv.md +14 -0
- invar/templates/protocol/universal/visible-workflow.md +25 -0
- invar/templates/skills/develop/SKILL.md.jinja +98 -3
- invar/templates/skills/extensions/_registry.yaml +93 -0
- invar/templates/skills/extensions/acceptance/SKILL.md +383 -0
- invar/templates/skills/extensions/invar-onboard/SKILL.md +448 -0
- invar/templates/skills/extensions/invar-onboard/patterns/python.md +347 -0
- invar/templates/skills/extensions/invar-onboard/patterns/typescript.md +452 -0
- invar/templates/skills/extensions/invar-onboard/templates/assessment.md.jinja +214 -0
- invar/templates/skills/extensions/invar-onboard/templates/roadmap.md.jinja +168 -0
- invar/templates/skills/extensions/security/SKILL.md +382 -0
- invar/templates/skills/extensions/security/patterns/_common.yaml +126 -0
- invar/templates/skills/extensions/security/patterns/python.yaml +155 -0
- invar/templates/skills/extensions/security/patterns/typescript.yaml +194 -0
- invar/templates/skills/investigate/SKILL.md.jinja +15 -0
- invar/templates/skills/propose/SKILL.md.jinja +33 -0
- invar/templates/skills/review/SKILL.md.jinja +346 -71
- {invar_tools-1.7.1.dist-info → invar_tools-1.10.0.dist-info}/METADATA +326 -19
- invar_tools-1.10.0.dist-info/RECORD +173 -0
- invar/templates/examples/core_shell.py +0 -127
- invar/templates/protocol/INVAR.md +0 -310
- invar_tools-1.7.1.dist-info/RECORD +0 -112
- /invar/templates/examples/{workflow.md → python/workflow.md} +0 -0
- {invar_tools-1.7.1.dist-info → invar_tools-1.10.0.dist-info}/WHEEL +0 -0
- {invar_tools-1.7.1.dist-info → invar_tools-1.10.0.dist-info}/entry_points.txt +0 -0
- {invar_tools-1.7.1.dist-info → invar_tools-1.10.0.dist-info}/licenses/LICENSE +0 -0
- {invar_tools-1.7.1.dist-info → invar_tools-1.10.0.dist-info}/licenses/LICENSE-GPL +0 -0
- {invar_tools-1.7.1.dist-info → invar_tools-1.10.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# TypeScript Onboarding Patterns
|
|
2
|
+
|
|
3
|
+
> Patterns for migrating TypeScript projects to Invar framework.
|
|
4
|
+
> Library: `neverthrow`
|
|
5
|
+
|
|
6
|
+
## 1. Overview
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
// Library: neverthrow
|
|
10
|
+
// Install: npm install neverthrow
|
|
11
|
+
|
|
12
|
+
import { Result, ResultAsync, ok, err, okAsync, errAsync } from 'neverthrow';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 2. Error Handling
|
|
18
|
+
|
|
19
|
+
### 2.1 Sync vs Async Result
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { Result, ResultAsync, ok, err, okAsync, errAsync } from 'neverthrow';
|
|
23
|
+
|
|
24
|
+
// Sync (Core layer)
|
|
25
|
+
function validateEmail(email: string): Result<string, ValidationError> {
|
|
26
|
+
if (!email.includes('@')) {
|
|
27
|
+
return err(new ValidationError('invalid_email'));
|
|
28
|
+
}
|
|
29
|
+
return ok(email);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Async (Shell layer)
|
|
33
|
+
function sendEmail(to: string): ResultAsync<void, EmailError> {
|
|
34
|
+
return ResultAsync.fromPromise(
|
|
35
|
+
emailClient.send({ to }),
|
|
36
|
+
(e) => new EmailError('send_failed', e)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2.2 Basic Transformation
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// Before: throw
|
|
45
|
+
async function getUser(id: string): Promise<User> {
|
|
46
|
+
const user = await db.user.findUnique({ where: { id } });
|
|
47
|
+
if (!user) throw new NotFoundError(`User ${id} not found`);
|
|
48
|
+
return user;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// After: ResultAsync
|
|
52
|
+
function getUser(id: string): ResultAsync<User, GetUserError> {
|
|
53
|
+
return ResultAsync.fromPromise(
|
|
54
|
+
db.user.findUnique({ where: { id } }),
|
|
55
|
+
() => new DbError('query_failed')
|
|
56
|
+
).andThen(user =>
|
|
57
|
+
user ? okAsync(user) : errAsync(new NotFoundError(`User ${id} not found`))
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2.3 Chaining (andThen, map)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
function processOrder(orderId: string): ResultAsync<Receipt, OrderError> {
|
|
66
|
+
return getOrder(orderId) // ResultAsync<Order, NotFoundError>
|
|
67
|
+
.andThen(validateOrder) // -> ResultAsync<Order, ValidationError>
|
|
68
|
+
.map(calculateTotal) // -> ResultAsync<number, ...>
|
|
69
|
+
.andThen(chargePayment) // -> ResultAsync<Payment, PaymentError>
|
|
70
|
+
.map(generateReceipt); // -> ResultAsync<Receipt, ...>
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2.4 Error Type Hierarchy (Discriminated Union)
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// Discriminated union for exhaustive checking
|
|
78
|
+
type OrderError =
|
|
79
|
+
| { type: 'NOT_FOUND'; orderId: string }
|
|
80
|
+
| { type: 'VALIDATION'; field: string; message: string }
|
|
81
|
+
| { type: 'PAYMENT'; code: string; retry: boolean };
|
|
82
|
+
|
|
83
|
+
function handleOrderError(error: OrderError): Response {
|
|
84
|
+
switch (error.type) {
|
|
85
|
+
case 'NOT_FOUND':
|
|
86
|
+
return notFound(`Order ${error.orderId} not found`);
|
|
87
|
+
case 'VALIDATION':
|
|
88
|
+
return badRequest(`${error.field}: ${error.message}`);
|
|
89
|
+
case 'PAYMENT':
|
|
90
|
+
return error.retry
|
|
91
|
+
? serviceUnavailable('Payment failed, please retry')
|
|
92
|
+
: badRequest(`Payment error: ${error.code}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 2.5 Safe Wrappers
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// Catch exceptions -> Result
|
|
101
|
+
function safeJsonParse<T>(json: string): Result<T, ParseError> {
|
|
102
|
+
return Result.fromThrowable(
|
|
103
|
+
() => JSON.parse(json) as T,
|
|
104
|
+
(e) => new ParseError('invalid_json', e)
|
|
105
|
+
)();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Wrap Promise -> ResultAsync
|
|
109
|
+
function safeFetch<T>(url: string): ResultAsync<T, FetchError> {
|
|
110
|
+
return ResultAsync.fromPromise(
|
|
111
|
+
fetch(url).then(r => r.json()),
|
|
112
|
+
(e) => new FetchError('fetch_failed', e)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 2.6 Combining Multiple Results
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { Result, ResultAsync } from 'neverthrow';
|
|
121
|
+
|
|
122
|
+
// Combine array of Results
|
|
123
|
+
function validateOrderItems(items: OrderItem[]): Result<OrderItem[], ValidationError[]> {
|
|
124
|
+
const results = items.map(validateItem);
|
|
125
|
+
return Result.combineWithAllErrors(results); // Collect all errors
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Combine multiple independent Results
|
|
129
|
+
const combined = Result.combine([
|
|
130
|
+
validateName(name),
|
|
131
|
+
validateEmail(email),
|
|
132
|
+
validateAge(age),
|
|
133
|
+
]);
|
|
134
|
+
// -> Result<[string, string, number], ValidationError>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 3. Contracts (Zod)
|
|
140
|
+
|
|
141
|
+
### 3.1 Schema Definition
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { z } from 'zod';
|
|
145
|
+
|
|
146
|
+
// Basic schemas
|
|
147
|
+
const UserIdSchema = z.string()
|
|
148
|
+
.min(1, 'ID is required')
|
|
149
|
+
.max(36, 'ID too long')
|
|
150
|
+
.regex(/^[a-zA-Z0-9-]+$/, 'Invalid ID format');
|
|
151
|
+
|
|
152
|
+
const EmailSchema = z.string()
|
|
153
|
+
.email('Invalid email format')
|
|
154
|
+
.transform(s => s.toLowerCase());
|
|
155
|
+
|
|
156
|
+
// Composite schema
|
|
157
|
+
const CreateUserSchema = z.object({
|
|
158
|
+
email: EmailSchema,
|
|
159
|
+
name: z.string().min(1).max(100),
|
|
160
|
+
role: z.enum(['admin', 'user', 'guest']).default('user'),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Schema with refinement
|
|
164
|
+
const OrderSchema = z.object({
|
|
165
|
+
items: z.array(z.object({
|
|
166
|
+
sku: z.string(),
|
|
167
|
+
qty: z.number().int().positive(),
|
|
168
|
+
price: z.number().positive(),
|
|
169
|
+
})).min(1, 'Order must have at least one item'),
|
|
170
|
+
}).refine(
|
|
171
|
+
(order) => order.items.reduce((sum, i) => sum + i.qty, 0) <= 100,
|
|
172
|
+
{ message: 'Order cannot exceed 100 items total' }
|
|
173
|
+
);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 3.2 Zod + Result Integration
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { z } from 'zod';
|
|
180
|
+
import { Result, ok, err } from 'neverthrow';
|
|
181
|
+
|
|
182
|
+
type ValidationError = {
|
|
183
|
+
type: 'VALIDATION';
|
|
184
|
+
issues: z.ZodIssue[];
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
function validate<T>(schema: z.ZodSchema<T>, data: unknown): Result<T, ValidationError> {
|
|
188
|
+
const result = schema.safeParse(data);
|
|
189
|
+
if (result.success) {
|
|
190
|
+
return ok(result.data);
|
|
191
|
+
}
|
|
192
|
+
return err({ type: 'VALIDATION', issues: result.error.issues });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Usage
|
|
196
|
+
function createUser(input: unknown): ResultAsync<User, CreateUserError> {
|
|
197
|
+
return validate(CreateUserSchema, input)
|
|
198
|
+
.asyncAndThen(validated =>
|
|
199
|
+
checkEmailUnique(validated.email)
|
|
200
|
+
.map(() => validated)
|
|
201
|
+
)
|
|
202
|
+
.andThen(saveUser);
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 3.3 Branded Types
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { z } from 'zod';
|
|
210
|
+
import { ResultAsync } from 'neverthrow';
|
|
211
|
+
|
|
212
|
+
// Type-safe IDs that can't be mixed up
|
|
213
|
+
const UserId = z.string().uuid().brand<'UserId'>();
|
|
214
|
+
type UserId = z.infer<typeof UserId>;
|
|
215
|
+
|
|
216
|
+
const OrderId = z.string().uuid().brand<'OrderId'>();
|
|
217
|
+
type OrderId = z.infer<typeof OrderId>;
|
|
218
|
+
|
|
219
|
+
function getUser(id: UserId): ResultAsync<User, UserError> { ... }
|
|
220
|
+
function getOrder(id: OrderId): ResultAsync<Order, OrderError> { ... }
|
|
221
|
+
|
|
222
|
+
// Compile error: OrderId cannot be assigned to UserId
|
|
223
|
+
// getUser(orderId);
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 3.4 JSDoc Contracts
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
/**
|
|
230
|
+
* Calculate order total with tax.
|
|
231
|
+
*
|
|
232
|
+
* @pre items.length > 0
|
|
233
|
+
* @pre taxRate >= 0 && taxRate <= 1
|
|
234
|
+
* @post result >= 0
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* const total = calculateTotal([{price: 100, qty: 2}], 0.1);
|
|
239
|
+
* assert(total === 220); // 200 + 20 tax
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
function calculateTotal(items: OrderItem[], taxRate: number): number {
|
|
243
|
+
const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0);
|
|
244
|
+
return subtotal * (1 + taxRate);
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 4. Core/Shell Separation
|
|
251
|
+
|
|
252
|
+
### 4.1 Directory Structure
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
lib/
|
|
256
|
+
├── core/ # Pure functions, sync preferred
|
|
257
|
+
│ ├── order/
|
|
258
|
+
│ │ ├── validation.ts # Zod schemas + pure validation
|
|
259
|
+
│ │ ├── calculation.ts # Pure calculations
|
|
260
|
+
│ │ └── types.ts # Domain types
|
|
261
|
+
│ └── user/
|
|
262
|
+
│ └── ...
|
|
263
|
+
├── services/ # Shell: I/O orchestration
|
|
264
|
+
│ ├── order.service.ts
|
|
265
|
+
│ └── user.service.ts
|
|
266
|
+
├── repositories/ # Shell: Data access
|
|
267
|
+
│ ├── order.repository.ts
|
|
268
|
+
│ └── user.repository.ts
|
|
269
|
+
└── errors/ # Error type definitions
|
|
270
|
+
└── index.ts
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### 4.2 Core Layer Example
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// lib/core/order/validation.ts
|
|
277
|
+
import { z } from 'zod';
|
|
278
|
+
import { Result, ok, err } from 'neverthrow';
|
|
279
|
+
|
|
280
|
+
export const OrderItemSchema = z.object({
|
|
281
|
+
sku: z.string().min(1),
|
|
282
|
+
qty: z.number().int().positive(),
|
|
283
|
+
price: z.number().positive(),
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
export type OrderItem = z.infer<typeof OrderItemSchema>;
|
|
287
|
+
|
|
288
|
+
// Pure validation (sync Result)
|
|
289
|
+
export function validateOrder(order: unknown): Result<Order, ValidationError> {
|
|
290
|
+
const result = OrderSchema.safeParse(order);
|
|
291
|
+
if (!result.success) {
|
|
292
|
+
return err({ type: 'VALIDATION', issues: result.error.issues });
|
|
293
|
+
}
|
|
294
|
+
return ok(result.data);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Pure calculation
|
|
298
|
+
export function calculateSubtotal(items: OrderItem[]): number {
|
|
299
|
+
return items.reduce((sum, item) => sum + item.qty * item.price, 0);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function applyDiscount(amount: number, rate: number): number {
|
|
303
|
+
return amount * (1 - rate);
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### 4.3 Shell Layer Example
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
// lib/services/order.service.ts
|
|
311
|
+
import { ResultAsync } from 'neverthrow';
|
|
312
|
+
import { validateOrder, calculateSubtotal } from '../core/order/validation';
|
|
313
|
+
import { OrderRepository } from '../repositories/order.repository';
|
|
314
|
+
|
|
315
|
+
export class OrderService {
|
|
316
|
+
constructor(private readonly repo: OrderRepository) {}
|
|
317
|
+
|
|
318
|
+
processOrder(orderId: string): ResultAsync<Receipt, OrderError> {
|
|
319
|
+
return this.repo.findById(orderId) // Shell: I/O
|
|
320
|
+
.andThen(validateOrder) // Core: pure (sync->async)
|
|
321
|
+
.map(order => ({ // Core: pure transform
|
|
322
|
+
order,
|
|
323
|
+
subtotal: calculateSubtotal(order.items),
|
|
324
|
+
}))
|
|
325
|
+
.andThen(({ order, subtotal }) =>
|
|
326
|
+
this.getDiscount(order.id)
|
|
327
|
+
.map(discount => applyDiscount(subtotal, discount))
|
|
328
|
+
)
|
|
329
|
+
.andThen(total => this.chargePayment(total)) // Shell: I/O
|
|
330
|
+
.map(this.generateReceipt); // Core: pure
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 5. Next.js Integration
|
|
338
|
+
|
|
339
|
+
### 5.1 Server Actions + Result
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// app/actions/order.ts
|
|
343
|
+
'use server';
|
|
344
|
+
|
|
345
|
+
import { ResultAsync } from 'neverthrow';
|
|
346
|
+
import { orderService } from '@/lib/services';
|
|
347
|
+
|
|
348
|
+
type ActionResult<T> =
|
|
349
|
+
| { success: true; data: T }
|
|
350
|
+
| { success: false; error: { type: string; message: string } };
|
|
351
|
+
|
|
352
|
+
export async function createOrder(formData: FormData): Promise<ActionResult<Order>> {
|
|
353
|
+
const input = Object.fromEntries(formData);
|
|
354
|
+
const result = await orderService.createOrder(input);
|
|
355
|
+
|
|
356
|
+
return result.match(
|
|
357
|
+
(order) => ({ success: true, data: order }),
|
|
358
|
+
(error) => ({
|
|
359
|
+
success: false,
|
|
360
|
+
error: { type: error.type, message: formatError(error) }
|
|
361
|
+
})
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### 5.2 React Hook
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// hooks/useAction.ts
|
|
370
|
+
import { useState, useCallback } from 'react';
|
|
371
|
+
|
|
372
|
+
export function useAction<TInput, TOutput>(
|
|
373
|
+
action: (input: TInput) => Promise<ActionResult<TOutput>>
|
|
374
|
+
) {
|
|
375
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
376
|
+
const [error, setError] = useState<string | null>(null);
|
|
377
|
+
const [data, setData] = useState<TOutput | null>(null);
|
|
378
|
+
|
|
379
|
+
const execute = useCallback(async (input: TInput) => {
|
|
380
|
+
setIsLoading(true);
|
|
381
|
+
setError(null);
|
|
382
|
+
|
|
383
|
+
const result = await action(input);
|
|
384
|
+
|
|
385
|
+
if (result.success) {
|
|
386
|
+
setData(result.data);
|
|
387
|
+
} else {
|
|
388
|
+
setError(result.error.message);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
setIsLoading(false);
|
|
392
|
+
return result;
|
|
393
|
+
}, [action]);
|
|
394
|
+
|
|
395
|
+
return { execute, isLoading, error, data };
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## 6. Must Keep `throw` Scenarios
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
// 1. React Error Boundary (must throw)
|
|
405
|
+
async function OrderDetails({ id }: { id: string }) {
|
|
406
|
+
const result = await orderService.getOrder(id);
|
|
407
|
+
if (result.isErr()) {
|
|
408
|
+
throw new Error(result.error.message); // Let ErrorBoundary catch
|
|
409
|
+
}
|
|
410
|
+
return <OrderView order={result.value} />;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 2. Next.js redirect/notFound (must throw)
|
|
414
|
+
import { redirect, notFound } from 'next/navigation';
|
|
415
|
+
|
|
416
|
+
export default async function OrderPage({ params }: { params: { id: string } }) {
|
|
417
|
+
const result = await orderService.getOrder(params.id);
|
|
418
|
+
|
|
419
|
+
if (result.isErr()) {
|
|
420
|
+
if (result.error.type === 'NOT_FOUND') {
|
|
421
|
+
notFound(); // throws internally
|
|
422
|
+
}
|
|
423
|
+
if (result.error.type === 'UNAUTHORIZED') {
|
|
424
|
+
redirect('/login'); // throws internally
|
|
425
|
+
}
|
|
426
|
+
throw new Error(result.error.message);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return <OrderDetails order={result.value} />;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// 3. Constructors (cannot return Result)
|
|
433
|
+
// 4. Top-level try-catch in entry points
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## 7. Migration Checklist
|
|
439
|
+
|
|
440
|
+
- [ ] Install `neverthrow` library: `npm install neverthrow`
|
|
441
|
+
- [ ] Install `zod` for validation: `npm install zod`
|
|
442
|
+
- [ ] Define error type hierarchy (discriminated unions)
|
|
443
|
+
- [ ] Transform entry points to return `ResultAsync<T, E>`
|
|
444
|
+
- [ ] Extract pure functions to `lib/core/` directory
|
|
445
|
+
- [ ] Add Zod schemas for Core validation
|
|
446
|
+
- [ ] Add JSDoc `@pre/@post` comments to Core functions
|
|
447
|
+
- [ ] Run TypeScript compiler in strict mode
|
|
448
|
+
- [ ] Update API handlers to use `result.match()`
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
*Pattern Library v1.0 — LX-09*
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Expected variables:
|
|
3
|
+
- project_name: str
|
|
4
|
+
- timestamp: str
|
|
5
|
+
- total_days: int
|
|
6
|
+
- session_count: int
|
|
7
|
+
- phases: list of {id: str, name: str, days: int, objective: str,
|
|
8
|
+
tasks: [{day: int, files: [str], scope: str}],
|
|
9
|
+
sessions: [{files: [str], estimate: str}],
|
|
10
|
+
gates: [str], verification_command?: str, rollback_action?: str}
|
|
11
|
+
- result_library: str
|
|
12
|
+
- install_command: str
|
|
13
|
+
- additional_deps?: [{name: str, purpose: str, install: str}]
|
|
14
|
+
- target_coverage?: int (default 80)
|
|
15
|
+
- dependency_graph?: str
|
|
16
|
+
- notes?: str
|
|
17
|
+
#}
|
|
18
|
+
# Invar Onboarding Roadmap
|
|
19
|
+
|
|
20
|
+
> Project: {{ project_name }}
|
|
21
|
+
> Generated: {{ timestamp }}
|
|
22
|
+
> Based on: docs/invar-onboard-assessment.md
|
|
23
|
+
|
|
24
|
+
## Overview
|
|
25
|
+
|
|
26
|
+
| Metric | Value |
|
|
27
|
+
|--------|-------|
|
|
28
|
+
| Total Phases | {{ phases | length }} |
|
|
29
|
+
| Total Days | {{ total_days }} |
|
|
30
|
+
| Agent Sessions | {{ session_count }} |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
{% for phase in phases %}
|
|
35
|
+
## Phase {{ loop.index }}: {{ phase.name }} ({{ phase.days }} days)
|
|
36
|
+
|
|
37
|
+
### Objective
|
|
38
|
+
|
|
39
|
+
{{ phase.objective }}
|
|
40
|
+
|
|
41
|
+
### Tasks
|
|
42
|
+
|
|
43
|
+
| Day | Files | Scope |
|
|
44
|
+
|-----|-------|-------|
|
|
45
|
+
{% for task in phase.tasks %}
|
|
46
|
+
| {{ task.day }} | {{ task.files | join(", ") }} | {{ task.scope }} |
|
|
47
|
+
{% endfor %}
|
|
48
|
+
|
|
49
|
+
### Sessions
|
|
50
|
+
|
|
51
|
+
| Session | Files | Estimated |
|
|
52
|
+
|---------|-------|-----------|
|
|
53
|
+
{% for session in phase.sessions %}
|
|
54
|
+
| {{ phase.id }}.{{ loop.index }} | {{ session.files | join(", ") }} | {{ session.estimate }} |
|
|
55
|
+
{% endfor %}
|
|
56
|
+
|
|
57
|
+
### Gate Checklist
|
|
58
|
+
|
|
59
|
+
{% for gate in phase.gates %}
|
|
60
|
+
- [ ] {{ gate }}
|
|
61
|
+
{% endfor %}
|
|
62
|
+
|
|
63
|
+
### Verification
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
{{ phase.verification_command | default("invar guard") }}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
{% endfor %}
|
|
72
|
+
|
|
73
|
+
## Rollback Strategy
|
|
74
|
+
|
|
75
|
+
| Phase | Rollback Point | Recovery Action |
|
|
76
|
+
|-------|----------------|-----------------|
|
|
77
|
+
{% for phase in phases %}
|
|
78
|
+
| {{ loop.index }} | Pre-{{ phase.name }} | {{ phase.rollback_action | default("Revert " + phase.name + " changes") }} |
|
|
79
|
+
{% endfor %}
|
|
80
|
+
|
|
81
|
+
## Session Guidelines
|
|
82
|
+
|
|
83
|
+
### Context Limits
|
|
84
|
+
|
|
85
|
+
Each agent session should:
|
|
86
|
+
- Focus on 2-3 files maximum
|
|
87
|
+
- Complete within 1 context window
|
|
88
|
+
- End with successful `invar guard`
|
|
89
|
+
|
|
90
|
+
### Handoff Protocol
|
|
91
|
+
|
|
92
|
+
1. **Before session end:**
|
|
93
|
+
- Commit all changes
|
|
94
|
+
- Update this roadmap (mark completed)
|
|
95
|
+
- Document any deviations
|
|
96
|
+
|
|
97
|
+
2. **Session start:**
|
|
98
|
+
- Read assessment and this roadmap
|
|
99
|
+
- Review previous session's changes
|
|
100
|
+
- Verify Guard passes before proceeding
|
|
101
|
+
|
|
102
|
+
### Emergency Procedures
|
|
103
|
+
|
|
104
|
+
| Situation | Action |
|
|
105
|
+
|-----------|--------|
|
|
106
|
+
| Guard fails after changes | Revert to last passing commit |
|
|
107
|
+
| Unexpected dependency | Add to blockers, pause phase |
|
|
108
|
+
| Scope creep detected | Stop, update assessment |
|
|
109
|
+
|
|
110
|
+
## Progress Tracking
|
|
111
|
+
|
|
112
|
+
### Phase Status
|
|
113
|
+
|
|
114
|
+
| Phase | Status | Started | Completed | Notes |
|
|
115
|
+
|-------|--------|---------|-----------|-------|
|
|
116
|
+
{% for phase in phases %}
|
|
117
|
+
| {{ loop.index }}. {{ phase.name }} | ⬜ Pending | - | - | |
|
|
118
|
+
{% endfor %}
|
|
119
|
+
|
|
120
|
+
### Session Log
|
|
121
|
+
|
|
122
|
+
| Session | Date | Duration | Files Changed | Guard | Notes |
|
|
123
|
+
|---------|------|----------|---------------|-------|-------|
|
|
124
|
+
| - | - | - | - | - | - |
|
|
125
|
+
|
|
126
|
+
## Dependencies
|
|
127
|
+
|
|
128
|
+
### External Libraries
|
|
129
|
+
|
|
130
|
+
| Library | Purpose | Install Command |
|
|
131
|
+
|---------|---------|-----------------|
|
|
132
|
+
| {{ result_library }} | Result types | {{ install_command }} |
|
|
133
|
+
{% for dep in additional_deps %}
|
|
134
|
+
| {{ dep.name }} | {{ dep.purpose }} | {{ dep.install }} |
|
|
135
|
+
{% endfor %}
|
|
136
|
+
|
|
137
|
+
### Internal Dependencies
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
{{ dependency_graph | default("No complex internal dependencies.") }}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Success Criteria
|
|
144
|
+
|
|
145
|
+
### Phase Completion
|
|
146
|
+
|
|
147
|
+
Each phase is complete when:
|
|
148
|
+
1. All tasks checked off
|
|
149
|
+
2. Gate checklist passed
|
|
150
|
+
3. `invar guard` passes
|
|
151
|
+
4. E2E tests still pass (if applicable)
|
|
152
|
+
|
|
153
|
+
### Project Completion
|
|
154
|
+
|
|
155
|
+
Project migration complete when:
|
|
156
|
+
- [ ] All phases completed
|
|
157
|
+
- [ ] Full `invar guard` passes
|
|
158
|
+
- [ ] Contract coverage > {{ target_coverage | default(80) }}%
|
|
159
|
+
- [ ] All Core functions have doctests
|
|
160
|
+
- [ ] Shell functions return Result types
|
|
161
|
+
|
|
162
|
+
## Notes
|
|
163
|
+
|
|
164
|
+
{{ notes | default("No additional notes.") }}
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
*Generated by /invar-onboard*
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{# INVAR.md Composition Template
|
|
2
|
+
Composes universal protocol + language-specific fragments
|
|
3
|
+
Variables: language (python|typescript)
|
|
4
|
+
#}
|
|
5
|
+
{% include "protocol/universal/header.md" %}
|
|
6
|
+
|
|
7
|
+
{% include "protocol/universal/six-laws.md" %}
|
|
8
|
+
|
|
9
|
+
{% include "protocol/universal/architecture.md" %}
|
|
10
|
+
|
|
11
|
+
{% if language == "python" %}
|
|
12
|
+
{% include "protocol/python/architecture-examples.md" %}
|
|
13
|
+
{% elif language == "typescript" %}
|
|
14
|
+
{% include "protocol/typescript/architecture-examples.md" %}
|
|
15
|
+
{% endif %}
|
|
16
|
+
|
|
17
|
+
{% if language == "python" %}
|
|
18
|
+
{% include "protocol/python/contracts-syntax.md" %}
|
|
19
|
+
{% elif language == "typescript" %}
|
|
20
|
+
{% include "protocol/typescript/contracts-syntax.md" %}
|
|
21
|
+
{% endif %}
|
|
22
|
+
|
|
23
|
+
{% include "protocol/universal/session.md" %}
|
|
24
|
+
|
|
25
|
+
{% include "protocol/universal/usbv.md" %}
|
|
26
|
+
|
|
27
|
+
{% include "protocol/universal/visible-workflow.md" %}
|
|
28
|
+
|
|
29
|
+
{% include "protocol/universal/completion.md" %}
|
|
30
|
+
|
|
31
|
+
{% if language == "python" %}
|
|
32
|
+
{% include "protocol/python/markers.md" %}
|
|
33
|
+
{% elif language == "typescript" %}
|
|
34
|
+
{% include "protocol/typescript/markers.md" %}
|
|
35
|
+
{% endif %}
|
|
36
|
+
|
|
37
|
+
{% if language == "python" %}
|
|
38
|
+
{% include "protocol/python/tools.md" %}
|
|
39
|
+
{% elif language == "typescript" %}
|
|
40
|
+
{% include "protocol/typescript/tools.md" %}
|
|
41
|
+
{% endif %}
|
|
42
|
+
|
|
43
|
+
{% if language == "python" %}
|
|
44
|
+
{% include "protocol/python/troubleshooting.md" %}
|
|
45
|
+
{% elif language == "typescript" %}
|
|
46
|
+
{% include "protocol/typescript/troubleshooting.md" %}
|
|
47
|
+
{% endif %}
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
*Protocol v5.0 — USBV workflow (DX-32) | [Examples](.invar/examples/)*
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
## Core Example (Python)
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
from deal import pre, post
|
|
5
|
+
|
|
6
|
+
@pre(lambda price, discount: price > 0 and 0 <= discount <= 1)
|
|
7
|
+
@post(lambda result: result >= 0)
|
|
8
|
+
def discounted_price(price: float, discount: float) -> float:
|
|
9
|
+
"""
|
|
10
|
+
>>> discounted_price(100, 0.2)
|
|
11
|
+
80.0
|
|
12
|
+
>>> discounted_price(100, 0) # Edge: no discount
|
|
13
|
+
100.0
|
|
14
|
+
"""
|
|
15
|
+
return price * (1 - discount)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Self-test:** Can someone else write the exact same function from just @pre/@post + doctests?
|
|
19
|
+
|
|
20
|
+
**Forbidden in Core:** `os`, `sys`, `subprocess`, `pathlib`, `open`, `requests`, `datetime.now`
|
|
21
|
+
|
|
22
|
+
## Shell Example (Python)
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from returns.result import Result, Success, Failure
|
|
27
|
+
|
|
28
|
+
def read_config(path: Path) -> Result[dict, str]:
|
|
29
|
+
"""Shell: handles I/O, returns Result for error handling."""
|
|
30
|
+
try:
|
|
31
|
+
import json
|
|
32
|
+
return Success(json.loads(path.read_text()))
|
|
33
|
+
except FileNotFoundError:
|
|
34
|
+
return Failure(f"File not found: {path}")
|
|
35
|
+
except json.JSONDecodeError as e:
|
|
36
|
+
return Failure(f"Invalid JSON: {e}")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Pattern:** Shell reads file → passes content to Core → returns Result.
|
|
40
|
+
|
|
41
|
+
More examples: `.invar/examples/`
|