@drawbridge/drawbridge-agents 0.1.79 → 0.1.80
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.
|
@@ -22,7 +22,7 @@ The ten lines below are the release, as Darren confirmed them on 2026-10-22. The
|
|
|
22
22
|
|
|
23
23
|
## Line 1: start
|
|
24
24
|
|
|
25
|
-
Read the release task and every subtask through `drawbridge-tests/lib/asana.js` (`api( 'GET', '/tasks/<id>?opt_fields=name,notes,memberships.section.name' )`, then `/tasks/<id>/subtasks`). Subtasks live in "DrawBridge - Application" and stay there. `gh auth status`
|
|
25
|
+
Read the release task and every subtask through `drawbridge-tests/lib/asana.js` (`api( 'GET', '/tasks/<id>?opt_fields=name,notes,memberships.section.name' )`, then `/tasks/<id>/subtasks`). Subtasks live in "DrawBridge - Application" (or the Backlog) and stay there. A release is named with one ice cream, in alphabetical order across releases, nothing else: "Almond", "Butterscotch", "Cherry", "Dulce", "Espresso" exist (Darren, 2026-10-22: no numbers, no weeks, no dates; the alphabet carries the order). The date is the task's due field. If asked to create one, take the next letter after the latest in the Releases project. Run `npx drawbridge-agents-release-doctor` from the api checkout first: it checks git, `gh auth status`, `op` on the beta channel, the api's two env files, `scripts/op.sh`, the npm registry and both clusters, and prints one numbered list of what is missing with the fix for each. Stop on any required miss and hand that list to the person. Other developers start releases from their own machines, so nothing is assumed.
|
|
26
26
|
|
|
27
27
|
## Line 2: the one list
|
|
28
28
|
|
|
@@ -30,7 +30,7 @@ For every subtask, find or write the story (`drawbridge-feature` step 1). Then O
|
|
|
30
30
|
|
|
31
31
|
- every unknown from every story;
|
|
32
32
|
- **who on the development team takes the pull requests**: their GitHub username (for the pull requests) and their Asana name (for the task). Asked now so nobody needs to be present between lines 3 and 5. Never guess a reviewer;
|
|
33
|
-
- **a package publish, if any subtask names `@drawbridge/components` or `@drawbridge/drawbridge-utils`**, in these words: this release publishes a package; the package is built
|
|
33
|
+
- **a package publish, if any subtask names `@drawbridge/components` or `@drawbridge/drawbridge-utils`**, in these words: this release publishes a package; the package is built first and gets its own pull request to the package's `main`; once that merges the person publishes from `main` (`npm run build`, the one-time password, until the automation token, queue task 1218041740718540, exists); the skill then bumps the pin in every consumer and no app pull request opens before the registry has the version. Line 4 step 2 is the exact sequence.
|
|
34
34
|
|
|
35
35
|
Wait for the yes. Nothing below runs before it. A release that publishes a package stops once more, at the publish (line 4 step 2); everything else between lines 3 and 5 runs without anyone present.
|
|
36
36
|
|
|
@@ -40,14 +40,21 @@ Wait for the yes. Nothing below runs before it. A release that publishes a packa
|
|
|
40
40
|
2. `release/<id>` in every repo a subtask touches, off `develop` (components and utils off `main`), each in a worktree under `/Users/darrenshea/Projects/.worktrees/<repo>-release-<id>` so no main checkout changes branch. **drawbridge-docs is the exception**: its `release/<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). "Repo a subtask touches" includes docs, components and utils, not only the six apps.
|
|
41
41
|
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.
|
|
42
42
|
4. If `drawbridge-api/release.json` does not already carry this task's link, write it, commit on the api's `develop` directly ("release <id>: band"), push; dev shows the yellow band above the account once the api redeploys. This is the only develop commit before the pull requests merge. When the link is already there, say so and move on.
|
|
43
|
-
5.
|
|
43
|
+
5. Move every subtask from Application → Queue to Application → In progress (`POST /sections/1211315620607633/addTask { task }`). In the Application project, Queue means not scheduled and In progress means in a release (Darren, 2026-10-22); Complete comes at line 10. The release task itself lives in the Releases project only, never in Application or the Backlog, because a release draws its subtasks from both (Darren, 2026-10-22: "releases will include application tasks and backlog items, so their own board makes more sense"). A Backlog subtask keeps its Backlog membership; only Application subtasks get the Queue → In progress move.
|
|
44
|
+
6. **No pull request yet.** No "Marketing: What's new" subtask yet either: it is created, written and completed in one go at line 10, so developers and QA never see an open marketing item among the subtasks they are building and testing (Darren, 2026-10-22: "we should not have the marketing task in a release until it's completed, as it's confusing for the developers and qa people"). Nothing is opened, drafted or assigned at this point; the developer hears nothing until line 5, when the branch is finished. A pull request opened early reads to the developer as "review this now" and there is nothing to review (Darren, 2026-10-22, after the first run).
|
|
44
45
|
|
|
45
46
|
## Line 4: build, on Local
|
|
46
47
|
|
|
47
48
|
All work on the release branches against the **Local** database. Develop and its schema are never touched while a release is being built, and one release at a time is promoted to develop.
|
|
48
49
|
|
|
49
|
-
1. **Local up.** `npm run migrate -- refresh` in the api (copies dev into Local; `--dry` first), then `npm run local` in every app the release touches: dashboard 3000, share 3001, website 3002, api 4000, sync 4001, webhooks 4002. The scripts force the ports; nothing sets them.
|
|
50
|
-
2. **Package first
|
|
50
|
+
1. **Local up.** `npm run migrate -- refresh` in the api (copies dev into Local; `--dry` first), then `npm run local` in every app the release touches: dashboard 3000, share 3001, website 3002, api 4000, sync 4001, webhooks 4002; the components app (`npm run app` in drawbridge-components) is 3003. The scripts force the ports; nothing sets them.
|
|
51
|
+
2. **Package first, and the publish never blocks.** Every subtask that touches `@drawbridge/drawbridge-utils` or `@drawbridge/components` is built before any app subtask, so the publish is the earliest thing the person is asked for, and it never pauses the build (Darren, 2026-10-22: "I had to wait a while for the publishing code to be written"; "the publish step includes merging each npm package to main, and bumping the pin(s)"). The whole package step, per package, utils before components (components peer-depends on utils):
|
|
52
|
+
1. Build the package subtasks on `release/<id>` in that package, tests first, `npm test` and `npm run test:app` (components) green, the two review passes.
|
|
53
|
+
2. Bump the package's own version in its `package.json` (patch), commit, push, and open its pull request to the package's `main` (`gh pr create --base main …`), assigned to the developer from line 2. Put the link on the release task.
|
|
54
|
+
3. Ask the person for the publish, as one numbered item with the exact words: merge that pull request, then from the package checkout on `main`: `git pull && npm run build` (tsup, tests, `npm publish` with the one-time password), then confirm with `npm view <package> version`.
|
|
55
|
+
4. **Keep going** on the app subtasks. For one that needs the new package code, `npm link` the local package checkout into the app worktree so it is written and tested against it now.
|
|
56
|
+
5. When `npm view <package> version` shows the new version (checked between subtasks, never by stopping): bump the pin to that exact version in **every consumer in the family**, not only the release's (Darren, 2026-10-22: "Once a package is published, all consumer pins should also be updated"; pins are version-exact family-wide, `conventions/drawbridge-packages.md`). One command does it: `npx drawbridge-agents-bump <package> <version>` (from any family checkout; `--dry` lists the consumers first) finds every consumer, keeps each one's field, installs the exact version so the lockfile names it, commits "<package> <version>" and pushes each repo's current branch. Before running it, the release's own consumers must have their `release/<id>` branch checked out (the worktrees), so they take the bump there, while every other consumer takes a pin-only commit on its integration branch. Unlink first where a worktree was linked; run the tests after. A sibling package bumped this way is itself published by the same steps before the apps that depend on it.
|
|
57
|
+
6. No app pull request opens while a pin still names a version the registry does not have.
|
|
51
58
|
3. **Per subtask**, on the release branch: read the story and the reference page, one graph query, failing test first where a suite exists, the change, `npm test` in that repo. One commit per subtask, its message starting with the subtask id.
|
|
52
59
|
4. **Migration.** If any subtask moves data, ONE file, `drawbridge-api/scripts/migration-<id>.js`, exporting `({ controller }) => async ({ dryRun, organization, ...args }) => counts`, holding every data step. It uses the controller only; a dry run refuses controller writes but cannot refuse a native `collection.drop()` or `dropIndex()`, so a step that drops anything branches on `dryRun` itself and reports what it would drop. Anything a person must do around the run (export a collection and read it back before a drop, take a snapshot) is written into the release doc's Migrations section as the developer's steps, not into the file. Prove it on Local: `npm run migrate -- <id> --dry --env local`, then `--env local`. The runner prints the counts and writes `runs/<env>-<id>-<time>.json`; nothing copies them anywhere.
|
|
53
60
|
5. **Walk every touched app on Local**: `npm run walk -- --local <path or url>` from drawbridge-tests (dashboard at `localhost:3000`; sync must be running for anything live). Record what was seen; a label quoted later comes from this walk.
|
|
@@ -58,16 +65,18 @@ All work on the release branches against the **Local** database. Develop and its
|
|
|
58
65
|
|
|
59
66
|
## Line 5: Review
|
|
60
67
|
|
|
68
|
+
Only when every repo's release branch is finished: every subtask committed, `npm test` green in each repo, every QA report at `fails : 0`, both review passes done, the branches pushed. A pull request is the request to review, so it exists only once there is something to review.
|
|
69
|
+
|
|
61
70
|
1. One pull request per repo: `gh pr create --base develop --head release/<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`.
|
|
62
71
|
2. Fill the Branches table in `releases/<id>.md` with the PR links; commit.
|
|
63
|
-
3.
|
|
72
|
+
3. 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`), assign the task to the developer, move it to Review (`POST /sections/1219762451512990/addTask`).
|
|
64
73
|
4. Stop. Report the PR links.
|
|
65
74
|
|
|
66
75
|
## Lines 6 to 8: the developer and the QA person
|
|
67
76
|
|
|
68
77
|
The skill does none of this. For the record, so the skill can answer "what is the next step":
|
|
69
78
|
|
|
70
|
-
- Line 6, the developer: reviews the diff, runs the branch locally, approves and merges. 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`. Then moves the task to QA and assigns the QA person in Asana.
|
|
79
|
+
- Line 6, the developer: reviews the diff, runs the branch locally, approves and merges. 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.
|
|
71
80
|
- Line 7, the QA person: tests on dev. The band already names the release.
|
|
72
81
|
- Line 8, the QA person: moves the task to Approved.
|
|
73
82
|
|
|
@@ -77,23 +86,23 @@ The skill does none of this. For the record, so the skill can answer "what is th
|
|
|
77
86
|
|
|
78
87
|
1. Confirm the task sits in Approved; refuse otherwise.
|
|
79
88
|
2. Hand over the preparation: the window (avoid 00:00 and 02:00 UTC, sync's scheduled jobs) and the Atlas snapshot. Wait.
|
|
80
|
-
3. Per app, in this order unless the release doc says otherwise (dashboard, api, sync, share, webhooks, website): hand over the fast-forward of `main` to the exact tested `develop` commit (`git push origin <sha>:refs/heads/main`), then the Deploy click in DigitalOcean (prod apps deploy from `main` with deploy-on-push off). Verify by reading: the deployment ACTIVE, `/health` reporting the commit. Note the commit in the release doc's Promote section.
|
|
81
|
-
4. Migration, after the api is ACTIVE and before sync: from the developer's api checkout on `main` at the promoted commit, with the api's two-line `.env.production` beside `migrate.js`: `npm run migrate -- <id> --env prod --dry --sha <promoted api commit>`, read the result, then without `--dry`. (The deployed api container can run it too,
|
|
89
|
+
3. Per app, in this order unless the release doc says otherwise (dashboard, api, sync, share, webhooks, website): hand over the fast-forward of `main` to the exact tested `develop` commit (`git push origin <sha>:refs/heads/main`), then the Deploy click in DigitalOcean (prod apps deploy from `main` with deploy-on-push off). Verify by reading: the deployment ACTIVE through the DigitalOcean MCP server registered in Claude Code (`apps-get-deployment-status`; the release doctor checks it is there, and no machine needs doctl), `/health` reporting the commit. Note the commit in the release doc's Promote section.
|
|
90
|
+
4. Migration, after the api is ACTIVE and before sync: from the developer's api checkout on `main` at the promoted commit, with the api's two-line `.env.production` beside `migrate.js`: `npm run migrate -- <id> --env prod --dry --sha <promoted api commit>`, read the result, then without `--dry`. (The deployed api container can run it too, from the api component's Console in the DigitalOcean dashboard.) Then `git rm drawbridge-api/scripts/migration-<id>.js`, committed on develop, and `main` fast-forwarded once more.
|
|
82
91
|
5. Hand over the verification list from the release doc's Promote section; the developer reports what they saw; the skill fills it in.
|
|
83
92
|
|
|
84
93
|
## Line 10: Released
|
|
85
94
|
|
|
86
|
-
1. **What's new.**
|
|
95
|
+
1. **What's new.** Create the "Marketing: What's new" subtask on the release task now (`POST /tasks/<id>/subtasks`), with the marketing breakdown as its description, in the customer's words, one item per subtask a merchant would notice (`drawbridge-asana-comment` for the shape, `humanizer` for the words), and complete it in the same step (`PUT /tasks/<subtask> { completed : true }`). It exists only finished; a release never carries an open marketing subtask.
|
|
87
96
|
2. Complete every subtask (Application → Complete). Move the release task to Released (`POST /sections/1219601137919036/addTask`).
|
|
88
97
|
3. Remove the worktrees; refresh the knowledge graph (`drawbridge-ship-feature` step 5). Report the promoted commits.
|
|
89
98
|
|
|
90
99
|
## What the skill never does
|
|
91
100
|
|
|
92
|
-
- Push `main`, deploy production, write to the production
|
|
101
|
+
- Push `main`, deploy production, write to the production database, publish a package, merge a pull request, assign the QA person or move the task to QA or Approved. Those are a developer's or a tester's lines. On dev the skill writes nothing of its own either (the refresh only READS dev to copy it into Local); the dev migration runs on a developer's explicit instruction, by the developer or by an agent they ask.
|
|
93
102
|
- Name a reviewer or a release scope that nobody told it; both are asked in line 2. The tester is the developer's choice at line 6 and the window is the developer's at line 9; the skill asks for neither.
|
|
94
103
|
- Write a release document, branch or migration under any name but the Asana id.
|
|
95
104
|
- Build or run the QA agent against dev or develop before the pull requests merge. Local only.
|
|
96
105
|
|
|
97
106
|
## Section ids
|
|
98
107
|
|
|
99
|
-
Releases project `1219601041167594` (workspace `15457086713130`): Planned `1219601041167595`, Building `1219601033297172`, Review `1219762451512990`, QA `1219601033155111`, Approved `1219601033299066`, Released `1219601137919036`. Application project
|
|
108
|
+
Releases project `1219601041167594` (workspace `15457086713130`): Planned `1219601041167595`, Building `1219601033297172`, Review `1219762451512990`, QA `1219601033155111`, Approved `1219601033299066`, Released `1219601137919036`. Application project `1207845659550631`, where the subtasks live: Queue `1207845659550632` (not scheduled), In progress `1211315620607633` (in a release), Complete `1213887707425532`.
|
package/bin/bump.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Bump one @drawbridge package to an exact version in EVERY consumer in the family,
|
|
3
|
+
// lockfile included, one commit per repo on its current branch, pushed.
|
|
4
|
+
//
|
|
5
|
+
// npx drawbridge-agents-bump @drawbridge/components 0.1.6
|
|
6
|
+
// npx drawbridge-agents-bump @drawbridge/drawbridge-agents 0.1.80 --no-push
|
|
7
|
+
// npx drawbridge-agents-bump @drawbridge/drawbridge-utils 0.0.222 --only drawbridge-api,drawbridge-sync
|
|
8
|
+
// … --dry list the consumers and stop
|
|
9
|
+
//
|
|
10
|
+
// Pins are version-exact family-wide (conventions/drawbridge-packages.md), so a
|
|
11
|
+
// publish is not finished until every consumer names it: the release skill runs
|
|
12
|
+
// this the moment the registry has the version (Darren, 2026-10-22: "Once a
|
|
13
|
+
// package is published, all consumer pins should also be updated"). A consumer's
|
|
14
|
+
// own place for the package (dependencies, devDependencies, peerDependencies) is
|
|
15
|
+
// kept. Bumping the agents package also mirrors its template into .claude.
|
|
16
|
+
const { execFileSync } = require('child_process')
|
|
17
|
+
const fs = require('fs')
|
|
18
|
+
const path = require('path')
|
|
19
|
+
|
|
20
|
+
const FLAGS = { dependencies: '--save', devDependencies: '--save-dev', peerDependencies: '--save-peer' }
|
|
21
|
+
|
|
22
|
+
// Which repos pin `name`, and under which field. `manifests` is { repoName: packageJson }.
|
|
23
|
+
const consumersOf = (manifests, name) => Object.entries(manifests)
|
|
24
|
+
.map(([ repo, pkg ]) => {
|
|
25
|
+
const field = Object.keys(FLAGS).find((key) => pkg && pkg[key] && pkg[key][name])
|
|
26
|
+
return field ? { repo, field, current: pkg[field][name] } : null
|
|
27
|
+
})
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.sort((a, b) => a.repo.localeCompare(b.repo))
|
|
30
|
+
|
|
31
|
+
const saveFlag = (field) => FLAGS[field]
|
|
32
|
+
|
|
33
|
+
// A pin lands on an integration branch or the release's own branch, never on a
|
|
34
|
+
// stray feature branch someone left checked out (growth-b sat on ship-main).
|
|
35
|
+
const takesBump = (branch) => branch === 'develop' || branch === 'main' || branch.startsWith('release/')
|
|
36
|
+
|
|
37
|
+
const readManifests = (projectsDir) => Object.fromEntries(
|
|
38
|
+
fs.readdirSync(projectsDir)
|
|
39
|
+
.filter((entry) => entry.startsWith('drawbridge-') && fs.existsSync(path.join(projectsDir, entry, 'package.json')) && fs.existsSync(path.join(projectsDir, entry, '.git')))
|
|
40
|
+
.map((entry) => {
|
|
41
|
+
try { return [ entry, JSON.parse(fs.readFileSync(path.join(projectsDir, entry, 'package.json'), 'utf8')) ] } catch (error) { return [ entry, null ] }
|
|
42
|
+
})
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const run = (cwd, cmd, args) => execFileSync(cmd, args, { cwd, encoding: 'utf8', stdio: [ 'ignore', 'pipe', 'pipe' ] }).trim()
|
|
46
|
+
|
|
47
|
+
const main = () => {
|
|
48
|
+
const argv = process.argv.slice(2)
|
|
49
|
+
const [ name, version ] = argv
|
|
50
|
+
const dry = argv.includes('--dry')
|
|
51
|
+
const push = !argv.includes('--no-push')
|
|
52
|
+
const onlyIndex = argv.indexOf('--only')
|
|
53
|
+
const only = onlyIndex === -1 ? null : new Set(argv[onlyIndex + 1].split(','))
|
|
54
|
+
|
|
55
|
+
if (!name || !version || !/^\d+\.\d+\.\d+/.test(version)) {
|
|
56
|
+
console.error('usage: drawbridge-agents-bump <@drawbridge/package> <exact version> [--dry] [--no-push] [--only repo,repo]')
|
|
57
|
+
process.exit(1)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const projectsDir = path.resolve(process.cwd(), '..')
|
|
61
|
+
const consumers = consumersOf(readManifests(projectsDir), name).filter((c) => !only || only.has(c.repo))
|
|
62
|
+
|
|
63
|
+
if (!consumers.length) {
|
|
64
|
+
console.error('no consumer of ' + name + ' under ' + projectsDir)
|
|
65
|
+
process.exit(1)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const { repo, field, current } of consumers) {
|
|
69
|
+
const dir = path.join(projectsDir, repo)
|
|
70
|
+
const branch = run(dir, 'git', [ 'branch', '--show-current' ])
|
|
71
|
+
if (current === version) { console.log(repo + ': already ' + version + ' (' + field + ', ' + branch + ')'); continue }
|
|
72
|
+
if (!takesBump(branch)) { console.log(repo + ': skipped, on ' + (branch || 'a detached head') + ' (check out develop, main or release/<id> first)'); continue }
|
|
73
|
+
if (dry) { console.log(repo + ': ' + current + ' -> ' + version + ' (' + field + ', ' + branch + ')'); continue }
|
|
74
|
+
run(dir, 'npm', [ 'install', '--save-exact', saveFlag(field), name + '@' + version, '--no-audit', '--no-fund', '--loglevel=error' ])
|
|
75
|
+
const files = [ 'package.json', 'package-lock.json' ]
|
|
76
|
+
if (name === '@drawbridge/drawbridge-agents') {
|
|
77
|
+
try { run(dir, 'npx', [ 'drawbridge-agents-sync' ]) } catch (error) { /* the mirror is best-effort; the pin still lands */ }
|
|
78
|
+
files.push('.claude')
|
|
79
|
+
}
|
|
80
|
+
run(dir, 'git', [ 'add', '-A', ...files.filter((f) => fs.existsSync(path.join(dir, f))) ])
|
|
81
|
+
if (!run(dir, 'git', [ 'status', '--porcelain', '--', ...files ])) { console.log(repo + ': nothing to commit'); continue }
|
|
82
|
+
run(dir, 'git', [ 'commit', '-q', '-m', name.replace('@drawbridge/', '') + ' ' + version ])
|
|
83
|
+
if (push) run(dir, 'git', [ 'push', '-q', 'origin', branch ])
|
|
84
|
+
console.log(repo + ': ' + current + ' -> ' + version + ' on ' + branch + (push ? ', pushed' : ''))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { consumersOf, readManifests, saveFlag, takesBump }
|
|
89
|
+
|
|
90
|
+
if (require.main === module) main()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Everything a release needs on this machine, checked in one go before line 1 of
|
|
3
|
+
// the release skill, printed as ONE numbered list of what is missing with the fix
|
|
4
|
+
// for each. Exit 1 when anything required is missing, so a developer on a new
|
|
5
|
+
// machine fails in ten seconds, not an hour in.
|
|
6
|
+
//
|
|
7
|
+
// npx drawbridge-agents-release-doctor
|
|
8
|
+
//
|
|
9
|
+
// Required: git, gh signed in, op on the beta channel (--environment), the api's
|
|
10
|
+
// .env.local and .env.development naming a 1Password Environment, the Local and
|
|
11
|
+
// dev clusters resolving, the npm registry reachable, the DigitalOcean MCP server
|
|
12
|
+
// registered in Claude Code. Warned, not required: graphify (the read-before-write
|
|
13
|
+
// gate skips it when absent).
|
|
14
|
+
const { execFileSync } = require('child_process')
|
|
15
|
+
const dns = require('dns')
|
|
16
|
+
const fs = require('fs')
|
|
17
|
+
const os = require('os')
|
|
18
|
+
const path = require('path')
|
|
19
|
+
|
|
20
|
+
process.env.PATH = [ path.join(os.homedir(), '.local', 'bin'), '/opt/homebrew/bin', '/usr/local/bin', process.env.PATH || '' ].join(path.delimiter)
|
|
21
|
+
|
|
22
|
+
const sh = (cmd, args) => { try { return execFileSync(cmd, args, { encoding: 'utf8', stdio: [ 'ignore', 'pipe', 'pipe' ] }) } catch (error) { return null } }
|
|
23
|
+
|
|
24
|
+
// A two-line env file: OP_SERVICE_ACCOUNT_TOKEN and OP_ENVIRONMENT_ID, both with a value.
|
|
25
|
+
const envFileOk = (text) => {
|
|
26
|
+
const keys = {}
|
|
27
|
+
for (const line of String(text || '').split('\n')) {
|
|
28
|
+
const match = line.match(/^\s*([A-Z_]+)\s*=\s*['"]?([^'"\s]*)['"]?\s*$/)
|
|
29
|
+
if (match) keys[match[1]] = match[2]
|
|
30
|
+
}
|
|
31
|
+
return Boolean(keys.OP_SERVICE_ACCOUNT_TOKEN && keys.OP_ENVIRONMENT_ID)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Deployments are read through the DigitalOcean MCP server registered in Claude Code
|
|
35
|
+
// at user scope (~/.claude.json, `mcpServers.digitalocean`), the way Darren's machine
|
|
36
|
+
// has it; every developer's machine is set up to match (Darren, 2026-10-22). doctl is
|
|
37
|
+
// not used anywhere in the flow.
|
|
38
|
+
const readClaudeConfig = () => { try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.claude.json'), 'utf8')) } catch (error) { return null } }
|
|
39
|
+
const hasDigitalOcean = (config) => Boolean(config && config.mcpServers && config.mcpServers.digitalocean && config.mcpServers.digitalocean.url)
|
|
40
|
+
|
|
41
|
+
// The printed list: one numbered line per failure, each with its fix; nothing when all pass.
|
|
42
|
+
const report = (results) => {
|
|
43
|
+
const failed = results.filter((r) => !r.ok)
|
|
44
|
+
if (!failed.length) return 'release doctor: everything present (' + results.length + ' checks).'
|
|
45
|
+
return 'release doctor: ' + failed.length + ' of ' + results.length + ' missing.\n' + failed.map((r, i) => (i + 1) + '. ' + r.name + (r.required ? '' : ' (optional)') + ': ' + r.fix).join('\n')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const checks = (projectsDir) => {
|
|
49
|
+
const api = path.join(projectsDir, 'drawbridge-api')
|
|
50
|
+
const read = (file) => { try { return fs.readFileSync(path.join(api, file), 'utf8') } catch (error) { return null } }
|
|
51
|
+
return [
|
|
52
|
+
{ name: 'git', required: true, ok: Boolean(sh('git', [ '--version' ])), fix: 'install Xcode command line tools' },
|
|
53
|
+
{ name: 'gh signed in', required: true, ok: Boolean(sh('gh', [ 'auth', 'status' ])), fix: 'brew install gh && gh auth login' },
|
|
54
|
+
{ 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' },
|
|
55
|
+
{ 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' },
|
|
56
|
+
{ name: 'drawbridge-api/.env.development', required: true, ok: envFileOk(read('.env.development')), fix: 'two lines naming the Development Environment' },
|
|
57
|
+
{ name: 'drawbridge-api/scripts/op.sh', required: true, ok: fs.existsSync(path.join(api, 'scripts', 'op.sh')), fix: 'pull develop in drawbridge-api' },
|
|
58
|
+
{ name: 'npm registry', required: true, ok: Boolean(sh('npm', [ 'view', '@drawbridge/drawbridge-agents', 'version' ])), fix: 'check the network or npm login' },
|
|
59
|
+
{ 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)' },
|
|
60
|
+
{ name: 'graphify', required: false, ok: Boolean(sh('graphify', [ '--version' ])), fix: 'npm run sync installs it; or DRAWBRIDGE_SKIP_GRAPHIFY=1' }
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const resolves = (host) => dns.promises.resolveSrv('_mongodb._tcp.' + host).then(() => true, () => false)
|
|
65
|
+
|
|
66
|
+
const main = async () => {
|
|
67
|
+
const projectsDir = path.resolve(process.cwd(), '..')
|
|
68
|
+
const results = checks(projectsDir)
|
|
69
|
+
results.push({ name: 'Local cluster resolves', required: true, ok: await resolves('cluster0.sbftndf.mongodb.net'), fix: 'the Local Atlas cluster is paused or gone; resume it in Atlas' })
|
|
70
|
+
results.push({ name: 'dev cluster resolves', required: true, ok: await resolves('cluster0.1iourxk.mongodb.net'), fix: 'check the network' })
|
|
71
|
+
console.log(report(results))
|
|
72
|
+
process.exit(results.some((r) => r.required && !r.ok) ? 1 : 0)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { checks, envFileOk, hasDigitalOcean, report }
|
|
76
|
+
|
|
77
|
+
if (require.main === module) main()
|
|
@@ -20,12 +20,14 @@ skill) so it stops living only in tribal memory.
|
|
|
20
20
|
(fails closed). Deploy order matters: ship app-web **before** api, or the api blocks every
|
|
21
21
|
signin that arrives without a token. See `drawbridge-docs/user-stories/03-auth`.
|
|
22
22
|
- `BASIC_AUTH_USERNAME` + `BASIC_AUTH_PASSWORD` is the shared basic-auth staging-gate pair read
|
|
23
|
-
by **drawbridge-website** (`
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
by **drawbridge-website** (`proxy.js`), **drawbridge-app-web** (`proxy.js`) and, since
|
|
24
|
+
2026-10-22, **drawbridge-components** (`server.js`, the components app at
|
|
25
|
+
components.trydrawbridge.com, where the pair is set directly on the DO app rather than
|
|
26
|
+
through a 1Password Environment) — same names everywhere so one credential pair gates
|
|
27
|
+
all three. The gate arms only when **both** are set (fails open: a missing/misnamed var
|
|
28
|
+
silently leaves the deployment public — unset both in prod, set both in dev). Exempt:
|
|
29
|
+
app-web `/api/health`, components `/health` (platform health checks) and website Builder
|
|
30
|
+
preview traffic. Not related to OAuth/session auth, which runs after the gate unchanged.
|
|
29
31
|
|
|
30
32
|
## Sync owns reactive side effects
|
|
31
33
|
|
|
@@ -62,11 +62,15 @@ quietly that dev served stale code the whole time.
|
|
|
62
62
|
`npm run migrate -- <release asana id> --env prod --dry --sha <promoted commit>`,
|
|
63
63
|
then the same without `--dry`. The runner refuses production off `main`, at
|
|
64
64
|
the wrong commit, or with `scripts/`, `lib/` or `migrate.js` modified. The
|
|
65
|
-
deployed api container can run it too (
|
|
66
|
-
|
|
67
|
-
migrate.js …` form; the image carries `migrate.js` and `scripts/`, there is no
|
|
65
|
+
deployed api container can run it too (the api component's Console in the
|
|
66
|
+
DigitalOcean dashboard, then the `op run --environment "$OP_ENVIRONMENT_ID" --
|
|
67
|
+
node migrate.js …` form; nobody installs doctl for this; the image carries `migrate.js` and `scripts/`, there is no
|
|
68
68
|
git, so `--sha` is proven against `APP_COMMIT_HASH`). An agent never runs it:
|
|
69
|
-
it hands the developer the command and verifies by reading afterwards.
|
|
69
|
+
it hands the developer the command and verifies by reading afterwards. The
|
|
70
|
+
dev run is different: a developer may ask an agent in their own session to run
|
|
71
|
+
the two `--env dev` commands and show the result, since the guard admits only
|
|
72
|
+
the dev cluster whoever types it (Darren, 2026-10-22). Never unasked, and
|
|
73
|
+
never production.
|
|
70
74
|
- **The runner records nothing but its counts file**, and nothing else records
|
|
71
75
|
a run either: a developer runs the migration on dev after the pull request
|
|
72
76
|
merges and reads the printed result, and runs it on production from their
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drawbridge/drawbridge-agents",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.80",
|
|
4
4
|
"description": "Shared agent-instruction content (rules, code style, conventions) for the drawbridge-* monorepo.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"drawbridge-agents-graph": "bin/graph.js",
|
|
17
17
|
"drawbridge-agents-check-docs": "scripts/check-doc-links.js",
|
|
18
18
|
"drawbridge-agents-check-stories": "scripts/check-stories.js",
|
|
19
|
-
"drawbridge-agents-check-streams": "scripts/check-streams.js"
|
|
19
|
+
"drawbridge-agents-check-streams": "scripts/check-streams.js",
|
|
20
|
+
"drawbridge-agents-bump": "bin/bump.js",
|
|
21
|
+
"drawbridge-agents-release-doctor": "bin/release-doctor.js"
|
|
20
22
|
},
|
|
21
23
|
"files": [
|
|
22
24
|
"claude",
|