@coralai/sps-cli 0.49.0 → 0.49.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.
Files changed (155) hide show
  1. package/dist/console-assets/assets/{index-C7skV1yt.js → index-Co0H5e5_.js} +14 -14
  2. package/dist/console-assets/index.html +1 -1
  3. package/package.json +1 -1
  4. package/dist/commands/cardMarkComplete.test.d.ts +0 -2
  5. package/dist/commands/cardMarkComplete.test.d.ts.map +0 -1
  6. package/dist/commands/cardMarkComplete.test.js +0 -142
  7. package/dist/commands/cardMarkComplete.test.js.map +0 -1
  8. package/dist/commands/cardMarkStarted.test.d.ts +0 -2
  9. package/dist/commands/cardMarkStarted.test.d.ts.map +0 -1
  10. package/dist/commands/cardMarkStarted.test.js +0 -98
  11. package/dist/commands/cardMarkStarted.test.js.map +0 -1
  12. package/dist/commands/projectInit.test.d.ts +0 -2
  13. package/dist/commands/projectInit.test.d.ts.map +0 -1
  14. package/dist/commands/projectInit.test.js +0 -126
  15. package/dist/commands/projectInit.test.js.map +0 -1
  16. package/dist/commands/tick-heartbeat.test.d.ts +0 -2
  17. package/dist/commands/tick-heartbeat.test.d.ts.map +0 -1
  18. package/dist/commands/tick-heartbeat.test.js +0 -89
  19. package/dist/commands/tick-heartbeat.test.js.map +0 -1
  20. package/dist/console-server/routes/chat.test.d.ts +0 -2
  21. package/dist/console-server/routes/chat.test.d.ts.map +0 -1
  22. package/dist/console-server/routes/chat.test.js +0 -131
  23. package/dist/console-server/routes/chat.test.js.map +0 -1
  24. package/dist/console-server/routes/projects.test.d.ts +0 -2
  25. package/dist/console-server/routes/projects.test.d.ts.map +0 -1
  26. package/dist/console-server/routes/projects.test.js +0 -150
  27. package/dist/console-server/routes/projects.test.js.map +0 -1
  28. package/dist/console-server/routes/system.test.d.ts +0 -2
  29. package/dist/console-server/routes/system.test.d.ts.map +0 -1
  30. package/dist/console-server/routes/system.test.js +0 -107
  31. package/dist/console-server/routes/system.test.js.map +0 -1
  32. package/dist/core/checklist.test.d.ts +0 -2
  33. package/dist/core/checklist.test.d.ts.map +0 -1
  34. package/dist/core/checklist.test.js +0 -74
  35. package/dist/core/checklist.test.js.map +0 -1
  36. package/dist/core/config.test.d.ts +0 -2
  37. package/dist/core/config.test.d.ts.map +0 -1
  38. package/dist/core/config.test.js +0 -352
  39. package/dist/core/config.test.js.map +0 -1
  40. package/dist/core/lock.test.d.ts +0 -2
  41. package/dist/core/lock.test.d.ts.map +0 -1
  42. package/dist/core/lock.test.js +0 -118
  43. package/dist/core/lock.test.js.map +0 -1
  44. package/dist/core/markerFile.test.d.ts +0 -2
  45. package/dist/core/markerFile.test.d.ts.map +0 -1
  46. package/dist/core/markerFile.test.js +0 -111
  47. package/dist/core/markerFile.test.js.map +0 -1
  48. package/dist/core/queue.test.d.ts +0 -2
  49. package/dist/core/queue.test.d.ts.map +0 -1
  50. package/dist/core/queue.test.js +0 -114
  51. package/dist/core/queue.test.js.map +0 -1
  52. package/dist/core/sessionCleanup.test.d.ts +0 -2
  53. package/dist/core/sessionCleanup.test.d.ts.map +0 -1
  54. package/dist/core/sessionCleanup.test.js +0 -158
  55. package/dist/core/sessionCleanup.test.js.map +0 -1
  56. package/dist/core/shellEnv.test.d.ts +0 -2
  57. package/dist/core/shellEnv.test.d.ts.map +0 -1
  58. package/dist/core/shellEnv.test.js +0 -116
  59. package/dist/core/shellEnv.test.js.map +0 -1
  60. package/dist/core/skillStore.test.d.ts +0 -2
  61. package/dist/core/skillStore.test.d.ts.map +0 -1
  62. package/dist/core/skillStore.test.js +0 -203
  63. package/dist/core/skillStore.test.js.map +0 -1
  64. package/dist/core/state.test.d.ts +0 -2
  65. package/dist/core/state.test.d.ts.map +0 -1
  66. package/dist/core/state.test.js +0 -336
  67. package/dist/core/state.test.js.map +0 -1
  68. package/dist/engines/CloseoutEngine.d.ts +0 -72
  69. package/dist/engines/CloseoutEngine.d.ts.map +0 -1
  70. package/dist/engines/CloseoutEngine.js +0 -648
  71. package/dist/engines/CloseoutEngine.js.map +0 -1
  72. package/dist/engines/EventHandler.test.d.ts +0 -2
  73. package/dist/engines/EventHandler.test.d.ts.map +0 -1
  74. package/dist/engines/EventHandler.test.js +0 -169
  75. package/dist/engines/EventHandler.test.js.map +0 -1
  76. package/dist/engines/ExecutionEngine.d.ts +0 -125
  77. package/dist/engines/ExecutionEngine.d.ts.map +0 -1
  78. package/dist/engines/ExecutionEngine.js +0 -766
  79. package/dist/engines/ExecutionEngine.js.map +0 -1
  80. package/dist/engines/MonitorEngine.test.d.ts +0 -2
  81. package/dist/engines/MonitorEngine.test.d.ts.map +0 -1
  82. package/dist/engines/MonitorEngine.test.js +0 -355
  83. package/dist/engines/MonitorEngine.test.js.map +0 -1
  84. package/dist/engines/engine-pipeline-adapter.test.d.ts +0 -17
  85. package/dist/engines/engine-pipeline-adapter.test.d.ts.map +0 -1
  86. package/dist/engines/engine-pipeline-adapter.test.js +0 -707
  87. package/dist/engines/engine-pipeline-adapter.test.js.map +0 -1
  88. package/dist/interfaces/HookProvider.d.ts +0 -9
  89. package/dist/interfaces/HookProvider.d.ts.map +0 -1
  90. package/dist/interfaces/HookProvider.js +0 -2
  91. package/dist/interfaces/HookProvider.js.map +0 -1
  92. package/dist/manager/completion-judge.test.d.ts +0 -17
  93. package/dist/manager/completion-judge.test.d.ts.map +0 -1
  94. package/dist/manager/completion-judge.test.js +0 -233
  95. package/dist/manager/completion-judge.test.js.map +0 -1
  96. package/dist/manager/integration-queue.d.ts +0 -71
  97. package/dist/manager/integration-queue.d.ts.map +0 -1
  98. package/dist/manager/integration-queue.js +0 -137
  99. package/dist/manager/integration-queue.js.map +0 -1
  100. package/dist/manager/integration-queue.test.d.ts +0 -17
  101. package/dist/manager/integration-queue.test.d.ts.map +0 -1
  102. package/dist/manager/integration-queue.test.js +0 -210
  103. package/dist/manager/integration-queue.test.js.map +0 -1
  104. package/dist/manager/pm-client.d.ts +0 -10
  105. package/dist/manager/pm-client.d.ts.map +0 -1
  106. package/dist/manager/pm-client.js +0 -260
  107. package/dist/manager/pm-client.js.map +0 -1
  108. package/dist/manager/resource-limiter.d.ts +0 -56
  109. package/dist/manager/resource-limiter.d.ts.map +0 -1
  110. package/dist/manager/resource-limiter.js +0 -116
  111. package/dist/manager/resource-limiter.js.map +0 -1
  112. package/dist/manager/resource-limiter.test.d.ts +0 -17
  113. package/dist/manager/resource-limiter.test.d.ts.map +0 -1
  114. package/dist/manager/resource-limiter.test.js +0 -118
  115. package/dist/manager/resource-limiter.test.js.map +0 -1
  116. package/dist/manager/supervisor.test.d.ts +0 -17
  117. package/dist/manager/supervisor.test.d.ts.map +0 -1
  118. package/dist/manager/supervisor.test.js +0 -216
  119. package/dist/manager/supervisor.test.js.map +0 -1
  120. package/dist/manager/worker-manager-impl.test.d.ts +0 -17
  121. package/dist/manager/worker-manager-impl.test.d.ts.map +0 -1
  122. package/dist/manager/worker-manager-impl.test.js +0 -446
  123. package/dist/manager/worker-manager-impl.test.js.map +0 -1
  124. package/dist/providers/PlaneTaskBackend.d.ts +0 -83
  125. package/dist/providers/PlaneTaskBackend.d.ts.map +0 -1
  126. package/dist/providers/PlaneTaskBackend.js +0 -461
  127. package/dist/providers/PlaneTaskBackend.js.map +0 -1
  128. package/dist/providers/TrelloTaskBackend.d.ts +0 -64
  129. package/dist/providers/TrelloTaskBackend.d.ts.map +0 -1
  130. package/dist/providers/TrelloTaskBackend.js +0 -298
  131. package/dist/providers/TrelloTaskBackend.js.map +0 -1
  132. package/dist/providers/adapters/acp-fs-handlers.test.d.ts +0 -2
  133. package/dist/providers/adapters/acp-fs-handlers.test.d.ts.map +0 -1
  134. package/dist/providers/adapters/acp-fs-handlers.test.js +0 -80
  135. package/dist/providers/adapters/acp-fs-handlers.test.js.map +0 -1
  136. package/dist/providers/adapters/acp-permissions.test.d.ts +0 -2
  137. package/dist/providers/adapters/acp-permissions.test.d.ts.map +0 -1
  138. package/dist/providers/adapters/acp-permissions.test.js +0 -103
  139. package/dist/providers/adapters/acp-permissions.test.js.map +0 -1
  140. package/dist/providers/adapters/acp-session-accumulator.test.d.ts +0 -2
  141. package/dist/providers/adapters/acp-session-accumulator.test.d.ts.map +0 -1
  142. package/dist/providers/adapters/acp-session-accumulator.test.js +0 -88
  143. package/dist/providers/adapters/acp-session-accumulator.test.js.map +0 -1
  144. package/dist/providers/adapters/acp-terminal-manager.test.d.ts +0 -2
  145. package/dist/providers/adapters/acp-terminal-manager.test.d.ts.map +0 -1
  146. package/dist/providers/adapters/acp-terminal-manager.test.js +0 -86
  147. package/dist/providers/adapters/acp-terminal-manager.test.js.map +0 -1
  148. package/dist/providers/outputParser.test.d.ts +0 -2
  149. package/dist/providers/outputParser.test.d.ts.map +0 -1
  150. package/dist/providers/outputParser.test.js +0 -185
  151. package/dist/providers/outputParser.test.js.map +0 -1
  152. package/dist/test-setup.d.ts +0 -2
  153. package/dist/test-setup.d.ts.map +0 -1
  154. package/dist/test-setup.js +0 -27
  155. package/dist/test-setup.js.map +0 -1
@@ -1,114 +0,0 @@
1
- /**
2
- * @module queue.test
3
- * @description 流水线队列读写操作的单元测试
4
- *
5
- * @author eddy
6
- * @organization wykj
7
- * @ownership wykj/eddy
8
- *
9
- * @created 2026-03-29
10
- * @updated 2026-04-03
11
- *
12
- * @role test
13
- * @layer core
14
- * @boundedContext pipeline
15
- */
16
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
17
- import { tmpdir } from 'node:os';
18
- import { join } from 'node:path';
19
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
20
- import { readQueue, removeFromQueue, writeQueue } from './queue.js';
21
- function makeTempDir() {
22
- return mkdtempSync(join(tmpdir(), 'sps-queue-test-'));
23
- }
24
- describe('readQueue', () => {
25
- let tempDir;
26
- beforeEach(() => { tempDir = makeTempDir(); });
27
- afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
28
- it('returns empty array when file does not exist', () => {
29
- expect(readQueue(join(tempDir, 'nonexistent.json'))).toEqual([]);
30
- });
31
- it('reads a valid queue file', () => {
32
- const file = join(tempDir, 'queue.json');
33
- writeFileSync(file, '[1, 2, 3, 4, 5]\n');
34
- expect(readQueue(file)).toEqual([1, 2, 3, 4, 5]);
35
- });
36
- it('returns empty array for invalid JSON', () => {
37
- const file = join(tempDir, 'bad.json');
38
- writeFileSync(file, 'NOT JSON');
39
- expect(readQueue(file)).toEqual([]);
40
- });
41
- it('returns empty array when file contains non-array JSON', () => {
42
- const file = join(tempDir, 'obj.json');
43
- writeFileSync(file, '{"not": "array"}');
44
- expect(readQueue(file)).toEqual([]);
45
- });
46
- it('filters out non-number values', () => {
47
- const file = join(tempDir, 'mixed.json');
48
- writeFileSync(file, '[1, "two", 3, null, 5, true]');
49
- expect(readQueue(file)).toEqual([1, 3, 5]);
50
- });
51
- it('handles empty array', () => {
52
- const file = join(tempDir, 'empty.json');
53
- writeFileSync(file, '[]');
54
- expect(readQueue(file)).toEqual([]);
55
- });
56
- });
57
- describe('writeQueue', () => {
58
- let tempDir;
59
- beforeEach(() => { tempDir = makeTempDir(); });
60
- afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
61
- it('writes a queue atomically', () => {
62
- const file = join(tempDir, 'queue.json');
63
- writeQueue(file, [10, 20, 30]);
64
- const raw = JSON.parse(readFileSync(file, 'utf-8'));
65
- expect(raw).toEqual([10, 20, 30]);
66
- });
67
- it('overwrites existing queue', () => {
68
- const file = join(tempDir, 'queue.json');
69
- writeQueue(file, [1, 2, 3]);
70
- writeQueue(file, [4, 5]);
71
- expect(readQueue(file)).toEqual([4, 5]);
72
- });
73
- it('writes empty array', () => {
74
- const file = join(tempDir, 'queue.json');
75
- writeQueue(file, []);
76
- expect(readQueue(file)).toEqual([]);
77
- });
78
- it('does not leave temp files', () => {
79
- const file = join(tempDir, 'queue.json');
80
- writeQueue(file, [1]);
81
- const { readdirSync } = require('node:fs');
82
- const files = readdirSync(tempDir);
83
- const tmpFiles = files.filter((f) => f.endsWith('.tmp'));
84
- expect(tmpFiles).toHaveLength(0);
85
- });
86
- });
87
- describe('removeFromQueue', () => {
88
- let tempDir;
89
- beforeEach(() => { tempDir = makeTempDir(); });
90
- afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
91
- it('removes a seq from the queue', () => {
92
- const file = join(tempDir, 'queue.json');
93
- writeQueue(file, [1, 2, 3, 4, 5]);
94
- removeFromQueue(file, 3);
95
- expect(readQueue(file)).toEqual([1, 2, 4, 5]);
96
- });
97
- it('does nothing when seq not in queue', () => {
98
- const file = join(tempDir, 'queue.json');
99
- writeQueue(file, [1, 2, 3]);
100
- removeFromQueue(file, 99);
101
- expect(readQueue(file)).toEqual([1, 2, 3]);
102
- });
103
- it('handles nonexistent file gracefully', () => {
104
- const file = join(tempDir, 'nofile.json');
105
- expect(() => removeFromQueue(file, 1)).not.toThrow();
106
- });
107
- it('removes duplicate occurrences', () => {
108
- const file = join(tempDir, 'queue.json');
109
- writeQueue(file, [1, 2, 2, 3]);
110
- removeFromQueue(file, 2);
111
- expect(readQueue(file)).toEqual([1, 3]);
112
- });
113
- });
114
- //# sourceMappingURL=queue.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"queue.test.js","sourceRoot":"","sources":["../../src/core/queue.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=sessionCleanup.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sessionCleanup.test.d.ts","sourceRoot":"","sources":["../../src/core/sessionCleanup.test.ts"],"names":[],"mappings":""}
@@ -1,158 +0,0 @@
1
- /**
2
- * @module sessionCleanup.test
3
- * @description 孤儿 ACP session 清理的单元测试
4
- *
5
- * @author eddy
6
- * @organization wykj
7
- * @ownership wykj/eddy
8
- *
9
- * @created 2026-04-17
10
- * @updated 2026-04-17
11
- *
12
- * @role test
13
- * @layer core
14
- * @boundedContext session-lifecycle
15
- */
16
- import { spawn } from 'node:child_process';
17
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
18
- import { tmpdir } from 'node:os';
19
- import { join } from 'node:path';
20
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
21
- import { Logger } from './logger.js';
22
- import { cleanupOrphanAcpSessions } from './sessionCleanup.js';
23
- function makeTempDir() {
24
- return mkdtempSync(join(tmpdir(), 'sps-session-cleanup-test-'));
25
- }
26
- function makeCtx(tempDir) {
27
- return {
28
- projectName: 'test',
29
- maxWorkers: 1,
30
- paths: { stateFile: join(tempDir, 'state.json') },
31
- };
32
- }
33
- function writeState(tempDir, sessions) {
34
- const state = {
35
- version: 3,
36
- updatedAt: new Date().toISOString(),
37
- updatedBy: 'test-setup',
38
- workers: {},
39
- leases: {},
40
- activeCards: {},
41
- worktreeEvidence: {},
42
- pendingPMActions: [],
43
- sessions,
44
- };
45
- writeFileSync(join(tempDir, 'state.json'), JSON.stringify(state));
46
- }
47
- function readSessions(tempDir) {
48
- const content = JSON.parse(readFileSync(join(tempDir, 'state.json'), 'utf-8'));
49
- return content.sessions ?? {};
50
- }
51
- /** Spawn a long-lived child process for use as a live orphan target. */
52
- function spawnVictim() {
53
- const child = spawn('sleep', ['30'], { detached: false, stdio: 'ignore' });
54
- if (!child.pid)
55
- throw new Error('failed to spawn test victim');
56
- return {
57
- pid: child.pid,
58
- kill: () => { try {
59
- child.kill('SIGKILL');
60
- }
61
- catch { /* noop */ } },
62
- };
63
- }
64
- describe('cleanupOrphanAcpSessions', () => {
65
- let tempDir;
66
- beforeEach(() => { tempDir = makeTempDir(); });
67
- afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
68
- it('clears stale session records whose pid is already dead', async () => {
69
- // Pid 1 (init) is always alive, but we want "already dead" — use a clearly
70
- // invalid pid (e.g. one that would fail isProcessAlive).
71
- // A PID so large it can't exist. Linux defaults cap at 4194304.
72
- const DEAD_PID = 9_999_999;
73
- writeState(tempDir, {
74
- 'worker-1': {
75
- slot: 'worker-1', tool: 'claude', sessionId: 'old-session',
76
- sessionName: 'sps-acp-test-worker-1', pid: DEAD_PID, cwd: '/tmp',
77
- status: 'idle', sessionState: 'ready', currentRun: null, pendingInput: null,
78
- createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
79
- lastSeenAt: null, lastPaneText: '',
80
- },
81
- });
82
- const ctx = makeCtx(tempDir);
83
- const log = new Logger('test', 'test');
84
- const result = await cleanupOrphanAcpSessions(ctx, log);
85
- expect(result.cleaned).toBe(1);
86
- expect(result.killed).toBe(0);
87
- expect(readSessions(tempDir)).toEqual({});
88
- });
89
- it('kills live orphan process and clears its record', async () => {
90
- const victim = spawnVictim();
91
- try {
92
- writeState(tempDir, {
93
- 'worker-1': {
94
- slot: 'worker-1', tool: 'claude', sessionId: 's1',
95
- sessionName: 'sps-acp-test-worker-1', pid: victim.pid, cwd: '/tmp',
96
- status: 'active', sessionState: 'busy', currentRun: null, pendingInput: null,
97
- createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
98
- lastSeenAt: null, lastPaneText: '',
99
- },
100
- });
101
- const ctx = makeCtx(tempDir);
102
- const log = new Logger('test', 'test');
103
- const result = await cleanupOrphanAcpSessions(ctx, log);
104
- expect(result.cleaned).toBe(1);
105
- expect(result.killed).toBe(1);
106
- expect(readSessions(tempDir)).toEqual({});
107
- // Give kill some time, then verify the victim is actually dead
108
- await new Promise((r) => setTimeout(r, 500));
109
- let alive = true;
110
- try {
111
- process.kill(victim.pid, 0);
112
- }
113
- catch {
114
- alive = false;
115
- }
116
- expect(alive).toBe(false);
117
- }
118
- finally {
119
- victim.kill();
120
- }
121
- });
122
- it('does NOT touch harness session-* slots', async () => {
123
- const harnessSession = {
124
- slot: 'session-my-chat', tool: 'claude', sessionId: 'chat-session',
125
- sessionName: 'sps-session-my-chat', pid: 9_999_999, cwd: '/tmp',
126
- status: 'idle', sessionState: 'ready', currentRun: null, pendingInput: null,
127
- createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
128
- lastSeenAt: null, lastPaneText: '',
129
- };
130
- writeState(tempDir, {
131
- 'worker-1': {
132
- slot: 'worker-1', tool: 'claude', sessionId: 'old',
133
- sessionName: 'sps-acp-test-worker-1', pid: 9_999_999, cwd: '/tmp',
134
- status: 'idle', sessionState: 'ready', currentRun: null, pendingInput: null,
135
- createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
136
- lastSeenAt: null, lastPaneText: '',
137
- },
138
- 'session-my-chat': harnessSession,
139
- });
140
- const ctx = makeCtx(tempDir);
141
- const log = new Logger('test', 'test');
142
- const result = await cleanupOrphanAcpSessions(ctx, log);
143
- expect(result.cleaned).toBe(1); // only worker-1
144
- expect(result.killed).toBe(0);
145
- const remaining = readSessions(tempDir);
146
- expect(remaining['worker-1']).toBeUndefined();
147
- expect(remaining['session-my-chat']).toBeDefined();
148
- });
149
- it('returns zero counts when no pipeline sessions exist', async () => {
150
- writeState(tempDir, {});
151
- const ctx = makeCtx(tempDir);
152
- const log = new Logger('test', 'test');
153
- const result = await cleanupOrphanAcpSessions(ctx, log);
154
- expect(result.cleaned).toBe(0);
155
- expect(result.killed).toBe(0);
156
- });
157
- });
158
- //# sourceMappingURL=sessionCleanup.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sessionCleanup.test.js","sourceRoot":"","sources":["../../src/core/sessionCleanup.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=shellEnv.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"shellEnv.test.d.ts","sourceRoot":"","sources":["../../src/core/shellEnv.test.ts"],"names":[],"mappings":""}
@@ -1,116 +0,0 @@
1
- /**
2
- * @module shellEnv.test
3
- * @description Shell 环境配置解析的单元测试
4
- *
5
- * @author eddy
6
- * @organization wykj
7
- * @ownership wykj/eddy
8
- *
9
- * @created 2026-03-29
10
- * @updated 2026-04-03
11
- *
12
- * @role test
13
- * @layer core
14
- * @boundedContext configuration
15
- */
16
- import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
17
- import { tmpdir } from 'node:os';
18
- import { join } from 'node:path';
19
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
20
- import { parseShellConf, sourceCombinedConf, sourceShellConf } from './shellEnv.js';
21
- function makeTempDir() {
22
- return mkdtempSync(join(tmpdir(), 'sps-shellenv-test-'));
23
- }
24
- describe('parseShellConf', () => {
25
- let tempDir;
26
- beforeEach(() => { tempDir = makeTempDir(); });
27
- afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
28
- it('parses KEY=value lines', () => {
29
- const file = join(tempDir, 'conf');
30
- writeFileSync(file, 'FOO=bar\nBAZ=qux\n');
31
- const result = parseShellConf(file);
32
- expect(result.FOO).toBe('bar');
33
- expect(result.BAZ).toBe('qux');
34
- });
35
- it('parses export KEY=value', () => {
36
- const file = join(tempDir, 'conf');
37
- writeFileSync(file, 'export MY_VAR=hello\n');
38
- expect(parseShellConf(file).MY_VAR).toBe('hello');
39
- });
40
- it('parses double-quoted values', () => {
41
- const file = join(tempDir, 'conf');
42
- writeFileSync(file, 'MY_VAR="hello world"\n');
43
- expect(parseShellConf(file).MY_VAR).toBe('hello world');
44
- });
45
- it('parses single-quoted values', () => {
46
- const file = join(tempDir, 'conf');
47
- writeFileSync(file, "MY_VAR='single'\n");
48
- expect(parseShellConf(file).MY_VAR).toBe('single');
49
- });
50
- it('skips comments and blank lines', () => {
51
- const file = join(tempDir, 'conf');
52
- writeFileSync(file, '# comment\n\nKEY=val\n# another\n');
53
- const result = parseShellConf(file);
54
- expect(Object.keys(result).filter(k => k === 'KEY')).toHaveLength(1);
55
- expect(result.KEY).toBe('val');
56
- });
57
- it('returns empty object for nonexistent file', () => {
58
- expect(parseShellConf(join(tempDir, 'no-file'))).toEqual({});
59
- });
60
- });
61
- describe('sourceShellConf', () => {
62
- let tempDir;
63
- beforeEach(() => { tempDir = makeTempDir(); });
64
- afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
65
- it('sources variables via bash', () => {
66
- const file = join(tempDir, 'conf');
67
- writeFileSync(file, 'TEST_SRC_VAR=sourced_value\n');
68
- const result = sourceShellConf(file);
69
- expect(result.TEST_SRC_VAR).toBe('sourced_value');
70
- });
71
- it('resolves variable interpolation', () => {
72
- const file = join(tempDir, 'conf');
73
- writeFileSync(file, 'BASE=hello\nDERIVED="${BASE}_world"\n');
74
- const result = sourceShellConf(file);
75
- expect(result.DERIVED).toBe('hello_world');
76
- });
77
- });
78
- describe('sourceCombinedConf', () => {
79
- let tempDir;
80
- beforeEach(() => { tempDir = makeTempDir(); });
81
- afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); });
82
- it('returns empty when no files exist', () => {
83
- expect(sourceCombinedConf(['/tmp/no1', '/tmp/no2'])).toEqual({});
84
- });
85
- it('sources a single file', () => {
86
- const file = join(tempDir, 'env');
87
- writeFileSync(file, 'SINGLE=yes\n');
88
- const result = sourceCombinedConf([file]);
89
- expect(result.SINGLE).toBe('yes');
90
- });
91
- it('merges multiple files with later overriding earlier', () => {
92
- const env = join(tempDir, 'env');
93
- const conf = join(tempDir, 'conf');
94
- writeFileSync(env, 'SHARED=from_env\nENV_ONLY=env\n');
95
- writeFileSync(conf, 'SHARED=from_conf\nCONF_ONLY=conf\n');
96
- const result = sourceCombinedConf([env, conf]);
97
- expect(result.SHARED).toBe('from_conf');
98
- expect(result.ENV_ONLY).toBe('env');
99
- expect(result.CONF_ONLY).toBe('conf');
100
- });
101
- it('later file can reference earlier file variables', () => {
102
- const env = join(tempDir, 'env');
103
- const conf = join(tempDir, 'conf');
104
- writeFileSync(env, 'BASE_URL=https://api.example.com\n');
105
- writeFileSync(conf, 'API_ENDPOINT="${BASE_URL}/v1"\n');
106
- const result = sourceCombinedConf([env, conf]);
107
- expect(result.API_ENDPOINT).toBe('https://api.example.com/v1');
108
- });
109
- it('skips nonexistent files gracefully', () => {
110
- const existing = join(tempDir, 'exists');
111
- writeFileSync(existing, 'EXISTS=yes\n');
112
- const result = sourceCombinedConf(['/tmp/no-file-xyz', existing]);
113
- expect(result.EXISTS).toBe('yes');
114
- });
115
- });
116
- //# sourceMappingURL=shellEnv.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"shellEnv.test.js","sourceRoot":"","sources":["../../src/core/shellEnv.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=skillStore.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"skillStore.test.d.ts","sourceRoot":"","sources":["../../src/core/skillStore.test.ts"],"names":[],"mappings":""}
@@ -1,203 +0,0 @@
1
- /**
2
- * @module skillStore.test
3
- * @description skillStore 单元测试 — symlink / frozen / 回退、幂等语义
4
- *
5
- * 测试 symlink 路径:在 tmp 目录构造一个假 HOME 和假 project,所以不污染真实 ~/.coral/。
6
- */
7
- import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
8
- import { tmpdir } from 'node:os';
9
- import { join, resolve } from 'node:path';
10
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
11
- function tempDir(prefix) {
12
- return mkdtempSync(join(tmpdir(), prefix));
13
- }
14
- /**
15
- * skillStore 用 process.env.HOME 计算 ~/.coral/skills/,所以要在 import 前
16
- * 覆盖 HOME;再 dynamic import 重新读取。用 vi.resetModules 确保不用缓存。
17
- */
18
- async function loadStoreWithFakeHome(fakeHome) {
19
- process.env.HOME = fakeHome;
20
- vi.resetModules();
21
- return await import('./skillStore.js');
22
- }
23
- function makeUserSkill(fakeHome, name, extraFiles = {}) {
24
- const dir = resolve(fakeHome, '.coral', 'skills', name);
25
- mkdirSync(dir, { recursive: true });
26
- writeFileSync(resolve(dir, 'SKILL.md'), `---\nname: ${name}\n---\n# ${name}\n`);
27
- for (const [rel, content] of Object.entries(extraFiles)) {
28
- const p = resolve(dir, rel);
29
- mkdirSync(resolve(p, '..'), { recursive: true });
30
- writeFileSync(p, content);
31
- }
32
- return dir;
33
- }
34
- function makeProject(root) {
35
- const dir = resolve(root, 'proj');
36
- mkdirSync(resolve(dir, '.claude'), { recursive: true });
37
- return dir;
38
- }
39
- describe('skillStore', () => {
40
- let fakeHome;
41
- let root;
42
- const origHome = process.env.HOME;
43
- beforeEach(() => {
44
- root = tempDir('sps-skillstore-');
45
- fakeHome = resolve(root, 'home');
46
- mkdirSync(fakeHome, { recursive: true });
47
- });
48
- afterEach(() => {
49
- process.env.HOME = origHome;
50
- rmSync(root, { recursive: true, force: true });
51
- });
52
- it('listUserSkills 返回含 SKILL.md 的目录', async () => {
53
- makeUserSkill(fakeHome, 'python');
54
- makeUserSkill(fakeHome, 'backend');
55
- // 无 SKILL.md 的目录不应出现
56
- mkdirSync(resolve(fakeHome, '.coral', 'skills', 'not-a-skill'), { recursive: true });
57
- const store = await loadStoreWithFakeHome(fakeHome);
58
- const users = store.listUserSkills();
59
- const names = users.map((u) => u.name).sort();
60
- expect(names).toEqual(['backend', 'python']);
61
- });
62
- it('addSkillToProject 建 symlink', async () => {
63
- makeUserSkill(fakeHome, 'python');
64
- const proj = makeProject(root);
65
- const store = await loadStoreWithFakeHome(fakeHome);
66
- const result = store.addSkillToProject(proj, 'python');
67
- expect(['linked', 'copied']).toContain(result);
68
- const linkPath = resolve(proj, '.claude', 'skills', 'python');
69
- expect(existsSync(linkPath)).toBe(true);
70
- expect(existsSync(resolve(linkPath, 'SKILL.md'))).toBe(true);
71
- });
72
- it('addSkillToProject 对已 linked 的 skill 幂等', async () => {
73
- makeUserSkill(fakeHome, 'python');
74
- const proj = makeProject(root);
75
- const store = await loadStoreWithFakeHome(fakeHome);
76
- store.addSkillToProject(proj, 'python');
77
- const again = store.addSkillToProject(proj, 'python');
78
- expect(['skipped-linked', 'skipped-frozen']).toContain(again);
79
- });
80
- it('addSkillToProject 不覆盖已 frozen 的真实目录', async () => {
81
- makeUserSkill(fakeHome, 'python');
82
- const proj = makeProject(root);
83
- const store = await loadStoreWithFakeHome(fakeHome);
84
- // 先手工放一个真实目录(模拟已 frozen / v0.42 cpSync 后的目录)
85
- const frozenPath = resolve(proj, '.claude', 'skills', 'python');
86
- mkdirSync(frozenPath, { recursive: true });
87
- writeFileSync(resolve(frozenPath, 'SKILL.md'), '# custom\n');
88
- const result = store.addSkillToProject(proj, 'python');
89
- expect(result).toBe('skipped-frozen');
90
- // 内容保留
91
- expect(readFileSync(resolve(frozenPath, 'SKILL.md'), 'utf-8')).toBe('# custom\n');
92
- });
93
- it('addSkillToProject 对不存在的 skill 返回 skipped-absent', async () => {
94
- const proj = makeProject(root);
95
- const store = await loadStoreWithFakeHome(fakeHome);
96
- expect(store.addSkillToProject(proj, 'nope')).toBe('skipped-absent');
97
- });
98
- it('inspectProjectSkill 区分 absent / linked / frozen', async () => {
99
- makeUserSkill(fakeHome, 'python');
100
- makeUserSkill(fakeHome, 'backend');
101
- const proj = makeProject(root);
102
- const store = await loadStoreWithFakeHome(fakeHome);
103
- expect(store.inspectProjectSkill(proj, 'python')?.state).toBe('absent');
104
- store.addSkillToProject(proj, 'python');
105
- const linked = store.inspectProjectSkill(proj, 'python');
106
- expect(linked?.state).toBe('linked');
107
- // 手工造一个 frozen
108
- const backendDir = resolve(proj, '.claude', 'skills', 'backend');
109
- mkdirSync(backendDir, { recursive: true });
110
- writeFileSync(resolve(backendDir, 'SKILL.md'), '# custom\n');
111
- expect(store.inspectProjectSkill(proj, 'backend')?.state).toBe('frozen');
112
- });
113
- it('freezeSkillInProject 把 symlink 转成独立副本', async () => {
114
- makeUserSkill(fakeHome, 'python', { 'references/a.md': 'hello\n' });
115
- const proj = makeProject(root);
116
- const store = await loadStoreWithFakeHome(fakeHome);
117
- store.addSkillToProject(proj, 'python');
118
- expect(store.freezeSkillInProject(proj, 'python')).toBe(true);
119
- const projPath = resolve(proj, '.claude', 'skills', 'python');
120
- const stat = lstatSync(projPath);
121
- expect(stat.isSymbolicLink()).toBe(false);
122
- // 改源文件后,项目里的副本不应跟着变
123
- const userPath = resolve(fakeHome, '.coral', 'skills', 'python', 'references', 'a.md');
124
- writeFileSync(userPath, 'modified\n');
125
- expect(readFileSync(resolve(projPath, 'references', 'a.md'), 'utf-8')).toBe('hello\n');
126
- });
127
- it('unfreezeSkillInProject 把真实副本转回 symlink', async () => {
128
- makeUserSkill(fakeHome, 'python', { 'references/a.md': 'hello\n' });
129
- const proj = makeProject(root);
130
- const store = await loadStoreWithFakeHome(fakeHome);
131
- store.addSkillToProject(proj, 'python');
132
- store.freezeSkillInProject(proj, 'python');
133
- expect(store.unfreezeSkillInProject(proj, 'python')).toBe(true);
134
- const projPath = resolve(proj, '.claude', 'skills', 'python');
135
- expect(lstatSync(projPath).isSymbolicLink()).toBe(true);
136
- // 改源文件后,项目里会跟着变
137
- const userPath = resolve(fakeHome, '.coral', 'skills', 'python', 'references', 'a.md');
138
- writeFileSync(userPath, 'modified\n');
139
- expect(readFileSync(resolve(projPath, 'references', 'a.md'), 'utf-8')).toBe('modified\n');
140
- });
141
- it('removeSkillFromProject 移除 symlink 不动源', async () => {
142
- makeUserSkill(fakeHome, 'python');
143
- const proj = makeProject(root);
144
- const store = await loadStoreWithFakeHome(fakeHome);
145
- store.addSkillToProject(proj, 'python');
146
- expect(store.removeSkillFromProject(proj, 'python')).toBe(true);
147
- expect(existsSync(resolve(proj, '.claude', 'skills', 'python'))).toBe(false);
148
- // 源还在
149
- expect(existsSync(resolve(fakeHome, '.coral', 'skills', 'python', 'SKILL.md'))).toBe(true);
150
- });
151
- it('syncAllSkillsToProject 对 20 个 skill 批量建 symlink,幂等', async () => {
152
- const names = Array.from({ length: 20 }, (_, i) => `skill-${i}`);
153
- for (const n of names)
154
- makeUserSkill(fakeHome, n);
155
- const proj = makeProject(root);
156
- const store = await loadStoreWithFakeHome(fakeHome);
157
- const first = store.syncAllSkillsToProject(proj);
158
- expect(first.linked + first.copied).toBe(20);
159
- const second = store.syncAllSkillsToProject(proj);
160
- expect(second.kept).toBe(20);
161
- });
162
- it('syncBundledSkillsToUser 把 bundled 目录 cpSync 到 ~/.coral/skills/,不覆盖已存在', async () => {
163
- const bundled = resolve(root, 'bundled');
164
- mkdirSync(resolve(bundled, 'python'), { recursive: true });
165
- writeFileSync(resolve(bundled, 'python', 'SKILL.md'), '# bundled python\n');
166
- mkdirSync(resolve(bundled, 'rust'), { recursive: true });
167
- writeFileSync(resolve(bundled, 'rust', 'SKILL.md'), '# bundled rust\n');
168
- // 用户已有一个 python(用户改过)
169
- makeUserSkill(fakeHome, 'python', {});
170
- writeFileSync(resolve(fakeHome, '.coral', 'skills', 'python', 'SKILL.md'), '# user customized\n');
171
- const store = await loadStoreWithFakeHome(fakeHome);
172
- const res = store.syncBundledSkillsToUser(bundled);
173
- expect(res.copied).toBe(1); // rust 新建
174
- expect(res.skipped).toBe(1); // python 保留
175
- expect(readFileSync(resolve(fakeHome, '.coral', 'skills', 'python', 'SKILL.md'), 'utf-8'))
176
- .toBe('# user customized\n');
177
- });
178
- it('listUserSkills 包括 symlink 指向目录的 skill(外部包 vendor 接入模式)', async () => {
179
- makeUserSkill(fakeHome, 'python');
180
- // 模拟外部 skill 包:在别处建 skill 目录,再 symlink 进 ~/.coral/skills/
181
- const externalRoot = resolve(root, 'external-pack');
182
- mkdirSync(externalRoot, { recursive: true });
183
- writeFileSync(resolve(externalRoot, 'SKILL.md'), '---\nname: external\n---\n# external\n');
184
- const skillsDir = resolve(fakeHome, '.coral', 'skills');
185
- const { symlinkSync } = await import('node:fs');
186
- symlinkSync(externalRoot, resolve(skillsDir, 'external'), 'dir');
187
- const store = await loadStoreWithFakeHome(fakeHome);
188
- const names = store.listUserSkills().map((s) => s.name).sort();
189
- expect(names).toEqual(['external', 'python']);
190
- });
191
- it('ensureSkillsGitignore 幂等追加 .claude/skills/', async () => {
192
- const proj = makeProject(root);
193
- const store = await loadStoreWithFakeHome(fakeHome);
194
- const gitignore = resolve(proj, '.gitignore');
195
- store.ensureSkillsGitignore(proj);
196
- expect(readFileSync(gitignore, 'utf-8')).toContain('.claude/skills/');
197
- // 再跑一次不应重复
198
- store.ensureSkillsGitignore(proj);
199
- const lines = readFileSync(gitignore, 'utf-8').split('\n').filter((l) => l.trim() === '.claude/skills/');
200
- expect(lines.length).toBe(1);
201
- });
202
- });
203
- //# sourceMappingURL=skillStore.test.js.map