aio-connect 1.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.
@@ -0,0 +1,129 @@
1
+ // selfcheck.js — one self-check per critical invariant (node --test, no framework)
2
+ import { test } from 'node:test';
3
+ import assert from 'node:assert/strict';
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+
8
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'aio-selfcheck-'));
9
+ process.env.AIO_STATE_DIR = path.join(tmp, 'state'); // isolate ~/.aio side effects
10
+
11
+ const { injectBlock, stripBlock, buildBlock, ensureJsonEntry, ensureTomlEntry } = await import('../src/write.js');
12
+ const { scanRepos, parseToolsIndex, detectAgents } = await import('../src/scan.js');
13
+ const { ART, banner } = await import('../src/banner.js');
14
+
15
+ test('injectBlock: exactly one block, refreshed in place, prefix kept (FR4)', () => {
16
+ const f = path.join(tmp, 'AGENTS.md');
17
+ fs.writeFileSync(f, '# My rules\nkeep me\n');
18
+ assert.equal(injectBlock(f, 'body-v1', 't').status, 'injected');
19
+ assert.equal(injectBlock(f, 'body-v2', 't').status, 'updated');
20
+ assert.equal(injectBlock(f, 'body-v2', 't').status, 'unchanged');
21
+ const txt = fs.readFileSync(f, 'utf8');
22
+ assert.equal(txt.split('aio:auto-config:v1:start').length - 1, 1);
23
+ assert.ok(txt.includes('body-v2'), 'new body present');
24
+ assert.ok(!txt.includes('body-v1'), 'old body gone');
25
+ assert.ok(txt.startsWith('# My rules\nkeep me'), 'user content preserved');
26
+ });
27
+
28
+ test('stripBlock: removes injected block, restores original (FR7)', () => {
29
+ const f = path.join(tmp, 'rules2.md');
30
+ const orig = 'keep this\n';
31
+ fs.writeFileSync(f, orig);
32
+ injectBlock(f, 'B', 't');
33
+ assert.equal(stripBlock(f), 'removed');
34
+ assert.equal(fs.readFileSync(f, 'utf8'), orig);
35
+ assert.equal(stripBlock(f), 'absent');
36
+ });
37
+
38
+ test('buildBlock: mandatory usage-disclosure line (FR6)', () => {
39
+ const b = buildBlock({ version: '1.0.0', manifestPath: '/tmp/m.md', dataDir: null });
40
+ assert.ok(b.includes('[aio] Using <name> (<type>) — <function>'));
41
+ assert.ok(b.includes('NO slash-commands'));
42
+ assert.ok(b.includes('/tmp/m.md'));
43
+ });
44
+
45
+ test('scanRepos: URL from real .git/config, non-repos listed as local-only', () => {
46
+ const rd = path.join(tmp, 'repos');
47
+ fs.mkdirSync(path.join(rd, 'proj-a', '.git'), { recursive: true });
48
+ fs.writeFileSync(
49
+ path.join(rd, 'proj-a', '.git', 'config'),
50
+ '[remote "origin"]\n\turl = https://github.com/foo/bar.git\n'
51
+ );
52
+ fs.mkdirSync(path.join(rd, 'not-a-repo'));
53
+ const repos = scanRepos(rd);
54
+ assert.equal(repos.length, 2);
55
+ assert.equal(repos.find((r) => r.name === 'proj-a').url, 'https://github.com/foo/bar');
56
+ assert.equal(repos.find((r) => r.name === 'not-a-repo').url, '');
57
+ });
58
+
59
+ test('parseToolsIndex: tables → rows, headers/separators skipped', () => {
60
+ const md = [
61
+ '# Registry',
62
+ '## 1. AI CODING AGENTS (CLI, langsung ketik di terminal)',
63
+ '| Command | Version | Catatan |',
64
+ '|---|---|---|',
65
+ '| opencode | v1.18 | provider lokal |',
66
+ '## 6. LAINNYA',
67
+ '| **Item** | **Lokasi/Akses** |',
68
+ '|---|---|',
69
+ '| **GSAP** `3.15` | `D:\\Tools\\gsap` |',
70
+ '',
71
+ ].join('\n');
72
+ const mdFile = path.join(tmp, 'TOOLS-INDEX.md');
73
+ fs.writeFileSync(mdFile, md);
74
+ const rows = parseToolsIndex(mdFile);
75
+ assert.equal(rows.length, 2);
76
+ assert.equal(rows[0].name, 'opencode');
77
+ assert.equal(rows[0].category, 'AI CODING AGENTS');
78
+ assert.ok(rows[0].description.includes('provider lokal'));
79
+ assert.equal(rows[1].name, 'GSAP 3.15');
80
+ assert.equal(rows[1].category, 'LAINNYA');
81
+ });
82
+
83
+ test('ensureJsonEntry: create=true bootstraps file inside existing dir (gemini)', () => {
84
+ const dir = path.join(tmp, '.gemini');
85
+ fs.mkdirSync(dir, { recursive: true });
86
+ const f = path.join(dir, 'settings.json');
87
+ assert.equal(
88
+ ensureJsonEntry(f, 'codebase-memory-mcp', { command: '/bin/mcp' }, 't', 'mcpServers', true).status,
89
+ 'added'
90
+ );
91
+ assert.equal(
92
+ ensureJsonEntry(f, 'codebase-memory-mcp', { command: '/bin/mcp' }, 't', 'mcpServers', true).status,
93
+ 'present'
94
+ );
95
+ assert.ok(JSON.parse(fs.readFileSync(f, 'utf8')).mcpServers['codebase-memory-mcp']);
96
+ // no parent dir → still skipped, nothing created
97
+ assert.equal(
98
+ ensureJsonEntry(path.join(tmp, 'no-such-dir', 'x.json'), 'k', {}, 't', 's', true).status,
99
+ 'file not found — skipped'
100
+ );
101
+ });
102
+
103
+ test('ensureTomlEntry: appends [mcp_servers.key] exactly once, idempotent (codex)', () => {
104
+ const f = path.join(tmp, 'config.toml');
105
+ fs.writeFileSync(f, 'model = "gpt"\n');
106
+ const e = { command: 'C:\\bin\\mcp.exe' };
107
+ assert.equal(ensureTomlEntry(f, 'codebase-memory-mcp', e, 't').status, 'added');
108
+ assert.equal(ensureTomlEntry(f, 'codebase-memory-mcp', e, 't').status, 'present');
109
+ const txt = fs.readFileSync(f, 'utf8');
110
+ assert.equal(txt.split('[mcp_servers.codebase-memory-mcp]').length - 1, 1);
111
+ assert.ok(txt.includes('model = "gpt"'), 'existing TOML kept');
112
+ assert.ok(txt.includes('command = "C:\\\\bin\\\\mcp.exe"'), 'command escaped');
113
+ });
114
+
115
+ test('detectAgents: PATH binary OR instruction dir → found (codex/gemini)', () => {
116
+ const list = detectAgents();
117
+ assert.ok(list.some((a) => a.name === 'codex'));
118
+ assert.ok(list.some((a) => a.name === 'gemini'));
119
+ for (const a of list) assert.ok(typeof a.found === 'boolean');
120
+ });
121
+
122
+ test('banner: strict grid — every ART row same width, no crooked letters', () => {
123
+ const w = ART[0].length;
124
+ assert.ok(ART.length >= 4);
125
+ for (const [i, line] of ART.entries()) {
126
+ assert.equal(line.length, w, `row ${i} width ${line.length} != ${w}`);
127
+ }
128
+ assert.ok(banner().includes('all-in-one-repo v'));
129
+ });