@drawbridge/drawbridge-agents 0.1.33 → 0.1.36
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,24 @@
|
|
|
35
35
|
"../drawbridge-website"
|
|
36
36
|
]
|
|
37
37
|
},
|
|
38
|
+
"extraKnownMarketplaces": {
|
|
39
|
+
"ponytail": {
|
|
40
|
+
"source": {
|
|
41
|
+
"source": "github",
|
|
42
|
+
"repo": "DietrichGebert/ponytail"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"humanizer": {
|
|
46
|
+
"source": {
|
|
47
|
+
"source": "github",
|
|
48
|
+
"repo": "blader/humanizer"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"enabledPlugins": {
|
|
53
|
+
"ponytail@ponytail": true,
|
|
54
|
+
"humanizer@humanizer": true
|
|
55
|
+
},
|
|
38
56
|
"hooks": {
|
|
39
57
|
"PreToolUse": [
|
|
40
58
|
{
|
|
@@ -21,26 +21,49 @@ need to retest. Use this right after merging the work to `develop` (which deploy
|
|
|
21
21
|
(`type` / `resource_subtype` is a comment, not a system story), take the **most recent**
|
|
22
22
|
one's `created_by`. This is the reassignee — do not hardcode a person. If there are no
|
|
23
23
|
comments, ask the user who to assign to.
|
|
24
|
-
3. **Draft the comment.**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
3. **Draft the comment — short.** Two parts, nothing else: what changed (and, if they
|
|
25
|
+
reported a bug, what actually caused it), then a **Retest** section with the steps and
|
|
26
|
+
the expected result. Ground the steps in the behaviour that actually changed.
|
|
27
|
+
|
|
28
|
+
**Hard limit: ~200 words, and never more than 6 short paragraphs/bullets.** These
|
|
29
|
+
comments have been far too long — reading them costs the reader more than the fix did.
|
|
30
|
+
Cut: the investigation narrative, why the bug shipped, code/file/commit detail, anything
|
|
31
|
+
restating what they already reported, and any sentence that would not change what they
|
|
32
|
+
do next.
|
|
33
|
+
|
|
34
|
+
**Short is not partial.** Include everything they need in ONE comment — every scenario to
|
|
35
|
+
retest, any setup or data gotcha, and anything about the result that would otherwise look
|
|
36
|
+
wrong (an odd amount, a delay, a cosmetic leftover). A follow-up comment correcting or
|
|
37
|
+
completing this one is the failure this limit exists to prevent, so check the draft for
|
|
38
|
+
omissions *before* trimming words.
|
|
39
|
+
|
|
40
|
+
**Open with an @-mention of the reassignee** — use `html_text` and start the body with
|
|
41
|
+
`<a data-asana-gid="<their user gid>"/>` (Asana expands it into a proper mention). A
|
|
42
|
+
comment without a mention may notify no one; reassignment alone is easy to miss.
|
|
43
|
+
4. **Run the draft through `humanizer`.** A real teammate reads this, and generated-sounding
|
|
44
|
+
prose reads as effort nobody spent. Invoke the `humanizer` skill on the drafted body: it
|
|
45
|
+
strips the AI tells — inflated claims, hedging, stock transitions, "it's worth noting",
|
|
46
|
+
restating the ask twice.
|
|
47
|
+
|
|
48
|
+
It pulls in the same direction as the word limit above, so apply it *before* the final
|
|
49
|
+
trim and re-check the result against both rules. Two things it must not touch: the
|
|
50
|
+
`<a data-asana-gid="..."/>` mention (leave the markup exactly as-is) and any concrete
|
|
51
|
+
detail from the Retest steps — if a pass makes the steps vaguer, keep the original wording.
|
|
52
|
+
5. **Confirm before writing.** This posts to an external system. Show the user the chosen
|
|
32
53
|
assignee and the drafted comment and get a go-ahead, unless they've told you to proceed
|
|
33
54
|
without asking.
|
|
34
|
-
|
|
55
|
+
6. **Apply** (on approval):
|
|
35
56
|
- `asana_update_task` — set `assignee` to the last commenter's gid and `due_at` to the
|
|
36
57
|
current date/time (ISO 8601).
|
|
37
58
|
- `asana_create_task_story` — post the comment.
|
|
38
|
-
|
|
59
|
+
7. **Report** the task URL, who it was assigned to, and confirm the due date was updated.
|
|
39
60
|
|
|
40
61
|
## Notes
|
|
41
62
|
|
|
42
63
|
- If the Asana MCP isn't connected, stop and tell the user to authorize it — don't fabricate a
|
|
43
64
|
handoff.
|
|
44
65
|
- Keep the comment user/QA-facing: what to test and expect, not internal implementation detail.
|
|
66
|
+
The reader is verifying behaviour, not reviewing the fix — if a sentence only makes sense to
|
|
67
|
+
someone who has read the diff, it does not belong in the comment.
|
|
45
68
|
- Consider running `drawbridge-ship-feature` instead if docs/graph updates for this ship
|
|
46
69
|
haven't been done yet — this skill is the final step of that flow.
|
|
@@ -14,13 +14,22 @@ knowledge graph, and the Asana handoff so nothing drifts at ship time.
|
|
|
14
14
|
1. **Verify it works.** Run the project's verification (the `verify` skill / build / lint /
|
|
15
15
|
manual smoke — there's no unit-test framework in api/sync/app-web). Confirm the behaviour
|
|
16
16
|
end-to-end, not just that it compiles.
|
|
17
|
-
2. **
|
|
17
|
+
2. **Review the diff for over-engineering.** Invoke the `ponytail-review` skill on the change
|
|
18
|
+
before it merges. It reports one line per finding — reinvented stdlib, a dependency that
|
|
19
|
+
wasn't needed, a speculative abstraction, flexibility nothing calls. Act on what's real and
|
|
20
|
+
say why you're keeping anything you skip; a finding that survives review deliberately is
|
|
21
|
+
fine, one that survives because nobody looked is how the family accretes code. This is the
|
|
22
|
+
cheapest point to cut it — after merge it's someone else's inheritance.
|
|
23
|
+
|
|
24
|
+
Ponytail's baseline is already ambient (a SessionStart hook applies it at `full` intensity
|
|
25
|
+
family-wide), so this step is the explicit diff pass, not the switch that turns it on.
|
|
26
|
+
3. **Reconcile the spec.** Find the matching `drawbridge-docs` user story by its
|
|
18
27
|
`<domain>/<slug>` ID (or the relevant `reference/` doc). If behaviour changed, update the
|
|
19
28
|
story in the same change (use `write-user-stories` for a new/edited story). If you can't,
|
|
20
29
|
flag the drift explicitly. See `conventions/docs-linkage.md`.
|
|
21
|
-
|
|
30
|
+
4. **Confirm doc anchors resolve.** Run `npx drawbridge-agents-check-docs`; fix any unresolved
|
|
22
31
|
`@story` / `@doc` tag. Add an anchor to newly-added feature code where it maps to a story.
|
|
23
|
-
|
|
32
|
+
5. **Refresh the knowledge graph — always, on every feature wrap** (not conditional on whether
|
|
24
33
|
structure "obviously" changed). The canonical graph is the committed snapshot in
|
|
25
34
|
`drawbridge-docs`, shared by the whole team; skipping it lets the shared graph drift. From the
|
|
26
35
|
`drawbridge-docs` repo: `npx drawbridge-agents-graph` (rebuilds `knowledge/graphs/global.json`
|
|
@@ -28,9 +37,9 @@ knowledge graph, and the Asana handoff so nothing drifts at ship time.
|
|
|
28
37
|
knowledge graph (<feature>)" && git push origin main`. If it reports "graphify runtime not
|
|
29
38
|
found", the `uv`/`graphify` binaries are in `~/.local/bin` — `export PATH="$HOME/.local/bin:$PATH"`
|
|
30
39
|
and retry (do NOT run `graphify install`; it rewrites `CLAUDE.md`).
|
|
31
|
-
|
|
40
|
+
6. **Hand off the task.** Invoke `drawbridge-asana-ship-handoff` with the Asana task to reassign
|
|
32
41
|
to the last commenter, post the change summary + retest steps, and set the due date.
|
|
33
|
-
|
|
42
|
+
7. **Clear the worktree.** If the feature was built in a git worktree (a sibling directory or a
|
|
34
43
|
`.worktrees/` folder), remove it now that the branch is merged: from the main checkout,
|
|
35
44
|
`git worktree remove <path>` then `git worktree prune`. If the removal complains about
|
|
36
45
|
uncommitted changes, inspect them first — add `--force` only once you've confirmed the dirt
|
|
@@ -39,6 +48,6 @@ knowledge graph, and the Asana handoff so nothing drifts at ship time.
|
|
|
39
48
|
|
|
40
49
|
## Notes
|
|
41
50
|
|
|
42
|
-
- Steps
|
|
51
|
+
- Steps 3–5 are what the drift-check Stop hook enforces; running them here means you won't get
|
|
43
52
|
blocked at turn end.
|
|
44
53
|
- "Shipped" in this family means merged to `develop` (that's the deployed dev environment).
|
package/README.md
CHANGED
|
@@ -20,7 +20,8 @@ claude/
|
|
|
20
20
|
CLAUDE.md ← aggregator — @-imports every conventions/*.md
|
|
21
21
|
|
|
22
22
|
.claude-template/ ← the complete shared .claude/, mirrored into each consumer repo
|
|
23
|
-
settings.json ← permissions, additionalDirectories, drift-check Stop hook
|
|
23
|
+
settings.json ← permissions, additionalDirectories, drift-check Stop hook,
|
|
24
|
+
family-wide plugin marketplaces + enabledPlugins
|
|
24
25
|
skills/ ← shared skills (drawbridge-ship-feature, -asana-ship-handoff, ...)
|
|
25
26
|
hooks/ agents/ commands/
|
|
26
27
|
|
|
@@ -54,6 +54,24 @@ skill) so it stops living only in tribal memory.
|
|
|
54
54
|
index manually is not enough; until the boot collMod lands, every insert carrying the field
|
|
55
55
|
fails Mongo 121 (not E11000), so keyed-idempotency writers silently drop their writes
|
|
56
56
|
(2026-07-30 `notification.key` dev outage, Sentry DRAWBRIDGE-SYNC-32).
|
|
57
|
+
- **Absent keys become BSON null on validated writes** (api ↔ sync): neither app's MongoClient
|
|
58
|
+
sets `ignoreUndefined`, so a JS `undefined` property serializes as an explicit `null` — which
|
|
59
|
+
strict validators reject wherever the key is typed non-nullable (bare `bsonType : 'string'`).
|
|
60
|
+
Submission `fields` items legally lack `label` (nothing per-item is required), so any consumer
|
|
61
|
+
copying them into a **validated** write must include keys conditionally
|
|
62
|
+
(`...( typeof label === 'string' && { label } )`), never `label : maybeUndefined`. Violated by
|
|
63
|
+
sync `buildContact` until 2026-08-22: contact CREATE is the one validated write in the upsert
|
|
64
|
+
(updates bypass), so one label-less lead failed the insert with Mongo 121 and killed the org's
|
|
65
|
+
entire nightly segment sync (Sentry DRAWBRIDGE-SYNC-32, prod).
|
|
66
|
+
- **Share-page freshness is invalidation-driven, not TTL-driven** (sync ↔ api ↔ share). The api
|
|
67
|
+
render cache (`lib/render-cache.js`, 5 min TTL) and share's HTML cache (SWR, 60 s soft) are
|
|
68
|
+
both busted ONLY through sync's seam: `RENDER_AFFECTING` in
|
|
69
|
+
`lib/render-affecting-collections.js` → `render-invalidate` worker →
|
|
70
|
+
`invalidateSubdomainPageKeys`. Any collection baked into the public page payload MUST be in
|
|
71
|
+
that allowlist or its edits sit stale for the full TTL with no error anywhere — `product` was
|
|
72
|
+
missing until 2026-08-13 (Shopify renames took 5+ min to reach share pages). Product scope is
|
|
73
|
+
special: the doc carries no campaign/org refs, so the worker resolves featuring campaigns via
|
|
74
|
+
`campaign.products[]`, per-page org (canonical product docs can span orgs).
|
|
57
75
|
- Scraped-asset file reuse keys on `organization + meta.origin + meta.element` **plus
|
|
58
76
|
`meta.render` for pipeline-rendered marks** (sync `resolveAsset` writes/queries it; the field
|
|
59
77
|
and its index live in drawbridge-api `schema/file.js`). `origin` identifies the source asset,
|
|
@@ -245,6 +263,32 @@ skill) so it stops living only in tribal memory.
|
|
|
245
263
|
parsed milliseconds — `frameMilliseconds` in api `route/organization-file.js`, `lib/frames.js`
|
|
246
264
|
in app-web.
|
|
247
265
|
|
|
266
|
+
## File uploads (utils ↔ api ↔ sync ↔ app-web)
|
|
267
|
+
|
|
268
|
+
- `drawbridge-utils/lib/upload.js` is the ONLY allowlist of what may be uploaded, and now also
|
|
269
|
+
owns `maximumUploadBytes` + `uploadPartSize()`. api validates against it, sync mimes-checks
|
|
270
|
+
against it, app-web derives its `accept` attribute, "Accepted formats" row and uploader
|
|
271
|
+
restrictions from it. Adding a format = edit + publish utils + bump the pin in all three; a
|
|
272
|
+
hand-written copy anywhere is how a format gets accepted by one side and rejected by another.
|
|
273
|
+
- **app-web must compute the part size itself**, not read it from the create response — Uppy's
|
|
274
|
+
`MultipartUploader` builds its chunk array in its constructor, *before* `createMultipartUpload`
|
|
275
|
+
is called. Both sides call the same `uploadPartSize()` so the boundaries cannot disagree.
|
|
276
|
+
- **Never derive a file's extension from the mime subtype.** That works only where subtype ==
|
|
277
|
+
extension; an iPhone `.mov` is `video/quicktime`, so the naive split sends `quicktime` and the
|
|
278
|
+
upload 400s before a byte moves. app-web `fileParts()` in `lib/helpers.js`.
|
|
279
|
+
- **api and sync deploy together for multipart.** The presigned routes create the `file` doc
|
|
280
|
+
BEFORE the bytes (`status: 'pending'`, `bytes: 0`), so without sync's update-side
|
|
281
|
+
`fileBytesDelta` the file counts as ZERO toward `organization.totals.storage` forever — the
|
|
282
|
+
storage restriction passes indefinitely and we under-bill, with nothing erroring.
|
|
283
|
+
- **Spaces bucket CORS must set `ExposeHeaders: [ ETag ]`** for the dashboard origin. Without it
|
|
284
|
+
`@uppy/aws-s3`'s `uploadPartBytes` cannot read the part ETag and returns without resolving or
|
|
285
|
+
rejecting — the upload hangs forever with no error, no retry and no timeout.
|
|
286
|
+
- Abandoned multipart parts are BILLED by Spaces while invisible to bucket listings, so the
|
|
287
|
+
`file.sweep` job in drawbridge-sync (hourly, 6h window) is not optional. Golden Retriever's
|
|
288
|
+
`expires` in app-web must match that window.
|
|
289
|
+
- `sizes.original` must be set when a multipart upload completes, not at create — the resize
|
|
290
|
+
worker reads it as its source, and a file without it is shaped unlike every other file.
|
|
291
|
+
|
|
248
292
|
## Import surfaces
|
|
249
293
|
|
|
250
294
|
- Import names against a package's **actual exports** — a missing export resolves to `undefined`
|
package/package.json
CHANGED