@mytechtoday/augment-extensions 1.2.2 → 1.3.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 (87) hide show
  1. package/LICENSE +22 -22
  2. package/augment-extensions/domain-rules/software-architecture/README.md +143 -143
  3. package/augment-extensions/domain-rules/software-architecture/examples/banking-layered.md +961 -961
  4. package/augment-extensions/domain-rules/software-architecture/examples/ecommerce-microservices.md +990 -990
  5. package/augment-extensions/domain-rules/software-architecture/examples/iot-eventdriven.md +882 -882
  6. package/augment-extensions/domain-rules/software-architecture/examples/monolith-to-microservices-migration.md +703 -703
  7. package/augment-extensions/domain-rules/software-architecture/examples/serverless-imageprocessing.md +957 -957
  8. package/augment-extensions/domain-rules/software-architecture/examples/trading-eventdriven.md +747 -747
  9. package/augment-extensions/domain-rules/software-architecture/module.json +119 -119
  10. package/augment-extensions/domain-rules/software-architecture/rules/challenges-solutions.md +763 -763
  11. package/augment-extensions/domain-rules/software-architecture/rules/definitions-terminology.md +409 -409
  12. package/augment-extensions/domain-rules/software-architecture/rules/design-principles.md +684 -684
  13. package/augment-extensions/domain-rules/software-architecture/rules/evaluation-testing.md +1381 -1381
  14. package/augment-extensions/domain-rules/software-architecture/rules/event-driven-architecture.md +616 -616
  15. package/augment-extensions/domain-rules/software-architecture/rules/fundamentals.md +306 -306
  16. package/augment-extensions/domain-rules/software-architecture/rules/industry-architectures.md +554 -554
  17. package/augment-extensions/domain-rules/software-architecture/rules/layered-architecture.md +776 -776
  18. package/augment-extensions/domain-rules/software-architecture/rules/microservices-architecture.md +503 -503
  19. package/augment-extensions/domain-rules/software-architecture/rules/modeling-documentation.md +1199 -1199
  20. package/augment-extensions/domain-rules/software-architecture/rules/monolithic-architecture.md +351 -351
  21. package/augment-extensions/domain-rules/software-architecture/rules/principles.md +556 -556
  22. package/augment-extensions/domain-rules/software-architecture/rules/quality-attributes.md +797 -797
  23. package/augment-extensions/domain-rules/software-architecture/rules/scalability-performance.md +1345 -1345
  24. package/augment-extensions/domain-rules/software-architecture/rules/security-architecture.md +1039 -1039
  25. package/augment-extensions/domain-rules/software-architecture/rules/serverless-architecture.md +711 -711
  26. package/augment-extensions/domain-rules/software-architecture/rules/skills-development.md +568 -568
  27. package/augment-extensions/domain-rules/software-architecture/rules/tools-methodologies.md +961 -961
  28. package/augment-extensions/visual-design/CHANGELOG.md +132 -0
  29. package/augment-extensions/visual-design/README.md +255 -0
  30. package/augment-extensions/visual-design/__tests__/README.md +119 -0
  31. package/augment-extensions/visual-design/__tests__/style-selector.test.ts +172 -0
  32. package/augment-extensions/visual-design/__tests__/vendor-styles.test.ts +214 -0
  33. package/augment-extensions/visual-design/domains/other/ai-prompt-helper.ts +157 -0
  34. package/augment-extensions/visual-design/domains/other/dotnet-application.ts +156 -0
  35. package/augment-extensions/visual-design/domains/other/linux-platform.ts +156 -0
  36. package/augment-extensions/visual-design/domains/other/mobile-application.ts +157 -0
  37. package/augment-extensions/visual-design/domains/other/motion-picture.ts +156 -0
  38. package/augment-extensions/visual-design/domains/other/os-application.ts +156 -0
  39. package/augment-extensions/visual-design/domains/other/print-campaigns.ts +158 -0
  40. package/augment-extensions/visual-design/domains/other/web-app.ts +157 -0
  41. package/augment-extensions/visual-design/domains/other/website.ts +161 -0
  42. package/augment-extensions/visual-design/domains/other/windows-platform.ts +156 -0
  43. package/augment-extensions/visual-design/domains/web-page-styles/amazon-cloudscape.ts +506 -0
  44. package/augment-extensions/visual-design/domains/web-page-styles/google-modern.ts +615 -0
  45. package/augment-extensions/visual-design/domains/web-page-styles/microsoft-fluent.ts +531 -0
  46. package/augment-extensions/visual-design/examples/README.md +97 -0
  47. package/augment-extensions/visual-design/examples/ai-prompt-generation.md +233 -0
  48. package/augment-extensions/visual-design/examples/basic-usage.md +216 -0
  49. package/augment-extensions/visual-design/examples/domain-workflows.md +257 -0
  50. package/augment-extensions/visual-design/examples/vendor-comparison.md +247 -0
  51. package/augment-extensions/visual-design/module.json +78 -0
  52. package/augment-extensions/visual-design/style-selector.ts +177 -0
  53. package/augment-extensions/visual-design/types.ts +302 -0
  54. package/augment-extensions/visual-design/visual-design-core.ts +469 -0
  55. package/augment-extensions/workflows/adr-support/README.md +227 -0
  56. package/augment-extensions/workflows/adr-support/__tests__/adr-validator.test.ts +203 -0
  57. package/augment-extensions/workflows/adr-support/adr-validator.ts +162 -0
  58. package/augment-extensions/workflows/adr-support/examples/complete-lifecycle-example.md +449 -0
  59. package/augment-extensions/workflows/adr-support/examples/integration-example.md +580 -0
  60. package/augment-extensions/workflows/adr-support/examples/superseding-example.md +436 -0
  61. package/augment-extensions/workflows/adr-support/module.json +112 -0
  62. package/augment-extensions/workflows/adr-support/rules/adr-creation.md +372 -0
  63. package/augment-extensions/workflows/adr-support/rules/beads-integration.md +443 -0
  64. package/augment-extensions/workflows/adr-support/rules/conflict-detection.md +486 -0
  65. package/augment-extensions/workflows/adr-support/rules/decision-detection.md +362 -0
  66. package/augment-extensions/workflows/adr-support/rules/lifecycle-management.md +427 -0
  67. package/augment-extensions/workflows/adr-support/rules/openspec-integration.md +465 -0
  68. package/augment-extensions/workflows/adr-support/rules/template-selection.md +405 -0
  69. package/augment-extensions/workflows/adr-support/rules/validation-rules.md +543 -0
  70. package/augment-extensions/workflows/adr-support/schemas/adr-config.json +191 -0
  71. package/augment-extensions/workflows/adr-support/schemas/adr-metadata.json +172 -0
  72. package/augment-extensions/workflows/adr-support/templates/business-case.md +235 -0
  73. package/augment-extensions/workflows/adr-support/templates/madr-elaborate.md +197 -0
  74. package/augment-extensions/workflows/adr-support/templates/madr-simple.md +68 -0
  75. package/augment-extensions/workflows/adr-support/templates/nygard.md +84 -0
  76. package/augment-extensions/writing-standards/screenplay/rules/file-organization.md +213 -213
  77. package/augment-extensions/writing-standards/screenplay/utils/__tests__/file-organization.test.ts +169 -169
  78. package/augment-extensions/writing-standards/screenplay/utils/file-organization.ts +165 -165
  79. package/cli/dist/utils/auto-sync.js +19 -19
  80. package/package.json +5 -3
  81. package/augment-extensions/workflows/openspec/README.md +0 -96
  82. package/augment-extensions/workflows/openspec/examples/complete-change-example.md +0 -244
  83. package/augment-extensions/workflows/openspec/module.json +0 -54
  84. package/augment-extensions/workflows/openspec/rules/best-practices.md +0 -272
  85. package/augment-extensions/workflows/openspec/rules/manual-setup.md +0 -231
  86. package/augment-extensions/workflows/openspec/rules/spec-format.md +0 -236
  87. package/augment-extensions/workflows/openspec/rules/workflow.md +0 -214
@@ -1,169 +1,169 @@
1
- /**
2
- * Integration tests for screenplay file organization utilities
3
- */
4
-
5
- import * as fs from 'fs';
6
- import * as path from 'path';
7
- import * as os from 'os';
8
- import {
9
- getScreenplaysDir,
10
- ensureScreenplaysDir,
11
- getProjectNameFromOpenSpec,
12
- getProjectNameFromBeads,
13
- getProjectName,
14
- createProjectDir,
15
- ScreenplayProjectInfo
16
- } from '../file-organization';
17
-
18
- describe('Screenplay File Organization', () => {
19
- let testDir: string;
20
-
21
- beforeEach(() => {
22
- // Create a temporary test directory
23
- testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'screenplay-test-'));
24
- });
25
-
26
- afterEach(() => {
27
- // Clean up test directory
28
- if (fs.existsSync(testDir)) {
29
- fs.rmSync(testDir, { recursive: true, force: true });
30
- }
31
- });
32
-
33
- describe('getScreenplaysDir', () => {
34
- it('should return screenplays directory path', () => {
35
- const result = getScreenplaysDir(testDir);
36
- expect(result).toBe(path.join(testDir, 'screenplays'));
37
- });
38
- });
39
-
40
- describe('ensureScreenplaysDir', () => {
41
- it('should create screenplays directory if it does not exist', () => {
42
- const screenplaysDir = ensureScreenplaysDir(testDir);
43
- expect(fs.existsSync(screenplaysDir)).toBe(true);
44
- expect(fs.statSync(screenplaysDir).isDirectory()).toBe(true);
45
- });
46
-
47
- it('should not fail if screenplays directory already exists', () => {
48
- const screenplaysDir = path.join(testDir, 'screenplays');
49
- fs.mkdirSync(screenplaysDir);
50
-
51
- const result = ensureScreenplaysDir(testDir);
52
- expect(result).toBe(screenplaysDir);
53
- expect(fs.existsSync(screenplaysDir)).toBe(true);
54
- });
55
- });
56
-
57
- describe('getProjectNameFromOpenSpec', () => {
58
- it('should return null if openspec directory does not exist', () => {
59
- const result = getProjectNameFromOpenSpec(testDir);
60
- expect(result).toBeNull();
61
- });
62
-
63
- it('should return project info from OpenSpec changes', () => {
64
- // Create mock OpenSpec structure
65
- const changesDir = path.join(testDir, 'openspec', 'changes', 'heist-movie');
66
- fs.mkdirSync(changesDir, { recursive: true });
67
-
68
- const result = getProjectNameFromOpenSpec(testDir);
69
- expect(result).not.toBeNull();
70
- expect(result?.name).toBe('heist-movie');
71
- expect(result?.source).toBe('openspec');
72
- expect(result?.specId).toBe('heist-movie');
73
- });
74
- });
75
-
76
- describe('getProjectNameFromBeads', () => {
77
- it('should return null if .beads directory does not exist', () => {
78
- const result = getProjectNameFromBeads(testDir);
79
- expect(result).toBeNull();
80
- });
81
-
82
- it('should return project info from Beads epic', () => {
83
- // Create mock Beads structure
84
- const beadsDir = path.join(testDir, '.beads');
85
- fs.mkdirSync(beadsDir);
86
-
87
- const issue = {
88
- id: 'bd-scr-test',
89
- title: 'Test Screenplay',
90
- issue_type: 'epic',
91
- status: 'open',
92
- labels: ['screenplay', 'writing-standards']
93
- };
94
-
95
- fs.writeFileSync(
96
- path.join(beadsDir, 'issues.jsonl'),
97
- JSON.stringify(issue) + '\n'
98
- );
99
-
100
- const result = getProjectNameFromBeads(testDir);
101
- expect(result).not.toBeNull();
102
- expect(result?.name).toBe('bd-scr-test');
103
- expect(result?.source).toBe('beads');
104
- expect(result?.epicId).toBe('bd-scr-test');
105
- });
106
- });
107
-
108
- describe('getProjectName', () => {
109
- it('should prefer OpenSpec over Beads', () => {
110
- // Create both OpenSpec and Beads structures
111
- const changesDir = path.join(testDir, 'openspec', 'changes', 'openspec-project');
112
- fs.mkdirSync(changesDir, { recursive: true });
113
-
114
- const beadsDir = path.join(testDir, '.beads');
115
- fs.mkdirSync(beadsDir);
116
- fs.writeFileSync(
117
- path.join(beadsDir, 'issues.jsonl'),
118
- JSON.stringify({
119
- id: 'bd-beads-project',
120
- issue_type: 'epic',
121
- status: 'open',
122
- labels: ['screenplay']
123
- }) + '\n'
124
- );
125
-
126
- const result = getProjectName(testDir);
127
- expect(result.name).toBe('openspec-project');
128
- expect(result.source).toBe('openspec');
129
- });
130
-
131
- it('should use timestamp fallback if no context available', () => {
132
- const result = getProjectName(testDir);
133
- expect(result.source).toBe('manual');
134
- expect(result.name).toMatch(/^screenplay-\d{4}-\d{2}-\d{2}$/);
135
- });
136
- });
137
-
138
- describe('createProjectDir', () => {
139
- it('should create project directory', () => {
140
- const projectInfo: ScreenplayProjectInfo = {
141
- name: 'test-project',
142
- source: 'manual'
143
- };
144
-
145
- const projectDir = createProjectDir(projectInfo, { rootDir: testDir });
146
- expect(fs.existsSync(projectDir)).toBe(true);
147
- expect(projectDir).toBe(path.join(testDir, 'screenplays', 'test-project'));
148
- });
149
-
150
- it('should handle conflicts with append-number strategy', () => {
151
- const projectInfo: ScreenplayProjectInfo = {
152
- name: 'conflict-test',
153
- source: 'manual'
154
- };
155
-
156
- // Create first project
157
- const firstDir = createProjectDir(projectInfo, { rootDir: testDir });
158
- expect(firstDir).toBe(path.join(testDir, 'screenplays', 'conflict-test'));
159
-
160
- // Create second project with same name
161
- const secondDir = createProjectDir(projectInfo, {
162
- rootDir: testDir,
163
- handleConflicts: 'append-number'
164
- });
165
- expect(secondDir).toBe(path.join(testDir, 'screenplays', 'conflict-test-1'));
166
- });
167
- });
168
- });
169
-
1
+ /**
2
+ * Integration tests for screenplay file organization utilities
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import * as os from 'os';
8
+ import {
9
+ getScreenplaysDir,
10
+ ensureScreenplaysDir,
11
+ getProjectNameFromOpenSpec,
12
+ getProjectNameFromBeads,
13
+ getProjectName,
14
+ createProjectDir,
15
+ ScreenplayProjectInfo
16
+ } from '../file-organization';
17
+
18
+ describe('Screenplay File Organization', () => {
19
+ let testDir: string;
20
+
21
+ beforeEach(() => {
22
+ // Create a temporary test directory
23
+ testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'screenplay-test-'));
24
+ });
25
+
26
+ afterEach(() => {
27
+ // Clean up test directory
28
+ if (fs.existsSync(testDir)) {
29
+ fs.rmSync(testDir, { recursive: true, force: true });
30
+ }
31
+ });
32
+
33
+ describe('getScreenplaysDir', () => {
34
+ it('should return screenplays directory path', () => {
35
+ const result = getScreenplaysDir(testDir);
36
+ expect(result).toBe(path.join(testDir, 'screenplays'));
37
+ });
38
+ });
39
+
40
+ describe('ensureScreenplaysDir', () => {
41
+ it('should create screenplays directory if it does not exist', () => {
42
+ const screenplaysDir = ensureScreenplaysDir(testDir);
43
+ expect(fs.existsSync(screenplaysDir)).toBe(true);
44
+ expect(fs.statSync(screenplaysDir).isDirectory()).toBe(true);
45
+ });
46
+
47
+ it('should not fail if screenplays directory already exists', () => {
48
+ const screenplaysDir = path.join(testDir, 'screenplays');
49
+ fs.mkdirSync(screenplaysDir);
50
+
51
+ const result = ensureScreenplaysDir(testDir);
52
+ expect(result).toBe(screenplaysDir);
53
+ expect(fs.existsSync(screenplaysDir)).toBe(true);
54
+ });
55
+ });
56
+
57
+ describe('getProjectNameFromOpenSpec', () => {
58
+ it('should return null if openspec directory does not exist', () => {
59
+ const result = getProjectNameFromOpenSpec(testDir);
60
+ expect(result).toBeNull();
61
+ });
62
+
63
+ it('should return project info from OpenSpec changes', () => {
64
+ // Create mock OpenSpec structure
65
+ const changesDir = path.join(testDir, 'openspec', 'changes', 'heist-movie');
66
+ fs.mkdirSync(changesDir, { recursive: true });
67
+
68
+ const result = getProjectNameFromOpenSpec(testDir);
69
+ expect(result).not.toBeNull();
70
+ expect(result?.name).toBe('heist-movie');
71
+ expect(result?.source).toBe('openspec');
72
+ expect(result?.specId).toBe('heist-movie');
73
+ });
74
+ });
75
+
76
+ describe('getProjectNameFromBeads', () => {
77
+ it('should return null if .beads directory does not exist', () => {
78
+ const result = getProjectNameFromBeads(testDir);
79
+ expect(result).toBeNull();
80
+ });
81
+
82
+ it('should return project info from Beads epic', () => {
83
+ // Create mock Beads structure
84
+ const beadsDir = path.join(testDir, '.beads');
85
+ fs.mkdirSync(beadsDir);
86
+
87
+ const issue = {
88
+ id: 'bd-scr-test',
89
+ title: 'Test Screenplay',
90
+ issue_type: 'epic',
91
+ status: 'open',
92
+ labels: ['screenplay', 'writing-standards']
93
+ };
94
+
95
+ fs.writeFileSync(
96
+ path.join(beadsDir, 'issues.jsonl'),
97
+ JSON.stringify(issue) + '\n'
98
+ );
99
+
100
+ const result = getProjectNameFromBeads(testDir);
101
+ expect(result).not.toBeNull();
102
+ expect(result?.name).toBe('bd-scr-test');
103
+ expect(result?.source).toBe('beads');
104
+ expect(result?.epicId).toBe('bd-scr-test');
105
+ });
106
+ });
107
+
108
+ describe('getProjectName', () => {
109
+ it('should prefer OpenSpec over Beads', () => {
110
+ // Create both OpenSpec and Beads structures
111
+ const changesDir = path.join(testDir, 'openspec', 'changes', 'openspec-project');
112
+ fs.mkdirSync(changesDir, { recursive: true });
113
+
114
+ const beadsDir = path.join(testDir, '.beads');
115
+ fs.mkdirSync(beadsDir);
116
+ fs.writeFileSync(
117
+ path.join(beadsDir, 'issues.jsonl'),
118
+ JSON.stringify({
119
+ id: 'bd-beads-project',
120
+ issue_type: 'epic',
121
+ status: 'open',
122
+ labels: ['screenplay']
123
+ }) + '\n'
124
+ );
125
+
126
+ const result = getProjectName(testDir);
127
+ expect(result.name).toBe('openspec-project');
128
+ expect(result.source).toBe('openspec');
129
+ });
130
+
131
+ it('should use timestamp fallback if no context available', () => {
132
+ const result = getProjectName(testDir);
133
+ expect(result.source).toBe('manual');
134
+ expect(result.name).toMatch(/^screenplay-\d{4}-\d{2}-\d{2}$/);
135
+ });
136
+ });
137
+
138
+ describe('createProjectDir', () => {
139
+ it('should create project directory', () => {
140
+ const projectInfo: ScreenplayProjectInfo = {
141
+ name: 'test-project',
142
+ source: 'manual'
143
+ };
144
+
145
+ const projectDir = createProjectDir(projectInfo, { rootDir: testDir });
146
+ expect(fs.existsSync(projectDir)).toBe(true);
147
+ expect(projectDir).toBe(path.join(testDir, 'screenplays', 'test-project'));
148
+ });
149
+
150
+ it('should handle conflicts with append-number strategy', () => {
151
+ const projectInfo: ScreenplayProjectInfo = {
152
+ name: 'conflict-test',
153
+ source: 'manual'
154
+ };
155
+
156
+ // Create first project
157
+ const firstDir = createProjectDir(projectInfo, { rootDir: testDir });
158
+ expect(firstDir).toBe(path.join(testDir, 'screenplays', 'conflict-test'));
159
+
160
+ // Create second project with same name
161
+ const secondDir = createProjectDir(projectInfo, {
162
+ rootDir: testDir,
163
+ handleConflicts: 'append-number'
164
+ });
165
+ expect(secondDir).toBe(path.join(testDir, 'screenplays', 'conflict-test-1'));
166
+ });
167
+ });
168
+ });
169
+
@@ -1,165 +1,165 @@
1
- /**
2
- * File Organization Utilities for Screenplay Module
3
- *
4
- * Provides utilities for organizing screenplay files into structured directories
5
- * based on OpenSpec specs or Beads epics.
6
- */
7
-
8
- import * as fs from 'fs';
9
- import * as path from 'path';
10
-
11
- export interface ScreenplayProjectInfo {
12
- name: string;
13
- source: 'openspec' | 'beads' | 'manual';
14
- specId?: string;
15
- epicId?: string;
16
- timestamp?: string;
17
- }
18
-
19
- export interface OrganizationOptions {
20
- rootDir?: string;
21
- createIfMissing?: boolean;
22
- handleConflicts?: 'append-timestamp' | 'append-number' | 'error';
23
- }
24
-
25
- /**
26
- * Get the screenplays directory path
27
- */
28
- export function getScreenplaysDir(rootDir: string = process.cwd()): string {
29
- return path.join(rootDir, 'screenplays');
30
- }
31
-
32
- /**
33
- * Ensure the screenplays directory exists
34
- */
35
- export function ensureScreenplaysDir(rootDir: string = process.cwd()): string {
36
- const screenplaysDir = getScreenplaysDir(rootDir);
37
-
38
- if (!fs.existsSync(screenplaysDir)) {
39
- fs.mkdirSync(screenplaysDir, { recursive: true });
40
- }
41
-
42
- return screenplaysDir;
43
- }
44
-
45
- /**
46
- * Get project name from OpenSpec spec
47
- */
48
- export function getProjectNameFromOpenSpec(rootDir: string = process.cwd()): ScreenplayProjectInfo | null {
49
- const openspecDir = path.join(rootDir, 'openspec');
50
-
51
- if (!fs.existsSync(openspecDir)) {
52
- return null;
53
- }
54
-
55
- // Check for active changes
56
- const changesDir = path.join(openspecDir, 'changes');
57
- if (fs.existsSync(changesDir)) {
58
- const changes = fs.readdirSync(changesDir, { withFileTypes: true })
59
- .filter(dirent => dirent.isDirectory())
60
- .map(dirent => dirent.name);
61
-
62
- if (changes.length > 0) {
63
- // Use the first active change as the project name
64
- return {
65
- name: changes[0],
66
- source: 'openspec',
67
- specId: changes[0]
68
- };
69
- }
70
- }
71
-
72
- return null;
73
- }
74
-
75
- /**
76
- * Get project name from Beads epic
77
- */
78
- export function getProjectNameFromBeads(rootDir: string = process.cwd()): ScreenplayProjectInfo | null {
79
- const beadsFile = path.join(rootDir, '.beads', 'issues.jsonl');
80
-
81
- if (!fs.existsSync(beadsFile)) {
82
- return null;
83
- }
84
-
85
- try {
86
- const lines = fs.readFileSync(beadsFile, 'utf-8').split('\n').filter(line => line.trim());
87
-
88
- // Find the most recent open epic with screenplay labels
89
- for (let i = lines.length - 1; i >= 0; i--) {
90
- const issue = JSON.parse(lines[i]);
91
-
92
- if (issue.issue_type === 'epic' &&
93
- issue.status === 'open' &&
94
- (issue.labels?.includes('screenplay') || issue.labels?.includes('writing-standards'))) {
95
- return {
96
- name: issue.id,
97
- source: 'beads',
98
- epicId: issue.id
99
- };
100
- }
101
- }
102
- } catch (error) {
103
- console.warn('Error reading Beads issues:', error);
104
- }
105
-
106
- return null;
107
- }
108
-
109
- /**
110
- * Get project name with fallback logic
111
- */
112
- export function getProjectName(rootDir: string = process.cwd()): ScreenplayProjectInfo {
113
- // Try OpenSpec first
114
- const openspecInfo = getProjectNameFromOpenSpec(rootDir);
115
- if (openspecInfo) {
116
- return openspecInfo;
117
- }
118
-
119
- // Try Beads second
120
- const beadsInfo = getProjectNameFromBeads(rootDir);
121
- if (beadsInfo) {
122
- return beadsInfo;
123
- }
124
-
125
- // Fallback to timestamp
126
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0];
127
- return {
128
- name: `screenplay-${timestamp}`,
129
- source: 'manual',
130
- timestamp
131
- };
132
- }
133
-
134
- /**
135
- * Create project directory with conflict resolution
136
- */
137
- export function createProjectDir(
138
- projectInfo: ScreenplayProjectInfo,
139
- options: OrganizationOptions = {}
140
- ): string {
141
- const { rootDir = process.cwd(), handleConflicts = 'append-number' } = options;
142
-
143
- const screenplaysDir = ensureScreenplaysDir(rootDir);
144
- let projectDir = path.join(screenplaysDir, projectInfo.name);
145
-
146
- // Handle conflicts
147
- if (fs.existsSync(projectDir)) {
148
- if (handleConflicts === 'error') {
149
- throw new Error(`Project directory already exists: ${projectDir}`);
150
- } else if (handleConflicts === 'append-timestamp') {
151
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
152
- projectDir = path.join(screenplaysDir, `${projectInfo.name}-${timestamp}`);
153
- } else if (handleConflicts === 'append-number') {
154
- let counter = 1;
155
- while (fs.existsSync(projectDir)) {
156
- projectDir = path.join(screenplaysDir, `${projectInfo.name}-${counter}`);
157
- counter++;
158
- }
159
- }
160
- }
161
-
162
- fs.mkdirSync(projectDir, { recursive: true });
163
- return projectDir;
164
- }
165
-
1
+ /**
2
+ * File Organization Utilities for Screenplay Module
3
+ *
4
+ * Provides utilities for organizing screenplay files into structured directories
5
+ * based on OpenSpec specs or Beads epics.
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+
11
+ export interface ScreenplayProjectInfo {
12
+ name: string;
13
+ source: 'openspec' | 'beads' | 'manual';
14
+ specId?: string;
15
+ epicId?: string;
16
+ timestamp?: string;
17
+ }
18
+
19
+ export interface OrganizationOptions {
20
+ rootDir?: string;
21
+ createIfMissing?: boolean;
22
+ handleConflicts?: 'append-timestamp' | 'append-number' | 'error';
23
+ }
24
+
25
+ /**
26
+ * Get the screenplays directory path
27
+ */
28
+ export function getScreenplaysDir(rootDir: string = process.cwd()): string {
29
+ return path.join(rootDir, 'screenplays');
30
+ }
31
+
32
+ /**
33
+ * Ensure the screenplays directory exists
34
+ */
35
+ export function ensureScreenplaysDir(rootDir: string = process.cwd()): string {
36
+ const screenplaysDir = getScreenplaysDir(rootDir);
37
+
38
+ if (!fs.existsSync(screenplaysDir)) {
39
+ fs.mkdirSync(screenplaysDir, { recursive: true });
40
+ }
41
+
42
+ return screenplaysDir;
43
+ }
44
+
45
+ /**
46
+ * Get project name from OpenSpec spec
47
+ */
48
+ export function getProjectNameFromOpenSpec(rootDir: string = process.cwd()): ScreenplayProjectInfo | null {
49
+ const openspecDir = path.join(rootDir, 'openspec');
50
+
51
+ if (!fs.existsSync(openspecDir)) {
52
+ return null;
53
+ }
54
+
55
+ // Check for active changes
56
+ const changesDir = path.join(openspecDir, 'changes');
57
+ if (fs.existsSync(changesDir)) {
58
+ const changes = fs.readdirSync(changesDir, { withFileTypes: true })
59
+ .filter(dirent => dirent.isDirectory())
60
+ .map(dirent => dirent.name);
61
+
62
+ if (changes.length > 0) {
63
+ // Use the first active change as the project name
64
+ return {
65
+ name: changes[0],
66
+ source: 'openspec',
67
+ specId: changes[0]
68
+ };
69
+ }
70
+ }
71
+
72
+ return null;
73
+ }
74
+
75
+ /**
76
+ * Get project name from Beads epic
77
+ */
78
+ export function getProjectNameFromBeads(rootDir: string = process.cwd()): ScreenplayProjectInfo | null {
79
+ const beadsFile = path.join(rootDir, '.beads', 'issues.jsonl');
80
+
81
+ if (!fs.existsSync(beadsFile)) {
82
+ return null;
83
+ }
84
+
85
+ try {
86
+ const lines = fs.readFileSync(beadsFile, 'utf-8').split('\n').filter(line => line.trim());
87
+
88
+ // Find the most recent open epic with screenplay labels
89
+ for (let i = lines.length - 1; i >= 0; i--) {
90
+ const issue = JSON.parse(lines[i]);
91
+
92
+ if (issue.issue_type === 'epic' &&
93
+ issue.status === 'open' &&
94
+ (issue.labels?.includes('screenplay') || issue.labels?.includes('writing-standards'))) {
95
+ return {
96
+ name: issue.id,
97
+ source: 'beads',
98
+ epicId: issue.id
99
+ };
100
+ }
101
+ }
102
+ } catch (error) {
103
+ console.warn('Error reading Beads issues:', error);
104
+ }
105
+
106
+ return null;
107
+ }
108
+
109
+ /**
110
+ * Get project name with fallback logic
111
+ */
112
+ export function getProjectName(rootDir: string = process.cwd()): ScreenplayProjectInfo {
113
+ // Try OpenSpec first
114
+ const openspecInfo = getProjectNameFromOpenSpec(rootDir);
115
+ if (openspecInfo) {
116
+ return openspecInfo;
117
+ }
118
+
119
+ // Try Beads second
120
+ const beadsInfo = getProjectNameFromBeads(rootDir);
121
+ if (beadsInfo) {
122
+ return beadsInfo;
123
+ }
124
+
125
+ // Fallback to timestamp
126
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0];
127
+ return {
128
+ name: `screenplay-${timestamp}`,
129
+ source: 'manual',
130
+ timestamp
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Create project directory with conflict resolution
136
+ */
137
+ export function createProjectDir(
138
+ projectInfo: ScreenplayProjectInfo,
139
+ options: OrganizationOptions = {}
140
+ ): string {
141
+ const { rootDir = process.cwd(), handleConflicts = 'append-number' } = options;
142
+
143
+ const screenplaysDir = ensureScreenplaysDir(rootDir);
144
+ let projectDir = path.join(screenplaysDir, projectInfo.name);
145
+
146
+ // Handle conflicts
147
+ if (fs.existsSync(projectDir)) {
148
+ if (handleConflicts === 'error') {
149
+ throw new Error(`Project directory already exists: ${projectDir}`);
150
+ } else if (handleConflicts === 'append-timestamp') {
151
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
152
+ projectDir = path.join(screenplaysDir, `${projectInfo.name}-${timestamp}`);
153
+ } else if (handleConflicts === 'append-number') {
154
+ let counter = 1;
155
+ while (fs.existsSync(projectDir)) {
156
+ projectDir = path.join(screenplaysDir, `${projectInfo.name}-${counter}`);
157
+ counter++;
158
+ }
159
+ }
160
+ }
161
+
162
+ fs.mkdirSync(projectDir, { recursive: true });
163
+ return projectDir;
164
+ }
165
+