repo-agent-brief 0.2.1 → 0.4.0
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/README.md +36 -0
- package/package.json +1 -1
- package/skills/repo-agent-brief/SKILL.md +9 -0
- package/src/cli.js +15 -4
- package/src/index.js +104 -2
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ npx repo-agent-brief
|
|
|
15
15
|
- Finds high-signal files: `AGENTS.md`, `CLAUDE.md`, `README.md`, `package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, etc.
|
|
16
16
|
- Infers stack and common commands.
|
|
17
17
|
- Builds a compact repo map.
|
|
18
|
+
- Suggests a prioritized verification plan (`must` / `should` / `optional`) from detected scripts, risks, and changed files.
|
|
18
19
|
- Optionally summarizes the current git diff so agents can start from “what changed?” instead of rereading the whole repo.
|
|
19
20
|
- Scans context files for obvious secrets and risky operational instructions.
|
|
20
21
|
- Emits Markdown for humans/agents or JSON for automation.
|
|
@@ -44,6 +45,7 @@ Options:
|
|
|
44
45
|
- `--max-file-bytes N` — max bytes to read per context file. Default: `12000`.
|
|
45
46
|
- `--no-snippets` — omit source snippets.
|
|
46
47
|
- `--diff [ref]` — include changed files, insertions/deletions, and high-impact path warnings versus a git ref. Defaults to `HEAD` when no ref is provided.
|
|
48
|
+
- `--bundle [dir]` — write a durable handoff bundle with `brief.md`, `brief.json`, and `verification.md`. Default: `.agent-brief`.
|
|
47
49
|
- `--fail-on-high-risk` — exit `2` if high-severity risk patterns are found.
|
|
48
50
|
|
|
49
51
|
Examples:
|
|
@@ -52,6 +54,7 @@ Examples:
|
|
|
52
54
|
agent-brief . > AGENT_BRIEF.md
|
|
53
55
|
agent-brief ~/dev/my-app --format json
|
|
54
56
|
agent-brief . --diff origin/main
|
|
57
|
+
agent-brief . --diff HEAD --bundle
|
|
55
58
|
agent-brief . --fail-on-high-risk
|
|
56
59
|
```
|
|
57
60
|
|
|
@@ -65,6 +68,39 @@ agent-brief . --diff origin/main > AGENT_HANDOFF.md
|
|
|
65
68
|
|
|
66
69
|
The brief adds a `Git diff` section with changed paths, line counts, and warnings for high-impact files such as GitHub Actions workflows, deploy scripts, migrations, Docker Compose files, and lockfiles. This keeps the first agent turn grounded in the actual patch instead of a vague repo overview.
|
|
67
70
|
|
|
71
|
+
## Verification plans
|
|
72
|
+
|
|
73
|
+
Every brief now includes a `Suggested verification plan` section. It turns discovered scripts plus patch context into a short checklist an agent can follow before finalizing:
|
|
74
|
+
|
|
75
|
+
```markdown
|
|
76
|
+
## Suggested verification plan
|
|
77
|
+
- [must] Run type checks for changed code paths — `npm run typecheck`
|
|
78
|
+
- [should] Run lint for fast static feedback — `npm run lint`
|
|
79
|
+
- [must] Run the primary test suite before final handoff — `npm run test`
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
If you pass `--diff`, the plan gets sharper: docs-only changes downgrade expensive checks, source changes promote tests/typechecks, and CI/deploy/infra/lockfile changes add a manual high-impact-path review. If no test/lint/build commands are found, the plan calls that gap out plainly so the agent does not pretend verification happened.
|
|
83
|
+
|
|
84
|
+
## Handoff bundles
|
|
85
|
+
|
|
86
|
+
For longer-running work, use `--bundle` to leave a stable artifact another agent or human can inspect later:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
agent-brief . --diff HEAD --bundle
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This writes:
|
|
93
|
+
|
|
94
|
+
- `.agent-brief/brief.md` — the full human-readable project brief.
|
|
95
|
+
- `.agent-brief/brief.json` — the same data for automation.
|
|
96
|
+
- `.agent-brief/verification.md` — a focused checklist of the exact checks the next agent should run or document.
|
|
97
|
+
|
|
98
|
+
Use a custom directory when you want to attach the bundle to a ticket, run log, or CI artifact:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
agent-brief . --diff origin/main --bundle artifacts/agent-brief
|
|
102
|
+
```
|
|
103
|
+
|
|
68
104
|
## Why this exists
|
|
69
105
|
|
|
70
106
|
The current agent tooling boom has plenty of orchestration, MCP servers, and observability dashboards. The missing small thing is a cheap, local preflight that gives any agent the same crisp project orientation before it spends tokens or touches files.
|
package/package.json
CHANGED
|
@@ -29,6 +29,14 @@ For machine-readable automation:
|
|
|
29
29
|
npx repo-agent-brief . --format json > agent-brief.json
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
For durable handoffs:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx repo-agent-brief . --diff HEAD --bundle
|
|
36
|
+
sed -n '1,220p' .agent-brief/brief.md
|
|
37
|
+
sed -n '1,160p' .agent-brief/verification.md
|
|
38
|
+
```
|
|
39
|
+
|
|
32
40
|
## When to use
|
|
33
41
|
|
|
34
42
|
- First pass in an unfamiliar repo.
|
|
@@ -48,6 +56,7 @@ npx repo-agent-brief . --format json > agent-brief.json
|
|
|
48
56
|
```bash
|
|
49
57
|
npx repo-agent-brief .
|
|
50
58
|
npx repo-agent-brief . --diff HEAD
|
|
59
|
+
npx repo-agent-brief . --diff HEAD --bundle
|
|
51
60
|
npx repo-agent-brief . --diff origin/main --fail-on-high-risk
|
|
52
61
|
npx repo-agent-brief . --no-snippets
|
|
53
62
|
```
|
package/src/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { generateBrief, formatJson, formatMarkdown } from './index.js';
|
|
3
|
+
import { generateBrief, formatJson, formatMarkdown, writeBundle } from './index.js';
|
|
4
4
|
|
|
5
5
|
function parseArgs(argv) {
|
|
6
|
-
const args = { path: '.', format: 'markdown', maxFileBytes: 12000, noSnippets: false, failOnHighRisk: false, diffRef: null };
|
|
6
|
+
const args = { path: '.', format: 'markdown', maxFileBytes: 12000, noSnippets: false, failOnHighRisk: false, diffRef: null, bundleDir: null };
|
|
7
7
|
for (let i = 0; i < argv.length; i++) {
|
|
8
8
|
const arg = argv[i];
|
|
9
9
|
if (arg === '--format' || arg === '-f') args.format = argv[++i];
|
|
@@ -11,6 +11,7 @@ function parseArgs(argv) {
|
|
|
11
11
|
else if (arg === '--no-snippets') args.noSnippets = true;
|
|
12
12
|
else if (arg === '--fail-on-high-risk') args.failOnHighRisk = true;
|
|
13
13
|
else if (arg === '--diff') args.diffRef = argv[i + 1] && !argv[i + 1].startsWith('-') ? argv[++i] : 'HEAD';
|
|
14
|
+
else if (arg === '--bundle') args.bundleDir = argv[i + 1] && !argv[i + 1].startsWith('-') ? argv[++i] : '.agent-brief';
|
|
14
15
|
else if (arg === '--help' || arg === '-h') args.help = true;
|
|
15
16
|
else if (!arg.startsWith('-')) args.path = arg;
|
|
16
17
|
else throw new Error(`Unknown option: ${arg}`);
|
|
@@ -29,13 +30,15 @@ Options:
|
|
|
29
30
|
--max-file-bytes N Max bytes to read per context file (default: 12000)
|
|
30
31
|
--no-snippets Omit context snippets from output
|
|
31
32
|
--diff [ref] Include changed files vs ref (default: HEAD)
|
|
33
|
+
--bundle [dir] Write brief.md, brief.json, and verification.md (default: .agent-brief)
|
|
32
34
|
--fail-on-high-risk Exit 2 if high-severity risk patterns are found
|
|
33
35
|
-h, --help Show help
|
|
34
36
|
|
|
35
37
|
Examples:
|
|
36
|
-
npx
|
|
38
|
+
npx repo-agent-brief
|
|
37
39
|
agent-brief ~/dev/my-app --format json
|
|
38
40
|
agent-brief . --diff origin/main
|
|
41
|
+
agent-brief . --diff HEAD --bundle
|
|
39
42
|
agent-brief . --fail-on-high-risk > AGENT_BRIEF.md
|
|
40
43
|
`;
|
|
41
44
|
}
|
|
@@ -52,7 +55,15 @@ try {
|
|
|
52
55
|
includeSnippets: !args.noSnippets,
|
|
53
56
|
diffRef: args.diffRef
|
|
54
57
|
});
|
|
55
|
-
|
|
58
|
+
if (args.bundleDir) {
|
|
59
|
+
const files = writeBundle(resolve(args.path), brief, args.bundleDir);
|
|
60
|
+
console.log(`Agent brief bundle written:
|
|
61
|
+
- Markdown: ${files.markdown}
|
|
62
|
+
- JSON: ${files.json}
|
|
63
|
+
- Verification: ${files.verification}`);
|
|
64
|
+
} else {
|
|
65
|
+
console.log(args.format === 'json' ? formatJson(brief) : formatMarkdown(brief));
|
|
66
|
+
}
|
|
56
67
|
if (args.failOnHighRisk && brief.risks.some(r => r.severity === 'high')) process.exit(2);
|
|
57
68
|
} catch (error) {
|
|
58
69
|
console.error(`agent-brief: ${error.message}`);
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
-
import { basename, join,
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename, join, resolve } from 'node:path';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
4
|
|
|
5
5
|
const DEFAULT_IGNORES = new Set([
|
|
@@ -44,6 +44,7 @@ export function generateBrief(root = process.cwd(), options = {}) {
|
|
|
44
44
|
const risks = scanRisks(absRoot, context.files);
|
|
45
45
|
const commands = inferCommands(absRoot, packageInfo);
|
|
46
46
|
const stack = inferStack(absRoot, packageInfo);
|
|
47
|
+
const verificationPlan = inferVerificationPlan({ commands, diff, risks });
|
|
47
48
|
const score = scoreRepo({ context, commands, risks });
|
|
48
49
|
|
|
49
50
|
return {
|
|
@@ -55,6 +56,7 @@ export function generateBrief(root = process.cwd(), options = {}) {
|
|
|
55
56
|
diff,
|
|
56
57
|
stack,
|
|
57
58
|
commands,
|
|
59
|
+
verificationPlan,
|
|
58
60
|
contextFiles: context.files,
|
|
59
61
|
tree,
|
|
60
62
|
risks,
|
|
@@ -83,6 +85,17 @@ export function formatMarkdown(brief) {
|
|
|
83
85
|
}
|
|
84
86
|
lines.push('');
|
|
85
87
|
|
|
88
|
+
lines.push('## Suggested verification plan');
|
|
89
|
+
if (brief.verificationPlan.length) {
|
|
90
|
+
for (const step of brief.verificationPlan) {
|
|
91
|
+
const command = step.command ? ` — \`${step.command}\`` : '';
|
|
92
|
+
lines.push(`- [${step.priority}] ${step.reason}${command}`);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
lines.push('- No automatic verification plan could be inferred. Add test/lint/build scripts for better agent handoffs.');
|
|
96
|
+
}
|
|
97
|
+
lines.push('');
|
|
98
|
+
|
|
86
99
|
if (brief.diff) {
|
|
87
100
|
lines.push(`## Git diff vs ${brief.diff.ref}`);
|
|
88
101
|
if (brief.diff.available) {
|
|
@@ -145,6 +158,61 @@ export function formatJson(brief) {
|
|
|
145
158
|
return JSON.stringify(brief, null, 2);
|
|
146
159
|
}
|
|
147
160
|
|
|
161
|
+
export function formatVerificationMarkdown(brief) {
|
|
162
|
+
const lines = [];
|
|
163
|
+
lines.push(`# Verification Plan: ${brief.project}`);
|
|
164
|
+
lines.push('');
|
|
165
|
+
lines.push(`Generated: ${brief.generatedAt}`);
|
|
166
|
+
if (brief.git.branch || brief.git.status) lines.push(`Git: ${brief.git.branch || 'unknown'}${brief.git.status ? ` - ${brief.git.status}` : ''}`);
|
|
167
|
+
lines.push('');
|
|
168
|
+
|
|
169
|
+
lines.push('## Checklist');
|
|
170
|
+
if (brief.verificationPlan.length) {
|
|
171
|
+
for (const step of brief.verificationPlan) {
|
|
172
|
+
const command = step.command ? `\n - Command: \`${step.command}\`` : '';
|
|
173
|
+
lines.push(`- [ ] [${step.priority}] ${step.reason}${command}`);
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
lines.push('- [ ] Do a focused manual smoke check and document what could not be verified.');
|
|
177
|
+
}
|
|
178
|
+
lines.push('');
|
|
179
|
+
|
|
180
|
+
if (brief.diff?.available) {
|
|
181
|
+
lines.push(`## Changed Files vs ${brief.diff.ref}`);
|
|
182
|
+
if (brief.diff.files.length) {
|
|
183
|
+
for (const file of brief.diff.files) {
|
|
184
|
+
lines.push(`- ${file.status} ${file.path}${file.risky ? ' (high-impact)' : ''}`);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
lines.push('- No changed files detected.');
|
|
188
|
+
}
|
|
189
|
+
lines.push('');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
lines.push('## Risk Notes');
|
|
193
|
+
if (brief.risks.length) {
|
|
194
|
+
for (const risk of brief.risks) lines.push(`- [${risk.severity}] ${risk.path}: ${risk.message}`);
|
|
195
|
+
} else {
|
|
196
|
+
lines.push('- No obvious secret/risky-instruction patterns found in scanned context files.');
|
|
197
|
+
}
|
|
198
|
+
lines.push('');
|
|
199
|
+
return lines.join('\n');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function writeBundle(root, brief, bundleDir = '.agent-brief') {
|
|
203
|
+
const outDir = resolve(root, bundleDir);
|
|
204
|
+
mkdirSync(outDir, { recursive: true });
|
|
205
|
+
const files = {
|
|
206
|
+
markdown: join(outDir, 'brief.md'),
|
|
207
|
+
json: join(outDir, 'brief.json'),
|
|
208
|
+
verification: join(outDir, 'verification.md')
|
|
209
|
+
};
|
|
210
|
+
writeFileSync(files.markdown, formatMarkdown(brief));
|
|
211
|
+
writeFileSync(files.json, `${formatJson(brief)}\n`);
|
|
212
|
+
writeFileSync(files.verification, formatVerificationMarkdown(brief));
|
|
213
|
+
return files;
|
|
214
|
+
}
|
|
215
|
+
|
|
148
216
|
function collectContext(root, opts) {
|
|
149
217
|
const files = [];
|
|
150
218
|
for (const name of CONTEXT_FILES) {
|
|
@@ -295,6 +363,40 @@ function inferCommands(root, pkg) {
|
|
|
295
363
|
return dedupe(commands, c => c.command);
|
|
296
364
|
}
|
|
297
365
|
|
|
366
|
+
function inferVerificationPlan({ commands, diff, risks }) {
|
|
367
|
+
const plan = [];
|
|
368
|
+
const byName = new Map(commands.map(command => [command.name, command]));
|
|
369
|
+
const add = (priority, reason, command) => {
|
|
370
|
+
if (command && plan.some(step => step.command === command.command)) return;
|
|
371
|
+
plan.push({ priority, reason, command: command?.command || '' });
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
if (risks.some(r => r.severity === 'high')) {
|
|
375
|
+
add('must', 'Manually inspect high-severity risk matches before sharing output or committing changes');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const changedPaths = diff?.available ? diff.files.map(file => file.path) : [];
|
|
379
|
+
const onlyDocsChanged = changedPaths.length > 0 && changedPaths.every(path => /(^|\/)(README|CHANGELOG|AGENTS|CLAUDE|GEMINI)\.md$|\.md$/i.test(path));
|
|
380
|
+
const changedPackageOrLock = changedPaths.some(path => /(^|\/)(package\.json|package-lock\.json|pnpm-lock\.yaml|yarn\.lock)$/i.test(path));
|
|
381
|
+
const changedSource = changedPaths.some(path => /(^|\/)(src|lib|app|pages|components|test|tests|spec)\//i.test(path) || /\.(?:[cm]?[jt]sx?|tsx?|py|rs|go)$/i.test(path));
|
|
382
|
+
const changedCiOrDeploy = changedPaths.some(isRiskyChangedPath);
|
|
383
|
+
|
|
384
|
+
if (changedCiOrDeploy) add('must', 'Review high-impact changed paths such as CI, deploy, infra, lockfiles, or migrations');
|
|
385
|
+
if (changedPackageOrLock) add('should', 'Inspect dependency or package metadata changes before publishing/merging');
|
|
386
|
+
|
|
387
|
+
if (byName.has('typecheck')) add(changedSource ? 'must' : 'should', 'Run type checks for changed code paths', byName.get('typecheck'));
|
|
388
|
+
if (byName.has('lint')) add(changedSource ? 'should' : 'optional', 'Run lint for fast static feedback', byName.get('lint'));
|
|
389
|
+
if (byName.has('test')) add(changedSource || !onlyDocsChanged ? 'must' : 'optional', 'Run the primary test suite before final handoff', byName.get('test'));
|
|
390
|
+
if (byName.has('build')) add(changedSource || changedPackageOrLock ? 'should' : 'optional', 'Run a production build if behavior or packaging changed', byName.get('build'));
|
|
391
|
+
|
|
392
|
+
if (!commands.some(c => ['test', 'lint', 'typecheck', 'build'].includes(c.name))) {
|
|
393
|
+
add('should', 'No test/lint/build commands were detected; do a focused manual smoke check and document the gap');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (!diff) add('optional', 'Run with --diff origin/main or --diff HEAD to tailor this plan to the current patch');
|
|
397
|
+
return plan;
|
|
398
|
+
}
|
|
399
|
+
|
|
298
400
|
function inferStack(root, pkg) {
|
|
299
401
|
const stack = [];
|
|
300
402
|
if (pkg) {
|