@gredescerber/agent-crew 0.5.0 → 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.
- package/AGENTS.md +1 -0
- package/README.md +8 -1
- package/README.ru.md +8 -1
- package/package.json +1 -1
- package/skills/crew-workflow/SKILL.md +4 -1
- package/src/commands/ask.js +123 -0
- package/src/commands/board.js +88 -0
- package/src/commands/check.js +21 -3
- package/src/commands/digest.js +64 -0
- package/src/commands/merge-ready.js +33 -0
- package/src/commands/pr.js +65 -3
- package/src/commands/task.js +22 -1
- package/src/commands/upgrade.js +178 -0
- package/src/core/board.js +127 -0
- package/src/core/config.js +6 -0
- package/src/core/digest.js +67 -0
- package/src/core/guard.js +29 -4
- package/src/core/issues.js +1 -1
- package/src/core/prd.js +23 -4
- package/src/core/questions.js +51 -0
- package/src/core/queue.js +4 -2
- package/src/core/registry.js +6 -1
- package/src/core/session.js +61 -0
- package/src/mcp/server.js +10 -0
- package/templates/labels.json +21 -16
- package/templates/product/.github/workflows/crew-guard.yml +1 -1
- package/templates/product/.github/workflows/crew.yml +13 -3
- package/templates/product/AGENTS.md +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// A unique agent name per session on one machine, without setting CREW_AGENT_ID by hand.
|
|
2
|
+
// Each Claude Code / Codex session starts its own crew MCP server; the server takes the first free slot
|
|
3
|
+
// (claude@pc, claude@pc-2, ...) with a lock file holding its PID, and frees it on exit.
|
|
4
|
+
// crew task claim pins the name into the task worktree (.crew/agent-id), so CLI calls there use it too.
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { dirname, join, resolve } from 'node:path';
|
|
8
|
+
|
|
9
|
+
export const SLOTS_DIR = join(tmpdir(), 'crew-agents');
|
|
10
|
+
export const PIN_FILE = join('.crew', 'agent-id');
|
|
11
|
+
|
|
12
|
+
const alive = (pid) => {
|
|
13
|
+
try { process.kill(pid, 0); return true; } catch (e) { return e.code === 'EPERM'; }
|
|
14
|
+
};
|
|
15
|
+
const slotName = (base, n) => (n === 1 ? base : `${base}-${n}`);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Take the first free slot for base (e.g. claude@pc). A slot is free if it has no lock file or its PID is dead.
|
|
19
|
+
* → { id, release() } or null when every slot is busy. fs and isAlive are injectable for tests.
|
|
20
|
+
*/
|
|
21
|
+
export function claimSlot(base, { dir = SLOTS_DIR, pid = process.pid, isAlive = alive, max = 20 } = {}) {
|
|
22
|
+
mkdirSync(dir, { recursive: true });
|
|
23
|
+
for (let n = 1; n <= max; n++) {
|
|
24
|
+
const id = slotName(base, n);
|
|
25
|
+
const file = join(dir, `${id.replace(/[^A-Za-z0-9@._-]/g, '_')}.lock`);
|
|
26
|
+
if (existsSync(file)) {
|
|
27
|
+
const owner = Number(readFileSync(file, 'utf8').trim());
|
|
28
|
+
if (owner && owner !== pid && isAlive(owner)) continue;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
writeFileSync(file, String(pid), { flag: existsSync(file) ? 'w' : 'wx' });
|
|
32
|
+
} catch {
|
|
33
|
+
continue; // another session took it between the check and the write
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
id,
|
|
37
|
+
release: () => {
|
|
38
|
+
try { if (Number(readFileSync(file, 'utf8').trim()) === pid) unlinkSync(file); } catch { /* already gone */ }
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The name pinned in this worktree (nearest .crew/agent-id walking up from cwd), or null. */
|
|
46
|
+
export function pinnedId(cwd = process.cwd()) {
|
|
47
|
+
let dir = resolve(cwd);
|
|
48
|
+
for (let i = 0; i < 30; i++) {
|
|
49
|
+
const f = join(dir, PIN_FILE);
|
|
50
|
+
if (existsSync(f)) return readFileSync(f, 'utf8').trim() || null;
|
|
51
|
+
const up = dirname(dir);
|
|
52
|
+
if (up === dir) return null;
|
|
53
|
+
dir = up;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function pinId(worktree, id) {
|
|
59
|
+
mkdirSync(join(worktree, '.crew'), { recursive: true });
|
|
60
|
+
writeFileSync(join(worktree, PIN_FILE), `${id}\n`);
|
|
61
|
+
}
|
package/src/mcp/server.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Minimal MCP server (stdio, JSON-RPC 2.0, one message per line).
|
|
2
2
|
// Exposes crew commands as tools: crew_task_next, crew_task_claim, crew_check, ...
|
|
3
3
|
// Connects to any MCP client: Claude Code, Codex, Cursor, etc. No dependencies.
|
|
4
|
+
import { claimSlot } from '../core/session.js';
|
|
5
|
+
import { agentId } from '../core/config.js';
|
|
4
6
|
import { createInterface } from 'node:readline';
|
|
5
7
|
import { agentCommands, toolName, inputSchema, validate } from '../core/registry.js';
|
|
6
8
|
import { CrewError, fail } from '../core/output.js';
|
|
@@ -51,6 +53,14 @@ export async function handle(msg) {
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
export function startServer() {
|
|
56
|
+
// One MCP server per agent session: take a free name slot (claude@pc, claude@pc-2, ...) unless CREW_AGENT_ID is set.
|
|
57
|
+
if (!process.env.CREW_AGENT_ID) {
|
|
58
|
+
const slot = claimSlot(agentId());
|
|
59
|
+
if (slot) {
|
|
60
|
+
process.env.CREW_AGENT_ID = slot.id;
|
|
61
|
+
for (const ev of ['exit', 'SIGINT', 'SIGTERM']) process.once(ev, () => { slot.release(); if (ev !== 'exit') process.exit(0); });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
54
64
|
const rl = createInterface({ input: process.stdin });
|
|
55
65
|
const send = (o) => process.stdout.write(JSON.stringify(o) + '\n');
|
|
56
66
|
rl.on('line', async (line) => {
|
package/templates/labels.json
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
[
|
|
2
|
-
{
|
|
3
|
-
{
|
|
4
|
-
{
|
|
5
|
-
{
|
|
6
|
-
{
|
|
7
|
-
{
|
|
8
|
-
{
|
|
9
|
-
{
|
|
10
|
-
{
|
|
11
|
-
{
|
|
12
|
-
{
|
|
13
|
-
{
|
|
14
|
-
{
|
|
15
|
-
{
|
|
16
|
-
{
|
|
17
|
-
{
|
|
2
|
+
{"name": "status:needs-spec", "color": "fbca04", "description": "no verifiable acceptance criteria - agents do not take it"},
|
|
3
|
+
{"name": "status:ready", "color": "0e8a16", "description": "ready, can be taken (crew task next)"},
|
|
4
|
+
{"name": "status:claimed", "color": "1d76db", "description": "taken by an agent, work in progress (lease)"},
|
|
5
|
+
{"name": "status:review", "color": "5319e7", "description": "PR open, waiting for review"},
|
|
6
|
+
{"name": "status:blocked", "color": "b60205", "description": "blocked, a human is needed"},
|
|
7
|
+
{"name": "status:waiting", "color": "c5def5", "description": "waiting for dependencies (after); crew task promote moves it to ready"},
|
|
8
|
+
{"name": "status:done", "color": "c2e0c6", "description": "done"},
|
|
9
|
+
{"name": "tier:low", "color": "f9f9f9", "description": "simple task - a junior model will do"},
|
|
10
|
+
{"name": "tier:mid", "color": "e4e4e4", "description": "regular task"},
|
|
11
|
+
{"name": "tier:high", "color": "bfbfbf", "description": "complex - senior model only"},
|
|
12
|
+
{"name": "risk:low", "color": "c2e0c6", "description": "one agent review, auto-merge"},
|
|
13
|
+
{"name": "risk:med", "color": "fbca04", "description": "agent review + tests for the criteria, auto-merge"},
|
|
14
|
+
{"name": "risk:high", "color": "d93f0b", "description": "agent review (security first), merged first by hand, E2E required"},
|
|
15
|
+
{"name": "priority:high", "color": "b60205", "description": "do first: crew task next hands it out before other tasks of the same owner"},
|
|
16
|
+
{"name": "priority:low", "color": "d4d4d4", "description": "do last"},
|
|
17
|
+
{"name": "feedback", "color": "d4c5f9", "description": "human feedback after testing the running app; the product lead triages it regularly"},
|
|
18
|
+
{"name": "epic", "color": "3e4b9e", "description": "a large scenario made of several tasks"},
|
|
19
|
+
{"name": "crew:pause", "color": "000000", "description": "EMERGENCY STOP: all agents stop"},
|
|
20
|
+
{"name": "crew:question", "color": "0052cc", "description": "a question from an agent to the owner of an area (crew ask); answered with crew answer"},
|
|
21
|
+
{"name": "needs-human", "color": "e99695", "description": "only a human can decide this (scope, contract, product choice)"},
|
|
22
|
+
{"name": "crew:digest", "color": "bfdadc", "description": "the pinned team status issue, rewritten by crew digest"}
|
|
18
23
|
]
|
|
@@ -26,7 +26,7 @@ permissions:
|
|
|
26
26
|
|
|
27
27
|
jobs:
|
|
28
28
|
guard:
|
|
29
|
-
if: github.event_name != 'issue_comment' || (github.event.issue.pull_request && contains(github.event.comment.body, 'crew:review'))
|
|
29
|
+
if: github.event_name != 'issue_comment' || (github.event.issue.pull_request && (contains(github.event.comment.body, 'crew:review') || contains(github.event.comment.body, 'crew:waive')))
|
|
30
30
|
runs-on: ubuntu-latest
|
|
31
31
|
concurrency:
|
|
32
32
|
group: crew-guard-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr }}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Code checks: linter, types, tests from crew.config.json. They work the same for Claude, Codex, and humans.
|
|
2
2
|
# PR rules (protected files, acceptance tests, agent review) live in crew-guard.yml, which runs from main.
|
|
3
|
-
# Requires the variable CREW_HARNESS (Settings → Variables), e.g. @gredescerber/agent-crew@0.
|
|
3
|
+
# Requires the variable CREW_HARNESS (Settings → Variables), e.g. @gredescerber/agent-crew@0.6.0
|
|
4
4
|
# (installed from the public npm registry at a pinned version; no token).
|
|
5
5
|
# Main's protection, the variable, and the labels are set up by `crew github setup --apply`.
|
|
6
6
|
name: crew
|
|
@@ -69,9 +69,9 @@ jobs:
|
|
|
69
69
|
if: github.event_name == 'schedule'
|
|
70
70
|
runs-on: ubuntu-latest
|
|
71
71
|
permissions:
|
|
72
|
-
contents:
|
|
72
|
+
contents: write # crew merge-ready merges ready PRs
|
|
73
73
|
issues: write # change status labels and write release markers
|
|
74
|
-
pull-requests:
|
|
74
|
+
pull-requests: write # recompute crew-guard and merge ready PRs
|
|
75
75
|
statuses: write # the crew-guard commit status
|
|
76
76
|
steps:
|
|
77
77
|
- uses: actions/checkout@v4
|
|
@@ -90,6 +90,16 @@ jobs:
|
|
|
90
90
|
crew guard --pr "$n" --report --json > /dev/null || true
|
|
91
91
|
done
|
|
92
92
|
|
|
93
|
+
# Merge PRs that are ready but not auto-merged (risk:high, or auto-merge off): no agent session needed.
|
|
94
|
+
- name: Merge ready PRs
|
|
95
|
+
env: { GH_TOKEN: "${{ github.token }}" }
|
|
96
|
+
run: crew merge-ready --json
|
|
97
|
+
|
|
98
|
+
# Team status in one pinned issue (no LLM): who waits for a human, stale leases, PRs, idle members.
|
|
99
|
+
- name: Update crew digest
|
|
100
|
+
env: { GH_TOKEN: "${{ github.token }}" }
|
|
101
|
+
run: crew digest --post --json
|
|
102
|
+
|
|
93
103
|
# An issue was closed → waiting tasks whose crew:after are all closed become ready.
|
|
94
104
|
promote:
|
|
95
105
|
if: github.event_name == 'issues'
|
|
@@ -62,7 +62,7 @@ A task is taken by the **executor** — the agent that ran `crew task claim`. It
|
|
|
62
62
|
## When to stop
|
|
63
63
|
- The same error 3 times → `crew task status <id> blocked --note "what I tried, what is in the way"`.
|
|
64
64
|
- The task is unclear or the acceptance criteria are not verifiable → `crew task status <id> needs-spec --note "question"`. Do not guess.
|
|
65
|
-
- Need
|
|
65
|
+
- Need something from someone else's area or `contracts/` → do not change it: ask its owner with `crew ask <area> --text "..." --task <id>` (English, with facts). Only a human can decide (scope, product) → `crew ask <area> --human`. The answer comes back through `crew task next`.
|
|
66
66
|
- The task is beyond you → `crew task release <id> --note "what I got done"`.
|
|
67
67
|
- An issue with the label `crew:pause` is open → finish the current step and stop. The code checks this: `crew task next` and `crew task claim` return the error `paused` until a human closes the issue.
|
|
68
68
|
|