@luquimbo/bi-superpowers 4.1.6 → 5.0.0
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/.claude-plugin/marketplace.json +5 -5
- package/.claude-plugin/plugin.json +1 -1
- package/.claude-plugin/skill-manifest.json +17 -17
- package/.plugin/plugin.json +1 -1
- package/AGENTS.md +34 -8
- package/CHANGELOG.md +32 -0
- package/README.md +97 -23
- package/bin/cli.js +6 -0
- package/bin/commands/diff.js +2 -2
- package/bin/commands/install.js +3 -3
- package/bin/commands/lint.js +2 -2
- package/bin/commands/validate-projects.js +425 -0
- package/bin/lib/generators/claude-plugin.js +20 -5
- package/bin/lib/generators/shared.js +7 -7
- package/bin/lib/skills.js +5 -5
- package/bin/postinstall.js +3 -3
- package/commands/{pbi-connect.md → bi-connect.md} +151 -3
- package/commands/{project-kickoff.md → bi-kickoff.md} +9 -9
- package/commands/{report-design.md → bi-report.md} +12 -12
- package/commands/bi-start.md +20 -20
- package/desktop-extension/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/{pbi-connect → bi-connect}/SKILL.md +153 -5
- package/skills/{pbi-connect → bi-connect}/scripts/update-check.js +1 -1
- package/skills/{project-kickoff → bi-kickoff}/SKILL.md +11 -11
- package/skills/{report-design → bi-kickoff}/scripts/update-check.js +1 -1
- package/skills/{report-design → bi-report}/SKILL.md +14 -14
- package/skills/{report-design → bi-report}/references/layouts/hr.md +1 -1
- package/{src/content/skills/report-design → skills/bi-report}/references/native-visuals.md +1 -1
- package/{src/content/skills/report-design → skills/bi-report}/references/pbir-preview-activation.md +3 -3
- package/{src/content/skills/report-design → skills/bi-report}/references/troubleshooting.md +1 -1
- package/skills/{project-kickoff → bi-report}/scripts/update-check.js +1 -1
- package/skills/bi-start/SKILL.md +21 -21
- package/skills/bi-start/scripts/update-check.js +1 -1
- package/src/content/base.md +3 -3
- package/src/content/routing.md +20 -19
- package/src/content/skills/{pbi-connect.md → bi-connect.md} +149 -1
- package/src/content/skills/{project-kickoff.md → bi-kickoff.md} +8 -8
- package/src/content/skills/{report-design → bi-report}/SKILL.md +11 -11
- package/src/content/skills/{report-design → bi-report}/references/layouts/hr.md +1 -1
- package/{skills/report-design → src/content/skills/bi-report}/references/native-visuals.md +1 -1
- package/{skills/report-design → src/content/skills/bi-report}/references/pbir-preview-activation.md +3 -3
- package/{skills/report-design → src/content/skills/bi-report}/references/troubleshooting.md +1 -1
- package/src/content/skills/bi-start.md +20 -20
- /package/skills/{report-design → bi-report}/references/cli-commands.md +0 -0
- /package/skills/{report-design → bi-report}/references/cli-setup.md +0 -0
- /package/skills/{report-design → bi-report}/references/close-write-open-pattern.md +0 -0
- /package/skills/{report-design → bi-report}/references/layouts/finance.md +0 -0
- /package/skills/{report-design → bi-report}/references/layouts/generic.md +0 -0
- /package/skills/{report-design → bi-report}/references/layouts/marketing.md +0 -0
- /package/skills/{report-design → bi-report}/references/layouts/operations.md +0 -0
- /package/skills/{report-design → bi-report}/references/layouts/sales.md +0 -0
- /package/skills/{report-design → bi-report}/references/pbi-desktop-installation.md +0 -0
- /package/skills/{report-design → bi-report}/references/slicer.md +0 -0
- /package/skills/{report-design → bi-report}/references/textbox.md +0 -0
- /package/skills/{report-design → bi-report}/references/themes/BISuperpowers.json +0 -0
- /package/skills/{report-design → bi-report}/references/visual-types.md +0 -0
- /package/skills/{report-design → bi-report}/scripts/apply-theme.js +0 -0
- /package/skills/{report-design → bi-report}/scripts/create-visual.js +0 -0
- /package/skills/{report-design → bi-report}/scripts/ensure-pbi-cli.sh +0 -0
- /package/skills/{report-design → bi-report}/scripts/validate-pbir.js +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/cli-commands.md +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/cli-setup.md +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/close-write-open-pattern.md +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/layouts/finance.md +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/layouts/generic.md +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/layouts/marketing.md +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/layouts/operations.md +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/layouts/sales.md +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/pbi-desktop-installation.md +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/slicer.md +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/textbox.md +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/themes/BISuperpowers.json +0 -0
- /package/src/content/skills/{report-design → bi-report}/references/visual-types.md +0 -0
- /package/src/content/skills/{report-design → bi-report}/scripts/apply-theme.js +0 -0
- /package/src/content/skills/{report-design → bi-report}/scripts/create-visual.js +0 -0
- /package/src/content/skills/{report-design → bi-report}/scripts/ensure-pbi-cli.sh +0 -0
- /package/src/content/skills/{report-design → bi-report}/scripts/validate-pbir.js +0 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Validation Command
|
|
3
|
+
* ==========================
|
|
4
|
+
*
|
|
5
|
+
* Validates public fixtures and private project references without copying
|
|
6
|
+
* customer repositories into this repo. Versioned descriptors live under
|
|
7
|
+
* validation/projects/*.json; private paths live in validation.local.json,
|
|
8
|
+
* which is intentionally ignored by git.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* super validate-projects
|
|
12
|
+
* super validate-projects --project super-test
|
|
13
|
+
* super validate-projects --root C:\path\to\bi-superpowers
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
const DEFAULT_VALIDATION_DIR = 'validation';
|
|
20
|
+
const DEFAULT_LOCAL_CONFIG = 'validation.local.json';
|
|
21
|
+
|
|
22
|
+
function parseArgs(args) {
|
|
23
|
+
const options = {
|
|
24
|
+
rootDir: process.cwd(),
|
|
25
|
+
localConfigPath: null,
|
|
26
|
+
projectIds: [],
|
|
27
|
+
json: false,
|
|
28
|
+
list: false,
|
|
29
|
+
strict: false,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
for (let index = 0; index < args.length; index++) {
|
|
33
|
+
const arg = args[index];
|
|
34
|
+
|
|
35
|
+
if (arg === '--root') {
|
|
36
|
+
options.rootDir = readValue(args, ++index, arg);
|
|
37
|
+
} else if (arg === '--local') {
|
|
38
|
+
options.localConfigPath = readValue(args, ++index, arg);
|
|
39
|
+
} else if (arg === '--project' || arg === '-p') {
|
|
40
|
+
options.projectIds.push(readValue(args, ++index, arg));
|
|
41
|
+
} else if (arg === '--json') {
|
|
42
|
+
options.json = true;
|
|
43
|
+
} else if (arg === '--list') {
|
|
44
|
+
options.list = true;
|
|
45
|
+
} else if (arg === '--strict') {
|
|
46
|
+
options.strict = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
options.rootDir = path.resolve(options.rootDir);
|
|
51
|
+
if (options.localConfigPath) {
|
|
52
|
+
options.localConfigPath = path.resolve(options.localConfigPath);
|
|
53
|
+
}
|
|
54
|
+
return options;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readValue(args, index, flag) {
|
|
58
|
+
const value = args[index];
|
|
59
|
+
if (!value || value.startsWith('-')) {
|
|
60
|
+
throw new Error(`${flag} requires a value`);
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readJsonStrict(filePath) {
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
68
|
+
} catch (err) {
|
|
69
|
+
throw new Error(`Cannot read JSON at ${filePath}: ${err.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getValidationProjectsDir(rootDir) {
|
|
74
|
+
return path.join(rootDir, DEFAULT_VALIDATION_DIR, 'projects');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getLocalConfigPath(rootDir, overridePath) {
|
|
78
|
+
return overridePath || path.join(rootDir, DEFAULT_LOCAL_CONFIG);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function loadProjectDescriptors(rootDir) {
|
|
82
|
+
const projectsDir = getValidationProjectsDir(rootDir);
|
|
83
|
+
if (!fs.existsSync(projectsDir)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Validation project descriptors not found at ${projectsDir}. ` +
|
|
86
|
+
'Run this command from the bi-superpowers source checkout or pass --root <repo>.'
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const descriptorFiles = fs
|
|
91
|
+
.readdirSync(projectsDir)
|
|
92
|
+
.filter((file) => file.endsWith('.json'))
|
|
93
|
+
.sort();
|
|
94
|
+
|
|
95
|
+
if (descriptorFiles.length === 0) {
|
|
96
|
+
throw new Error(`No validation project descriptors found in ${projectsDir}.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return descriptorFiles.map((file) => {
|
|
100
|
+
const filePath = path.join(projectsDir, file);
|
|
101
|
+
const descriptor = readJsonStrict(filePath);
|
|
102
|
+
validateDescriptor(descriptor, filePath);
|
|
103
|
+
return { ...descriptor, descriptorPath: filePath };
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function validateDescriptor(descriptor, filePath) {
|
|
108
|
+
if (!descriptor || typeof descriptor !== 'object' || Array.isArray(descriptor)) {
|
|
109
|
+
throw new Error(`Project descriptor must be an object: ${filePath}`);
|
|
110
|
+
}
|
|
111
|
+
if (!isValidProjectId(descriptor.id)) {
|
|
112
|
+
throw new Error(`Project descriptor has invalid id at ${filePath}`);
|
|
113
|
+
}
|
|
114
|
+
if (!descriptor.name || typeof descriptor.name !== 'string') {
|
|
115
|
+
throw new Error(`Project descriptor ${descriptor.id} must include a name`);
|
|
116
|
+
}
|
|
117
|
+
if (descriptor.path != null && typeof descriptor.path !== 'string') {
|
|
118
|
+
throw new Error(`Project descriptor ${descriptor.id} path must be a string`);
|
|
119
|
+
}
|
|
120
|
+
if (descriptor.path != null && path.isAbsolute(descriptor.path)) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Project descriptor ${descriptor.id} path must be relative. Put private absolute paths in validation.local.json.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (descriptor.skills != null && !isStringArray(descriptor.skills)) {
|
|
126
|
+
throw new Error(`Project descriptor ${descriptor.id} skills must be an array of strings`);
|
|
127
|
+
}
|
|
128
|
+
if (descriptor.checks != null && !Array.isArray(descriptor.checks)) {
|
|
129
|
+
throw new Error(`Project descriptor ${descriptor.id} checks must be an array`);
|
|
130
|
+
}
|
|
131
|
+
for (const check of descriptor.checks || []) {
|
|
132
|
+
if (!check || typeof check !== 'object' || Array.isArray(check)) {
|
|
133
|
+
throw new Error(`Project descriptor ${descriptor.id} has an invalid check`);
|
|
134
|
+
}
|
|
135
|
+
if (!check.name || typeof check.name !== 'string') {
|
|
136
|
+
throw new Error(`Project descriptor ${descriptor.id} has a check without a name`);
|
|
137
|
+
}
|
|
138
|
+
if (!check.path || typeof check.path !== 'string') {
|
|
139
|
+
throw new Error(`Project descriptor ${descriptor.id} check ${check.name} needs a path`);
|
|
140
|
+
}
|
|
141
|
+
if (path.isAbsolute(check.path)) {
|
|
142
|
+
throw new Error(`Project descriptor ${descriptor.id} check ${check.name} must be relative`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isValidProjectId(value) {
|
|
148
|
+
return typeof value === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isStringArray(value) {
|
|
152
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function loadLocalConfig(rootDir, overridePath) {
|
|
156
|
+
const localPath = getLocalConfigPath(rootDir, overridePath);
|
|
157
|
+
if (!fs.existsSync(localPath)) {
|
|
158
|
+
return { path: localPath, projects: {} };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const config = readJsonStrict(localPath);
|
|
162
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
163
|
+
throw new Error(`Local validation config must be an object: ${localPath}`);
|
|
164
|
+
}
|
|
165
|
+
if (config.projects == null) {
|
|
166
|
+
return { path: localPath, projects: {} };
|
|
167
|
+
}
|
|
168
|
+
if (typeof config.projects !== 'object' || Array.isArray(config.projects)) {
|
|
169
|
+
throw new Error(`Local validation config projects must be an object: ${localPath}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (const [projectId, project] of Object.entries(config.projects)) {
|
|
173
|
+
if (!isValidProjectId(projectId)) {
|
|
174
|
+
throw new Error(`Local validation config has invalid project id: ${projectId}`);
|
|
175
|
+
}
|
|
176
|
+
if (!project || typeof project !== 'object' || Array.isArray(project)) {
|
|
177
|
+
throw new Error(`Local validation project ${projectId} must be an object`);
|
|
178
|
+
}
|
|
179
|
+
if (!project.path || typeof project.path !== 'string') {
|
|
180
|
+
throw new Error(`Local validation project ${projectId} must include a path`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { path: localPath, projects: config.projects };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function mergeProjects(descriptors, localProjects) {
|
|
188
|
+
const merged = new Map();
|
|
189
|
+
|
|
190
|
+
for (const descriptor of descriptors) {
|
|
191
|
+
const local = localProjects[descriptor.id] || {};
|
|
192
|
+
merged.set(descriptor.id, {
|
|
193
|
+
...descriptor,
|
|
194
|
+
path: local.path || descriptor.path,
|
|
195
|
+
localOnly: false,
|
|
196
|
+
localNotes: local.notes,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (const [id, local] of Object.entries(localProjects)) {
|
|
201
|
+
if (merged.has(id)) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
merged.set(id, {
|
|
205
|
+
id,
|
|
206
|
+
name: local.name || id,
|
|
207
|
+
privacy: 'private-local',
|
|
208
|
+
path: local.path,
|
|
209
|
+
skills: local.skills || [],
|
|
210
|
+
checks: local.checks || [],
|
|
211
|
+
localOnly: true,
|
|
212
|
+
localNotes: local.notes,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return Array.from(merged.values()).sort((a, b) => a.id.localeCompare(b.id));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function resolveProjectPath(rootDir, projectPath) {
|
|
220
|
+
if (!projectPath) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return path.isAbsolute(projectPath)
|
|
224
|
+
? path.normalize(projectPath)
|
|
225
|
+
: path.resolve(rootDir, projectPath);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function validateProject(project, rootDir) {
|
|
229
|
+
const resolvedPath = resolveProjectPath(rootDir, project.path);
|
|
230
|
+
const result = {
|
|
231
|
+
id: project.id,
|
|
232
|
+
name: project.name,
|
|
233
|
+
privacy: project.privacy || 'unspecified',
|
|
234
|
+
path: resolvedPath,
|
|
235
|
+
status: 'passed',
|
|
236
|
+
reason: null,
|
|
237
|
+
checks: [],
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
if (!resolvedPath) {
|
|
241
|
+
result.status = 'skipped';
|
|
242
|
+
result.reason = 'No path configured. Add this project to validation.local.json.';
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
247
|
+
result.status = 'failed';
|
|
248
|
+
result.reason = `Project path does not exist: ${resolvedPath}`;
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (const check of project.checks || []) {
|
|
253
|
+
const checkPath = path.resolve(resolvedPath, check.path);
|
|
254
|
+
if (!isPathInside(resolvedPath, checkPath)) {
|
|
255
|
+
result.checks.push({
|
|
256
|
+
name: check.name,
|
|
257
|
+
path: checkPath,
|
|
258
|
+
passed: false,
|
|
259
|
+
reason: 'Check path escapes the project root.',
|
|
260
|
+
});
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const passed = fs.existsSync(checkPath);
|
|
264
|
+
result.checks.push({
|
|
265
|
+
name: check.name,
|
|
266
|
+
path: checkPath,
|
|
267
|
+
passed,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (result.checks.some((check) => !check.passed)) {
|
|
272
|
+
result.status = 'failed';
|
|
273
|
+
result.reason = 'One or more required project files are missing.';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function isPathInside(parentPath, childPath) {
|
|
280
|
+
const relative = path.relative(parentPath, childPath);
|
|
281
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function runValidation(options) {
|
|
285
|
+
const descriptors = loadProjectDescriptors(options.rootDir);
|
|
286
|
+
const localConfig = loadLocalConfig(options.rootDir, options.localConfigPath);
|
|
287
|
+
const projects = mergeProjects(descriptors, localConfig.projects);
|
|
288
|
+
const selected = filterProjects(projects, options.projectIds);
|
|
289
|
+
const results = selected.projects.map((project) => validateProject(project, options.rootDir));
|
|
290
|
+
|
|
291
|
+
if (options.strict) {
|
|
292
|
+
for (const result of results) {
|
|
293
|
+
if (result.status === 'skipped') {
|
|
294
|
+
result.status = 'failed';
|
|
295
|
+
result.reason = `${result.reason} Strict mode treats skipped projects as failures.`;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
rootDir: options.rootDir,
|
|
302
|
+
localConfigPath: localConfig.path,
|
|
303
|
+
missingProjectIds: selected.missingProjectIds,
|
|
304
|
+
results,
|
|
305
|
+
summary: summarizeResults(results, selected.missingProjectIds),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function filterProjects(projects, ids) {
|
|
310
|
+
if (ids.length === 0) {
|
|
311
|
+
return { projects, missingProjectIds: [] };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const byId = new Map(projects.map((project) => [project.id, project]));
|
|
315
|
+
const selected = [];
|
|
316
|
+
const missingProjectIds = [];
|
|
317
|
+
|
|
318
|
+
for (const id of ids) {
|
|
319
|
+
if (byId.has(id)) {
|
|
320
|
+
selected.push(byId.get(id));
|
|
321
|
+
} else {
|
|
322
|
+
missingProjectIds.push(id);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return { projects: selected, missingProjectIds };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function summarizeResults(results, missingProjectIds = []) {
|
|
330
|
+
return {
|
|
331
|
+
total: results.length,
|
|
332
|
+
passed: results.filter((result) => result.status === 'passed').length,
|
|
333
|
+
failed:
|
|
334
|
+
results.filter((result) => result.status === 'failed').length + missingProjectIds.length,
|
|
335
|
+
skipped: results.filter((result) => result.status === 'skipped').length,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function printList(report) {
|
|
340
|
+
console.log('BI Agent Superpowers — Validation Projects');
|
|
341
|
+
console.log('==========================================\n');
|
|
342
|
+
|
|
343
|
+
if (report.results.length === 0) {
|
|
344
|
+
console.log('No project descriptors found.');
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
for (const result of report.results) {
|
|
349
|
+
console.log(`- ${result.id}: ${result.name} (${result.privacy})`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function printReport(report) {
|
|
354
|
+
console.log('BI Agent Superpowers — Project Validation');
|
|
355
|
+
console.log('=========================================\n');
|
|
356
|
+
console.log(`Root: ${report.rootDir}`);
|
|
357
|
+
console.log(`Local config: ${report.localConfigPath}`);
|
|
358
|
+
console.log('');
|
|
359
|
+
|
|
360
|
+
for (const missingId of report.missingProjectIds) {
|
|
361
|
+
console.log(`FAIL ${missingId}`);
|
|
362
|
+
console.log(' Project id was requested but no descriptor or local config exists.\n');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
for (const result of report.results) {
|
|
366
|
+
const label = result.status.toUpperCase();
|
|
367
|
+
console.log(`${label} ${result.id} — ${result.name}`);
|
|
368
|
+
if (result.path) {
|
|
369
|
+
console.log(` Path: ${result.path}`);
|
|
370
|
+
}
|
|
371
|
+
if (result.reason) {
|
|
372
|
+
console.log(` ${result.reason}`);
|
|
373
|
+
}
|
|
374
|
+
for (const check of result.checks) {
|
|
375
|
+
const checkLabel = check.passed ? 'OK' : 'MISS';
|
|
376
|
+
console.log(` ${checkLabel} ${check.name}: ${check.path}`);
|
|
377
|
+
}
|
|
378
|
+
console.log('');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const summary = report.summary;
|
|
382
|
+
console.log(
|
|
383
|
+
`Summary: ${summary.passed} passed, ${summary.failed} failed, ${summary.skipped} skipped (${summary.total} checked)`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function validateProjectsCommand(args) {
|
|
388
|
+
let options;
|
|
389
|
+
try {
|
|
390
|
+
options = parseArgs(args || []);
|
|
391
|
+
const report = runValidation(options);
|
|
392
|
+
|
|
393
|
+
if (options.json) {
|
|
394
|
+
console.log(JSON.stringify(report, null, 2));
|
|
395
|
+
} else if (options.list) {
|
|
396
|
+
printList(report);
|
|
397
|
+
} else {
|
|
398
|
+
printReport(report);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (report.summary.failed > 0) {
|
|
402
|
+
process.exitCode = 1;
|
|
403
|
+
}
|
|
404
|
+
} catch (err) {
|
|
405
|
+
if (options && options.json) {
|
|
406
|
+
console.log(JSON.stringify({ error: err.message }, null, 2));
|
|
407
|
+
} else {
|
|
408
|
+
console.error(`Error: ${err.message}`);
|
|
409
|
+
}
|
|
410
|
+
process.exitCode = 1;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
module.exports = Object.assign(validateProjectsCommand, {
|
|
415
|
+
parseArgs,
|
|
416
|
+
readJsonStrict,
|
|
417
|
+
loadProjectDescriptors,
|
|
418
|
+
loadLocalConfig,
|
|
419
|
+
mergeProjects,
|
|
420
|
+
resolveProjectPath,
|
|
421
|
+
validateProject,
|
|
422
|
+
isPathInside,
|
|
423
|
+
runValidation,
|
|
424
|
+
summarizeResults,
|
|
425
|
+
});
|
|
@@ -20,12 +20,12 @@ const {
|
|
|
20
20
|
} = require('../microsoft-mcp');
|
|
21
21
|
const { parseSkillMetadata, getSkillPurpose } = require('./shared');
|
|
22
22
|
|
|
23
|
-
// Currently the plugin ships 4 skills (bi-start,
|
|
24
|
-
//
|
|
23
|
+
// Currently the plugin ships 4 skills (bi-start, bi-kickoff,
|
|
24
|
+
// bi-connect, bi-report). bi-start is the session-opener that
|
|
25
25
|
// routes to the other three; the rest are specialists.
|
|
26
26
|
// The old reference-skills split is kept as an empty set so that any
|
|
27
27
|
// downstream code that checks `REFERENCE_SKILLS.has(x)` still works.
|
|
28
|
-
const COMMAND_SKILLS = new Set(['bi-start', '
|
|
28
|
+
const COMMAND_SKILLS = new Set(['bi-start', 'bi-kickoff', 'bi-connect', 'bi-report']);
|
|
29
29
|
|
|
30
30
|
const REFERENCE_SKILLS = new Set();
|
|
31
31
|
|
|
@@ -124,6 +124,19 @@ function toFrontmatterValue(value) {
|
|
|
124
124
|
return JSON.stringify(value);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Return the canonical source path that maintainers should edit.
|
|
129
|
+
*
|
|
130
|
+
* @param {Object} skill - Skill definition object
|
|
131
|
+
* @returns {string} Source path relative to the repository root
|
|
132
|
+
*/
|
|
133
|
+
function getSourceEditPath(skill) {
|
|
134
|
+
if (skill.bundleDir) {
|
|
135
|
+
return `src/content/skills/${skill.name}/SKILL.md`;
|
|
136
|
+
}
|
|
137
|
+
return `src/content/skills/${skill.name}.md`;
|
|
138
|
+
}
|
|
139
|
+
|
|
127
140
|
// ---------------------------------------------------------------------------
|
|
128
141
|
// Update-check preamble
|
|
129
142
|
// ---------------------------------------------------------------------------
|
|
@@ -185,12 +198,13 @@ function buildCommandMarkdown(skill, libraryPrefix, options = {}) {
|
|
|
185
198
|
const description = getSkillPurpose(skill.name);
|
|
186
199
|
const content = rewriteLibraryReferences(skill.content, libraryPrefix);
|
|
187
200
|
const preamble = shouldIncludeUpdateCheckPreamble(skill, options) ? UPDATE_CHECK_PREAMBLE : '';
|
|
201
|
+
const sourceEditPath = getSourceEditPath(skill);
|
|
188
202
|
|
|
189
203
|
return `---
|
|
190
204
|
description: ${toFrontmatterValue(description)}
|
|
191
205
|
---
|
|
192
206
|
|
|
193
|
-
<!-- Generated by BI Agent Superpowers. Edit
|
|
207
|
+
<!-- Generated by BI Agent Superpowers. Edit ${sourceEditPath} instead. -->
|
|
194
208
|
|
|
195
209
|
${preamble}${content}`;
|
|
196
210
|
}
|
|
@@ -208,6 +222,7 @@ ${preamble}${content}`;
|
|
|
208
222
|
function buildSkillMarkdown(skill, version, libraryPrefix, options = {}) {
|
|
209
223
|
const content = rewriteLibraryReferences(skill.content, libraryPrefix);
|
|
210
224
|
const preamble = shouldIncludeUpdateCheckPreamble(skill, options) ? UPDATE_CHECK_PREAMBLE : '';
|
|
225
|
+
const sourceEditPath = getSourceEditPath(skill);
|
|
211
226
|
|
|
212
227
|
return `---
|
|
213
228
|
name: ${toFrontmatterValue(skill.name)}
|
|
@@ -215,7 +230,7 @@ description: ${toFrontmatterValue(getPluginDescription(skill))}
|
|
|
215
230
|
version: ${toFrontmatterValue(version)}
|
|
216
231
|
---
|
|
217
232
|
|
|
218
|
-
<!-- Generated by BI Agent Superpowers. Edit
|
|
233
|
+
<!-- Generated by BI Agent Superpowers. Edit ${sourceEditPath} instead. -->
|
|
219
234
|
|
|
220
235
|
${preamble}${content}`;
|
|
221
236
|
}
|
|
@@ -35,8 +35,8 @@ function parseSkillMetadata(content) {
|
|
|
35
35
|
// - Prose bullets: - User mentions: "analizar proyecto", "new project"
|
|
36
36
|
// - Asterisk bullets: * "pattern"
|
|
37
37
|
// The regex intentionally doesn't anchor to bullet syntax — that's
|
|
38
|
-
// what broke extraction for
|
|
39
|
-
//
|
|
38
|
+
// what broke extraction for bi-kickoff (prose) and under-counted
|
|
39
|
+
// bi-connect (first quote per bullet only) before this change.
|
|
40
40
|
const triggerSection = content.match(/##\s+Trigger[\s\S]*?(?=##|$)/i);
|
|
41
41
|
if (triggerSection) {
|
|
42
42
|
const quotedFragments = triggerSection[0].match(/"([^"]+)"/g);
|
|
@@ -65,9 +65,9 @@ function parseSkillMetadata(content) {
|
|
|
65
65
|
*/
|
|
66
66
|
const SKILL_PURPOSES = {
|
|
67
67
|
'bi-start': 'Session opener — environment snapshot, update check, and routing to other skills',
|
|
68
|
-
'
|
|
69
|
-
'
|
|
70
|
-
'report
|
|
68
|
+
'bi-kickoff': 'Project analysis and planning',
|
|
69
|
+
'bi-connect': 'Power BI Desktop connection and DAX UDF authoring',
|
|
70
|
+
'bi-report': '3-page PBIR report generation for Power BI Desktop (Windows)',
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
/**
|
|
@@ -205,8 +205,8 @@ function getFormatFooter() {
|
|
|
205
205
|
## Resources
|
|
206
206
|
|
|
207
207
|
- Skills: \`skills/*/SKILL.md\` - BI workflow instructions
|
|
208
|
-
- Report design references: \`skills/report
|
|
209
|
-
- Runtime scripts: \`skills/report
|
|
208
|
+
- Report design references: \`skills/bi-report/references/\` - PBIR layouts, themes, native visuals, and troubleshooting
|
|
209
|
+
- Runtime scripts: \`skills/bi-report/scripts/\` - PBIR visual, theme, and validation helpers
|
|
210
210
|
|
|
211
211
|
---
|
|
212
212
|
|
package/bin/lib/skills.js
CHANGED
|
@@ -138,16 +138,16 @@ function loadSkills(options = {}) {
|
|
|
138
138
|
/**
|
|
139
139
|
* Normalize any user-facing skill identifier into its canonical name.
|
|
140
140
|
*
|
|
141
|
-
* Accepts `dax`, `dax.md`, `report
|
|
142
|
-
* or `report
|
|
141
|
+
* Accepts `dax`, `dax.md`, `bi-report`, `bi-report/SKILL.md`,
|
|
142
|
+
* or `bi-report\SKILL.md` (Windows separator) and returns the
|
|
143
143
|
* canonical skill name used as the key in readSkillDirectory's
|
|
144
|
-
* output map (`dax`, `report
|
|
144
|
+
* output map (`dax`, `bi-report`).
|
|
145
145
|
*
|
|
146
146
|
* Uses a single alternation regex so the longer suffix `/SKILL.md`
|
|
147
147
|
* is tried BEFORE the bare `.md`. Earlier callers chained two
|
|
148
148
|
* `.replace()` calls and stripped `.md` first, which meant the
|
|
149
|
-
* `/SKILL.md` alternative never matched — `report
|
|
150
|
-
* came out as `report
|
|
149
|
+
* `/SKILL.md` alternative never matched — `bi-report/SKILL.md`
|
|
150
|
+
* came out as `bi-report/SKILL` and the lookup failed.
|
|
151
151
|
*
|
|
152
152
|
* @param {string} input - User-supplied skill identifier
|
|
153
153
|
* @returns {string} Canonical skill name
|
package/bin/postinstall.js
CHANGED
|
@@ -31,9 +31,9 @@ Funciona con los 5 agentes:
|
|
|
31
31
|
|
|
32
32
|
4 skills iniciales:
|
|
33
33
|
/bi-start — Arrancar una sesión (menú + update + conexión)
|
|
34
|
-
/
|
|
35
|
-
/
|
|
36
|
-
/report
|
|
34
|
+
/bi-kickoff — Analizá tu proyecto BI (proyecto nuevo)
|
|
35
|
+
/bi-connect — Conectá tu agente a Power BI Desktop
|
|
36
|
+
/bi-report — Generá reportes PBIR para Power BI Desktop (Windows)
|
|
37
37
|
|
|
38
38
|
2 MCP servers:
|
|
39
39
|
powerbi-modeling-mcp — Conexión local a Power BI Desktop (XMLA)
|