@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.
|
@@ -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
|
|
package/bin/release-doctor.js
CHANGED
|
@@ -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
|
|
42
|
-
//
|
|
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
|
|
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
|
|
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>"
|
|
54
|
-
grep -rn "<file-name-or-export>"
|
|
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