@citadel-labs/citadel 0.1.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 +145 -0
- package/dist/__tests__/append-bead.test.d.ts +2 -0
- package/dist/__tests__/append-bead.test.d.ts.map +1 -0
- package/dist/__tests__/append-bead.test.js +88 -0
- package/dist/__tests__/append-bead.test.js.map +1 -0
- package/dist/__tests__/blocked-outposts.test.d.ts +2 -0
- package/dist/__tests__/blocked-outposts.test.d.ts.map +1 -0
- package/dist/__tests__/blocked-outposts.test.js +142 -0
- package/dist/__tests__/blocked-outposts.test.js.map +1 -0
- package/dist/__tests__/bugfixes.test.d.ts +2 -0
- package/dist/__tests__/bugfixes.test.d.ts.map +1 -0
- package/dist/__tests__/bugfixes.test.js +503 -0
- package/dist/__tests__/bugfixes.test.js.map +1 -0
- package/dist/__tests__/citizen-tribute.test.d.ts +2 -0
- package/dist/__tests__/citizen-tribute.test.d.ts.map +1 -0
- package/dist/__tests__/citizen-tribute.test.js +106 -0
- package/dist/__tests__/citizen-tribute.test.js.map +1 -0
- package/dist/__tests__/cli-e2e/dispatch-note-resolve.test.d.ts +2 -0
- package/dist/__tests__/cli-e2e/dispatch-note-resolve.test.d.ts.map +1 -0
- package/dist/__tests__/cli-e2e/dispatch-note-resolve.test.js +65 -0
- package/dist/__tests__/cli-e2e/dispatch-note-resolve.test.js.map +1 -0
- package/dist/__tests__/cli-e2e/full-lifecycle.test.d.ts +2 -0
- package/dist/__tests__/cli-e2e/full-lifecycle.test.d.ts.map +1 -0
- package/dist/__tests__/cli-e2e/full-lifecycle.test.js +157 -0
- package/dist/__tests__/cli-e2e/full-lifecycle.test.js.map +1 -0
- package/dist/__tests__/cli-e2e/helpers.d.ts +28 -0
- package/dist/__tests__/cli-e2e/helpers.d.ts.map +1 -0
- package/dist/__tests__/cli-e2e/helpers.js +76 -0
- package/dist/__tests__/cli-e2e/helpers.js.map +1 -0
- package/dist/__tests__/cli-e2e/init-outpost-status.test.d.ts +2 -0
- package/dist/__tests__/cli-e2e/init-outpost-status.test.d.ts.map +1 -0
- package/dist/__tests__/cli-e2e/init-outpost-status.test.js +79 -0
- package/dist/__tests__/cli-e2e/init-outpost-status.test.js.map +1 -0
- package/dist/__tests__/cli-e2e/reset-stop-tribute-trace.test.d.ts +2 -0
- package/dist/__tests__/cli-e2e/reset-stop-tribute-trace.test.d.ts.map +1 -0
- package/dist/__tests__/cli-e2e/reset-stop-tribute-trace.test.js +158 -0
- package/dist/__tests__/cli-e2e/reset-stop-tribute-trace.test.js.map +1 -0
- package/dist/__tests__/courier.test.d.ts +2 -0
- package/dist/__tests__/courier.test.d.ts.map +1 -0
- package/dist/__tests__/courier.test.js +97 -0
- package/dist/__tests__/courier.test.js.map +1 -0
- package/dist/__tests__/e2e-smoke.test.d.ts +2 -0
- package/dist/__tests__/e2e-smoke.test.d.ts.map +1 -0
- package/dist/__tests__/e2e-smoke.test.js +137 -0
- package/dist/__tests__/e2e-smoke.test.js.map +1 -0
- package/dist/__tests__/fo-broadcast.test.d.ts +2 -0
- package/dist/__tests__/fo-broadcast.test.d.ts.map +1 -0
- package/dist/__tests__/fo-broadcast.test.js +134 -0
- package/dist/__tests__/fo-broadcast.test.js.map +1 -0
- package/dist/__tests__/fo-command-processor.test.d.ts +2 -0
- package/dist/__tests__/fo-command-processor.test.d.ts.map +1 -0
- package/dist/__tests__/fo-command-processor.test.js +86 -0
- package/dist/__tests__/fo-command-processor.test.js.map +1 -0
- package/dist/__tests__/fo-escalation.test.d.ts +2 -0
- package/dist/__tests__/fo-escalation.test.d.ts.map +1 -0
- package/dist/__tests__/fo-escalation.test.js +126 -0
- package/dist/__tests__/fo-escalation.test.js.map +1 -0
- package/dist/__tests__/fo-nudge-watcher.test.d.ts +2 -0
- package/dist/__tests__/fo-nudge-watcher.test.d.ts.map +1 -0
- package/dist/__tests__/fo-nudge-watcher.test.js +154 -0
- package/dist/__tests__/fo-nudge-watcher.test.js.map +1 -0
- package/dist/__tests__/fo-nudge-wiring.test.d.ts +2 -0
- package/dist/__tests__/fo-nudge-wiring.test.d.ts.map +1 -0
- package/dist/__tests__/fo-nudge-wiring.test.js +31 -0
- package/dist/__tests__/fo-nudge-wiring.test.js.map +1 -0
- package/dist/__tests__/fo-relay.test.d.ts +2 -0
- package/dist/__tests__/fo-relay.test.d.ts.map +1 -0
- package/dist/__tests__/fo-relay.test.js +90 -0
- package/dist/__tests__/fo-relay.test.js.map +1 -0
- package/dist/__tests__/fo-task-report.test.d.ts +2 -0
- package/dist/__tests__/fo-task-report.test.d.ts.map +1 -0
- package/dist/__tests__/fo-task-report.test.js +81 -0
- package/dist/__tests__/fo-task-report.test.js.map +1 -0
- package/dist/__tests__/fo-webhook.test.d.ts +2 -0
- package/dist/__tests__/fo-webhook.test.d.ts.map +1 -0
- package/dist/__tests__/fo-webhook.test.js +70 -0
- package/dist/__tests__/fo-webhook.test.js.map +1 -0
- package/dist/__tests__/integration.test.d.ts +2 -0
- package/dist/__tests__/integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration.test.js +763 -0
- package/dist/__tests__/integration.test.js.map +1 -0
- package/dist/__tests__/multi-outpost-dep.test.d.ts +2 -0
- package/dist/__tests__/multi-outpost-dep.test.d.ts.map +1 -0
- package/dist/__tests__/multi-outpost-dep.test.js +173 -0
- package/dist/__tests__/multi-outpost-dep.test.js.map +1 -0
- package/dist/__tests__/nudge.test.d.ts +2 -0
- package/dist/__tests__/nudge.test.d.ts.map +1 -0
- package/dist/__tests__/nudge.test.js +103 -0
- package/dist/__tests__/nudge.test.js.map +1 -0
- package/dist/__tests__/outpost-registry.test.d.ts +2 -0
- package/dist/__tests__/outpost-registry.test.d.ts.map +1 -0
- package/dist/__tests__/outpost-registry.test.js +72 -0
- package/dist/__tests__/outpost-registry.test.js.map +1 -0
- package/dist/__tests__/process-registry.test.d.ts +2 -0
- package/dist/__tests__/process-registry.test.d.ts.map +1 -0
- package/dist/__tests__/process-registry.test.js +108 -0
- package/dist/__tests__/process-registry.test.js.map +1 -0
- package/dist/__tests__/session-log.test.d.ts +2 -0
- package/dist/__tests__/session-log.test.d.ts.map +1 -0
- package/dist/__tests__/session-log.test.js +60 -0
- package/dist/__tests__/session-log.test.js.map +1 -0
- package/dist/__tests__/spawn-citizen.test.d.ts +2 -0
- package/dist/__tests__/spawn-citizen.test.d.ts.map +1 -0
- package/dist/__tests__/spawn-citizen.test.js +48 -0
- package/dist/__tests__/spawn-citizen.test.js.map +1 -0
- package/dist/__tests__/timeout-watchdog.test.d.ts +2 -0
- package/dist/__tests__/timeout-watchdog.test.d.ts.map +1 -0
- package/dist/__tests__/timeout-watchdog.test.js +81 -0
- package/dist/__tests__/timeout-watchdog.test.js.map +1 -0
- package/dist/__tests__/worktree-manager.test.d.ts +2 -0
- package/dist/__tests__/worktree-manager.test.d.ts.map +1 -0
- package/dist/__tests__/worktree-manager.test.js +98 -0
- package/dist/__tests__/worktree-manager.test.js.map +1 -0
- package/dist/cli/aliases.d.ts +10 -0
- package/dist/cli/aliases.d.ts.map +1 -0
- package/dist/cli/aliases.js +56 -0
- package/dist/cli/aliases.js.map +1 -0
- package/dist/cli/command.d.ts +3 -0
- package/dist/cli/command.d.ts.map +1 -0
- package/dist/cli/command.js +63 -0
- package/dist/cli/command.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +29 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +57 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/outpost.d.ts +3 -0
- package/dist/cli/outpost.d.ts.map +1 -0
- package/dist/cli/outpost.js +65 -0
- package/dist/cli/outpost.js.map +1 -0
- package/dist/cli/reset.d.ts +3 -0
- package/dist/cli/reset.d.ts.map +1 -0
- package/dist/cli/reset.js +67 -0
- package/dist/cli/reset.js.map +1 -0
- package/dist/cli/session.d.ts +3 -0
- package/dist/cli/session.d.ts.map +1 -0
- package/dist/cli/session.js +112 -0
- package/dist/cli/session.js.map +1 -0
- package/dist/cli/start.d.ts +3 -0
- package/dist/cli/start.d.ts.map +1 -0
- package/dist/cli/start.js +105 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/cli/status.d.ts +3 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +128 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/tribute.d.ts +3 -0
- package/dist/cli/tribute.d.ts.map +1 -0
- package/dist/cli/tribute.js +160 -0
- package/dist/cli/tribute.js.map +1 -0
- package/dist/cli/worktree.d.ts +3 -0
- package/dist/cli/worktree.d.ts.map +1 -0
- package/dist/cli/worktree.js +64 -0
- package/dist/cli/worktree.js.map +1 -0
- package/dist/core/append-bead.d.ts +13 -0
- package/dist/core/append-bead.d.ts.map +1 -0
- package/dist/core/append-bead.js +68 -0
- package/dist/core/append-bead.js.map +1 -0
- package/dist/core/blocked-outposts.d.ts +15 -0
- package/dist/core/blocked-outposts.d.ts.map +1 -0
- package/dist/core/blocked-outposts.js +77 -0
- package/dist/core/blocked-outposts.js.map +1 -0
- package/dist/core/citizen-bootstrap.d.ts +17 -0
- package/dist/core/citizen-bootstrap.d.ts.map +1 -0
- package/dist/core/citizen-bootstrap.js +118 -0
- package/dist/core/citizen-bootstrap.js.map +1 -0
- package/dist/core/citizen-tribute.d.ts +17 -0
- package/dist/core/citizen-tribute.d.ts.map +1 -0
- package/dist/core/citizen-tribute.js +55 -0
- package/dist/core/citizen-tribute.js.map +1 -0
- package/dist/core/command-bead.d.ts +6 -0
- package/dist/core/command-bead.d.ts.map +1 -0
- package/dist/core/command-bead.js +20 -0
- package/dist/core/command-bead.js.map +1 -0
- package/dist/core/courier.d.ts +52 -0
- package/dist/core/courier.d.ts.map +1 -0
- package/dist/core/courier.js +167 -0
- package/dist/core/courier.js.map +1 -0
- package/dist/core/fo-broadcast.d.ts +15 -0
- package/dist/core/fo-broadcast.d.ts.map +1 -0
- package/dist/core/fo-broadcast.js +57 -0
- package/dist/core/fo-broadcast.js.map +1 -0
- package/dist/core/fo-command-processor.d.ts +7 -0
- package/dist/core/fo-command-processor.d.ts.map +1 -0
- package/dist/core/fo-command-processor.js +123 -0
- package/dist/core/fo-command-processor.js.map +1 -0
- package/dist/core/fo-dispatch.d.ts +24 -0
- package/dist/core/fo-dispatch.d.ts.map +1 -0
- package/dist/core/fo-dispatch.js +76 -0
- package/dist/core/fo-dispatch.js.map +1 -0
- package/dist/core/fo-escalation.d.ts +20 -0
- package/dist/core/fo-escalation.d.ts.map +1 -0
- package/dist/core/fo-escalation.js +83 -0
- package/dist/core/fo-escalation.js.map +1 -0
- package/dist/core/fo-handlers/ceiling-hit.d.ts +8 -0
- package/dist/core/fo-handlers/ceiling-hit.d.ts.map +1 -0
- package/dist/core/fo-handlers/ceiling-hit.js +10 -0
- package/dist/core/fo-handlers/ceiling-hit.js.map +1 -0
- package/dist/core/fo-handlers/slot-open.d.ts +13 -0
- package/dist/core/fo-handlers/slot-open.d.ts.map +1 -0
- package/dist/core/fo-handlers/slot-open.js +63 -0
- package/dist/core/fo-handlers/slot-open.js.map +1 -0
- package/dist/core/fo-nudge-watcher.d.ts +19 -0
- package/dist/core/fo-nudge-watcher.d.ts.map +1 -0
- package/dist/core/fo-nudge-watcher.js +71 -0
- package/dist/core/fo-nudge-watcher.js.map +1 -0
- package/dist/core/fo-nudge-wiring.d.ts +13 -0
- package/dist/core/fo-nudge-wiring.d.ts.map +1 -0
- package/dist/core/fo-nudge-wiring.js +120 -0
- package/dist/core/fo-nudge-wiring.js.map +1 -0
- package/dist/core/fo-relay.d.ts +7 -0
- package/dist/core/fo-relay.d.ts.map +1 -0
- package/dist/core/fo-relay.js +47 -0
- package/dist/core/fo-relay.js.map +1 -0
- package/dist/core/fo-retry-policy.d.ts +17 -0
- package/dist/core/fo-retry-policy.d.ts.map +1 -0
- package/dist/core/fo-retry-policy.js +89 -0
- package/dist/core/fo-retry-policy.js.map +1 -0
- package/dist/core/fo-state.d.ts +25 -0
- package/dist/core/fo-state.d.ts.map +1 -0
- package/dist/core/fo-state.js +99 -0
- package/dist/core/fo-state.js.map +1 -0
- package/dist/core/fo-task-report.d.ts +16 -0
- package/dist/core/fo-task-report.d.ts.map +1 -0
- package/dist/core/fo-task-report.js +63 -0
- package/dist/core/fo-task-report.js.map +1 -0
- package/dist/core/fo-webhook.d.ts +22 -0
- package/dist/core/fo-webhook.d.ts.map +1 -0
- package/dist/core/fo-webhook.js +43 -0
- package/dist/core/fo-webhook.js.map +1 -0
- package/dist/core/grand-archives.d.ts +14 -0
- package/dist/core/grand-archives.d.ts.map +1 -0
- package/dist/core/grand-archives.js +79 -0
- package/dist/core/grand-archives.js.map +1 -0
- package/dist/core/nudge.d.ts +6 -0
- package/dist/core/nudge.d.ts.map +1 -0
- package/dist/core/nudge.js +19 -0
- package/dist/core/nudge.js.map +1 -0
- package/dist/core/outpost-registry.d.ts +16 -0
- package/dist/core/outpost-registry.d.ts.map +1 -0
- package/dist/core/outpost-registry.js +27 -0
- package/dist/core/outpost-registry.js.map +1 -0
- package/dist/core/pre-spawn-check.d.ts +16 -0
- package/dist/core/pre-spawn-check.d.ts.map +1 -0
- package/dist/core/pre-spawn-check.js +50 -0
- package/dist/core/pre-spawn-check.js.map +1 -0
- package/dist/core/process-registry.d.ts +17 -0
- package/dist/core/process-registry.d.ts.map +1 -0
- package/dist/core/process-registry.js +71 -0
- package/dist/core/process-registry.js.map +1 -0
- package/dist/core/spawn-citizen.d.ts +23 -0
- package/dist/core/spawn-citizen.d.ts.map +1 -0
- package/dist/core/spawn-citizen.js +99 -0
- package/dist/core/spawn-citizen.js.map +1 -0
- package/dist/core/timeout-watchdog.d.ts +14 -0
- package/dist/core/timeout-watchdog.d.ts.map +1 -0
- package/dist/core/timeout-watchdog.js +68 -0
- package/dist/core/timeout-watchdog.js.map +1 -0
- package/dist/core/validate-tribute.d.ts +11 -0
- package/dist/core/validate-tribute.d.ts.map +1 -0
- package/dist/core/validate-tribute.js +44 -0
- package/dist/core/validate-tribute.js.map +1 -0
- package/dist/core/worktree-manager.d.ts +20 -0
- package/dist/core/worktree-manager.d.ts.map +1 -0
- package/dist/core/worktree-manager.js +123 -0
- package/dist/core/worktree-manager.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/types/agent.d.ts +47 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +4 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/beads.d.ts +175 -0
- package/dist/types/beads.d.ts.map +1 -0
- package/dist/types/beads.js +4 -0
- package/dist/types/beads.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/registry.d.ts +110 -0
- package/dist/types/registry.d.ts.map +1 -0
- package/dist/types/registry.js +3 -0
- package/dist/types/registry.js.map +1 -0
- package/docs/README.md +7 -0
- package/docs/architecture.md +106 -0
- package/docs/cli-reference.md +81 -0
- package/docs/configuration.md +143 -0
- package/docs/contributing.md +65 -0
- package/docs/getting-started.md +88 -0
- package/grand-archives/archetypes/architect/archetype.json +18 -0
- package/grand-archives/archetypes/qa-engineer/archetype.json +18 -0
- package/grand-archives/archetypes/software-engineer/archetype.json +18 -0
- package/grand-archives/archetypes/software-engineer/system-prompt.md +39 -0
- package/grand-archives/archetypes/software-engineer/toolset.json +15 -0
- package/grand-archives/first-officer/config.json +21 -0
- package/grand-archives/first-officer/system-prompt.md +47 -0
- package/grand-archives/first-officer/toolset.json +19 -0
- package/grand-archives/registry.json +8 -0
- package/grand-archives/shared/base-prompt-preamble.md +25 -0
- package/grand-archives/shared/base-tools.json +10 -0
- package/package.json +73 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-spawn-check.d.ts","sourceRoot":"","sources":["../../src/core/pre-spawn-check.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAExE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,oBAAoB,EAC7B,eAAe,EAAE,eAAe,EAChC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,cAAc,CAAC,CAwCzB"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { readBeads } from './append-bead.js';
|
|
4
|
+
import { nudgeFirstOfficer } from './nudge.js';
|
|
5
|
+
/**
|
|
6
|
+
* Pre-spawn check for an outpost:
|
|
7
|
+
* 1. Read unread TASK_ASSIGNMENT mail
|
|
8
|
+
* 2. If none, nudge NO_TASK_ASSIGNMENT
|
|
9
|
+
* 3. Check citizen ceiling
|
|
10
|
+
* 4. If at ceiling, nudge PARALLELISM_CEILING_HIT
|
|
11
|
+
*/
|
|
12
|
+
export async function preSpawnCheck(citadelRoot, outpost, processRegistry, maxCitizens) {
|
|
13
|
+
// 1. Read unread mail from outpost inbox
|
|
14
|
+
const mailDir = path.join(outpost.path, '.beads', 'mail');
|
|
15
|
+
const allMail = [];
|
|
16
|
+
try {
|
|
17
|
+
const files = await fs.readdir(mailDir);
|
|
18
|
+
for (const file of files) {
|
|
19
|
+
if (!file.endsWith('.jsonl'))
|
|
20
|
+
continue;
|
|
21
|
+
const beads = await readBeads(path.join(mailDir, file));
|
|
22
|
+
allMail.push(...beads);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// mail dir may not exist
|
|
27
|
+
}
|
|
28
|
+
// Find unread TASK_ASSIGNMENT
|
|
29
|
+
const taskMail = allMail.find(m => m.mail_type === 'TASK_ASSIGNMENT' && !m.read);
|
|
30
|
+
if (!taskMail) {
|
|
31
|
+
// Nudge NO_TASK_ASSIGNMENT
|
|
32
|
+
await nudgeFirstOfficer(citadelRoot, 'NO_TASK_ASSIGNMENT', {
|
|
33
|
+
outpost: outpost.slug,
|
|
34
|
+
triggering_mail_bead_id: '',
|
|
35
|
+
});
|
|
36
|
+
return { canSpawn: false, taskMail: null, reason: 'No unread TASK_ASSIGNMENT mail' };
|
|
37
|
+
}
|
|
38
|
+
// 2. Check ceiling
|
|
39
|
+
const activeCount = processRegistry.countActive(outpost.slug);
|
|
40
|
+
if (activeCount >= maxCitizens) {
|
|
41
|
+
await nudgeFirstOfficer(citadelRoot, 'PARALLELISM_CEILING_HIT', {
|
|
42
|
+
outpost: outpost.slug,
|
|
43
|
+
active_count: activeCount,
|
|
44
|
+
ceiling: maxCitizens,
|
|
45
|
+
});
|
|
46
|
+
return { canSpawn: false, taskMail, reason: `Ceiling reached: ${activeCount}/${maxCitizens}` };
|
|
47
|
+
}
|
|
48
|
+
return { canSpawn: true, taskMail };
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=pre-spawn-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-spawn-check.js","sourceRoot":"","sources":["../../src/core/pre-spawn-check.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAU/C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,WAAmB,EACnB,OAA6B,EAC7B,eAAgC,EAChC,WAAmB;IAEnB,yCAAyC;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAe,EAAE,CAAC;IAE/B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACxC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACvC,MAAM,KAAK,GAAG,MAAM,SAAS,CAAW,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAClE,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,yBAAyB;IAC3B,CAAC;IAED,8BAA8B;IAC9B,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,iBAAiB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAEjF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,2BAA2B;QAC3B,MAAM,iBAAiB,CAAC,WAAW,EAAE,oBAAoB,EAAE;YACzD,OAAO,EAAE,OAAO,CAAC,IAAI;YACrB,uBAAuB,EAAE,EAAE;SAC5B,CAAC,CAAC;QACH,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,gCAAgC,EAAE,CAAC;IACvF,CAAC;IAED,mBAAmB;IACnB,MAAM,WAAW,GAAG,eAAe,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,IAAI,WAAW,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,iBAAiB,CAAC,WAAW,EAAE,yBAAyB,EAAE;YAC9D,OAAO,EAAE,OAAO,CAAC,IAAI;YACrB,YAAY,EAAE,WAAW;YACzB,OAAO,EAAE,WAAW;SACrB,CAAC,CAAC;QACH,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,oBAAoB,WAAW,IAAI,WAAW,EAAE,EAAE,CAAC;IACjG,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ProcessRegistryEntry } from '../types/registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* In-memory registry of active citizen processes, keyed by outpost slug then citizen_id.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ProcessRegistry {
|
|
6
|
+
private registry;
|
|
7
|
+
set(outpost: string, citizenId: string, entry: ProcessRegistryEntry): void;
|
|
8
|
+
get(outpost: string, citizenId: string): ProcessRegistryEntry | undefined;
|
|
9
|
+
getAll(outpost: string): Map<string, ProcessRegistryEntry>;
|
|
10
|
+
delete(outpost: string, citizenId: string): boolean;
|
|
11
|
+
countActive(outpost: string): number;
|
|
12
|
+
/** Check if a process is still alive via process.kill(pid, 0). */
|
|
13
|
+
isAlive(outpost: string, citizenId: string): boolean;
|
|
14
|
+
/** Remove all dead processes across all outposts, return count removed. */
|
|
15
|
+
pruneDeadProcesses(): number;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=process-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-registry.d.ts","sourceRoot":"","sources":["../../src/core/process-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAwD;IAExE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,oBAAoB,GAAG,IAAI;IAS1E,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS;IAIzE,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC;IAI1D,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAUnD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIpC,kEAAkE;IAClE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAWpD,2EAA2E;IAC3E,kBAAkB,IAAI,MAAM;CAsB7B"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory registry of active citizen processes, keyed by outpost slug then citizen_id.
|
|
3
|
+
*/
|
|
4
|
+
export class ProcessRegistry {
|
|
5
|
+
registry = new Map();
|
|
6
|
+
set(outpost, citizenId, entry) {
|
|
7
|
+
let citizens = this.registry.get(outpost);
|
|
8
|
+
if (!citizens) {
|
|
9
|
+
citizens = new Map();
|
|
10
|
+
this.registry.set(outpost, citizens);
|
|
11
|
+
}
|
|
12
|
+
citizens.set(citizenId, entry);
|
|
13
|
+
}
|
|
14
|
+
get(outpost, citizenId) {
|
|
15
|
+
return this.registry.get(outpost)?.get(citizenId);
|
|
16
|
+
}
|
|
17
|
+
getAll(outpost) {
|
|
18
|
+
return this.registry.get(outpost) ?? new Map();
|
|
19
|
+
}
|
|
20
|
+
delete(outpost, citizenId) {
|
|
21
|
+
const citizens = this.registry.get(outpost);
|
|
22
|
+
if (!citizens)
|
|
23
|
+
return false;
|
|
24
|
+
const deleted = citizens.delete(citizenId);
|
|
25
|
+
if (citizens.size === 0) {
|
|
26
|
+
this.registry.delete(outpost);
|
|
27
|
+
}
|
|
28
|
+
return deleted;
|
|
29
|
+
}
|
|
30
|
+
countActive(outpost) {
|
|
31
|
+
return this.registry.get(outpost)?.size ?? 0;
|
|
32
|
+
}
|
|
33
|
+
/** Check if a process is still alive via process.kill(pid, 0). */
|
|
34
|
+
isAlive(outpost, citizenId) {
|
|
35
|
+
const entry = this.get(outpost, citizenId);
|
|
36
|
+
if (!entry)
|
|
37
|
+
return false;
|
|
38
|
+
try {
|
|
39
|
+
process.kill(entry.pid, 0);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Remove all dead processes across all outposts, return count removed. */
|
|
47
|
+
pruneDeadProcesses() {
|
|
48
|
+
let removed = 0;
|
|
49
|
+
for (const [outpost, citizens] of this.registry) {
|
|
50
|
+
for (const [citizenId, entry] of citizens) {
|
|
51
|
+
let alive;
|
|
52
|
+
try {
|
|
53
|
+
process.kill(entry.pid, 0);
|
|
54
|
+
alive = true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
alive = false;
|
|
58
|
+
}
|
|
59
|
+
if (!alive) {
|
|
60
|
+
citizens.delete(citizenId);
|
|
61
|
+
removed++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (citizens.size === 0) {
|
|
65
|
+
this.registry.delete(outpost);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return removed;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=process-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-registry.js","sourceRoot":"","sources":["../../src/core/process-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { WorktreeManager } from './worktree-manager.js';
|
|
2
|
+
import { GrandArchives } from './grand-archives.js';
|
|
3
|
+
import { ProcessRegistry } from './process-registry.js';
|
|
4
|
+
import type { MailBead } from '../types/index.js';
|
|
5
|
+
export interface SpawnCitizenOptions {
|
|
6
|
+
citadelRoot: string;
|
|
7
|
+
outpostSlug: string;
|
|
8
|
+
taskMail: MailBead;
|
|
9
|
+
worktreeManager: WorktreeManager;
|
|
10
|
+
grandArchives: GrandArchives;
|
|
11
|
+
processRegistry: ProcessRegistry;
|
|
12
|
+
agentBinaryOverride?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Spawn a citizen agent process:
|
|
16
|
+
* 1. Create worktree if not exists (preserved on retry)
|
|
17
|
+
* 2. Resolve archetype from GrandArchives
|
|
18
|
+
* 3. Spawn agent process with env vars
|
|
19
|
+
* 4. Register in ProcessRegistry
|
|
20
|
+
* 5. On exit(0): remove worktree. On error: preserve, nudge CITIZEN_EXIT_ERROR
|
|
21
|
+
*/
|
|
22
|
+
export declare function spawnCitizen(opts: SpawnCitizenOptions): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=spawn-citizen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-citizen.d.ts","sourceRoot":"","sources":["../../src/core/spawn-citizen.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,eAAe,EAAE,eAAe,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,eAAe,EAAE,eAAe,CAAC;IACjC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2F3E"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { nudgeFirstOfficer } from './nudge.js';
|
|
4
|
+
/**
|
|
5
|
+
* Spawn a citizen agent process:
|
|
6
|
+
* 1. Create worktree if not exists (preserved on retry)
|
|
7
|
+
* 2. Resolve archetype from GrandArchives
|
|
8
|
+
* 3. Spawn agent process with env vars
|
|
9
|
+
* 4. Register in ProcessRegistry
|
|
10
|
+
* 5. On exit(0): remove worktree. On error: preserve, nudge CITIZEN_EXIT_ERROR
|
|
11
|
+
*/
|
|
12
|
+
export async function spawnCitizen(opts) {
|
|
13
|
+
const { citadelRoot, outpostSlug, taskMail, worktreeManager, grandArchives, processRegistry } = opts;
|
|
14
|
+
const spawnHint = taskMail.spawn_hint;
|
|
15
|
+
if (!spawnHint) {
|
|
16
|
+
throw new Error(`TASK_ASSIGNMENT mail ${taskMail.bead_id} missing spawn_hint`);
|
|
17
|
+
}
|
|
18
|
+
const { archetype, citizen_id: citizenId } = spawnHint;
|
|
19
|
+
const taskId = taskMail.task_id ?? taskMail.bead_id;
|
|
20
|
+
// 1. Create worktree (reuse existing on retry)
|
|
21
|
+
let worktreePath;
|
|
22
|
+
const exists = await worktreeManager.exists(outpostSlug, taskId);
|
|
23
|
+
if (exists) {
|
|
24
|
+
worktreePath = await worktreeManager.create(outpostSlug, citizenId, taskId)
|
|
25
|
+
.catch(() => {
|
|
26
|
+
// Already exists — reconstruct the path the same way WorktreeManager does
|
|
27
|
+
// This is a fallback; the worktree is already on disk.
|
|
28
|
+
return '';
|
|
29
|
+
});
|
|
30
|
+
if (!worktreePath) {
|
|
31
|
+
// Derive path from the worktreeManager.create convention:
|
|
32
|
+
// repoRoot/worktrees/citizen/<taskId> — but we don't have repoRoot here.
|
|
33
|
+
// Instead, rely on the fact that exists() confirmed the worktree is there,
|
|
34
|
+
// so we can safely re-derive it. WorktreeManager keeps the same layout.
|
|
35
|
+
// We call list() and match by taskId.
|
|
36
|
+
const entries = await worktreeManager.list(outpostSlug);
|
|
37
|
+
const match = entries.find(e => path.basename(e.path) === taskId);
|
|
38
|
+
if (!match)
|
|
39
|
+
throw new Error(`Worktree exists but could not resolve path for task ${taskId}`);
|
|
40
|
+
worktreePath = match.path;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
worktreePath = await worktreeManager.create(outpostSlug, citizenId, taskId);
|
|
45
|
+
}
|
|
46
|
+
// 2. Resolve archetype
|
|
47
|
+
const resolved = await grandArchives.resolve(archetype);
|
|
48
|
+
// 3. Set up env
|
|
49
|
+
const env = {
|
|
50
|
+
...process.env,
|
|
51
|
+
CITADEL_OUTPOST: outpostSlug,
|
|
52
|
+
CITADEL_ARCHETYPE: archetype,
|
|
53
|
+
CITADEL_CITIZEN_ID: citizenId,
|
|
54
|
+
CITADEL_TASK_ID: taskId,
|
|
55
|
+
CITADEL_WORKTREE: worktreePath,
|
|
56
|
+
CITADEL_BEADS_ROOT: citadelRoot,
|
|
57
|
+
};
|
|
58
|
+
// 4. Spawn agent process (configurable binary: override > archetype > default)
|
|
59
|
+
const agentBin = opts.agentBinaryOverride ?? resolved.agent_binary;
|
|
60
|
+
const child = spawn(agentBin, [
|
|
61
|
+
'--system-prompt', resolved.system_prompt_path,
|
|
62
|
+
'--tools', resolved.toolset_manifest_path,
|
|
63
|
+
'--dir', worktreePath,
|
|
64
|
+
], {
|
|
65
|
+
cwd: worktreePath,
|
|
66
|
+
env,
|
|
67
|
+
stdio: 'pipe',
|
|
68
|
+
detached: false,
|
|
69
|
+
});
|
|
70
|
+
// 5. Register in ProcessRegistry
|
|
71
|
+
processRegistry.set(outpostSlug, citizenId, {
|
|
72
|
+
pid: child.pid,
|
|
73
|
+
spawned_at: new Date().toISOString(),
|
|
74
|
+
worktree_path: worktreePath,
|
|
75
|
+
task_id: taskId,
|
|
76
|
+
});
|
|
77
|
+
// 6. Handle exit
|
|
78
|
+
child.on('close', async (code, _signal) => {
|
|
79
|
+
processRegistry.delete(outpostSlug, citizenId);
|
|
80
|
+
if (code === 0) {
|
|
81
|
+
try {
|
|
82
|
+
await worktreeManager.remove(outpostSlug, taskId);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// best effort
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
await nudgeFirstOfficer(citadelRoot, 'CITIZEN_EXIT_ERROR', {
|
|
90
|
+
outpost: outpostSlug,
|
|
91
|
+
citizen_id: citizenId,
|
|
92
|
+
task_id: taskId,
|
|
93
|
+
exit_code: code,
|
|
94
|
+
worktree: worktreePath,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=spawn-citizen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-citizen.js","sourceRoot":"","sources":["../../src/core/spawn-citizen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ProcessRegistry } from './process-registry.js';
|
|
2
|
+
export declare class TimeoutWatchdog {
|
|
3
|
+
private citadelRoot;
|
|
4
|
+
private processRegistry;
|
|
5
|
+
private timeoutSeconds;
|
|
6
|
+
private pollIntervalMs;
|
|
7
|
+
private intervalHandle;
|
|
8
|
+
constructor(citadelRoot: string, processRegistry: ProcessRegistry, timeoutSeconds: number, pollIntervalMs?: number);
|
|
9
|
+
start(): void;
|
|
10
|
+
stop(): void;
|
|
11
|
+
/** Check all active citizens for timeout */
|
|
12
|
+
check(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=timeout-watchdog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeout-watchdog.d.ts","sourceRoot":"","sources":["../../src/core/timeout-watchdog.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAIxD,qBAAa,eAAe;IAIxB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,cAAc;IANxB,OAAO,CAAC,cAAc,CAA+C;gBAG3D,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,eAAe,EAChC,cAAc,EAAE,MAAM,EACtB,cAAc,GAAE,MAAc;IAGxC,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;IAOZ,4CAA4C;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAuC7B"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { nudgeFirstOfficer } from './nudge.js';
|
|
4
|
+
export class TimeoutWatchdog {
|
|
5
|
+
citadelRoot;
|
|
6
|
+
processRegistry;
|
|
7
|
+
timeoutSeconds;
|
|
8
|
+
pollIntervalMs;
|
|
9
|
+
intervalHandle = null;
|
|
10
|
+
constructor(citadelRoot, processRegistry, timeoutSeconds, pollIntervalMs = 10000) {
|
|
11
|
+
this.citadelRoot = citadelRoot;
|
|
12
|
+
this.processRegistry = processRegistry;
|
|
13
|
+
this.timeoutSeconds = timeoutSeconds;
|
|
14
|
+
this.pollIntervalMs = pollIntervalMs;
|
|
15
|
+
}
|
|
16
|
+
start() {
|
|
17
|
+
this.intervalHandle = setInterval(() => {
|
|
18
|
+
this.check().catch(err => {
|
|
19
|
+
console.warn(`[TimeoutWatchdog] check error: ${err.message}`);
|
|
20
|
+
});
|
|
21
|
+
}, this.pollIntervalMs);
|
|
22
|
+
}
|
|
23
|
+
stop() {
|
|
24
|
+
if (this.intervalHandle) {
|
|
25
|
+
clearInterval(this.intervalHandle);
|
|
26
|
+
this.intervalHandle = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Check all active citizens for timeout */
|
|
30
|
+
async check() {
|
|
31
|
+
const outpostsPath = path.join(this.citadelRoot, '.citadel', 'outposts.json');
|
|
32
|
+
let registry;
|
|
33
|
+
try {
|
|
34
|
+
const raw = await fs.readFile(outpostsPath, 'utf-8');
|
|
35
|
+
registry = JSON.parse(raw);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
for (const outpost of registry.outposts) {
|
|
42
|
+
const citizens = this.processRegistry.getAll(outpost.slug);
|
|
43
|
+
for (const [citizenId, entry] of citizens) {
|
|
44
|
+
const elapsed = (now - new Date(entry.spawned_at).getTime()) / 1000;
|
|
45
|
+
if (elapsed >= this.timeoutSeconds) {
|
|
46
|
+
// Kill the process
|
|
47
|
+
try {
|
|
48
|
+
process.kill(entry.pid, 'SIGTERM');
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// already dead
|
|
52
|
+
}
|
|
53
|
+
// Remove from registry
|
|
54
|
+
this.processRegistry.delete(outpost.slug, citizenId);
|
|
55
|
+
// Nudge CITIZEN_TIMEOUT (preserve worktree)
|
|
56
|
+
await nudgeFirstOfficer(this.citadelRoot, 'CITIZEN_TIMEOUT', {
|
|
57
|
+
outpost: outpost.slug,
|
|
58
|
+
citizen_id: citizenId,
|
|
59
|
+
task_id: entry.task_id,
|
|
60
|
+
spawned_at: entry.spawned_at,
|
|
61
|
+
worktree: entry.worktree_path,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=timeout-watchdog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeout-watchdog.js","sourceRoot":"","sources":["../../src/core/timeout-watchdog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAG/C,MAAM,OAAO,eAAe;IAIhB;IACA;IACA;IACA;IANF,cAAc,GAA0C,IAAI,CAAC;IAErE,YACU,WAAmB,EACnB,eAAgC,EAChC,cAAsB,EACtB,iBAAyB,KAAK;QAH9B,gBAAW,GAAX,WAAW,CAAQ;QACnB,oBAAe,GAAf,eAAe,CAAiB;QAChC,mBAAc,GAAd,cAAc,CAAQ;QACtB,mBAAc,GAAd,cAAc,CAAgB;IACrC,CAAC;IAEJ,KAAK;QACH,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;YACrC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;gBACvB,OAAO,CAAC,IAAI,CAAC,kCAAmC,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YAC3E,CAAC,CAAC,CAAC;QACL,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACnC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,4CAA4C;IAC5C,KAAK,CAAC,KAAK;QACT,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;QAC9E,IAAI,QAAyB,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YACrD,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC3D,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;gBAC1C,MAAM,OAAO,GAAG,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;gBACpE,IAAI,OAAO,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;oBACnC,mBAAmB;oBACnB,IAAI,CAAC;wBACH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;oBACrC,CAAC;oBAAC,MAAM,CAAC;wBACP,eAAe;oBACjB,CAAC;oBAED,uBAAuB;oBACvB,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;oBAErD,4CAA4C;oBAC5C,MAAM,iBAAiB,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,EAAE;wBAC3D,OAAO,EAAE,OAAO,CAAC,IAAI;wBACrB,UAAU,EAAE,SAAS;wBACrB,OAAO,EAAE,KAAK,CAAC,OAAO;wBACtB,UAAU,EAAE,KAAK,CAAC,UAAU;wBAC5B,QAAQ,EAAE,KAAK,CAAC,aAAa;qBAC9B,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TributeBead, MailBead } from '../types/index.js';
|
|
2
|
+
export interface TributeValidationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
errors: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Validate a TributeBead against its originating TASK_ASSIGNMENT mail.
|
|
8
|
+
* Schema + acceptance criteria validation only — no ceiling checks.
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateTribute(tribute: TributeBead, taskMail: MailBead): TributeValidationResult;
|
|
11
|
+
//# sourceMappingURL=validate-tribute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-tribute.d.ts","sourceRoot":"","sources":["../../src/core/validate-tribute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAyB,MAAM,mBAAmB,CAAC;AAEtF,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,QAAQ,GACjB,uBAAuB,CA8CzB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate a TributeBead against its originating TASK_ASSIGNMENT mail.
|
|
3
|
+
* Schema + acceptance criteria validation only — no ceiling checks.
|
|
4
|
+
*/
|
|
5
|
+
export function validateTribute(tribute, taskMail) {
|
|
6
|
+
const errors = [];
|
|
7
|
+
const payload = taskMail.payload;
|
|
8
|
+
// task_id must match
|
|
9
|
+
if (tribute.task_id !== (taskMail.task_id ?? taskMail.bead_id)) {
|
|
10
|
+
errors.push(`task_id mismatch: tribute="${tribute.task_id}" mail="${taskMail.task_id ?? taskMail.bead_id}"`);
|
|
11
|
+
}
|
|
12
|
+
// originating_mail_ref must match
|
|
13
|
+
if (tribute.originating_mail_ref !== taskMail.bead_id) {
|
|
14
|
+
errors.push(`originating_mail_ref mismatch: expected="${taskMail.bead_id}" got="${tribute.originating_mail_ref}"`);
|
|
15
|
+
}
|
|
16
|
+
// citizen_id must match spawn_hint
|
|
17
|
+
if (taskMail.spawn_hint && tribute.citizen_id !== taskMail.spawn_hint.citizen_id) {
|
|
18
|
+
errors.push(`citizen_id mismatch: expected="${taskMail.spawn_hint.citizen_id}" got="${tribute.citizen_id}"`);
|
|
19
|
+
}
|
|
20
|
+
// summary required
|
|
21
|
+
if (!tribute.summary || tribute.summary.trim() === '') {
|
|
22
|
+
errors.push('summary is required');
|
|
23
|
+
}
|
|
24
|
+
// acceptance_met count must match acceptance_criteria count
|
|
25
|
+
const criteriaCount = payload.acceptance_criteria?.length ?? 0;
|
|
26
|
+
const metCount = tribute.acceptance_met?.length ?? 0;
|
|
27
|
+
if (metCount !== criteriaCount) {
|
|
28
|
+
errors.push(`acceptance_met count (${metCount}) does not match acceptance_criteria count (${criteriaCount})`);
|
|
29
|
+
}
|
|
30
|
+
// SUCCESS requires all criteria met
|
|
31
|
+
if (tribute.status === 'SUCCESS') {
|
|
32
|
+
if (tribute.acceptance_met && !tribute.acceptance_met.every(Boolean)) {
|
|
33
|
+
errors.push('SUCCESS status requires all acceptance criteria to be met');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// PARTIAL requires not all met
|
|
37
|
+
if (tribute.status === 'PARTIAL') {
|
|
38
|
+
if (tribute.acceptance_met && tribute.acceptance_met.every(Boolean)) {
|
|
39
|
+
errors.push('PARTIAL status should not have all acceptance criteria met (use SUCCESS instead)');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { valid: errors.length === 0, errors };
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=validate-tribute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-tribute.js","sourceRoot":"","sources":["../../src/core/validate-tribute.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAoB,EACpB,QAAkB;IAElB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAgC,CAAC;IAE1D,qBAAqB;IACrB,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/D,MAAM,CAAC,IAAI,CAAC,8BAA8B,OAAO,CAAC,OAAO,WAAW,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,OAAO,GAAG,CAAC,CAAC;IAC/G,CAAC;IAED,kCAAkC;IAClC,IAAI,OAAO,CAAC,oBAAoB,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,4CAA4C,QAAQ,CAAC,OAAO,UAAU,OAAO,CAAC,oBAAoB,GAAG,CAAC,CAAC;IACrH,CAAC;IAED,mCAAmC;IACnC,IAAI,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,KAAK,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;QACjF,MAAM,CAAC,IAAI,CAAC,kCAAkC,QAAQ,CAAC,UAAU,CAAC,UAAU,UAAU,OAAO,CAAC,UAAU,GAAG,CAAC,CAAC;IAC/G,CAAC;IAED,mBAAmB;IACnB,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACrC,CAAC;IAED,4DAA4D;IAC5D,MAAM,aAAa,GAAG,OAAO,CAAC,mBAAmB,EAAE,MAAM,IAAI,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,EAAE,MAAM,IAAI,CAAC,CAAC;IACrD,IAAI,QAAQ,KAAK,aAAa,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,yBAAyB,QAAQ,+CAA+C,aAAa,GAAG,CAAC,CAAC;IAChH,CAAC;IAED,oCAAoC;IACpC,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACjC,IAAI,OAAO,CAAC,cAAc,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACrE,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IAED,+BAA+B;IAC/B,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACjC,IAAI,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACpE,MAAM,CAAC,IAAI,CAAC,kFAAkF,CAAC,CAAC;QAClG,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { WorktreeEntry } from '../types/index.js';
|
|
2
|
+
export declare class WorktreeManager {
|
|
3
|
+
private citadelRoot;
|
|
4
|
+
constructor(citadelRoot: string);
|
|
5
|
+
/** Resolve outpost repo path from slug via outposts.json */
|
|
6
|
+
private resolveOutpostPath;
|
|
7
|
+
/** Create a worktree for a citizen task */
|
|
8
|
+
create(slug: string, _citizenId: string, taskId: string): Promise<string>;
|
|
9
|
+
/** Remove a worktree by task ID */
|
|
10
|
+
remove(slug: string, taskId: string): Promise<void>;
|
|
11
|
+
/** Check if a worktree exists for a task */
|
|
12
|
+
exists(slug: string, taskId: string): Promise<boolean>;
|
|
13
|
+
/** List all worktrees for an outpost (parse git worktree list --porcelain) */
|
|
14
|
+
list(slug: string): Promise<WorktreeEntry[]>;
|
|
15
|
+
/** Find orphaned worktrees (exist on disk but not in a provided set of active task IDs) */
|
|
16
|
+
listOrphaned(slug: string, activeTaskIds: Set<string>): Promise<WorktreeEntry[]>;
|
|
17
|
+
/** Prune all orphaned worktrees */
|
|
18
|
+
pruneOrphaned(slug: string, activeTaskIds: Set<string>): Promise<number>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=worktree-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree-manager.d.ts","sourceRoot":"","sources":["../../src/core/worktree-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAmB,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIxE,qBAAa,eAAe;IACd,OAAO,CAAC,WAAW;gBAAX,WAAW,EAAE,MAAM;IAEvC,4DAA4D;YAC9C,kBAAkB;IAUhC,2CAA2C;IACrC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQ/E,mCAAmC;IAC7B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQzD,4CAA4C;IACtC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAW5D,8EAA8E;IACxE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAMlD,2FAA2F;IACrF,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAatF,mCAAmC;IAC7B,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;CAc/E"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { execFile as execFileCb } from 'node:child_process';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
const execFile = promisify(execFileCb);
|
|
6
|
+
export class WorktreeManager {
|
|
7
|
+
citadelRoot;
|
|
8
|
+
constructor(citadelRoot) {
|
|
9
|
+
this.citadelRoot = citadelRoot;
|
|
10
|
+
}
|
|
11
|
+
/** Resolve outpost repo path from slug via outposts.json */
|
|
12
|
+
async resolveOutpostPath(slug) {
|
|
13
|
+
const raw = await fs.readFile(path.join(this.citadelRoot, '.citadel', 'outposts.json'), 'utf-8');
|
|
14
|
+
const registry = JSON.parse(raw);
|
|
15
|
+
const entry = registry.outposts.find(o => o.slug === slug);
|
|
16
|
+
if (!entry)
|
|
17
|
+
throw new Error(`Outpost "${slug}" not found in registry`);
|
|
18
|
+
return entry.path;
|
|
19
|
+
}
|
|
20
|
+
/** Create a worktree for a citizen task */
|
|
21
|
+
async create(slug, _citizenId, taskId) {
|
|
22
|
+
const repoRoot = await this.resolveOutpostPath(slug);
|
|
23
|
+
const worktreePath = path.join(repoRoot, 'worktrees', 'citizen', taskId);
|
|
24
|
+
const branch = `citizen/${taskId}`;
|
|
25
|
+
await execFile('git', ['worktree', 'add', '-b', branch, worktreePath], { cwd: repoRoot });
|
|
26
|
+
return worktreePath;
|
|
27
|
+
}
|
|
28
|
+
/** Remove a worktree by task ID */
|
|
29
|
+
async remove(slug, taskId) {
|
|
30
|
+
const repoRoot = await this.resolveOutpostPath(slug);
|
|
31
|
+
const worktreePath = path.join(repoRoot, 'worktrees', 'citizen', taskId);
|
|
32
|
+
await execFile('git', ['worktree', 'remove', '--force', worktreePath], { cwd: repoRoot });
|
|
33
|
+
// Prune stale worktree metadata
|
|
34
|
+
await execFile('git', ['worktree', 'prune'], { cwd: repoRoot });
|
|
35
|
+
}
|
|
36
|
+
/** Check if a worktree exists for a task */
|
|
37
|
+
async exists(slug, taskId) {
|
|
38
|
+
const repoRoot = await this.resolveOutpostPath(slug);
|
|
39
|
+
const worktreePath = path.join(repoRoot, 'worktrees', 'citizen', taskId);
|
|
40
|
+
try {
|
|
41
|
+
await fs.access(worktreePath);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** List all worktrees for an outpost (parse git worktree list --porcelain) */
|
|
49
|
+
async list(slug) {
|
|
50
|
+
const repoRoot = await this.resolveOutpostPath(slug);
|
|
51
|
+
const { stdout } = await execFile('git', ['worktree', 'list', '--porcelain'], { cwd: repoRoot });
|
|
52
|
+
return parseWorktreeList(stdout);
|
|
53
|
+
}
|
|
54
|
+
/** Find orphaned worktrees (exist on disk but not in a provided set of active task IDs) */
|
|
55
|
+
async listOrphaned(slug, activeTaskIds) {
|
|
56
|
+
const entries = await this.list(slug);
|
|
57
|
+
const repoRoot = await this.resolveOutpostPath(slug);
|
|
58
|
+
const citizenDir = path.resolve(repoRoot, 'worktrees', 'citizen');
|
|
59
|
+
// Only consider worktrees under worktrees/citizen/
|
|
60
|
+
return entries.filter(e => {
|
|
61
|
+
const resolved = path.resolve(e.path);
|
|
62
|
+
if (!resolved.startsWith(citizenDir))
|
|
63
|
+
return false;
|
|
64
|
+
const taskId = path.basename(resolved);
|
|
65
|
+
return !activeTaskIds.has(taskId);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Prune all orphaned worktrees */
|
|
69
|
+
async pruneOrphaned(slug, activeTaskIds) {
|
|
70
|
+
const orphaned = await this.listOrphaned(slug, activeTaskIds);
|
|
71
|
+
for (const entry of orphaned) {
|
|
72
|
+
const repoRoot = await this.resolveOutpostPath(slug);
|
|
73
|
+
try {
|
|
74
|
+
await execFile('git', ['worktree', 'remove', '--force', entry.path], { cwd: repoRoot });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// best effort
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const repoRoot = await this.resolveOutpostPath(slug);
|
|
81
|
+
await execFile('git', ['worktree', 'prune'], { cwd: repoRoot });
|
|
82
|
+
return orphaned.length;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Parse `git worktree list --porcelain` output */
|
|
86
|
+
function parseWorktreeList(output) {
|
|
87
|
+
const entries = [];
|
|
88
|
+
let current = {};
|
|
89
|
+
for (const line of output.split('\n')) {
|
|
90
|
+
if (line.startsWith('worktree ')) {
|
|
91
|
+
current.path = line.slice('worktree '.length);
|
|
92
|
+
}
|
|
93
|
+
else if (line.startsWith('HEAD ')) {
|
|
94
|
+
current.head = line.slice('HEAD '.length);
|
|
95
|
+
}
|
|
96
|
+
else if (line.startsWith('branch ')) {
|
|
97
|
+
current.branch = line.slice('branch '.length);
|
|
98
|
+
}
|
|
99
|
+
else if (line === 'prunable') {
|
|
100
|
+
current.prunable = true;
|
|
101
|
+
}
|
|
102
|
+
else if (line.trim() === '' && current.path) {
|
|
103
|
+
entries.push({
|
|
104
|
+
path: current.path,
|
|
105
|
+
branch: current.branch ?? '',
|
|
106
|
+
head: current.head ?? '',
|
|
107
|
+
prunable: current.prunable ?? false,
|
|
108
|
+
});
|
|
109
|
+
current = {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Handle last entry if no trailing newline
|
|
113
|
+
if (current.path) {
|
|
114
|
+
entries.push({
|
|
115
|
+
path: current.path,
|
|
116
|
+
branch: current.branch ?? '',
|
|
117
|
+
head: current.head ?? '',
|
|
118
|
+
prunable: current.prunable ?? false,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return entries;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=worktree-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree-manager.js","sourceRoot":"","sources":["../../src/core/worktree-manager.ts"],"names":[],"mappings":"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"}
|