@drawbridge/drawbridge-agents 0.0.6 → 0.0.9
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/.root-template/.mcp.json +8 -0
- package/README.md +9 -2
- package/bin/sync-claude.js +16 -8
- package/claude/CLAUDE.md +4 -0
- package/conventions/app-web-forms.md +28 -0
- package/conventions/cascade-cleanup.md +25 -0
- package/conventions/drawbridge-packages.md +66 -0
- package/conventions/javascript-formatting.md +4 -0
- package/conventions/sentry-sdk.md +39 -0
- package/package.json +2 -1
- package/.claude-template/mcp.json +0 -9
package/README.md
CHANGED
|
@@ -26,8 +26,11 @@ claude/
|
|
|
26
26
|
agents/
|
|
27
27
|
commands/
|
|
28
28
|
|
|
29
|
+
.root-template/ ← mirrored into each consumer repo's root
|
|
30
|
+
.mcp.json ← project-level MCP servers (e.g. Sentry)
|
|
31
|
+
|
|
29
32
|
bin/
|
|
30
|
-
sync-claude.js ← drawbridge-agents-sync — mirrors
|
|
33
|
+
sync-claude.js ← drawbridge-agents-sync — mirrors templates into consumer repo
|
|
31
34
|
```
|
|
32
35
|
|
|
33
36
|
## Consuming from a drawbridge-* repo
|
|
@@ -55,7 +58,11 @@ bin/
|
|
|
55
58
|
"sync": ". \"$HOME/.nvm/nvm.sh\" && nvm use && npm prune && npm install && npx drawbridge-agents-sync"
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
On every `npm run sync`,
|
|
61
|
+
On every `npm run sync`, this package mirrors:
|
|
62
|
+
- `.claude-template/` → consumer's `.claude/`
|
|
63
|
+
- `.root-template/` → consumer's repo root (currently just `.mcp.json`)
|
|
64
|
+
|
|
65
|
+
Paths that exist in the templates are **managed** (overwritten on each sync). Anything else in `.claude/` or at the repo root — including `settings.local.json` and any consumer-only hooks/agents/commands — is left untouched.
|
|
59
66
|
|
|
60
67
|
Claude Code's `@` imports cascade — one import line resolves the whole tree under `claude/CLAUDE.md`.
|
|
61
68
|
|
package/bin/sync-claude.js
CHANGED
|
@@ -3,13 +3,12 @@ const fs = require('fs')
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
|
|
5
5
|
const consumerRoot = process.env.INIT_CWD || process.cwd()
|
|
6
|
-
const
|
|
7
|
-
const destRoot = path.join(consumerRoot, '.claude')
|
|
6
|
+
const packageRoot = path.resolve(__dirname, '..')
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
const targets = [
|
|
9
|
+
{ src: path.join(packageRoot, '.claude-template'), dst: path.join(consumerRoot, '.claude'), required: true },
|
|
10
|
+
{ src: path.join(packageRoot, '.root-template'), dst: consumerRoot, required: false },
|
|
11
|
+
]
|
|
13
12
|
|
|
14
13
|
const copied = []
|
|
15
14
|
|
|
@@ -27,7 +26,16 @@ const mirror = (src, dst) => {
|
|
|
27
26
|
}
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
for (const { src, dst, required } of targets) {
|
|
30
|
+
if (!fs.existsSync(src)) {
|
|
31
|
+
if (required) {
|
|
32
|
+
console.error(`drawbridge-agents-sync: template not found at ${src}`)
|
|
33
|
+
process.exit(1)
|
|
34
|
+
}
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
mirror(src, dst)
|
|
38
|
+
}
|
|
31
39
|
|
|
32
|
-
console.log(`drawbridge-agents-sync: ${copied.length} file(s) mirrored
|
|
40
|
+
console.log(`drawbridge-agents-sync: ${copied.length} file(s) mirrored`)
|
|
33
41
|
for (const p of copied) console.log(` ${p}`)
|
package/claude/CLAUDE.md
CHANGED
|
@@ -3,4 +3,8 @@
|
|
|
3
3
|
@../conventions/nested-objects.md
|
|
4
4
|
@../conventions/jsx-fragments.md
|
|
5
5
|
@../conventions/transactions.md
|
|
6
|
+
@../conventions/cascade-cleanup.md
|
|
6
7
|
@../conventions/property-shorthand.md
|
|
8
|
+
@../conventions/drawbridge-packages.md
|
|
9
|
+
@../conventions/sentry-sdk.md
|
|
10
|
+
@../conventions/app-web-forms.md
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# app-web form conventions
|
|
2
|
+
|
|
3
|
+
Applies to `drawbridge-app-web` only.
|
|
4
|
+
|
|
5
|
+
## Always use react-hook-form
|
|
6
|
+
|
|
7
|
+
Every form is built with **react-hook-form** (`useForm` + `Controller`,
|
|
8
|
+
with the `yup` resolver from `@/lib/yup` where validation is needed).
|
|
9
|
+
Never hand-rolled `useState` field handling. Mirror existing forms like
|
|
10
|
+
`components/form-profile.js` and `components/form-auth.js`.
|
|
11
|
+
|
|
12
|
+
This applies even to **field-less submit/action forms** — a form that's
|
|
13
|
+
just a button, e.g. a "send code" or confirm step. Use
|
|
14
|
+
`useForm()` + `handleSubmit(onSubmit)` and drive disabled/loader state
|
|
15
|
+
off `formState.isSubmitting`. Do NOT use a manual
|
|
16
|
+
`const [submitting, setSubmitting] = useState(false)` +
|
|
17
|
+
`event.preventDefault()`. Reference pattern: `components/form-action.js`.
|
|
18
|
+
|
|
19
|
+
## Secondary forms launch in the global modal
|
|
20
|
+
|
|
21
|
+
For a form triggered from within another form (e.g. "change email" from
|
|
22
|
+
the profile form), render it inside the **global modal** via
|
|
23
|
+
`useModal({ queryClient })` → `modal.open({ title, message: <TheForm/> })`.
|
|
24
|
+
Do not nest a second `<form>` inside an existing `<form>`.
|
|
25
|
+
|
|
26
|
+
The modal portals to the top level, so the inner `<form>` is not a DOM
|
|
27
|
+
descendant of the outer one. Nested `<form>` is invalid HTML and breaks
|
|
28
|
+
submit propagation. Reference pattern: `components/form-verify-prize.js`.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Cascade cleanup belongs in drawbridge-sync
|
|
2
|
+
|
|
3
|
+
When a Drawbridge document delete should trigger cleanup of dependent rows in another collection — actions tied to a workflow, workflows tied to a connection, exports tied to a user, anything where deleting X requires also deleting Y — the cleanup belongs in a change-stream listener in `drawbridge-sync`, **not** inline in the API route handler that triggered the original delete.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
- API routes stay focused on the user's request. Cascade work shouldn't block the HTTP response or balloon the request's transaction scope.
|
|
8
|
+
- A sync-side listener fires on **every** delete path: the direct route, a cascade from a different route, an admin tool, a manual Mongo write. An inline cascade only runs on the one route that has it.
|
|
9
|
+
- Change streams are configured with `fullDocumentBeforeChange: 'required'`, so the listener receives the full prior document and has everything it needs to reason about the dependents.
|
|
10
|
+
|
|
11
|
+
## How to apply
|
|
12
|
+
|
|
13
|
+
- In an API route delete handler, do **not** add `controllers.base.delete` (or `bulk` with `deleteMany`) for child collections. Just delete the parent. The sync listener cleans up.
|
|
14
|
+
- Add the cleanup handler in `drawbridge-sync/queue/<collection>.js`, in the `handlers.delete` slot, signature `async ({ fullDocumentBeforeChange : doc }) => { ... }`. Copy the shape from `queue/user.js` or `queue/organization.js`.
|
|
15
|
+
- For the listener to fire at all, the collection must be in both:
|
|
16
|
+
- `drawbridge-sync/stream.js` `collections` array
|
|
17
|
+
- `drawbridge-sync/lib/queue.js` queue map
|
|
18
|
+
- **And** registered in `drawbridge-sync/queue/index.js`'s workers array.
|
|
19
|
+
- If any of these are missing, change-stream events get enqueued and silently dropped, or never enqueued at all. Audit all four sites when adding a new collection.
|
|
20
|
+
- For deep cascades (parent → child → grandchild), let the chain ride the change streams: the child's own listener handles its own dependents. Don't reach down two levels from the parent's listener.
|
|
21
|
+
|
|
22
|
+
## Counter-examples (these stay inline in the API)
|
|
23
|
+
|
|
24
|
+
- Decrementing `organization.totals.<resource>` and `usage.totals.<resource>` on delete. These are counter mutations against the *same* request, not cleanup of dependent rows. Done inline in the API route handler.
|
|
25
|
+
- The deleted document itself. The route deletes it; the listener reacts to the deletion.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Drawbridge package workflow
|
|
2
|
+
|
|
3
|
+
Conventions for working on the `@drawbridge/*` npm packages
|
|
4
|
+
(`drawbridge-utils`, `drawbridge-components`, `drawbridge-mongodb`,
|
|
5
|
+
`drawbridge-redis`, `drawbridge-stripe`, `drawbridge-shopify`,
|
|
6
|
+
`drawbridge-telemetry`) and the apps that consume them.
|
|
7
|
+
|
|
8
|
+
## Build + publish script
|
|
9
|
+
|
|
10
|
+
All `@drawbridge/*` packages standardize on:
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
"scripts": {
|
|
14
|
+
"sync": ". \"$HOME/.nvm/nvm.sh\" && nvm use && npm prune && npm install",
|
|
15
|
+
"build": "tsup && npm publish"
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Do NOT add a `prepare: tsup` script.** `prepare` runs automatically on
|
|
20
|
+
`npm publish`, so it duplicates the work `build` already does. Worse: if
|
|
21
|
+
`npm publish` is invoked from a shell where the wrong Node version is
|
|
22
|
+
active (e.g. system Node before `nvm use`), `prepare` triggers `tsup`
|
|
23
|
+
under that wrong Node and fails with cryptic syntax errors (optional
|
|
24
|
+
chaining unrecognized, etc.) even though `npm publish` itself would have
|
|
25
|
+
worked fine.
|
|
26
|
+
|
|
27
|
+
## Coordinating peer dependencies on bump
|
|
28
|
+
|
|
29
|
+
When bumping any `@drawbridge/*` package, audit every sibling package's
|
|
30
|
+
`peerDependencies` for a stale pin. Peer pins are version-exact (e.g.
|
|
31
|
+
`"@drawbridge/shopify": "0.0.3"`), so bumping the package in consumer
|
|
32
|
+
apps while leaving a sibling's peer dep on the old version triggers
|
|
33
|
+
`ERESOLVE` at install time.
|
|
34
|
+
|
|
35
|
+
Before publishing a bump, grep peerDependencies across the family:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
grep -A8 peerDependencies /Users/darrenshea/Projects/drawbridge-*/package.json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If a sibling package peer-pins something you just bumped, that sibling
|
|
42
|
+
needs a coordinated peer-dep update + republish in the same release.
|
|
43
|
+
|
|
44
|
+
## Check for duplicate logic before renaming across repos
|
|
45
|
+
|
|
46
|
+
When changing a value at the api side (enum keys, schema shape, operator
|
|
47
|
+
vocabulary, etc.), grep every sibling repo for identical-purpose code
|
|
48
|
+
paths that operate on that value. There are known copy-paste duplicates
|
|
49
|
+
that drift silently when only one side is updated.
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
grep -rn "<old-value>" /Users/darrenshea/Projects/drawbridge-* --include="*.js"
|
|
53
|
+
grep -rn "<file-name-or-export>" /Users/darrenshea/Projects/drawbridge-* --include="*.js"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Specifically watch for:
|
|
57
|
+
- segment / rules evaluation
|
|
58
|
+
- mongo schema-shape consumers
|
|
59
|
+
- credential / connection field reads
|
|
60
|
+
- any "pure helper" pattern that may have been copied rather than shared
|
|
61
|
+
|
|
62
|
+
If a duplicate is found, prefer extracting the live copy and deleting
|
|
63
|
+
the stale one over leaving both. Don't reflexively reach for
|
|
64
|
+
`drawbridge-utils` — utils is for code with multiple live consumers;
|
|
65
|
+
if only one repo actually imports the duplicate, the others are dead
|
|
66
|
+
code that should just be deleted.
|
|
@@ -53,3 +53,7 @@ const run = async () => {
|
|
|
53
53
|
- Single quotes for strings
|
|
54
54
|
|
|
55
55
|
When adding or removing a wrapper block (e.g. a transaction callback, an `if` block), re-indent all enclosed lines to match the new nesting level. If the indentation shift is large or the block is long, prefer rewriting the whole function with the Write tool rather than using multiple Edit calls.
|
|
56
|
+
|
|
57
|
+
## No underscores in identifiers or prose
|
|
58
|
+
|
|
59
|
+
The drawbridge codebases use camelCase identifiers — never snake_case. This applies to both code and prose in comments, commit messages, and chat. Say "worker thread" (two words) in prose, not "worker_thread". The only acceptable underscores are literal external API names that require them (e.g. the Node module `worker_threads` in a `require`), and even then refer to the concept as "worker thread" in surrounding prose.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Sentry SDK encapsulation
|
|
2
|
+
|
|
3
|
+
Shared `@drawbridge/*` packages that wrap a stateful SDK (one that
|
|
4
|
+
maintains module-scope state — Sentry scopes, registries, singletons)
|
|
5
|
+
must depend on that SDK as a **direct dependency** with a pinned exact
|
|
6
|
+
version. Do NOT declare it as a peer dep with a loose range like
|
|
7
|
+
`">=10"`.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
Loose peer-dep ranges let npm resolve two module instances with
|
|
12
|
+
independent state. The previous incident: `drawbridge-telemetry`
|
|
13
|
+
declared `@sentry/core: ">=10"` as a peer. Consumers' `@sentry/node`
|
|
14
|
+
had a strict `@sentry/core@<exact>` dep, but the loose peer range
|
|
15
|
+
happily resolved to a hoisted `@sentry/core@<different version>` from
|
|
16
|
+
elsewhere in the tree. npm kept both: a hoisted copy used by
|
|
17
|
+
`drawbridge-telemetry` and a nested copy under `@sentry/node` used by
|
|
18
|
+
service code. Each held its own `getIsolationScope()` registry, so
|
|
19
|
+
`Sentry.setTag(...)` writes from the wrapper landed on one scope while
|
|
20
|
+
`captureException` from service code read from the other —
|
|
21
|
+
silently-broken telemetry that took an entire debug session to
|
|
22
|
+
root-cause.
|
|
23
|
+
|
|
24
|
+
## How to apply
|
|
25
|
+
|
|
26
|
+
- New shared package wrapping `@sentry/*`, `redis`, `mongodb`,
|
|
27
|
+
`opentelemetry`, or any other SDK with module-state: direct-dep the
|
|
28
|
+
SDK, pinned to an exact version. Export the SDK namespace if
|
|
29
|
+
consumers need the full surface
|
|
30
|
+
(`export { Sentry }; export * from 'X';`).
|
|
31
|
+
- New consumer of such a wrapper: NEVER `require('@sentry/node')` or
|
|
32
|
+
similar in service code. Route everything through the wrapper.
|
|
33
|
+
- For heavy SDKs only some consumers need (e.g. `@sentry/nextjs`),
|
|
34
|
+
use an *optional* peer dep at an exact pinned version, and document
|
|
35
|
+
in the wrapper README that consumers must install that version.
|
|
36
|
+
- When stateful behavior fails mysteriously — scope tags missing,
|
|
37
|
+
registry lookups empty, singleton mismatch — `npm ls <sdk-package>`
|
|
38
|
+
is the first diagnostic. There must be exactly one entry, fully
|
|
39
|
+
deduped.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drawbridge/drawbridge-agents",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Shared agent-instruction content (rules, code style, conventions) for the drawbridge-* monorepo.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"claude",
|
|
18
18
|
"conventions",
|
|
19
19
|
".claude-template",
|
|
20
|
+
".root-template",
|
|
20
21
|
"bin",
|
|
21
22
|
"README.md"
|
|
22
23
|
],
|