@drawbridge/drawbridge-agents 0.1.87 → 0.1.88

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.
@@ -14,6 +14,7 @@
14
14
  "mcp__*"
15
15
  ],
16
16
  "additionalDirectories": [
17
+ "../.worktrees",
17
18
  "../drawbridge-agents",
18
19
  "../drawbridge-api",
19
20
  "../drawbridge-app-web",
@@ -45,7 +45,7 @@ A run does not stop while there is code left to write. Whatever the hour, the ag
45
45
  1. **No question, no comment, no request.** The line 3 writes to Asana (section moves, the story on each subtask) happen right after the yes and ask nothing; after them, no question to the person, no Asana comment, no publish ask, no access request: not for a missing credential, not for an MCP server that is not connected, not for a tool a hook or a permission refuses. The rule "ask before writing a line" (`conventions/rules.md` rule 1) was satisfied at line 2; a question born mid-build is parked, not asked.
46
46
  2. **Park it, build the rest.** Anything that needs a person is written into the release doc's Parked section (`releases/<id>.md`, committed on the docs branch so it survives a long run): the subtask, what exactly is needed, in the three-sentence shape of rule 7, ready to paste into the end list. Then build every subtask that does not depend on it.
47
47
  3. **Be optimistic about access.** When something is missing (a token, a connected service, a tool), write the code as if it will be granted, test what can be tested without it, and park the one step that cannot run (Darren: "lets be optimistic that access can be granted so work isn't held up"). The parked line names what the grant unblocks, so the close-out finishes that step and nothing else.
48
- 4. **Permissions never prompt.** The shared `.claude/settings.json` the agents package mirrors into every repo allows every Bash command, file writes, web fetches and every MCP tool; the hooks that refuse a step (read-before-write, drift, QA gate) return an error the skill handles. The release doctor confirms the mirrored settings file is present at line 1, so a machine that would prompt fails before the run, not in the middle of it.
48
+ 4. **Permissions never prompt.** The shared `.claude/settings.json` the agents package mirrors into every repo allows every Bash command, file writes, web fetches and every MCP tool, and names `../.worktrees` as an allowed directory, because a Write or Edit outside the allowed directories prompts the person whatever the allow list says (that is what stopped Butterscotch mid-build on 2026-10-22: its build agents edited files in the worktrees). Build agents dispatched with the Agent tool inherit the same settings, so they never prompt either; the hooks that refuse a step (read-before-write, drift, QA gate) return an error the skill handles. The release doctor confirms the mirrored settings file is present at line 1, so a machine that would prompt fails before the run, not in the middle of it.
49
49
  5. **The Local refresh asks nothing.** It runs once, at the start of line 4, and is skipped when the Releases board shows another release in Building, because that release's walk may be mid-flight on the same Local database. Then the build runs on Local as it stands.
50
50
  6. **Everything owed to a person comes out once**, as the end list in line 5, after every subtask that can be built has been built.
51
51
 
@@ -38,15 +38,39 @@ const envFileOk = (text) => {
38
38
  const readClaudeConfig = () => { try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.claude.json'), 'utf8')) } catch (error) { return null } }
39
39
  const hasDigitalOcean = (config) => Boolean(config && config.mcpServers && config.mcpServers.digitalocean && config.mcpServers.digitalocean.url)
40
40
 
41
- // The mirrored settings file is what keeps an unattended run from prompting: the shared
42
- // allow list covers Bash, file writes, web fetches and every MCP tool.
41
+ // The mirrored settings file is what keeps an unattended run from prompting on EVERY
42
+ // machine: it is committed in each repo, so a clone carries it, and it covers Bash,
43
+ // file writes, web fetches and every MCP tool. Two per-machine files can still undo it:
44
+ // a drifted mirror (fixed by drawbridge-agents-sync) and a user-level deny rule in
45
+ // ~/.claude/settings.json, which beats any allow (Darren, 2026-10-23: "All machines
46
+ // using our agents repo should be exempt from system prompts").
47
+ // It must also name ../.worktrees as an allowed directory: the release builds in
48
+ // worktrees there, and a Write or Edit outside the allowed directories prompts the
49
+ // person no matter what the allow list says (Butterscotch, 2026-10-22, stopped mid-build).
43
50
  const settingsAllowAll = (text) => {
44
51
  try {
45
- const allow = JSON.parse(text).permissions.allow
46
- return [ 'Bash(*)', 'Write', 'Edit', 'mcp__*' ].every((rule) => allow.includes(rule))
52
+ const { allow, additionalDirectories } = JSON.parse(text).permissions
53
+ return [ 'Bash(*)', 'Write', 'Edit', 'mcp__*' ].every((rule) => allow.includes(rule)) && additionalDirectories.includes('../.worktrees')
47
54
  } catch (error) { return false }
48
55
  }
49
56
 
57
+ // The mirror must equal the installed package's template; a hand-edited copy is a
58
+ // machine that prompts where the others do not.
59
+ const settingsMatchTemplate = (mirror, template) => {
60
+ try { return JSON.stringify(JSON.parse(mirror).permissions) === JSON.stringify(JSON.parse(template).permissions) } catch (error) { return false }
61
+ }
62
+
63
+ // A user-level deny on Bash, Write, Edit, WebFetch or an MCP tool overrides the
64
+ // project's allow and reintroduces the prompt. `undefined` text means no user file,
65
+ // which is fine.
66
+ const userDenies = (text) => {
67
+ if (text == null) return []
68
+ try {
69
+ const deny = (JSON.parse(text).permissions || {}).deny || []
70
+ return deny.filter((rule) => /^(Bash|Write|Edit|MultiEdit|WebFetch|mcp__)/.test(rule))
71
+ } catch (error) { return [] }
72
+ }
73
+
50
74
  // The printed list: one numbered line per failure, each with its fix; nothing when all pass.
51
75
  const report = (results) => {
52
76
  const failed = results.filter((r) => !r.ok)
@@ -57,13 +81,16 @@ const report = (results) => {
57
81
  const checks = (projectsDir) => {
58
82
  const api = path.join(projectsDir, 'drawbridge-api')
59
83
  const read = (file) => { try { return fs.readFileSync(path.join(api, file), 'utf8') } catch (error) { return null } }
84
+ const readUser = () => { try { return fs.readFileSync(path.join(os.homedir(), '.claude', 'settings.json'), 'utf8') } catch (error) { return undefined } }
60
85
  return [
61
86
  { name: 'git', required: true, ok: Boolean(sh('git', [ '--version' ])), fix: 'install Xcode command line tools' },
62
87
  { name: 'gh signed in', required: true, ok: Boolean(sh('gh', [ 'auth', 'status' ])), fix: 'brew install gh && gh auth login' },
63
88
  { name: 'op with --environment (beta channel)', required: true, ok: /--environment/.test(sh('op', [ 'run', '--help' ]) || ''), fix: 'install the 1Password CLI beta: see drawbridge-growth/scripts/op.sh for the curl line' },
64
89
  { name: 'drawbridge-api/.env.local', required: true, ok: envFileOk(read('.env.local')), fix: 'two lines, OP_SERVICE_ACCOUNT_TOKEN and OP_ENVIRONMENT_ID, naming the Local Environment' },
65
90
  { name: 'drawbridge-api/.env.development', required: true, ok: envFileOk(read('.env.development')), fix: 'two lines naming the Development Environment' },
66
- { name: 'drawbridge-api/.claude/settings.json allows every tool', required: true, ok: settingsAllowAll(read(path.join('.claude', 'settings.json'))), fix: 'npx drawbridge-agents-sync in drawbridge-api mirrors the shared settings; without the allow list an overnight run stops on a permission prompt' },
91
+ { name: 'drawbridge-api/.claude/settings.json allows every tool and the worktrees folder', required: true, ok: settingsAllowAll(read(path.join('.claude', 'settings.json'))), fix: 'npx drawbridge-agents-sync in drawbridge-api mirrors the shared settings; without the allow list an overnight run stops on a permission prompt' },
92
+ { name: 'drawbridge-api/.claude/settings.json matches the installed agents template', required: true, ok: settingsMatchTemplate(read(path.join('.claude', 'settings.json')), read(path.join('node_modules', '@drawbridge', 'drawbridge-agents', '.claude-template', 'settings.json'))), fix: 'npx drawbridge-agents-sync in drawbridge-api; a hand-edited mirror prompts where other machines do not' },
93
+ { name: 'no user-level deny on the release tools', required: true, ok: userDenies(readUser()).length === 0, fix: 'remove the deny rules for Bash, Write, Edit, WebFetch or mcp__ from ~/.claude/settings.json; a deny beats the project allow and prompts mid-run' },
67
94
  { name: 'drawbridge-api/scripts/op.sh', required: true, ok: fs.existsSync(path.join(api, 'scripts', 'op.sh')), fix: 'pull develop in drawbridge-api' },
68
95
  { name: 'npm registry', required: true, ok: Boolean(sh('npm', [ 'view', '@drawbridge/drawbridge-agents', 'version' ])), fix: 'check the network or npm login' },
69
96
  { name: 'DigitalOcean MCP server in Claude Code', required: true, ok: hasDigitalOcean(readClaudeConfig()), fix: 'claude mcp add --scope user --transport http digitalocean https://apps.mcp.digitalocean.com/mcp --header "Authorization: Bearer <your DigitalOcean API token>" (the skill reads deployments through it; no doctl)' },
@@ -82,6 +109,6 @@ const main = async () => {
82
109
  process.exit(results.some((r) => r.required && !r.ok) ? 1 : 0)
83
110
  }
84
111
 
85
- module.exports = { checks, envFileOk, hasDigitalOcean, report, settingsAllowAll }
112
+ module.exports = { checks, envFileOk, hasDigitalOcean, report, settingsAllowAll, settingsMatchTemplate, userDenies }
86
113
 
87
114
  if (require.main === module) main()
@@ -36,7 +36,7 @@ apps while leaving a sibling's peer dep on the old version triggers
36
36
  Before publishing a bump, grep peerDependencies across the family:
37
37
 
38
38
  ```sh
39
- grep -A8 peerDependencies /Users/darrenshea/Projects/drawbridge-*/package.json
39
+ grep -A8 peerDependencies ../drawbridge-*/package.json
40
40
  ```
41
41
 
42
42
  If a sibling package peer-pins something you just bumped, that sibling
@@ -50,8 +50,8 @@ paths that operate on that value. There are known copy-paste duplicates
50
50
  that drift silently when only one side is updated.
51
51
 
52
52
  ```sh
53
- grep -rn "<old-value>" /Users/darrenshea/Projects/drawbridge-* --include="*.js"
54
- grep -rn "<file-name-or-export>" /Users/darrenshea/Projects/drawbridge-* --include="*.js"
53
+ grep -rn "<old-value>" ../drawbridge-* --include="*.js"
54
+ grep -rn "<file-name-or-export>" ../drawbridge-* --include="*.js"
55
55
  ```
56
56
 
57
57
  Specifically watch for:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawbridge/drawbridge-agents",
3
- "version": "0.1.87",
3
+ "version": "0.1.88",
4
4
  "description": "Shared agent-instruction content (rules, code style, conventions) for the drawbridge-* monorepo.",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {