@drawbridge/drawbridge-agents 0.1.19 → 0.1.21
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.
|
@@ -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
|
}
|
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) {
|
|
@@ -40,7 +40,14 @@ skill) so it stops living only in tribal memory.
|
|
|
40
40
|
Emitting first hands the client a stale menu nothing re-emits to correct (the stuck-dot bug).
|
|
41
41
|
Workflow doc changes have **no** socket event, so app-web workflow status mutations
|
|
42
42
|
(pause/resume/delete) invalidate the menu client-side themselves.
|
|
43
|
-
-
|
|
43
|
+
- Notification dedupe has two distinct mechanisms — pick by intent. `notification.coalesce`
|
|
44
|
+
is **last-write-wins supersede** (stream/notification.js deletes the prior doc sharing the
|
|
45
|
+
key); `notification.key` is **first-write-wins idempotency** — a partial unique index in
|
|
46
|
+
drawbridge-api `schema/notification.js` refuses the duplicate with E11000, which the writer
|
|
47
|
+
must swallow as already-notified (sync queue/connection.js rehydrate,
|
|
48
|
+
`ads-republishing.<connectionId>`). Never unique-index `coalesce` (superseding requires the
|
|
49
|
+
second insert to land), and writes that set `key` need the writer-side E11000 handler or a
|
|
50
|
+
replayed event logs a false error.
|
|
44
51
|
`meta.render` for pipeline-rendered marks** (sync `resolveAsset` writes/queries it; the field
|
|
45
52
|
and its index live in drawbridge-api `schema/file.js`). `origin` identifies the source asset,
|
|
46
53
|
`render` content-hashes the rendered output — drop `render` from either side and a rendering
|
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 = () => {
|