@jaimevalasek/aioson 1.4.0 → 1.5.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 (199) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/LICENSE +661 -21
  3. package/README.md +3 -1
  4. package/docs/en/squad-dashboard.md +372 -0
  5. package/docs/openclaw-bridge.md +308 -0
  6. package/docs/pt/agentes.md +124 -10
  7. package/docs/pt/cenarios.md +46 -2
  8. package/docs/pt/comandos-cli.md +60 -1
  9. package/docs/pt/inicio-rapido.md +18 -2
  10. package/docs/pt/squad-dashboard.md +373 -0
  11. package/docs/testing/genome-2.0-matrix.md +5 -5
  12. package/docs/testing/genome-2.0-rollout.md +9 -9
  13. package/package.json +2 -2
  14. package/src/backup-local.js +74 -0
  15. package/src/cli.js +98 -0
  16. package/src/commands/backup-local-cmd.js +25 -0
  17. package/src/commands/runtime.js +242 -0
  18. package/src/commands/setup-context.js +7 -2
  19. package/src/commands/squad-daemon.js +209 -0
  20. package/src/commands/squad-dashboard.js +39 -0
  21. package/src/commands/squad-deploy.js +64 -0
  22. package/src/commands/squad-doctor.js +52 -0
  23. package/src/commands/squad-mcp.js +270 -0
  24. package/src/commands/squad-processes.js +56 -0
  25. package/src/commands/squad-recovery.js +42 -0
  26. package/src/commands/squad-roi.js +291 -0
  27. package/src/commands/squad-score.js +250 -0
  28. package/src/commands/squad-status.js +37 -1
  29. package/src/commands/squad-validate.js +62 -1
  30. package/src/commands/squad-webhook.js +160 -0
  31. package/src/commands/squad-worker.js +191 -0
  32. package/src/commands/squad-worktrees.js +75 -0
  33. package/src/commands/web-map.js +70 -0
  34. package/src/commands/web-scrape.js +71 -0
  35. package/src/constants.js +8 -0
  36. package/src/context-writer.js +45 -1
  37. package/src/i18n/messages/en.js +127 -1
  38. package/src/i18n/messages/es.js +117 -0
  39. package/src/i18n/messages/fr.js +117 -0
  40. package/src/i18n/messages/pt-BR.js +126 -1
  41. package/src/lib/webhook-server.js +328 -0
  42. package/src/mcp-connectors/registry.js +602 -0
  43. package/src/runtime-store.js +259 -2
  44. package/src/squad/external-session.js +180 -0
  45. package/src/squad/inter-squad.js +74 -0
  46. package/src/squad/recovery-context.js +201 -0
  47. package/src/squad/worktree-manager.js +114 -0
  48. package/src/squad-daemon.js +490 -0
  49. package/src/squad-dashboard/api.js +223 -0
  50. package/src/squad-dashboard/attachment-handler.js +93 -0
  51. package/src/squad-dashboard/context-monitor.js +157 -0
  52. package/src/squad-dashboard/execution-logs.js +115 -0
  53. package/src/squad-dashboard/hunk-review.js +209 -0
  54. package/src/squad-dashboard/metrics.js +133 -0
  55. package/src/squad-dashboard/process-monitor.js +125 -0
  56. package/src/squad-dashboard/renderer.js +858 -0
  57. package/src/squad-dashboard/server.js +232 -0
  58. package/src/squad-dashboard/styles.js +525 -0
  59. package/src/squad-dashboard/token-tracker.js +99 -0
  60. package/src/web.js +284 -0
  61. package/src/worker-runner.js +339 -0
  62. package/template/.aioson/agents/analyst.md +4 -0
  63. package/template/.aioson/agents/architect.md +4 -0
  64. package/template/.aioson/agents/dev.md +120 -11
  65. package/template/.aioson/agents/deyvin.md +8 -0
  66. package/template/.aioson/agents/neo.md +152 -0
  67. package/template/.aioson/agents/orache.md +17 -0
  68. package/template/.aioson/agents/orchestrator.md +26 -0
  69. package/template/.aioson/agents/product.md +60 -12
  70. package/template/.aioson/agents/qa.md +1 -0
  71. package/template/.aioson/agents/setup.md +63 -19
  72. package/template/.aioson/agents/sheldon.md +603 -0
  73. package/template/.aioson/agents/squad.md +191 -0
  74. package/template/.aioson/agents/tester.md +254 -0
  75. package/template/.aioson/agents/ux-ui.md +12 -0
  76. package/template/.aioson/config.md +6 -0
  77. package/template/.aioson/locales/en/agents/analyst.md +8 -0
  78. package/template/.aioson/locales/en/agents/architect.md +8 -0
  79. package/template/.aioson/locales/en/agents/dev.md +66 -7
  80. package/template/.aioson/locales/en/agents/deyvin.md +8 -0
  81. package/template/.aioson/locales/en/agents/neo.md +8 -0
  82. package/template/.aioson/locales/en/agents/orchestrator.md +26 -0
  83. package/template/.aioson/locales/en/agents/qa.md +49 -0
  84. package/template/.aioson/locales/en/agents/setup.md +2 -1
  85. package/template/.aioson/locales/en/agents/sheldon.md +340 -0
  86. package/template/.aioson/locales/en/agents/ux-ui.md +8 -0
  87. package/template/.aioson/locales/es/agents/analyst.md +8 -0
  88. package/template/.aioson/locales/es/agents/architect.md +8 -0
  89. package/template/.aioson/locales/es/agents/dev.md +66 -7
  90. package/template/.aioson/locales/es/agents/deyvin.md +8 -0
  91. package/template/.aioson/locales/es/agents/neo.md +48 -0
  92. package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
  93. package/template/.aioson/locales/es/agents/qa.md +26 -0
  94. package/template/.aioson/locales/es/agents/setup.md +2 -1
  95. package/template/.aioson/locales/es/agents/sheldon.md +192 -0
  96. package/template/.aioson/locales/es/agents/squad.md +63 -0
  97. package/template/.aioson/locales/es/agents/ux-ui.md +8 -0
  98. package/template/.aioson/locales/fr/agents/analyst.md +8 -0
  99. package/template/.aioson/locales/fr/agents/architect.md +8 -0
  100. package/template/.aioson/locales/fr/agents/dev.md +66 -7
  101. package/template/.aioson/locales/fr/agents/deyvin.md +8 -0
  102. package/template/.aioson/locales/fr/agents/neo.md +48 -0
  103. package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
  104. package/template/.aioson/locales/fr/agents/qa.md +26 -0
  105. package/template/.aioson/locales/fr/agents/setup.md +2 -1
  106. package/template/.aioson/locales/fr/agents/sheldon.md +192 -0
  107. package/template/.aioson/locales/fr/agents/squad.md +63 -0
  108. package/template/.aioson/locales/fr/agents/ux-ui.md +8 -0
  109. package/template/.aioson/locales/pt-BR/agents/analyst.md +19 -0
  110. package/template/.aioson/locales/pt-BR/agents/architect.md +19 -0
  111. package/template/.aioson/locales/pt-BR/agents/dev.md +75 -12
  112. package/template/.aioson/locales/pt-BR/agents/deyvin.md +8 -0
  113. package/template/.aioson/locales/pt-BR/agents/neo.md +147 -0
  114. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +26 -0
  115. package/template/.aioson/locales/pt-BR/agents/product.md +8 -3
  116. package/template/.aioson/locales/pt-BR/agents/qa.md +60 -0
  117. package/template/.aioson/locales/pt-BR/agents/setup.md +2 -1
  118. package/template/.aioson/locales/pt-BR/agents/sheldon.md +192 -0
  119. package/template/.aioson/locales/pt-BR/agents/squad.md +105 -0
  120. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +8 -0
  121. package/template/.aioson/schemas/squad-blueprint.schema.json +21 -0
  122. package/template/.aioson/schemas/squad-manifest.schema.json +178 -1
  123. package/template/.aioson/skills/design/bold-editorial-ui/SKILL.md +205 -0
  124. package/template/.aioson/skills/design/bold-editorial-ui/references/art-direction.md +338 -0
  125. package/template/.aioson/skills/design/bold-editorial-ui/references/components.md +977 -0
  126. package/template/.aioson/skills/design/bold-editorial-ui/references/dashboards.md +218 -0
  127. package/template/.aioson/skills/design/bold-editorial-ui/references/design-tokens.md +326 -0
  128. package/template/.aioson/skills/design/bold-editorial-ui/references/motion.md +461 -0
  129. package/template/.aioson/skills/design/bold-editorial-ui/references/patterns.md +293 -0
  130. package/template/.aioson/skills/design/bold-editorial-ui/references/websites.md +352 -0
  131. package/template/.aioson/skills/design/clean-saas-ui/SKILL.md +210 -0
  132. package/template/.aioson/skills/design/clean-saas-ui/references/art-direction.md +319 -0
  133. package/template/.aioson/skills/design/clean-saas-ui/references/components.md +365 -0
  134. package/template/.aioson/skills/design/clean-saas-ui/references/dashboards.md +196 -0
  135. package/template/.aioson/skills/design/clean-saas-ui/references/design-tokens.md +244 -0
  136. package/template/.aioson/skills/design/clean-saas-ui/references/motion.md +235 -0
  137. package/template/.aioson/skills/design/clean-saas-ui/references/patterns.md +215 -0
  138. package/template/.aioson/skills/design/clean-saas-ui/references/websites.md +295 -0
  139. package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +55 -9
  140. package/template/.aioson/skills/design/cognitive-core-ui/references/art-direction.md +339 -0
  141. package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +1 -1
  142. package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +100 -0
  143. package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +43 -9
  144. package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +40 -0
  145. package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +1 -1
  146. package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +99 -12
  147. package/template/.aioson/skills/design/warm-craft-ui/SKILL.md +209 -0
  148. package/template/.aioson/skills/design/warm-craft-ui/references/art-direction.md +324 -0
  149. package/template/.aioson/skills/design/warm-craft-ui/references/components.md +508 -0
  150. package/template/.aioson/skills/design/warm-craft-ui/references/dashboards.md +223 -0
  151. package/template/.aioson/skills/design/warm-craft-ui/references/design-tokens.md +374 -0
  152. package/template/.aioson/skills/design/warm-craft-ui/references/motion.md +356 -0
  153. package/template/.aioson/skills/design/warm-craft-ui/references/patterns.md +288 -0
  154. package/template/.aioson/skills/design/warm-craft-ui/references/websites.md +289 -0
  155. package/template/.aioson/skills/premium-visual-design/SKILL.md +83 -0
  156. package/template/.aioson/skills/premium-visual-design/components/agent-badge.md +92 -0
  157. package/template/.aioson/skills/premium-visual-design/components/dependency-node.md +102 -0
  158. package/template/.aioson/skills/premium-visual-design/components/mention-autocomplete.md +136 -0
  159. package/template/.aioson/skills/premium-visual-design/components/notification-center.md +136 -0
  160. package/template/.aioson/skills/premium-visual-design/components/review-action-bar.md +188 -0
  161. package/template/.aioson/skills/premium-visual-design/components/team-switcher.md +131 -0
  162. package/template/.aioson/skills/premium-visual-design/patterns/agent-message-thread.md +198 -0
  163. package/template/.aioson/skills/premium-visual-design/patterns/notification-panel.md +275 -0
  164. package/template/.aioson/skills/premium-visual-design/patterns/review-workflow-ui.md +234 -0
  165. package/template/.aioson/skills/premium-visual-design/patterns/task-dependency-graph.md +147 -0
  166. package/template/.aioson/skills/premium-visual-design/tokens/status-extended.md +142 -0
  167. package/template/.aioson/skills/squad/formats/catalog.json +15 -0
  168. package/template/.aioson/skills/squad/formats/content/blog-post.md +47 -0
  169. package/template/.aioson/skills/squad/formats/content/newsletter.md +47 -0
  170. package/template/.aioson/skills/squad/formats/creative/podcast-script.md +43 -0
  171. package/template/.aioson/skills/squad/formats/creative/video-script.md +41 -0
  172. package/template/.aioson/skills/squad/formats/social/instagram-feed.md +42 -0
  173. package/template/.aioson/skills/squad/formats/social/linkedin-post.md +42 -0
  174. package/template/.aioson/skills/squad/formats/social/tiktok.md +39 -0
  175. package/template/.aioson/skills/squad/formats/social/twitter-thread.md +39 -0
  176. package/template/.aioson/skills/squad/formats/social/youtube-long.md +47 -0
  177. package/template/.aioson/skills/squad/formats/social/youtube-shorts.md +39 -0
  178. package/template/.aioson/skills/squad/patterns/multi-platform-pattern.md +108 -0
  179. package/template/.aioson/skills/squad/patterns/persona-based-pattern.md +98 -0
  180. package/template/.aioson/skills/squad/patterns/pipeline-pattern.md +106 -0
  181. package/template/.aioson/skills/squad/patterns/review-loop-pattern.md +81 -0
  182. package/template/.aioson/skills/squad/references/checklist-templates.md +122 -0
  183. package/template/.aioson/skills/squad/references/executor-archetypes.md +123 -0
  184. package/template/.aioson/skills/squad/references/workflow-templates.md +169 -0
  185. package/template/.aioson/skills/static/debugging-protocol.md +42 -0
  186. package/template/.aioson/skills/static/git-worktrees.md +36 -0
  187. package/template/.aioson/tasks/implementation-plan.md +19 -0
  188. package/template/.aioson/tasks/squad-design.md +28 -0
  189. package/template/.aioson/tasks/squad-profile.md +48 -0
  190. package/template/.aioson/tasks/squad-review.md +61 -0
  191. package/template/.aioson/tasks/squad-task-decompose.md +66 -0
  192. package/template/.claude/commands/aioson/agent/neo.md +5 -0
  193. package/template/.claude/commands/aioson/agent/tester.md +5 -0
  194. package/template/.gemini/GEMINI.md +1 -0
  195. package/template/.gemini/commands/aios-neo.toml +4 -0
  196. package/template/.gemini/commands/aios-tester.toml +6 -0
  197. package/template/AGENTS.md +3 -0
  198. package/template/CLAUDE.md +5 -2
  199. package/template/OPENCODE.md +2 -0
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { getActiveProcesses, stopProcess, stopSquadProcesses } = require('../squad-dashboard/process-monitor');
5
+
6
+ function formatElapsed(seconds) {
7
+ if (seconds == null) return '-';
8
+ if (seconds < 60) return `${seconds}s`;
9
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m${seconds % 60}s`;
10
+ return `${Math.floor(seconds / 3600)}h${Math.floor((seconds % 3600) / 60)}m`;
11
+ }
12
+
13
+ async function runSquadProcesses({ args, options, logger }) {
14
+ const projectDir = path.resolve(process.cwd(), args[0] || '.');
15
+ const squadFilter = options.squad || null;
16
+
17
+ // --stop <pid>
18
+ if (options.stop && options.stop !== true) {
19
+ const result = await stopProcess(projectDir, options.stop);
20
+ if (result.ok) {
21
+ logger.log(`Process ${options.stop} stopped.`);
22
+ } else {
23
+ logger.error(`Failed to stop ${options.stop}: ${result.error}`);
24
+ }
25
+ return result;
26
+ }
27
+
28
+ // --stop-squad <squad>
29
+ if (options['stop-squad']) {
30
+ const slug = options['stop-squad'];
31
+ const results = await stopSquadProcesses(projectDir, slug);
32
+ const stopped = results.filter(r => r.ok).length;
33
+ logger.log(`Stopped ${stopped}/${results.length} processes for squad "${slug}".`);
34
+ return { ok: true, results };
35
+ }
36
+
37
+ // List
38
+ const processes = await getActiveProcesses(projectDir, squadFilter);
39
+ if (processes.length === 0) {
40
+ logger.log(squadFilter ? `No active processes for squad "${squadFilter}".` : 'No active processes.');
41
+ return { ok: true, processes: [] };
42
+ }
43
+
44
+ logger.log(`Active processes (${processes.length}):`);
45
+ for (const proc of processes) {
46
+ const status = proc.alive ? '[*]' : '[ ]';
47
+ const elapsed = formatElapsed(proc.elapsedSeconds);
48
+ const ctx = proc.contextPct != null ? ` ctx:${proc.contextPct}%` : '';
49
+ const url = proc.url ? ` ${proc.url}` : '';
50
+ logger.log(` ${status} [${proc.squadSlug}] ${proc.agentSlug} pid:${proc.pid} elapsed:${elapsed}${ctx}${url}`);
51
+ }
52
+
53
+ return { ok: true, processes };
54
+ }
55
+
56
+ module.exports = { runSquadProcesses };
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { generateRecovery, readRecovery } = require('../squad/recovery-context');
5
+
6
+ /**
7
+ * aioson squad:recovery <project> --squad <squad> --agent <agent> [--show]
8
+ *
9
+ * Generates (or re-generates) the recovery-context.md for an agent.
10
+ * --show: print the generated content to stdout instead of just confirming.
11
+ */
12
+ async function runSquadRecovery({ args, options, logger }) {
13
+ const projectDir = path.resolve(process.cwd(), args[0] || '.');
14
+ const squadSlug = options.squad || args[1];
15
+ const agentSlug = options.agent || args[2];
16
+
17
+ if (!squadSlug || !agentSlug) {
18
+ logger.error('Usage: aioson squad:recovery <project> --squad <squad> --agent <agent> [--show]');
19
+ return { ok: false };
20
+ }
21
+
22
+ const result = await generateRecovery(projectDir, squadSlug, agentSlug);
23
+
24
+ if (!result.ok) {
25
+ logger.error(`Failed to generate recovery context: ${result.error}`);
26
+ return result;
27
+ }
28
+
29
+ logger.log(`Recovery context generated: ${result.path} (~${result.tokens} tokens)`);
30
+
31
+ if (options.show) {
32
+ const content = await readRecovery(projectDir, squadSlug);
33
+ if (content) {
34
+ logger.log('');
35
+ logger.log(content);
36
+ }
37
+ }
38
+
39
+ return result;
40
+ }
41
+
42
+ module.exports = { runSquadRecovery };
@@ -0,0 +1,291 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const {
5
+ openRuntimeDb,
6
+ upsertSquadMetric,
7
+ listSquadMetrics,
8
+ upsertROIConfig,
9
+ getROIConfig
10
+ } = require('../runtime-store');
11
+
12
+ async function runSquadRoi({ args, options, logger, t }) {
13
+ const projectDir = path.resolve(args[0] || options.path || '.');
14
+ const squadSlug = options.squad;
15
+ const sub = options.sub || 'report';
16
+
17
+ if (!squadSlug) {
18
+ logger.log(t('squad_roi.squad_required'));
19
+ return { ok: false, error: 'squad_required' };
20
+ }
21
+
22
+ if (sub === 'config') {
23
+ return handleConfig({ projectDir, squadSlug, options, logger, t });
24
+ }
25
+ if (sub === 'metric') {
26
+ return handleMetric({ projectDir, squadSlug, options, logger, t });
27
+ }
28
+ if (sub === 'report') {
29
+ return handleReport({ projectDir, squadSlug, options, logger, t });
30
+ }
31
+ if (sub === 'export') {
32
+ return handleExport({ projectDir, squadSlug, options, logger, t });
33
+ }
34
+
35
+ logger.log(t('squad_roi.unknown_sub', { sub }));
36
+ return { ok: false, error: 'unknown_sub' };
37
+ }
38
+
39
+ async function handleConfig({ projectDir, squadSlug, options, logger, t }) {
40
+ const handle = await openRuntimeDb(projectDir);
41
+
42
+ upsertROIConfig(handle.db, {
43
+ squadSlug,
44
+ pricingModel: options.pricing || options.pricing_model,
45
+ setupFee: options.setup ? parseFloat(options.setup) : undefined,
46
+ monthlyFee: options.monthly ? parseFloat(options.monthly) : undefined,
47
+ percentageFee: options.percentage ? parseFloat(options.percentage) : undefined,
48
+ percentageBase: options.base,
49
+ currency: options.currency,
50
+ contractMonths: options.months ? parseInt(options.months, 10) : undefined
51
+ });
52
+
53
+ const config = getROIConfig(handle.db, squadSlug);
54
+ handle.db.close();
55
+
56
+ logger.log(t('squad_roi.config_saved', { squad: squadSlug }));
57
+ logger.log(` ${t('squad_roi.pricing_model')}: ${config.pricing_model}`);
58
+ if (config.setup_fee) logger.log(` ${t('squad_roi.setup_fee')}: ${config.currency} ${config.setup_fee}`);
59
+ if (config.monthly_fee) logger.log(` ${t('squad_roi.monthly_fee')}: ${config.currency} ${config.monthly_fee}`);
60
+ if (config.percentage_fee) logger.log(` ${t('squad_roi.percentage')}: ${config.percentage_fee}% (${config.percentage_base || 'n/a'})`);
61
+ logger.log(` ${t('squad_roi.contract')}: ${config.contract_months} months`);
62
+
63
+ return { ok: true, config };
64
+ }
65
+
66
+ async function handleMetric({ projectDir, squadSlug, options, logger, t }) {
67
+ const key = options.key;
68
+ const value = options.value;
69
+ const period = options.period;
70
+
71
+ if (!key || value === undefined) {
72
+ logger.log(t('squad_roi.metric_required'));
73
+ return { ok: false, error: 'metric_required' };
74
+ }
75
+
76
+ const handle = await openRuntimeDb(projectDir);
77
+ upsertSquadMetric(handle.db, {
78
+ squadSlug,
79
+ metricKey: key,
80
+ value: parseFloat(value),
81
+ unit: options.unit || null,
82
+ period: period || null,
83
+ baseline: options.baseline ? parseFloat(options.baseline) : null,
84
+ target: options.target ? parseFloat(options.target) : null,
85
+ source: options.source || 'manual',
86
+ notes: options.notes || null
87
+ });
88
+ handle.db.close();
89
+
90
+ logger.log(t('squad_roi.metric_saved', { key, value, squad: squadSlug }));
91
+ return { ok: true, key, value: parseFloat(value), period };
92
+ }
93
+
94
+ function calculateROI(metrics, config) {
95
+ if (!config) return null;
96
+
97
+ const improvements = [];
98
+ for (const m of metrics) {
99
+ if (m.baseline === null || m.baseline === undefined) continue;
100
+ const improvement = m.baseline - m.metric_value;
101
+ improvements.push({
102
+ key: m.metric_key,
103
+ baseline: m.baseline,
104
+ actual: m.metric_value,
105
+ target: m.target,
106
+ improvement,
107
+ unit: m.metric_unit,
108
+ period: m.period
109
+ });
110
+ }
111
+
112
+ const monthlyCost = (config.monthly_fee || 0) + ((config.setup_fee || 0) / (config.contract_months || 12));
113
+
114
+ return {
115
+ improvements,
116
+ monthly_cost: monthlyCost,
117
+ pricing: {
118
+ model: config.pricing_model,
119
+ setup_fee: config.setup_fee,
120
+ monthly_fee: config.monthly_fee,
121
+ percentage_fee: config.percentage_fee,
122
+ currency: config.currency
123
+ }
124
+ };
125
+ }
126
+
127
+ async function handleReport({ projectDir, squadSlug, options, logger, t }) {
128
+ const handle = await openRuntimeDb(projectDir);
129
+ const config = getROIConfig(handle.db, squadSlug);
130
+ const metrics = listSquadMetrics(handle.db, squadSlug);
131
+ handle.db.close();
132
+
133
+ if (metrics.length === 0) {
134
+ logger.log(t('squad_roi.no_metrics', { squad: squadSlug }));
135
+ return { ok: true, metrics: [], roi: null };
136
+ }
137
+
138
+ const period = options.period || null;
139
+ const filtered = period ? metrics.filter(m => m.period === period) : metrics;
140
+
141
+ logger.log(t('squad_roi.report_title', { squad: squadSlug }));
142
+ logger.log('');
143
+
144
+ // Group by metric_key
145
+ const byKey = {};
146
+ for (const m of filtered) {
147
+ if (!byKey[m.metric_key]) byKey[m.metric_key] = [];
148
+ byKey[m.metric_key].push(m);
149
+ }
150
+
151
+ for (const [key, entries] of Object.entries(byKey)) {
152
+ const latest = entries[entries.length - 1];
153
+ logger.log(` ${key}`);
154
+ if (latest.baseline !== null) {
155
+ logger.log(` ${t('squad_roi.baseline')}: ${latest.baseline}${latest.metric_unit || ''}`);
156
+ }
157
+ logger.log(` ${t('squad_roi.actual')}: ${latest.metric_value}${latest.metric_unit || ''}`);
158
+ if (latest.target !== null) {
159
+ logger.log(` ${t('squad_roi.target')}: ${latest.target}${latest.metric_unit || ''}`);
160
+ // Progress bar
161
+ if (latest.baseline !== null && latest.baseline !== latest.target) {
162
+ const totalGap = Math.abs(latest.baseline - latest.target);
163
+ const achieved = Math.abs(latest.baseline - latest.metric_value);
164
+ const pct = Math.min(100, Math.round((achieved / totalGap) * 100));
165
+ const filled = Math.round(pct / 5);
166
+ const bar = '#'.repeat(filled) + '.'.repeat(20 - filled);
167
+ logger.log(` [${bar}] ${pct}%`);
168
+ }
169
+ }
170
+ if (latest.period) {
171
+ logger.log(` ${t('squad_roi.period')}: ${latest.period}`);
172
+ }
173
+ logger.log('');
174
+ }
175
+
176
+ const roi = calculateROI(filtered, config);
177
+ if (roi && config) {
178
+ logger.log(t('squad_roi.cost_section'));
179
+ logger.log(` ${t('squad_roi.monthly_cost')}: ${config.currency} ${roi.monthly_cost.toFixed(2)}`);
180
+ }
181
+
182
+ return { ok: true, metrics: filtered, roi };
183
+ }
184
+
185
+ function renderReportHtml(squadSlug, metrics, config) {
186
+ const roi = calculateROI(metrics, config);
187
+ const currency = (config && config.currency) || 'BRL';
188
+
189
+ const byKey = {};
190
+ for (const m of metrics) {
191
+ if (!byKey[m.metric_key]) byKey[m.metric_key] = [];
192
+ byKey[m.metric_key].push(m);
193
+ }
194
+
195
+ const metricsHtml = Object.entries(byKey).map(([key, entries]) => {
196
+ const latest = entries[entries.length - 1];
197
+ let progressBar = '';
198
+ if (latest.baseline !== null && latest.target !== null && latest.baseline !== latest.target) {
199
+ const totalGap = Math.abs(latest.baseline - latest.target);
200
+ const achieved = Math.abs(latest.baseline - latest.metric_value);
201
+ const pct = Math.min(100, Math.round((achieved / totalGap) * 100));
202
+ progressBar = `<div style="background:#333;border-radius:4px;overflow:hidden;height:8px;margin-top:4px">
203
+ <div style="background:#4ade80;height:100%;width:${pct}%"></div>
204
+ </div><small>${pct}% of target</small>`;
205
+ }
206
+ return `<div style="background:#1e1e2e;padding:16px;border-radius:8px;margin-bottom:12px">
207
+ <h3 style="margin:0 0 8px">${esc(key)}</h3>
208
+ ${latest.baseline !== null ? `<p>Baseline: <strong>${latest.baseline}${latest.metric_unit || ''}</strong></p>` : ''}
209
+ <p>Current: <strong>${latest.metric_value}${latest.metric_unit || ''}</strong></p>
210
+ ${latest.target !== null ? `<p>Target: <strong>${latest.target}${latest.metric_unit || ''}</strong></p>` : ''}
211
+ ${progressBar}
212
+ ${latest.period ? `<small>Period: ${esc(latest.period)}</small>` : ''}
213
+ </div>`;
214
+ }).join('\n');
215
+
216
+ const costHtml = roi && config ? `
217
+ <div style="background:#1e1e2e;padding:16px;border-radius:8px;margin-top:16px">
218
+ <h3>Cost Summary</h3>
219
+ <p>Pricing: ${esc(config.pricing_model)}</p>
220
+ ${config.setup_fee ? `<p>Setup: ${currency} ${config.setup_fee}</p>` : ''}
221
+ ${config.monthly_fee ? `<p>Monthly: ${currency} ${config.monthly_fee}</p>` : ''}
222
+ <p>Monthly effective cost: <strong>${currency} ${roi.monthly_cost.toFixed(2)}</strong></p>
223
+ </div>` : '';
224
+
225
+ return `<!DOCTYPE html>
226
+ <html lang="en">
227
+ <head>
228
+ <meta charset="UTF-8">
229
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
230
+ <title>ROI Report — ${esc(squadSlug)}</title>
231
+ <style>
232
+ body{font-family:system-ui,sans-serif;background:#0d1117;color:#e6edf3;margin:0;padding:24px}
233
+ .container{max-width:720px;margin:0 auto}
234
+ h1{border-bottom:1px solid #30363d;padding-bottom:12px}
235
+ h3{color:#58a6ff}
236
+ p{margin:4px 0}
237
+ small{color:#8b949e}
238
+ </style>
239
+ </head>
240
+ <body>
241
+ <div class="container">
242
+ <h1>ROI Report — ${esc(squadSlug)}</h1>
243
+ <p style="color:#8b949e">Generated: ${new Date().toISOString().slice(0, 10)}</p>
244
+ <h2>Metrics</h2>
245
+ ${metricsHtml}
246
+ ${costHtml}
247
+ <footer style="margin-top:32px;color:#8b949e;font-size:12px">
248
+ Generated by aioson squad:roi:export
249
+ </footer>
250
+ </div>
251
+ </body>
252
+ </html>`;
253
+ }
254
+
255
+ function esc(str) {
256
+ if (!str) return '';
257
+ return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
258
+ }
259
+
260
+ async function handleExport({ projectDir, squadSlug, options, logger, t }) {
261
+ const format = options.format || 'html';
262
+ const handle = await openRuntimeDb(projectDir);
263
+ const config = getROIConfig(handle.db, squadSlug);
264
+ const metrics = listSquadMetrics(handle.db, squadSlug);
265
+ handle.db.close();
266
+
267
+ if (metrics.length === 0) {
268
+ logger.log(t('squad_roi.no_metrics', { squad: squadSlug }));
269
+ return { ok: true, exported: false };
270
+ }
271
+
272
+ const period = options.period || null;
273
+ const filtered = period ? metrics.filter(m => m.period === period) : metrics;
274
+
275
+ if (format === 'json') {
276
+ const roi = calculateROI(filtered, config);
277
+ const output = JSON.stringify({ squad: squadSlug, metrics: filtered, roi, config }, null, 2);
278
+ logger.log(output);
279
+ return { ok: true, exported: true, format: 'json' };
280
+ }
281
+
282
+ // HTML
283
+ const html = renderReportHtml(squadSlug, filtered, config);
284
+ const fs = require('node:fs/promises');
285
+ const outFile = options.output || `roi-report-${squadSlug}.html`;
286
+ await fs.writeFile(outFile, html);
287
+ logger.log(t('squad_roi.exported', { file: outFile, format: 'html' }));
288
+ return { ok: true, exported: true, format: 'html', file: outFile };
289
+ }
290
+
291
+ module.exports = { runSquadRoi, calculateROI, renderReportHtml, esc };
@@ -0,0 +1,250 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+
6
+ async function readJsonIfExists(filePath) {
7
+ try {
8
+ const raw = await fs.readFile(filePath, 'utf8');
9
+ return JSON.parse(raw);
10
+ } catch { return null; }
11
+ }
12
+
13
+ function scoreCompletude(manifest) {
14
+ let score = 0;
15
+ const details = {};
16
+ const executors = Array.isArray(manifest.executors) ? manifest.executors : [];
17
+ const workflows = Array.isArray(manifest.workflows) ? manifest.workflows : [];
18
+ const checklists = Array.isArray(manifest.checklists) ? manifest.checklists : [];
19
+
20
+ // Executors typed (5pts)
21
+ const allTyped = executors.length > 0 && executors.every(e => e.type);
22
+ if (allTyped) { score += 5; details.executorsTyped = true; }
23
+
24
+ // Workflow defined (5pts)
25
+ const hasWorkflow = workflows.some(w => Array.isArray(w.phases) && w.phases.length >= 2);
26
+ if (hasWorkflow) { score += 5; details.workflowDefined = true; }
27
+
28
+ // Checklists present (3pts)
29
+ if (checklists.length > 0) { score += 3; details.checklistsPresent = true; }
30
+
31
+ // Tasks decomposed (5pts)
32
+ const hasTask = executors.some(e => Array.isArray(e.tasks) && e.tasks.length > 0);
33
+ if (hasTask) { score += 5; details.tasksDecomposed = true; }
34
+
35
+ // Workers present (2pts)
36
+ const hasWorker = executors.some(e => e.type === 'worker' || e.usesLLM === false);
37
+ if (hasWorker) { score += 2; details.workersPresent = true; }
38
+
39
+ // Investigation report (3pts)
40
+ if (manifest._investigationPath) { score += 3; details.investigationReport = true; }
41
+
42
+ // Model tiering (2pts)
43
+ const allTiered = executors.length > 0 && executors.every(e => e.modelTier);
44
+ if (allTiered) { score += 2; details.modelTiering = true; }
45
+
46
+ return { score, max: 25, details };
47
+ }
48
+
49
+ function scoreProfundidade(manifest) {
50
+ let score = 0;
51
+ const details = {};
52
+ const executors = Array.isArray(manifest.executors) ? manifest.executors : [];
53
+
54
+ // Executor focus areas avg >= 3 (5pts)
55
+ const focusCounts = executors.map(e => (Array.isArray(e.focus) ? e.focus : []).length);
56
+ const avgFocus = focusCounts.length > 0 ? focusCounts.reduce((a, b) => a + b, 0) / focusCounts.length : 0;
57
+ if (avgFocus >= 3) { score += 5; details.focusAreas = true; }
58
+
59
+ // Task quality criteria avg >= 3 (5pts)
60
+ const allTasks = executors.flatMap(e => Array.isArray(e.tasks) ? e.tasks : []);
61
+ if (allTasks.length > 0) { score += 5; details.taskCriteria = true; }
62
+
63
+ // Veto conditions (5pts)
64
+ const workflows = Array.isArray(manifest.workflows) ? manifest.workflows : [];
65
+ const hasVeto = workflows.some(w =>
66
+ (Array.isArray(w.phases) ? w.phases : []).some(p =>
67
+ Array.isArray(p.vetoConditions) && p.vetoConditions.length > 0
68
+ )
69
+ );
70
+ if (hasVeto) { score += 5; details.vetoConditions = true; }
71
+
72
+ // Content blueprints with 3+ sections (5pts)
73
+ const blueprints = Array.isArray(manifest.contentBlueprints) ? manifest.contentBlueprints : [];
74
+ const hasBp = blueprints.some(bp => Array.isArray(bp.sections) && bp.sections.length >= 3);
75
+ if (hasBp) { score += 5; details.contentBlueprints = true; }
76
+
77
+ // Skills declared >= 2 (5pts)
78
+ const skills = Array.isArray(manifest.skills) ? manifest.skills : [];
79
+ if (skills.length >= 2) { score += 5; details.skillsDeclared = true; }
80
+
81
+ return { score, max: 25, details };
82
+ }
83
+
84
+ function scoreQualidadeEstrutural(manifest) {
85
+ let score = 0;
86
+ const details = {};
87
+ const executors = Array.isArray(manifest.executors) ? manifest.executors : [];
88
+ const workflows = Array.isArray(manifest.workflows) ? manifest.workflows : [];
89
+
90
+ // Review loops (5pts)
91
+ const hasReview = workflows.some(w =>
92
+ (Array.isArray(w.phases) ? w.phases : []).some(p => p.review)
93
+ );
94
+ if (hasReview) { score += 5; details.reviewLoops = true; }
95
+
96
+ // Human gates (5pts)
97
+ const hasGate = workflows.some(w =>
98
+ (Array.isArray(w.phases) ? w.phases : []).some(p => p.humanGate)
99
+ );
100
+ if (hasGate) { score += 5; details.humanGates = true; }
101
+
102
+ // Cross-squad awareness (3pts) — orchestrator mentions cross-squad
103
+ // Simplified: check if ports are defined
104
+ if (manifest.ports && (Array.isArray(manifest.ports.inputs) || Array.isArray(manifest.ports.outputs))) {
105
+ score += 3; details.crossSquad = true;
106
+ }
107
+
108
+ // Output strategy configured (4pts)
109
+ const os = manifest.outputStrategy;
110
+ if (os && os.mode && os.mode !== 'hybrid') { score += 4; details.outputStrategy = true; }
111
+ else if (os && os.mode === 'hybrid') { score += 4; details.outputStrategy = true; }
112
+
113
+ // Genome bindings (3pts)
114
+ const genomes = manifest.genomes;
115
+ const hasGenome = genomes && (
116
+ (Array.isArray(genomes) && genomes.length > 0) ||
117
+ (typeof genomes === 'object' && !Array.isArray(genomes))
118
+ );
119
+ if (hasGenome) { score += 3; details.genomeBindings = true; }
120
+
121
+ // Format references (5pts)
122
+ const hasFormat = executors.some(e => Array.isArray(e.formats) && e.formats.length > 0);
123
+ if (hasFormat) { score += 5; details.formatReferences = true; }
124
+
125
+ return { score, max: 25, details };
126
+ }
127
+
128
+ function scorePotencial(manifest) {
129
+ let score = 0;
130
+ const details = {};
131
+ const workflows = Array.isArray(manifest.workflows) ? manifest.workflows : [];
132
+
133
+ // Anti-pattern guards via veto conditions (5pts)
134
+ const hasVeto = workflows.some(w =>
135
+ (Array.isArray(w.phases) ? w.phases : []).some(p =>
136
+ Array.isArray(p.vetoConditions) && p.vetoConditions.length > 0
137
+ )
138
+ );
139
+ if (hasVeto) { score += 5; details.antiPatternGuards = true; }
140
+
141
+ // Domain vocabulary — investigation (5pts)
142
+ if (manifest._investigationPath) { score += 5; details.domainVocabulary = true; }
143
+
144
+ // Structural patterns — content blueprints (5pts)
145
+ const blueprints = Array.isArray(manifest.contentBlueprints) ? manifest.contentBlueprints : [];
146
+ if (blueprints.length > 0) { score += 5; details.structuralPatterns = true; }
147
+
148
+ // Executor coherence + Output realism (10pts) → LLM-only, mark as partial
149
+ details.llmAssessmentPending = true;
150
+
151
+ return { score, max: 25, details };
152
+ }
153
+
154
+ function gradeFromScore(total) {
155
+ if (total >= 90) return 'S (Exceptional)';
156
+ if (total >= 80) return 'A (Excellent)';
157
+ if (total >= 70) return 'B (Good)';
158
+ if (total >= 50) return 'C (Adequate)';
159
+ return 'D (Needs work)';
160
+ }
161
+
162
+ function suggestQuickWins(d1, d2, d3, d4) {
163
+ const wins = [];
164
+ if (!d3.details.reviewLoops) wins.push({ action: 'Add review loop to a workflow phase', pts: 5 });
165
+ if (!d1.details.tasksDecomposed) wins.push({ action: 'Decompose at least 1 executor into tasks', pts: 5 });
166
+ if (!d3.details.formatReferences) wins.push({ action: 'Add a format reference to an executor', pts: 5 });
167
+ if (!d2.details.vetoConditions) wins.push({ action: 'Add veto conditions to a workflow phase', pts: 5 });
168
+ if (!d1.details.modelTiering) wins.push({ action: 'Assign modelTier to all executors', pts: 2 });
169
+ if (!d1.details.workersPresent) wins.push({ action: 'Add a worker executor (no LLM)', pts: 2 });
170
+ return wins.slice(0, 3);
171
+ }
172
+
173
+ async function runSquadScore({ args = [], options = {}, logger = console, translator } = {}) {
174
+ const projectDir = path.resolve(process.cwd(), args[0] || '.');
175
+ const slug = options.squad || args[1];
176
+
177
+ if (!slug) {
178
+ logger.error('Usage: aioson squad:score [path] --squad=<slug>');
179
+ return { valid: false, error: 'No slug provided' };
180
+ }
181
+
182
+ const manifestPath = path.join(projectDir, '.aioson', 'squads', slug, 'squad.manifest.json');
183
+ const manifest = await readJsonIfExists(manifestPath);
184
+
185
+ if (!manifest) {
186
+ logger.error(`Squad "${slug}" not found or invalid manifest`);
187
+ return { valid: false, error: 'Manifest not found' };
188
+ }
189
+
190
+ // Check for investigation
191
+ const squadSearchDir = path.join(projectDir, 'squad-searches', slug);
192
+ try {
193
+ await fs.access(squadSearchDir);
194
+ manifest._investigationPath = squadSearchDir;
195
+ } catch { /* no investigation */ }
196
+
197
+ const d1 = scoreCompletude(manifest);
198
+ const d2 = scoreProfundidade(manifest);
199
+ const d3 = scoreQualidadeEstrutural(manifest);
200
+ const d4 = scorePotencial(manifest);
201
+
202
+ const total = d1.score + d2.score + d3.score + d4.score;
203
+ const maxTotal = d1.max + d2.max + d3.max + d4.max;
204
+ const grade = gradeFromScore(total);
205
+ const wins = suggestQuickWins(d1, d2, d3, d4);
206
+
207
+ logger.log('');
208
+ logger.log(` Squad: ${slug}`);
209
+ logger.log('');
210
+ logger.log(` Completude: ${String(d1.score).padStart(2)}/${d1.max}`);
211
+ logger.log(` Profundidade: ${String(d2.score).padStart(2)}/${d2.max}`);
212
+ logger.log(` Qualidade Estrutural: ${String(d3.score).padStart(2)}/${d3.max}`);
213
+ logger.log(` Potencial de Resultado: ${String(d4.score).padStart(2)}/${d4.max}*`);
214
+ logger.log(` ${'─'.repeat(40)}`);
215
+ logger.log(` TOTAL: ${String(total).padStart(2)}/${maxTotal}`);
216
+ logger.log(` Grade: ${grade}`);
217
+ logger.log('');
218
+ logger.log(' * Dimensão 4 parcial (sem LLM assessment)');
219
+
220
+ if (wins.length > 0) {
221
+ logger.log('');
222
+ logger.log(' Quick wins:');
223
+ wins.forEach((w, i) => logger.log(` ${i + 1}. ${w.action} → +${w.pts} pts`));
224
+ }
225
+
226
+ logger.log('');
227
+
228
+ // Store in runtime if available
229
+ try {
230
+ const { openRuntimeDb } = require('../runtime-store');
231
+ const { db } = openRuntimeDb(projectDir);
232
+ const now = new Date().toISOString();
233
+ const stmt = db.prepare('INSERT OR REPLACE INTO squad_scores (squad_slug, dimension, score, max_score, details_json, scored_at) VALUES (?, ?, ?, ?, ?, ?)');
234
+ stmt.run(slug, 'completude', d1.score, d1.max, JSON.stringify(d1.details), now);
235
+ stmt.run(slug, 'profundidade', d2.score, d2.max, JSON.stringify(d2.details), now);
236
+ stmt.run(slug, 'qualidade', d3.score, d3.max, JSON.stringify(d3.details), now);
237
+ stmt.run(slug, 'potencial', d4.score, d4.max, JSON.stringify(d4.details), now);
238
+ } catch { /* runtime not available */ }
239
+
240
+ return {
241
+ slug,
242
+ total,
243
+ max: maxTotal,
244
+ grade,
245
+ dimensions: { completude: d1, profundidade: d2, qualidade: d3, potencial: d4 },
246
+ quickWins: wins
247
+ };
248
+ }
249
+
250
+ module.exports = { runSquadScore, scoreCompletude, scoreProfundidade, scoreQualidadeEstrutural, scorePotencial, gradeFromScore };