@gredescerber/agent-crew 0.5.0 → 0.6.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.
@@ -28,6 +28,11 @@ import kickoff from '../commands/kickoff.js';
28
28
  import guard from '../commands/guard.js';
29
29
  import up from '../commands/up.js';
30
30
  import shots from '../commands/shots.js';
31
+ import board from '../commands/board.js';
32
+ import upgrade from '../commands/upgrade.js';
33
+ import askCmds from '../commands/ask.js';
34
+ import mergeReady from '../commands/merge-ready.js';
35
+ import digest from '../commands/digest.js';
31
36
  import { existsSync, realpathSync } from 'node:fs';
32
37
  import { isAbsolute, resolve } from 'node:path';
33
38
  import { loadConfig } from './config.js';
@@ -43,7 +48,7 @@ export const CWD_ARG = {
43
48
  /** Command arguments including the shared ones (cwd). */
44
49
  export const argsOf = (cmd) => [...(cmd.args ?? []), ...(cmd.usesCwd && !(cmd.args ?? []).some((a) => a.name === 'cwd') ? [CWD_ARG] : [])];
45
50
 
46
- export const commands = [...task, ...kickoff, check, ...pr, guard, status, commit, init, up, adapters, labels, ...compliance, doctor, worker, github, metrics, evalCmd, shots];
51
+ export const commands = [...task, ...kickoff, check, ...pr, guard, status, commit, init, up, adapters, labels, ...compliance, doctor, worker, github, metrics, evalCmd, shots, board, upgrade, ...askCmds, mergeReady, digest];
47
52
 
48
53
  /** For MCP: only commands safe for agents (no init/labels/adapters; a human does those). */
49
54
  export const agentCommands = commands.filter((c) => c.agent !== false);
@@ -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) => {
@@ -1,18 +1,23 @@
1
1
  [
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": "two agent reviews (one security), merged first, E2E required" },
15
- { "name": "feedback", "color": "d4c5f9", "description": "human feedback after testing the running app; the product lead triages it regularly" },
16
- { "name": "epic", "color": "3e4b9e", "description": "a large scenario made of several tasks" },
17
- { "name": "crew:pause", "color": "000000", "description": "EMERGENCY STOP: all agents stop" }
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.5.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: read
72
+ contents: write # crew merge-ready merges ready PRs
73
73
  issues: write # change status labels and write release markers
74
- pull-requests: read # recompute crew-guard for open PRs
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 to change someone else's area or `contracts/` → do not change, `blocked` with a description.
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