adi_dev_workflow 1.3.0 → 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 (3) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +27 -2
  3. package/src/installer.js +185 -106
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adi_dev_workflow",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Install SDD, miniStack and TaskCard development frameworks for Claude Code and Cursor",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -92,10 +92,35 @@ export async function run(argv) {
92
92
 
93
93
  console.log(`\nInstalling to ${ideLabel} ...\n`);
94
94
 
95
- const summary = await install({ cwd, target, frameworks });
95
+ const summary = await install({
96
+ cwd,
97
+ target,
98
+ frameworks,
99
+ confirmOverwrite: async (labels) => {
100
+ console.log(` Os seguintes ${labels.length} itens ja existem em ${ideLabel} e serao SOBRESCRITOS:\n`);
101
+ for (const l of labels) console.log(` - ${l}`);
102
+ console.log('');
103
+ const res = await prompts({
104
+ type: 'confirm',
105
+ name: 'ok',
106
+ message: 'Deseja continuar e sobrescrever?',
107
+ initial: true,
108
+ });
109
+ return res.ok === true;
110
+ },
111
+ });
112
+
113
+ if (summary.cancelled) {
114
+ console.log('\n Install cancelled.\n');
115
+ return;
116
+ }
96
117
 
97
118
  console.log(` Done!\n`);
98
- console.log(` ${summary.skills} skills + ${summary.commands} commands + ${summary.templates} templates + ${summary.agents} agents + ${summary.config} config installed.\n`);
119
+ console.log(` ${summary.skills} skills + ${summary.commands} commands + ${summary.templates} templates + ${summary.agents} agents + ${summary.config} config installed.`);
120
+ if (summary.overwritten > 0) {
121
+ console.log(` ${summary.overwritten} item(s) sobrescritos.`);
122
+ }
123
+ console.log('');
99
124
 
100
125
  // Usage hints
101
126
  const hints = [];
package/src/installer.js CHANGED
@@ -6,64 +6,176 @@ import { transformContent } from './transformer.js';
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
7
  const FRAMEWORKS_DIR = join(__dirname, '..', 'frameworks');
8
8
 
9
- const FRAMEWORK_MAP = {
10
- sdd: {
11
- commands: 'sdd',
12
- skills: ['sdd-prd-expert', 'sdd-spec-tech-expert', 'sdd-task-plan-expert', 'sdd-qa-expert'],
13
- },
14
- ministack: {
15
- commands: 'ministack',
16
- skills: ['ministack-intent-expert', 'ministack-scope-expert', 'ministack-expert', 'ministack-qa-expert'],
17
- },
18
- taskcard: {
19
- commands: 'taskcard',
20
- skills: ['taskcard-expert', 'taskcard-qa-expert'],
21
- },
22
- shared: {
23
- commands: null, // root-level commands
24
- skills: [],
25
- },
26
- };
27
-
28
9
  const IDE_DIRS = {
29
10
  claude: '.claude',
30
11
  cursor: '.cursor',
31
12
  };
32
13
 
33
- /**
34
- * Copy a directory recursively, applying transformation.
35
- */
14
+ // Prefixos usados para rotear skills e commands ao framework correto.
15
+ // Qualquer skill sem prefixo conhecido vai para 'shared'.
16
+ const FRAMEWORK_PREFIXES = {
17
+ sdd: 'sdd',
18
+ ministack: 'ministack',
19
+ taskcard: 'taskcard',
20
+ };
21
+
22
+ async function exists(path) {
23
+ try {
24
+ await stat(path);
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ async function listDirs(dir) {
32
+ if (!(await exists(dir))) return [];
33
+ const entries = await readdir(dir, { withFileTypes: true });
34
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
35
+ }
36
+
37
+ async function listFiles(dir) {
38
+ if (!(await exists(dir))) return [];
39
+ const entries = await readdir(dir, { withFileTypes: true });
40
+ return entries.filter((e) => e.isFile()).map((e) => e.name);
41
+ }
42
+
43
+ function skillFramework(skillName) {
44
+ for (const [fw, prefix] of Object.entries(FRAMEWORK_PREFIXES)) {
45
+ if (skillName === prefix || skillName.startsWith(prefix + '-')) return fw;
46
+ }
47
+ return 'shared';
48
+ }
49
+
50
+ async function copyFile(src, dest, target) {
51
+ await mkdir(dirname(dest), { recursive: true });
52
+ if (src.endsWith('.md')) {
53
+ const content = await readFile(src, 'utf-8');
54
+ const transformed = transformContent(content, target);
55
+ await writeFile(dest, transformed, 'utf-8');
56
+ } else {
57
+ const content = await readFile(src);
58
+ await writeFile(dest, content);
59
+ }
60
+ }
61
+
36
62
  async function copyDir(src, dest, target) {
37
63
  await mkdir(dest, { recursive: true });
38
64
  const entries = await readdir(src, { withFileTypes: true });
39
-
40
65
  for (const entry of entries) {
41
66
  const srcPath = join(src, entry.name);
42
67
  const destPath = join(dest, entry.name);
43
-
44
68
  if (entry.isDirectory()) {
45
69
  await copyDir(srcPath, destPath, target);
46
- } else if (entry.name.endsWith('.md')) {
47
- const content = await readFile(srcPath, 'utf-8');
48
- const transformed = transformContent(content, target);
49
- await writeFile(destPath, transformed, 'utf-8');
50
70
  } else {
51
- const content = await readFile(srcPath);
52
- await writeFile(destPath, content);
71
+ await copyFile(srcPath, destPath, target);
53
72
  }
54
73
  }
55
74
  }
56
75
 
76
+ async function countMarkdownRecursive(dir) {
77
+ let count = 0;
78
+ const entries = await readdir(dir, { withFileTypes: true });
79
+ for (const entry of entries) {
80
+ const full = join(dir, entry.name);
81
+ if (entry.isDirectory()) count += await countMarkdownRecursive(full);
82
+ else if (entry.name.endsWith('.md')) count++;
83
+ }
84
+ return count;
85
+ }
86
+
57
87
  /**
58
- * Check if a path exists.
88
+ * Constroi o plano de instalacao varrendo frameworks/ e mapeando cada item
89
+ * (skill, command, agent, template, config) para seu destino no IDE.
90
+ * Cada item e marcado com `overwrite: true` se o destino ja existir.
59
91
  */
60
- async function exists(path) {
61
- try {
62
- await stat(path);
63
- return true;
64
- } catch {
65
- return false;
92
+ async function buildPlan({ cwd, target, frameworks }) {
93
+ const ideDir = join(cwd, IDE_DIRS[target]);
94
+ const selected = new Set(frameworks);
95
+ const items = [];
96
+
97
+ // Skills: detectadas automaticamente por prefixo
98
+ const skillNames = await listDirs(join(FRAMEWORKS_DIR, 'skills'));
99
+ for (const name of skillNames) {
100
+ const fw = skillFramework(name);
101
+ if (!selected.has(fw)) continue;
102
+ items.push({
103
+ category: 'skills',
104
+ kind: 'dir',
105
+ src: join(FRAMEWORKS_DIR, 'skills', name),
106
+ dest: join(ideDir, 'skills', name),
107
+ label: `skills/${name}`,
108
+ });
109
+ }
110
+
111
+ // Commands: subdir por framework + arquivos root-level para 'shared'
112
+ const commandDirs = await listDirs(join(FRAMEWORKS_DIR, 'commands'));
113
+ for (const name of commandDirs) {
114
+ if (!FRAMEWORK_PREFIXES[name] || !selected.has(name)) continue;
115
+ items.push({
116
+ category: 'commands',
117
+ kind: 'dir',
118
+ src: join(FRAMEWORKS_DIR, 'commands', name),
119
+ dest: join(ideDir, 'commands', name),
120
+ label: `commands/${name}/`,
121
+ });
122
+ }
123
+ if (selected.has('shared')) {
124
+ const rootCmds = await listFiles(join(FRAMEWORKS_DIR, 'commands'));
125
+ for (const name of rootCmds) {
126
+ if (!name.endsWith('.md')) continue;
127
+ items.push({
128
+ category: 'commands',
129
+ kind: 'file',
130
+ src: join(FRAMEWORKS_DIR, 'commands', name),
131
+ dest: join(ideDir, 'commands', name),
132
+ label: `commands/${name}`,
133
+ });
134
+ }
135
+ }
136
+
137
+ // Agents, templates, config: pertencem a 'shared'
138
+ if (selected.has('shared')) {
139
+ for (const name of await listFiles(join(FRAMEWORKS_DIR, 'agents'))) {
140
+ if (!name.endsWith('.md')) continue;
141
+ items.push({
142
+ category: 'agents',
143
+ kind: 'file',
144
+ src: join(FRAMEWORKS_DIR, 'agents', name),
145
+ dest: join(ideDir, 'agents', name),
146
+ label: `agents/${name}`,
147
+ });
148
+ }
149
+ for (const name of await listFiles(join(FRAMEWORKS_DIR, 'templates'))) {
150
+ if (!name.endsWith('.md')) continue;
151
+ items.push({
152
+ category: 'templates',
153
+ kind: 'file',
154
+ src: join(FRAMEWORKS_DIR, 'templates', name),
155
+ dest: join(ideDir, 'templates', name),
156
+ label: `templates/${name}`,
157
+ });
158
+ }
159
+ for (const name of await listFiles(join(FRAMEWORKS_DIR, 'config'))) {
160
+ items.push({
161
+ category: 'config',
162
+ kind: 'file',
163
+ src: join(FRAMEWORKS_DIR, 'config', name),
164
+ dest: join(ideDir, 'config', name),
165
+ label: `config/${name}`,
166
+ });
167
+ }
168
+ }
169
+
170
+ for (const item of items) {
171
+ item.overwrite = await exists(item.dest);
66
172
  }
173
+
174
+ return { ideDir, items };
175
+ }
176
+
177
+ export async function planInstall(options) {
178
+ return buildPlan(options);
67
179
  }
68
180
 
69
181
  /**
@@ -73,81 +185,48 @@ async function exists(path) {
73
185
  * @param {string} options.cwd - Current working directory
74
186
  * @param {'claude'|'cursor'} options.target - Target IDE
75
187
  * @param {string[]} options.frameworks - Frameworks to install ('sdd', 'ministack', 'taskcard', 'shared')
76
- * @returns {Object} Summary of installed items
188
+ * @param {(labels: string[]) => Promise<boolean>} [options.confirmOverwrite] - Called when items will be overwritten
189
+ * @returns {Object} Summary of installed items (or { cancelled: true })
77
190
  */
78
- export async function install({ cwd, target, frameworks }) {
79
- const ideDir = join(cwd, IDE_DIRS[target]);
80
- const commandsDir = join(ideDir, 'commands');
81
- const skillsDir = join(ideDir, 'skills');
82
- const templatesDir = join(ideDir, 'templates');
83
- const agentsDir = join(ideDir, 'agents');
84
-
85
- const summary = { commands: 0, skills: 0, templates: 0, agents: 0, config: 0 };
86
-
87
- for (const fw of frameworks) {
88
- const config = FRAMEWORK_MAP[fw];
89
- if (!config) continue;
90
-
91
- // Copy commands
92
- if (fw === 'shared') {
93
- // Root-level commands (generate-prompt.md, sync-tasks-to-linear.md)
94
- await mkdir(commandsDir, { recursive: true });
95
- const srcCmds = join(FRAMEWORKS_DIR, 'commands');
96
- const entries = await readdir(srcCmds, { withFileTypes: true });
97
-
98
- for (const entry of entries) {
99
- if (entry.isFile() && entry.name.endsWith('.md')) {
100
- const content = await readFile(join(srcCmds, entry.name), 'utf-8');
101
- const transformed = transformContent(content, target);
102
- await writeFile(join(commandsDir, entry.name), transformed, 'utf-8');
103
- summary.commands++;
104
- }
105
- }
191
+ export async function install({ cwd, target, frameworks, confirmOverwrite }) {
192
+ const plan = await buildPlan({ cwd, target, frameworks });
193
+ const overwrites = plan.items.filter((i) => i.overwrite);
106
194
 
107
- // Shared templates
108
- const srcTemplates = join(FRAMEWORKS_DIR, 'templates');
109
- if (await exists(srcTemplates)) {
110
- await copyDir(srcTemplates, templatesDir, target);
111
- const tplEntries = await readdir(srcTemplates);
112
- summary.templates += tplEntries.filter((f) => f.endsWith('.md')).length;
113
- }
114
-
115
- // Shared agents
116
- const srcAgents = join(FRAMEWORKS_DIR, 'agents');
117
- if (await exists(srcAgents)) {
118
- await copyDir(srcAgents, agentsDir, target);
119
- const agentEntries = await readdir(srcAgents);
120
- summary.agents += agentEntries.filter((f) => f.endsWith('.md')).length;
121
- }
195
+ if (overwrites.length > 0 && typeof confirmOverwrite === 'function') {
196
+ const ok = await confirmOverwrite(overwrites.map((i) => i.label));
197
+ if (!ok) return { cancelled: true };
198
+ }
122
199
 
123
- // Config file
124
- const srcConfig = join(FRAMEWORKS_DIR, 'config', 'ai-framework-config.yaml');
125
- if (await exists(srcConfig)) {
126
- const configDestDir = join(ideDir, 'config');
127
- await mkdir(configDestDir, { recursive: true });
128
- const content = await readFile(srcConfig);
129
- await writeFile(join(configDestDir, 'ai-framework-config.yaml'), content);
130
- summary.config = 1;
131
- }
200
+ const summary = {
201
+ skills: 0,
202
+ commands: 0,
203
+ templates: 0,
204
+ agents: 0,
205
+ config: 0,
206
+ overwritten: overwrites.length,
207
+ };
208
+
209
+ for (const item of plan.items) {
210
+ if (item.kind === 'dir') {
211
+ await copyDir(item.src, item.dest, target);
132
212
  } else {
133
- // Framework-specific commands
134
- if (config.commands) {
135
- const srcCmdDir = join(FRAMEWORKS_DIR, 'commands', config.commands);
136
- const destCmdDir = join(commandsDir, config.commands);
137
- await copyDir(srcCmdDir, destCmdDir, target);
138
- const entries = await readdir(srcCmdDir);
139
- summary.commands += entries.filter((f) => f.endsWith('.md')).length;
140
- }
213
+ await copyFile(item.src, item.dest, target);
214
+ }
141
215
 
142
- // Framework-specific skills
143
- for (const skillName of config.skills) {
144
- const srcSkillDir = join(FRAMEWORKS_DIR, 'skills', skillName);
145
- const destSkillDir = join(skillsDir, skillName);
146
- if (await exists(srcSkillDir)) {
147
- await copyDir(srcSkillDir, destSkillDir, target);
148
- summary.skills++;
149
- }
216
+ if (item.category === 'skills') {
217
+ summary.skills++;
218
+ } else if (item.category === 'commands') {
219
+ if (item.kind === 'dir') {
220
+ summary.commands += await countMarkdownRecursive(item.src);
221
+ } else {
222
+ summary.commands++;
150
223
  }
224
+ } else if (item.category === 'agents') {
225
+ summary.agents++;
226
+ } else if (item.category === 'templates') {
227
+ summary.templates++;
228
+ } else if (item.category === 'config') {
229
+ summary.config++;
151
230
  }
152
231
  }
153
232