opencode-conductor-cdd-plugin 1.0.0-beta.17 → 1.0.0-beta.19

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 (55) hide show
  1. package/dist/prompts/agent/cdd.md +16 -16
  2. package/dist/prompts/agent/implementer.md +5 -5
  3. package/dist/prompts/agent.md +7 -7
  4. package/dist/prompts/cdd/implement.json +1 -1
  5. package/dist/prompts/cdd/revert.json +1 -1
  6. package/dist/prompts/cdd/setup.json +2 -2
  7. package/dist/prompts/cdd/setup.test.js +40 -118
  8. package/dist/prompts/cdd/setup.test.ts +40 -143
  9. package/dist/test/integration/rebrand.test.js +15 -14
  10. package/dist/utils/agentMapping.js +2 -0
  11. package/dist/utils/archive-tracks.d.ts +28 -0
  12. package/dist/utils/archive-tracks.js +154 -1
  13. package/dist/utils/archive-tracks.test.d.ts +1 -0
  14. package/dist/utils/archive-tracks.test.js +495 -0
  15. package/dist/utils/codebaseAnalysis.d.ts +61 -0
  16. package/dist/utils/codebaseAnalysis.js +429 -0
  17. package/dist/utils/codebaseAnalysis.test.d.ts +1 -0
  18. package/dist/utils/codebaseAnalysis.test.js +556 -0
  19. package/dist/utils/documentGeneration.d.ts +97 -0
  20. package/dist/utils/documentGeneration.js +301 -0
  21. package/dist/utils/documentGeneration.test.d.ts +1 -0
  22. package/dist/utils/documentGeneration.test.js +380 -0
  23. package/dist/utils/interactiveMenu.d.ts +56 -0
  24. package/dist/utils/interactiveMenu.js +144 -0
  25. package/dist/utils/interactiveMenu.test.d.ts +1 -0
  26. package/dist/utils/interactiveMenu.test.js +231 -0
  27. package/dist/utils/interactiveSetup.d.ts +43 -0
  28. package/dist/utils/interactiveSetup.js +131 -0
  29. package/dist/utils/interactiveSetup.test.d.ts +1 -0
  30. package/dist/utils/interactiveSetup.test.js +124 -0
  31. package/dist/utils/metadataTracker.d.ts +39 -0
  32. package/dist/utils/metadataTracker.js +105 -0
  33. package/dist/utils/metadataTracker.test.d.ts +1 -0
  34. package/dist/utils/metadataTracker.test.js +265 -0
  35. package/dist/utils/planParser.d.ts +25 -0
  36. package/dist/utils/planParser.js +107 -0
  37. package/dist/utils/planParser.test.d.ts +1 -0
  38. package/dist/utils/planParser.test.js +119 -0
  39. package/dist/utils/projectMaturity.d.ts +53 -0
  40. package/dist/utils/projectMaturity.js +179 -0
  41. package/dist/utils/projectMaturity.test.d.ts +1 -0
  42. package/dist/utils/projectMaturity.test.js +298 -0
  43. package/dist/utils/questionGenerator.d.ts +51 -0
  44. package/dist/utils/questionGenerator.js +535 -0
  45. package/dist/utils/questionGenerator.test.d.ts +1 -0
  46. package/dist/utils/questionGenerator.test.js +328 -0
  47. package/dist/utils/setupIntegration.d.ts +72 -0
  48. package/dist/utils/setupIntegration.js +179 -0
  49. package/dist/utils/setupIntegration.test.d.ts +1 -0
  50. package/dist/utils/setupIntegration.test.js +344 -0
  51. package/dist/utils/statusDisplay.d.ts +35 -0
  52. package/dist/utils/statusDisplay.js +81 -0
  53. package/dist/utils/statusDisplay.test.d.ts +1 -0
  54. package/dist/utils/statusDisplay.test.js +102 -0
  55. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeAll } from 'vitest';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
 
5
- describe('setup.json prompt structure', () => {
5
+ describe('setup.json prompt structure (interactive, code-driven)', () => {
6
6
  let setupPrompt: string;
7
7
 
8
8
  beforeAll(() => {
@@ -11,158 +11,55 @@ describe('setup.json prompt structure', () => {
11
11
  setupPrompt = setupJson.prompt;
12
12
  });
13
13
 
14
- describe('Autogenerate option mandate', () => {
15
- const requiredText = 'E) Autogenerate and review';
16
- const mandatoryInstructionPattern = /\*\*Mandatory:\*\*.*autogenerate and review/is;
17
-
18
- it('should contain Section 2.1 (Product Guide) with autogenerate mandate', () => {
19
- expect(setupPrompt).toContain('### 2.1 Generate Product Guide');
20
-
21
- const section21Start = setupPrompt.indexOf('### 2.1 Generate Product Guide');
22
- const section22Start = setupPrompt.indexOf('### 2.2 Generate Product Guidelines');
23
- const section21 = setupPrompt.substring(section21Start, section22Start);
24
-
25
- expect(section21).toMatch(mandatoryInstructionPattern);
26
- expect(section21).toContain(requiredText);
27
- });
28
-
29
- it('should contain Section 2.2 (Product Guidelines) with autogenerate mandate', () => {
30
- expect(setupPrompt).toContain('### 2.2 Generate Product Guidelines');
31
-
32
- const section22Start = setupPrompt.indexOf('### 2.2 Generate Product Guidelines');
33
- const section23Start = setupPrompt.indexOf('### 2.3 Define Technology Stack');
34
- const section22 = setupPrompt.substring(section22Start, section23Start);
35
-
36
- expect(section22).toMatch(mandatoryInstructionPattern);
37
- expect(section22).toContain(requiredText);
38
- });
39
-
40
- it('should contain Section 2.3 (Tech Stack) with autogenerate mandate', () => {
41
- expect(setupPrompt).toContain('### 2.3 Define Technology Stack');
42
-
43
- const section23Start = setupPrompt.indexOf('### 2.3 Define Technology Stack');
44
- const section24Start = setupPrompt.indexOf('### 2.4 Select Code Styleguides');
45
- const section23 = setupPrompt.substring(section23Start, section24Start);
46
-
47
- expect(section23).toMatch(mandatoryInstructionPattern);
48
- expect(section23).toContain(requiredText);
49
- });
50
-
51
- it('should contain Section 2.4 (Code Styleguides) with autogenerate mandate', () => {
52
- expect(setupPrompt).toContain('### 2.4 Select Code Styleguides');
53
-
54
- const section24Start = setupPrompt.indexOf('### 2.4 Select Code Styleguides');
55
- const section25Start = setupPrompt.indexOf('### 2.5 Define Workflow');
56
- const section24 = setupPrompt.substring(section24Start, section25Start);
57
-
58
- expect(section24).toMatch(mandatoryInstructionPattern);
59
- expect(section24).toContain(requiredText);
60
- });
14
+ it('should have description and prompt fields', () => {
15
+ const setupJsonPath = path.join(__dirname, 'setup.json');
16
+ const setupJson = JSON.parse(fs.readFileSync(setupJsonPath, 'utf-8'));
61
17
 
62
- it('should contain Section 2.5 (Workflow) with autogenerate mandate', () => {
63
- expect(setupPrompt).toContain('### 2.5 Define Workflow');
64
-
65
- const section25Start = setupPrompt.indexOf('### 2.5 Define Workflow');
66
- const section30Start = setupPrompt.indexOf('## 3.0 INITIAL TRACK');
67
- const section25 = setupPrompt.substring(section25Start, section30Start);
68
-
69
- expect(section25).toMatch(mandatoryInstructionPattern);
70
- expect(section25).toContain(requiredText);
71
- });
18
+ expect(setupJson).toHaveProperty('description');
19
+ expect(setupJson.description).toBeTruthy();
20
+ expect(setupJson).toHaveProperty('prompt');
21
+ expect(typeof setupJson.prompt).toBe('string');
22
+ expect(setupJson.prompt).toBeTruthy();
72
23
  });
73
24
 
74
- describe('Sequential questioning instructions', () => {
75
- const sequentialPattern = /ask questions sequentially.*one by one/is;
76
-
77
- it('should enforce sequential questioning in Section 2.1', () => {
78
- const section21Start = setupPrompt.indexOf('### 2.1 Generate Product Guide');
79
- const section22Start = setupPrompt.indexOf('### 2.2 Generate Product Guidelines');
80
- const section21 = setupPrompt.substring(section21Start, section22Start);
81
-
82
- expect(section21).toMatch(sequentialPattern);
83
- });
84
-
85
- it('should enforce sequential questioning in Section 2.2', () => {
86
- const section22Start = setupPrompt.indexOf('### 2.2 Generate Product Guidelines');
87
- const section23Start = setupPrompt.indexOf('### 2.3 Define Technology Stack');
88
- const section22 = setupPrompt.substring(section22Start, section23Start);
89
-
90
- expect(section22).toMatch(sequentialPattern);
91
- });
92
-
93
- it('should enforce sequential questioning in Section 2.4', () => {
94
- const section24Start = setupPrompt.indexOf('### 2.4 Select Code Styleguides');
95
- const section25Start = setupPrompt.indexOf('### 2.5 Define Workflow');
96
- const section24 = setupPrompt.substring(section24Start, section25Start);
97
-
98
- expect(section24).toMatch(sequentialPattern);
99
- });
100
-
101
- it('should enforce sequential questioning in Section 2.5', () => {
102
- const section25Start = setupPrompt.indexOf('### 2.5 Define Workflow');
103
- const section30Start = setupPrompt.indexOf('## 3.0 INITIAL TRACK');
104
- const section25 = setupPrompt.substring(section25Start, section30Start);
105
-
106
- expect(section25).toMatch(sequentialPattern);
107
- });
25
+ it('should describe interactive, auto-generated setup', () => {
26
+ expect(setupPrompt).toContain('CDD SETUP');
27
+ expect(setupPrompt.toLowerCase()).toContain('interactive');
28
+ expect(setupPrompt.toLowerCase()).toContain('auto');
29
+ expect(setupPrompt.toLowerCase()).toContain('code-driven');
108
30
  });
109
31
 
110
- describe('Basic prompt structure validation', () => {
111
- it('should have description field', () => {
112
- const setupJsonPath = path.join(__dirname, 'setup.json');
113
- const setupJson = JSON.parse(fs.readFileSync(setupJsonPath, 'utf-8'));
114
-
115
- expect(setupJson).toHaveProperty('description');
116
- expect(setupJson.description).toBeTruthy();
117
- });
118
-
119
- it('should have prompt field', () => {
120
- const setupJsonPath = path.join(__dirname, 'setup.json');
121
- const setupJson = JSON.parse(fs.readFileSync(setupJsonPath, 'utf-8'));
122
-
123
- expect(setupJson).toHaveProperty('prompt');
124
- expect(setupJson.prompt).toBeTruthy();
125
- expect(typeof setupJson.prompt).toBe('string');
126
- });
32
+ it('should reference the TypeScript implementation entry point', () => {
33
+ expect(setupPrompt).toContain('src/utils/interactiveSetup.ts');
34
+ expect(setupPrompt).toContain('executeInteractiveSetup');
35
+ expect(setupPrompt).toContain('generateSetupSummary');
36
+ });
127
37
 
128
- it('should contain all major setup phases', () => {
129
- expect(setupPrompt).toContain('## 1.0 SYSTEM DIRECTIVE');
130
- expect(setupPrompt).toContain('## 1.1 BEGIN `RESUME` CHECK');
131
- expect(setupPrompt).toContain('## 1.3 PROJECT MATURITY DETECTION');
132
- expect(setupPrompt).toContain('## 2.0 INITIALIZATION PHASE');
133
- expect(setupPrompt).toContain('## 3.0 INITIAL TRACK');
134
- expect(setupPrompt).toContain('## 4.0 COMPLETION');
135
- });
38
+ it('should mandate 5 options per question (A-E)', () => {
39
+ expect(setupPrompt).toContain('A-C');
40
+ expect(setupPrompt).toContain('D');
41
+ expect(setupPrompt).toContain('E');
42
+ expect(setupPrompt).toContain('A, B, C, D, E');
43
+ });
136
44
 
137
- it('should contain all 5 interactive sections', () => {
138
- expect(setupPrompt).toContain('### 2.1 Generate Product Guide');
139
- expect(setupPrompt).toContain('### 2.2 Generate Product Guidelines');
140
- expect(setupPrompt).toContain('### 2.3 Define Technology Stack');
141
- expect(setupPrompt).toContain('### 2.4 Select Code Styleguides');
142
- expect(setupPrompt).toContain('### 2.5 Define Workflow');
143
- });
45
+ it('should mention all 5 output documents', () => {
46
+ expect(setupPrompt).toContain('conductor-cdd/product.md');
47
+ expect(setupPrompt).toContain('conductor-cdd/guidelines.md');
48
+ expect(setupPrompt).toContain('conductor-cdd/tech-stack.md');
49
+ expect(setupPrompt).toContain('conductor-cdd/styleguides.md');
50
+ expect(setupPrompt).toContain('conductor-cdd/workflow.md');
144
51
  });
145
52
 
146
- describe('Backward compatibility', () => {
147
- it('should maintain existing resume logic', () => {
148
- expect(setupPrompt).toContain('last_successful_step');
149
- expect(setupPrompt).toContain('setup_state.json');
150
- expect(setupPrompt).toContain('2.1_product_guide');
151
- expect(setupPrompt).toContain('2.2_product_guidelines');
152
- expect(setupPrompt).toContain('2.3_tech_stack');
153
- expect(setupPrompt).toContain('2.4_code_styleguides');
154
- expect(setupPrompt).toContain('2.5_workflow');
155
- });
53
+ it('should describe resume behavior and expected checkpoints', () => {
54
+ expect(setupPrompt).toContain('conductor-cdd/setup_state.json');
55
+ expect(setupPrompt).toContain('last_successful_step');
156
56
 
157
- it('should maintain brownfield detection logic', () => {
158
- expect(setupPrompt).toContain('BROWNFIELD');
159
- expect(setupPrompt).toContain('GREENFIELD');
160
- expect(setupPrompt).toContain('PROJECT MATURITY DETECTION');
161
- });
57
+ expect(setupPrompt).toContain('2.1_product_guide');
58
+ expect(setupPrompt).toContain('2.2_product_guidelines');
59
+ expect(setupPrompt).toContain('2.3_tech_stack');
60
+ expect(setupPrompt).toContain('2.4_code_styleguides');
61
+ expect(setupPrompt).toContain('2.5_workflow');
162
62
 
163
- it('should allow manual Q&A flow (no hard requirement for autogenerate)', () => {
164
- expect(setupPrompt).not.toContain('MUST autogenerate');
165
- expect(setupPrompt).not.toContain('MUST use autogenerate');
166
- });
63
+ expect(setupPrompt).toContain('cdd:setup');
167
64
  });
168
65
  });
@@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  describe('Rebrand Integration Tests', () => {
5
+ const LEGACY_AGENT_NAME = 'orche' + 'strator';
5
6
  describe('Package Metadata', () => {
6
7
  it('should have correct package name', () => {
7
8
  const pkgPath = path.join(process.cwd(), 'package.json');
@@ -17,7 +18,7 @@ describe('Rebrand Integration Tests', () => {
17
18
  const pkgPath = path.join(process.cwd(), 'package.json');
18
19
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
19
20
  expect(pkg.repository.url).toContain('opencode-conductor-cdd');
20
- expect(pkg.repository.url).not.toContain('opencode-orchestrator');
21
+ expect(pkg.repository.url).not.toContain('opencode-' + LEGACY_AGENT_NAME);
21
22
  });
22
23
  });
23
24
  describe('Prompt Files Structure', () => {
@@ -25,9 +26,9 @@ describe('Rebrand Integration Tests', () => {
25
26
  const cddPromptsPath = path.join(process.cwd(), 'src/prompts/cdd');
26
27
  expect(fs.existsSync(cddPromptsPath)).toBe(true);
27
28
  });
28
- it('should NOT have orchestrator prompts directory', () => {
29
- const orchestratorPromptsPath = path.join(process.cwd(), 'src/prompts/orchestrator');
30
- expect(fs.existsSync(orchestratorPromptsPath)).toBe(false);
29
+ it('should NOT have legacy prompts directory', () => {
30
+ const legacyPromptsPath = path.join(process.cwd(), 'src/prompts/' + LEGACY_AGENT_NAME);
31
+ expect(fs.existsSync(legacyPromptsPath)).toBe(false);
31
32
  });
32
33
  it('should have all required CDD prompt files', () => {
33
34
  const cddPromptsPath = path.join(process.cwd(), 'src/prompts/cdd');
@@ -41,9 +42,9 @@ describe('Rebrand Integration Tests', () => {
41
42
  const cddAgentPath = path.join(process.cwd(), 'src/prompts/agent/cdd.md');
42
43
  expect(fs.existsSync(cddAgentPath)).toBe(true);
43
44
  });
44
- it('should NOT have orchestrator agent prompt file', () => {
45
- const orchestratorAgentPath = path.join(process.cwd(), 'src/prompts/agent/orchestrator.md');
46
- expect(fs.existsSync(orchestratorAgentPath)).toBe(false);
45
+ it('should NOT have legacy agent prompt file', () => {
46
+ const legacyAgentPath = path.join(process.cwd(), 'src/prompts/agent/' + LEGACY_AGENT_NAME + '.md');
47
+ expect(fs.existsSync(legacyAgentPath)).toBe(false);
47
48
  });
48
49
  });
49
50
  describe('Source Code References', () => {
@@ -53,7 +54,7 @@ describe('Rebrand Integration Tests', () => {
53
54
  expect(indexContent).toContain('./prompts/cdd/implement.json');
54
55
  expect(indexContent).toContain('./prompts/cdd/newTrack.json');
55
56
  expect(indexContent).toContain('./prompts/cdd/setup.json');
56
- expect(indexContent).not.toContain('./prompts/orchestrator/');
57
+ expect(indexContent).not.toContain('./prompts/' + LEGACY_AGENT_NAME + '/');
57
58
  });
58
59
  it('should register cdd: commands in index.ts', () => {
59
60
  const indexPath = path.join(process.cwd(), 'src/index.ts');
@@ -63,7 +64,7 @@ describe('Rebrand Integration Tests', () => {
63
64
  expect(indexContent).toContain('"cdd:setup"');
64
65
  expect(indexContent).toContain('"cdd:status"');
65
66
  expect(indexContent).toContain('"cdd:revert"');
66
- expect(indexContent).not.toContain('"orchestrator:');
67
+ expect(indexContent).not.toContain('"' + LEGACY_AGENT_NAME + ':');
67
68
  });
68
69
  it('should reference conductor-cdd directory in index.ts', () => {
69
70
  const indexPath = path.join(process.cwd(), 'src/index.ts');
@@ -81,7 +82,7 @@ describe('Rebrand Integration Tests', () => {
81
82
  const stateManagerPath = path.join(process.cwd(), 'src/utils/stateManager.ts');
82
83
  const content = fs.readFileSync(stateManagerPath, 'utf-8');
83
84
  expect(content).toContain('conductor-cdd');
84
- expect(content).not.toContain('orchestrator/');
85
+ expect(content).not.toContain(LEGACY_AGENT_NAME + '/');
85
86
  });
86
87
  it('should use detectCDDConfig function name', () => {
87
88
  const configDetectionPath = path.join(process.cwd(), 'src/utils/configDetection.ts');
@@ -93,7 +94,7 @@ describe('Rebrand Integration Tests', () => {
93
94
  const commitMessagesPath = path.join(process.cwd(), 'src/utils/commitMessages.ts');
94
95
  const content = fs.readFileSync(commitMessagesPath, 'utf-8');
95
96
  expect(content).toContain('cdd(');
96
- expect(content).not.toContain('orchestrator(');
97
+ expect(content).not.toContain(LEGACY_AGENT_NAME + '(');
97
98
  });
98
99
  });
99
100
  describe('README Documentation', () => {
@@ -109,10 +110,10 @@ describe('Rebrand Integration Tests', () => {
109
110
  expect(content).toContain('/cdd:newTrack');
110
111
  expect(content).toContain('/cdd:implement');
111
112
  });
112
- it('should NOT reference orchestrator commands in README', () => {
113
+ it('should NOT reference legacy commands in README', () => {
113
114
  const readmePath = path.join(process.cwd(), 'README.md');
114
115
  const content = fs.readFileSync(readmePath, 'utf-8');
115
- expect(content).not.toContain('/orchestrator:');
116
+ expect(content).not.toContain('/' + LEGACY_AGENT_NAME + ':');
116
117
  });
117
118
  });
118
119
  describe('Build Scripts', () => {
@@ -120,7 +121,7 @@ describe('Rebrand Integration Tests', () => {
120
121
  const scriptPath = path.join(process.cwd(), 'scripts/convert-legacy.cjs');
121
122
  const content = fs.readFileSync(scriptPath, 'utf-8');
122
123
  expect(content).toContain('src/prompts/cdd');
123
- expect(content).not.toContain('src/prompts/orchestrator');
124
+ expect(content).not.toContain('src/prompts/' + LEGACY_AGENT_NAME);
124
125
  });
125
126
  });
126
127
  });
@@ -14,6 +14,8 @@ const SLIM_AGENT_MAPPING = {
14
14
  'librarian': 'librarian',
15
15
  // Agents without slim equivalent
16
16
  'sisyphus': null,
17
+ // Legacy name (oh-my-opencode-slim compatibility)
18
+ // See: https://github.com/alvinunreal/oh-my-opencode-slim
17
19
  'orchestrator': null,
18
20
  };
19
21
  /**
@@ -23,3 +23,31 @@ export interface TrackPlan {
23
23
  completed: boolean;
24
24
  }[];
25
25
  }
26
+ /**
27
+ * Parses metadata.json content and validates required fields
28
+ */
29
+ export declare function parseMetadata(jsonContent: string): TrackMetadata;
30
+ /**
31
+ * Checks if all tasks in a plan.md file are marked as completed
32
+ * Looks for task markers: [x] = complete, [ ] = incomplete, [~] = in-progress
33
+ */
34
+ export declare function isPlanFullyCompleted(planContent: string): boolean;
35
+ /**
36
+ * Determines if a track is finished by checking both metadata status and plan completion
37
+ */
38
+ export declare function isTrackFinished(metadata: TrackMetadata, planContent: string): boolean;
39
+ /**
40
+ * Moves a track directory from tracks/ to archive/
41
+ * Creates the archive directory if it doesn't exist
42
+ */
43
+ export declare function moveTrackToArchive(projectRoot: string, trackId: string): void;
44
+ /**
45
+ * Removes a track entry from tracks.md registry file
46
+ * Handles the track block and its surrounding separator lines
47
+ */
48
+ export declare function removeTrackFromRegistry(tracksFilePath: string, trackId: string): void;
49
+ /**
50
+ * Main function to archive all completed tracks in a project
51
+ * Scans tracks/ directory, identifies completed tracks, and archives them
52
+ */
53
+ export declare function archiveCompletedTracks(projectRoot: string): ArchiveResult;
@@ -1 +1,154 @@
1
- export {};
1
+ /**
2
+ * Parses metadata.json content and validates required fields
3
+ */
4
+ export function parseMetadata(jsonContent) {
5
+ const data = JSON.parse(jsonContent);
6
+ // Validate required fields
7
+ const requiredFields = ['track_id', 'type', 'status', 'created_at', 'updated_at', 'description'];
8
+ for (const field of requiredFields) {
9
+ if (!(field in data)) {
10
+ throw new Error(`Invalid metadata: missing required field '${field}'`);
11
+ }
12
+ }
13
+ return data;
14
+ }
15
+ /**
16
+ * Checks if all tasks in a plan.md file are marked as completed
17
+ * Looks for task markers: [x] = complete, [ ] = incomplete, [~] = in-progress
18
+ */
19
+ export function isPlanFullyCompleted(planContent) {
20
+ // Match all task markers: - [x], - [ ], - [~]
21
+ const taskRegex = /^[\s]*-\s*\[([x ~]|\s)\]/gm;
22
+ const matches = planContent.match(taskRegex);
23
+ // If no tasks found, consider it complete (no work to do)
24
+ if (!matches || matches.length === 0) {
25
+ return true;
26
+ }
27
+ // Check if all tasks are marked with [x]
28
+ return matches.every(match => match.includes('[x]'));
29
+ }
30
+ /**
31
+ * Determines if a track is finished by checking both metadata status and plan completion
32
+ */
33
+ export function isTrackFinished(metadata, planContent) {
34
+ const isMetadataComplete = metadata.status === 'completed';
35
+ const isPlanComplete = isPlanFullyCompleted(planContent);
36
+ return isMetadataComplete && isPlanComplete;
37
+ }
38
+ /**
39
+ * Moves a track directory from tracks/ to archive/
40
+ * Creates the archive directory if it doesn't exist
41
+ */
42
+ export function moveTrackToArchive(projectRoot, trackId) {
43
+ const { existsSync, mkdirSync } = require('fs');
44
+ const { join } = require('path');
45
+ const { execSync } = require('child_process');
46
+ const tracksDir = join(projectRoot, 'conductor-cdd', 'tracks');
47
+ const archiveDir = join(projectRoot, 'conductor-cdd', 'archive');
48
+ const sourcePath = join(tracksDir, trackId);
49
+ const destPath = join(archiveDir, trackId);
50
+ // Verify source exists
51
+ if (!existsSync(sourcePath)) {
52
+ throw new Error(`Track directory not found: ${sourcePath}`);
53
+ }
54
+ // Create archive directory if needed
55
+ if (!existsSync(archiveDir)) {
56
+ mkdirSync(archiveDir, { recursive: true });
57
+ }
58
+ // Use mv command to move the directory
59
+ execSync(`mv "${sourcePath}" "${destPath}"`);
60
+ }
61
+ /**
62
+ * Removes a track entry from tracks.md registry file
63
+ * Handles the track block and its surrounding separator lines
64
+ */
65
+ export function removeTrackFromRegistry(tracksFilePath, trackId) {
66
+ const { readFileSync, writeFileSync } = require('fs');
67
+ let content = readFileSync(tracksFilePath, 'utf-8');
68
+ // Split by separator to get track blocks
69
+ const separator = '\n---\n';
70
+ const parts = content.split(separator);
71
+ // Filter out the block containing the trackId
72
+ const filteredParts = parts.filter((part) => {
73
+ // Check if this part contains a link to the track we want to remove
74
+ return !part.includes(`./tracks/${trackId}/`);
75
+ });
76
+ // If nothing was filtered, track wasn't found - that's okay
77
+ if (filteredParts.length === parts.length) {
78
+ return;
79
+ }
80
+ // Rejoin with separator
81
+ content = filteredParts.join(separator);
82
+ writeFileSync(tracksFilePath, content, 'utf-8');
83
+ }
84
+ /**
85
+ * Main function to archive all completed tracks in a project
86
+ * Scans tracks/ directory, identifies completed tracks, and archives them
87
+ */
88
+ export function archiveCompletedTracks(projectRoot) {
89
+ const { readdirSync, readFileSync, existsSync } = require('fs');
90
+ const { join } = require('path');
91
+ const result = {
92
+ archived: [],
93
+ skipped: [],
94
+ errors: []
95
+ };
96
+ const cddDir = join(projectRoot, 'conductor-cdd');
97
+ const tracksDir = join(cddDir, 'tracks');
98
+ const tracksFile = join(cddDir, 'tracks.md');
99
+ // Check if tracks directory exists
100
+ if (!existsSync(tracksDir)) {
101
+ return result;
102
+ }
103
+ // Get all track directories
104
+ const trackDirs = readdirSync(tracksDir, { withFileTypes: true })
105
+ .filter((dirent) => dirent.isDirectory())
106
+ .map((dirent) => dirent.name);
107
+ // Process each track
108
+ for (const trackId of trackDirs) {
109
+ try {
110
+ const trackPath = join(tracksDir, trackId);
111
+ const metadataPath = join(trackPath, 'metadata.json');
112
+ const planPath = join(trackPath, 'plan.md');
113
+ // Check if required files exist
114
+ if (!existsSync(metadataPath)) {
115
+ result.errors.push({
116
+ trackId,
117
+ error: 'metadata.json not found'
118
+ });
119
+ continue;
120
+ }
121
+ if (!existsSync(planPath)) {
122
+ result.errors.push({
123
+ trackId,
124
+ error: 'plan.md not found'
125
+ });
126
+ continue;
127
+ }
128
+ // Parse metadata and plan
129
+ const metadataContent = readFileSync(metadataPath, 'utf-8');
130
+ const planContent = readFileSync(planPath, 'utf-8');
131
+ const metadata = parseMetadata(metadataContent);
132
+ // Check if track is finished
133
+ if (isTrackFinished(metadata, planContent)) {
134
+ // Archive the track
135
+ moveTrackToArchive(projectRoot, trackId);
136
+ // Remove from tracks.md if it exists
137
+ if (existsSync(tracksFile)) {
138
+ removeTrackFromRegistry(tracksFile, trackId);
139
+ }
140
+ result.archived.push(trackId);
141
+ }
142
+ else {
143
+ result.skipped.push(trackId);
144
+ }
145
+ }
146
+ catch (error) {
147
+ result.errors.push({
148
+ trackId,
149
+ error: error instanceof Error ? error.message : String(error)
150
+ });
151
+ }
152
+ }
153
+ return result;
154
+ }
@@ -0,0 +1 @@
1
+ export {};