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.
Files changed (116) hide show
  1. package/.sinapse-ai/cli/commands/qa/audit.js +557 -0
  2. package/.sinapse-ai/cli/commands/qa/index.js +7 -0
  3. package/.sinapse-ai/data/entity-registry.yaml +846 -771
  4. package/.sinapse-ai/development/agent-teams/team-all.yaml +3 -3
  5. package/.sinapse-ai/development/agent-teams/team-fullstack.yaml +6 -6
  6. package/.sinapse-ai/development/agent-teams/team-ide-minimal.yaml +4 -4
  7. package/.sinapse-ai/development/agent-teams/team-no-ui.yaml +5 -5
  8. package/.sinapse-ai/development/agent-teams/team-qa-focused.yaml +9 -9
  9. package/.sinapse-ai/install-manifest.yaml +21 -17
  10. package/bin/sinapse.js +16 -0
  11. package/package.json +1 -1
  12. package/scripts/audit-tasks.cjs +256 -0
  13. package/squads/claude-code-mastery/tasks/audit-integration.md +6 -0
  14. package/squads/claude-code-mastery/tasks/audit-settings.md +6 -0
  15. package/squads/claude-code-mastery/tasks/audit-setup.md +6 -0
  16. package/squads/claude-code-mastery/tasks/brownfield-setup.md +6 -0
  17. package/squads/claude-code-mastery/tasks/ci-cd-setup.md +6 -0
  18. package/squads/claude-code-mastery/tasks/claude-md-engineer.md +6 -0
  19. package/squads/claude-code-mastery/tasks/configure-claude-code.md +6 -0
  20. package/squads/claude-code-mastery/tasks/context-rot-audit.md +6 -0
  21. package/squads/claude-code-mastery/tasks/create-agent-definition.md +6 -0
  22. package/squads/claude-code-mastery/tasks/create-rules.md +6 -0
  23. package/squads/claude-code-mastery/tasks/create-team-topology.md +6 -0
  24. package/squads/claude-code-mastery/tasks/diagnose.md +6 -0
  25. package/squads/claude-code-mastery/tasks/enterprise-config.md +6 -0
  26. package/squads/claude-code-mastery/tasks/hook-designer.md +6 -0
  27. package/squads/claude-code-mastery/tasks/integrate-project.md +6 -0
  28. package/squads/claude-code-mastery/tasks/mcp-integration-plan.md +6 -0
  29. package/squads/claude-code-mastery/tasks/mcp-workflow.md +6 -0
  30. package/squads/claude-code-mastery/tasks/multi-project-setup.md +6 -0
  31. package/squads/claude-code-mastery/tasks/optimize-context.md +6 -0
  32. package/squads/claude-code-mastery/tasks/optimize-workflow.md +6 -0
  33. package/squads/claude-code-mastery/tasks/parallel-decomposition.md +6 -0
  34. package/squads/claude-code-mastery/tasks/permission-strategy.md +6 -0
  35. package/squads/claude-code-mastery/tasks/sandbox-setup.md +6 -0
  36. package/squads/claude-code-mastery/tasks/setup-repository.md +6 -0
  37. package/squads/claude-code-mastery/tasks/setup-wizard.md +6 -0
  38. package/squads/claude-code-mastery/tasks/worktree-strategy.md +6 -0
  39. package/squads/squad-animations/workflows/3d-scene-creation-cycle.yaml +5 -0
  40. package/squads/squad-animations/workflows/animation-quality-review-cycle.yaml +5 -0
  41. package/squads/squad-animations/workflows/generative-art-creation-cycle.yaml +5 -0
  42. package/squads/squad-animations/workflows/prompt-to-animation-cycle.yaml +5 -0
  43. package/squads/squad-animations/workflows/scroll-experience-creation-cycle.yaml +5 -0
  44. package/squads/squad-brand/knowledge-base/routing-catalog.md +8 -0
  45. package/squads/squad-cloning/workflows/full-clone-pipeline.yaml +5 -0
  46. package/squads/squad-cloning/workflows/quality-validation-cycle.yaml +5 -0
  47. package/squads/squad-cloning/workflows/source-discovery-cycle.yaml +5 -0
  48. package/squads/squad-cloning/workflows/tier1-kb-only.yaml +5 -0
  49. package/squads/squad-cloning/workflows/tier2-consultant.yaml +5 -0
  50. package/squads/squad-cloning/workflows/tier3-full-clone.yaml +5 -0
  51. package/squads/squad-commercial/knowledge-base/routing-catalog.md +12 -0
  52. package/squads/squad-commercial/workflows/churn-prevention-protocol.yaml +5 -0
  53. package/squads/squad-commercial/workflows/client-onboarding-activation.yaml +5 -0
  54. package/squads/squad-commercial/workflows/expansion-revenue-cycle.yaml +5 -0
  55. package/squads/squad-commercial/workflows/new-offer-launch.yaml +5 -0
  56. package/squads/squad-commercial/workflows/quarterly-commercial-review.yaml +5 -0
  57. package/squads/squad-commercial/workflows/revenue-forecasting-cycle.yaml +5 -0
  58. package/squads/squad-content/knowledge-base/routing-catalog.md +12 -0
  59. package/squads/squad-content/workflows/content-audit-cycle.yaml +4 -0
  60. package/squads/squad-content/workflows/content-creation-cycle.yaml +4 -0
  61. package/squads/squad-content/workflows/editorial-planning-cycle.yaml +4 -0
  62. package/squads/squad-content/workflows/onboarding-content-cycle.yaml +4 -0
  63. package/squads/squad-content/workflows/performance-feedback-loop.yaml +4 -0
  64. package/squads/squad-content/workflows/signal-to-content-cycle.yaml +4 -0
  65. package/squads/squad-copy/knowledge-base/routing-catalog.md +12 -0
  66. package/squads/squad-copy/workflows/brand-voice-development.yaml +5 -0
  67. package/squads/squad-copy/workflows/campaign-copy-cycle.yaml +5 -0
  68. package/squads/squad-copy/workflows/content-copy-cycle.yaml +5 -0
  69. package/squads/squad-copy/workflows/conversion-copy-sprint.yaml +5 -0
  70. package/squads/squad-copy/workflows/full-copy-cycle.yaml +5 -0
  71. package/squads/squad-copy/workflows/sales-copy-pipeline.yaml +5 -0
  72. package/squads/squad-council/workflows/business-audit-cycle.yaml +1 -0
  73. package/squads/squad-council/workflows/strategic-advisory-session.yaml +1 -0
  74. package/squads/squad-courses/workflows/course-launch-cycle.yaml +1 -6
  75. package/squads/squad-courses/workflows/presentation-creation.yaml +2 -2
  76. package/squads/squad-design/knowledge-base/routing-catalog.md +12 -0
  77. package/squads/squad-design/workflows/a11y-compliance-cycle.yaml +1 -0
  78. package/squads/squad-design/workflows/design-system-build-cycle.yaml +1 -0
  79. package/squads/squad-design/workflows/landing-page-sprint.yaml +1 -0
  80. package/squads/squad-design/workflows/performance-remediation-cycle.yaml +1 -0
  81. package/squads/squad-design/workflows/ux-research-sprint.yaml +1 -0
  82. package/squads/squad-design/workflows/zero-to-digital-product-cycle.yaml +1 -0
  83. package/squads/squad-finance/knowledge-base/routing-catalog.md +12 -0
  84. package/squads/squad-finance/workflows/client-profitability-audit.yaml +5 -0
  85. package/squads/squad-finance/workflows/monthly-financial-cycle.yaml +5 -0
  86. package/squads/squad-finance/workflows/pricing-design-cycle.yaml +5 -0
  87. package/squads/squad-finance/workflows/quarterly-financial-review.yaml +5 -0
  88. package/squads/squad-growth/knowledge-base/routing-catalog.md +12 -0
  89. package/squads/squad-growth/workflows/analytics-instrumentation-pipeline.yaml +1 -0
  90. package/squads/squad-growth/workflows/campaign-performance-review.yaml +1 -0
  91. package/squads/squad-growth/workflows/cro-experimentation-sprint.yaml +1 -0
  92. package/squads/squad-growth/workflows/full-growth-analytics-cycle.yaml +1 -0
  93. package/squads/squad-growth/workflows/growth-experiment-loop.yaml +1 -0
  94. package/squads/squad-growth/workflows/seo-audit-optimization-cycle.yaml +1 -0
  95. package/squads/squad-paidmedia/knowledge-base/routing-catalog.md +12 -0
  96. package/squads/squad-paidmedia/workflows/account-audit-cycle.yaml +5 -0
  97. package/squads/squad-paidmedia/workflows/campaign-launch-cycle.yaml +5 -0
  98. package/squads/squad-paidmedia/workflows/creative-testing-cycle.yaml +5 -0
  99. package/squads/squad-paidmedia/workflows/cro-optimization-cycle.yaml +5 -0
  100. package/squads/squad-paidmedia/workflows/scaling-sprint.yaml +5 -0
  101. package/squads/squad-product/knowledge-base/routing-catalog.md +12 -0
  102. package/squads/squad-product/workflows/client-roadmap-alignment-cycle.yaml +4 -0
  103. package/squads/squad-product/workflows/product-discovery-cycle.yaml +4 -0
  104. package/squads/squad-product/workflows/product-handoff-cycle.yaml +4 -0
  105. package/squads/squad-product/workflows/product-launch-cycle.yaml +4 -0
  106. package/squads/squad-product/workflows/product-strategy-definition-cycle.yaml +4 -0
  107. package/squads/squad-product/workflows/story-development-delivery-cycle.yaml +4 -0
  108. package/squads/squad-research/knowledge-base/routing-catalog.md +12 -0
  109. package/squads/squad-research/workflows/audience-intelligence-cycle.yaml +4 -0
  110. package/squads/squad-research/workflows/competitive-intelligence-cycle.yaml +4 -0
  111. package/squads/squad-research/workflows/deep-research-cycle.yaml +4 -0
  112. package/squads/squad-research/workflows/full-research-sprint.yaml +4 -0
  113. package/squads/squad-research/workflows/market-analysis-cycle.yaml +4 -0
  114. package/squads/squad-research/workflows/trend-forecasting-cycle.yaml +4 -0
  115. package/squads/squad-storytelling/workflows/pitch-narrative-workflow.yaml +1 -0
  116. 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
  }