@drawbridge/drawbridge-agents 0.1.85 → 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",
@@ -14,7 +14,7 @@ The ten lines below are the release, as Darren confirmed them on 2026-10-22. The
14
14
  3. Task → Building: branches, the release document, the band.
15
15
  4. The skill builds on the release branches against the Local database, with the QA agent run locally. Nobody is asked anything; whatever needs a person is parked.
16
16
  5. One end list: the parked questions, the access requests, the publish ask, the pull requests that are ready. The person answers, the skill closes out the rest, task → Review.
17
- 6. The developer reviews, runs the branch locally, merges (only when no other release is in QA or Approved); runs any migration on dev; moves the task → QA and assigns the QA person.
17
+ 6. The developer reviews, runs the branch locally, reviews whenever ready and merges only when develop is free; runs any migration on dev; moves the task → QA and assigns the QA person.
18
18
  7. The QA person tests on dev.
19
19
  8. The QA person moves the task → Approved.
20
20
  9. The developer promotes by hand, with the skill as checklist.
@@ -45,13 +45,13 @@ 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
 
52
52
  ## Line 3: Building
53
53
 
54
- 1. **Many releases build at once; one at a time reaches develop.** List the project's tasks (`GET /projects/1219601041167594/tasks?opt_fields=name,memberships.section.name`). Other releases in Building or Review are fine: every release is built on its own branches against the Local database, so they do not touch each other (Darren, 2026-10-22: "We should be able to do multiple releases at once since the code is all being done locally anyways"). Say which ones are in flight and go on. The lane that holds one release is develop: a release in QA or Approved owns dev until it is Released, and the developer merges no other release's pull requests meanwhile (line 6). This step never blocks on it; the merge does. Then move this task to Building (`POST /sections/1219601033297172/addTask { task }`).
54
+ 1. **Many releases build at once; one at a time reaches develop.** List the project's tasks (`GET /projects/1219601041167594/tasks?opt_fields=name,memberships.section.name`). Other releases in Building or Review are fine: every release is built on its own branches against the Local database, so they do not touch each other (Darren, 2026-10-22: "We should be able to do multiple releases at once since the code is all being done locally anyways"). Say which ones are in flight and go on. Pull requests never wait on another release: they open when the work is finished, and the developer reviews them whenever they like, on the feature branch locally. **Develop holds one release at a time.** The merge is what waits: a reviewed release sits in Review until the release before it is Released, then merges, gets its dev migration, and moves to QA. The Review slot may queue several reviewed releases; that is fine (Darren, 2026-10-23: "There will only ever be one on develop at once. The Review slot may queue up once the devs have reviewed a release but that's fine"; "Develop can only go one at a time"). This step never blocks on it; the merge does. Then move this task to Building (`POST /sections/1219601033297172/addTask { task }`).
55
55
  2. `feature/<id>` in every repo a subtask touches, off `develop` (components and utils off `main`): a git-flow FEATURE branch, never `release/<id>`, because the repos carry git-flow config and a client's "Finish release" merges into `main` with a tag, which would put unapproved code on main before dev QA; "Finish feature" merges into `develop`, which is what this flow does (Darren, 2026-10-22; Almond keeps its older `release/` name). Each in a worktree under `/Users/darrenshea/Projects/.worktrees/<repo>-feature-<id>` so no main checkout changes branch. **drawbridge-docs is the exception**: its `feature/<id>` branch is made in the main docs checkout (docs is `main`-only and single-writer), because `npx drawbridge-agents-check-stories` reads nothing from a worktree path (Asana 1218852240225835). **The stories do not wait on that branch.** They are committed and pushed to docs `main` here, before any code, straight from the main docs checkout (`git -C ../drawbridge-docs push origin main`): a story is the spec, text only, nothing deploys from docs, and the feature pipeline writes the story before the first edit anyway. That is what makes the story links in step 5 work the moment they are added (Darren, 2026-10-22: "Story links will need to be added after the docs repo has been pushed on main so the links work"). The docs `feature/<id>` branch then carries only what rides with the code: `releases/<id>.md`, reference pages, a story edited again during the build. "Repo a subtask touches" includes docs, components and utils, not only the six apps.
56
56
  3. Copy `drawbridge-docs/templates/release.md` to `releases/<id>.md`, fill Scope and Branches (reviewer from line 2), commit on the docs branch. The markdown is the one record; there is no json.
57
57
  4. Write this task's link into `drawbridge-api/release.json` and commit it on the api's `feature/<id>` branch ("release <id>: band"), never on `develop`: with several releases building at once, a band written to develop at open would name a release that is not on dev. The band reaches develop with the merge, and dev shows it above the account once the api redeploys, which is exactly when that release is the one on dev. A release that touches no api code still gets an api `feature/<id>` branch carrying this one commit and its own pull request at line 5. Nothing is committed to develop before the pull requests merge.
@@ -86,7 +86,7 @@ Only when every subtask that can be built has been built: each committed, `npm t
86
86
  **First, the end list.** ONE numbered list to the person who started the release, the first words anyone has heard since the yes (Darren, 2026-10-22: "ask finalizing questions / request access to complete then"). In this order: every parked question, every access request with what it unblocks, the publish ask when there is a package, the subtasks not built and why, and the repos whose pull requests are ready to open. Each item three plain sentences (rule 7). Then, in the same message, open the pull requests that depend on nothing parked (steps 1 to 3 below) and stop. When the answers and grants arrive, finish the parked steps on the release branches, bump the pins once the registry has the version, run the tests and the QA agent on what changed, open the remaining pull requests, and only then move the task to Review. This close-out is the end of the coding segment of a release; nothing in it is asked for a second time.
87
87
 
88
88
  1. One pull request per repo: `gh pr create --base develop --head feature/<id> --title "Release <id>: <task name>" --assignee <developer> --reviewer <developer> --body <the Scope table, the per-subtask commits, the QA reports' paths, and the migration's dev command if there is one>`. Components and utils target `main`.
89
- 2. Every pull request body ends with the lane rule in one line: "Merge only when no other release is in QA or Approved on the Releases board; dev holds one release at a time." Fill the Branches table in `releases/<id>.md` with the PR links; commit.
89
+ 2. Every pull request body ends with the lane rule in one line: "Review any time, on the feature branch locally. Merge only when develop is free: one release on develop at a time, so this merges once the release before it is Released." Fill the Branches table in `releases/<id>.md` with the PR links; commit.
90
90
  3. **How to test, on every subtask, for both readers.** Append to each subtask's description, under its Story lines, two headings (Darren, 2026-10-22: "steps for a developer to test via code, or locally, and a QA person's steps"). First, "Test locally (developer)": one numbered list with the branch to check out, the exact commands (`npm test` in the repo, the test file that proves the change, `npm run migrate -- <id> --dry --env local` when there is a migration, `npm run walk -- --local <path>` for a screen), the localhost URL and the result each command must print or show. Then, "Test on dev (QA)": one numbered list with the dev app to open first by URL (dashboard `https://dev.dashboard.trydrawbridge.com/...`, a share page `https://<slug>.dev.drwbrdg.com/...`, the website `https://dev.trydrawbridge.com/...`, the components app `https://components.trydrawbridge.com/...`), then one action per step, then what the tester must see. Every label in it is quoted from the Local walk and the QA reports of line 4, never from memory (`conventions/walk-the-app.md`), with dev URLs in place of localhost. A migration subtask says what the tester checks after the developer's dev run. The words go through `humanizer`. Both lists are written here, after the build, because only then are the commands and labels known (Darren, 2026-10-22: "include steps on how to test in the respective development apps"). The pull request body and the release doc's QA table point at the subtasks for these steps rather than repeating them.
91
91
  4. Attach every QA report's screenshots to the release task (`node lib/asana-attach.js <release id> <png>` from drawbridge-tests, one per scenario, named `<subtask id>-<scenario>.png`), because the reports under `results/qa/` are gitignored and exist only on the machine that ran them. Then put the PR links on the release task as ONE numbered list (`drawbridge-asana-comment`): this is the first Asana comment of the release, and the package pull request's link rides in it too. Assign the task to the developer and move it to Review (`POST /sections/1219762451512990/addTask`) only once every pull request is open, parked items included.
92
92
  5. Stop. Report the PR links.
@@ -95,7 +95,7 @@ Only when every subtask that can be built has been built: each committed, `npm t
95
95
 
96
96
  The skill does none of this. For the record, so the skill can answer "what is the next step":
97
97
 
98
- - Line 6, the developer: reviews the diff, runs the branch locally, approves and merges, but only when no other release sits in QA or Approved: dev carries one release at a time, and a second merge would put two releases' code in front of the QA person under one band (Darren, 2026-10-22: "only 1 QA release can hit develop at any given time"). Dev deploys itself. If there is a migration: `npm run migrate -- <id> --dry --env dev` from the api checkout on `develop`, read the printed result, then without `--dry`. The developer may have an agent run those two dev commands on their explicit instruction in their own session (Darren, 2026-10-22); the skill never runs them on its own, and no agent ever runs the production one. Then moves the task to QA and assigns the QA person in Asana.
98
+ - Line 6, the developer: reviews the diff, runs the branch locally, approves and merges, but merges only when develop is free. Develop holds one release at a time: the review happens whenever the developer likes, on the feature branch locally, and the approved release waits in Review until the one before it is Released (Darren, 2026-10-23: "Develop can only go one at a time"; "The Review slot may queue up once the devs have reviewed a release but that's fine"). Then the merge, the dev migration, the move to QA and the QA assignment, in that order. Dev deploys itself. If there is a migration: `npm run migrate -- <id> --dry --env dev` from the api checkout on `develop`, read the printed result, then without `--dry`. The developer may have an agent run those two dev commands on their explicit instruction in their own session (Darren, 2026-10-22); the skill never runs them on its own, and no agent ever runs the production one. Then moves the task to QA and assigns the QA person in Asana.
99
99
  - Line 7, the QA person: tests on dev. The band already names the release.
100
100
  - Line 8, the QA person: moves the task to Approved.
101
101
 
@@ -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.85",
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": {