@jaimevalasek/aioson 1.29.1 → 1.30.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.
- package/CHANGELOG.md +28 -0
- package/README.md +7 -5
- package/docs/en/5-reference/cli-reference.md +40 -10
- package/docs/pt/4-agentes/pm.md +1 -1
- package/docs/pt/5-referencia/autopilot-handoff.md +4 -4
- package/docs/pt/5-referencia/comandos-cli.md +5 -3
- package/docs/pt/5-referencia/fluxo-artefatos.md +1 -1
- package/docs/pt/5-referencia/memoria-e-contexto.md +2 -2
- package/docs/pt/_arquivo/monitor-de-contexto.md +2 -2
- package/package.json +4 -2
- package/src/cli.js +67 -24
- package/src/commands/ac-test-audit.js +45 -0
- package/src/commands/artifact-validate.js +62 -50
- package/src/commands/classify.js +73 -2
- package/src/commands/context-brief.js +59 -0
- package/src/commands/context-guard.js +88 -0
- package/src/commands/context-monitor.js +1 -1
- package/src/commands/context-search.js +101 -52
- package/src/commands/context-select.js +11 -2
- package/src/commands/feature-archive.js +21 -12
- package/src/commands/feature-current.js +82 -0
- package/src/commands/gate-check.js +32 -15
- package/src/commands/harness-check.js +17 -1
- package/src/commands/hooks-install.js +169 -26
- package/src/commands/hygiene-scan.js +423 -0
- package/src/commands/rules-lint.js +11 -3
- package/src/commands/sdd-benchmark.js +134 -0
- package/src/commands/spec-analyze.js +6 -4
- package/src/commands/store-system.js +329 -49
- package/src/constants.js +19 -6
- package/src/context-brief.js +585 -0
- package/src/context-guard.js +209 -0
- package/src/context-search.js +796 -96
- package/src/context-selector.js +802 -444
- package/src/handoff-contract.js +14 -6
- package/src/harness/contract-schema.js +1 -1
- package/src/i18n/messages/en.js +12 -5
- package/src/i18n/messages/es.js +11 -4
- package/src/i18n/messages/fr.js +11 -4
- package/src/i18n/messages/pt-BR.js +12 -5
- package/src/lib/ac-test-audit.js +194 -0
- package/src/preflight-engine.js +10 -6
- package/src/squad/state-manager.js +1 -1
- package/template/.aioson/agents/analyst.md +41 -17
- package/template/.aioson/agents/architect.md +4 -2
- package/template/.aioson/agents/briefing-refiner.md +15 -2
- package/template/.aioson/agents/briefing.md +12 -8
- package/template/.aioson/agents/committer.md +1 -1
- package/template/.aioson/agents/copywriter.md +20 -9
- package/template/.aioson/agents/design-hybrid-forge.md +9 -5
- package/template/.aioson/agents/dev.md +22 -25
- package/template/.aioson/agents/deyvin.md +126 -124
- package/template/.aioson/agents/discover.md +3 -1
- package/template/.aioson/agents/discovery-design-doc.md +11 -2
- package/template/.aioson/agents/forge-run.md +3 -0
- package/template/.aioson/agents/genome.md +9 -5
- package/template/.aioson/agents/neo.md +30 -24
- package/template/.aioson/agents/orache.md +10 -6
- package/template/.aioson/agents/orchestrator.md +4 -2
- package/template/.aioson/agents/pentester.md +22 -12
- package/template/.aioson/agents/pm.md +5 -3
- package/template/.aioson/agents/product.md +25 -18
- package/template/.aioson/agents/profiler-enricher.md +10 -6
- package/template/.aioson/agents/profiler-forge.md +10 -6
- package/template/.aioson/agents/profiler-researcher.md +10 -6
- package/template/.aioson/agents/qa.md +21 -19
- package/template/.aioson/agents/scope-check.md +9 -3
- package/template/.aioson/agents/sheldon.md +22 -8
- package/template/.aioson/agents/site-forge.md +2 -0
- package/template/.aioson/agents/squad.md +4 -2
- package/template/.aioson/agents/tester.md +19 -15
- package/template/.aioson/agents/ux-ui.md +16 -8
- package/template/.aioson/config.md +4 -3
- package/template/.aioson/design-docs/agent-loading-contract.md +3 -3
- package/template/.aioson/docs/autopilot-handoff.md +3 -3
- package/template/.aioson/docs/dev/simple-plan-lane.md +73 -27
- package/template/.aioson/docs/dev/stack-conventions.md +1 -1
- package/template/.aioson/docs/deyvin/continuity-recovery.md +1 -1
- package/template/.aioson/docs/deyvin/runtime-handoffs.md +3 -3
- package/template/.aioson/docs/feature-expansion-taxonomy.md +53 -0
- package/template/.aioson/docs/handoff-persistence.md +14 -12
- package/template/.aioson/docs/integrations/dashboard-app-form-publish-mapping.md +183 -0
- package/template/.aioson/docs/play/README.md +72 -0
- package/template/.aioson/docs/play/agent-usage-guide.md +106 -0
- package/template/.aioson/docs/play/app-compatibility-guide.md +112 -0
- package/template/.aioson/docs/play/auth-services-and-testing.md +220 -0
- package/template/.aioson/docs/play/llm-data-and-bindings.md +238 -0
- package/template/.aioson/docs/play/manifest-and-runtime.md +244 -0
- package/template/.aioson/docs/play/source-map.md +104 -0
- package/template/.aioson/docs/product/conversation-playbook.md +1 -1
- package/template/.aioson/docs/sheldon/enrichment-paths.md +44 -1
- package/template/.aioson/docs/sheldon/harness-contract.md +23 -21
- package/template/.aioson/docs/tester/coverage-quality.md +1 -1
- package/template/.aioson/docs/ux-ui/design-execution.md +9 -7
- package/template/.aioson/rules/README.md +35 -17
- package/template/.aioson/rules/agent-structural-contract.md +165 -160
- package/template/.aioson/rules/aioson-context-boundary.md +5 -4
- package/template/.aioson/rules/canonical-path-contract.md +5 -4
- package/template/.aioson/rules/data-format-convention.md +5 -4
- package/template/.aioson/rules/disk-first-artifacts.md +2 -2
- package/template/.aioson/rules/implementation-structure-and-data-access.md +50 -0
- package/template/.aioson/rules/security-baseline.md +4 -3
- package/template/.aioson/rules/simple-plan-lane.md +18 -6
- package/template/.aioson/rules/source-code-language-convention.md +34 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +24 -23
- package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +2 -2
- package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +1 -1
- package/template/.aioson/skills/process/briefing-expansion-scout/SKILL.md +72 -0
- package/template/.aioson/skills/process/product-scope-expansion/SKILL.md +74 -0
- package/template/.aioson/skills/process/sheldon-expansion-audit/SKILL.md +67 -0
- package/template/.aioson/skills/static/context-budget-guide.md +1 -1
- package/template/.aioson/skills/static/multi-agent-patterns.md +5 -4
- package/template/AGENTS.md +36 -19
- package/template/CLAUDE.md +9 -5
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs/promises');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
+
const ignore = require('ignore');
|
|
5
6
|
const { exists, ensureDir } = require('../utils');
|
|
6
7
|
const { readConfig } = require('./config');
|
|
7
8
|
const { readWorkspace, findProjectRoot } = require('./workspace');
|
|
@@ -12,7 +13,55 @@ function getTerser() {
|
|
|
12
13
|
return _terser;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
let _obfuscator = null;
|
|
17
|
+
function getObfuscator() {
|
|
18
|
+
if (!_obfuscator) _obfuscator = require('javascript-obfuscator');
|
|
19
|
+
return _obfuscator;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Ofusca JS compilado no publish --build (minify + string-array encoding +
|
|
23
|
+
// mangling). Funcionalidade do framework — o app não configura nada. Conservador
|
|
24
|
+
// (renameGlobals/controlFlowFlattening/selfDefending OFF) pra não quebrar runtime
|
|
25
|
+
// Node (require/exports, prisma) nem bundles de frontend. Falha num arquivo →
|
|
26
|
+
// devolve o compilado original (não derruba o publish).
|
|
27
|
+
// Detecta JS já minificado (bundle de frontend tipo vite/webpack): linhas muito
|
|
28
|
+
// longas e poucas quebras. Não vale re-ofuscar — incha o pacote, pode quebrar o
|
|
29
|
+
// React e o ganho é baixo (já está minificado). O alvo de valor é o backend (tsc,
|
|
30
|
+
// código legível), esse sim é ofuscado.
|
|
31
|
+
function looksMinified(code) {
|
|
32
|
+
const newlines = (code.match(/\n/g) || []).length;
|
|
33
|
+
const avgLineLen = code.length / (newlines + 1);
|
|
34
|
+
return code.length > 30000 && avgLineLen > 200;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function obfuscateJs(code) {
|
|
38
|
+
if (looksMinified(code)) return code; // já minificado → mantém como está
|
|
39
|
+
try {
|
|
40
|
+
return getObfuscator()
|
|
41
|
+
.obfuscate(code, {
|
|
42
|
+
compact: true,
|
|
43
|
+
controlFlowFlattening: false,
|
|
44
|
+
deadCodeInjection: false,
|
|
45
|
+
stringArray: true,
|
|
46
|
+
stringArrayEncoding: ['base64'],
|
|
47
|
+
stringArrayThreshold: 0.75,
|
|
48
|
+
identifierNamesGenerator: 'hexadecimal',
|
|
49
|
+
renameGlobals: false,
|
|
50
|
+
selfDefending: false,
|
|
51
|
+
debugProtection: false,
|
|
52
|
+
disableConsoleOutput: false,
|
|
53
|
+
sourceMap: false,
|
|
54
|
+
})
|
|
55
|
+
.getObfuscatedCode();
|
|
56
|
+
} catch {
|
|
57
|
+
return code;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
15
61
|
async function createZipBuffer(files) {
|
|
62
|
+
// archiver fica fixado em ^7 (CJS, API chamável `archiver('zip', opts)`). A v8
|
|
63
|
+
// virou ESM e trocou a API por classes nomeadas (sem função default) — o que
|
|
64
|
+
// quebrava com "archiver is not a function" no Node 23. Ver package.json.
|
|
16
65
|
const archiver = require('archiver');
|
|
17
66
|
const { PassThrough } = require('stream');
|
|
18
67
|
return new Promise((resolve, reject) => {
|
|
@@ -46,6 +95,7 @@ const SYSTEM_ALLOWED_EXTS = new Set([
|
|
|
46
95
|
'.svg', '.ico',
|
|
47
96
|
'.md', '.txt',
|
|
48
97
|
'.sql',
|
|
98
|
+
'.prisma',
|
|
49
99
|
'.env', '.env.example', '.env.template',
|
|
50
100
|
'.yaml', '.yml',
|
|
51
101
|
'.toml',
|
|
@@ -68,21 +118,80 @@ const SKIP_DIRS = new Set([
|
|
|
68
118
|
'node_modules', '.git', 'dist', 'build', '.turbo', '.next',
|
|
69
119
|
'.cache', 'coverage', '.nyc_output', 'out',
|
|
70
120
|
// AIOSON tooling — não faz parte do código-fonte do sistema
|
|
71
|
-
'.aioson', '.claude', '.codex', 'researchs',
|
|
121
|
+
'.aioson', '.claude', '.codex', 'researchs',
|
|
72
122
|
]);
|
|
73
123
|
|
|
74
124
|
const SKIP_DIRS_BUILD = new Set([
|
|
75
125
|
'node_modules', '.git', '.turbo', '.next',
|
|
76
126
|
'.cache', 'coverage', '.nyc_output',
|
|
77
127
|
'src', 'dashboard/src',
|
|
78
|
-
'.aioson', '.claude', '.codex', 'researchs',
|
|
128
|
+
'.aioson', '.claude', '.codex', 'researchs',
|
|
79
129
|
]);
|
|
80
130
|
|
|
81
131
|
const SKIP_FILES = new Set([
|
|
82
132
|
'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
|
|
83
133
|
'bun.lockb',
|
|
134
|
+
// Arquivo de credenciais LLM local (convenção AIOSON) — NUNCA publicar, mesmo
|
|
135
|
+
// que tenha sido commitado por engano. Defense-in-depth: o filtro de
|
|
136
|
+
// .gitignore abaixo também pega, mas isto cobre apps sem .gitignore.
|
|
137
|
+
'aioson-models.json',
|
|
84
138
|
]);
|
|
85
139
|
|
|
140
|
+
// `.aioson` é tooling/dev e fica de fora do pacote (SKIP_DIRS), MAS algumas
|
|
141
|
+
// coisas dali os squads leem EM RUNTIME — sem elas o app quebra (ex.:
|
|
142
|
+
// SQUAD_MANIFEST_INVALID procurando `.aioson/squads/<slug>/squad.manifest.json`).
|
|
143
|
+
//
|
|
144
|
+
// Modelo: `.aioson/squads` é SEMPRE incluído (obrigatório). O resto é OPT-IN —
|
|
145
|
+
// o dev declara só o que o squad realmente precisa num `.aioson/build-options.json`
|
|
146
|
+
// (não viaja peso à toa). Cada entrada é um caminho relativo a `.aioson/` e pode
|
|
147
|
+
// ser pasta (`docs`), subpasta (`skills/skill-x`) ou arquivo (`docs/guia.md`).
|
|
148
|
+
// { "include": ["docs", "skills/atendimento", "rules/foo.md"] }
|
|
149
|
+
const AIOSON_MANDATORY_INCLUDES = ['squads'];
|
|
150
|
+
|
|
151
|
+
/** Resolve o que incluir do `.aioson` de um app: `squads` (sempre) + o que o
|
|
152
|
+
* `build-options.json` declarar. Normaliza e descarta entradas inseguras. */
|
|
153
|
+
async function readAiosonIncludes(aiosonDir) {
|
|
154
|
+
const includes = new Set(AIOSON_MANDATORY_INCLUDES);
|
|
155
|
+
try {
|
|
156
|
+
const optsPath = path.join(aiosonDir, 'build-options.json');
|
|
157
|
+
if (await exists(optsPath)) {
|
|
158
|
+
const opts = JSON.parse(await fs.readFile(optsPath, 'utf8'));
|
|
159
|
+
const list = Array.isArray(opts.include) ? opts.include : [];
|
|
160
|
+
for (let entry of list) {
|
|
161
|
+
if (typeof entry !== 'string') continue;
|
|
162
|
+
entry = entry.replace(/^\.aioson[\\/]/i, '').replace(/^[\\/]+/, '').replace(/[\\/]+$/, '').trim();
|
|
163
|
+
if (!entry || entry.split(/[\\/]/).includes('..')) continue; // anti path-traversal
|
|
164
|
+
includes.add(entry);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} catch { /* build-options.json inválido → só os obrigatórios */ }
|
|
168
|
+
return [...includes];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// No modo --build, estas pastas são a SAÍDA do build (compilado/minificado) e
|
|
172
|
+
// DEVEM viajar no pacote — mesmo estando no .gitignore (build output costuma ser
|
|
173
|
+
// gitignored). Sem isso, o filtro de .gitignore mataria o `dist/` e o app
|
|
174
|
+
// instalado não teria o que rodar.
|
|
175
|
+
const BUILD_OUTPUT_DIRS = new Set(['dist', 'build', 'out', '.next']);
|
|
176
|
+
|
|
177
|
+
// Testes / mocks NUNCA vão no pacote — são peso morto em runtime (e ainda
|
|
178
|
+
// inflavam o pacote ao serem ofuscados). O check é INCONDICIONAL: pega também os
|
|
179
|
+
// testes COMPILADOS dentro do `dist/` (que viajam mesmo com `src/` excluído).
|
|
180
|
+
const TEST_DIRS = new Set(['__tests__', '__mocks__', '__snapshots__']);
|
|
181
|
+
const TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
|
|
182
|
+
|
|
183
|
+
// Config de runtime que PRECISA viajar mesmo no --build (mesmo sendo .ts, que
|
|
184
|
+
// normalmente é excluído): o `vite preview` (frontend em produção) lê o
|
|
185
|
+
// `vite.config.*` pra porta + proxy do /api. Sem ele, instalação limpa quebra o
|
|
186
|
+
// frontend. NÃO é ofuscado (é config lida pelo vite, não lógica a proteger).
|
|
187
|
+
const RUNTIME_CONFIG_RE = /^vite\.config\.[cm]?[jt]s$/i;
|
|
188
|
+
|
|
189
|
+
// Dentro das pastas de runtime do `.aioson`, os arquivos são majoritariamente
|
|
190
|
+
// markdown (definições de agentes/skills, docs) além de json/yaml — então
|
|
191
|
+
// ampliamos as extensões permitidas pra esse subconjunto, senão os `.md` seriam
|
|
192
|
+
// filtrados e o squad subiria sem os agentes.
|
|
193
|
+
const AIOSON_RUNTIME_EXTS = new Set(['.md', '.mdx', '.txt']);
|
|
194
|
+
|
|
86
195
|
const MAX_FILE_BYTES = 512 * 1024; // 512 KB per file (source)
|
|
87
196
|
const MAX_FILE_BYTES_BUILD = 2 * 1024 * 1024; // 2 MB per file (compiled bundles)
|
|
88
197
|
const MAX_PACKAGE_BYTES = 20 * 1024 * 1024; // 20 MB total
|
|
@@ -167,62 +276,115 @@ async function collectSystemFiles(dir, { buildMode = false } = {}) {
|
|
|
167
276
|
const skipDirs = buildMode ? SKIP_DIRS_BUILD : SKIP_DIRS;
|
|
168
277
|
const allowedExts = buildMode ? SYSTEM_BUILD_ALLOWED_EXTS : SYSTEM_ALLOWED_EXTS;
|
|
169
278
|
|
|
170
|
-
|
|
279
|
+
// Respeita o .gitignore do app: arquivos/pastas locais ou gerados em runtime
|
|
280
|
+
// NÃO devem viajar no pacote. Sem isso vazavam coisas como `aioson-models.json`
|
|
281
|
+
// (chave LLM do dev!), `.env` e `atendimento-config.json` (config por-instalação
|
|
282
|
+
// que gateia o onboarding). NÃO se aplica às pastas de runtime do `.aioson`
|
|
283
|
+
// (forceInclude) — essas viajam por design, mesmo que gitignored.
|
|
284
|
+
const ig = ignore();
|
|
285
|
+
try {
|
|
286
|
+
const gitignorePath = path.join(dir, '.gitignore');
|
|
287
|
+
if (await exists(gitignorePath)) {
|
|
288
|
+
ig.add(await fs.readFile(gitignorePath, 'utf8'));
|
|
289
|
+
}
|
|
290
|
+
} catch { /* sem .gitignore → não filtra por ignore */ }
|
|
291
|
+
|
|
292
|
+
let limitHit = false;
|
|
293
|
+
|
|
294
|
+
// Processa UM arquivo (checa skip/ignore/extensão/tamanho, lê, ofusca se build,
|
|
295
|
+
// grava). Usado pelo walk e pelos includes pontuais do `.aioson` (que podem ser
|
|
296
|
+
// arquivo único). `forceInclude` = bypassa skip/ignore/extensão (pastas runtime).
|
|
297
|
+
async function addFile(fullPath, relPath, forceInclude, entryName) {
|
|
298
|
+
if (limitHit) return;
|
|
299
|
+
if (!forceInclude && SKIP_FILES.has(entryName)) return;
|
|
300
|
+
if (!forceInclude && ig.ignores(relPath)) return;
|
|
301
|
+
|
|
302
|
+
const ext = entryName.includes('.')
|
|
303
|
+
? `.${entryName.split('.').pop().toLowerCase()}`
|
|
304
|
+
: '';
|
|
305
|
+
const extAllowed =
|
|
306
|
+
allowedExts.has(ext) ||
|
|
307
|
+
(forceInclude && AIOSON_RUNTIME_EXTS.has(ext)) ||
|
|
308
|
+
RUNTIME_CONFIG_RE.test(entryName); // vite.config.* viaja mesmo no --build
|
|
309
|
+
if (!extAllowed && ext !== '') return;
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const stat = await fs.stat(fullPath);
|
|
313
|
+
const maxBytes = buildMode ? MAX_FILE_BYTES_BUILD : MAX_FILE_BYTES;
|
|
314
|
+
if (stat.size > maxBytes) {
|
|
315
|
+
errors.push(`File too large (skipped): "${relPath}" (${(stat.size / 1024).toFixed(0)} KB)`);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
totalBytes += stat.size;
|
|
319
|
+
if (totalBytes > MAX_PACKAGE_BYTES) {
|
|
320
|
+
errors.push(`Package exceeds ${MAX_PACKAGE_BYTES / 1024 / 1024} MB limit — stop collecting.`);
|
|
321
|
+
limitHit = true;
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
let content = await fs.readFile(fullPath, 'utf8');
|
|
325
|
+
if (
|
|
326
|
+
buildMode &&
|
|
327
|
+
(ext === '.js' || ext === '.mjs' || ext === '.cjs') &&
|
|
328
|
+
!RUNTIME_CONFIG_RE.test(entryName) // não ofuscar config lida pelo vite
|
|
329
|
+
) {
|
|
330
|
+
content = obfuscateJs(content);
|
|
331
|
+
}
|
|
332
|
+
files[relPath] = content;
|
|
333
|
+
} catch {
|
|
334
|
+
// binary or unreadable — skip silently
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function walk(current, rel, forceInclude = false) {
|
|
339
|
+
if (limitHit) return;
|
|
171
340
|
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
172
341
|
for (const entry of entries) {
|
|
173
|
-
if (skipDirs.has(entry.name)) continue;
|
|
174
|
-
if (rel && skipDirs.has(`${rel}/${entry.name}`)) continue;
|
|
175
|
-
if (SKIP_FILES.has(entry.name)) continue;
|
|
176
|
-
|
|
177
342
|
const fullPath = path.join(current, entry.name);
|
|
178
343
|
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
179
344
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const ext = entry.name.includes('.')
|
|
186
|
-
? `.${entry.name.split('.').pop().toLowerCase()}`
|
|
187
|
-
: '';
|
|
345
|
+
// Testes/mocks fora do pacote — incondicional (pega até dentro do dist).
|
|
346
|
+
if (entry.isDirectory() && TEST_DIRS.has(entry.name)) continue;
|
|
347
|
+
if (!entry.isDirectory() && TEST_FILE_RE.test(entry.name)) continue;
|
|
188
348
|
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
|
|
349
|
+
if (entry.isDirectory()) {
|
|
350
|
+
// `.aioson`: normalmente fica fora, mas descemos só nas subpastas de
|
|
351
|
+
// runtime (squads/docs/skills/rules/genomes/agents) pra o app não
|
|
352
|
+
// quebrar. As subpastas entram em modo forceInclude (mantém estrutura
|
|
353
|
+
// e arquivos originais). Vale pra qualquer nível onde apareça `.aioson`.
|
|
354
|
+
if (entry.name === '.aioson' && !forceInclude) {
|
|
355
|
+
// `squads` (sempre) + o que o build-options.json declarar. Cada include
|
|
356
|
+
// pode ser pasta/subpasta (→ walk) ou arquivo único (→ addFile).
|
|
357
|
+
const includes = await readAiosonIncludes(fullPath);
|
|
358
|
+
for (const inc of includes) {
|
|
359
|
+
const incPath = path.join(fullPath, inc);
|
|
360
|
+
if (!(await exists(incPath))) continue;
|
|
361
|
+
const st = await fs.stat(incPath);
|
|
362
|
+
if (st.isDirectory()) {
|
|
363
|
+
await walk(incPath, `${relPath}/${inc}`, true);
|
|
364
|
+
} else {
|
|
365
|
+
await addFile(incPath, `${relPath}/${inc}`, true, path.basename(inc));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
196
368
|
continue;
|
|
197
369
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
370
|
+
// Saída do build (--build): viaja mesmo gitignored. forceInclude bypassa
|
|
371
|
+
// o filtro de .gitignore; o filtro de extensão + minify continuam (sourcemaps
|
|
372
|
+
// `.map` ficam de fora por não estarem nas extensões permitidas → não vaza fonte).
|
|
373
|
+
if (buildMode && !forceInclude && BUILD_OUTPUT_DIRS.has(entry.name)) {
|
|
374
|
+
await walk(fullPath, relPath, true);
|
|
375
|
+
continue;
|
|
202
376
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const terser = getTerser();
|
|
208
|
-
const result = await terser.minify(content, {
|
|
209
|
-
compress: { passes: 2, drop_console: false },
|
|
210
|
-
mangle: {
|
|
211
|
-
toplevel: true,
|
|
212
|
-
properties: { regex: /^_/ },
|
|
213
|
-
},
|
|
214
|
-
format: { comments: false },
|
|
215
|
-
});
|
|
216
|
-
if (result.code) content = result.code;
|
|
217
|
-
} catch {
|
|
218
|
-
// terser failed on this file — keep original compiled JS
|
|
219
|
-
}
|
|
377
|
+
if (!forceInclude) {
|
|
378
|
+
if (skipDirs.has(entry.name)) continue;
|
|
379
|
+
if (rel && skipDirs.has(`${rel}/${entry.name}`)) continue;
|
|
380
|
+
if (ig.ignores(relPath)) continue; // gitignored → não viaja
|
|
220
381
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
} catch {
|
|
224
|
-
// binary or unreadable — skip silently
|
|
382
|
+
await walk(fullPath, relPath, forceInclude);
|
|
383
|
+
continue;
|
|
225
384
|
}
|
|
385
|
+
|
|
386
|
+
// Arquivo — processado pelo addFile (skip/ignore/ext/tamanho/ofuscação).
|
|
387
|
+
await addFile(fullPath, relPath, forceInclude, entry.name);
|
|
226
388
|
}
|
|
227
389
|
}
|
|
228
390
|
|
|
@@ -248,9 +410,91 @@ async function readSystemJson(dir, t) {
|
|
|
248
410
|
if (!manifest.slug) throw new Error(t('system.error_manifest_missing_slug'));
|
|
249
411
|
if (!manifest.version) throw new Error(t('system.error_manifest_missing_version'));
|
|
250
412
|
if (!manifest.name) throw new Error(t('system.error_manifest_missing_name'));
|
|
413
|
+
validateListingFields(manifest);
|
|
251
414
|
return manifest;
|
|
252
415
|
}
|
|
253
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Valida os campos opcionais de listing da loja (modelo Chrome Web Store).
|
|
419
|
+
* Todos são opcionais — apps antigos sem eles publicam normalmente. Falha cedo,
|
|
420
|
+
* com mensagem clara, quando um campo presente está num formato inválido.
|
|
421
|
+
* Imagens (icon/screenshots) são URLs http(s) externas (Opção A — sem hosting).
|
|
422
|
+
*/
|
|
423
|
+
function isHttpUrl(value) {
|
|
424
|
+
return typeof value === 'string' && /^https?:\/\/.+/i.test(value.trim());
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function validateListingFields(m) {
|
|
428
|
+
const fail = (msg) => { throw new Error(`system.json: ${msg}`); };
|
|
429
|
+
|
|
430
|
+
if (m.summary != null) {
|
|
431
|
+
if (typeof m.summary !== 'string') fail('"summary" deve ser texto.');
|
|
432
|
+
if (m.summary.length > 132) fail(`"summary" deve ter no máximo 132 caracteres (tem ${m.summary.length}).`);
|
|
433
|
+
}
|
|
434
|
+
if (m.purpose != null) {
|
|
435
|
+
if (typeof m.purpose !== 'string') fail('"purpose" deve ser texto.');
|
|
436
|
+
if (m.purpose.length > 280) fail(`"purpose" deve ter no máximo 280 caracteres (tem ${m.purpose.length}).`);
|
|
437
|
+
}
|
|
438
|
+
if (m.category != null && typeof m.category !== 'string') fail('"category" deve ser texto.');
|
|
439
|
+
if (m.permissions_note != null && typeof m.permissions_note !== 'string') fail('"permissions_note" deve ser texto.');
|
|
440
|
+
|
|
441
|
+
if (m.tags != null) {
|
|
442
|
+
if (!Array.isArray(m.tags) || m.tags.some((x) => typeof x !== 'string')) fail('"tags" deve ser uma lista de textos.');
|
|
443
|
+
if (m.tags.length > 10) fail('"tags" aceita no máximo 10 itens.');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (m.icon != null && !isHttpUrl(m.icon)) fail('"icon" deve ser uma URL http(s) (Opção A — imagem hospedada externamente).');
|
|
447
|
+
|
|
448
|
+
if (m.screenshots != null) {
|
|
449
|
+
if (!Array.isArray(m.screenshots)) fail('"screenshots" deve ser uma lista de URLs.');
|
|
450
|
+
if (m.screenshots.length > 5) fail('"screenshots" aceita no máximo 5 itens.');
|
|
451
|
+
for (const s of m.screenshots) {
|
|
452
|
+
if (!isHttpUrl(s)) fail('cada item de "screenshots" deve ser uma URL http(s).');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
for (const key of ['homepage_url', 'support_url', 'privacy_url']) {
|
|
457
|
+
if (m[key] != null && !isHttpUrl(m[key])) fail(`"${key}" deve ser uma URL http(s).`);
|
|
458
|
+
}
|
|
459
|
+
if (m.support_email != null) {
|
|
460
|
+
if (typeof m.support_email !== 'string' || !m.support_email.includes('@')) fail('"support_email" deve ser um e-mail válido.');
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Sincroniza a versão dos package.json do app com o system.json (fonte da
|
|
466
|
+
* verdade do publish). Sem isso o package.json fica preso (ex.: 1.0.0) e os logs
|
|
467
|
+
* mostram `app@1.0.0` enquanto a loja publica 1.2.13 — divergência que só
|
|
468
|
+
* confunde. O Play usa a versão do system.json/manifest; aqui só alinhamos os
|
|
469
|
+
* package.json (raiz + `dashboard/` em apps split-stack) pra não divergir. Roda
|
|
470
|
+
* ANTES do build/coleta, então a versão sincronizada já entra no pacote.
|
|
471
|
+
* Best-effort: package.json ausente/ilegível não bloqueia o publish.
|
|
472
|
+
*/
|
|
473
|
+
async function syncPackageVersions(dir, version, logger) {
|
|
474
|
+
const candidates = [
|
|
475
|
+
path.join(dir, 'package.json'),
|
|
476
|
+
path.join(dir, 'dashboard', 'package.json'),
|
|
477
|
+
];
|
|
478
|
+
for (const pkgPath of candidates) {
|
|
479
|
+
if (!(await exists(pkgPath))) continue;
|
|
480
|
+
let pkg;
|
|
481
|
+
try {
|
|
482
|
+
pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
483
|
+
} catch {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (pkg.version === version) continue;
|
|
487
|
+
const prev = pkg.version || '(ausente)';
|
|
488
|
+
pkg.version = version;
|
|
489
|
+
try {
|
|
490
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
491
|
+
logger.log(`package.json sincronizado: ${path.relative(dir, pkgPath) || 'package.json'} ${prev} → ${version}`);
|
|
492
|
+
} catch {
|
|
493
|
+
// best-effort — não bloqueia o publish
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
254
498
|
// ── system:package ──────────────────────────────────────────────────────────
|
|
255
499
|
|
|
256
500
|
async function runSystemPackage({ args, options, logger, t }) {
|
|
@@ -260,6 +504,11 @@ async function runSystemPackage({ args, options, logger, t }) {
|
|
|
260
504
|
const manifest = await readSystemJson(dir, t);
|
|
261
505
|
logger.log(t('system.package_manifest_ok', { slug: manifest.slug, version: manifest.version, name: manifest.name }));
|
|
262
506
|
|
|
507
|
+
// Alinha os package.json à versão do system.json antes de coletar.
|
|
508
|
+
if (!options['dry-run']) {
|
|
509
|
+
await syncPackageVersions(dir, manifest.version, logger);
|
|
510
|
+
}
|
|
511
|
+
|
|
263
512
|
logger.log(t('system.package_collecting_files'));
|
|
264
513
|
const { files, totalBytes, errors } = await collectSystemFiles(dir);
|
|
265
514
|
|
|
@@ -304,6 +553,11 @@ async function runSystemPublish({ args, options, logger, t }) {
|
|
|
304
553
|
const manifest = await readSystemJson(dir, t);
|
|
305
554
|
logger.log(t('system.package_manifest_ok', { slug: manifest.slug, version: manifest.version, name: manifest.name }));
|
|
306
555
|
|
|
556
|
+
// Alinha os package.json à versão do system.json antes de buildar/coletar.
|
|
557
|
+
if (!options['dry-run']) {
|
|
558
|
+
await syncPackageVersions(dir, manifest.version, logger);
|
|
559
|
+
}
|
|
560
|
+
|
|
307
561
|
if (buildMode) {
|
|
308
562
|
const buildCmd = manifest.build_command || 'npm run build';
|
|
309
563
|
logger.log(`Building: ${buildCmd}`);
|
|
@@ -337,6 +591,28 @@ async function runSystemPublish({ args, options, logger, t }) {
|
|
|
337
591
|
|
|
338
592
|
const visibility = options.private ? 'private' : 'public';
|
|
339
593
|
const paid = Boolean(options.paid);
|
|
594
|
+
|
|
595
|
+
// app-licensing-revenue-share (Fase 5 / BR-01): app PAID exige preço na fonte
|
|
596
|
+
// única (system.json). Falha cedo aqui, antes do upload — o servidor também
|
|
597
|
+
// recusa na criação. Aceita priceInCents | price_in_cents | price (em unidades).
|
|
598
|
+
if (paid) {
|
|
599
|
+
const priceCents =
|
|
600
|
+
Number(manifest.priceInCents) ||
|
|
601
|
+
Number(manifest.price_in_cents) ||
|
|
602
|
+
(Number(manifest.price) > 0 ? Math.round(Number(manifest.price) * 100) : 0);
|
|
603
|
+
if (!priceCents || priceCents <= 0) {
|
|
604
|
+
throw new Error(
|
|
605
|
+
'App PAID exige preço: defina "priceInCents" (centavos) ou "price" no system.json antes de publicar com --paid.'
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
// SF-alrs-03: visibility=PAID e preço são geridos no banco do aioson.com (dashboard
|
|
609
|
+
// da loja) — NÃO via este flag. O --paid só valida o preço localmente; não publica
|
|
610
|
+
// o app como pago por si só.
|
|
611
|
+
logger.log(
|
|
612
|
+
'Nota: visibility=PAID e preço são definidos no aioson.com (dashboard da loja). O flag --paid valida o preço localmente, mas não publica o app como pago sozinho.'
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
340
616
|
const ws = await readWorkspace(dir);
|
|
341
617
|
|
|
342
618
|
// Lista de emails autorizados a instalar quando visibility=private.
|
|
@@ -351,9 +627,13 @@ async function runSystemPublish({ args, options, logger, t }) {
|
|
|
351
627
|
|
|
352
628
|
logger.log('Creating ZIP package...');
|
|
353
629
|
const zipBuffer = await createZipBuffer(files);
|
|
354
|
-
|
|
630
|
+
// 10 MB: a ofuscação (string-array/base64 + alta entropia) incha e comprime
|
|
631
|
+
// pior que a fonte, então 2 MB era apertado demais pra `--build`. O servidor
|
|
632
|
+
// (aioson-com) não impõe limite na rota (só `request.json()`).
|
|
633
|
+
const MAX_ZIP_BYTES = 10 * 1024 * 1024;
|
|
355
634
|
if (zipBuffer.length > MAX_ZIP_BYTES) {
|
|
356
|
-
|
|
635
|
+
const mb = (MAX_ZIP_BYTES / 1024 / 1024).toFixed(0);
|
|
636
|
+
throw new Error(`ZIP exceeds ${mb} MB limit (${(zipBuffer.length / 1024 / 1024).toFixed(2)} MB). Reduce the number of files or bundle size.`);
|
|
357
637
|
}
|
|
358
638
|
const zipBase64 = zipBuffer.toString('base64');
|
|
359
639
|
const zipKb = (zipBuffer.length / 1024).toFixed(1);
|
package/src/constants.js
CHANGED
|
@@ -45,6 +45,7 @@ const MANAGED_FILES = [
|
|
|
45
45
|
'.aioson/docs/squad/content-output.md',
|
|
46
46
|
'.aioson/docs/squad/session-operations.md',
|
|
47
47
|
'.aioson/docs/squad/genome-bindings.md',
|
|
48
|
+
'.aioson/docs/feature-expansion-taxonomy.md',
|
|
48
49
|
'.aioson/docs/product/conversation-playbook.md',
|
|
49
50
|
'.aioson/docs/product/research-loop.md',
|
|
50
51
|
'.aioson/docs/product/quality-lens.md',
|
|
@@ -52,15 +53,24 @@ const MANAGED_FILES = [
|
|
|
52
53
|
'.aioson/docs/deyvin/continuity-recovery.md',
|
|
53
54
|
'.aioson/docs/deyvin/pair-execution.md',
|
|
54
55
|
'.aioson/docs/deyvin/runtime-handoffs.md',
|
|
55
|
-
'.aioson/docs/deyvin/debugging-escalation.md',
|
|
56
|
-
'.aioson/docs/handoff-persistence.md',
|
|
57
|
-
'.aioson/docs/
|
|
56
|
+
'.aioson/docs/deyvin/debugging-escalation.md',
|
|
57
|
+
'.aioson/docs/handoff-persistence.md',
|
|
58
|
+
'.aioson/docs/integrations/dashboard-app-form-publish-mapping.md',
|
|
59
|
+
'.aioson/docs/play/README.md',
|
|
60
|
+
'.aioson/docs/play/agent-usage-guide.md',
|
|
61
|
+
'.aioson/docs/play/app-compatibility-guide.md',
|
|
62
|
+
'.aioson/docs/play/auth-services-and-testing.md',
|
|
63
|
+
'.aioson/docs/play/llm-data-and-bindings.md',
|
|
64
|
+
'.aioson/docs/play/manifest-and-runtime.md',
|
|
65
|
+
'.aioson/docs/play/source-map.md',
|
|
66
|
+
'.aioson/docs/sheldon/research-loop.md',
|
|
58
67
|
'.aioson/docs/sheldon/web-intelligence.md',
|
|
59
68
|
'.aioson/docs/sheldon/quality-lens.md',
|
|
60
69
|
'.aioson/docs/sheldon/enrichment-paths.md',
|
|
61
70
|
'.aioson/docs/sheldon/harness-contract.md',
|
|
62
71
|
'.aioson/docs/dev/stack-conventions.md',
|
|
63
72
|
'.aioson/docs/dev/execution-discipline.md',
|
|
73
|
+
'.aioson/docs/dev/simple-plan-lane.md',
|
|
64
74
|
'.aioson/docs/quality/code-health-analysis.md',
|
|
65
75
|
'.aioson/skills/process/decision-presentation/SKILL.md',
|
|
66
76
|
'.aioson/skills/process/decision-presentation/references/jargon-map.en.yaml',
|
|
@@ -68,6 +78,9 @@ const MANAGED_FILES = [
|
|
|
68
78
|
'.aioson/skills/process/prompt-sharpener/SKILL.md',
|
|
69
79
|
'.aioson/skills/process/prompt-sharpener/references/prompt-diagnostics.md',
|
|
70
80
|
'.aioson/skills/process/prompt-sharpener/agents/openai.yaml',
|
|
81
|
+
'.aioson/skills/process/briefing-expansion-scout/SKILL.md',
|
|
82
|
+
'.aioson/skills/process/product-scope-expansion/SKILL.md',
|
|
83
|
+
'.aioson/skills/process/sheldon-expansion-audit/SKILL.md',
|
|
71
84
|
'.aioson/skills/static/laravel-conventions.md',
|
|
72
85
|
'.aioson/skills/static/tall-stack-patterns.md',
|
|
73
86
|
'.aioson/skills/static/jetstream-setup.md',
|
|
@@ -265,7 +278,7 @@ const AGENT_DEFINITIONS = [
|
|
|
265
278
|
'.aioson/context/discovery.md',
|
|
266
279
|
'.aioson/context/architecture.md'
|
|
267
280
|
],
|
|
268
|
-
output: '.aioson/context/ui-spec.md + Visual identity enrichment in prd.md or prd-{slug}.md'
|
|
281
|
+
output: '.aioson/context/ui-spec-{slug}.md (project mode: .aioson/context/ui-spec.md) + Visual identity enrichment in prd.md or prd-{slug}.md'
|
|
269
282
|
},
|
|
270
283
|
{
|
|
271
284
|
id: 'pm',
|
|
@@ -342,7 +355,7 @@ const AGENT_DEFINITIONS = [
|
|
|
342
355
|
command: '@tester',
|
|
343
356
|
path: '.aioson/agents/tester.md',
|
|
344
357
|
dependsOn: ['.aioson/context/project.context.md'],
|
|
345
|
-
output: '.aioson/context/test-inventory.md + .aioson/context/test-plan.md'
|
|
358
|
+
output: '.aioson/context/test-inventory-{slug}.md + .aioson/context/test-plan-{slug}.md (project mode: bare names)'
|
|
346
359
|
},
|
|
347
360
|
{
|
|
348
361
|
id: 'orchestrator',
|
|
@@ -431,7 +444,7 @@ const AGENT_DEFINITIONS = [
|
|
|
431
444
|
command: '@sheldon',
|
|
432
445
|
path: '.aioson/agents/sheldon.md',
|
|
433
446
|
dependsOn: ['.aioson/context/project.context.md'],
|
|
434
|
-
output: 'enriched PRD or phased execution plan'
|
|
447
|
+
output: 'enriched PRD or phased execution plan (+ sheldon-validation-{slug}.md readiness gate on MEDIUM)'
|
|
435
448
|
},
|
|
436
449
|
{
|
|
437
450
|
id: 'committer',
|