@jaimevalasek/aioson 1.3.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 +456 -0
- package/CODE_OF_CONDUCT.md +12 -0
- package/CONTRIBUTING.md +13 -0
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/bin/aioson.js +4 -0
- package/docs/en/cli-reference.md +398 -0
- package/docs/en/i18n.md +52 -0
- package/docs/en/json-schemas.md +41 -0
- package/docs/en/mcp.md +56 -0
- package/docs/en/parallel.md +82 -0
- package/docs/en/qa-browser.md +339 -0
- package/docs/en/release-flow.md +22 -0
- package/docs/en/release-notes-template.md +41 -0
- package/docs/en/release.md +28 -0
- package/docs/en/schemas/agent-prompt.schema.json +17 -0
- package/docs/en/schemas/agents.schema.json +32 -0
- package/docs/en/schemas/context-validate.schema.json +36 -0
- package/docs/en/schemas/doctor.schema.json +89 -0
- package/docs/en/schemas/error.schema.json +24 -0
- package/docs/en/schemas/i18n-add.schema.json +15 -0
- package/docs/en/schemas/index.json +116 -0
- package/docs/en/schemas/info.schema.json +39 -0
- package/docs/en/schemas/init.schema.json +48 -0
- package/docs/en/schemas/install.schema.json +60 -0
- package/docs/en/schemas/locale-apply.schema.json +30 -0
- package/docs/en/schemas/mcp-doctor.schema.json +95 -0
- package/docs/en/schemas/mcp-init.schema.json +122 -0
- package/docs/en/schemas/package-test.schema.json +24 -0
- package/docs/en/schemas/parallel-assign.schema.json +57 -0
- package/docs/en/schemas/parallel-doctor.schema.json +86 -0
- package/docs/en/schemas/parallel-init.schema.json +53 -0
- package/docs/en/schemas/parallel-status.schema.json +94 -0
- package/docs/en/schemas/setup-context.schema.json +39 -0
- package/docs/en/schemas/smoke.schema.json +23 -0
- package/docs/en/schemas/update.schema.json +48 -0
- package/docs/en/schemas/workflow-plan.schema.json +30 -0
- package/docs/en/web3.md +54 -0
- package/docs/pt/README.md +46 -0
- package/docs/pt/advisor-spec.md +335 -0
- package/docs/pt/agentes.md +453 -0
- package/docs/pt/cenarios.md +1230 -0
- package/docs/pt/clientes-ai.md +224 -0
- package/docs/pt/comandos-cli.md +511 -0
- package/docs/pt/genome-3.0-spec.md +296 -0
- package/docs/pt/guia-engineer.md +226 -0
- package/docs/pt/inicio-rapido.md +138 -0
- package/docs/pt/profiler-system.md +214 -0
- package/docs/pt/runtime-observability.md +72 -0
- package/docs/pt/squad-genoma.md +777 -0
- package/docs/pt/web3.md +797 -0
- package/docs/testing/genome-2.0-manual-regression.md +23 -0
- package/docs/testing/genome-2.0-matrix.md +36 -0
- package/docs/testing/genome-2.0-rollout.md +184 -0
- package/package.json +50 -0
- package/src/agents.js +56 -0
- package/src/cli.js +497 -0
- package/src/commands/agents.js +142 -0
- package/src/commands/cloud.js +1767 -0
- package/src/commands/config.js +90 -0
- package/src/commands/context-validate.js +91 -0
- package/src/commands/doctor.js +123 -0
- package/src/commands/genome-doctor.js +41 -0
- package/src/commands/genome-migrate.js +49 -0
- package/src/commands/i18n-add.js +56 -0
- package/src/commands/info.js +41 -0
- package/src/commands/init.js +75 -0
- package/src/commands/install.js +68 -0
- package/src/commands/locale-apply.js +51 -0
- package/src/commands/locale-diff.js +126 -0
- package/src/commands/mcp-doctor.js +406 -0
- package/src/commands/mcp-init.js +379 -0
- package/src/commands/package-e2e.js +273 -0
- package/src/commands/parallel-assign.js +403 -0
- package/src/commands/parallel-doctor.js +437 -0
- package/src/commands/parallel-init.js +249 -0
- package/src/commands/parallel-status.js +290 -0
- package/src/commands/qa-doctor.js +185 -0
- package/src/commands/qa-init.js +161 -0
- package/src/commands/qa-report.js +58 -0
- package/src/commands/qa-run.js +873 -0
- package/src/commands/qa-scan.js +337 -0
- package/src/commands/runtime.js +948 -0
- package/src/commands/scan-project.js +1107 -0
- package/src/commands/setup-context.js +650 -0
- package/src/commands/smoke.js +426 -0
- package/src/commands/squad-doctor.js +358 -0
- package/src/commands/squad-export.js +46 -0
- package/src/commands/squad-pipeline.js +97 -0
- package/src/commands/squad-repair-genomes.js +39 -0
- package/src/commands/squad-status.js +424 -0
- package/src/commands/squad-validate.js +230 -0
- package/src/commands/test-agents.js +194 -0
- package/src/commands/update.js +55 -0
- package/src/commands/workflow-next.js +594 -0
- package/src/commands/workflow-plan.js +108 -0
- package/src/constants.js +314 -0
- package/src/context-parse-reason.js +22 -0
- package/src/context-writer.js +150 -0
- package/src/context.js +217 -0
- package/src/detector.js +261 -0
- package/src/doctor.js +289 -0
- package/src/execution-gateway.js +461 -0
- package/src/genome-files.js +198 -0
- package/src/genome-format.js +442 -0
- package/src/genome-schema.js +215 -0
- package/src/genomes/bindings.js +281 -0
- package/src/genomes.js +467 -0
- package/src/i18n/index.js +103 -0
- package/src/i18n/messages/en.js +784 -0
- package/src/i18n/messages/es.js +718 -0
- package/src/i18n/messages/fr.js +725 -0
- package/src/i18n/messages/pt-BR.js +818 -0
- package/src/i18n/scaffold.js +64 -0
- package/src/installer.js +232 -0
- package/src/lib/genomes/compat.js +206 -0
- package/src/lib/genomes/migrate.js +90 -0
- package/src/lib/squads/genome-repair.js +49 -0
- package/src/locales.js +84 -0
- package/src/onboarding.js +305 -0
- package/src/parser.js +53 -0
- package/src/prompt-tool.js +20 -0
- package/src/qa-html-report.js +472 -0
- package/src/runtime-store.js +1527 -0
- package/src/squads/apply-genome.js +21 -0
- package/src/squads/genome-binding-service.js +154 -0
- package/src/updater.js +32 -0
- package/src/utils.js +46 -0
- package/src/version.js +50 -0
- package/template/.aioson/advisors/.gitkeep +1 -0
- package/template/.aioson/agents/analyst.md +225 -0
- package/template/.aioson/agents/architect.md +221 -0
- package/template/.aioson/agents/dev.md +201 -0
- package/template/.aioson/agents/discovery-design-doc.md +196 -0
- package/template/.aioson/agents/genoma.md +300 -0
- package/template/.aioson/agents/orchestrator.md +107 -0
- package/template/.aioson/agents/pm.md +89 -0
- package/template/.aioson/agents/product.md +361 -0
- package/template/.aioson/agents/profiler-enricher.md +266 -0
- package/template/.aioson/agents/profiler-forge.md +188 -0
- package/template/.aioson/agents/profiler-researcher.md +245 -0
- package/template/.aioson/agents/qa.md +344 -0
- package/template/.aioson/agents/setup.md +381 -0
- package/template/.aioson/agents/squad.md +837 -0
- package/template/.aioson/agents/ux-ui.md +416 -0
- package/template/.aioson/config.md +56 -0
- package/template/.aioson/context/.gitkeep +0 -0
- package/template/.aioson/context/parallel/.gitkeep +0 -0
- package/template/.aioson/context/spec.md.template +37 -0
- package/template/.aioson/genomas/.gitkeep +0 -0
- package/template/.aioson/locales/en/agents/analyst.md +214 -0
- package/template/.aioson/locales/en/agents/architect.md +210 -0
- package/template/.aioson/locales/en/agents/dev.md +187 -0
- package/template/.aioson/locales/en/agents/discovery-design-doc.md +27 -0
- package/template/.aioson/locales/en/agents/genoma.md +212 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +105 -0
- package/template/.aioson/locales/en/agents/pm.md +77 -0
- package/template/.aioson/locales/en/agents/product.md +310 -0
- package/template/.aioson/locales/en/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/en/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/en/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/en/agents/qa.md +214 -0
- package/template/.aioson/locales/en/agents/setup.md +342 -0
- package/template/.aioson/locales/en/agents/squad.md +247 -0
- package/template/.aioson/locales/en/agents/ux-ui.md +320 -0
- package/template/.aioson/locales/es/agents/analyst.md +203 -0
- package/template/.aioson/locales/es/agents/architect.md +208 -0
- package/template/.aioson/locales/es/agents/dev.md +183 -0
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +19 -0
- package/template/.aioson/locales/es/agents/genoma.md +102 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/es/agents/pm.md +81 -0
- package/template/.aioson/locales/es/agents/product.md +310 -0
- package/template/.aioson/locales/es/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/es/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/es/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/es/agents/qa.md +163 -0
- package/template/.aioson/locales/es/agents/setup.md +347 -0
- package/template/.aioson/locales/es/agents/squad.md +247 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +201 -0
- package/template/.aioson/locales/fr/agents/analyst.md +203 -0
- package/template/.aioson/locales/fr/agents/architect.md +208 -0
- package/template/.aioson/locales/fr/agents/dev.md +183 -0
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +19 -0
- package/template/.aioson/locales/fr/agents/genoma.md +102 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/fr/agents/pm.md +81 -0
- package/template/.aioson/locales/fr/agents/product.md +310 -0
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/fr/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/fr/agents/qa.md +163 -0
- package/template/.aioson/locales/fr/agents/setup.md +347 -0
- package/template/.aioson/locales/fr/agents/squad.md +247 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +201 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +217 -0
- package/template/.aioson/locales/pt-BR/agents/architect.md +213 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +198 -0
- package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +198 -0
- package/template/.aioson/locales/pt-BR/agents/genoma.md +297 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/pt-BR/agents/pm.md +81 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +316 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/qa.md +217 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +371 -0
- package/template/.aioson/locales/pt-BR/agents/squad.md +772 -0
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +322 -0
- package/template/.aioson/mcp/servers.md +24 -0
- package/template/.aioson/profiler-reports/.gitkeep +1 -0
- package/template/.aioson/schemas/content-blueprint.schema.json +30 -0
- package/template/.aioson/schemas/genome-meta.schema.json +150 -0
- package/template/.aioson/schemas/genome.schema.json +115 -0
- package/template/.aioson/schemas/readiness.schema.json +27 -0
- package/template/.aioson/schemas/squad-blueprint.schema.json +172 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +276 -0
- package/template/.aioson/skills/dynamic/README.md +30 -0
- package/template/.aioson/skills/dynamic/cardano-docs.md +16 -0
- package/template/.aioson/skills/dynamic/ethereum-docs.md +17 -0
- package/template/.aioson/skills/dynamic/flux-ui-docs.md +13 -0
- package/template/.aioson/skills/dynamic/laravel-docs.md +41 -0
- package/template/.aioson/skills/dynamic/npm-packages.md +16 -0
- package/template/.aioson/skills/dynamic/solana-docs.md +16 -0
- package/template/.aioson/skills/references/premium-command-center-ui/master-application-prompt.md +79 -0
- package/template/.aioson/skills/references/premium-command-center-ui/operational-ux-playbook.md +253 -0
- package/template/.aioson/skills/references/premium-command-center-ui/quality-validation-checklist.md +82 -0
- package/template/.aioson/skills/references/premium-command-center-ui/visual-system-and-component-patterns.md +270 -0
- package/template/.aioson/skills/static/django-patterns.md +342 -0
- package/template/.aioson/skills/static/fastapi-patterns.md +344 -0
- package/template/.aioson/skills/static/filament-patterns.md +267 -0
- package/template/.aioson/skills/static/flux-ui-components.md +262 -0
- package/template/.aioson/skills/static/git-conventions.md +227 -0
- package/template/.aioson/skills/static/interface-design.md +372 -0
- package/template/.aioson/skills/static/jetstream-setup.md +200 -0
- package/template/.aioson/skills/static/laravel-conventions.md +491 -0
- package/template/.aioson/skills/static/nextjs-patterns.md +321 -0
- package/template/.aioson/skills/static/node-express-patterns.md +317 -0
- package/template/.aioson/skills/static/node-typescript-patterns.md +282 -0
- package/template/.aioson/skills/static/premium-command-center-ui.md +190 -0
- package/template/.aioson/skills/static/rails-conventions.md +307 -0
- package/template/.aioson/skills/static/react-motion-patterns.md +577 -0
- package/template/.aioson/skills/static/static-html-patterns.md +1935 -0
- package/template/.aioson/skills/static/tall-stack-patterns.md +286 -0
- package/template/.aioson/skills/static/ui-ux-modern.md +75 -0
- package/template/.aioson/skills/static/web3-cardano-patterns.md +337 -0
- package/template/.aioson/skills/static/web3-ethereum-patterns.md +310 -0
- package/template/.aioson/skills/static/web3-security-checklist.md +284 -0
- package/template/.aioson/skills/static/web3-solana-patterns.md +324 -0
- package/template/.aioson/squads/.artisan/.gitkeep +0 -0
- package/template/.aioson/squads/.gitkeep +0 -0
- package/template/.aioson/squads/memory.md +5 -0
- package/template/.aioson/tasks/squad-analyze.md +83 -0
- package/template/.aioson/tasks/squad-create.md +99 -0
- package/template/.aioson/tasks/squad-design.md +100 -0
- package/template/.aioson/tasks/squad-export.md +20 -0
- package/template/.aioson/tasks/squad-extend.md +68 -0
- package/template/.aioson/tasks/squad-pipeline.md +122 -0
- package/template/.aioson/tasks/squad-repair.md +85 -0
- package/template/.aioson/tasks/squad-validate.md +58 -0
- package/template/.aioson/templates/squads/content-basic/template.json +21 -0
- package/template/.aioson/templates/squads/media-channel/template.json +24 -0
- package/template/.aioson/templates/squads/research-analysis/template.json +22 -0
- package/template/.aioson/templates/squads/software-delivery/template.json +21 -0
- package/template/.claude/commands/aioson/analyst.md +5 -0
- package/template/.claude/commands/aioson/architect.md +5 -0
- package/template/.claude/commands/aioson/dev.md +5 -0
- package/template/.claude/commands/aioson/orchestrator.md +5 -0
- package/template/.claude/commands/aioson/pm.md +5 -0
- package/template/.claude/commands/aioson/qa.md +5 -0
- package/template/.claude/commands/aioson/setup.md +5 -0
- package/template/.claude/commands/aioson/ux-ui.md +5 -0
- package/template/.gemini/GEMINI.md +10 -0
- package/template/.gemini/commands/aios-analyst.toml +4 -0
- package/template/.gemini/commands/aios-architect.toml +7 -0
- package/template/.gemini/commands/aios-dev.toml +8 -0
- package/template/.gemini/commands/aios-discovery-design-doc.toml +4 -0
- package/template/.gemini/commands/aios-orchestrator.toml +8 -0
- package/template/.gemini/commands/aios-pm.toml +8 -0
- package/template/.gemini/commands/aios-product.toml +4 -0
- package/template/.gemini/commands/aios-qa.toml +6 -0
- package/template/.gemini/commands/aios-setup.toml +3 -0
- package/template/.gemini/commands/aios-ux-ui.toml +8 -0
- package/template/AGENTS.md +67 -0
- package/template/CLAUDE.md +31 -0
- package/template/OPENCODE.md +24 -0
- package/template/aioson-models.json +40 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { exists } = require('../utils');
|
|
6
|
+
const { recordRuntimeOperation } = require('../execution-gateway');
|
|
7
|
+
|
|
8
|
+
const KNOWN_STATUSES = ['pending', 'in_progress', 'completed', 'blocked'];
|
|
9
|
+
|
|
10
|
+
function parseLaneIndex(fileName) {
|
|
11
|
+
const match = String(fileName || '').match(/^agent-(\d+)\.status\.md$/);
|
|
12
|
+
if (!match) return null;
|
|
13
|
+
const value = Number(match[1]);
|
|
14
|
+
if (!Number.isFinite(value) || value <= 0) return null;
|
|
15
|
+
return Math.floor(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function extractMetadata(content, key, fallback = '') {
|
|
19
|
+
const regex = new RegExp(`^-\\s*${key}:\\s*(.*)$`, 'im');
|
|
20
|
+
const match = String(content || '').match(regex);
|
|
21
|
+
if (!match) return fallback;
|
|
22
|
+
return String(match[1] || '').trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function extractSectionLines(content, title) {
|
|
26
|
+
const lines = String(content || '').split(/\r?\n/);
|
|
27
|
+
const start = lines.findIndex((line) => line.trim() === `## ${title}`);
|
|
28
|
+
if (start === -1) return [];
|
|
29
|
+
|
|
30
|
+
const output = [];
|
|
31
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
32
|
+
const line = lines[i];
|
|
33
|
+
if (line.startsWith('## ')) break;
|
|
34
|
+
output.push(line);
|
|
35
|
+
}
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function extractSectionBullets(content, title) {
|
|
40
|
+
const lines = extractSectionLines(content, title);
|
|
41
|
+
const items = [];
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
const match = line.match(/^\s*-\s+(.*)$/);
|
|
44
|
+
if (!match) continue;
|
|
45
|
+
items.push(String(match[1] || '').trim());
|
|
46
|
+
}
|
|
47
|
+
return items;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseDeliverables(content) {
|
|
51
|
+
const lines = extractSectionLines(content, 'Deliverables');
|
|
52
|
+
let total = 0;
|
|
53
|
+
let completed = 0;
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
const match = line.match(/^\s*-\s+\[([ xX])\]\s+(.+)$/);
|
|
56
|
+
if (!match) continue;
|
|
57
|
+
total += 1;
|
|
58
|
+
if (match[1].toLowerCase() === 'x') completed += 1;
|
|
59
|
+
}
|
|
60
|
+
return { completed, total };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeStatus(value) {
|
|
64
|
+
const normalized = String(value || '')
|
|
65
|
+
.trim()
|
|
66
|
+
.toLowerCase();
|
|
67
|
+
if (KNOWN_STATUSES.includes(normalized)) return normalized;
|
|
68
|
+
return normalized || 'other';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sanitizeScopeItems(items) {
|
|
72
|
+
return (items || []).filter((item) => {
|
|
73
|
+
const value = String(item || '').trim().toLowerCase();
|
|
74
|
+
if (!value) return false;
|
|
75
|
+
if (value === '[define module or feature boundary]') return false;
|
|
76
|
+
if (value === '[unassigned]') return false;
|
|
77
|
+
return true;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function sanitizeBlockerItems(items) {
|
|
82
|
+
return (items || []).filter((item) => {
|
|
83
|
+
const value = String(item || '').trim().toLowerCase();
|
|
84
|
+
if (!value) return false;
|
|
85
|
+
if (value === '[none]') return false;
|
|
86
|
+
return true;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function countDecisionRows(content) {
|
|
91
|
+
const lines = String(content || '').split(/\r?\n/);
|
|
92
|
+
return lines.filter((line) => {
|
|
93
|
+
const trimmed = line.trim();
|
|
94
|
+
if (!trimmed.startsWith('|')) return false;
|
|
95
|
+
if (trimmed.includes('| time | decision | rationale | impact |')) return false;
|
|
96
|
+
if (/^\|\-+\|/.test(trimmed)) return false;
|
|
97
|
+
return true;
|
|
98
|
+
}).length;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function createStatusCounts() {
|
|
102
|
+
return {
|
|
103
|
+
pending: 0,
|
|
104
|
+
in_progress: 0,
|
|
105
|
+
completed: 0,
|
|
106
|
+
blocked: 0,
|
|
107
|
+
other: 0
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function formatStatusLabel(status, t) {
|
|
112
|
+
const normalized = String(status || '')
|
|
113
|
+
.trim()
|
|
114
|
+
.toLowerCase();
|
|
115
|
+
if (normalized === 'pending') return t('parallel_status.status_pending');
|
|
116
|
+
if (normalized === 'in_progress') return t('parallel_status.status_in_progress');
|
|
117
|
+
if (normalized === 'completed') return t('parallel_status.status_completed');
|
|
118
|
+
if (normalized === 'blocked') return t('parallel_status.status_blocked');
|
|
119
|
+
return t('parallel_status.status_other');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function parseLaneFile(parallelDir, index) {
|
|
123
|
+
const fileName = `agent-${index}.status.md`;
|
|
124
|
+
const absPath = path.join(parallelDir, fileName);
|
|
125
|
+
const content = await fs.readFile(absPath, 'utf8');
|
|
126
|
+
const status = normalizeStatus(extractMetadata(content, 'status', 'pending'));
|
|
127
|
+
const owner = extractMetadata(content, 'owner', '[unassigned]');
|
|
128
|
+
const priority = extractMetadata(content, 'priority', 'medium');
|
|
129
|
+
const updatedAt = extractMetadata(content, 'updated_at', '');
|
|
130
|
+
const scopeItems = sanitizeScopeItems(extractSectionBullets(content, 'Scope'));
|
|
131
|
+
const blockerItems = sanitizeBlockerItems(extractSectionBullets(content, 'Blockers'));
|
|
132
|
+
const deliverables = parseDeliverables(content);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
lane: index,
|
|
136
|
+
file: path.join('.aioson/context/parallel', fileName).replace(/\\/g, '/'),
|
|
137
|
+
status,
|
|
138
|
+
owner,
|
|
139
|
+
priority,
|
|
140
|
+
updatedAt,
|
|
141
|
+
scopeCount: scopeItems.length,
|
|
142
|
+
blockerCount: blockerItems.length,
|
|
143
|
+
deliverables,
|
|
144
|
+
scopeItems,
|
|
145
|
+
blockerItems
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function runParallelStatus({ args, options = {}, logger, t }) {
|
|
150
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
151
|
+
const parallelDir = path.join(targetDir, '.aioson/context/parallel');
|
|
152
|
+
|
|
153
|
+
if (!(await exists(parallelDir))) {
|
|
154
|
+
throw new Error(t('parallel_status.parallel_missing', { path: parallelDir }));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const entries = await fs.readdir(parallelDir);
|
|
158
|
+
const laneIndices = entries
|
|
159
|
+
.map(parseLaneIndex)
|
|
160
|
+
.filter((value) => value !== null)
|
|
161
|
+
.sort((a, b) => a - b);
|
|
162
|
+
|
|
163
|
+
if (laneIndices.length === 0) {
|
|
164
|
+
throw new Error(t('parallel_status.no_lanes'));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const lanes = [];
|
|
168
|
+
for (const index of laneIndices) {
|
|
169
|
+
lanes.push(await parseLaneFile(parallelDir, index));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const statusCounts = createStatusCounts();
|
|
173
|
+
let scopeCount = 0;
|
|
174
|
+
let blockerCount = 0;
|
|
175
|
+
let deliverablesCompleted = 0;
|
|
176
|
+
let deliverablesTotal = 0;
|
|
177
|
+
|
|
178
|
+
for (const lane of lanes) {
|
|
179
|
+
const key = Object.prototype.hasOwnProperty.call(statusCounts, lane.status) ? lane.status : 'other';
|
|
180
|
+
statusCounts[key] += 1;
|
|
181
|
+
scopeCount += lane.scopeCount;
|
|
182
|
+
blockerCount += lane.blockerCount;
|
|
183
|
+
deliverablesCompleted += lane.deliverables.completed;
|
|
184
|
+
deliverablesTotal += lane.deliverables.total;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const sharedPath = path.join(parallelDir, 'shared-decisions.md');
|
|
188
|
+
const sharedExists = await exists(sharedPath);
|
|
189
|
+
const sharedDecisionEntries = sharedExists
|
|
190
|
+
? countDecisionRows(await fs.readFile(sharedPath, 'utf8'))
|
|
191
|
+
: 0;
|
|
192
|
+
|
|
193
|
+
const output = {
|
|
194
|
+
ok: true,
|
|
195
|
+
targetDir,
|
|
196
|
+
parallelDir,
|
|
197
|
+
laneCount: lanes.length,
|
|
198
|
+
statusCounts,
|
|
199
|
+
scopeCount,
|
|
200
|
+
blockerCount,
|
|
201
|
+
deliverables: {
|
|
202
|
+
completed: deliverablesCompleted,
|
|
203
|
+
total: deliverablesTotal
|
|
204
|
+
},
|
|
205
|
+
sharedDecisions: {
|
|
206
|
+
exists: sharedExists,
|
|
207
|
+
entries: sharedDecisionEntries
|
|
208
|
+
},
|
|
209
|
+
lanes: lanes.map((lane) => ({
|
|
210
|
+
lane: lane.lane,
|
|
211
|
+
file: lane.file,
|
|
212
|
+
status: lane.status,
|
|
213
|
+
owner: lane.owner,
|
|
214
|
+
priority: lane.priority,
|
|
215
|
+
updatedAt: lane.updatedAt,
|
|
216
|
+
scopeCount: lane.scopeCount,
|
|
217
|
+
blockerCount: lane.blockerCount,
|
|
218
|
+
deliverables: lane.deliverables
|
|
219
|
+
}))
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
output.runtime = await recordRuntimeOperation(targetDir, {
|
|
223
|
+
agentName: 'orchestrator',
|
|
224
|
+
source: 'orchestration',
|
|
225
|
+
sessionKey: 'parallel:workspace',
|
|
226
|
+
title: 'Parallel orchestration workspace',
|
|
227
|
+
goal: 'Prepare and manage parallel development lanes',
|
|
228
|
+
runTitle: 'parallel:status',
|
|
229
|
+
message: 'Parallel status inspection started',
|
|
230
|
+
summary: `Parallel status inspected for ${output.laneCount} lanes`,
|
|
231
|
+
eventType: 'parallel.status_reported',
|
|
232
|
+
phase: 'parallel',
|
|
233
|
+
payload: {
|
|
234
|
+
command: 'parallel:status',
|
|
235
|
+
laneCount: output.laneCount,
|
|
236
|
+
statusCounts,
|
|
237
|
+
scopeCount,
|
|
238
|
+
blockerCount,
|
|
239
|
+
deliverables: output.deliverables,
|
|
240
|
+
sharedDecisions: output.sharedDecisions
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (options.json) {
|
|
245
|
+
return output;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
logger.log(t('parallel_status.title', { path: targetDir }));
|
|
249
|
+
logger.log(t('parallel_status.lanes_count', { count: output.laneCount }));
|
|
250
|
+
logger.log(t('parallel_status.statuses_title'));
|
|
251
|
+
for (const key of Object.keys(statusCounts)) {
|
|
252
|
+
logger.log(
|
|
253
|
+
t('parallel_status.status_line', {
|
|
254
|
+
status: formatStatusLabel(key, t),
|
|
255
|
+
count: statusCounts[key]
|
|
256
|
+
})
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
logger.log(t('parallel_status.scopes_count', { count: scopeCount }));
|
|
260
|
+
logger.log(
|
|
261
|
+
t('parallel_status.deliverables_progress', {
|
|
262
|
+
completed: deliverablesCompleted,
|
|
263
|
+
total: deliverablesTotal
|
|
264
|
+
})
|
|
265
|
+
);
|
|
266
|
+
logger.log(t('parallel_status.blockers_count', { count: blockerCount }));
|
|
267
|
+
logger.log(
|
|
268
|
+
t('parallel_status.shared_decisions', {
|
|
269
|
+
count: sharedDecisionEntries
|
|
270
|
+
})
|
|
271
|
+
);
|
|
272
|
+
for (const lane of output.lanes) {
|
|
273
|
+
logger.log(
|
|
274
|
+
t('parallel_status.lane_line', {
|
|
275
|
+
lane: lane.lane,
|
|
276
|
+
status: formatStatusLabel(lane.status, t),
|
|
277
|
+
scope: lane.scopeCount,
|
|
278
|
+
blockers: lane.blockerCount
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return output;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = {
|
|
287
|
+
runParallelStatus,
|
|
288
|
+
parseLaneIndex,
|
|
289
|
+
countDecisionRows
|
|
290
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { readTextIfExists, exists } = require('../utils');
|
|
5
|
+
const { validateProjectContextFile } = require('../context');
|
|
6
|
+
|
|
7
|
+
function makeCheck(id, ok, severity, message, hint = '') {
|
|
8
|
+
return { id, ok: Boolean(ok), severity, message: String(message || ''), hint: String(hint || '') };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function summarizeChecks(checks) {
|
|
12
|
+
const passed = checks.filter((c) => c.ok).length;
|
|
13
|
+
const failed = checks.filter((c) => !c.ok && c.severity === 'error').length;
|
|
14
|
+
const warnings = checks.filter((c) => !c.ok && c.severity === 'warn').length;
|
|
15
|
+
return { total: checks.length, passed, failed, warnings };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatPrefix(check, t) {
|
|
19
|
+
if (check.ok) return t('qa_doctor.prefix_ok');
|
|
20
|
+
if (check.severity === 'warn') return t('qa_doctor.prefix_warn');
|
|
21
|
+
return t('qa_doctor.prefix_fail');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function requirePlaywright() {
|
|
25
|
+
try {
|
|
26
|
+
return require('playwright');
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function checkTargetUrl(url) {
|
|
33
|
+
if (!url) return { reachable: false, error: 'no_url' };
|
|
34
|
+
try {
|
|
35
|
+
const http = url.startsWith('https') ? require('node:https') : require('node:http');
|
|
36
|
+
await new Promise((resolve, reject) => {
|
|
37
|
+
const req = http.get(url, { timeout: 5000 }, (res) => {
|
|
38
|
+
res.destroy();
|
|
39
|
+
resolve(res.statusCode);
|
|
40
|
+
});
|
|
41
|
+
req.on('error', reject);
|
|
42
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
|
43
|
+
});
|
|
44
|
+
return { reachable: true, error: '' };
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return { reachable: false, error: err.message };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function countAcItems(prdContent) {
|
|
51
|
+
if (!prdContent) return 0;
|
|
52
|
+
const tableMatches = prdContent.matchAll(/\|\s*(AC-\d+)\s*\|/g);
|
|
53
|
+
return [...tableMatches].length;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runQaDoctor({ args, options = {}, logger, t }) {
|
|
57
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
58
|
+
const configPath = path.join(targetDir, 'aios-qa.config.json');
|
|
59
|
+
const prdPath = path.join(targetDir, '.aioson/context/prd.md');
|
|
60
|
+
const checks = [];
|
|
61
|
+
|
|
62
|
+
// Check 1 — Playwright installed
|
|
63
|
+
const pw = requirePlaywright();
|
|
64
|
+
checks.push(makeCheck(
|
|
65
|
+
'playwright.installed',
|
|
66
|
+
Boolean(pw),
|
|
67
|
+
'error',
|
|
68
|
+
pw ? t('qa_doctor.playwright_ok') : t('qa_doctor.playwright_missing'),
|
|
69
|
+
pw ? '' : t('qa_doctor.playwright_missing_hint')
|
|
70
|
+
));
|
|
71
|
+
|
|
72
|
+
// Check 2 — Chromium binary
|
|
73
|
+
if (pw) {
|
|
74
|
+
let chromiumOk = false;
|
|
75
|
+
try {
|
|
76
|
+
const execPath = pw.chromium.executablePath();
|
|
77
|
+
chromiumOk = Boolean(execPath) && await exists(execPath);
|
|
78
|
+
} catch {
|
|
79
|
+
chromiumOk = false;
|
|
80
|
+
}
|
|
81
|
+
checks.push(makeCheck(
|
|
82
|
+
'chromium.binary',
|
|
83
|
+
chromiumOk,
|
|
84
|
+
'error',
|
|
85
|
+
chromiumOk ? t('qa_doctor.chromium_ok') : t('qa_doctor.chromium_missing'),
|
|
86
|
+
chromiumOk ? '' : t('qa_doctor.chromium_missing_hint')
|
|
87
|
+
));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check 3 — Config file
|
|
91
|
+
const configExists = await exists(configPath);
|
|
92
|
+
let config = null;
|
|
93
|
+
let configParsed = false;
|
|
94
|
+
let configError = '';
|
|
95
|
+
|
|
96
|
+
if (configExists) {
|
|
97
|
+
try {
|
|
98
|
+
const raw = await require('node:fs/promises').readFile(configPath, 'utf8');
|
|
99
|
+
config = JSON.parse(raw);
|
|
100
|
+
configParsed = true;
|
|
101
|
+
} catch (err) {
|
|
102
|
+
configError = err.message;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!configExists) {
|
|
107
|
+
checks.push(makeCheck('config.exists', false, 'error', t('qa_doctor.config_missing'), t('qa_doctor.config_missing_hint')));
|
|
108
|
+
} else if (!configParsed) {
|
|
109
|
+
checks.push(makeCheck('config.parsed', false, 'error', t('qa_doctor.config_invalid', { error: configError }), t('qa_doctor.config_missing_hint')));
|
|
110
|
+
} else {
|
|
111
|
+
checks.push(makeCheck('config.exists', true, 'info', t('qa_doctor.config_ok')));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check 4 — Target URL reachable
|
|
115
|
+
const configUrl = config && config.url ? config.url : '';
|
|
116
|
+
if (!configUrl) {
|
|
117
|
+
checks.push(makeCheck('url.reachable', false, 'warn', t('qa_doctor.url_missing'), t('qa_doctor.url_missing_hint')));
|
|
118
|
+
} else {
|
|
119
|
+
const { reachable, error } = await checkTargetUrl(configUrl);
|
|
120
|
+
checks.push(makeCheck(
|
|
121
|
+
'url.reachable',
|
|
122
|
+
reachable,
|
|
123
|
+
'warn',
|
|
124
|
+
reachable
|
|
125
|
+
? t('qa_doctor.url_ok', { url: configUrl })
|
|
126
|
+
: t('qa_doctor.url_unreachable', { url: configUrl, error }),
|
|
127
|
+
reachable ? '' : t('qa_doctor.url_unreachable_hint')
|
|
128
|
+
));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check 5 — project.context.md
|
|
132
|
+
const contextResult = await validateProjectContextFile(targetDir);
|
|
133
|
+
checks.push(makeCheck(
|
|
134
|
+
'context.exists',
|
|
135
|
+
contextResult.exists,
|
|
136
|
+
'warn',
|
|
137
|
+
contextResult.exists ? t('qa_doctor.context_ok') : t('qa_doctor.context_missing')
|
|
138
|
+
));
|
|
139
|
+
|
|
140
|
+
// Check 6 — prd.md (optional enrichment)
|
|
141
|
+
const prdContent = await readTextIfExists(prdPath);
|
|
142
|
+
const acCount = countAcItems(prdContent || '');
|
|
143
|
+
checks.push(makeCheck(
|
|
144
|
+
'prd.exists',
|
|
145
|
+
Boolean(prdContent),
|
|
146
|
+
'warn',
|
|
147
|
+
prdContent
|
|
148
|
+
? t('qa_doctor.prd_ok', { count: acCount })
|
|
149
|
+
: t('qa_doctor.prd_missing')
|
|
150
|
+
));
|
|
151
|
+
|
|
152
|
+
const summary = summarizeChecks(checks);
|
|
153
|
+
const output = {
|
|
154
|
+
ok: summary.failed === 0,
|
|
155
|
+
targetDir,
|
|
156
|
+
configPath,
|
|
157
|
+
configExists,
|
|
158
|
+
configParsed,
|
|
159
|
+
url: configUrl,
|
|
160
|
+
checks,
|
|
161
|
+
summary
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
if (options.json) return output;
|
|
165
|
+
|
|
166
|
+
logger.log(t('qa_doctor.report_title', { path: targetDir }));
|
|
167
|
+
for (const check of checks) {
|
|
168
|
+
logger.log(t('qa_doctor.check_line', {
|
|
169
|
+
prefix: formatPrefix(check, t),
|
|
170
|
+
id: check.id,
|
|
171
|
+
message: check.message
|
|
172
|
+
}));
|
|
173
|
+
if (check.hint) logger.log(t('qa_doctor.hint_line', { hint: check.hint }));
|
|
174
|
+
}
|
|
175
|
+
logger.log(t('qa_doctor.summary', {
|
|
176
|
+
passed: summary.passed,
|
|
177
|
+
failed: summary.failed,
|
|
178
|
+
warnings: summary.warnings
|
|
179
|
+
}));
|
|
180
|
+
|
|
181
|
+
if (!output.ok) process.exitCode = 1;
|
|
182
|
+
return output;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = { runQaDoctor };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const fs = require('node:fs/promises');
|
|
5
|
+
const { readTextIfExists, ensureDir, exists } = require('../utils');
|
|
6
|
+
const { validateProjectContextFile } = require('../context');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_PERSONAS = ['naive', 'hacker', 'power', 'mobile'];
|
|
9
|
+
|
|
10
|
+
const DEFAULT_SECURITY_PROBES = [
|
|
11
|
+
'exposed_env_vars',
|
|
12
|
+
'xss_inputs',
|
|
13
|
+
'open_redirect',
|
|
14
|
+
'sensitive_files',
|
|
15
|
+
'idor_probe',
|
|
16
|
+
'console_leaks',
|
|
17
|
+
'debug_routes',
|
|
18
|
+
'mixed_content',
|
|
19
|
+
'sensitive_get_params'
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const DEFAULT_PERFORMANCE_THRESHOLDS = {
|
|
23
|
+
page_load_ms: 3000,
|
|
24
|
+
ttfb_ms: 800,
|
|
25
|
+
requests_max: 80,
|
|
26
|
+
transfer_max_kb: 2048
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function extractFrontmatterValue(markdown, key) {
|
|
30
|
+
if (!markdown) return '';
|
|
31
|
+
const regex = new RegExp(`^-\\s*${key}:\\s*(.*)$`, 'im');
|
|
32
|
+
const match = String(markdown).match(regex);
|
|
33
|
+
return match ? String(match[1] || '').trim() : '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function extractYamlValue(markdown, key) {
|
|
37
|
+
if (!markdown) return '';
|
|
38
|
+
const regex = new RegExp(`^${key}:\\s*(.*)$`, 'im');
|
|
39
|
+
const match = String(markdown).match(regex);
|
|
40
|
+
return match ? String(match[1] || '').trim().replace(/^['"]|['"]$/g, '') : '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseAcItems(prdContent) {
|
|
44
|
+
if (!prdContent) return [];
|
|
45
|
+
const items = [];
|
|
46
|
+
// Match table rows: | AC-01 | description |
|
|
47
|
+
const tableRows = [...String(prdContent).matchAll(/\|\s*(AC-\d+)\s*\|\s*([^|]+)\|/g)];
|
|
48
|
+
for (const match of tableRows) {
|
|
49
|
+
items.push({ id: match[1].trim(), description: match[2].trim() });
|
|
50
|
+
}
|
|
51
|
+
// Match must-have items in MVP section
|
|
52
|
+
const mvpMatches = [...String(prdContent).matchAll(/🔴\s*([^\n]+)/g)];
|
|
53
|
+
for (const match of mvpMatches) {
|
|
54
|
+
if (items.length >= 20) break;
|
|
55
|
+
items.push({ id: `AC-${String(items.length + 1).padStart(2, '0')}`, description: match[1].trim() });
|
|
56
|
+
}
|
|
57
|
+
return items.slice(0, 20);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseBusinessRules(discoveryContent) {
|
|
61
|
+
if (!discoveryContent) return [];
|
|
62
|
+
const rules = [];
|
|
63
|
+
const matches = [...String(discoveryContent).matchAll(/[-*]\s*([A-Z][^\n]{10,80})/g)];
|
|
64
|
+
for (const match of matches.slice(0, 15)) {
|
|
65
|
+
rules.push(match[1].trim());
|
|
66
|
+
}
|
|
67
|
+
return rules;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function runQaInit({ args, options = {}, logger, t }) {
|
|
71
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
72
|
+
const dryRun = Boolean(options['dry-run']);
|
|
73
|
+
const configPath = path.join(targetDir, 'aios-qa.config.json');
|
|
74
|
+
|
|
75
|
+
const contextResult = await validateProjectContextFile(targetDir);
|
|
76
|
+
const contextMarkdown = await readTextIfExists(path.join(targetDir, '.aioson/context/project.context.md'));
|
|
77
|
+
const prdContent = await readTextIfExists(path.join(targetDir, '.aioson/context/prd.md'));
|
|
78
|
+
const discoveryContent = await readTextIfExists(path.join(targetDir, '.aioson/context/discovery.md'));
|
|
79
|
+
|
|
80
|
+
const contextData = contextResult.parsed && contextResult.data ? contextResult.data : {};
|
|
81
|
+
|
|
82
|
+
// Resolve URL: CLI flag > context app_url > context framework hint > ask
|
|
83
|
+
let url = String(options.url || '').trim();
|
|
84
|
+
if (!url && contextMarkdown) {
|
|
85
|
+
url = extractFrontmatterValue(contextMarkdown, 'app_url') ||
|
|
86
|
+
extractFrontmatterValue(contextMarkdown, 'dev_url') || '';
|
|
87
|
+
}
|
|
88
|
+
if (!url) {
|
|
89
|
+
url = 'http://localhost:3000';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const projectName = String(contextData.project_name || path.basename(targetDir) || 'Project');
|
|
93
|
+
const language = String(contextData.conversation_language || 'en');
|
|
94
|
+
|
|
95
|
+
// Parse prd.md for AC items
|
|
96
|
+
const acItems = parseAcItems(prdContent);
|
|
97
|
+
const businessRules = parseBusinessRules(discoveryContent);
|
|
98
|
+
|
|
99
|
+
if (contextResult.exists) {
|
|
100
|
+
logger.log(t('qa_init.context_found', { name: projectName, url }));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (prdContent) {
|
|
104
|
+
logger.log(t('qa_init.prd_found', { count: acItems.length }));
|
|
105
|
+
} else {
|
|
106
|
+
logger.log(t('qa_init.prd_missing'));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const config = {
|
|
110
|
+
project_name: projectName,
|
|
111
|
+
url,
|
|
112
|
+
language,
|
|
113
|
+
personas: DEFAULT_PERSONAS,
|
|
114
|
+
security_probes: DEFAULT_SECURITY_PROBES,
|
|
115
|
+
performance_thresholds: DEFAULT_PERFORMANCE_THRESHOLDS,
|
|
116
|
+
accessibility: true,
|
|
117
|
+
network_capture: true,
|
|
118
|
+
screenshot_on_finding: true,
|
|
119
|
+
scenarios: acItems,
|
|
120
|
+
business_rules: businessRules,
|
|
121
|
+
generated_at: new Date().toISOString(),
|
|
122
|
+
aioson_version: require('../../package.json').version
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (!dryRun) {
|
|
126
|
+
await ensureDir(path.dirname(configPath));
|
|
127
|
+
await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const output = {
|
|
131
|
+
ok: true,
|
|
132
|
+
targetDir,
|
|
133
|
+
configPath,
|
|
134
|
+
dryRun,
|
|
135
|
+
written: !dryRun,
|
|
136
|
+
url,
|
|
137
|
+
projectName,
|
|
138
|
+
scenariosCount: acItems.length,
|
|
139
|
+
personasCount: DEFAULT_PERSONAS.length,
|
|
140
|
+
probesCount: DEFAULT_SECURITY_PROBES.length,
|
|
141
|
+
config
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
if (options.json) return output;
|
|
145
|
+
|
|
146
|
+
logger.log(
|
|
147
|
+
dryRun
|
|
148
|
+
? t('qa_init.dry_run_generated', { path: configPath })
|
|
149
|
+
: t('qa_init.generated', { path: configPath })
|
|
150
|
+
);
|
|
151
|
+
logger.log(t('qa_init.scenarios_count', { count: acItems.length }));
|
|
152
|
+
logger.log(t('qa_init.personas_count', { count: DEFAULT_PERSONAS.length }));
|
|
153
|
+
logger.log(t('qa_init.probes_count', { count: DEFAULT_SECURITY_PROBES.length }));
|
|
154
|
+
logger.log(t('qa_init.next_steps'));
|
|
155
|
+
logger.log(t('qa_init.step_doctor'));
|
|
156
|
+
logger.log(t('qa_init.step_run'));
|
|
157
|
+
|
|
158
|
+
return output;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { runQaInit };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const fs = require('node:fs/promises');
|
|
5
|
+
const { readTextIfExists, exists } = require('../utils');
|
|
6
|
+
|
|
7
|
+
async function runQaReport({ args, options = {}, logger, t }) {
|
|
8
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
9
|
+
const mdPath = path.join(targetDir, 'aios-qa-report.md');
|
|
10
|
+
const jsonPath = path.join(targetDir, 'aios-qa-report.json');
|
|
11
|
+
|
|
12
|
+
if (options.json) {
|
|
13
|
+
if (!(await exists(jsonPath))) {
|
|
14
|
+
return { ok: false, error: 'report_not_found', path: jsonPath };
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const raw = await fs.readFile(jsonPath, 'utf8');
|
|
18
|
+
return { ok: true, ...JSON.parse(raw) };
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return { ok: false, error: err.message };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (options.html) {
|
|
25
|
+
if (!(await exists(jsonPath))) {
|
|
26
|
+
logger.error(t('qa_report.not_found'));
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return { ok: false, error: 'report_not_found' };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const raw = await fs.readFile(jsonPath, 'utf8');
|
|
32
|
+
const data = JSON.parse(raw);
|
|
33
|
+
const { writeHtmlReport } = require('../qa-html-report');
|
|
34
|
+
const screenshotsDir = path.join(targetDir, 'aios-qa-screenshots');
|
|
35
|
+
const result = await writeHtmlReport(
|
|
36
|
+
targetDir, data.project || 'Project', data.url || '',
|
|
37
|
+
data.findings || [], data.ac_coverage || [], data.performance || null,
|
|
38
|
+
data.mode || 'run', screenshotsDir, { routes: data.routes_scanned }
|
|
39
|
+
);
|
|
40
|
+
logger.log(t('qa_report.html_report_written', { path: result.htmlPath }));
|
|
41
|
+
return { ok: true, htmlPath: result.htmlPath };
|
|
42
|
+
} catch (err) {
|
|
43
|
+
return { ok: false, error: err.message };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const content = await readTextIfExists(mdPath);
|
|
48
|
+
if (!content) {
|
|
49
|
+
logger.error(t('qa_report.not_found'));
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
return { ok: false, error: 'report_not_found' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
logger.log(content);
|
|
55
|
+
return { ok: true, path: mdPath };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { runQaReport };
|