@drawbridge/drawbridge-agents 0.1.19 → 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.
@@ -1,5 +1,12 @@
1
1
  # Managed by @drawbridge/drawbridge-agents
2
2
 
3
- Hook scripts placed here are mirrored into every consumer repo's `.claude/hooks/` on `npm run sync`. To add a shared hook: drop it in this directory in `drawbridge-agents`, bump the package version, publish, then `npm run sync` in each consumer.
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
- Consumer-local hooks should be added directly to the consumer's `.claude/hooks/` with a different filename — the sync mirror only overwrites paths that exist in this template.
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 ./node_modules/@drawbridge/drawbridge-agents/hooks/guard-superpowers-docs.js"
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 ./node_modules/@drawbridge/drawbridge-agents/hooks/drift-check.js"
55
+ "command": "node ./.claude/hooks/drift-check.js"
56
56
  }
57
57
  ]
58
58
  }
@@ -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) {
@@ -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
- const packageRoot = path.resolve(__dirname, '..')
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
- const changedSource = changed.filter((file) => SOURCE_EXTENSIONS.has(path.extname(file)))
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
- try {
61
- execFileSync('node', [ path.join(packageRoot, 'scripts', 'check-doc-links.js') ], { cwd, encoding: 'utf8', stdio: 'pipe' })
62
- } catch (error) {
63
- const detail = ((error.stdout || '') + (error.stderr || '')).trim()
64
- 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.`)
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
- 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.')
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawbridge/drawbridge-agents",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "Shared agent-instruction content (rules, code style, conventions) for the drawbridge-* monorepo.",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {
@@ -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
- const SKIP_DIRS = new Set([ 'node_modules', '.git', '.next', 'dist', 'build', 'coverage', '.turbo', 'graphify-out' ])
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 = () => {