sdd-toolkit 1.9.0 → 1.9.2
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.
- package/README.md +393 -339
- package/README.pt.md +327 -327
- package/definitions/sdd-coder.yaml +71 -71
- package/definitions/sdd-feature.yaml +73 -73
- package/definitions/sdd-log.yaml +61 -61
- package/definitions/sdd-project.yaml +4 -28
- package/definitions/sdd-requirements.yaml +70 -70
- package/definitions/sdd-review.yaml +88 -88
- package/definitions/sdd.yaml +23 -23
- package/package.json +42 -42
- package/src/commands/view.js +18 -18
- package/src/index.js +98 -98
- package/src/lib/dashboard.js +187 -187
- package/src/lib/docs.js +69 -69
- package/src/lib/i18n.js +65 -65
- package/src/lib/messages.js +234 -234
- package/src/lib/profiles.js +186 -186
- package/src/lib/transformers.js +12 -12
- package/templates/guidelines.md +9 -9
- package/templates/project.md +28 -28
- package/templates/requirements.md +15 -15
- package/templates/task.md +11 -11
package/src/lib/dashboard.js
CHANGED
|
@@ -1,188 +1,188 @@
|
|
|
1
|
-
const fs = require('fs/promises');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const pc = require('picocolors');
|
|
4
|
-
const { t } = require('./i18n');
|
|
5
|
-
|
|
6
|
-
const DOCS_DIR = '.sdd-toolkit';
|
|
7
|
-
const PROJECT_FILE = path.join(DOCS_DIR, 'project.md');
|
|
8
|
-
const TASKS_FILE = path.join(DOCS_DIR, 'task.md');
|
|
9
|
-
const LOGS_DIR = path.join(DOCS_DIR, 'logs', 'executions');
|
|
10
|
-
const PACKAGE_FILE = 'package.json';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Extracts metadata from package.json (priority) or project.md
|
|
14
|
-
*/
|
|
15
|
-
async function getProjectMetadata() {
|
|
16
|
-
let meta = { title: 'Project', version: '0.0.0', status: 'Active' };
|
|
17
|
-
|
|
18
|
-
// Try reading package.json first (Single Source of Truth for Version)
|
|
19
|
-
try {
|
|
20
|
-
const pkgContent = await fs.readFile(PACKAGE_FILE, 'utf8');
|
|
21
|
-
const pkg = JSON.parse(pkgContent);
|
|
22
|
-
if (pkg.name) meta.title = pkg.name;
|
|
23
|
-
if (pkg.version) meta.version = pkg.version;
|
|
24
|
-
} catch (e) {
|
|
25
|
-
// Ignore if no package.json
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Try reading project.md for status or title fallback
|
|
29
|
-
try {
|
|
30
|
-
const content = await fs.readFile(PROJECT_FILE, 'utf8');
|
|
31
|
-
|
|
32
|
-
// Only override title if package.json didn't provide one
|
|
33
|
-
if (meta.title === 'Project') {
|
|
34
|
-
const titleMatch = content.match(/title:\s*(.+)/);
|
|
35
|
-
if (titleMatch) meta.title = titleMatch[1].trim();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const statusMatch = content.match(/status:\s*(.+)/);
|
|
39
|
-
if (statusMatch) meta.status = statusMatch[1].trim();
|
|
40
|
-
|
|
41
|
-
} catch (e) {
|
|
42
|
-
// Ignore errors
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return meta;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Parses task.md to calculate progress metrics
|
|
50
|
-
*/
|
|
51
|
-
async function getTaskProgress() {
|
|
52
|
-
try {
|
|
53
|
-
const content = await fs.readFile(TASKS_FILE, 'utf8');
|
|
54
|
-
|
|
55
|
-
// Extract Milestone Name
|
|
56
|
-
const milestoneMatch = content.match(/# Execution Backlog:\s*(.+)/);
|
|
57
|
-
const milestone = milestoneMatch ? milestoneMatch[1].trim() : 'General Tasks';
|
|
58
|
-
|
|
59
|
-
// Count Checkboxes
|
|
60
|
-
const total = (content.match(/- \[ \]/g) || []).length + (content.match(/- \[x\]/g) || []).length;
|
|
61
|
-
const done = (content.match(/- \[x\]/g) || []).length;
|
|
62
|
-
const pending = total - done;
|
|
63
|
-
const percent = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
64
|
-
|
|
65
|
-
return { milestone, total, done, pending, percent };
|
|
66
|
-
} catch (e) {
|
|
67
|
-
return { milestone: 'No active plan', total: 0, done: 0, pending: 0, percent: 0 };
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Retrieves the N most recent execution logs
|
|
73
|
-
*/
|
|
74
|
-
async function getRecentLogs(limit = 5) {
|
|
75
|
-
try {
|
|
76
|
-
// Ensure dir exists to avoid crash
|
|
77
|
-
await fs.access(LOGS_DIR);
|
|
78
|
-
|
|
79
|
-
const files = await fs.readdir(LOGS_DIR);
|
|
80
|
-
|
|
81
|
-
// Read stats for sorting
|
|
82
|
-
const fileStats = await Promise.all(
|
|
83
|
-
files
|
|
84
|
-
.filter(f => f.endsWith('.md'))
|
|
85
|
-
.map(async (file) => {
|
|
86
|
-
const filePath = path.join(LOGS_DIR, file);
|
|
87
|
-
const stats = await fs.stat(filePath);
|
|
88
|
-
return { file, mtime: stats.mtime, filePath };
|
|
89
|
-
})
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
// Sort by time desc
|
|
93
|
-
const sorted = fileStats.sort((a, b) => b.mtime - a.mtime).slice(0, limit);
|
|
94
|
-
|
|
95
|
-
// Read content to get status/task ID if possible (simplified for now)
|
|
96
|
-
const logs = sorted.map(item => {
|
|
97
|
-
const name = item.file.replace('.md', '');
|
|
98
|
-
return {
|
|
99
|
-
name,
|
|
100
|
-
date: item.mtime.toLocaleDateString(),
|
|
101
|
-
status: 'Completed'
|
|
102
|
-
};
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
return logs;
|
|
106
|
-
} catch (e) {
|
|
107
|
-
return [];
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* UI Component: Progress Bar
|
|
113
|
-
* [██████░░░░] 60%
|
|
114
|
-
*/
|
|
115
|
-
function drawProgressBar(percent, width = 20) {
|
|
116
|
-
const filled = Math.round((percent / 100) * width);
|
|
117
|
-
const empty = width - filled;
|
|
118
|
-
|
|
119
|
-
const bar = pc.green('█'.repeat(filled)) + pc.gray('░'.repeat(empty));
|
|
120
|
-
return `[${bar}] ${pc.bold(percent + '%')}`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* UI Component: Box
|
|
125
|
-
*/
|
|
126
|
-
function drawBox(lines) {
|
|
127
|
-
const maxWidth = Math.max(60, ...lines.map(l => l.replace(/\x1b\[[0-9;]*m/g, '').length));
|
|
128
|
-
const borderTop = '╭' + '─'.repeat(maxWidth + 2) + '╮';
|
|
129
|
-
const borderBottom = '╰' + '─'.repeat(maxWidth + 2) + '╯';
|
|
130
|
-
|
|
131
|
-
console.log(pc.gray(borderTop));
|
|
132
|
-
lines.forEach(line => {
|
|
133
|
-
const len = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
134
|
-
const padding = ' '.repeat(maxWidth - len);
|
|
135
|
-
console.log(pc.gray('│ ') + line + padding + pc.gray(' │'));
|
|
136
|
-
});
|
|
137
|
-
console.log(pc.gray(borderBottom));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Main Render Function
|
|
142
|
-
*/
|
|
143
|
-
async function renderDashboard() {
|
|
144
|
-
console.clear();
|
|
145
|
-
|
|
146
|
-
// Fetch Data Parallel
|
|
147
|
-
const [meta, progress, logs] = await Promise.all([
|
|
148
|
-
getProjectMetadata(),
|
|
149
|
-
getTaskProgress(),
|
|
150
|
-
getRecentLogs(5)
|
|
151
|
-
]);
|
|
152
|
-
|
|
153
|
-
// Header (using i18n keys)
|
|
154
|
-
drawBox([
|
|
155
|
-
`${pc.magenta(t('DASHBOARD.TITLE'))} ${pc.bold(meta.title)} (v${meta.version})`,
|
|
156
|
-
`${pc.cyan(t('DASHBOARD.PHASE'))} ${progress.milestone}`,
|
|
157
|
-
`${pc.yellow(t('DASHBOARD.STATUS'))} ${meta.status}`
|
|
158
|
-
]);
|
|
159
|
-
|
|
160
|
-
console.log(''); // Spacing
|
|
161
|
-
|
|
162
|
-
// Progress Section
|
|
163
|
-
console.log(pc.bold(t('DASHBOARD.OVERALL')));
|
|
164
|
-
console.log(drawProgressBar(progress.percent, 40));
|
|
165
|
-
console.log(`${pc.green('✅ ' + progress.done + ' ' + t('DASHBOARD.COMPLETED'))} | ${pc.red('⭕ ' + progress.pending + ' ' + t('DASHBOARD.PENDING'))}`);
|
|
166
|
-
|
|
167
|
-
console.log(''); // Spacing
|
|
168
|
-
|
|
169
|
-
// Recent Logs Section
|
|
170
|
-
if (logs.length > 0) {
|
|
171
|
-
console.log(pc.bold(t('DASHBOARD.RECENT')));
|
|
172
|
-
logs.forEach(log => {
|
|
173
|
-
console.log(`${pc.gray('•')} ${pc.dim(`[${log.date}]`)} ${log.name}`);
|
|
174
|
-
});
|
|
175
|
-
} else {
|
|
176
|
-
console.log(pc.gray(t('DASHBOARD.NO_ACTIVITY')));
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
console.log(''); // Spacing
|
|
180
|
-
console.log(pc.bgMagenta(pc.black(t('DASHBOARD.ACTION'))) + ' ' + t('DASHBOARD.HINT') + ' ' + pc.cyan('/dev:coder <Task_ID>') + ' ' + t('DASHBOARD.HINT_SUFFIX'));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
module.exports = {
|
|
184
|
-
getProjectMetadata,
|
|
185
|
-
getTaskProgress,
|
|
186
|
-
getRecentLogs,
|
|
187
|
-
renderDashboard
|
|
1
|
+
const fs = require('fs/promises');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const pc = require('picocolors');
|
|
4
|
+
const { t } = require('./i18n');
|
|
5
|
+
|
|
6
|
+
const DOCS_DIR = '.sdd-toolkit';
|
|
7
|
+
const PROJECT_FILE = path.join(DOCS_DIR, 'project.md');
|
|
8
|
+
const TASKS_FILE = path.join(DOCS_DIR, 'task.md');
|
|
9
|
+
const LOGS_DIR = path.join(DOCS_DIR, 'logs', 'executions');
|
|
10
|
+
const PACKAGE_FILE = 'package.json';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extracts metadata from package.json (priority) or project.md
|
|
14
|
+
*/
|
|
15
|
+
async function getProjectMetadata() {
|
|
16
|
+
let meta = { title: 'Project', version: '0.0.0', status: 'Active' };
|
|
17
|
+
|
|
18
|
+
// Try reading package.json first (Single Source of Truth for Version)
|
|
19
|
+
try {
|
|
20
|
+
const pkgContent = await fs.readFile(PACKAGE_FILE, 'utf8');
|
|
21
|
+
const pkg = JSON.parse(pkgContent);
|
|
22
|
+
if (pkg.name) meta.title = pkg.name;
|
|
23
|
+
if (pkg.version) meta.version = pkg.version;
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// Ignore if no package.json
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Try reading project.md for status or title fallback
|
|
29
|
+
try {
|
|
30
|
+
const content = await fs.readFile(PROJECT_FILE, 'utf8');
|
|
31
|
+
|
|
32
|
+
// Only override title if package.json didn't provide one
|
|
33
|
+
if (meta.title === 'Project') {
|
|
34
|
+
const titleMatch = content.match(/title:\s*(.+)/);
|
|
35
|
+
if (titleMatch) meta.title = titleMatch[1].trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const statusMatch = content.match(/status:\s*(.+)/);
|
|
39
|
+
if (statusMatch) meta.status = statusMatch[1].trim();
|
|
40
|
+
|
|
41
|
+
} catch (e) {
|
|
42
|
+
// Ignore errors
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return meta;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parses task.md to calculate progress metrics
|
|
50
|
+
*/
|
|
51
|
+
async function getTaskProgress() {
|
|
52
|
+
try {
|
|
53
|
+
const content = await fs.readFile(TASKS_FILE, 'utf8');
|
|
54
|
+
|
|
55
|
+
// Extract Milestone Name
|
|
56
|
+
const milestoneMatch = content.match(/# Execution Backlog:\s*(.+)/);
|
|
57
|
+
const milestone = milestoneMatch ? milestoneMatch[1].trim() : 'General Tasks';
|
|
58
|
+
|
|
59
|
+
// Count Checkboxes
|
|
60
|
+
const total = (content.match(/- \[ \]/g) || []).length + (content.match(/- \[x\]/g) || []).length;
|
|
61
|
+
const done = (content.match(/- \[x\]/g) || []).length;
|
|
62
|
+
const pending = total - done;
|
|
63
|
+
const percent = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
64
|
+
|
|
65
|
+
return { milestone, total, done, pending, percent };
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return { milestone: 'No active plan', total: 0, done: 0, pending: 0, percent: 0 };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Retrieves the N most recent execution logs
|
|
73
|
+
*/
|
|
74
|
+
async function getRecentLogs(limit = 5) {
|
|
75
|
+
try {
|
|
76
|
+
// Ensure dir exists to avoid crash
|
|
77
|
+
await fs.access(LOGS_DIR);
|
|
78
|
+
|
|
79
|
+
const files = await fs.readdir(LOGS_DIR);
|
|
80
|
+
|
|
81
|
+
// Read stats for sorting
|
|
82
|
+
const fileStats = await Promise.all(
|
|
83
|
+
files
|
|
84
|
+
.filter(f => f.endsWith('.md'))
|
|
85
|
+
.map(async (file) => {
|
|
86
|
+
const filePath = path.join(LOGS_DIR, file);
|
|
87
|
+
const stats = await fs.stat(filePath);
|
|
88
|
+
return { file, mtime: stats.mtime, filePath };
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Sort by time desc
|
|
93
|
+
const sorted = fileStats.sort((a, b) => b.mtime - a.mtime).slice(0, limit);
|
|
94
|
+
|
|
95
|
+
// Read content to get status/task ID if possible (simplified for now)
|
|
96
|
+
const logs = sorted.map(item => {
|
|
97
|
+
const name = item.file.replace('.md', '');
|
|
98
|
+
return {
|
|
99
|
+
name,
|
|
100
|
+
date: item.mtime.toLocaleDateString(),
|
|
101
|
+
status: 'Completed'
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return logs;
|
|
106
|
+
} catch (e) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* UI Component: Progress Bar
|
|
113
|
+
* [██████░░░░] 60%
|
|
114
|
+
*/
|
|
115
|
+
function drawProgressBar(percent, width = 20) {
|
|
116
|
+
const filled = Math.round((percent / 100) * width);
|
|
117
|
+
const empty = width - filled;
|
|
118
|
+
|
|
119
|
+
const bar = pc.green('█'.repeat(filled)) + pc.gray('░'.repeat(empty));
|
|
120
|
+
return `[${bar}] ${pc.bold(percent + '%')}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* UI Component: Box
|
|
125
|
+
*/
|
|
126
|
+
function drawBox(lines) {
|
|
127
|
+
const maxWidth = Math.max(60, ...lines.map(l => l.replace(/\x1b\[[0-9;]*m/g, '').length));
|
|
128
|
+
const borderTop = '╭' + '─'.repeat(maxWidth + 2) + '╮';
|
|
129
|
+
const borderBottom = '╰' + '─'.repeat(maxWidth + 2) + '╯';
|
|
130
|
+
|
|
131
|
+
console.log(pc.gray(borderTop));
|
|
132
|
+
lines.forEach(line => {
|
|
133
|
+
const len = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
134
|
+
const padding = ' '.repeat(maxWidth - len);
|
|
135
|
+
console.log(pc.gray('│ ') + line + padding + pc.gray(' │'));
|
|
136
|
+
});
|
|
137
|
+
console.log(pc.gray(borderBottom));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Main Render Function
|
|
142
|
+
*/
|
|
143
|
+
async function renderDashboard() {
|
|
144
|
+
console.clear();
|
|
145
|
+
|
|
146
|
+
// Fetch Data Parallel
|
|
147
|
+
const [meta, progress, logs] = await Promise.all([
|
|
148
|
+
getProjectMetadata(),
|
|
149
|
+
getTaskProgress(),
|
|
150
|
+
getRecentLogs(5)
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
// Header (using i18n keys)
|
|
154
|
+
drawBox([
|
|
155
|
+
`${pc.magenta(t('DASHBOARD.TITLE'))} ${pc.bold(meta.title)} (v${meta.version})`,
|
|
156
|
+
`${pc.cyan(t('DASHBOARD.PHASE'))} ${progress.milestone}`,
|
|
157
|
+
`${pc.yellow(t('DASHBOARD.STATUS'))} ${meta.status}`
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
console.log(''); // Spacing
|
|
161
|
+
|
|
162
|
+
// Progress Section
|
|
163
|
+
console.log(pc.bold(t('DASHBOARD.OVERALL')));
|
|
164
|
+
console.log(drawProgressBar(progress.percent, 40));
|
|
165
|
+
console.log(`${pc.green('✅ ' + progress.done + ' ' + t('DASHBOARD.COMPLETED'))} | ${pc.red('⭕ ' + progress.pending + ' ' + t('DASHBOARD.PENDING'))}`);
|
|
166
|
+
|
|
167
|
+
console.log(''); // Spacing
|
|
168
|
+
|
|
169
|
+
// Recent Logs Section
|
|
170
|
+
if (logs.length > 0) {
|
|
171
|
+
console.log(pc.bold(t('DASHBOARD.RECENT')));
|
|
172
|
+
logs.forEach(log => {
|
|
173
|
+
console.log(`${pc.gray('•')} ${pc.dim(`[${log.date}]`)} ${log.name}`);
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
console.log(pc.gray(t('DASHBOARD.NO_ACTIVITY')));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log(''); // Spacing
|
|
180
|
+
console.log(pc.bgMagenta(pc.black(t('DASHBOARD.ACTION'))) + ' ' + t('DASHBOARD.HINT') + ' ' + pc.cyan('/dev:coder <Task_ID>') + ' ' + t('DASHBOARD.HINT_SUFFIX'));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = {
|
|
184
|
+
getProjectMetadata,
|
|
185
|
+
getTaskProgress,
|
|
186
|
+
getRecentLogs,
|
|
187
|
+
renderDashboard
|
|
188
188
|
};
|
package/src/lib/docs.js
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Generates the necessary folder structure for the agents
|
|
6
|
-
* Implements Smart Scaffolding: Only creates missing files/folders.
|
|
7
|
-
*/
|
|
8
|
-
function generateWorkflowGuide(baseDir) {
|
|
9
|
-
const docsDir = path.join(baseDir, '.sdd-toolkit');
|
|
10
|
-
|
|
11
|
-
// 1. Define folder structure based on new logging architecture
|
|
12
|
-
const folders = [
|
|
13
|
-
path.join(docsDir, 'features'),
|
|
14
|
-
path.join(docsDir, 'logs'),
|
|
15
|
-
path.join(docsDir, 'logs', 'executions'),
|
|
16
|
-
path.join(docsDir, 'logs', 'reviews'),
|
|
17
|
-
path.join(docsDir, 'logs', 'archive'),
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
let stats = { created: 0, verified: 0 };
|
|
21
|
-
|
|
22
|
-
// Create folders
|
|
23
|
-
folders.forEach(dir => {
|
|
24
|
-
if (!fs.existsSync(dir)) {
|
|
25
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
26
|
-
stats.created++;
|
|
27
|
-
} else {
|
|
28
|
-
stats.verified++;
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// 2. Define Templates Mapping
|
|
33
|
-
// Assumes templates are located in project_root/templates/
|
|
34
|
-
// __dirname is src/lib/, so templates is ../../templates
|
|
35
|
-
const templatesDir = path.join(__dirname, '..', '..', 'templates');
|
|
36
|
-
|
|
37
|
-
const templateFiles = [
|
|
38
|
-
{ src: 'guidelines.md', dest: 'guidelines.md' },
|
|
39
|
-
{ src: 'project.md', dest: 'project.md' },
|
|
40
|
-
{ src: 'requirements.md', dest: 'requirements.md' },
|
|
41
|
-
{ src: 'milestones.md', dest: 'milestones.md' },
|
|
42
|
-
{ src: 'task.md', dest: 'task.md' }
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
// Create files if they don't exist
|
|
46
|
-
templateFiles.forEach(tpl => {
|
|
47
|
-
const destPath = path.join(docsDir, tpl.dest);
|
|
48
|
-
if (!fs.existsSync(destPath)) {
|
|
49
|
-
try {
|
|
50
|
-
// Ensure template exists before reading
|
|
51
|
-
const templatePath = path.join(templatesDir, tpl.src);
|
|
52
|
-
if (fs.existsSync(templatePath)) {
|
|
53
|
-
const content = fs.readFileSync(templatePath, 'utf8');
|
|
54
|
-
fs.writeFileSync(destPath, content);
|
|
55
|
-
stats.created++;
|
|
56
|
-
}
|
|
57
|
-
} catch (e) {
|
|
58
|
-
// Fail silently/warn, do not crash the installer
|
|
59
|
-
console.warn(`Warning: Could not scaffold ${tpl.dest}`);
|
|
60
|
-
}
|
|
61
|
-
} else {
|
|
62
|
-
stats.verified++;
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
return stats;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
module.exports = { generateWorkflowGuide };
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates the necessary folder structure for the agents
|
|
6
|
+
* Implements Smart Scaffolding: Only creates missing files/folders.
|
|
7
|
+
*/
|
|
8
|
+
function generateWorkflowGuide(baseDir) {
|
|
9
|
+
const docsDir = path.join(baseDir, '.sdd-toolkit');
|
|
10
|
+
|
|
11
|
+
// 1. Define folder structure based on new logging architecture
|
|
12
|
+
const folders = [
|
|
13
|
+
path.join(docsDir, 'features'),
|
|
14
|
+
path.join(docsDir, 'logs'),
|
|
15
|
+
path.join(docsDir, 'logs', 'executions'),
|
|
16
|
+
path.join(docsDir, 'logs', 'reviews'),
|
|
17
|
+
path.join(docsDir, 'logs', 'archive'),
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
let stats = { created: 0, verified: 0 };
|
|
21
|
+
|
|
22
|
+
// Create folders
|
|
23
|
+
folders.forEach(dir => {
|
|
24
|
+
if (!fs.existsSync(dir)) {
|
|
25
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
26
|
+
stats.created++;
|
|
27
|
+
} else {
|
|
28
|
+
stats.verified++;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// 2. Define Templates Mapping
|
|
33
|
+
// Assumes templates are located in project_root/templates/
|
|
34
|
+
// __dirname is src/lib/, so templates is ../../templates
|
|
35
|
+
const templatesDir = path.join(__dirname, '..', '..', 'templates');
|
|
36
|
+
|
|
37
|
+
const templateFiles = [
|
|
38
|
+
{ src: 'guidelines.md', dest: 'guidelines.md' },
|
|
39
|
+
{ src: 'project.md', dest: 'project.md' },
|
|
40
|
+
{ src: 'requirements.md', dest: 'requirements.md' },
|
|
41
|
+
{ src: 'milestones.md', dest: 'milestones.md' },
|
|
42
|
+
{ src: 'task.md', dest: 'task.md' }
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// Create files if they don't exist
|
|
46
|
+
templateFiles.forEach(tpl => {
|
|
47
|
+
const destPath = path.join(docsDir, tpl.dest);
|
|
48
|
+
if (!fs.existsSync(destPath)) {
|
|
49
|
+
try {
|
|
50
|
+
// Ensure template exists before reading
|
|
51
|
+
const templatePath = path.join(templatesDir, tpl.src);
|
|
52
|
+
if (fs.existsSync(templatePath)) {
|
|
53
|
+
const content = fs.readFileSync(templatePath, 'utf8');
|
|
54
|
+
fs.writeFileSync(destPath, content);
|
|
55
|
+
stats.created++;
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
// Fail silently/warn, do not crash the installer
|
|
59
|
+
console.warn(`Warning: Could not scaffold ${tpl.dest}`);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
stats.verified++;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return stats;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = { generateWorkflowGuide };
|
package/src/lib/i18n.js
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal i18n Engine
|
|
3
|
-
*/
|
|
4
|
-
const { TRANSLATIONS } = require('./messages');
|
|
5
|
-
|
|
6
|
-
let currentLocale = 'en';
|
|
7
|
-
|
|
8
|
-
function setLocale(locale) {
|
|
9
|
-
// Normalize logic (pt-br -> pt_br) if passed incorrectly, though prompts usually give controlled values
|
|
10
|
-
const normalized = locale.toLowerCase().replace('-', '_');
|
|
11
|
-
|
|
12
|
-
if (TRANSLATIONS[normalized]) {
|
|
13
|
-
currentLocale = normalized;
|
|
14
|
-
} else {
|
|
15
|
-
// Fallback to en
|
|
16
|
-
currentLocale = 'en';
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function getLocale() {
|
|
21
|
-
return currentLocale;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Retrieves a string from the dictionary based on dot notation.
|
|
26
|
-
* e.g. t('INTRO.TITLE')
|
|
27
|
-
*/
|
|
28
|
-
function t(path, ...args) {
|
|
29
|
-
const keys = path.split('.');
|
|
30
|
-
let value = TRANSLATIONS[currentLocale];
|
|
31
|
-
|
|
32
|
-
// Traverse the object for the current locale
|
|
33
|
-
for (const key of keys) {
|
|
34
|
-
if (value && value[key]) {
|
|
35
|
-
value = value[key];
|
|
36
|
-
} else {
|
|
37
|
-
value = undefined;
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// If not found, Try Fallback to EN
|
|
43
|
-
if (!value) {
|
|
44
|
-
let fallback = TRANSLATIONS['en'];
|
|
45
|
-
for (const k of keys) {
|
|
46
|
-
if (fallback && fallback[k]) fallback = fallback[k];
|
|
47
|
-
else {
|
|
48
|
-
fallback = undefined;
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
value = fallback;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// If still not found, return the path itself as a debug marker
|
|
56
|
-
if (!value) return path;
|
|
57
|
-
|
|
58
|
-
if (typeof value === 'function') {
|
|
59
|
-
return value(...args);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return value;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
module.exports = { t, setLocale, getLocale };
|
|
1
|
+
/**
|
|
2
|
+
* Internal i18n Engine
|
|
3
|
+
*/
|
|
4
|
+
const { TRANSLATIONS } = require('./messages');
|
|
5
|
+
|
|
6
|
+
let currentLocale = 'en';
|
|
7
|
+
|
|
8
|
+
function setLocale(locale) {
|
|
9
|
+
// Normalize logic (pt-br -> pt_br) if passed incorrectly, though prompts usually give controlled values
|
|
10
|
+
const normalized = locale.toLowerCase().replace('-', '_');
|
|
11
|
+
|
|
12
|
+
if (TRANSLATIONS[normalized]) {
|
|
13
|
+
currentLocale = normalized;
|
|
14
|
+
} else {
|
|
15
|
+
// Fallback to en
|
|
16
|
+
currentLocale = 'en';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getLocale() {
|
|
21
|
+
return currentLocale;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Retrieves a string from the dictionary based on dot notation.
|
|
26
|
+
* e.g. t('INTRO.TITLE')
|
|
27
|
+
*/
|
|
28
|
+
function t(path, ...args) {
|
|
29
|
+
const keys = path.split('.');
|
|
30
|
+
let value = TRANSLATIONS[currentLocale];
|
|
31
|
+
|
|
32
|
+
// Traverse the object for the current locale
|
|
33
|
+
for (const key of keys) {
|
|
34
|
+
if (value && value[key]) {
|
|
35
|
+
value = value[key];
|
|
36
|
+
} else {
|
|
37
|
+
value = undefined;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If not found, Try Fallback to EN
|
|
43
|
+
if (!value) {
|
|
44
|
+
let fallback = TRANSLATIONS['en'];
|
|
45
|
+
for (const k of keys) {
|
|
46
|
+
if (fallback && fallback[k]) fallback = fallback[k];
|
|
47
|
+
else {
|
|
48
|
+
fallback = undefined;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
value = fallback;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// If still not found, return the path itself as a debug marker
|
|
56
|
+
if (!value) return path;
|
|
57
|
+
|
|
58
|
+
if (typeof value === 'function') {
|
|
59
|
+
return value(...args);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { t, setLocale, getLocale };
|