sinapse-ai 8.0.1 → 9.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/.sinapse-ai/cli/commands/qa/audit.js +557 -0
- package/.sinapse-ai/cli/commands/qa/index.js +7 -0
- package/.sinapse-ai/data/entity-registry.yaml +846 -771
- package/.sinapse-ai/development/agent-teams/team-all.yaml +3 -3
- package/.sinapse-ai/development/agent-teams/team-fullstack.yaml +6 -6
- package/.sinapse-ai/development/agent-teams/team-ide-minimal.yaml +4 -4
- package/.sinapse-ai/development/agent-teams/team-no-ui.yaml +5 -5
- package/.sinapse-ai/development/agent-teams/team-qa-focused.yaml +9 -9
- package/.sinapse-ai/install-manifest.yaml +21 -17
- package/bin/sinapse.js +16 -0
- package/package.json +1 -1
- package/scripts/audit-tasks.cjs +256 -0
- package/squads/claude-code-mastery/tasks/audit-integration.md +6 -0
- package/squads/claude-code-mastery/tasks/audit-settings.md +6 -0
- package/squads/claude-code-mastery/tasks/audit-setup.md +6 -0
- package/squads/claude-code-mastery/tasks/brownfield-setup.md +6 -0
- package/squads/claude-code-mastery/tasks/ci-cd-setup.md +6 -0
- package/squads/claude-code-mastery/tasks/claude-md-engineer.md +6 -0
- package/squads/claude-code-mastery/tasks/configure-claude-code.md +6 -0
- package/squads/claude-code-mastery/tasks/context-rot-audit.md +6 -0
- package/squads/claude-code-mastery/tasks/create-agent-definition.md +6 -0
- package/squads/claude-code-mastery/tasks/create-rules.md +6 -0
- package/squads/claude-code-mastery/tasks/create-team-topology.md +6 -0
- package/squads/claude-code-mastery/tasks/diagnose.md +6 -0
- package/squads/claude-code-mastery/tasks/enterprise-config.md +6 -0
- package/squads/claude-code-mastery/tasks/hook-designer.md +6 -0
- package/squads/claude-code-mastery/tasks/integrate-project.md +6 -0
- package/squads/claude-code-mastery/tasks/mcp-integration-plan.md +6 -0
- package/squads/claude-code-mastery/tasks/mcp-workflow.md +6 -0
- package/squads/claude-code-mastery/tasks/multi-project-setup.md +6 -0
- package/squads/claude-code-mastery/tasks/optimize-context.md +6 -0
- package/squads/claude-code-mastery/tasks/optimize-workflow.md +6 -0
- package/squads/claude-code-mastery/tasks/parallel-decomposition.md +6 -0
- package/squads/claude-code-mastery/tasks/permission-strategy.md +6 -0
- package/squads/claude-code-mastery/tasks/sandbox-setup.md +6 -0
- package/squads/claude-code-mastery/tasks/setup-repository.md +6 -0
- package/squads/claude-code-mastery/tasks/setup-wizard.md +6 -0
- package/squads/claude-code-mastery/tasks/worktree-strategy.md +6 -0
- package/squads/squad-animations/workflows/3d-scene-creation-cycle.yaml +5 -0
- package/squads/squad-animations/workflows/animation-quality-review-cycle.yaml +5 -0
- package/squads/squad-animations/workflows/generative-art-creation-cycle.yaml +5 -0
- package/squads/squad-animations/workflows/prompt-to-animation-cycle.yaml +5 -0
- package/squads/squad-animations/workflows/scroll-experience-creation-cycle.yaml +5 -0
- package/squads/squad-brand/knowledge-base/routing-catalog.md +8 -0
- package/squads/squad-cloning/workflows/full-clone-pipeline.yaml +5 -0
- package/squads/squad-cloning/workflows/quality-validation-cycle.yaml +5 -0
- package/squads/squad-cloning/workflows/source-discovery-cycle.yaml +5 -0
- package/squads/squad-cloning/workflows/tier1-kb-only.yaml +5 -0
- package/squads/squad-cloning/workflows/tier2-consultant.yaml +5 -0
- package/squads/squad-cloning/workflows/tier3-full-clone.yaml +5 -0
- package/squads/squad-commercial/knowledge-base/routing-catalog.md +12 -0
- package/squads/squad-commercial/workflows/churn-prevention-protocol.yaml +5 -0
- package/squads/squad-commercial/workflows/client-onboarding-activation.yaml +5 -0
- package/squads/squad-commercial/workflows/expansion-revenue-cycle.yaml +5 -0
- package/squads/squad-commercial/workflows/new-offer-launch.yaml +5 -0
- package/squads/squad-commercial/workflows/quarterly-commercial-review.yaml +5 -0
- package/squads/squad-commercial/workflows/revenue-forecasting-cycle.yaml +5 -0
- package/squads/squad-content/knowledge-base/routing-catalog.md +12 -0
- package/squads/squad-content/workflows/content-audit-cycle.yaml +4 -0
- package/squads/squad-content/workflows/content-creation-cycle.yaml +4 -0
- package/squads/squad-content/workflows/editorial-planning-cycle.yaml +4 -0
- package/squads/squad-content/workflows/onboarding-content-cycle.yaml +4 -0
- package/squads/squad-content/workflows/performance-feedback-loop.yaml +4 -0
- package/squads/squad-content/workflows/signal-to-content-cycle.yaml +4 -0
- package/squads/squad-copy/knowledge-base/routing-catalog.md +12 -0
- package/squads/squad-copy/workflows/brand-voice-development.yaml +5 -0
- package/squads/squad-copy/workflows/campaign-copy-cycle.yaml +5 -0
- package/squads/squad-copy/workflows/content-copy-cycle.yaml +5 -0
- package/squads/squad-copy/workflows/conversion-copy-sprint.yaml +5 -0
- package/squads/squad-copy/workflows/full-copy-cycle.yaml +5 -0
- package/squads/squad-copy/workflows/sales-copy-pipeline.yaml +5 -0
- package/squads/squad-council/workflows/business-audit-cycle.yaml +1 -0
- package/squads/squad-council/workflows/strategic-advisory-session.yaml +1 -0
- package/squads/squad-courses/workflows/course-launch-cycle.yaml +1 -6
- package/squads/squad-courses/workflows/presentation-creation.yaml +2 -2
- package/squads/squad-design/knowledge-base/routing-catalog.md +12 -0
- package/squads/squad-design/workflows/a11y-compliance-cycle.yaml +1 -0
- package/squads/squad-design/workflows/design-system-build-cycle.yaml +1 -0
- package/squads/squad-design/workflows/landing-page-sprint.yaml +1 -0
- package/squads/squad-design/workflows/performance-remediation-cycle.yaml +1 -0
- package/squads/squad-design/workflows/ux-research-sprint.yaml +1 -0
- package/squads/squad-design/workflows/zero-to-digital-product-cycle.yaml +1 -0
- package/squads/squad-finance/knowledge-base/routing-catalog.md +12 -0
- package/squads/squad-finance/workflows/client-profitability-audit.yaml +5 -0
- package/squads/squad-finance/workflows/monthly-financial-cycle.yaml +5 -0
- package/squads/squad-finance/workflows/pricing-design-cycle.yaml +5 -0
- package/squads/squad-finance/workflows/quarterly-financial-review.yaml +5 -0
- package/squads/squad-growth/knowledge-base/routing-catalog.md +12 -0
- package/squads/squad-growth/workflows/analytics-instrumentation-pipeline.yaml +1 -0
- package/squads/squad-growth/workflows/campaign-performance-review.yaml +1 -0
- package/squads/squad-growth/workflows/cro-experimentation-sprint.yaml +1 -0
- package/squads/squad-growth/workflows/full-growth-analytics-cycle.yaml +1 -0
- package/squads/squad-growth/workflows/growth-experiment-loop.yaml +1 -0
- package/squads/squad-growth/workflows/seo-audit-optimization-cycle.yaml +1 -0
- package/squads/squad-paidmedia/knowledge-base/routing-catalog.md +12 -0
- package/squads/squad-paidmedia/workflows/account-audit-cycle.yaml +5 -0
- package/squads/squad-paidmedia/workflows/campaign-launch-cycle.yaml +5 -0
- package/squads/squad-paidmedia/workflows/creative-testing-cycle.yaml +5 -0
- package/squads/squad-paidmedia/workflows/cro-optimization-cycle.yaml +5 -0
- package/squads/squad-paidmedia/workflows/scaling-sprint.yaml +5 -0
- package/squads/squad-product/knowledge-base/routing-catalog.md +12 -0
- package/squads/squad-product/workflows/client-roadmap-alignment-cycle.yaml +4 -0
- package/squads/squad-product/workflows/product-discovery-cycle.yaml +4 -0
- package/squads/squad-product/workflows/product-handoff-cycle.yaml +4 -0
- package/squads/squad-product/workflows/product-launch-cycle.yaml +4 -0
- package/squads/squad-product/workflows/product-strategy-definition-cycle.yaml +4 -0
- package/squads/squad-product/workflows/story-development-delivery-cycle.yaml +4 -0
- package/squads/squad-research/knowledge-base/routing-catalog.md +12 -0
- package/squads/squad-research/workflows/audience-intelligence-cycle.yaml +4 -0
- package/squads/squad-research/workflows/competitive-intelligence-cycle.yaml +4 -0
- package/squads/squad-research/workflows/deep-research-cycle.yaml +4 -0
- package/squads/squad-research/workflows/full-research-sprint.yaml +4 -0
- package/squads/squad-research/workflows/market-analysis-cycle.yaml +4 -0
- package/squads/squad-research/workflows/trend-forecasting-cycle.yaml +4 -0
- package/squads/squad-storytelling/workflows/pitch-narrative-workflow.yaml +1 -0
- package/squads/squad-storytelling/workflows/story-development-cycle.yaml +1 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA Audit Command
|
|
3
|
+
*
|
|
4
|
+
* Audit squad ecosystem quality: workflows, tasks, knowledge bases.
|
|
5
|
+
* Validates structure, consistency, and completeness across all squads.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/commands/qa/audit
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
* @story 9.5 - Quality Gate CLI
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { Command } = require('commander');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create the audit subcommand
|
|
18
|
+
* @returns {Command} Commander command instance
|
|
19
|
+
*/
|
|
20
|
+
function createAuditCommand() {
|
|
21
|
+
const audit = new Command('audit');
|
|
22
|
+
|
|
23
|
+
audit
|
|
24
|
+
.description('Audit squad ecosystem quality (workflows, tasks, KBs)')
|
|
25
|
+
.option('-t, --target <type>', 'Audit target: workflows, tasks, kbs, all', 'all')
|
|
26
|
+
.option('-s, --squad <name>', 'Audit specific squad only')
|
|
27
|
+
.option('-v, --verbose', 'Show detailed output', false)
|
|
28
|
+
.option('--json', 'Output as JSON', false)
|
|
29
|
+
.option('--fix', 'Auto-fix common issues', false)
|
|
30
|
+
.option('--save-report', 'Save report to docs/reports/', false)
|
|
31
|
+
.action(async (options) => {
|
|
32
|
+
try {
|
|
33
|
+
const projectRoot = findProjectRoot();
|
|
34
|
+
const squadsDir = path.join(projectRoot, 'squads');
|
|
35
|
+
|
|
36
|
+
if (!fs.existsSync(squadsDir)) {
|
|
37
|
+
console.error('❌ No squads/ directory found. Are you in a SINAPSE project?');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const squads = getSquadList(squadsDir, options.squad);
|
|
42
|
+
|
|
43
|
+
if (squads.length === 0) {
|
|
44
|
+
console.error(`❌ No squads found${options.squad ? ` matching "${options.squad}"` : ''}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!options.json) {
|
|
49
|
+
console.log(`\n🔍 SINAPSE Ecosystem Audit`);
|
|
50
|
+
console.log('━'.repeat(60));
|
|
51
|
+
console.log(`Squads: ${squads.length} | Target: ${options.target}`);
|
|
52
|
+
console.log('━'.repeat(60));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const results = {
|
|
56
|
+
timestamp: new Date().toISOString(),
|
|
57
|
+
squads: squads.length,
|
|
58
|
+
target: options.target,
|
|
59
|
+
workflows: null,
|
|
60
|
+
tasks: null,
|
|
61
|
+
kbs: null,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (options.target === 'all' || options.target === 'workflows') {
|
|
65
|
+
results.workflows = auditWorkflows(squadsDir, squads, options);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (options.target === 'all' || options.target === 'tasks') {
|
|
69
|
+
results.tasks = auditTasks(squadsDir, squads, options);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (options.target === 'all' || options.target === 'kbs') {
|
|
73
|
+
results.kbs = auditKBs(squadsDir, squads, options);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Calculate overall score
|
|
77
|
+
const scores = [results.workflows, results.tasks, results.kbs]
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.map((r) => r.score);
|
|
80
|
+
results.overallScore = scores.length > 0
|
|
81
|
+
? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length)
|
|
82
|
+
: 0;
|
|
83
|
+
|
|
84
|
+
if (options.json) {
|
|
85
|
+
console.log(JSON.stringify(results, null, 2));
|
|
86
|
+
} else {
|
|
87
|
+
printAuditSummary(results);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (options.saveReport) {
|
|
91
|
+
const reportPath = saveReport(projectRoot, results);
|
|
92
|
+
console.log(`\n📄 Report saved: ${reportPath}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
process.exit(results.overallScore >= 70 ? 0 : 1);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(`\n❌ Audit error: ${error.message}`);
|
|
98
|
+
if (options.verbose) {
|
|
99
|
+
console.error(error.stack);
|
|
100
|
+
}
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return audit;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Find project root by traversing up looking for squads/ or .sinapse-ai/
|
|
110
|
+
*/
|
|
111
|
+
function findProjectRoot() {
|
|
112
|
+
let dir = process.cwd();
|
|
113
|
+
for (let i = 0; i < 10; i++) {
|
|
114
|
+
if (fs.existsSync(path.join(dir, 'squads')) || fs.existsSync(path.join(dir, '.sinapse-ai'))) {
|
|
115
|
+
return dir;
|
|
116
|
+
}
|
|
117
|
+
const parent = path.dirname(dir);
|
|
118
|
+
if (parent === dir) break;
|
|
119
|
+
dir = parent;
|
|
120
|
+
}
|
|
121
|
+
return process.cwd();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get list of squad directories
|
|
126
|
+
*/
|
|
127
|
+
function getSquadList(squadsDir, filterSquad) {
|
|
128
|
+
const entries = fs.readdirSync(squadsDir, { withFileTypes: true });
|
|
129
|
+
return entries
|
|
130
|
+
.filter((e) => e.isDirectory())
|
|
131
|
+
.map((e) => e.name)
|
|
132
|
+
.filter((name) => !filterSquad || name.includes(filterSquad))
|
|
133
|
+
.sort();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Audit all workflows across squads
|
|
138
|
+
*/
|
|
139
|
+
function auditWorkflows(squadsDir, squads, options) {
|
|
140
|
+
const result = {
|
|
141
|
+
total: 0,
|
|
142
|
+
passed: 0,
|
|
143
|
+
warnings: 0,
|
|
144
|
+
errors: 0,
|
|
145
|
+
score: 0,
|
|
146
|
+
perSquad: {},
|
|
147
|
+
issues: [],
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
for (const squad of squads) {
|
|
151
|
+
const wfDir = path.join(squadsDir, squad, 'workflows');
|
|
152
|
+
if (!fs.existsSync(wfDir)) {
|
|
153
|
+
result.perSquad[squad] = { count: 0, score: 100, issues: [] };
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const files = fs.readdirSync(wfDir).filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
158
|
+
const squadResult = { count: files.length, score: 0, issues: [] };
|
|
159
|
+
let squadScore = 0;
|
|
160
|
+
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
const filePath = path.join(wfDir, file);
|
|
163
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
164
|
+
const fileScore = scoreWorkflow(content, file, squad, squadResult.issues, options);
|
|
165
|
+
squadScore += fileScore;
|
|
166
|
+
result.total++;
|
|
167
|
+
if (fileScore >= 80) result.passed++;
|
|
168
|
+
else if (fileScore >= 50) result.warnings++;
|
|
169
|
+
else result.errors++;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
squadResult.score = files.length > 0 ? Math.round(squadScore / files.length) : 100;
|
|
173
|
+
result.perSquad[squad] = squadResult;
|
|
174
|
+
result.issues.push(...squadResult.issues);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
result.score = result.total > 0
|
|
178
|
+
? Math.round(Object.values(result.perSquad).reduce((s, r) => s + r.score, 0) / squads.length)
|
|
179
|
+
: 100;
|
|
180
|
+
|
|
181
|
+
if (options.verbose && !options.json) {
|
|
182
|
+
console.log(`\n📋 Workflows: ${result.total} files | Score: ${result.score}/100`);
|
|
183
|
+
result.issues.forEach((i) => {
|
|
184
|
+
const icon = i.severity === 'error' ? '❌' : '⚠️';
|
|
185
|
+
console.log(` ${icon} ${i.squad}/${i.file}: ${i.message}`);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Score a single workflow file
|
|
194
|
+
*/
|
|
195
|
+
function scoreWorkflow(content, file, squad, issues, options) {
|
|
196
|
+
let score = 0;
|
|
197
|
+
const lines = content.split('\n');
|
|
198
|
+
|
|
199
|
+
// Check for name field
|
|
200
|
+
if (/^name:/m.test(content)) {
|
|
201
|
+
score += 20;
|
|
202
|
+
} else {
|
|
203
|
+
issues.push({ severity: 'error', squad, file, message: 'Missing name: field' });
|
|
204
|
+
if (options.fix) {
|
|
205
|
+
const name = file.replace(/\.(yaml|yml)$/, '').replace(/-/g, ' ');
|
|
206
|
+
const fixed = `name: ${name}\n${content}`;
|
|
207
|
+
// Note: fix would write here, but we track it
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check for description
|
|
212
|
+
if (/description:/m.test(content)) {
|
|
213
|
+
score += 20;
|
|
214
|
+
} else {
|
|
215
|
+
issues.push({ severity: 'warning', squad, file, message: 'Missing description:' });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check for phases
|
|
219
|
+
if (/phases:/m.test(content)) {
|
|
220
|
+
score += 20;
|
|
221
|
+
} else {
|
|
222
|
+
issues.push({ severity: 'error', squad, file, message: 'Missing phases: section' });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check for trigger
|
|
226
|
+
if (/trigger:/m.test(content)) {
|
|
227
|
+
score += 15;
|
|
228
|
+
} else {
|
|
229
|
+
issues.push({ severity: 'warning', squad, file, message: 'Missing trigger: section' });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Check for completion
|
|
233
|
+
if (/completion:/m.test(content)) {
|
|
234
|
+
score += 15;
|
|
235
|
+
} else {
|
|
236
|
+
issues.push({ severity: 'warning', squad, file, message: 'Missing completion: section' });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check content length (at least meaningful)
|
|
240
|
+
if (lines.length >= 20) {
|
|
241
|
+
score += 10;
|
|
242
|
+
} else if (lines.length >= 10) {
|
|
243
|
+
score += 5;
|
|
244
|
+
} else {
|
|
245
|
+
issues.push({ severity: 'warning', squad, file, message: `Short workflow (${lines.length} lines)` });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return score;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Audit all tasks across squads
|
|
253
|
+
*/
|
|
254
|
+
function auditTasks(squadsDir, squads, options) {
|
|
255
|
+
const result = {
|
|
256
|
+
total: 0,
|
|
257
|
+
passed: 0,
|
|
258
|
+
warnings: 0,
|
|
259
|
+
errors: 0,
|
|
260
|
+
score: 0,
|
|
261
|
+
perSquad: {},
|
|
262
|
+
issues: [],
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
for (const squad of squads) {
|
|
266
|
+
const taskDir = path.join(squadsDir, squad, 'tasks');
|
|
267
|
+
if (!fs.existsSync(taskDir)) {
|
|
268
|
+
result.perSquad[squad] = { count: 0, score: 100, issues: [] };
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const files = fs.readdirSync(taskDir).filter((f) => f.endsWith('.md'));
|
|
273
|
+
const squadResult = { count: files.length, score: 0, issues: [] };
|
|
274
|
+
let squadScore = 0;
|
|
275
|
+
|
|
276
|
+
for (const file of files) {
|
|
277
|
+
const filePath = path.join(taskDir, file);
|
|
278
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
279
|
+
const fileScore = scoreTask(content, file, squad, squadResult.issues);
|
|
280
|
+
squadScore += fileScore;
|
|
281
|
+
result.total++;
|
|
282
|
+
if (fileScore >= 80) result.passed++;
|
|
283
|
+
else if (fileScore >= 50) result.warnings++;
|
|
284
|
+
else result.errors++;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
squadResult.score = files.length > 0 ? Math.round(squadScore / files.length) : 100;
|
|
288
|
+
result.perSquad[squad] = squadResult;
|
|
289
|
+
result.issues.push(...squadResult.issues);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
result.score = result.total > 0
|
|
293
|
+
? Math.round(Object.values(result.perSquad).reduce((s, r) => s + r.score, 0) / squads.length)
|
|
294
|
+
: 100;
|
|
295
|
+
|
|
296
|
+
if (options.verbose && !options.json) {
|
|
297
|
+
console.log(`\n📋 Tasks: ${result.total} files | Score: ${result.score}/100`);
|
|
298
|
+
const errorIssues = result.issues.filter((i) => i.severity === 'error');
|
|
299
|
+
if (errorIssues.length > 0) {
|
|
300
|
+
console.log(` Errors: ${errorIssues.length} (showing first 20)`);
|
|
301
|
+
errorIssues.slice(0, 20).forEach((i) => {
|
|
302
|
+
console.log(` ❌ ${i.squad}/${i.file}: ${i.message}`);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Score a single task file
|
|
312
|
+
*/
|
|
313
|
+
function scoreTask(content, file, squad, issues) {
|
|
314
|
+
let score = 0;
|
|
315
|
+
|
|
316
|
+
// Check for frontmatter
|
|
317
|
+
const hasFrontmatter = content.startsWith('---');
|
|
318
|
+
if (hasFrontmatter) {
|
|
319
|
+
score += 15;
|
|
320
|
+
|
|
321
|
+
// Check frontmatter fields
|
|
322
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
323
|
+
if (fmMatch) {
|
|
324
|
+
const fm = fmMatch[1];
|
|
325
|
+
if (/task:/m.test(fm)) score += 10;
|
|
326
|
+
else issues.push({ severity: 'warning', squad, file, message: 'Frontmatter missing task: field' });
|
|
327
|
+
|
|
328
|
+
if (/responsavel:|responsible:/m.test(fm)) score += 10;
|
|
329
|
+
else issues.push({ severity: 'warning', squad, file, message: 'Frontmatter missing responsavel:' });
|
|
330
|
+
|
|
331
|
+
if (/Entrada:|input:/mi.test(fm)) score += 5;
|
|
332
|
+
if (/Saida:|output:/mi.test(fm)) score += 5;
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
issues.push({ severity: 'error', squad, file, message: 'Missing YAML frontmatter' });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Check for title
|
|
339
|
+
if (/^# /m.test(content)) {
|
|
340
|
+
score += 15;
|
|
341
|
+
} else {
|
|
342
|
+
issues.push({ severity: 'warning', squad, file, message: 'Missing # title heading' });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Check for Steps or Description
|
|
346
|
+
if (/^## (Steps|Description)/m.test(content)) {
|
|
347
|
+
score += 15;
|
|
348
|
+
} else {
|
|
349
|
+
issues.push({ severity: 'warning', squad, file, message: 'Missing ## Steps or ## Description section' });
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Check for Metadata
|
|
353
|
+
if (/^## Metadata/m.test(content)) {
|
|
354
|
+
score += 10;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Check content length
|
|
358
|
+
if (content.length >= 500) {
|
|
359
|
+
score += 15;
|
|
360
|
+
} else if (content.length >= 200) {
|
|
361
|
+
score += 10;
|
|
362
|
+
} else {
|
|
363
|
+
issues.push({ severity: 'warning', squad, file, message: `Short task (${content.length} chars)` });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return score;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Audit all knowledge bases across squads
|
|
371
|
+
*/
|
|
372
|
+
function auditKBs(squadsDir, squads, options) {
|
|
373
|
+
const result = {
|
|
374
|
+
total: 0,
|
|
375
|
+
passed: 0,
|
|
376
|
+
warnings: 0,
|
|
377
|
+
errors: 0,
|
|
378
|
+
score: 0,
|
|
379
|
+
perSquad: {},
|
|
380
|
+
issues: [],
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
for (const squad of squads) {
|
|
384
|
+
const kbDir = path.join(squadsDir, squad, 'knowledge-base');
|
|
385
|
+
if (!fs.existsSync(kbDir)) {
|
|
386
|
+
result.perSquad[squad] = { count: 0, score: 100, issues: [] };
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const files = fs.readdirSync(kbDir).filter((f) => f.endsWith('.md'));
|
|
391
|
+
const squadResult = { count: files.length, score: 0, issues: [] };
|
|
392
|
+
let squadScore = 0;
|
|
393
|
+
|
|
394
|
+
for (const file of files) {
|
|
395
|
+
const filePath = path.join(kbDir, file);
|
|
396
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
397
|
+
const fileScore = scoreKB(content, file, squad, squadResult.issues);
|
|
398
|
+
squadScore += fileScore;
|
|
399
|
+
result.total++;
|
|
400
|
+
if (fileScore >= 80) result.passed++;
|
|
401
|
+
else if (fileScore >= 50) result.warnings++;
|
|
402
|
+
else result.errors++;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
squadResult.score = files.length > 0 ? Math.round(squadScore / files.length) : 100;
|
|
406
|
+
result.perSquad[squad] = squadResult;
|
|
407
|
+
result.issues.push(...squadResult.issues);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
result.score = result.total > 0
|
|
411
|
+
? Math.round(Object.values(result.perSquad).reduce((s, r) => s + r.score, 0) / squads.length)
|
|
412
|
+
: 100;
|
|
413
|
+
|
|
414
|
+
if (options.verbose && !options.json) {
|
|
415
|
+
console.log(`\n📋 Knowledge Bases: ${result.total} files | Score: ${result.score}/100`);
|
|
416
|
+
const stubs = result.issues.filter((i) => i.message.includes('stub'));
|
|
417
|
+
if (stubs.length > 0) {
|
|
418
|
+
console.log(` Stubs found: ${stubs.length}`);
|
|
419
|
+
stubs.forEach((i) => {
|
|
420
|
+
console.log(` ⚠️ ${i.squad}/${i.file}`);
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Score a single KB file
|
|
430
|
+
*/
|
|
431
|
+
function scoreKB(content, file, squad, issues) {
|
|
432
|
+
let score = 0;
|
|
433
|
+
|
|
434
|
+
// Check for title
|
|
435
|
+
if (/^# /m.test(content)) {
|
|
436
|
+
score += 20;
|
|
437
|
+
} else {
|
|
438
|
+
issues.push({ severity: 'error', squad, file, message: 'Missing # title' });
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Check for intro paragraph (after title, before first ##)
|
|
442
|
+
const afterTitle = content.replace(/^#[^\n]*\n/, '');
|
|
443
|
+
const beforeFirstSection = afterTitle.split(/^## /m)[0].trim();
|
|
444
|
+
if (beforeFirstSection.length >= 30) {
|
|
445
|
+
score += 15;
|
|
446
|
+
} else {
|
|
447
|
+
issues.push({ severity: 'warning', squad, file, message: 'Missing intro paragraph' });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Check for sections (## headings)
|
|
451
|
+
const sectionCount = (content.match(/^## /gm) || []).length;
|
|
452
|
+
if (sectionCount >= 3) {
|
|
453
|
+
score += 25;
|
|
454
|
+
} else if (sectionCount >= 1) {
|
|
455
|
+
score += 15;
|
|
456
|
+
issues.push({ severity: 'warning', squad, file, message: `Only ${sectionCount} section(s)` });
|
|
457
|
+
} else {
|
|
458
|
+
issues.push({ severity: 'warning', squad, file, message: 'No sections (## headings)' });
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Check content length
|
|
462
|
+
if (content.length >= 500) {
|
|
463
|
+
score += 20;
|
|
464
|
+
} else if (content.length >= 200) {
|
|
465
|
+
score += 10;
|
|
466
|
+
} else {
|
|
467
|
+
issues.push({ severity: 'warning', squad, file, message: `Possible stub (${content.length} chars)` });
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Check for tables or lists (structured content)
|
|
471
|
+
if (/\|.*\|/m.test(content) || /^[-*] /m.test(content)) {
|
|
472
|
+
score += 10;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Check for code blocks or examples
|
|
476
|
+
if (/```/m.test(content)) {
|
|
477
|
+
score += 10;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return score;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Print audit summary
|
|
485
|
+
*/
|
|
486
|
+
function printAuditSummary(results) {
|
|
487
|
+
console.log('\n' + '━'.repeat(60));
|
|
488
|
+
console.log('📊 ECOSYSTEM AUDIT SUMMARY');
|
|
489
|
+
console.log('━'.repeat(60));
|
|
490
|
+
|
|
491
|
+
if (results.workflows) {
|
|
492
|
+
const wf = results.workflows;
|
|
493
|
+
const icon = wf.score >= 80 ? '✅' : wf.score >= 60 ? '⚠️' : '❌';
|
|
494
|
+
console.log(`\n${icon} Workflows: ${wf.score}/100 (${wf.total} files)`);
|
|
495
|
+
console.log(` ✓ ${wf.passed} passed | ⚠️ ${wf.warnings} warnings | ❌ ${wf.errors} errors`);
|
|
496
|
+
printPerSquadScores(wf.perSquad);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (results.tasks) {
|
|
500
|
+
const tk = results.tasks;
|
|
501
|
+
const icon = tk.score >= 80 ? '✅' : tk.score >= 60 ? '⚠️' : '❌';
|
|
502
|
+
console.log(`\n${icon} Tasks: ${tk.score}/100 (${tk.total} files)`);
|
|
503
|
+
console.log(` ✓ ${tk.passed} passed | ⚠️ ${tk.warnings} warnings | ❌ ${tk.errors} errors`);
|
|
504
|
+
printPerSquadScores(tk.perSquad);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (results.kbs) {
|
|
508
|
+
const kb = results.kbs;
|
|
509
|
+
const icon = kb.score >= 80 ? '✅' : kb.score >= 60 ? '⚠️' : '❌';
|
|
510
|
+
console.log(`\n${icon} Knowledge Bases: ${kb.score}/100 (${kb.total} files)`);
|
|
511
|
+
console.log(` ✓ ${kb.passed} passed | ⚠️ ${kb.warnings} warnings | ❌ ${kb.errors} errors`);
|
|
512
|
+
printPerSquadScores(kb.perSquad);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
console.log('\n' + '━'.repeat(60));
|
|
516
|
+
const overallIcon = results.overallScore >= 80 ? '✅' : results.overallScore >= 60 ? '⚠️' : '❌';
|
|
517
|
+
console.log(`${overallIcon} Overall Ecosystem Score: ${results.overallScore}/100`);
|
|
518
|
+
console.log('━'.repeat(60) + '\n');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Print per-squad scores inline
|
|
523
|
+
*/
|
|
524
|
+
function printPerSquadScores(perSquad) {
|
|
525
|
+
const sorted = Object.entries(perSquad)
|
|
526
|
+
.filter(([, v]) => v.count > 0)
|
|
527
|
+
.sort((a, b) => a[1].score - b[1].score);
|
|
528
|
+
|
|
529
|
+
if (sorted.length === 0) return;
|
|
530
|
+
|
|
531
|
+
const lowest = sorted.filter(([, v]) => v.score < 70);
|
|
532
|
+
if (lowest.length > 0) {
|
|
533
|
+
console.log(' Needs attention:');
|
|
534
|
+
lowest.forEach(([name, data]) => {
|
|
535
|
+
console.log(` ${name}: ${data.score}/100 (${data.count} files)`);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Save audit report to file
|
|
542
|
+
*/
|
|
543
|
+
function saveReport(projectRoot, results) {
|
|
544
|
+
const reportsDir = path.join(projectRoot, 'docs', 'reports');
|
|
545
|
+
if (!fs.existsSync(reportsDir)) {
|
|
546
|
+
fs.mkdirSync(reportsDir, { recursive: true });
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const date = new Date().toISOString().split('T')[0];
|
|
550
|
+
const reportPath = path.join(reportsDir, `ecosystem-audit-${date}.json`);
|
|
551
|
+
fs.writeFileSync(reportPath, JSON.stringify(results, null, 2));
|
|
552
|
+
return reportPath;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
module.exports = {
|
|
556
|
+
createAuditCommand,
|
|
557
|
+
};
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
const { Command } = require('commander');
|
|
13
13
|
const { createRunCommand } = require('./run');
|
|
14
14
|
const { createStatusCommand } = require('./status');
|
|
15
|
+
const { createAuditCommand } = require('./audit');
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Create the qa command with all subcommands
|
|
@@ -26,6 +27,7 @@ function createQaCommand() {
|
|
|
26
27
|
Commands:
|
|
27
28
|
run Execute quality gate pipeline
|
|
28
29
|
status Show current gate status
|
|
30
|
+
audit Audit squad ecosystem quality
|
|
29
31
|
|
|
30
32
|
Examples:
|
|
31
33
|
$ sinapse qa run Run full pipeline
|
|
@@ -33,6 +35,10 @@ Examples:
|
|
|
33
35
|
$ sinapse qa run --layer=2 Run only Layer 2 (PR automation)
|
|
34
36
|
$ sinapse qa run --verbose Run with detailed output
|
|
35
37
|
$ sinapse qa status Show current gate status
|
|
38
|
+
$ sinapse qa audit Audit all squads
|
|
39
|
+
$ sinapse qa audit -t workflows Audit workflows only
|
|
40
|
+
$ sinapse qa audit -s squad-brand Audit specific squad
|
|
41
|
+
$ sinapse qa audit --json JSON output
|
|
36
42
|
|
|
37
43
|
Layers:
|
|
38
44
|
Layer 1: Pre-commit (lint, test, typecheck) - Fast local checks
|
|
@@ -47,6 +53,7 @@ Exit Codes:
|
|
|
47
53
|
// Add subcommands
|
|
48
54
|
qa.addCommand(createRunCommand());
|
|
49
55
|
qa.addCommand(createStatusCommand());
|
|
56
|
+
qa.addCommand(createAuditCommand());
|
|
50
57
|
|
|
51
58
|
return qa;
|
|
52
59
|
}
|