@gredescerber/agent-crew 0.0.0-stage → 0.6.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.
Files changed (101) hide show
  1. package/AGENTS.md +25 -0
  2. package/LICENSE +201 -0
  3. package/README.md +418 -2
  4. package/README.ru.md +421 -0
  5. package/adapters/claude/adapter.js +107 -0
  6. package/adapters/codex/adapter.js +101 -0
  7. package/bin/crew.js +60 -0
  8. package/docs/AGENT-SETUP.md +138 -0
  9. package/ecc/profile.json +16 -0
  10. package/evals/README.md +123 -0
  11. package/evals/fixtures/mini-app/README.md +10 -0
  12. package/evals/fixtures/mini-app/contracts/health.json +11 -0
  13. package/evals/fixtures/mini-app/package.json +10 -0
  14. package/evals/fixtures/mini-app/src/cart.js +14 -0
  15. package/evals/fixtures/mini-app/src/handlers.js +16 -0
  16. package/evals/fixtures/mini-app/src/pager.js +7 -0
  17. package/evals/fixtures/mini-app/test/cart.test.js +10 -0
  18. package/evals/fixtures/mini-app/test/handlers.test.js +13 -0
  19. package/evals/fixtures/mini-app/test/pager.test.js +8 -0
  20. package/evals/tasks/add-input-validation.json +15 -0
  21. package/evals/tasks/fix-off-by-one.json +14 -0
  22. package/evals/tasks/handler-per-contract.json +15 -0
  23. package/evals/tasks/judges/add-input-validation.judge.mjs +28 -0
  24. package/evals/tasks/judges/fix-off-by-one.judge.mjs +22 -0
  25. package/evals/tasks/judges/handler-per-contract.judge.mjs +18 -0
  26. package/package.json +46 -4
  27. package/roles/analyst.md +11 -0
  28. package/roles/builder.md +16 -0
  29. package/roles/implementer.md +17 -0
  30. package/roles/integrator.md +12 -0
  31. package/roles/planner.md +19 -0
  32. package/roles/reviewer.md +16 -0
  33. package/roles/scout.md +15 -0
  34. package/skills/crew-brief-analysis/SKILL.md +32 -0
  35. package/skills/crew-kickoff/SKILL.md +77 -0
  36. package/skills/crew-kickoff/task-format.md +215 -0
  37. package/skills/crew-review/SKILL.md +22 -0
  38. package/skills/crew-workflow/SKILL.md +46 -0
  39. package/src/commands/adapters.js +85 -0
  40. package/src/commands/ask.js +123 -0
  41. package/src/commands/board.js +88 -0
  42. package/src/commands/check.js +148 -0
  43. package/src/commands/commit.js +105 -0
  44. package/src/commands/compliance.js +109 -0
  45. package/src/commands/digest.js +64 -0
  46. package/src/commands/doctor.js +48 -0
  47. package/src/commands/eval.js +507 -0
  48. package/src/commands/github.js +169 -0
  49. package/src/commands/guard.js +101 -0
  50. package/src/commands/init.js +76 -0
  51. package/src/commands/kickoff.js +275 -0
  52. package/src/commands/labels.js +28 -0
  53. package/src/commands/merge-ready.js +33 -0
  54. package/src/commands/metrics.js +48 -0
  55. package/src/commands/pr.js +273 -0
  56. package/src/commands/shots.js +51 -0
  57. package/src/commands/status.js +48 -0
  58. package/src/commands/task.js +590 -0
  59. package/src/commands/up.js +355 -0
  60. package/src/commands/upgrade.js +178 -0
  61. package/src/commands/worker.js +322 -0
  62. package/src/core/board.js +127 -0
  63. package/src/core/config.js +83 -0
  64. package/src/core/digest.js +67 -0
  65. package/src/core/exec.js +50 -0
  66. package/src/core/guard.js +273 -0
  67. package/src/core/issues.js +154 -0
  68. package/src/core/lease.js +48 -0
  69. package/src/core/metrics.js +232 -0
  70. package/src/core/output.js +56 -0
  71. package/src/core/prd.js +612 -0
  72. package/src/core/proc.js +52 -0
  73. package/src/core/questions.js +51 -0
  74. package/src/core/queue.js +94 -0
  75. package/src/core/registry.js +143 -0
  76. package/src/core/session.js +61 -0
  77. package/src/core/shots.js +68 -0
  78. package/src/core/supervisor.js +56 -0
  79. package/src/core/version.js +44 -0
  80. package/src/mcp/server.js +81 -0
  81. package/templates/labels.json +23 -0
  82. package/templates/mcp.optional.json +23 -0
  83. package/templates/product/.github/ISSUE_TEMPLATE/epic.yml +22 -0
  84. package/templates/product/.github/ISSUE_TEMPLATE/feedback.yml +36 -0
  85. package/templates/product/.github/ISSUE_TEMPLATE/task.yml +62 -0
  86. package/templates/product/.github/pull_request_template.md +16 -0
  87. package/templates/product/.github/workflows/crew-guard.yml +45 -0
  88. package/templates/product/.github/workflows/crew.yml +118 -0
  89. package/templates/product/AGENTS.md +109 -0
  90. package/templates/product/contracts/README.md +8 -0
  91. package/templates/product/docs/architecture.md +41 -0
  92. package/templates/product/docs/brief-analysis.md +62 -0
  93. package/templates/product/docs/brief.md +25 -0
  94. package/templates/product/docs/playbooks/issues.md +91 -0
  95. package/templates/product/docs/playbooks/preflight.md +28 -0
  96. package/templates/product/docs/playbooks/timeline.md +52 -0
  97. package/templates/product/docs/prd.md +58 -0
  98. package/templates/product/events/event.example.json +10 -0
  99. package/templates/product/tests/acceptance/README.md +9 -0
  100. package/templates/stacks/web-fastapi.json +74 -0
  101. package/templates/stacks/web-node.json +70 -0
@@ -0,0 +1,109 @@
1
+ // crew compliance check | disclosure — compliance with the event rules (event mode).
2
+ // Event rules live in events/<name>.json (start, end, ai_allowed, team_members, ...).
3
+ import { writeFileSync, readFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { ok, warn, fail } from '../core/output.js';
6
+ import { git, run } from '../core/exec.js';
7
+ import { loadConfig, loadEvent, HARNESS_ROOT } from '../core/config.js';
8
+
9
+ /** Commits with changes, excluding service merge commits: author date, author, Agent trailer. */
10
+ export function commits(root) {
11
+ const out = git(['log', '--no-merges', '--format=%H%x1f%aI%x1f%ae%x1f%(trailers:key=Agent,valueonly,separator=%x2c)%x1e'], { cwd: root, allowFail: true });
12
+ if (out.code !== 0) return [];
13
+ return out.stdout.split('\x1e').map((r) => r.trim()).filter(Boolean).map((r) => {
14
+ const [sha, date, email, agent] = r.split('\x1f');
15
+ return { sha, date, email, agent: (agent ?? '').trim() || null };
16
+ });
17
+ }
18
+
19
+ /** Pure check function — the tests run it too. */
20
+ export function evaluate(event, list, prAuthors = []) {
21
+ const problems = [];
22
+ const warnings = [];
23
+ const start = Date.parse(event.start);
24
+ const end = Date.parse(event.end);
25
+ const early = list.filter((c) => Date.parse(c.date) < start);
26
+ const late = list.filter((c) => Date.parse(c.date) > end);
27
+ if (early.length) problems.push(`commits before the start: ${early.length} (first ${early.at(-1).sha.slice(0, 7)})`);
28
+ if (late.length) problems.push(`commits after the end: ${late.length}`);
29
+ const agentCommits = list.filter((c) => c.agent && c.agent !== 'human');
30
+ if (event.ai_allowed === false && agentCommits.length) problems.push(`AI is forbidden, but agent commits: ${agentCommits.length}`);
31
+ const team = new Set((event.team_members ?? []).map((s) => s.toLowerCase()));
32
+ if (team.size) {
33
+ const outsiders = [...new Set(prAuthors.filter((a) => !team.has(a.toLowerCase())))];
34
+ if (outsiders.length) problems.push(`PRs from humans outside the team: ${outsiders.join(', ')}`);
35
+ } else {
36
+ warnings.push('team_members is empty — team membership is not checked');
37
+ }
38
+ const noTrailer = list.filter((c) => !c.agent && Date.parse(c.date) >= start);
39
+ if (noTrailer.length) problems.push(`commits without the Agent trailer: ${noTrailer.length} (use crew commit)`);
40
+ return { problems, warnings, stats: { commits: list.length, agent_commits: agentCommits.length } };
41
+ }
42
+
43
+ const check = {
44
+ name: 'compliance check',
45
+ summary: 'Event mode: check the repo against the event rules (time window, team membership, AI use)',
46
+ usesCwd: true,
47
+ args: [],
48
+ run({ cwd } = {}) {
49
+ const { config, root } = loadConfig(cwd);
50
+ const event = loadEvent(config, root);
51
+ if (!event) {
52
+ return warn('No event configured — nothing to check', {
53
+ next_actions: ['fill in events/event.json and set "event": "events/event.json" in crew.config.json'],
54
+ });
55
+ }
56
+ const pr = run('gh', ['pr', 'list', '--state', 'all', '--limit', '500', '--json', 'author'], { cwd: root, allowFail: true });
57
+ const authors = pr.code === 0 ? JSON.parse(pr.stdout || '[]').map((p) => p.author?.login).filter(Boolean) : [];
58
+ const res = evaluate(event, commits(root), authors);
59
+ if (res.problems.length) {
60
+ return fail('compliance_violation', `Violations: ${res.problems.length}`, {
61
+ cause_hint: res.problems.join('; '),
62
+ stop_when: 'always — a human decides this; agents do not rewrite history',
63
+ next_actions: ['tell the team captain', 'do not rewrite git history'],
64
+ artifacts: res,
65
+ });
66
+ }
67
+ return (res.warnings.length ? warn : ok)(`Compliance with the rules of "${event.name}": OK`, { artifacts: res });
68
+ },
69
+ };
70
+
71
+ const disclosure = {
72
+ name: 'compliance disclosure',
73
+ summary: 'Generate DISCLOSURE.md: which tools and agents were used',
74
+ agent: false,
75
+ args: [],
76
+ run() {
77
+ const { config, root } = loadConfig();
78
+ const event = loadEvent(config, root) ?? {};
79
+ const list = commits(root);
80
+ const byAgent = {};
81
+ for (const c of list) byAgent[c.agent ?? 'unknown'] = (byAgent[c.agent ?? 'unknown'] ?? 0) + 1;
82
+ const version = JSON.parse(readFileSync(join(HARNESS_ROOT, 'package.json'), 'utf8')).version;
83
+ const md = [
84
+ '# Disclosure',
85
+ '',
86
+ `Event: **${event.name ?? '—'}** (${event.start ?? '?'} — ${event.end ?? '?'})`,
87
+ '',
88
+ '## Tools',
89
+ `- [agent-crew](${config.harness_url ?? 'https://github.com/<org>/agent-crew'}) v${version} — an open-source tool for coordinating agents (rules, templates, CLI). Contains no product code.`,
90
+ `- AI agents: ${config.hosts.join(', ')}${config.ecc?.enabled !== false ? '; ECC plugin for Claude Code' : ''}.`,
91
+ `- Stack generators: ${(config.scaffold ?? []).map((s) => '`' + s + '`').join(', ') || '—'}`,
92
+ '',
93
+ '## What was done before the event',
94
+ '- Only tooling and public dependencies. All product code was written during the event.',
95
+ '',
96
+ '## Commit provenance',
97
+ '| Agent / author | Commits |',
98
+ '|---|---|',
99
+ ...Object.entries(byAgent).map(([a, n]) => `| ${a} | ${n} |`),
100
+ '',
101
+ 'Every agent commit carries an `Agent:` trailer and a link to the task (`Issue: #N`).',
102
+ '',
103
+ ].join('\n');
104
+ writeFileSync(join(root, 'DISCLOSURE.md'), md);
105
+ return ok('DISCLOSURE.md created', { artifacts: { path: join(root, 'DISCLOSURE.md'), by_agent: byAgent } });
106
+ },
107
+ };
108
+
109
+ export default [check, disclosure];
@@ -0,0 +1,64 @@
1
+ // crew digest [--post] — team status without an LLM: what waits for a human, stale leases, PRs, open questions,
2
+ // idle members. --post keeps it in one pinned issue (label crew:digest); the sweep job runs it every 15 minutes,
3
+ // so nobody has to keep an agent session polling the repo.
4
+ import { existsSync, readFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { ok, fail } from '../core/output.js';
7
+ import { gh, ghJson } from '../core/exec.js';
8
+ import { loadConfig } from '../core/config.js';
9
+ import { listIssues } from '../core/issues.js';
10
+ import { DEFAULT_PRD, parsePrd } from '../core/prd.js';
11
+ import { QUESTION_LABEL } from '../core/questions.js';
12
+ import { digestModel, renderDigest, DIGEST_LABEL, DIGEST_TITLE } from '../core/digest.js';
13
+
14
+ const PR_FIELDS = 'number,title,body,headRefOid,createdAt,isDraft,author,labels,comments,statusCheckRollup,mergeable,isCrossRepository,autoMergeRequest';
15
+
16
+ /** Live data through gh (tests pass deps.data instead). */
17
+ export function githubData(root) {
18
+ const p = join(root, DEFAULT_PRD);
19
+ return {
20
+ issues: listIssues({ limit: 500 }),
21
+ prs: ghJson(['pr', 'list', '--state', 'open', '--limit', '50', '--json', PR_FIELDS], { cwd: root, allowFail: true }) ?? [],
22
+ questions: ghJson(['issue', 'list', '--state', 'open', '--label', QUESTION_LABEL, '--limit', '100', '--json', 'number,title,body,labels,assignees,createdAt'], { cwd: root, allowFail: true }) ?? [],
23
+ team: existsSync(p) ? parsePrd(readFileSync(p, 'utf8')).team : null,
24
+ };
25
+ }
26
+
27
+ /** Create the pinned digest issue once, then edit its body. -> { issue, created } | { error } */
28
+ export function postDigest(body, { root = '.', gh: ghf = gh, ghJson: ghj = ghJson } = {}) {
29
+ const found = ghj(['issue', 'list', '--state', 'open', '--label', DIGEST_LABEL, '--limit', '1', '--json', 'number'], { cwd: root, allowFail: true }) ?? [];
30
+ if (found[0]?.number) {
31
+ const r = ghf(['issue', 'edit', String(found[0].number), '--body-file', '-'], { cwd: root, allowFail: true, input: body });
32
+ return r.code === 0 ? { issue: found[0].number, created: false } : { error: (r.stderr || r.stdout || '').split('\n')[0] || 'issue edit failed' };
33
+ }
34
+ const r = ghf(['issue', 'create', '--title', DIGEST_TITLE, '--label', DIGEST_LABEL, '--body-file', '-'], { cwd: root, allowFail: true, input: body });
35
+ const number = Number(/\/issues\/(\d+)/.exec(r.stdout ?? '')?.[1]);
36
+ if (r.code !== 0 || !number) return { error: (r.stderr || r.stdout || '').split('\n')[0] || 'issue create failed' };
37
+ ghf(['issue', 'pin', String(number)], { cwd: root, allowFail: true });
38
+ return { issue: number, created: true };
39
+ }
40
+
41
+ export default {
42
+ name: 'digest',
43
+ summary: 'Team status without an LLM: needs-a-human, stale leases, PRs, open questions, idle members; --post updates the pinned issue',
44
+ args: [{ name: 'post', type: 'boolean', description: 'create or update the pinned "crew digest" issue (the sweep job does this)' }],
45
+ run({ post = false } = {}, deps = {}) {
46
+ const { config, root } = deps.config ? { config: deps.config, root: '.' } : loadConfig();
47
+ const data = deps.data ?? githubData(root);
48
+ const now = deps.now ?? Date.now();
49
+ const model = digestModel({ ...data, config, now });
50
+ const markdown = renderDigest(model, { now });
51
+ let posted = null;
52
+ if (post) {
53
+ posted = postDigest(markdown, { root, ...(deps.gh ? { gh: deps.gh } : {}), ...(deps.ghJson ? { ghJson: deps.ghJson } : {}) });
54
+ if (posted.error) {
55
+ return fail('gh_failed', 'Could not update the digest issue', { cause_hint: posted.error, retry: 'crew labels sync (the crew:digest label may be missing), then crew digest --post' });
56
+ }
57
+ }
58
+ const c = model.counts;
59
+ return ok(`${c.human} need a human, ${c.stale} stale, ${c.prs} PRs waiting, ${c.idle} idle${posted ? `; digest #${posted.issue}${posted.created ? ' created and pinned' : ' updated'}` : ''}`, {
60
+ status: c.human || c.stale ? 'warning' : 'success',
61
+ artifacts: { counts: c, sections: model.sections, markdown, issue: posted?.issue ?? null },
62
+ });
63
+ },
64
+ };
@@ -0,0 +1,48 @@
1
+ // crew doctor — check that the machine is ready to work.
2
+ import { ok, fail } from '../core/output.js';
3
+ import { run } from '../core/exec.js';
4
+ import { loadConfig, agentId } from '../core/config.js';
5
+ import { LOCAL_VERSION, updateHint } from '../core/version.js';
6
+
7
+ const probe = (cmd, args) => {
8
+ const r = run(cmd, args, { allowFail: true });
9
+ return r.code === 0 ? (r.stdout || r.stderr).split('\n')[0] : null;
10
+ };
11
+
12
+ export default {
13
+ name: 'doctor',
14
+ summary: 'Check the environment: node, git, gh (auth), claude, codex, project config',
15
+ args: [],
16
+ run() {
17
+ const { config, path } = loadConfig();
18
+ const winCmd = process.platform === 'win32';
19
+ const tool = (name, args) => probe(name, args) ?? (winCmd ? probe('cmd', ['/c', name, ...args]) : null);
20
+ const items = {
21
+ node: process.version,
22
+ git: tool('git', ['--version']),
23
+ gh: tool('gh', ['--version']),
24
+ gh_auth: run('gh', ['auth', 'status'], { allowFail: true }).code === 0 ? 'ok' : null,
25
+ config: path ?? null,
26
+ agent_id: agentId(),
27
+ };
28
+ for (const h of config.hosts ?? []) items[h] = tool(h, ['--version']);
29
+ const required = ['git', 'gh', 'gh_auth'];
30
+ const missing = required.filter((k) => !items[k]);
31
+ const hostsMissing = (config.hosts ?? []).filter((h) => !items[h]);
32
+ if (missing.length) {
33
+ return fail('env_missing', `Missing: ${missing.join(', ')}`, {
34
+ cause_hint: missing.includes('gh_auth') ? 'gh is not authenticated' : 'tool is not installed',
35
+ retry: missing.includes('gh_auth') ? 'gh auth login' : 'install it and re-run crew doctor',
36
+ artifacts: items,
37
+ });
38
+ }
39
+ items.crew = LOCAL_VERSION;
40
+ const hint = updateHint();
41
+ const notes = [hostsMissing.length ? `agents missing: ${hostsMissing.join(', ')}` : null, hint ? `crew is outdated (${LOCAL_VERSION})` : null].filter(Boolean);
42
+ return ok(notes.length ? `Environment OK, but ${notes.join('; ')}` : 'Environment ready', {
43
+ status: notes.length ? 'warning' : 'success',
44
+ artifacts: items,
45
+ next_actions: [...(hint ? [hint] : []), ...(path ? ['crew status'] : ['crew init <dir> — create a project'])],
46
+ });
47
+ },
48
+ };