agentsdotmd 1.1.2 → 1.1.3
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/bin/agentsdotmd.js +1 -3
- package/changelog.md +6 -0
- package/dedent.md +16 -0
- package/fly.md +2 -0
- package/package.json +1 -1
- package/prisma.md +24 -4
- package/react.md +12 -0
- package/retries-and-resumability.md +84 -0
- package/shadcn.md +5 -1
- package/stripe.md +37 -3
- package/tailwind.md +2 -0
- package/typescript.md +5 -1
- package/vitest.md +2 -0
package/bin/agentsdotmd.js
CHANGED
|
@@ -50,9 +50,7 @@ cli
|
|
|
50
50
|
|
|
51
51
|
try {
|
|
52
52
|
const contents = await Promise.all(promises)
|
|
53
|
-
const header = `<!-- This file is
|
|
54
|
-
Do not edit this file directly. To add custom instructions, create a local file ./MY_AGENTS.md
|
|
55
|
-
and add it to your agentsdotmd command in package.json. -->\n\n`
|
|
53
|
+
const header = `<!-- This AGENTS.md file is generated. Look for an agents.md package.json script to see what files to update instead. -->\n\n`
|
|
56
54
|
const content = header + contents.join('\n') + '\n'
|
|
57
55
|
fs.writeFileSync('AGENTS.md', content)
|
|
58
56
|
console.log(`AGENTS.md generated successfully with ${files.length} file(s)`)
|
package/changelog.md
CHANGED
package/dedent.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## dedent
|
|
2
|
+
|
|
3
|
+
when creating long strings in functions use dedent so that we can indent the string content and make it more readable
|
|
4
|
+
|
|
5
|
+
for example:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import dedent from 'string-dedent'
|
|
9
|
+
|
|
10
|
+
const content = dedent`
|
|
11
|
+
some content
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
IMPORTANT: notice that i have at start and end a new line. this is required when using string-dedent. Also notice npm package `string-dedent` instead of `dedent`.
|
|
15
|
+
|
|
16
|
+
When creating code snippets alias dedent to variables like html or javascript so that I get syntax highlight in my editor: `const html = dedent`
|
package/fly.md
CHANGED
|
@@ -4,6 +4,8 @@ fly is a deployment platform. some packages use it to deploy the website. you ca
|
|
|
4
4
|
|
|
5
5
|
usually there are 2 fly apps for each package, one staging environment and one production. these are 2 different apps at 2 different urls, you can target the right app usually by using `pnpm fly:preview` or `pnpm fly:prod`. sometimes there is only `pnpm fly` and you can use that instead. These scripts will append the right --app argument to work on the right fly app.
|
|
6
6
|
|
|
7
|
+
Never deploy with fly yourself. ask the user to do it
|
|
8
|
+
|
|
7
9
|
## reading logs
|
|
8
10
|
|
|
9
11
|
you can read fly apps logs using `pnpm fly:preview logs --no-tail | tail -n 100`
|
package/package.json
CHANGED
package/prisma.md
CHANGED
|
@@ -49,10 +49,10 @@ this simply means to always include a check in prisma queries to make sure that
|
|
|
49
49
|
|
|
50
50
|
```typescript
|
|
51
51
|
const resource = await prisma.resource.findFirst({
|
|
52
|
-
|
|
53
|
-
})
|
|
52
|
+
where: { resourceId, parentResource: { users: { some: { userId } } } },
|
|
53
|
+
});
|
|
54
54
|
if (!resource) {
|
|
55
|
-
|
|
55
|
+
throw new AppError(`cannot find resource`);
|
|
56
56
|
}
|
|
57
57
|
```
|
|
58
58
|
|
|
@@ -81,4 +81,24 @@ if (!user.subscription) {
|
|
|
81
81
|
JSON.stringify({ message: `user has no subscription` }),
|
|
82
82
|
)
|
|
83
83
|
}
|
|
84
|
-
````
|
|
84
|
+
````
|
|
85
|
+
|
|
86
|
+
## foreign key constraints
|
|
87
|
+
|
|
88
|
+
sometimes you will get errors like "Invalid `upsert()` invocation: Foreign key constraint violated on the constraint: `filed1_filed2_fkey`". This can be caused by the following issue
|
|
89
|
+
|
|
90
|
+
- a field that has a relation to table X is being passed a value where no table X exists for that id. You can fix the issue by making sure that the table exists before doing the create or upsert
|
|
91
|
+
- With upsert, even if the create branch is valid, the update branch can violate the FK.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
await prisma.child.upsert({
|
|
95
|
+
where: { id: 1 },
|
|
96
|
+
create: {
|
|
97
|
+
parent: { connect: { id: 1 } },
|
|
98
|
+
},
|
|
99
|
+
update: {
|
|
100
|
+
parent: { connect: { id: 9999 } }, // no such parent
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
-
|
package/react.md
CHANGED
|
@@ -37,3 +37,15 @@
|
|
|
37
37
|
- non component code should be put in the src/lib folder.
|
|
38
38
|
|
|
39
39
|
- hooks should be put in the src/hooks.tsx file. do not create a new file for each new hook. also notice that you should never create custom hooks, only do it if asked for.
|
|
40
|
+
|
|
41
|
+
## zustand
|
|
42
|
+
|
|
43
|
+
zustand is the preferred way to created global React state. put it in files like state.ts or x-state.ts where x is something that describe a portion of app state in case of multiple global states or multiple apps
|
|
44
|
+
|
|
45
|
+
- minimize number of props. do not use props if you can use zustand state instead. the app has global zustand state that lets you get a piece of state down from the component tree by using something like `useStore(x => x.something)` or `useLoaderData<typeof loader>()` or even useRouteLoaderData if you are deep in the react component tree
|
|
46
|
+
|
|
47
|
+
- do not consider local state truthful when interacting with server. when interacting with the server with rpc or api calls never use state from the render function as input for the api call. this state can easily become stale or not get updated in the closure context. instead prefer using zustand `useStore.getState().stateValue`. notice that useLoaderData or useParams should be fine in this case.
|
|
48
|
+
|
|
49
|
+
- NEVER add zustand state setter methods. instead use useStore.setState to set state. For example never add a method `setVariable` in the state type. Instead call `setState` directly
|
|
50
|
+
|
|
51
|
+
- zustand already merges new partial state with the previous state. NEVER DO `useStore.setState({ ...useStore.getInitialState(), ... })` unless for resetting state
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Retryable, Resumable, Idempotent Tasks — Summary
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
- Long-running tasks (e.g.,Notion sync, AI chat, website generation) take minutes.
|
|
6
|
+
- Keeping a single connection open → network error probability grows with time.
|
|
7
|
+
- Tasks also do outbound network calls → compound failure surface.
|
|
8
|
+
- Risk of duplicate/concurrent runs corrupting state.
|
|
9
|
+
|
|
10
|
+
## Goals
|
|
11
|
+
|
|
12
|
+
- **Resumable**: continue from last checkpoint after failures.
|
|
13
|
+
- **Retryable**: safe to retry automatically.
|
|
14
|
+
- **Idempotent**: side effects via upserts/deterministic writes.
|
|
15
|
+
- **Single-flight**: prevent concurrent execution per task.
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Checkpoint progress (cursor/step) frequently.
|
|
20
|
+
- Deterministic ordering (e.g., pages list) so “skip until cursor” is correct.
|
|
21
|
+
- At-least-once safe semantics: dedupe by (task_id, sequence/event_id).
|
|
22
|
+
- Small, explicit payload carrying resume info on every retry.
|
|
23
|
+
|
|
24
|
+
## Client vs Server State
|
|
25
|
+
|
|
26
|
+
### Client-held (prefer for AI chat / interactive flows)
|
|
27
|
+
|
|
28
|
+
- Client persists: messages[], partial assistant text, last_event_seq.
|
|
29
|
+
- Request includes: task_id (resume_token), messages[], partial_assistant, last_event_seq.
|
|
30
|
+
- Server is stateless: reconstructs from request; uses model “prefill” to resume (verify model support; Anthropic supports; OpenAI may vary).
|
|
31
|
+
- Benefit: fewer server-side moving parts. Cost: model compatibility + client must track emitted events.
|
|
32
|
+
|
|
33
|
+
### Server-held (required for background jobs / fixed retry bodies, e.g., QStash)
|
|
34
|
+
|
|
35
|
+
- DB state per task: {task_id, step, cursor (last_synced_page_id), checksum, updated_at}.
|
|
36
|
+
- Resume by skipping to cursor using deterministic ordering.
|
|
37
|
+
- Needed when infrastructure cannot modify retry payloads.
|
|
38
|
+
|
|
39
|
+
## Concurrency Control
|
|
40
|
+
|
|
41
|
+
- Single-flight/lease per task_id:
|
|
42
|
+
- Acquire lease with TTL + heartbeats.
|
|
43
|
+
- If a retry arrives while running: attach to existing run or reject with retry-after.
|
|
44
|
+
- Release lease on completion or expiry.
|
|
45
|
+
|
|
46
|
+
## API Shape (minimal)
|
|
47
|
+
|
|
48
|
+
- POST /tasks/start → {task_id, optional cursor}
|
|
49
|
+
- POST /tasks/resume → body must include:
|
|
50
|
+
- task_id (resume_token)
|
|
51
|
+
- last_cursor (e.g., last_synced_page_id)
|
|
52
|
+
- last_event_seq (for stream dedupe)
|
|
53
|
+
- client_state if server is stateless (e.g., messages[])
|
|
54
|
+
- GET /tasks/status?task_id=… → {step, cursor, percent}
|
|
55
|
+
|
|
56
|
+
## Retry Strategy
|
|
57
|
+
|
|
58
|
+
- Exponential backoff + jitter; cap total time.
|
|
59
|
+
- Persist checkpoint before/after each substantial step.
|
|
60
|
+
- Retries always include last_cursor + last_event_seq.
|
|
61
|
+
- Treat transport and model/API timeouts as retriable; invalid input as terminal.
|
|
62
|
+
|
|
63
|
+
## Event Replay (AI chat)
|
|
64
|
+
|
|
65
|
+
- All server events carry monotonically increasing seq.
|
|
66
|
+
- Client sends last_event_seq on resume; server suppresses ≤ seq to avoid duplicates.
|
|
67
|
+
|
|
68
|
+
## Sync Pattern
|
|
69
|
+
|
|
70
|
+
- Pre-compute deterministic ordered page list.
|
|
71
|
+
- Cursor = last_synced_page_id.
|
|
72
|
+
- On resume: “skip-until-cursor” then continue.
|
|
73
|
+
- Idempotent writes (upsert by external_id).
|
|
74
|
+
|
|
75
|
+
## Spice Flow Notes
|
|
76
|
+
|
|
77
|
+
- Current “retry same body” is insufficient; add ability to override body on retry.
|
|
78
|
+
- Temporary workaround: client-managed loop that updates last_cursor and re-posts.
|
|
79
|
+
|
|
80
|
+
## What to Verify
|
|
81
|
+
|
|
82
|
+
- Model prefill/continuation support for chosen LLM.
|
|
83
|
+
- Lease/lock implementation (row-level lock or key-value lease with TTL).
|
|
84
|
+
- Observability: log task_id, step, cursor, retry_count, lease_owner.
|
package/shadcn.md
CHANGED
|
@@ -16,4 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
this project uses shadcn components placed in the website/src/components/ui folder. never add a new shadcn component yourself by writing code. instead use the shadcn cli installed locally.
|
|
18
18
|
|
|
19
|
-
try to reuse these available components when you can, for example for buttons, tooltips, scroll areas, etc.
|
|
19
|
+
try to reuse these available components when you can, for example for buttons, tooltips, scroll areas, etc.
|
|
20
|
+
|
|
21
|
+
## reusing shadcn components
|
|
22
|
+
|
|
23
|
+
when creating a new React component or adding jsx before creating your own buttons or other elements first check the files inside `src/components/ui` and `src/components` to see what is already available. So you can reuse things like Button and Tooltip components instead of creating your own.
|
package/stripe.md
CHANGED
|
@@ -7,6 +7,31 @@ the Stripe billing portal is used to
|
|
|
7
7
|
- send user to payment to create a sub via `stripe.checkout.sessions.create`
|
|
8
8
|
- let user change plan, cancel or change payment method ("manage subscription") via `stripe.billingPortal.sessions.create`
|
|
9
9
|
|
|
10
|
+
## customerId
|
|
11
|
+
|
|
12
|
+
every time you are about to do a call to `checkout.sessions.create` make sure that we create the Stripe customer first. So that we do not get duplicate Stripe customers for different subscriptions:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
|
|
16
|
+
let customerId = org.stripeCustomerId
|
|
17
|
+
|
|
18
|
+
if (!customerId) {
|
|
19
|
+
const customer = await stripe.customers.create({
|
|
20
|
+
email: org.email || undefined,
|
|
21
|
+
name: org.name || undefined,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
customerId = customer.id
|
|
25
|
+
|
|
26
|
+
await prisma.org.update({
|
|
27
|
+
where: { id: orgId },
|
|
28
|
+
data: { stripeCustomerId: customerId },
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await stripe.checkout.sessions.create({ customer: customerId, ... })
|
|
33
|
+
```
|
|
34
|
+
|
|
10
35
|
## subscriptions
|
|
11
36
|
|
|
12
37
|
a subscription is active if state is in
|
|
@@ -14,6 +39,18 @@ a subscription is active if state is in
|
|
|
14
39
|
- trialing
|
|
15
40
|
- active
|
|
16
41
|
|
|
42
|
+
```ts
|
|
43
|
+
await prisma.subscription.findFirst({
|
|
44
|
+
where: {
|
|
45
|
+
orgId: orgId,
|
|
46
|
+
status: {
|
|
47
|
+
in: ["active", "trialing"],
|
|
48
|
+
},
|
|
49
|
+
// ...
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
17
54
|
a subscription can be reactivated if state is NOT in
|
|
18
55
|
|
|
19
56
|
- canceled
|
|
@@ -21,6 +58,3 @@ a subscription can be reactivated if state is NOT in
|
|
|
21
58
|
- unpaid
|
|
22
59
|
|
|
23
60
|
> If sub is in any of these states the user will not be able to use the billing portal to reactivate it. Meaning we should treat a subscription in these states as completely missing. Forcing the user to create a new one instead of shoging the "manage subscription" button that redirects the user to the billing portal. BUT customer id must be preserved, reusing previous sub customerId in `stripe.billingPortal.sessions.create({ customer: prevCustomerId })`
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
##
|
package/tailwind.md
CHANGED
|
@@ -11,3 +11,5 @@ for margin, padding, gaps, widths and heights it is preferable to use multiples
|
|
|
11
11
|
4 is equal to 16px which is the default font size of the page. this way every spacing is a multiple of the height and width of a default letter.
|
|
12
12
|
|
|
13
13
|
user interfaces are mostly text so using the letter width and height as a base unit makes it easier to reason about the layout and sizes.
|
|
14
|
+
|
|
15
|
+
use grow instead of flex-1.
|
package/typescript.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
- always add the {} block body in arrow functions: arrow functions should never be written as `onClick={(x) => setState('')}`. NEVER. instead you should ALWAYS write `onClick={() => {setState('')}}`. this way it's easy to add new statements in the arrow function without refactoring it.
|
|
8
8
|
|
|
9
|
+
- in array operations .map, .filter, .reduce and .flatMap are preferred over .forEach and for of loops. For example prefer doing `.push(...array.map(x => x.items))` over mutating array variables inside for loops. Always think of how to turn for loops into expressions using .map, .filter or .flatMap if you ever are about to write a for loop.
|
|
10
|
+
|
|
11
|
+
- if you encounter typescript errors like "undefined | T is not assignable to T" after .filter(Boolean) operations: use a guarded function instead of Boolean: `.filter(isTruthy)`. implemented as `function isTruthy<T>(value: T): value is NonNullable<T> { return Boolean(value) }`
|
|
12
|
+
|
|
9
13
|
- minimize useless comments: do not add useless comments if the code is self descriptive. only add comments if requested or if this was a change that i asked for, meaning it is not obvious code and needs some inline documentation. if a comment is required because the part of the code was result of difficult back and forth with me, keep it very short.
|
|
10
14
|
|
|
11
15
|
- ALWAYS add all information encapsulated in my prompt to comments: when my prompt is super detailed and in depth, all this information should be added to comments in your code. this is because if the prompt is very detailed it must be the fruit of a lot of research. all this information would be lost if you don't put it in the code. next LLM calls would misinterpret the code and miss context.
|
|
@@ -28,7 +32,7 @@
|
|
|
28
32
|
|
|
29
33
|
- when creating urls from a path and a base url, prefer using `new URL(path, baseUrl).toString()` instead of normal string interpolation. use type-safe react-router `href` or spiceflow `this.safePath` (available inside routes) if possible
|
|
30
34
|
|
|
31
|
-
- for node built-in imports, never import singular names. instead do `import fs from 'node:fs'`, same for path, os, etc.
|
|
35
|
+
- for node built-in imports, never import singular exported names. instead do `import fs from 'node:fs'`, same for path, os, etc.
|
|
32
36
|
|
|
33
37
|
- NEVER start the development server with pnpm dev yourself. there is no reason to do so, even with &
|
|
34
38
|
|
package/vitest.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
do not write new test files unless asked. do not write tests if there is not already a test or describe block for that function or module.
|
|
4
4
|
|
|
5
|
+
if the inputs for the tests is an array of repetitive fields and long content, generate this input data programmatically instead of hardcoding everything. only hardcode the important parts and generate other repetitive fields in a .map or .reduce
|
|
6
|
+
|
|
5
7
|
tests should validate complex and non-obvious logic. if a test looks like a placeholder, do not add it.
|
|
6
8
|
|
|
7
9
|
use vitest to run tests. tests should be run from the current package directory and not root. try using the test script instead of vitest directly. additional vitest flags can be added at the end, like --run to disable watch mode or -u to update snapshots.
|