@drawbridge/drawbridge-agents 0.1.18 → 0.1.20
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/.claude-template/hooks/README.md +9 -2
- package/.claude-template/settings.json +2 -2
- package/.claude-template/skills/drawbridge-asana-ship-handoff/SKILL.md +4 -1
- package/bin/sync-claude.js +4 -0
- package/conventions/cross-repo-contracts.md +7 -0
- package/hooks/drift-check.js +30 -9
- package/package.json +1 -1
- package/scripts/check-doc-links.js +3 -1
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Managed by @drawbridge/drawbridge-agents
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared hook scripts are mirrored into every consumer repo's `.claude/hooks/` on `npm run sync`,
|
|
4
|
+
from the package's `hooks/` directory (the single source). They are **tracked** in each consumer
|
|
5
|
+
and invoked by `.claude/settings.json` via `node ./.claude/hooks/<name>.js` — never through
|
|
6
|
+
`node_modules` — so they keep working in fresh git worktrees that haven't run `npm install`.
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
To add or change a shared hook: edit it in `drawbridge-agents/hooks/`, bump the package version,
|
|
9
|
+
publish, then `npm run sync` in each consumer and commit the mirrored `.claude/` changes.
|
|
10
|
+
|
|
11
|
+
Consumer-local hooks can be added directly to the consumer's `.claude/hooks/` with a different
|
|
12
|
+
filename — the sync mirror only overwrites paths that exist in the package.
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"hooks": [
|
|
42
42
|
{
|
|
43
43
|
"type": "command",
|
|
44
|
-
"command": "node
|
|
44
|
+
"command": "node ./.claude/hooks/guard-superpowers-docs.js"
|
|
45
45
|
}
|
|
46
46
|
]
|
|
47
47
|
}
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"hooks": [
|
|
53
53
|
{
|
|
54
54
|
"type": "command",
|
|
55
|
-
"command": "node
|
|
55
|
+
"command": "node ./.claude/hooks/drift-check.js"
|
|
56
56
|
}
|
|
57
57
|
]
|
|
58
58
|
}
|
|
@@ -24,7 +24,10 @@ need to retest. Use this right after merging the work to `develop` (which deploy
|
|
|
24
24
|
3. **Draft the comment.** A concise summary of what changed (not a changelog dump), then an
|
|
25
25
|
explicit **Retest & verify** section: the exact steps and expected results to confirm the
|
|
26
26
|
fix, and any data/setup needed. Ground the retest steps in the actual behaviour that
|
|
27
|
-
changed.
|
|
27
|
+
changed. **Open the comment with an @-mention of the reassignee** — use `html_text` and
|
|
28
|
+
start the body with `<a data-asana-gid="<their user gid>"/>` (Asana expands it into a
|
|
29
|
+
proper mention). A comment without a mention may notify no one; reassignment alone is
|
|
30
|
+
easy to miss.
|
|
28
31
|
4. **Confirm before writing.** This posts to an external system. Show the user the chosen
|
|
29
32
|
assignee and the drafted comment and get a go-ahead, unless they've told you to proceed
|
|
30
33
|
without asking.
|
package/bin/sync-claude.js
CHANGED
|
@@ -43,9 +43,13 @@ const mirror = (src, dst) => {
|
|
|
43
43
|
// 1. Mirror the single source of truth into the consumer. .claude-template holds the entire
|
|
44
44
|
// shared .claude/ setup (settings.json incl. permissions + the drift-check Stop hook, plus
|
|
45
45
|
// agents/commands/hooks/skills); .root-template holds root files (.mcp.json, .editorconfig).
|
|
46
|
+
// The package's hooks/ scripts are mirrored into the consumer's tracked .claude/hooks/ so
|
|
47
|
+
// settings.json can invoke them without node_modules — a fresh worktree that hasn't run
|
|
48
|
+
// npm install would otherwise fail open and lose the guard.
|
|
46
49
|
const mirrorTemplates = () => {
|
|
47
50
|
const targets = [
|
|
48
51
|
{ src: path.join(packageRoot, '.claude-template'), dst: path.join(consumerRoot, '.claude'), required: true },
|
|
52
|
+
{ src: path.join(packageRoot, 'hooks'), dst: path.join(consumerRoot, '.claude', 'hooks'), required: true },
|
|
49
53
|
{ src: path.join(packageRoot, '.root-template'), dst: consumerRoot, required: false }
|
|
50
54
|
]
|
|
51
55
|
for (const { src, dst, required } of targets) {
|
|
@@ -33,6 +33,13 @@ skill) so it stops living only in tribal memory.
|
|
|
33
33
|
`queue/index.js` together.
|
|
34
34
|
- BullMQ jobs are keyed on the change-stream resume token as `jobId` for HA dedup across sync
|
|
35
35
|
replicas. Reusing a static `jobId` with `removeOnComplete` makes re-enqueues no-op silently.
|
|
36
|
+
- Sync's `organization.connections` emit (stream/connection.js) doubles as app-web's cue to
|
|
37
|
+
refetch the server-built org menu (`['menu','organization',id]`), whose sidebar
|
|
38
|
+
Connections/Workflows warning dots reflect statuses `reconcileWorkflowStatus` writes — every
|
|
39
|
+
handler must emit **after** the reconcile (regression: sync test/connection-emit-order.test.js).
|
|
40
|
+
Emitting first hands the client a stale menu nothing re-emits to correct (the stuck-dot bug).
|
|
41
|
+
Workflow doc changes have **no** socket event, so app-web workflow status mutations
|
|
42
|
+
(pause/resume/delete) invalidate the menu client-side themselves.
|
|
36
43
|
- Scraped-asset file reuse keys on `organization + meta.origin + meta.element` **plus
|
|
37
44
|
`meta.render` for pipeline-rendered marks** (sync `resolveAsset` writes/queries it; the field
|
|
38
45
|
and its index live in drawbridge-api `schema/file.js`). `origin` identifies the source asset,
|
package/hooks/drift-check.js
CHANGED
|
@@ -7,7 +7,16 @@ const path = require('path')
|
|
|
7
7
|
const { execFileSync } = require('child_process')
|
|
8
8
|
|
|
9
9
|
const SOURCE_EXTENSIONS = new Set([ '.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs' ])
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
// This file runs from two places: hooks/ inside the package (the
|
|
12
|
+
// drawbridge-agents repo itself, or node_modules) where scripts/ is a sibling,
|
|
13
|
+
// and the mirrored copy in a consumer's tracked .claude/hooks/, where it
|
|
14
|
+
// isn't — there, resolve through the consumer's installed package.
|
|
15
|
+
const resolvePackageRoot = (cwd) => {
|
|
16
|
+
const local = path.resolve(__dirname, '..')
|
|
17
|
+
if (fs.existsSync(path.join(local, 'scripts', 'check-doc-links.js'))) return local
|
|
18
|
+
return path.join(cwd, 'node_modules', '@drawbridge', 'drawbridge-agents')
|
|
19
|
+
}
|
|
11
20
|
|
|
12
21
|
const block = (reason) => {
|
|
13
22
|
process.stdout.write(JSON.stringify({ decision: 'block', reason }))
|
|
@@ -51,17 +60,27 @@ const run = () => {
|
|
|
51
60
|
|
|
52
61
|
const cwd = input.cwd || process.cwd()
|
|
53
62
|
const changed = changedFiles(cwd)
|
|
54
|
-
|
|
63
|
+
|
|
64
|
+
// .claude/ churn is sync-mirror tooling (including this hook's own mirrored
|
|
65
|
+
// copy), not project source — it must not trip the drift checks.
|
|
66
|
+
const changedSource = changed.filter((file) =>
|
|
67
|
+
SOURCE_EXTENSIONS.has(path.extname(file)) && !file.split(path.sep).includes('.claude')
|
|
68
|
+
)
|
|
55
69
|
|
|
56
70
|
// Pure Q&A / non-source turns: nothing to check.
|
|
57
71
|
if (changedSource.length === 0) process.exit(0)
|
|
58
72
|
|
|
59
|
-
// 1. Doc-link validity (blocking) — cheap, precise, quick to fix.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
// 1. Doc-link validity (blocking) — cheap, precise, quick to fix. A missing
|
|
74
|
+
// checker (fresh worktree with no node_modules) is not drift — skip, never
|
|
75
|
+
// block on it.
|
|
76
|
+
const checkDocLinks = path.join(resolvePackageRoot(cwd), 'scripts', 'check-doc-links.js')
|
|
77
|
+
if (fs.existsSync(checkDocLinks)) {
|
|
78
|
+
try {
|
|
79
|
+
execFileSync('node', [ checkDocLinks ], { cwd, encoding: 'utf8', stdio: 'pipe' })
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const detail = ((error.stdout || '') + (error.stderr || '')).trim()
|
|
82
|
+
block(`Documentation drift: one or more @story / @doc tags no longer resolve.\n${ detail }\nFix the tag(s) to point at a real drawbridge-docs story ID / reference anchor before finishing.`)
|
|
83
|
+
}
|
|
65
84
|
}
|
|
66
85
|
|
|
67
86
|
const skipGraph = process.env.DRAWBRIDGE_SKIP_GRAPHIFY === '1'
|
|
@@ -100,7 +119,9 @@ const run = () => {
|
|
|
100
119
|
})
|
|
101
120
|
const touchedDocs = changed.some((file) => file.includes(`${ path.sep }drawbridge-docs${ path.sep }`))
|
|
102
121
|
if (touchedAnchoredCode && !touchedDocs) {
|
|
103
|
-
|
|
122
|
+
// `@story` is backticked so doc-link scanners (including stale node_modules
|
|
123
|
+
// copies that don't skip .claude/) never read this sentence as a real tag.
|
|
124
|
+
nudge('You changed code that implements a documented behaviour (carries an `@story` anchor) without touching drawbridge-docs. Confirm the matching user story still matches, or update it in this change.')
|
|
104
125
|
}
|
|
105
126
|
|
|
106
127
|
process.exit(0)
|
package/package.json
CHANGED
|
@@ -6,7 +6,9 @@ const fs = require('fs')
|
|
|
6
6
|
const path = require('path')
|
|
7
7
|
|
|
8
8
|
const SOURCE_EXTENSIONS = new Set([ '.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs' ])
|
|
9
|
-
|
|
9
|
+
// .claude is skipped because the sync mirror places shared tooling there (the drift-check hook's
|
|
10
|
+
// own source mentions the tag syntax) — it's not project source and must never be scanned.
|
|
11
|
+
const SKIP_DIRS = new Set([ 'node_modules', '.git', '.next', 'dist', 'build', 'coverage', '.turbo', 'graphify-out', '.claude' ])
|
|
10
12
|
|
|
11
13
|
// The family root is the directory that contains drawbridge-docs. Walk up from cwd to find it.
|
|
12
14
|
const findFamilyRoot = () => {
|