()
| 101 | JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }), |
| 102 | JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} }), |
| 103 | JSON.stringify({ jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: 'citadel_status', arguments: { includeFiles: true } } }), |
| 104 | JSON.stringify({ jsonrpc: '2.0', id: 4, method: 'resources/read', params: { uri: 'citadel://status' } }), |
| 105 | '', |
| 106 | ].join('\n'); |
| 107 | const result = spawnSync(process.execPath, [path.join(CITADEL_ROOT, 'mcp-servers', 'citadel-state', 'index.js')], { |
| 108 | input, |
| 109 | env: { ...process.env, CITADEL_PROJECT_ROOT: tmp }, |
| 110 | encoding: 'utf8', |
| 111 | timeout: 5000, |
| 112 | }); |
| 113 | assert.equal(result.status, 0, result.stderr); |
| 114 | const messages = result.stdout.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line)); |
| 115 | assert(messages.find((msg) => msg.id === 2).result.tools.some((tool) => tool.name === 'citadel_status')); |
| 116 | const statusText = messages.find((msg) => msg.id === 3).result.content[0].text; |
| 117 | assert(statusText.includes('"campaigns": 1'), 'citadel_status should report campaign count'); |
| 118 | assert(messages.find((msg) => msg.id === 4).result.contents[0].text.includes('"planningExists": true')); |
| 119 | } finally { |
| 120 | fs.rmSync(tmp, { recursive: true, force: true }); |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | function testBridgeUtilities() { |
| 125 | const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'citadel-native-bridges-')); |
| 126 | try { |
| 127 | const automation = createAutomationPlan({ |
| 128 | projectRoot: tmp, |
| 129 | type: 'daemon', |
| 130 | command: '/daemon tick', |
| 131 | cadence: 'every 30 minutes', |
| 132 | now: '2026-06-01T00:00:00.000Z', |
| 133 | write: true, |
| 134 | }); |
| 135 | assert(fs.existsSync(path.join(tmp, '.planning', 'codex-automations', `${automation.id}.json`))); |
| 136 | assert(automation.prompt.includes('.planning/daemon.json')); |
| 137 | assert(automation.loopId.startsWith('loop-codex-daemon-')); |
| 138 | assert(automation.loopStopConditions.includes('budget-exhausted')); |
| 139 | |
| 140 | const prPlan = createPrReviewPlan({ |
| 141 | projectRoot: tmp, |
| 142 | repo: 'owner/repo', |
| 143 | prNumber: 42, |
| 144 | risk: 'high', |
| 145 | changedFiles: 25, |
| 146 | write: true, |
| 147 | }); |
| 148 | assert.equal(prPlan.decision, 'combined'); |
| 149 | assert(prPlan.followUpPrompt.includes('@codex review')); |
| 150 | |
| 151 | const artifact = recordAppArtifact({ |
| 152 | projectRoot: tmp, |
| 153 | kind: 'screenshot', |
| 154 | path: '.planning/screenshots/qa-flow-1.png', |
| 155 | workflow: 'qa', |
| 156 | status: 'pass', |
| 157 | }); |
| 158 | assert.equal(artifact.workflow, 'qa'); |
| 159 | assert.equal(readAppArtifacts(tmp).length, 1); |
| 160 |
no test coverage detected