opencode-conductor-cdd-plugin 1.0.0-beta.13 → 1.0.0-beta.16

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,185 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import { getSynergyState, clearSynergyCache } from "./synergyState.js";
3
+ // Mock the configDetection module
4
+ vi.mock("./configDetection.js", () => ({
5
+ detectCDDConfig: vi.fn(),
6
+ }));
7
+ import { detectCDDConfig } from "./configDetection.js";
8
+ describe("synergyState", () => {
9
+ beforeEach(() => {
10
+ clearSynergyCache();
11
+ vi.clearAllMocks();
12
+ });
13
+ describe("getSynergyState", () => {
14
+ it("should detect and cache synergy state on first call", () => {
15
+ vi.mocked(detectCDDConfig).mockReturnValue({
16
+ hasCDDInOpenCode: false,
17
+ hasCDDInOMO: false,
18
+ synergyActive: true,
19
+ synergyFramework: 'oh-my-opencode-slim',
20
+ slimAgents: ['explorer', 'librarian', 'oracle', 'designer'],
21
+ });
22
+ const state = getSynergyState("/test/dir");
23
+ expect(state.framework).toBe('oh-my-opencode-slim');
24
+ expect(state.availableAgents).toEqual(['explorer', 'librarian', 'oracle', 'designer']);
25
+ expect(detectCDDConfig).toHaveBeenCalledTimes(1);
26
+ });
27
+ it("should return cached state on subsequent calls with same directory", () => {
28
+ vi.mocked(detectCDDConfig).mockReturnValue({
29
+ hasCDDInOpenCode: false,
30
+ hasCDDInOMO: false,
31
+ synergyActive: true,
32
+ synergyFramework: 'oh-my-opencode-slim',
33
+ slimAgents: ['explorer', 'librarian'],
34
+ });
35
+ const state1 = getSynergyState("/test/dir");
36
+ const state2 = getSynergyState("/test/dir");
37
+ expect(state1).toBe(state2); // Same object reference
38
+ expect(detectCDDConfig).toHaveBeenCalledTimes(1); // Only called once
39
+ });
40
+ it("should refresh state when working directory changes", () => {
41
+ vi.mocked(detectCDDConfig)
42
+ .mockReturnValueOnce({
43
+ hasCDDInOpenCode: false,
44
+ hasCDDInOMO: false,
45
+ synergyActive: true,
46
+ synergyFramework: 'oh-my-opencode-slim',
47
+ slimAgents: ['explorer'],
48
+ })
49
+ .mockReturnValueOnce({
50
+ hasCDDInOpenCode: false,
51
+ hasCDDInOMO: true,
52
+ synergyActive: true,
53
+ synergyFramework: 'oh-my-opencode',
54
+ cddModel: 'model-1',
55
+ });
56
+ const state1 = getSynergyState("/dir1");
57
+ const state2 = getSynergyState("/dir2");
58
+ expect(state1.framework).toBe('oh-my-opencode-slim');
59
+ expect(state2.framework).toBe('oh-my-opencode');
60
+ expect(detectCDDConfig).toHaveBeenCalledTimes(2);
61
+ });
62
+ it("should force refresh when forceRefresh is true", () => {
63
+ vi.mocked(detectCDDConfig)
64
+ .mockReturnValueOnce({
65
+ hasCDDInOpenCode: false,
66
+ hasCDDInOMO: false,
67
+ synergyActive: true,
68
+ synergyFramework: 'oh-my-opencode-slim',
69
+ slimAgents: ['explorer'],
70
+ })
71
+ .mockReturnValueOnce({
72
+ hasCDDInOpenCode: false,
73
+ hasCDDInOMO: false,
74
+ synergyActive: true,
75
+ synergyFramework: 'oh-my-opencode',
76
+ cddModel: 'model-changed',
77
+ });
78
+ const state1 = getSynergyState("/test/dir");
79
+ const state2 = getSynergyState("/test/dir", true); // Force refresh
80
+ expect(state1.framework).toBe('oh-my-opencode-slim');
81
+ expect(state2.framework).toBe('oh-my-opencode');
82
+ expect(detectCDDConfig).toHaveBeenCalledTimes(2);
83
+ });
84
+ it("should handle no synergy active", () => {
85
+ vi.mocked(detectCDDConfig).mockReturnValue({
86
+ hasCDDInOpenCode: true,
87
+ hasCDDInOMO: false,
88
+ synergyActive: false,
89
+ synergyFramework: 'none',
90
+ cddModel: 'model-1',
91
+ });
92
+ const state = getSynergyState("/test/dir");
93
+ expect(state.framework).toBe('none');
94
+ expect(state.availableAgents).toEqual([]);
95
+ });
96
+ it("should refresh cache after TTL expires", () => {
97
+ vi.useFakeTimers();
98
+ vi.mocked(detectCDDConfig)
99
+ .mockReturnValueOnce({
100
+ hasCDDInOpenCode: false,
101
+ hasCDDInOMO: false,
102
+ synergyActive: true,
103
+ synergyFramework: 'oh-my-opencode-slim',
104
+ slimAgents: ['explorer'],
105
+ })
106
+ .mockReturnValueOnce({
107
+ hasCDDInOpenCode: false,
108
+ hasCDDInOMO: false,
109
+ synergyActive: true,
110
+ synergyFramework: 'oh-my-opencode',
111
+ cddModel: 'model-1',
112
+ });
113
+ const state1 = getSynergyState("/test/dir");
114
+ // Advance time by 61 seconds (TTL is 60 seconds)
115
+ vi.advanceTimersByTime(61000);
116
+ const state2 = getSynergyState("/test/dir");
117
+ expect(state1.framework).toBe('oh-my-opencode-slim');
118
+ expect(state2.framework).toBe('oh-my-opencode');
119
+ expect(detectCDDConfig).toHaveBeenCalledTimes(2);
120
+ vi.useRealTimers();
121
+ });
122
+ it("should include detection timestamp", () => {
123
+ const beforeTime = Date.now();
124
+ vi.mocked(detectCDDConfig).mockReturnValue({
125
+ hasCDDInOpenCode: false,
126
+ hasCDDInOMO: false,
127
+ synergyActive: false,
128
+ synergyFramework: 'none',
129
+ });
130
+ const state = getSynergyState("/test/dir");
131
+ const afterTime = Date.now();
132
+ expect(state.detectedAt.getTime()).toBeGreaterThanOrEqual(beforeTime);
133
+ expect(state.detectedAt.getTime()).toBeLessThanOrEqual(afterTime);
134
+ });
135
+ it("should extract agents from oh-my-opencode-slim", () => {
136
+ vi.mocked(detectCDDConfig).mockReturnValue({
137
+ hasCDDInOpenCode: false,
138
+ hasCDDInOMO: false,
139
+ synergyActive: true,
140
+ synergyFramework: 'oh-my-opencode-slim',
141
+ slimAgents: ['explorer', 'oracle'],
142
+ });
143
+ const state = getSynergyState("/test/dir");
144
+ expect(state.availableAgents).toEqual(['explorer', 'oracle']);
145
+ });
146
+ it("should handle oh-my-opencode framework", () => {
147
+ vi.mocked(detectCDDConfig).mockReturnValue({
148
+ hasCDDInOpenCode: false,
149
+ hasCDDInOMO: true,
150
+ synergyActive: true,
151
+ synergyFramework: 'oh-my-opencode',
152
+ cddModel: 'model-1',
153
+ });
154
+ const state = getSynergyState("/test/dir");
155
+ expect(state.framework).toBe('oh-my-opencode');
156
+ // oh-my-opencode doesn't expose agent list through config, so availableAgents should be empty
157
+ expect(state.availableAgents).toEqual([]);
158
+ });
159
+ });
160
+ describe("clearSynergyCache", () => {
161
+ it("should clear the cache and force re-detection", () => {
162
+ vi.mocked(detectCDDConfig)
163
+ .mockReturnValueOnce({
164
+ hasCDDInOpenCode: false,
165
+ hasCDDInOMO: false,
166
+ synergyActive: true,
167
+ synergyFramework: 'oh-my-opencode-slim',
168
+ slimAgents: ['explorer'],
169
+ })
170
+ .mockReturnValueOnce({
171
+ hasCDDInOpenCode: false,
172
+ hasCDDInOMO: false,
173
+ synergyActive: true,
174
+ synergyFramework: 'oh-my-opencode',
175
+ cddModel: 'changed',
176
+ });
177
+ const state1 = getSynergyState("/test/dir");
178
+ clearSynergyCache();
179
+ const state2 = getSynergyState("/test/dir");
180
+ expect(state1.framework).toBe('oh-my-opencode-slim');
181
+ expect(state2.framework).toBe('oh-my-opencode');
182
+ expect(detectCDDConfig).toHaveBeenCalledTimes(2);
183
+ });
184
+ });
185
+ });
@@ -0,0 +1,27 @@
1
+ import type { SynergyFramework } from './configDetection.js';
2
+ import type { SynergyState } from './synergyState.js';
3
+ /**
4
+ * Synergy status information for display
5
+ */
6
+ export interface SynergyStatus {
7
+ framework: SynergyFramework;
8
+ isActive: boolean;
9
+ availableAgents: string[];
10
+ workingDir: string;
11
+ detectedAt: Date;
12
+ }
13
+ /**
14
+ * Get synergy status for display
15
+ *
16
+ * @param workingDir - Current working directory
17
+ * @param state - Current synergy state
18
+ * @returns Status information
19
+ */
20
+ export declare function getSynergyStatus(workingDir: string, state: SynergyState): SynergyStatus;
21
+ /**
22
+ * Format synergy status for display
23
+ *
24
+ * @param status - Synergy status to format
25
+ * @returns Formatted status string
26
+ */
27
+ export declare function formatSynergyStatus(status: SynergyStatus): string;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Get synergy status for display
3
+ *
4
+ * @param workingDir - Current working directory
5
+ * @param state - Current synergy state
6
+ * @returns Status information
7
+ */
8
+ export function getSynergyStatus(workingDir, state) {
9
+ return {
10
+ framework: state.framework,
11
+ isActive: state.framework !== 'none',
12
+ availableAgents: state.availableAgents,
13
+ workingDir,
14
+ detectedAt: state.detectedAt,
15
+ };
16
+ }
17
+ /**
18
+ * Format synergy status for display
19
+ *
20
+ * @param status - Synergy status to format
21
+ * @returns Formatted status string
22
+ */
23
+ export function formatSynergyStatus(status) {
24
+ const lines = [];
25
+ lines.push('='.repeat(60));
26
+ lines.push('Synergy Framework Status');
27
+ lines.push('='.repeat(60));
28
+ lines.push('');
29
+ // Framework and status
30
+ lines.push(`Synergy Framework: ${status.framework}`);
31
+ lines.push(`Status: ${status.isActive ? 'Active' : 'Inactive'}`);
32
+ lines.push('');
33
+ // Working directory
34
+ lines.push(`Working Directory: ${status.workingDir}`);
35
+ lines.push('');
36
+ // Detection timestamp
37
+ lines.push(`Detected At: ${status.detectedAt.toISOString()}`);
38
+ lines.push('');
39
+ // Available agents (only if active and has agents)
40
+ if (status.isActive && status.availableAgents.length > 0) {
41
+ lines.push(`Available Agents (${status.availableAgents.length}):`);
42
+ status.availableAgents.forEach(agent => {
43
+ lines.push(` - ${agent}`);
44
+ });
45
+ lines.push('');
46
+ }
47
+ else if (!status.isActive) {
48
+ lines.push('No synergy framework detected.');
49
+ lines.push('Configure oh-my-opencode or oh-my-opencode-slim to enable synergy.');
50
+ lines.push('');
51
+ }
52
+ lines.push('='.repeat(60));
53
+ return lines.join('\n');
54
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,143 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { getSynergyStatus, formatSynergyStatus } from './synergyStatus.js';
3
+ describe('Synergy Status', () => {
4
+ describe('getSynergyStatus', () => {
5
+ it('should return status with oh-my-opencode framework', () => {
6
+ const status = getSynergyStatus('/fake/dir', {
7
+ framework: 'oh-my-opencode',
8
+ detectedAt: new Date('2026-01-19T14:00:00Z'),
9
+ availableAgents: [],
10
+ lastCheck: new Date('2026-01-19T14:00:00Z'),
11
+ });
12
+ expect(status.framework).toBe('oh-my-opencode');
13
+ expect(status.isActive).toBe(true);
14
+ expect(status.availableAgents).toEqual([]);
15
+ expect(status.detectedAt).toBeInstanceOf(Date);
16
+ });
17
+ it('should return status with oh-my-opencode-slim framework', () => {
18
+ const status = getSynergyStatus('/fake/dir', {
19
+ framework: 'oh-my-opencode-slim',
20
+ detectedAt: new Date('2026-01-19T14:00:00Z'),
21
+ availableAgents: ['explorer', 'designer', 'librarian'],
22
+ lastCheck: new Date('2026-01-19T14:00:00Z'),
23
+ });
24
+ expect(status.framework).toBe('oh-my-opencode-slim');
25
+ expect(status.isActive).toBe(true);
26
+ expect(status.availableAgents).toHaveLength(3);
27
+ expect(status.availableAgents).toContain('explorer');
28
+ });
29
+ it('should return inactive status when no framework', () => {
30
+ const status = getSynergyStatus('/fake/dir', {
31
+ framework: 'none',
32
+ detectedAt: new Date('2026-01-19T14:00:00Z'),
33
+ availableAgents: [],
34
+ lastCheck: new Date('2026-01-19T14:00:00Z'),
35
+ });
36
+ expect(status.framework).toBe('none');
37
+ expect(status.isActive).toBe(false);
38
+ expect(status.availableAgents).toEqual([]);
39
+ });
40
+ it('should include working directory in status', () => {
41
+ const workingDir = '/test/project';
42
+ const status = getSynergyStatus(workingDir, {
43
+ framework: 'oh-my-opencode-slim',
44
+ detectedAt: new Date(),
45
+ availableAgents: [],
46
+ lastCheck: new Date(),
47
+ });
48
+ expect(status.workingDir).toBe(workingDir);
49
+ });
50
+ it('should include detection timestamp', () => {
51
+ const detectedAt = new Date('2026-01-19T14:30:00Z');
52
+ const status = getSynergyStatus('/fake/dir', {
53
+ framework: 'oh-my-opencode',
54
+ detectedAt,
55
+ availableAgents: [],
56
+ lastCheck: new Date(),
57
+ });
58
+ expect(status.detectedAt).toBe(detectedAt);
59
+ });
60
+ });
61
+ describe('formatSynergyStatus', () => {
62
+ it('should format status for oh-my-opencode', () => {
63
+ const status = {
64
+ framework: 'oh-my-opencode',
65
+ isActive: true,
66
+ availableAgents: [],
67
+ workingDir: '/test/project',
68
+ detectedAt: new Date('2026-01-19T14:00:00Z'),
69
+ };
70
+ const formatted = formatSynergyStatus(status);
71
+ expect(formatted).toContain('Synergy Framework: oh-my-opencode');
72
+ expect(formatted).toContain('Status: Active');
73
+ expect(formatted).toContain('Working Directory: /test/project');
74
+ expect(formatted).toContain('Detected At:');
75
+ });
76
+ it('should format status for oh-my-opencode-slim with agents', () => {
77
+ const status = {
78
+ framework: 'oh-my-opencode-slim',
79
+ isActive: true,
80
+ availableAgents: ['explorer', 'designer', 'librarian'],
81
+ workingDir: '/test/project',
82
+ detectedAt: new Date('2026-01-19T14:00:00Z'),
83
+ };
84
+ const formatted = formatSynergyStatus(status);
85
+ expect(formatted).toContain('Synergy Framework: oh-my-opencode-slim');
86
+ expect(formatted).toContain('Status: Active');
87
+ expect(formatted).toContain('Available Agents (3):');
88
+ expect(formatted).toContain('- explorer');
89
+ expect(formatted).toContain('- designer');
90
+ expect(formatted).toContain('- librarian');
91
+ });
92
+ it('should format inactive status', () => {
93
+ const status = {
94
+ framework: 'none',
95
+ isActive: false,
96
+ availableAgents: [],
97
+ workingDir: '/test/project',
98
+ detectedAt: new Date('2026-01-19T14:00:00Z'),
99
+ };
100
+ const formatted = formatSynergyStatus(status);
101
+ expect(formatted).toContain('Synergy Framework: none');
102
+ expect(formatted).toContain('Status: Inactive');
103
+ expect(formatted).toContain('No synergy framework detected');
104
+ });
105
+ it('should handle empty agents list for active framework', () => {
106
+ const status = {
107
+ framework: 'oh-my-opencode',
108
+ isActive: true,
109
+ availableAgents: [],
110
+ workingDir: '/test/project',
111
+ detectedAt: new Date('2026-01-19T14:00:00Z'),
112
+ };
113
+ const formatted = formatSynergyStatus(status);
114
+ expect(formatted).toContain('oh-my-opencode');
115
+ expect(formatted).toContain('Active');
116
+ // Should not show agent list for oh-my-opencode (doesn't expose agents)
117
+ expect(formatted).not.toContain('Available Agents');
118
+ });
119
+ it('should format timestamps in readable format', () => {
120
+ const status = {
121
+ framework: 'oh-my-opencode-slim',
122
+ isActive: true,
123
+ availableAgents: [],
124
+ workingDir: '/test/project',
125
+ detectedAt: new Date('2026-01-19T14:30:45.123Z'),
126
+ };
127
+ const formatted = formatSynergyStatus(status);
128
+ expect(formatted).toContain('Detected At:');
129
+ expect(formatted).toMatch(/\d{4}-\d{2}-\d{2}/); // Should contain date
130
+ });
131
+ it('should show agent count correctly', () => {
132
+ const status = {
133
+ framework: 'oh-my-opencode-slim',
134
+ isActive: true,
135
+ availableAgents: ['explorer'],
136
+ workingDir: '/test/project',
137
+ detectedAt: new Date(),
138
+ };
139
+ const formatted = formatSynergyStatus(status);
140
+ expect(formatted).toContain('Available Agents (1):');
141
+ });
142
+ });
143
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-conductor-cdd-plugin",
3
- "version": "1.0.0-beta.13",
3
+ "version": "1.0.0-beta.16",
4
4
  "description": "Context-Driven Development (CDD) plugin for OpenCode - Transform your AI coding workflow with structured specifications, plans, and implementation tracking",
5
5
  "type": "module",
6
6
  "repository": {
@@ -8,7 +8,7 @@ const targetAgentDir = path.join(opencodeConfigDir, 'agent');
8
8
  const targetCommandDir = path.join(opencodeConfigDir, 'command');
9
9
 
10
10
  const sourcePromptsDir = path.join(__dirname, '..', 'dist', 'prompts');
11
- const sourceAgentFile = path.join(sourcePromptsDir, 'agent', 'orchestrator.md');
11
+ const sourceAgentFile = path.join(sourcePromptsDir, 'agent', 'cdd.md');
12
12
  const sourceCommandsDir = path.join(sourcePromptsDir, 'commands');
13
13
 
14
14
  function ensureDir(dir) {
@@ -22,8 +22,8 @@ try {
22
22
  ensureDir(targetCommandDir);
23
23
 
24
24
  if (fs.existsSync(sourceAgentFile)) {
25
- fs.copyFileSync(sourceAgentFile, path.join(targetAgentDir, 'orchestrator.md'));
26
- console.log('[Orchestrator] Installed agent definition.');
25
+ fs.copyFileSync(sourceAgentFile, path.join(targetAgentDir, 'cdd.md'));
26
+ console.log('[CDD] Installed agent definition.');
27
27
  }
28
28
 
29
29
  if (fs.existsSync(sourceCommandsDir)) {
@@ -31,8 +31,8 @@ try {
31
31
  for (const cmdFile of commands) {
32
32
  fs.copyFileSync(path.join(sourceCommandsDir, cmdFile), path.join(targetCommandDir, cmdFile));
33
33
  }
34
- console.log('[Orchestrator] Installed slash commands.');
34
+ console.log('[CDD] Installed slash commands.');
35
35
  }
36
36
  } catch (err) {
37
- console.error('[Orchestrator] Setup failed:', err.message);
37
+ console.error('[CDD] Setup failed:', err.message);
38
38
  }