Skip to content

Commit 4ea8a91

Browse files
authored
chore: harden release — green suite, lint 0, ANSI unification (INT-2271, INT-2260) (#190)
* test(service): fix stale provider-override boot tests + harden release (INT-2271) The 2 failing service.test.ts tests were stale, not a regression: the provider- override reapply lives inside the `if (config.autonomous?.enabled)` block (it calls runnerInstance.switchProvider, which needs a runner), but the tests drove it with a non-autonomous config — one spread `mockConfig.autonomous` (undefined → {}), the other asserted readProviderOverride on a plain boot. - 'reapplies override on boot' now uses mockAutonomousConfig (+ adapter override). - 'initialize all required modules' drops the autonomous-only readProviderOverride assertion (the reapply is covered by the two override-specific tests). - service.test.ts: 37/37 green. Also harden the release pipeline now that the suite is green: - package.json: postbuild `chmod +x dist/cli.js` — a clean `rm -rf dist && build` otherwise drops the exec bit and the global CLI hits "permission denied". - ci.yml: promote the test job to a hard gate (drop continue-on-error). * refactor: clean all lint warnings + finish ANSI unification (INT-2260, INT-2270) Now that the suite is green, make lint meaningful and finish the status-color consistency work so the hard CI/release gates hold. Lint (36 warnings → 0): - Remove unused imports / variables across 11 files (issues bridges, discordCore, chatMemory, scanner's recursion-only param, cli.ts self-registering command binding, several test files). - Prefix intentionally-unused mock params with `_`; disable no-control-regex on the ANSI-stripping regex; two endsWith() swaps in bsDetector. ANSI unification (checkHandler.ts): - Replace the file's hand-rolled always-on ANSI `c` map (~108 call sites) with the shared NO_COLOR/TTY-safe helper from src/support/colors — `c.green('x')` + `status.ok/err/warn/glyph` where a status glyph fits. Non-status badges keep their glyph. Output is byte-identical under non-TTY, so substring tests hold. Full suite now green: typecheck / build / lint (0 warnings) / test (1306 passed).
1 parent 5b89425 commit 4ea8a91

14 files changed

Lines changed: 150 additions & 180 deletions

File tree

.github/workflows/ci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ jobs:
6969
cache: 'npm'
7070
- run: npm ci
7171
- run: npm test
72-
continue-on-error: true
7372

7473
loc-gate:
7574
name: LOC Warning (800 lines)

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"dev": "node --env-file=.env --import=tsx src/index.ts",
2121
"prestart": "pkill -f 'dist/index.js' 2>/dev/null || true",
2222
"build": "tsc",
23+
"postbuild": "chmod +x dist/cli.js",
2324
"start": "node --env-file=.env dist/index.js",
2425
"stop": "pkill -f 'node --env-file=.env.*openswarm' 2>/dev/null || echo 'No process running'",
2526
"lint": "oxlint src/",

src/automation/longRunningMonitor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ function safeCompileRegex(pattern: string | undefined): RegExp | null {
4545
// Argv validation: reject null bytes, newlines, and other control chars.
4646
// Because we never spawn a shell, shell metacharacters are inert — only
4747
// control characters meaningfully change behavior (e.g. null byte truncation).
48+
// eslint-disable-next-line no-control-regex
4849
const ARGV_SAFE = /^[^\x00-\x1F\x7F]+$/;
4950

5051
// Program allowlist for monitor checks. Anything outside this list — or any

src/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ program
513513

514514
// openswarm check annotate <qualifiedName>
515515

516-
const checkAnnotate = program
516+
program
517517
.command('annotate')
518518
.description('Annotate a code entity in the registry')
519519
.argument('<qualifiedName>', 'Entity qualified name (file::name) or search term')

src/cli/checkHandler.ts

Lines changed: 114 additions & 127 deletions
Large diffs are not rendered by default.

src/core/config.test.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3-
import { join } from 'node:path';
2+
import { existsSync, readFileSync } from 'node:fs';
43
import { homedir } from 'node:os';
54
import {
65
loadConfig,
76
validateConfig,
87
createAgentSession,
98
generateSampleConfig,
10-
type RawConfig,
119
} from './config.js';
1210
import * as fs from 'node:fs';
1311

@@ -22,8 +20,6 @@ vi.mock('node:fs', async () => {
2220
});
2321

2422
describe('config', () => {
25-
const testDir = '/tmp/test-config';
26-
2723
beforeEach(() => {
2824
vi.clearAllMocks();
2925
process.env.DISCORD_TOKEN = 'test-discord-token';
@@ -69,7 +65,7 @@ agents:
6965
expect(config).toBeDefined();
7066
expect(config.agents).toHaveLength(1);
7167
expect(config.agents[0].name).toBe('main');
72-
} catch (err) {
68+
} catch {
7369
// YAML parsing might fail in test environment, but we're testing the function exists
7470
}
7571
});
@@ -102,7 +98,7 @@ agents:
10298
const config = loadConfig('/tmp/config.json');
10399
expect(config).toBeDefined();
104100
expect(config.agents).toHaveLength(1);
105-
} catch (err) {
101+
} catch {
106102
// JSON parsing might fail in test environment
107103
}
108104
});
@@ -257,7 +253,7 @@ agents:
257253
const config = loadConfig('/tmp/config.yaml');
258254
// Path should be expanded
259255
expect(config.agents[0].projectPath).toContain(homedir());
260-
} catch (err) {
256+
} catch {
261257
// Might fail due to YAML parsing or validation
262258
}
263259
});
@@ -293,7 +289,7 @@ agents:
293289
const config = loadConfig('/tmp/config.yaml');
294290
// After substitution, token should be replaced
295291
expect(config.discordToken).toBeDefined();
296-
} catch (err) {
292+
} catch {
297293
// Validation might fail but substitution happened
298294
}
299295

@@ -323,7 +319,7 @@ agents:
323319
const config = loadConfig('/tmp/config.yaml');
324320
// Should use default value
325321
expect(config.discordToken).toBeDefined();
326-
} catch (err) {
322+
} catch {
327323
// Validation might fail
328324
}
329325
});
@@ -678,7 +674,7 @@ agents:
678674
// Defaults should be applied
679675
expect(config.agents[0].enabled).toBe(true);
680676
expect(config.agents[0].paused).toBe(false);
681-
} catch (err) {
677+
} catch {
682678
// Validation might fail
683679
}
684680
});
@@ -713,7 +709,7 @@ agents:
713709
try {
714710
const config = loadConfig('/tmp/config.yaml');
715711
expect(config.githubRepos).toEqual(['owner/repo1', 'owner/repo2']);
716-
} catch (err) {
712+
} catch {
717713
// Parsing might fail
718714
}
719715
});
@@ -739,7 +735,7 @@ agents:
739735
const config = loadConfig('/tmp/config.yaml');
740736
// Should have default heartbeat interval
741737
expect(config.defaultHeartbeatInterval).toBeGreaterThan(0);
742-
} catch (err) {
738+
} catch {
743739
// Parsing might fail
744740
}
745741
});
@@ -770,7 +766,7 @@ agents:
770766
try {
771767
const config = loadConfig('/tmp/config.yaml');
772768
expect(config).toBeDefined();
773-
} catch (err) {
769+
} catch {
774770
// Parsing might fail
775771
}
776772
});
@@ -796,7 +792,7 @@ agents:
796792
try {
797793
const config = loadConfig('/tmp/config.yaml');
798794
expect(config.discordToken.length).toBeGreaterThan(500);
799-
} catch (err) {
795+
} catch {
800796
// Parsing might fail
801797
}
802798
});
@@ -824,7 +820,7 @@ agents:
824820
try {
825821
const config = loadConfig('/tmp/config.yaml');
826822
expect(config.agents[0].heartbeatInterval).toBe(1800000);
827-
} catch (err) {
823+
} catch {
828824
// Parsing might fail
829825
}
830826
});
@@ -853,7 +849,7 @@ agents:
853849
try {
854850
const config = loadConfig('/tmp/config.yaml');
855851
expect(config.agents.length).toBe(3);
856-
} catch (err) {
852+
} catch {
857853
// Parsing might fail
858854
}
859855
});

src/core/service.test.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ vi.mock('../automation/prProcessor.js', () => {
8181
stop = vi.fn();
8282
}
8383
return {
84-
PRProcessor: vi.fn((...args) => new MockPRProcessor()),
84+
PRProcessor: vi.fn((..._args) => new MockPRProcessor()),
8585
};
8686
});
8787

@@ -137,7 +137,7 @@ vi.mock('../memory/compaction.js', () => ({
137137
}));
138138

139139
vi.mock('croner', () => ({
140-
Cron: vi.fn((pattern, fn) => ({
140+
Cron: vi.fn((_pattern, _fn) => ({
141141
stop: vi.fn(),
142142
})),
143143
}));
@@ -202,12 +202,12 @@ describe('service', () => {
202202

203203
vi.mocked(readProviderOverride).mockReturnValue('gpt');
204204

205+
// The override reapply lives inside the autonomous-start block (it calls
206+
// runnerInstance.switchProvider), so the config must enable autonomous mode.
207+
// (INT-2271)
205208
const config = {
206-
...mockConfig,
209+
...mockAutonomousConfig,
207210
adapter: 'claude',
208-
autonomous: {
209-
...mockConfig.autonomous,
210-
},
211211
} as SwarmConfig;
212212

213213
await startService(config);
@@ -247,7 +247,9 @@ describe('service', () => {
247247
await startService(mockConfig);
248248

249249
expect(setDefaultAdapter).toHaveBeenCalledWith('claude');
250-
expect(readProviderOverride).toHaveBeenCalled();
250+
// readProviderOverride is only consulted in the autonomous-start block (it
251+
// feeds runnerInstance.switchProvider); a non-autonomous boot doesn't call
252+
// it. Its reapply is covered by the two override-specific tests. (INT-2271)
251253
expect(initLocale).toHaveBeenCalled();
252254
expect(initLinear).toHaveBeenCalledWith(
253255
mockConfig.linearApiKey,

src/core/traceCollector.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ describe('TraceCollector', () => {
298298

299299
it('should handle span metadata with complex structures', () => {
300300
const traceId = collector.startTrace('session');
301-
const spanId = collector.startSpan(traceId, 'worker', undefined, {
301+
collector.startSpan(traceId, 'worker', undefined, {
302302
nested: {
303303
object: {
304304
with: {
@@ -324,7 +324,7 @@ describe('TraceCollector', () => {
324324
it('should handle very large metadata values', () => {
325325
const traceId = collector.startTrace('session');
326326
const largeString = 'A'.repeat(100000);
327-
const spanId = collector.startSpan(traceId, 'worker', undefined, {
327+
collector.startSpan(traceId, 'worker', undefined, {
328328
largeData: largeString,
329329
});
330330

@@ -336,7 +336,7 @@ describe('TraceCollector', () => {
336336
it('should handle special characters in span names and traces', () => {
337337
const specialName = 'session-with-special_chars';
338338
const traceId = collector.startTrace(specialName);
339-
const spanId = collector.startSpan(traceId, 'tool:git:special_chars');
339+
collector.startSpan(traceId, 'tool:git:special_chars');
340340

341341
const trace = collector.getTrace(traceId);
342342
expect(trace!.name).toBe(specialName);
@@ -417,7 +417,7 @@ describe('TraceCollector', () => {
417417

418418
it('should handle getChildSpans with non-existent parent', () => {
419419
const traceId = collector.startTrace('session');
420-
const spanId = collector.startSpan(traceId, 'worker')!;
420+
collector.startSpan(traceId, 'worker')!;
421421

422422
const children = collector.getChildSpans(traceId, 'non-existent-parent');
423423
expect(children).toEqual([]);
@@ -445,7 +445,7 @@ describe('TraceCollector', () => {
445445
const completed1 = collector.startTrace('completed1');
446446
const completed2 = collector.startTrace('completed2');
447447
const failed1 = collector.startTrace('failed1');
448-
const running1 = collector.startTrace('running1');
448+
collector.startTrace('running1');
449449
const failed2 = collector.startTrace('failed2');
450450

451451
collector.endTrace(completed1, 'completed');
@@ -503,7 +503,7 @@ describe('TraceCollector', () => {
503503

504504
it('should handle empty span name', () => {
505505
const traceId = collector.startTrace('session');
506-
const spanId = collector.startSpan(traceId, '');
506+
collector.startSpan(traceId, '');
507507

508508
const trace = collector.getTrace(traceId);
509509
expect(trace!.spans[0].name).toBe('');

src/discord/discordCore.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
EmbedBuilder,
1414
ThreadChannel,
1515
} from 'discord.js';
16-
import { spawn } from 'node:child_process';
1716
import fs from 'node:fs/promises';
1817
import type { SwarmEvent, AgentStatus } from '../core/types.js';
1918
import { getAdapter, spawnCli } from '../adapters/index.js';

src/issues/linearBridge.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ export async function syncStatusToLinear(
162162
// ============ 매핑 유틸 ============
163163

164164
function findByLinearId(store: SqliteIssueStore, linearId: string): Issue | null {
165-
const { issues } = store.listIssues({ limit: 1, offset: 0 });
166165
// linear ID로 검색하려면 직접 쿼리가 필요
167166
// 간단히 전체 목록에서 찾기 (비효율적이지만 동기화는 드물게 실행)
168167
const { issues: all } = store.listIssues({ limit: 1000, offset: 0 });

0 commit comments

Comments
 (0)