@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,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const baseEn = require('./messages/en');
|
|
6
|
+
const { exists, ensureDir } = require('../utils');
|
|
7
|
+
|
|
8
|
+
function isValidLocaleCode(locale) {
|
|
9
|
+
return /^[a-z]{2,3}([_-][a-z0-9]{2,8})?$/i.test(locale);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeLocaleFileName(locale) {
|
|
13
|
+
return String(locale).trim().toLowerCase().replace(/_/g, '-');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function createLocaleScaffold(locale, options = {}) {
|
|
17
|
+
const normalized = normalizeLocaleFileName(locale);
|
|
18
|
+
const messagesDir = options.messagesDir || path.join(__dirname, 'messages');
|
|
19
|
+
const force = Boolean(options.force);
|
|
20
|
+
const dryRun = Boolean(options.dryRun);
|
|
21
|
+
|
|
22
|
+
if (!isValidLocaleCode(normalized)) {
|
|
23
|
+
const error = new Error(`Invalid locale code: ${locale}`);
|
|
24
|
+
error.code = 'INVALID_LOCALE';
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (normalized === 'en') {
|
|
29
|
+
const error = new Error('Cannot scaffold locale "en" because it is the base dictionary.');
|
|
30
|
+
error.code = 'BASE_LOCALE';
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await ensureDir(messagesDir);
|
|
35
|
+
const filePath = path.join(messagesDir, `${normalized}.js`);
|
|
36
|
+
const alreadyExists = await exists(filePath);
|
|
37
|
+
|
|
38
|
+
if (alreadyExists && !force) {
|
|
39
|
+
const error = new Error(`Locale file already exists: ${filePath}`);
|
|
40
|
+
error.code = 'LOCALE_EXISTS';
|
|
41
|
+
error.path = filePath;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const payload = JSON.stringify(baseEn, null, 2);
|
|
46
|
+
const content = `'use strict';\n\n// TODO: Replace English strings with ${normalized} translations.\nmodule.exports = ${payload};\n`;
|
|
47
|
+
|
|
48
|
+
if (!dryRun) {
|
|
49
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
locale: normalized,
|
|
54
|
+
filePath,
|
|
55
|
+
overwritten: alreadyExists,
|
|
56
|
+
dryRun
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
createLocaleScaffold,
|
|
62
|
+
isValidLocaleCode,
|
|
63
|
+
normalizeLocaleFileName
|
|
64
|
+
};
|
package/src/installer.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { MANAGED_FILES } = require('./constants');
|
|
6
|
+
const { getCliVersion } = require('./version');
|
|
7
|
+
const { exists, ensureDir, copyFileWithDir, nowStamp, toRelativeSafe } = require('./utils');
|
|
8
|
+
const { ensureProjectRuntime } = require('./execution-gateway');
|
|
9
|
+
|
|
10
|
+
const ROOT_DIR = path.join(__dirname, '..');
|
|
11
|
+
const TEMPLATE_DIR = path.join(ROOT_DIR, 'template');
|
|
12
|
+
const PROJECT_LOCAL_FILES = new Set(['aioson-models.json']);
|
|
13
|
+
const GITIGNORE_POLICY_LINES = [
|
|
14
|
+
'# AIOSON — keep shared project memory and tool contracts',
|
|
15
|
+
'!AGENTS.md',
|
|
16
|
+
'!CLAUDE.md',
|
|
17
|
+
'!OPENCODE.md',
|
|
18
|
+
'!.claude/',
|
|
19
|
+
'!.claude/**',
|
|
20
|
+
'!.gemini/',
|
|
21
|
+
'!.gemini/**',
|
|
22
|
+
'!.aioson/',
|
|
23
|
+
'!.aioson/**',
|
|
24
|
+
'# AIOSON — local-only artifacts',
|
|
25
|
+
'aioson-models.json',
|
|
26
|
+
'.aioson/backups/',
|
|
27
|
+
'.aioson/cloud-imports/',
|
|
28
|
+
'.aioson/runtime/',
|
|
29
|
+
'.aioson/mcp/presets/',
|
|
30
|
+
'.aioson/install.json',
|
|
31
|
+
'.aioson/mcp/servers.local.json'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
async function detectExistingInstall(targetDir) {
|
|
35
|
+
return exists(path.join(targetDir, '.aioson/config.md'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function ensureGitignoreEntry(targetDir, entry) {
|
|
39
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
40
|
+
let content = '';
|
|
41
|
+
if (await exists(gitignorePath)) {
|
|
42
|
+
content = await fs.readFile(gitignorePath, 'utf8');
|
|
43
|
+
}
|
|
44
|
+
if (content.split('\n').some(line => line.trim() === entry)) return false;
|
|
45
|
+
const separator = content.length > 0 && !content.endsWith('\n') ? '\n' : '';
|
|
46
|
+
await fs.writeFile(gitignorePath, `${content}${separator}${entry}\n`, 'utf8');
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function ensureGitignoreEntries(targetDir, entries) {
|
|
51
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
52
|
+
let content = '';
|
|
53
|
+
if (await exists(gitignorePath)) {
|
|
54
|
+
content = await fs.readFile(gitignorePath, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const existing = new Set(content.split('\n').map((line) => line.trim()));
|
|
58
|
+
const missing = entries.filter((entry) => !existing.has(entry));
|
|
59
|
+
if (missing.length === 0) return 0;
|
|
60
|
+
|
|
61
|
+
const separator = content.length > 0 && !content.endsWith('\n') ? '\n' : '';
|
|
62
|
+
await fs.writeFile(gitignorePath, `${content}${separator}${missing.join('\n')}\n`, 'utf8');
|
|
63
|
+
return missing.length;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function countProjectFiles(targetDir) {
|
|
67
|
+
const SKIP = new Set(['.git', 'node_modules', 'vendor', '.aioson', 'dist', 'build', '__pycache__']);
|
|
68
|
+
let count = 0;
|
|
69
|
+
async function walk(dir) {
|
|
70
|
+
let entries;
|
|
71
|
+
try { entries = await fs.readdir(dir, { withFileTypes: true }); } catch { return; }
|
|
72
|
+
for (const e of entries) {
|
|
73
|
+
if (SKIP.has(e.name)) continue;
|
|
74
|
+
if (e.isDirectory()) await walk(path.join(dir, e.name));
|
|
75
|
+
else count++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
await walk(targetDir);
|
|
79
|
+
return count;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function listFilesRecursive(dir) {
|
|
83
|
+
const out = [];
|
|
84
|
+
|
|
85
|
+
async function walk(current) {
|
|
86
|
+
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const full = path.join(current, entry.name);
|
|
89
|
+
if (entry.isDirectory()) {
|
|
90
|
+
await walk(full);
|
|
91
|
+
} else {
|
|
92
|
+
out.push(full);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await walk(dir);
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function shouldSkipTemplatePath(rel) {
|
|
102
|
+
if (rel.startsWith('.aioson/context/')) return true;
|
|
103
|
+
if (rel === '.aioson/context/.gitkeep') return false;
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function writeInstallMetadata(targetDir, action, frameworkDetection) {
|
|
108
|
+
const metaPath = path.join(targetDir, '.aioson/install.json');
|
|
109
|
+
await ensureDir(path.dirname(metaPath));
|
|
110
|
+
const existing = (await exists(metaPath)) ? JSON.parse(await fs.readFile(metaPath, 'utf8')) : {};
|
|
111
|
+
|
|
112
|
+
const version = await getCliVersion();
|
|
113
|
+
const data = {
|
|
114
|
+
...existing,
|
|
115
|
+
managed_by: 'aioson',
|
|
116
|
+
template_version: version,
|
|
117
|
+
last_action: action,
|
|
118
|
+
last_action_at: new Date().toISOString(),
|
|
119
|
+
framework_detected: frameworkDetection || existing.framework_detected || null
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
await fs.writeFile(metaPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function backupManagedFile(targetDir, relPath, backupRoot) {
|
|
126
|
+
const source = path.join(targetDir, relPath);
|
|
127
|
+
if (!(await exists(source))) return null;
|
|
128
|
+
|
|
129
|
+
const dest = path.join(backupRoot, relPath);
|
|
130
|
+
await copyFileWithDir(source, dest);
|
|
131
|
+
return dest;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function installTemplate(targetDir, options = {}) {
|
|
135
|
+
const {
|
|
136
|
+
overwrite = false,
|
|
137
|
+
dryRun = false,
|
|
138
|
+
mode = 'install',
|
|
139
|
+
backupOnOverwrite = mode === 'update',
|
|
140
|
+
frameworkDetection = null
|
|
141
|
+
} = options;
|
|
142
|
+
|
|
143
|
+
await ensureDir(targetDir);
|
|
144
|
+
const existingInstall = await detectExistingInstall(targetDir);
|
|
145
|
+
|
|
146
|
+
const templateFiles = await listFilesRecursive(TEMPLATE_DIR);
|
|
147
|
+
const copied = [];
|
|
148
|
+
const skipped = [];
|
|
149
|
+
const backedUp = [];
|
|
150
|
+
let runtime = null;
|
|
151
|
+
|
|
152
|
+
let backupRoot = null;
|
|
153
|
+
if (backupOnOverwrite) {
|
|
154
|
+
backupRoot = path.join(targetDir, '.aioson/backups', nowStamp());
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (const absPath of templateFiles) {
|
|
158
|
+
const rel = toRelativeSafe(TEMPLATE_DIR, absPath);
|
|
159
|
+
if (shouldSkipTemplatePath(rel)) {
|
|
160
|
+
skipped.push({ path: rel, reason: 'context-protected' });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const dest = path.join(targetDir, rel);
|
|
165
|
+
const destExists = await exists(dest);
|
|
166
|
+
|
|
167
|
+
if (destExists && PROJECT_LOCAL_FILES.has(rel)) {
|
|
168
|
+
skipped.push({ path: rel, reason: 'project-local' });
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (destExists && !overwrite && mode !== 'update') {
|
|
173
|
+
skipped.push({ path: rel, reason: 'already-exists' });
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (destExists && mode === 'update' && backupOnOverwrite && MANAGED_FILES.includes(rel)) {
|
|
178
|
+
if (!dryRun) {
|
|
179
|
+
const backupPath = await backupManagedFile(targetDir, rel, backupRoot);
|
|
180
|
+
if (backupPath) backedUp.push(toRelativeSafe(targetDir, backupPath));
|
|
181
|
+
} else {
|
|
182
|
+
backedUp.push(toRelativeSafe(targetDir, path.join(backupRoot, rel)));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!dryRun) {
|
|
187
|
+
await copyFileWithDir(absPath, dest);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
copied.push(rel);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!dryRun) {
|
|
194
|
+
await ensureDir(path.join(targetDir, '.aioson/context/parallel'));
|
|
195
|
+
await ensureDir(path.join(targetDir, '.aioson/context'));
|
|
196
|
+
const gitkeep = path.join(targetDir, '.aioson/context/.gitkeep');
|
|
197
|
+
if (!(await exists(gitkeep))) {
|
|
198
|
+
await fs.writeFile(gitkeep, '', 'utf8');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await writeInstallMetadata(targetDir, mode, frameworkDetection);
|
|
202
|
+
|
|
203
|
+
await ensureGitignoreEntries(targetDir, GITIGNORE_POLICY_LINES);
|
|
204
|
+
|
|
205
|
+
runtime = await ensureProjectRuntime(targetDir);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Detect if this is an existing project with many files
|
|
209
|
+
const projectFileCount = await countProjectFiles(targetDir);
|
|
210
|
+
const isExistingProject = frameworkDetection && projectFileCount > 20;
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
existingInstall,
|
|
214
|
+
copied,
|
|
215
|
+
skipped,
|
|
216
|
+
backedUp,
|
|
217
|
+
runtime,
|
|
218
|
+
dryRun,
|
|
219
|
+
projectFileCount,
|
|
220
|
+
isExistingProject
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = {
|
|
225
|
+
TEMPLATE_DIR,
|
|
226
|
+
detectExistingInstall,
|
|
227
|
+
installTemplate,
|
|
228
|
+
listFilesRecursive,
|
|
229
|
+
ensureGitignoreEntry,
|
|
230
|
+
ensureGitignoreEntries,
|
|
231
|
+
countProjectFiles
|
|
232
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
parseGenomeMarkdown,
|
|
5
|
+
serializeGenomeMarkdown
|
|
6
|
+
} = require('../../genome-format');
|
|
7
|
+
const { isGenomeV2, normalizeGenome } = require('../../genomes');
|
|
8
|
+
const { mergeGenomeBindings, normalizeGenomeBindings } = require('../../genomes/bindings');
|
|
9
|
+
|
|
10
|
+
function isObject(value) {
|
|
11
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function hasLegacyObjectSignals(input) {
|
|
15
|
+
return Boolean(
|
|
16
|
+
input &&
|
|
17
|
+
(
|
|
18
|
+
input.genome ||
|
|
19
|
+
input.slug ||
|
|
20
|
+
input.domain ||
|
|
21
|
+
input.title ||
|
|
22
|
+
input.name ||
|
|
23
|
+
isObject(input.sections) ||
|
|
24
|
+
Array.isArray(input.knowledge) ||
|
|
25
|
+
Array.isArray(input.mentes) ||
|
|
26
|
+
Array.isArray(input.skills)
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildSectionsFromLegacyObject(input = {}) {
|
|
32
|
+
if (isObject(input.sections)) {
|
|
33
|
+
return input.sections;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
knowledge: Array.isArray(input.knowledge) ? input.knowledge : [],
|
|
38
|
+
philosophies: Array.isArray(input.philosophies) ? input.philosophies : [],
|
|
39
|
+
mentalModels: Array.isArray(input.mentalModels) ? input.mentalModels : [],
|
|
40
|
+
heuristics: Array.isArray(input.heuristics) ? input.heuristics : [],
|
|
41
|
+
frameworks: Array.isArray(input.frameworks) ? input.frameworks : [],
|
|
42
|
+
methodologies: Array.isArray(input.methodologies) ? input.methodologies : [],
|
|
43
|
+
mentes: Array.isArray(input.mentes) ? input.mentes : Array.isArray(input.minds) ? input.minds : [],
|
|
44
|
+
skills: Array.isArray(input.skills) ? input.skills : [],
|
|
45
|
+
evidence: Array.isArray(input.evidence) ? input.evidence : [],
|
|
46
|
+
applicationNotes: Array.isArray(input.applicationNotes) ? input.applicationNotes : []
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hasExplicitModernMarkdownMarkers(input) {
|
|
51
|
+
const text = String(input || '');
|
|
52
|
+
return (
|
|
53
|
+
/\nformat:\s*genome-v[23]\s*$/im.test(text) ||
|
|
54
|
+
/\nversion:\s*[23]\s*$/im.test(text) ||
|
|
55
|
+
/\nevidence_mode:\s*.+$/im.test(text) ||
|
|
56
|
+
/\nsources_count:\s*\d+\s*$/im.test(text) ||
|
|
57
|
+
/^##\s+(Filosofias|Modelos mentais|Heurísticas|Heuristicas|Frameworks|Metodologias|Perfil Cognitivo|Estilo de Comunicação|Vieses e Pontos Cegos|Evidence|Application notes)\s*$/im.test(text)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function hasLegacyMarkdownSignals(input) {
|
|
62
|
+
const text = String(input || '');
|
|
63
|
+
return (
|
|
64
|
+
/^\s*---\s*$/m.test(text) ||
|
|
65
|
+
/\n(?:genome|slug):\s*.+$/im.test(text) ||
|
|
66
|
+
/^#\s+(Genome|Genoma)\s*:/im.test(text) ||
|
|
67
|
+
/^##\s+(O que saber|Mentes|Skills)\s*$/im.test(text)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function toCompatibleGenomeDocument(input, options = {}) {
|
|
72
|
+
return normalizeGenome({
|
|
73
|
+
...(isObject(input) ? input : {}),
|
|
74
|
+
sections: buildSectionsFromLegacyObject(input),
|
|
75
|
+
legacyFormat: false,
|
|
76
|
+
hasFrontmatter: true,
|
|
77
|
+
sourcePath: options.filePath || null
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function detectGenomeFormat(input) {
|
|
82
|
+
if (!input) return 'unknown';
|
|
83
|
+
|
|
84
|
+
if (typeof input === 'string') {
|
|
85
|
+
try {
|
|
86
|
+
if (hasExplicitModernMarkdownMarkers(input)) {
|
|
87
|
+
const parsed = parseGenomeMarkdown(input);
|
|
88
|
+
return parsed.version >= 3 || parsed.format === 'genome-v3' ? 'v3-markdown' : 'v2-markdown';
|
|
89
|
+
}
|
|
90
|
+
if (hasLegacyMarkdownSignals(input)) return 'legacy-markdown';
|
|
91
|
+
const genome = parseGenomeMarkdown(input);
|
|
92
|
+
if (genome.version >= 3 || genome.format === 'genome-v3') return 'v3-markdown';
|
|
93
|
+
if (isGenomeV2(genome)) return 'v2-markdown';
|
|
94
|
+
if (genome.legacyFormat) return 'legacy-markdown';
|
|
95
|
+
} catch {
|
|
96
|
+
return 'unknown';
|
|
97
|
+
}
|
|
98
|
+
return 'unknown';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (isObject(input)) {
|
|
102
|
+
const normalized = normalizeGenome(input);
|
|
103
|
+
if (normalized.version >= 3 || normalized.format === 'genome-v3') return 'v3-object';
|
|
104
|
+
if (isGenomeV2(input)) return 'v2-object';
|
|
105
|
+
if (hasLegacyObjectSignals(input)) return 'legacy-object';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return 'unknown';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function loadCompatibleGenome(input, options = {}) {
|
|
112
|
+
const format = detectGenomeFormat(input);
|
|
113
|
+
|
|
114
|
+
if (format === 'v2-markdown' || format === 'v3-markdown' || format === 'legacy-markdown') {
|
|
115
|
+
const parsed = parseGenomeMarkdown(input);
|
|
116
|
+
return {
|
|
117
|
+
format,
|
|
118
|
+
document: toCompatibleGenomeDocument(parsed, options),
|
|
119
|
+
migrated: format === 'legacy-markdown'
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (format === 'v2-object' || format === 'v3-object') {
|
|
124
|
+
return {
|
|
125
|
+
format,
|
|
126
|
+
document: toCompatibleGenomeDocument(input, options),
|
|
127
|
+
migrated: false
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (format === 'legacy-object') {
|
|
132
|
+
return {
|
|
133
|
+
format,
|
|
134
|
+
document: toCompatibleGenomeDocument(
|
|
135
|
+
{
|
|
136
|
+
slug: input.slug || input.genome,
|
|
137
|
+
domain: input.domain || input.title || input.name || input.slug || input.genome,
|
|
138
|
+
type: input.type,
|
|
139
|
+
language: input.language,
|
|
140
|
+
depth: input.depth,
|
|
141
|
+
version: input.version,
|
|
142
|
+
format: input.format,
|
|
143
|
+
evidenceMode: input.evidenceMode || input.evidence_mode,
|
|
144
|
+
sourceCount: input.sourceCount ?? input.sourcesCount ?? input.sources_count,
|
|
145
|
+
generated: input.generated,
|
|
146
|
+
personaSource: input.personaSource || input.persona_source,
|
|
147
|
+
personaSources: input.personaSources || input.persona_sources,
|
|
148
|
+
disc: input.disc,
|
|
149
|
+
enneagram: input.enneagram,
|
|
150
|
+
bigFive: input.bigFive || input.big_five,
|
|
151
|
+
mbti: input.mbti,
|
|
152
|
+
confidence: input.confidence,
|
|
153
|
+
profilerReport: input.profilerReport || input.profiler_report,
|
|
154
|
+
hybridMode: input.hybridMode || input.hybrid_mode,
|
|
155
|
+
sections: buildSectionsFromLegacyObject(input)
|
|
156
|
+
},
|
|
157
|
+
options
|
|
158
|
+
),
|
|
159
|
+
migrated: true
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
throw new Error('Unsupported genome format.');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function serializeCompatibleGenome(document, options = {}) {
|
|
167
|
+
return serializeGenomeMarkdown(toCompatibleGenomeDocument(document, options));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function normalizeCompatibleBindings(bindings = {}) {
|
|
171
|
+
return normalizeGenomeBindings(bindings);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function hasLegacySquadGenomeSignals(manifest = {}) {
|
|
175
|
+
return Boolean(
|
|
176
|
+
Array.isArray(manifest.genomes) ||
|
|
177
|
+
isObject(manifest.genomes) ||
|
|
178
|
+
isObject(manifest.genomeBindings) ||
|
|
179
|
+
(Array.isArray(manifest.executors) &&
|
|
180
|
+
manifest.executors.some((executor) => Array.isArray(executor && executor.genomes) && executor.genomes.length > 0))
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function normalizeLegacySquadGenomes(manifest = {}) {
|
|
185
|
+
if (!isObject(manifest)) return {};
|
|
186
|
+
if (!hasLegacySquadGenomeSignals(manifest)) return { ...manifest };
|
|
187
|
+
|
|
188
|
+
const genomeBindings = mergeGenomeBindings({
|
|
189
|
+
blueprintBindings: manifest.genomeBindings,
|
|
190
|
+
manifestBindings: manifest.genomeBindings || manifest.genomes,
|
|
191
|
+
legacyExecutors: manifest.executors
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
...manifest,
|
|
196
|
+
genomeBindings
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = {
|
|
201
|
+
detectGenomeFormat,
|
|
202
|
+
loadCompatibleGenome,
|
|
203
|
+
serializeCompatibleGenome,
|
|
204
|
+
normalizeCompatibleBindings,
|
|
205
|
+
normalizeLegacySquadGenomes
|
|
206
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { loadCompatibleGenome, serializeCompatibleGenome } = require('./compat');
|
|
6
|
+
|
|
7
|
+
async function ensureDir(dirPath) {
|
|
8
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function fileExists(filePath) {
|
|
12
|
+
try {
|
|
13
|
+
await fs.access(filePath);
|
|
14
|
+
return true;
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function migrateGenomeFile(filePath, options = {}) {
|
|
21
|
+
const {
|
|
22
|
+
dryRun = true,
|
|
23
|
+
write = false,
|
|
24
|
+
backup = true,
|
|
25
|
+
backupDir = null
|
|
26
|
+
} = options;
|
|
27
|
+
|
|
28
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
29
|
+
const loaded = loadCompatibleGenome(raw, { filePath });
|
|
30
|
+
const output = serializeCompatibleGenome(loaded.document, { filePath });
|
|
31
|
+
const changed = loaded.migrated;
|
|
32
|
+
|
|
33
|
+
const result = {
|
|
34
|
+
filePath,
|
|
35
|
+
detectedFormat: loaded.format,
|
|
36
|
+
migrated: loaded.migrated,
|
|
37
|
+
changed,
|
|
38
|
+
output,
|
|
39
|
+
backupPath: null
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (!write || dryRun || !changed) {
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (backup) {
|
|
47
|
+
const targetBackupDir = backupDir || path.join(path.dirname(filePath), '.backup');
|
|
48
|
+
await ensureDir(targetBackupDir);
|
|
49
|
+
const backupPath = path.join(targetBackupDir, `${path.basename(filePath)}.bak`);
|
|
50
|
+
await fs.writeFile(backupPath, raw, 'utf8');
|
|
51
|
+
result.backupPath = backupPath;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await fs.writeFile(filePath, output, 'utf8');
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function migrateGenomeDirectory(dirPath, options = {}) {
|
|
59
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
60
|
+
const results = [];
|
|
61
|
+
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
if (!entry.isFile()) continue;
|
|
64
|
+
if (!entry.name.endsWith('.md')) continue;
|
|
65
|
+
if (entry.name.endsWith('.meta.json')) continue;
|
|
66
|
+
|
|
67
|
+
const filePath = path.join(dirPath, entry.name);
|
|
68
|
+
results.push(await migrateGenomeFile(filePath, options));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
directory: dirPath,
|
|
73
|
+
total: results.length,
|
|
74
|
+
changed: results.filter((item) => item.changed).length,
|
|
75
|
+
results
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function migrateIfLegacyGenome(filePath, options = {}) {
|
|
80
|
+
if (!(await fileExists(filePath))) {
|
|
81
|
+
throw new Error(`Genome file not found: ${filePath}`);
|
|
82
|
+
}
|
|
83
|
+
return migrateGenomeFile(filePath, options);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
migrateGenomeFile,
|
|
88
|
+
migrateGenomeDirectory,
|
|
89
|
+
migrateIfLegacyGenome
|
|
90
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { normalizeLegacySquadGenomes } = require('../genomes/compat');
|
|
6
|
+
|
|
7
|
+
async function ensureDir(dirPath) {
|
|
8
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function repairSquadManifestGenomeBindings(manifestPath, options = {}) {
|
|
12
|
+
const {
|
|
13
|
+
dryRun = true,
|
|
14
|
+
write = false,
|
|
15
|
+
backup = true
|
|
16
|
+
} = options;
|
|
17
|
+
|
|
18
|
+
const raw = await fs.readFile(manifestPath, 'utf8');
|
|
19
|
+
const manifest = JSON.parse(raw);
|
|
20
|
+
const repaired = normalizeLegacySquadGenomes(manifest);
|
|
21
|
+
const changed = JSON.stringify(manifest) !== JSON.stringify(repaired);
|
|
22
|
+
|
|
23
|
+
const result = {
|
|
24
|
+
manifestPath,
|
|
25
|
+
changed,
|
|
26
|
+
before: manifest,
|
|
27
|
+
after: repaired,
|
|
28
|
+
backupPath: null
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (!changed || dryRun || !write) {
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (backup) {
|
|
36
|
+
const backupDir = path.join(path.dirname(manifestPath), '.backup');
|
|
37
|
+
await ensureDir(backupDir);
|
|
38
|
+
const backupPath = path.join(backupDir, `${path.basename(manifestPath)}.bak`);
|
|
39
|
+
await fs.writeFile(backupPath, raw, 'utf8');
|
|
40
|
+
result.backupPath = backupPath;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await fs.writeFile(manifestPath, `${JSON.stringify(repaired, null, 2)}\n`, 'utf8');
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
repairSquadManifestGenomeBindings
|
|
49
|
+
};
|