@bugb-technologies/bravos 0.0.0-stage → 1.0.1
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.
- package/LICENSE +70 -0
- package/README.md +225 -2
- package/dist/account.js +140 -0
- package/dist/artifacts.js +450 -0
- package/dist/ci.js +52 -0
- package/dist/cli.js +963 -0
- package/dist/codegraph.js +268 -0
- package/dist/cursor.js +70 -0
- package/dist/detect.js +26 -0
- package/dist/doctor.js +99 -0
- package/dist/global.js +95 -0
- package/dist/graphbin.js +136 -0
- package/dist/guardlink.js +123 -0
- package/dist/guide.js +255 -0
- package/dist/help.js +334 -0
- package/dist/hooks.js +167 -0
- package/dist/install.js +209 -0
- package/dist/licence.js +41 -0
- package/dist/lock.js +50 -0
- package/dist/mcp.js +139 -0
- package/dist/migrate.js +275 -0
- package/dist/omp.js +46 -0
- package/dist/opencode.js +118 -0
- package/dist/paths.js +33 -0
- package/dist/plugin.js +101 -0
- package/dist/report/check.js +252 -0
- package/dist/report/data.js +401 -0
- package/dist/report/export.js +105 -0
- package/dist/report/markdown.js +310 -0
- package/dist/report/meta.js +48 -0
- package/dist/report/pdf.js +96 -0
- package/dist/review-server.js +7 -0
- package/dist/review.js +274 -0
- package/dist/setup.js +133 -0
- package/dist/skills.js +81 -0
- package/dist/types.js +9 -0
- package/dist/uninstall.js +192 -0
- package/dist/versions.js +8 -0
- package/dist/watch-frame.js +593 -0
- package/dist/watch.js +873 -0
- package/dist/workspace.js +316 -0
- package/docs/GUIDE.md +323 -0
- package/hooks/autopilot.mjs +45 -0
- package/hooks/lib/autopilot.mjs +191 -0
- package/hooks/lib/briefing.mjs +750 -0
- package/hooks/lib/licence.mjs +452 -0
- package/hooks/lib/pre-pr.mjs +209 -0
- package/hooks/lib/sast.mjs +207 -0
- package/hooks/lib/setup.mjs +107 -0
- package/hooks/lib/workspace.mjs +78 -0
- package/hooks/pre-pr.mjs +54 -0
- package/hooks/session-start.mjs +82 -0
- package/package.json +49 -4
- package/skills/bravos/SKILL.md +63 -0
- package/skills/bravos-annotate/SKILL.md +56 -0
- package/skills/bravos-annotate/scripts/lib/pick.mjs +127 -0
- package/skills/bravos-annotate/scripts/slice.mjs +141 -0
- package/skills/bravos-campaign/SKILL.md +49 -0
- package/skills/bravos-cicd/SKILL.md +48 -0
- package/skills/bravos-codegraph/SKILL.md +57 -0
- package/skills/bravos-context/SKILL.md +51 -0
- package/skills/bravos-context/intent.example.md +48 -0
- package/skills/bravos-env/SKILL.md +48 -0
- package/skills/bravos-env/scope.example.yaml +50 -0
- package/skills/bravos-prove/SKILL.md +52 -0
- package/skills/bravos-recheck/SKILL.md +47 -0
- package/skills/bravos-report/SKILL.md +53 -0
- package/skills/bravos-report/assessment.md +54 -0
- package/skills/bravos-report/brief.md +23 -0
- package/skills/bravos-report/finding.md +74 -0
- package/skills/bravos-report/retest.md +29 -0
- package/skills/bravos-report/style.md +44 -0
- package/skills/bravos-sast/SKILL.md +48 -0
- package/skills/bravos-schedule/SKILL.md +44 -0
- package/skills/bravos-schedule/loop.md +16 -0
- package/skills/bravos-ship/SKILL.md +58 -0
- package/skills/bravos-templatize/SKILL.md +48 -0
- package/skills/bravos-templatize/operator-guidance.md +20 -0
- package/templates/omp/bravos.js +62 -0
- package/templates/opencode/bravos.js +33 -0
- package/templates/report/report.html +88 -0
- package/templates/review/review.html +327 -0
- package/templates/workflows/bravos-nightly-claude.yml +173 -0
- package/templates/workflows/bravos-nightly-codex.yml +172 -0
- package/templates/workflows/bravos-pr-gate.yml +142 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, readlinkSync, rmdirSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { LOCK_PATH, readLock, sha256 } from './lock.js';
|
|
4
|
+
import { BravosError } from './types.js';
|
|
5
|
+
const isObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
6
|
+
// Any command bravos registers: the per-repo relative forms and the user-level absolute one.
|
|
7
|
+
const isBravosCommand = (c) => typeof c === 'string' && /\.bravos\/hooks\/(session-start|pre-pr|autopilot)\.mjs/.test(c);
|
|
8
|
+
function readJson(root, rel) {
|
|
9
|
+
const abs = join(root, rel);
|
|
10
|
+
if (!existsSync(abs))
|
|
11
|
+
return null;
|
|
12
|
+
let parsed;
|
|
13
|
+
try {
|
|
14
|
+
parsed = JSON.parse(readFileSync(abs, 'utf8'));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new BravosError(`${rel} is not valid JSON; fix it or remove it, nothing was removed`);
|
|
18
|
+
}
|
|
19
|
+
if (!isObject(parsed))
|
|
20
|
+
throw new BravosError(`${rel} is not a JSON object; fix it or remove it, nothing was removed`);
|
|
21
|
+
return parsed;
|
|
22
|
+
}
|
|
23
|
+
/** Removes bravos's commands from a Claude Code / Codex hooks file; returns the new document, or null when nothing changed. */
|
|
24
|
+
function stripHooks(doc) {
|
|
25
|
+
if (!isObject(doc.hooks))
|
|
26
|
+
return null;
|
|
27
|
+
let changed = false;
|
|
28
|
+
const hooks = {};
|
|
29
|
+
for (const [event, groups] of Object.entries(doc.hooks)) {
|
|
30
|
+
if (!Array.isArray(groups)) {
|
|
31
|
+
hooks[event] = groups;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const kept = groups
|
|
35
|
+
.map((g) => {
|
|
36
|
+
if (!isObject(g) || !Array.isArray(g.hooks))
|
|
37
|
+
return g;
|
|
38
|
+
const left = g.hooks.filter((h) => !(isObject(h) && isBravosCommand(h.command)));
|
|
39
|
+
if (left.length !== g.hooks.length)
|
|
40
|
+
changed = true;
|
|
41
|
+
return { ...g, hooks: left };
|
|
42
|
+
})
|
|
43
|
+
.filter((g) => !(isObject(g) && Array.isArray(g.hooks) && g.hooks.length === 0));
|
|
44
|
+
if (kept.length > 0)
|
|
45
|
+
hooks[event] = kept;
|
|
46
|
+
}
|
|
47
|
+
if (!changed)
|
|
48
|
+
return null;
|
|
49
|
+
const out = { ...doc, hooks };
|
|
50
|
+
if (Object.keys(hooks).length === 0)
|
|
51
|
+
delete out.hooks;
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
function writeOrDelete(root, rel, doc, dryRun) {
|
|
55
|
+
if (dryRun)
|
|
56
|
+
return;
|
|
57
|
+
const abs = join(root, rel);
|
|
58
|
+
if (Object.keys(doc).length === 0)
|
|
59
|
+
unlinkSync(abs);
|
|
60
|
+
else
|
|
61
|
+
writeFileSync(abs, JSON.stringify(doc, null, 2) + '\n');
|
|
62
|
+
}
|
|
63
|
+
/** Removes now-empty folders from `rel`'s parent up to (not including) the root. */
|
|
64
|
+
function pruneEmpty(root, rel) {
|
|
65
|
+
let dir = dirname(rel);
|
|
66
|
+
while (dir && dir !== '.' && dir !== '/') {
|
|
67
|
+
const abs = join(root, dir);
|
|
68
|
+
try {
|
|
69
|
+
if (!lstatSync(abs).isDirectory() || readdirSync(abs).length > 0)
|
|
70
|
+
return;
|
|
71
|
+
rmdirSync(abs);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
dir = dirname(dir);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// @comment -- "Removes what bravos installed under root: every file its lock records that is unchanged (edited ones are kept and listed unless --force), bravos's hook entries in .claude/settings.json and .codex/hooks.json, its group in .agents/hooks.json, its pre_pr entry in .guardlink/config.json, and .bravos/ itself; with home set, also the ~/.agents/skills/bravos* links. Never removes guardlink's own files, a reviewed scope.yaml, or MCP entries (which may predate bravos); those are listed. Hooks files are validated before anything is removed."
|
|
80
|
+
export function uninstall(root, opts = {}) {
|
|
81
|
+
const dryRun = !!opts.dryRun;
|
|
82
|
+
const report = { removed: [], kept: [], unregistered: [], notes: [] };
|
|
83
|
+
if (!existsSync(join(root, '.bravos'))) {
|
|
84
|
+
throw new BravosError(`bravos is not installed in ${root} (no .bravos/ folder); nothing was removed`);
|
|
85
|
+
}
|
|
86
|
+
// Validate every structured file first, so a malformed one stops the run before any removal.
|
|
87
|
+
const hookFiles = ['.claude/settings.json', '.codex/hooks.json'];
|
|
88
|
+
const docs = new Map(hookFiles.map((f) => [f, readJson(root, f)]));
|
|
89
|
+
const agy = opts.home ? null : readJson(root, '.agents/hooks.json');
|
|
90
|
+
const glConfig = opts.home ? null : readJson(root, '.guardlink/config.json');
|
|
91
|
+
const lock = readLock(root);
|
|
92
|
+
for (const [rel, hash] of Object.entries(lock.files).sort(([a], [b]) => a.localeCompare(b))) {
|
|
93
|
+
if (rel.startsWith('.guardlink/')) {
|
|
94
|
+
report.kept.push({ path: rel, reason: 'guardlink data (a reviewed scope is the team\'s); delete it yourself if unwanted' });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const abs = join(root, rel);
|
|
98
|
+
let st = null;
|
|
99
|
+
try {
|
|
100
|
+
st = lstatSync(abs);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (!st.isFile())
|
|
106
|
+
continue;
|
|
107
|
+
if (!opts.force && sha256(readFileSync(abs)) !== hash) {
|
|
108
|
+
report.kept.push({ path: rel, reason: 'edited since bravos wrote it (use --force to remove)' });
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
report.removed.push(rel);
|
|
112
|
+
if (!dryRun) {
|
|
113
|
+
unlinkSync(abs);
|
|
114
|
+
pruneEmpty(root, rel);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const f of hookFiles) {
|
|
118
|
+
const doc = docs.get(f);
|
|
119
|
+
if (!doc)
|
|
120
|
+
continue;
|
|
121
|
+
const next = stripHooks(doc);
|
|
122
|
+
if (!next)
|
|
123
|
+
continue;
|
|
124
|
+
report.unregistered.push(f);
|
|
125
|
+
writeOrDelete(root, f, next, dryRun);
|
|
126
|
+
}
|
|
127
|
+
if (agy && Object.hasOwn(agy, 'bravos')) {
|
|
128
|
+
const { bravos: _j, ...rest } = agy;
|
|
129
|
+
report.unregistered.push('.agents/hooks.json');
|
|
130
|
+
writeOrDelete(root, '.agents/hooks.json', rest, dryRun);
|
|
131
|
+
}
|
|
132
|
+
if (glConfig && Array.isArray(glConfig.pre_pr)) {
|
|
133
|
+
const left = glConfig.pre_pr.filter((e) => !(isObject(e) && isBravosCommand(e.command)));
|
|
134
|
+
if (left.length !== glConfig.pre_pr.length) {
|
|
135
|
+
report.unregistered.push('.guardlink/config.json (pre_pr)');
|
|
136
|
+
if (!dryRun)
|
|
137
|
+
writeFileSync(join(root, '.guardlink/config.json'), JSON.stringify({ ...glConfig, pre_pr: left }, null, 2) + '\n');
|
|
138
|
+
report.notes.push('run guardlink sync to drop the gate from the agent instruction files');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (opts.home) {
|
|
142
|
+
const dir = join(root, '.agents', 'skills');
|
|
143
|
+
let entries = [];
|
|
144
|
+
try {
|
|
145
|
+
entries = lstatSync(dir).isSymbolicLink() ? [] : readdirSync(dir);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
entries = [];
|
|
149
|
+
}
|
|
150
|
+
for (const name of entries.filter((n) => n === 'bravos' || n.startsWith('bravos-'))) {
|
|
151
|
+
const link = join(dir, name);
|
|
152
|
+
try {
|
|
153
|
+
if (lstatSync(link).isSymbolicLink() && readlinkSync(link) === `../../.claude/skills/${name}`) {
|
|
154
|
+
report.removed.push(`.agents/skills/${name} (link)`);
|
|
155
|
+
if (!dryRun)
|
|
156
|
+
unlinkSync(link);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
/* gone already */
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// @comment -- "Removes only what the install wrote under .bravos/ (the hook scripts, removed above when unchanged, and the lock), never the folder itself: .bravos/ is shared with the Python bravos CLI's committed templates and identities, and with this CLI's own reports, SAST runs, watch log and workspace snapshot, none of which an uninstall owns."
|
|
165
|
+
const keptInBravos = report.kept.some((k) => k.path.startsWith('.bravos/'));
|
|
166
|
+
if (!keptInBravos) {
|
|
167
|
+
report.removed.push(LOCK_PATH);
|
|
168
|
+
if (!dryRun) {
|
|
169
|
+
rmSync(join(root, LOCK_PATH), { force: true });
|
|
170
|
+
rmSync(join(root, '.bravos', 'hooks'), { recursive: true, force: true });
|
|
171
|
+
pruneEmpty(root, join('.bravos', 'x'));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else
|
|
175
|
+
report.notes.push('.bravos/hooks kept because a hook script in it was edited');
|
|
176
|
+
const leftover = ['reports', 'sast', 'watch', 'workspace', 'review'].filter((d) => existsSync(join(root, '.bravos', d)));
|
|
177
|
+
if (leftover.length)
|
|
178
|
+
report.notes.push(`.bravos/{${leftover.join(',')}} kept: reports, runs and snapshots are yours, not the install's`);
|
|
179
|
+
try {
|
|
180
|
+
if (!opts.home && lstatSync(join(root, '.agents', 'skills')).isSymbolicLink()) {
|
|
181
|
+
report.notes.push('.agents/skills is still a link to .claude/skills (from --link-skills); remove it if no other agent needs it');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
/* no link */
|
|
186
|
+
}
|
|
187
|
+
const mcp = opts.home ? ['.codex/config.toml'] : ['.mcp.json', '.codex/config.toml', 'opencode.json'];
|
|
188
|
+
const withMcp = mcp.filter((f) => existsSync(join(root, f)));
|
|
189
|
+
if (withMcp.length)
|
|
190
|
+
report.notes.push(`MCP entries (guardlink, cxg, codegraph) are left in ${withMcp.join(', ')}; they may predate bravos`);
|
|
191
|
+
return report;
|
|
192
|
+
}
|
package/dist/versions.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @comment -- "The versions of the two binary bundles this CLI installs. Pinned here, not read from the network: a bravos release names the analyzer and code-graph builds it was tested with, and bravos setup fetches exactly those."
|
|
2
|
+
/** The licensed analyzer bundle (bravos-cli plus gitleaks, osv-scanner, kics, zizmor and the rules), published by specter-v1's analyzer-release workflow. */
|
|
3
|
+
export const ANALYZER_VERSION = '0.6.1';
|
|
4
|
+
/** The code graph binaries (codegraph-build, codegraph-mcp), published by specter-v1's codegraph-release workflow. */
|
|
5
|
+
export const CODEGRAPH_VERSION = '0.1.0';
|
|
6
|
+
/** Pins for the two free CLIs bravos setup --tools installs. */
|
|
7
|
+
export const GUARDLINK_VERSION = '2.5.0';
|
|
8
|
+
export const CXG_VERSION = '1.7.0';
|