@drawbridge/drawbridge-agents 0.1.9 → 0.1.12

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.
@@ -35,6 +35,17 @@
35
35
  ]
36
36
  },
37
37
  "hooks": {
38
+ "PreToolUse": [
39
+ {
40
+ "matcher": "Write|Edit|MultiEdit|NotebookEdit",
41
+ "hooks": [
42
+ {
43
+ "type": "command",
44
+ "command": "node ./node_modules/@drawbridge/drawbridge-agents/hooks/guard-superpowers-docs.js"
45
+ }
46
+ ]
47
+ }
48
+ ],
38
49
  "Stop": [
39
50
  {
40
51
  "matcher": "",
@@ -20,7 +20,14 @@ knowledge graph, and the Asana handoff so nothing drifts at ship time.
20
20
  flag the drift explicitly. See `conventions/docs-linkage.md`.
21
21
  3. **Confirm doc anchors resolve.** Run `npx drawbridge-agents-check-docs`; fix any unresolved
22
22
  `@story` / `@doc` tag. Add an anchor to newly-added feature code where it maps to a story.
23
- 4. **Refresh the graph** if the change altered cross-repo structure: `npx drawbridge-agents-graph`.
23
+ 4. **Refresh the knowledge graph always, on every feature wrap** (not conditional on whether
24
+ structure "obviously" changed). The canonical graph is the committed snapshot in
25
+ `drawbridge-docs`, shared by the whole team; skipping it lets the shared graph drift. From the
26
+ `drawbridge-docs` repo: `npx drawbridge-agents-graph` (rebuilds `knowledge/graphs/global.json`
27
+ from every repo), then `git add knowledge/graphs/global.json && git commit -m "chore: refresh
28
+ knowledge graph (<feature>)" && git push origin main`. If it reports "graphify runtime not
29
+ found", the `uv`/`graphify` binaries are in `~/.local/bin` — `export PATH="$HOME/.local/bin:$PATH"`
30
+ and retry (do NOT run `graphify install`; it rewrites `CLAUDE.md`).
24
31
  5. **Hand off the task.** Invoke `drawbridge-asana-ship-handoff` with the Asana task to reassign
25
32
  to the last commenter, post the change summary + retest steps, and set the due date.
26
33
 
@@ -9,6 +9,11 @@ skill) so it stops living only in tribal memory.
9
9
 
10
10
  - `OAUTH_TOKEN_HMAC_KEY` must be identical in **drawbridge-api** and **drawbridge-sync** — both
11
11
  `assertEnv` it at boot. A mismatch silently breaks realtime token verification.
12
+ - reCAPTCHA on auth is a key pair: `GOOGLE_RECAPTCHA_SECRET` (**drawbridge-api**) + `NEXT_PUBLIC_GOOGLE_RECAPTCHA_KEY`
13
+ (**drawbridge-app-web**), both `assertEnv`'d at boot. The api verifies the v3 token app-web's
14
+ `/auth` forms attach and enforces it **unconditionally** on `/oauth/{signup,signin}/request`
15
+ (fails closed). Deploy order matters: ship app-web **before** api, or the api blocks every
16
+ signin that arrives without a token. See `drawbridge-docs/user-stories/03-auth`.
12
17
 
13
18
  ## Sync owns reactive side effects
14
19
 
@@ -34,6 +39,14 @@ skill) so it stops living only in tribal memory.
34
39
  sync upserts the same invoice from three concurrent webhooks (finalized/payment_succeeded/voided)
35
40
  and relies on the index to reject the losing insert so the catch can redo it as an update. Drop
36
41
  the index → silent duplicate invoice rows; remove the catch → concurrent events throw + retry.
42
+ - The `contact` collection's **unique multikey index on `leads`** (drawbridge-api
43
+ `schema/contact.js`) and sync's contact resolution (`lib/contact.js` + `queue/contact.js`) are
44
+ one contract: a lead belongs to at most one contact. One submission enqueues two resolve jobs
45
+ (lead insert + totals-update events) that race; the index rejects the losing create and the
46
+ worker retries it into the update path. The merge path must delete duplicate contacts BEFORE
47
+ `$set`-ing the survivor (the expanded `leads` array collides with rows about to be deleted).
48
+ Drop the index → duplicate contacts, doubled segment members, double-billed `contact.create` +
49
+ `segment.join`; reorder the merge → every merge aborts E11000 and duplicates never heal.
37
50
 
38
51
  ## `@drawbridge/*` package coordination
39
52
 
@@ -49,3 +62,16 @@ skill) so it stops living only in tribal memory.
49
62
  - Import names against a package's **actual exports** — a missing export resolves to `undefined`
50
63
  and throws at call time, not at import. Check `drawbridge-docs/reference/packages.md` (the
51
64
  generated export surface) when unsure.
65
+
66
+ ## Shopify sales channel
67
+
68
+ - `@drawbridge/shopify` storefront `getProduct`/`getProductVariantsPage` throw the **exact
69
+ message `'Shopify product not found'`** when the Storefront API can't see a product
70
+ (unpublished from the Drawbridge channel / deleted); transport and GraphQL failures throw
71
+ `'Shopify storefront error: …'`. drawbridge-sync's product worker (`queue/product.js`)
72
+ string-matches the not-found message to send publish-this-product ResourceFeedback instead of
73
+ retrying. Reword the message → unpublished products retry-loop forever and merchants get no
74
+ feedback; drawbridge-api's shopifyProduct route relies on the not-found throw staying a throw.
75
+ - The `write_resource_feedbacks` and `read_publications` scopes live in drawbridge-shopify-app's
76
+ `shopify.app.*.toml` but power other repos: sync's product ResourceFeedback sends and the
77
+ embedded app's publishing section. Removing either → silent Shopify userErrors, no crash.
@@ -13,6 +13,11 @@ This is the **only** allowed home for these working docs. Rules:
13
13
  - Do **not** create or keep superpowers plans/specs (or `docs/superpowers/`, `docs/plans/`)
14
14
  inside any other repo. When you finalize a plan or spec, write it under
15
15
  `../drawbridge-docs/superpowers/{plans,specs}/`, not the repo you're working in.
16
+ - ⚠️ The superpowers **brainstorming** and **writing-plans** skills hardcode a save path of
17
+ `docs/superpowers/{specs,plans}/YYYY-MM-DD-<slug>.md` in the current repo (and tell you to
18
+ commit it). **Override that** — redirect the path to `../drawbridge-docs/superpowers/…`. A
19
+ `PreToolUse` guard hook (`hooks/guard-superpowers-docs.js`) denies writes to `docs/superpowers/`
20
+ and `docs/plans/` outside drawbridge-docs, so following the skill's default path will be blocked.
16
21
  - When plan mode or a superpowers skill produces a plan/spec, the finalized document goes into
17
22
  drawbridge-docs — regardless of which repo the work targets. Note the target repo in the doc.
18
23
  - `.superpowers/` (brainstorm scratch — server state, HTML mockups) is ephemeral. **Never commit
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ // PreToolUse hook (Write/Edit/MultiEdit/NotebookEdit): superpowers plans & specs must live in
3
+ // drawbridge-docs, never in a working repo. The superpowers brainstorming / writing-plans skills
4
+ // hardcode a `docs/superpowers/{specs,plans}/…` save path (and tell the agent to commit it),
5
+ // which silently overrides the passive conventions/superpowers-docs.md rule at write time. A
6
+ // convention can't out-argue a skill's explicit step, so this enforces it: deny the write and
7
+ // tell the agent where it belongs. See conventions/superpowers-docs.md.
8
+ const fs = require('fs')
9
+ const path = require('path')
10
+
11
+ const readStdin = () => {
12
+ try {
13
+ return JSON.parse(fs.readFileSync(0, 'utf8') || '{}')
14
+ } catch (error) {
15
+ return {}
16
+ }
17
+ }
18
+
19
+ const deny = (reason) => {
20
+ process.stdout.write(JSON.stringify({
21
+ hookSpecificOutput: {
22
+ hookEventName: 'PreToolUse',
23
+ permissionDecision: 'deny',
24
+ permissionDecisionReason: reason
25
+ }
26
+ }))
27
+ process.exit(0)
28
+ }
29
+
30
+ // Path segments that are never allowed outside drawbridge-docs. These are the superpowers-skill
31
+ // defaults (`docs/superpowers/`) plus the sibling the convention also forbids (`docs/plans/`).
32
+ const FORBIDDEN = [ 'docs/superpowers', 'docs/plans' ]
33
+
34
+ const run = () => {
35
+ const input = readStdin()
36
+
37
+ const tool = input.tool_name || ''
38
+ if (!/^(Write|Edit|MultiEdit|NotebookEdit)$/.test(tool)) process.exit(0)
39
+
40
+ const toolInput = input.tool_input || {}
41
+ const filePath = toolInput.file_path || toolInput.notebook_path
42
+ if (!filePath) process.exit(0)
43
+
44
+ // Normalise to forward slashes so the match is OS-independent.
45
+ const resolved = path.resolve(filePath).split(path.sep).join('/')
46
+
47
+ // drawbridge-docs is the canonical home — anything there is fine.
48
+ if (resolved.split('/').includes('drawbridge-docs')) process.exit(0)
49
+
50
+ const hit = FORBIDDEN.find((seg) => resolved.includes('/' + seg + '/') || resolved.endsWith('/' + seg))
51
+ if (!hit) process.exit(0)
52
+
53
+ deny(
54
+ 'Blocked: superpowers plans/specs must live in drawbridge-docs, not in this repo (`' + hit + '/`). ' +
55
+ 'The brainstorming / writing-plans skills instruct you to save here and commit — override that path. ' +
56
+ 'Write to `../drawbridge-docs/superpowers/{plans,specs}/YYYY-MM-DD-<slug>.md` instead, and note the ' +
57
+ 'target repo inside the doc. See conventions/superpowers-docs.md.'
58
+ )
59
+ }
60
+
61
+ try {
62
+ run()
63
+ } catch (error) {
64
+ // Never break a user's turn because of a hook bug — fail open.
65
+ process.exit(0)
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawbridge/drawbridge-agents",
3
- "version": "0.1.9",
3
+ "version": "0.1.12",
4
4
  "description": "Shared agent-instruction content (rules, code style, conventions) for the drawbridge-* monorepo.",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {