wize-dev-kit 0.6.0 → 0.7.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/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ Format inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.7.0] — 2026-06-21
9
+
10
+ ### Added
11
+
12
+ - **Post-scan remediation planning (security-overlay).** Ao fim do `wize-sec-pentest`, o overlay traduz os findings em um backlog de correção pronto para `wize-create-epics-and-stories`.
13
+ - **`security-backlog.md`** gerado em `.wize/security/`: findings agrupados por tema (ex.: 97 secrets → 1 epic de rotação, não 97 stories), priorizados **P0/P1/P2** pela pior severidade do grupo, estimados S/M/L, com rastreabilidade aos findings de origem + `scope_sha256` e DoD ("re-rodar scan e confirmar finding ausente").
14
+ - Epics semeados pelo action plan do `ai-insights.json` quando presente.
15
+ - **Call-to-action** com o comando exato (`/wize-create-epics-and-stories --from .wize/security/security-backlog.md`) impresso no terminal, no `report.md` e como banner no `report.html`.
16
+ - Mantém **zero runtime próprio**: o overlay gera o backlog e imprime o comando; o usuário/agente é quem executa a skill de planejamento (o Node nunca invoca skills).
17
+
8
18
  ## [0.6.0] — 2026-06-20
9
19
 
10
20
  ### Added
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "wize-dev-kit",
4
- "version": "0.6.0",
4
+ "version": "0.7.0",
5
5
  "description": "Full-lifecycle AI-assisted development kit with Test Architect and Whiteport Design Studio embedded. Inspired by BMAD Method and WDS.",
6
6
  "keywords": [
7
7
  "ai",
@@ -0,0 +1,180 @@
1
+ 'use strict';
2
+
3
+ // backlog.js — turns classified security findings into a remediation
4
+ // backlog (epics + stories) that wize-create-epics-and-stories can consume.
5
+ //
6
+ // Design (per brief-post-scan):
7
+ // - Group findings by theme/section, NOT 1-story-per-finding (97 secrets
8
+ // => 1 "rotate secrets" story, not 97).
9
+ // - Priority P0/P1/P2 derived from the worst severity in the group.
10
+ // - Each story keeps traceability to the source findings + scope hash.
11
+ // - Seed epics from the AI action plan (ai-insights.json) when present.
12
+ // - Zero-dep, file-first. The overlay never invokes a skill — it prints
13
+ // a clear CTA command for the user/agent to run.
14
+
15
+ const CTA_COMMAND = '/wize-create-epics-and-stories --from .wize/security/security-backlog.md';
16
+
17
+ // Severity -> remediation priority.
18
+ function priorityFor(severity) {
19
+ switch (severity) {
20
+ case 'Critical':
21
+ case 'High':
22
+ return 'P0';
23
+ case 'Medium':
24
+ return 'P1';
25
+ default:
26
+ return 'P2'; // Low, Info-surface, None, unknown
27
+ }
28
+ }
29
+
30
+ // Group size -> rough estimate.
31
+ function estimateFor(count) {
32
+ if (count <= 2) return 'S';
33
+ if (count <= 10) return 'M';
34
+ return 'L';
35
+ }
36
+
37
+ // Human label + remediation intent per section.
38
+ const SECTION_THEME = {
39
+ secrets: { theme: 'Rotacionar e remover segredos expostos', owasp: 'A07:2021' },
40
+ deps: { theme: 'Atualizar dependências vulneráveis', owasp: 'A06:2021' },
41
+ nuclei: { theme: 'Corrigir vulnerabilidades detectadas (nuclei)', owasp: null },
42
+ nikto: { theme: 'Hardening de servidor web (nikto)', owasp: 'A05:2021' },
43
+ sqlmap: { theme: 'Corrigir injeção SQL', owasp: 'A03:2021' },
44
+ ffuf: { theme: 'Revisar endpoints/arquivos descobertos', owasp: 'A05:2021' },
45
+ tech: { theme: 'Hardening: ocultar fingerprinting de versões', owasp: 'A05:2021' },
46
+ open_ports: { theme: 'Revisar exposição de portas/serviços', owasp: 'A05:2021' },
47
+ surface: { theme: 'Revisar superfície HTTP exposta', owasp: 'A05:2021' }
48
+ };
49
+
50
+ // Severity rank for "worst in group".
51
+ const SEV_RANK = { Critical: 5, High: 4, Medium: 3, Low: 2, 'Info-surface': 1, None: 0, unknown: 0 };
52
+
53
+ // groupFindings(findings) -> [{ section, theme, owasp, count, priority,
54
+ // worstSeverity, findings: [...] }] sorted by priority then count.
55
+ function groupFindings(findings) {
56
+ const bySection = {};
57
+ for (const f of findings || []) {
58
+ const s = f.section || 'unknown';
59
+ if (!bySection[s]) bySection[s] = [];
60
+ bySection[s].push(f);
61
+ }
62
+ const groups = [];
63
+ for (const [section, fs] of Object.entries(bySection)) {
64
+ let worst = 'unknown';
65
+ for (const f of fs) {
66
+ if ((SEV_RANK[f.severity] || 0) > (SEV_RANK[worst] || 0)) worst = f.severity;
67
+ }
68
+ const theme = (SECTION_THEME[section] && SECTION_THEME[section].theme) || `Revisar findings de ${section}`;
69
+ const owasp = SECTION_THEME[section] && SECTION_THEME[section].owasp;
70
+ groups.push({
71
+ section,
72
+ theme,
73
+ owasp,
74
+ count: fs.length,
75
+ worstSeverity: worst,
76
+ priority: priorityFor(worst),
77
+ findings: fs
78
+ });
79
+ }
80
+ // Order: P0 first, then by count desc.
81
+ const pOrder = { P0: 0, P1: 1, P2: 2 };
82
+ groups.sort((a, b) => (pOrder[a.priority] - pOrder[b.priority]) || (b.count - a.count));
83
+ return groups;
84
+ }
85
+
86
+ function escapeMd(s) {
87
+ return String(s == null ? '' : s);
88
+ }
89
+
90
+ // buildBacklog({ findings, actionPlan, scopeSha, generatedAt }) -> markdown.
91
+ function buildBacklog({ findings = [], actionPlan = [], scopeSha = '', generatedAt = '' } = {}) {
92
+ const groups = groupFindings(findings);
93
+ const lines = [];
94
+
95
+ lines.push('---');
96
+ lines.push('kind: security-remediation-backlog');
97
+ lines.push('owner: red-teamer');
98
+ lines.push(`source_scope_sha256: ${escapeMd(scopeSha)}`);
99
+ lines.push(`generated_at: ${escapeMd(generatedAt)}`);
100
+ lines.push('consumed_by: wize-create-epics-and-stories');
101
+ lines.push('---');
102
+ lines.push('');
103
+ lines.push('# Security Remediation Backlog');
104
+ lines.push('');
105
+ lines.push('Backlog de correção derivado do scan de segurança. Cada epic agrupa findings por tema; cada story rastreia os findings de origem. Prioridade vem da severidade (P0 = Critical/High, P1 = Medium, P2 = Low/informativo).');
106
+ lines.push('');
107
+ lines.push(`> **Próximo passo:** rode \`${CTA_COMMAND}\` para transformar este backlog em stories formais.`);
108
+ lines.push('');
109
+
110
+ // Action plan summary (from AI insights) — the executive framing.
111
+ if (actionPlan && actionPlan.length) {
112
+ lines.push('## Plano de ação (resumo)');
113
+ lines.push('');
114
+ for (const a of actionPlan) {
115
+ lines.push(`- **[${escapeMd(a.priority)}] ${escapeMd(a.title)}** — ${escapeMd(a.detail)}`);
116
+ }
117
+ lines.push('');
118
+ }
119
+
120
+ // Build a lookup from action plan title keywords to attach detail to epics.
121
+ const planByTheme = {};
122
+ for (const a of (actionPlan || [])) planByTheme[(a.title || '').toLowerCase()] = a;
123
+
124
+ const actionable = groups.filter(g => g.worstSeverity !== 'Info-surface' || g.priority !== 'P2' ? true : true);
125
+
126
+ if (groups.length === 0) {
127
+ lines.push('## (sem itens)');
128
+ lines.push('');
129
+ lines.push('_Nenhum finding acionável neste scan — no actionable findings._');
130
+ return lines.join('\n');
131
+ }
132
+
133
+ // One epic per group.
134
+ let epicN = 0;
135
+ for (const g of groups) {
136
+ epicN++;
137
+ const est = estimateFor(g.count);
138
+ // Find a matching action-plan detail by theme keyword overlap.
139
+ let planDetail = '';
140
+ for (const [title, a] of Object.entries(planByTheme)) {
141
+ const key = g.section;
142
+ if (title.includes(key) || (key === 'secrets' && title.includes('segredo')) ||
143
+ (key === 'deps' && (title.includes('depend') || title.includes('dep'))) ||
144
+ (key === 'tech' && title.includes('fingerprint'))) {
145
+ planDetail = a.detail; break;
146
+ }
147
+ }
148
+ lines.push(`## Epic ${String(epicN).padStart(2, '0')}: ${g.theme} [${g.priority}]`);
149
+ lines.push('');
150
+ lines.push(`- **Prioridade:** ${g.priority} (pior severidade: ${g.worstSeverity})`);
151
+ lines.push(`- **Findings cobertos:** ${g.count}`);
152
+ if (g.owasp) lines.push(`- **OWASP:** ${g.owasp}`);
153
+ lines.push(`- **Estimativa:** ${est}`);
154
+ if (planDetail) lines.push(`- **Como corrigir:** ${planDetail}`);
155
+ lines.push('');
156
+ lines.push('### Stories');
157
+ lines.push('');
158
+ // For large groups, a single remediation story + a verification story.
159
+ lines.push(`- **${g.theme}** (${g.priority}, est ${est}) — corrigir os ${g.count} finding(s) de \`${g.section}\`. _Origem: ${g.section} (${g.count} findings, ${g.worstSeverity})._`);
160
+ lines.push(`- **Verificar correção de ${g.section}** (${g.priority}, est S) — re-rodar \`/wize-sec-pentest\` e confirmar que os findings de \`${g.section}\` sumiram (DoD).`);
161
+ lines.push('');
162
+ // Sample of source findings for traceability (cap at 5).
163
+ const sample = g.findings.slice(0, 5);
164
+ lines.push('<details><summary>Findings de origem (amostra)</summary>');
165
+ lines.push('');
166
+ for (const f of sample) {
167
+ lines.push(`- ${escapeMd(f.raw)}`);
168
+ }
169
+ if (g.findings.length > sample.length) {
170
+ lines.push(`- _… e mais ${g.findings.length - sample.length}._`);
171
+ }
172
+ lines.push('');
173
+ lines.push('</details>');
174
+ lines.push('');
175
+ }
176
+
177
+ return lines.join('\n');
178
+ }
179
+
180
+ module.exports = { buildBacklog, priorityFor, estimateFor, groupFindings, CTA_COMMAND };
@@ -147,6 +147,16 @@ async function main() {
147
147
  if (result.skipped.length) {
148
148
  console.log(`! skipped: ${result.skipped.join(', ')}`);
149
149
  }
150
+ // Next-step CTA: turn the findings into a remediation sprint. The overlay
151
+ // never invokes a skill itself (zero runtime própio) — it prints the exact
152
+ // command for the user/agent to run.
153
+ try {
154
+ const { CTA_COMMAND } = require('../../../_shared/backlog.js');
155
+ console.log('');
156
+ console.log('→ Backlog de correção gerado em .wize/security/security-backlog.md');
157
+ console.log('→ Próximo passo — planejar a sprint de correção, rode:');
158
+ console.log(` ${CTA_COMMAND}`);
159
+ } catch (_) { /* backlog module optional */ }
150
160
  process.exit(result.ok ? 0 : 1);
151
161
  }
152
162
 
@@ -470,15 +470,36 @@ function renderReport({ securityDir } = {}) {
470
470
  lines.push('');
471
471
  }
472
472
 
473
+ // Next step CTA — turn findings into a remediation sprint.
474
+ {
475
+ const { CTA_COMMAND } = require('../../../_shared/backlog.js');
476
+ lines.push('## Próximo passo — planejar a correção');
477
+ lines.push('');
478
+ lines.push('Um backlog de remediação foi gerado em `.wize/security/security-backlog.md` (epics/stories priorizados a partir destes findings).');
479
+ lines.push('');
480
+ lines.push('Para transformá-lo em uma sprint formal, rode:');
481
+ lines.push('');
482
+ lines.push('```');
483
+ lines.push(CTA_COMMAND);
484
+ lines.push('```');
485
+ lines.push('');
486
+ }
487
+
473
488
  // Final newline + deterministic timestamp is the only line that
474
489
  // changes between runs in tests; the rest of the report is stable.
475
490
  const reportPath = path.join(sec, 'report.md');
476
491
  fs.writeFileSync(reportPath, lines.join('\n'), 'utf8');
477
492
 
493
+ // Generate the remediation backlog (consumable by
494
+ // wize-create-epics-and-stories) from the same findings + AI action plan.
495
+ const { buildBacklog, CTA_COMMAND } = require('../../../_shared/backlog.js');
496
+ const backlogMd = buildBacklog({ findings: allFindings, actionPlan, scopeSha, generatedAt });
497
+ fs.writeFileSync(path.join(sec, 'security-backlog.md'), backlogMd, 'utf8');
498
+
478
499
  // Also generate the HTML report (self-contained, no remote refs).
479
- renderReportHtml({ securityDir: sec, phaseSummaries, allFindings, refusals, generatedAt, scopeSha, risk, coverage, briefing, actionPlan });
500
+ renderReportHtml({ securityDir: sec, phaseSummaries, allFindings, refusals, generatedAt, scopeSha, risk, coverage, briefing, actionPlan, ctaCommand: CTA_COMMAND });
480
501
 
481
- return { ok: true, findings: allFindings.length };
502
+ return { ok: true, findings: allFindings.length, backlog: 'security-backlog.md', cta: CTA_COMMAND };
482
503
  }
483
504
 
484
505
  // --- HTML report -------------------------------------------------------
@@ -744,10 +765,17 @@ footer.site code { background: var(--code-bg); padding: 1px 6px; border-radius:
744
765
  .card .rec { margin: .75rem 0 0; padding: .6rem .8rem; background: var(--owasp-bg); border-radius: var(--radius-sm); font-size: .85rem; color: var(--fg); }
745
766
  .card .rec strong { color: var(--owasp); }
746
767
 
768
+ /* Call to action — next-step sprint planning */
769
+ .cta { background: var(--bg-elev); border: 1px solid var(--accent); border-radius: var(--radius); padding: 1rem 1.25rem; }
770
+ .cta h2 { margin: 0 0 .5rem; font-size: 1.05rem; color: var(--accent); }
771
+ .cta p { margin: 0 0 .5rem; font-size: .92rem; }
772
+ .cta .cta-cmd { background: var(--code-bg); border: 1px dashed var(--accent); border-radius: var(--radius-sm); padding: .75rem 1rem; margin: 0; overflow-x: auto; }
773
+ .cta .cta-cmd code { font-family: ui-monospace, "SF Mono", Menlo, monospace; color: var(--fg); user-select: all; }
774
+
747
775
  @media print {
748
776
  header.site { position: static; }
749
777
  .skip-link, .filters { display: none; }
750
- .card, .risk-banner, .briefing, .action-plan .plan li { break-inside: avoid; box-shadow: none; border-color: #888; }
778
+ .card, .risk-banner, .briefing, .action-plan .plan li, .cta { break-inside: avoid; box-shadow: none; border-color: #888; }
751
779
  }
752
780
  `;
753
781
 
@@ -760,13 +788,14 @@ function escapeHtml(s) {
760
788
  .replace(/'/g, '&#39;');
761
789
  }
762
790
 
763
- function renderReportHtml({ securityDir, phaseSummaries, allFindings, refusals, generatedAt, scopeSha, risk, coverage, briefing, actionPlan }) {
791
+ function renderReportHtml({ securityDir, phaseSummaries, allFindings, refusals, generatedAt, scopeSha, risk, coverage, briefing, actionPlan, ctaCommand }) {
764
792
  const sec = securityDir;
765
793
  const title = `Security Report — ${scopeSha ? scopeSha.slice(0, 12) : 'unknown'}`;
766
794
  risk = risk || computeRisk(allFindings);
767
795
  coverage = coverage || computeCoverage(loadPartial, sec);
768
796
  briefing = briefing || heuristicBriefing(risk, coverage, allFindings);
769
797
  actionPlan = (actionPlan && actionPlan.length) ? actionPlan : heuristicActionPlan(allFindings, coverage);
798
+ ctaCommand = ctaCommand || require('../../../_shared/backlog.js').CTA_COMMAND;
770
799
 
771
800
  // Severity counts (for sticky header). 'Info-surface' is folded into a
772
801
  // dedicated "surface" bucket so the stakeholder header isn't dominated by
@@ -949,6 +978,11 @@ ${actionPlan.map(a => ` <li class="prio-${escapeHtml((a.priority||'').toLow
949
978
  ` <div class="findings">${findingsHtml}</div>`,
950
979
  ` </section>`,
951
980
  refusalsHtml,
981
+ ` <section class="cta" aria-labelledby="cta-h">
982
+ <h2 id="cta-h">Próximo passo — planejar a correção</h2>
983
+ <p>Um backlog de remediação foi gerado em <code>.wize/security/security-backlog.md</code> (epics/stories priorizados a partir destes findings). Para transformá-lo em uma sprint formal, rode:</p>
984
+ <pre class="cta-cmd"><code>${escapeHtml(ctaCommand)}</code></pre>
985
+ </section>`,
952
986
  `</main>`,
953
987
  `<footer class="site" role="contentinfo">`,
954
988
  ` <p>Gerado por <code>wize-sec-report</code> (overlay <code>security-overlay</code>) · Self-contained (sem refs remotas) · CSS inline · Default: dark mode.</p>`,