aios-core 2.1.6 → 2.2.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/.aios-core/core/README.md +229 -229
- package/.aios-core/core/data/agent-config-requirements.yaml +368 -368
- package/.aios-core/core/data/aios-kb.md +923 -923
- package/.aios-core/core/data/workflow-patterns.yaml +267 -267
- package/.aios-core/core/docs/SHARD-TRANSLATION-GUIDE.md +335 -335
- package/.aios-core/core/docs/component-creation-guide.md +457 -457
- package/.aios-core/core/docs/session-update-pattern.md +307 -307
- package/.aios-core/core/docs/template-syntax.md +266 -266
- package/.aios-core/core/docs/troubleshooting-guide.md +624 -624
- package/.aios-core/core/elicitation/elicitation-engine.js +1 -1
- package/.aios-core/core/index.esm.js +42 -42
- package/.aios-core/core/index.js +1 -1
- package/.aios-core/core/migration/migration-config.yaml +83 -83
- package/.aios-core/core/migration/module-mapping.yaml +89 -89
- package/.aios-core/core/quality-gates/layer2-pr-automation.js +1 -1
- package/.aios-core/core/quality-gates/quality-gate-config.yaml +86 -86
- package/.aios-core/core/registry/README.md +179 -179
- package/.aios-core/core/utils/security-utils.js +1 -1
- package/.aios-core/core-config.yaml +391 -382
- package/.aios-core/data/agent-config-requirements.yaml +368 -368
- package/.aios-core/data/aios-kb.md +923 -923
- package/.aios-core/data/technical-preferences.md +3 -3
- package/.aios-core/data/workflow-patterns.yaml +267 -267
- package/.aios-core/development/README.md +142 -142
- package/.aios-core/development/agent-teams/team-all.yaml +15 -15
- package/.aios-core/development/agent-teams/team-fullstack.yaml +18 -18
- package/.aios-core/development/agent-teams/team-ide-minimal.yaml +10 -10
- package/.aios-core/development/agent-teams/team-no-ui.yaml +13 -13
- package/.aios-core/development/agent-teams/team-qa-focused.yaml +155 -155
- package/.aios-core/development/agents/aios-master.md +339 -339
- package/.aios-core/development/agents/analyst.md +195 -195
- package/.aios-core/development/agents/architect.md +359 -359
- package/.aios-core/development/agents/data-engineer.md +468 -468
- package/.aios-core/development/agents/dev.md +390 -390
- package/.aios-core/development/agents/devops.md +398 -398
- package/.aios-core/development/agents/pm.md +198 -198
- package/.aios-core/development/agents/po.md +256 -256
- package/.aios-core/development/agents/qa.md +312 -312
- package/.aios-core/development/agents/sm.md +220 -220
- package/.aios-core/development/agents/ux-design-expert.md +451 -451
- package/.aios-core/development/scripts/greeting-config-cli.js +85 -85
- package/.aios-core/development/tasks/add-mcp.md +319 -319
- package/.aios-core/development/tasks/advanced-elicitation.md +318 -318
- package/.aios-core/development/tasks/analyst-facilitate-brainstorming.md +341 -341
- package/.aios-core/development/tasks/analyze-framework.md +696 -696
- package/.aios-core/development/tasks/analyze-performance.md +637 -637
- package/.aios-core/development/tasks/apply-qa-fixes.md +340 -340
- package/.aios-core/development/tasks/architect-analyze-impact.md +826 -826
- package/.aios-core/development/tasks/audit-codebase.md +429 -429
- package/.aios-core/development/tasks/audit-tailwind-config.md +270 -270
- package/.aios-core/development/tasks/audit-utilities.md +358 -358
- package/.aios-core/development/tasks/bootstrap-shadcn-library.md +286 -286
- package/.aios-core/development/tasks/brownfield-create-epic.md +485 -485
- package/.aios-core/development/tasks/brownfield-create-story.md +356 -356
- package/.aios-core/development/tasks/build-component.md +478 -478
- package/.aios-core/development/tasks/calculate-roi.md +455 -455
- package/.aios-core/development/tasks/ci-cd-configuration.md +764 -764
- package/.aios-core/development/tasks/cleanup-utilities.md +670 -670
- package/.aios-core/development/tasks/collaborative-edit.md +1108 -1108
- package/.aios-core/development/tasks/compose-molecule.md +284 -284
- package/.aios-core/development/tasks/consolidate-patterns.md +414 -414
- package/.aios-core/development/tasks/correct-course.md +279 -279
- package/.aios-core/development/tasks/create-agent.md +321 -321
- package/.aios-core/development/tasks/create-brownfield-story.md +726 -726
- package/.aios-core/development/tasks/create-deep-research-prompt.md +498 -498
- package/.aios-core/development/tasks/create-doc.md +316 -316
- package/.aios-core/development/tasks/create-next-story.md +774 -774
- package/.aios-core/development/tasks/create-suite.md +283 -283
- package/.aios-core/development/tasks/create-task.md +371 -371
- package/.aios-core/development/tasks/create-workflow.md +370 -370
- package/.aios-core/development/tasks/db-analyze-hotpaths.md +572 -572
- package/.aios-core/development/tasks/db-apply-migration.md +381 -381
- package/.aios-core/development/tasks/db-bootstrap.md +642 -642
- package/.aios-core/development/tasks/db-domain-modeling.md +693 -693
- package/.aios-core/development/tasks/db-dry-run.md +293 -293
- package/.aios-core/development/tasks/db-env-check.md +260 -260
- package/.aios-core/development/tasks/db-expansion-pack-integration.md +663 -663
- package/.aios-core/development/tasks/db-explain.md +631 -631
- package/.aios-core/development/tasks/db-impersonate.md +495 -495
- package/.aios-core/development/tasks/db-load-csv.md +593 -593
- package/.aios-core/development/tasks/db-policy-apply.md +653 -653
- package/.aios-core/development/tasks/db-rls-audit.md +411 -411
- package/.aios-core/development/tasks/db-rollback.md +739 -739
- package/.aios-core/development/tasks/db-run-sql.md +613 -613
- package/.aios-core/development/tasks/db-schema-audit.md +1011 -1011
- package/.aios-core/development/tasks/db-seed.md +390 -390
- package/.aios-core/development/tasks/db-smoke-test.md +351 -351
- package/.aios-core/development/tasks/db-snapshot.md +569 -569
- package/.aios-core/development/tasks/db-supabase-setup.md +712 -712
- package/.aios-core/development/tasks/db-verify-order.md +515 -515
- package/.aios-core/development/tasks/deprecate-component.md +956 -956
- package/.aios-core/development/tasks/dev-apply-qa-fixes.md +318 -318
- package/.aios-core/development/tasks/dev-backlog-debt.md +469 -469
- package/.aios-core/development/tasks/dev-develop-story.md +846 -846
- package/.aios-core/development/tasks/dev-improve-code-quality.md +872 -872
- package/.aios-core/development/tasks/dev-optimize-performance.md +1033 -1033
- package/.aios-core/development/tasks/dev-suggest-refactoring.md +870 -870
- package/.aios-core/development/tasks/dev-validate-next-story.md +348 -348
- package/.aios-core/development/tasks/document-project.md +552 -552
- package/.aios-core/development/tasks/environment-bootstrap.md +1311 -1311
- package/.aios-core/development/tasks/execute-checklist.md +301 -301
- package/.aios-core/development/tasks/export-design-tokens-dtcg.md +274 -274
- package/.aios-core/development/tasks/extend-pattern.md +269 -269
- package/.aios-core/development/tasks/extract-tokens.md +467 -467
- package/.aios-core/development/tasks/facilitate-brainstorming-session.md +518 -518
- package/.aios-core/development/tasks/generate-ai-frontend-prompt.md +260 -260
- package/.aios-core/development/tasks/generate-documentation.md +284 -284
- package/.aios-core/development/tasks/generate-migration-strategy.md +522 -522
- package/.aios-core/development/tasks/generate-shock-report.md +501 -501
- package/.aios-core/development/tasks/github-devops-github-pr-automation.md +427 -427
- package/.aios-core/development/tasks/github-devops-pre-push-quality-gate.md +733 -733
- package/.aios-core/development/tasks/github-devops-repository-cleanup.md +374 -374
- package/.aios-core/development/tasks/github-devops-version-management.md +483 -483
- package/.aios-core/development/tasks/improve-self.md +822 -822
- package/.aios-core/development/tasks/index-docs.md +387 -387
- package/.aios-core/development/tasks/init-project-status.md +506 -506
- package/.aios-core/development/tasks/integrate-expansion-pack.md +314 -314
- package/.aios-core/development/tasks/kb-mode-interaction.md +283 -283
- package/.aios-core/development/tasks/learn-patterns.md +900 -900
- package/.aios-core/development/tasks/mcp-workflow.md +437 -437
- package/.aios-core/development/tasks/modify-agent.md +381 -381
- package/.aios-core/development/tasks/modify-task.md +424 -424
- package/.aios-core/development/tasks/modify-workflow.md +465 -465
- package/.aios-core/development/tasks/po-backlog-add.md +370 -370
- package/.aios-core/development/tasks/po-manage-story-backlog.md +523 -523
- package/.aios-core/development/tasks/po-pull-story-from-clickup.md +540 -540
- package/.aios-core/development/tasks/po-pull-story.md +316 -316
- package/.aios-core/development/tasks/po-stories-index.md +351 -351
- package/.aios-core/development/tasks/po-sync-story-to-clickup.md +457 -457
- package/.aios-core/development/tasks/po-sync-story.md +303 -303
- package/.aios-core/development/tasks/pr-automation.md +701 -701
- package/.aios-core/development/tasks/propose-modification.md +842 -842
- package/.aios-core/development/tasks/qa-backlog-add-followup.md +425 -425
- package/.aios-core/development/tasks/qa-gate.md +373 -373
- package/.aios-core/development/tasks/qa-generate-tests.md +1174 -1174
- package/.aios-core/development/tasks/qa-nfr-assess.md +557 -557
- package/.aios-core/development/tasks/qa-review-proposal.md +1157 -1157
- package/.aios-core/development/tasks/qa-review-story.md +682 -682
- package/.aios-core/development/tasks/qa-risk-profile.md +566 -566
- package/.aios-core/development/tasks/qa-run-tests.md +277 -277
- package/.aios-core/development/tasks/qa-test-design.md +387 -387
- package/.aios-core/development/tasks/qa-trace-requirements.md +476 -476
- package/.aios-core/development/tasks/release-management.md +723 -723
- package/.aios-core/development/tasks/security-audit.md +554 -554
- package/.aios-core/development/tasks/security-scan.md +790 -790
- package/.aios-core/development/tasks/setup-database.md +741 -741
- package/.aios-core/development/tasks/setup-design-system.md +462 -462
- package/.aios-core/development/tasks/setup-github.md +874 -874
- package/.aios-core/development/tasks/setup-llm-routing.md +1 -1
- package/.aios-core/development/tasks/setup-mcp-docker.md +584 -584
- package/.aios-core/development/tasks/setup-project-docs.md +1 -1
- package/.aios-core/development/tasks/shard-doc.md +537 -537
- package/.aios-core/development/tasks/sm-create-next-story.md +480 -480
- package/.aios-core/development/tasks/sync-documentation.md +864 -864
- package/.aios-core/development/tasks/tailwind-upgrade.md +294 -294
- package/.aios-core/development/tasks/test-as-user.md +621 -621
- package/.aios-core/development/tasks/test-validation-task.md +171 -171
- package/.aios-core/development/tasks/undo-last.md +346 -346
- package/.aios-core/development/tasks/update-manifest.md +409 -409
- package/.aios-core/development/tasks/ux-create-wireframe.md +617 -617
- package/.aios-core/development/tasks/ux-ds-scan-artifact.md +672 -672
- package/.aios-core/development/tasks/ux-user-research.md +559 -559
- package/.aios-core/development/tasks/validate-next-story.md +422 -422
- package/.aios-core/development/workflows/README.md +83 -83
- package/.aios-core/development/workflows/brownfield-fullstack.yaml +297 -297
- package/.aios-core/development/workflows/brownfield-service.yaml +187 -187
- package/.aios-core/development/workflows/brownfield-ui.yaml +197 -197
- package/.aios-core/development/workflows/greenfield-fullstack.yaml +333 -333
- package/.aios-core/development/workflows/greenfield-service.yaml +206 -206
- package/.aios-core/development/workflows/greenfield-ui.yaml +235 -235
- package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +335 -335
- package/.aios-core/docs/component-creation-guide.md +457 -457
- package/.aios-core/docs/session-update-pattern.md +307 -307
- package/.aios-core/docs/standards/AGENT-PERSONALIZATION-STANDARD-V1.md +572 -572
- package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-QUICK-REFERENCE.md +185 -185
- package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-V2.1.md +354 -354
- package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +1963 -1963
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +821 -821
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +1190 -1190
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +439 -439
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.2-SUMMARY.md +1339 -1339
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +5398 -5398
- package/.aios-core/docs/standards/EXECUTOR-DECISION-TREE.md +697 -697
- package/.aios-core/docs/standards/OPEN-SOURCE-VS-SERVICE-DIFFERENCES.md +511 -511
- package/.aios-core/docs/standards/QUALITY-GATES-SPECIFICATION.md +556 -556
- package/.aios-core/docs/standards/STANDARDS-INDEX.md +210 -210
- package/.aios-core/docs/standards/STORY-TEMPLATE-V2-SPECIFICATION.md +550 -550
- package/.aios-core/docs/standards/TASK-FORMAT-SPECIFICATION-V1.md +1414 -1414
- package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +523 -523
- package/.aios-core/docs/template-syntax.md +266 -266
- package/.aios-core/docs/troubleshooting-guide.md +624 -624
- package/.aios-core/index.esm.js +15 -15
- package/.aios-core/index.js +1 -1
- package/.aios-core/infrastructure/README.md +126 -126
- package/.aios-core/infrastructure/integrations/pm-adapters/README.md +59 -59
- package/.aios-core/infrastructure/scripts/approval-workflow.js +1 -1
- package/.aios-core/infrastructure/scripts/batch-creator.js +1 -1
- package/.aios-core/infrastructure/scripts/component-generator.js +3 -3
- package/.aios-core/infrastructure/scripts/component-metadata.js +1 -1
- package/.aios-core/infrastructure/scripts/component-search.js +1 -1
- package/.aios-core/infrastructure/scripts/coverage-analyzer.js +1 -1
- package/.aios-core/infrastructure/scripts/dependency-analyzer.js +1 -1
- package/.aios-core/infrastructure/scripts/dependency-impact-analyzer.js +1 -1
- package/.aios-core/infrastructure/scripts/framework-analyzer.js +1 -1
- package/.aios-core/infrastructure/scripts/improvement-engine.js +1 -1
- package/.aios-core/infrastructure/scripts/llm-routing/install-llm-routing.js +26 -13
- package/.aios-core/infrastructure/scripts/llm-routing/templates/claude-free-tracked.cmd +127 -0
- package/.aios-core/infrastructure/scripts/llm-routing/templates/claude-free-tracked.sh +108 -0
- package/.aios-core/infrastructure/scripts/llm-routing/templates/deepseek-proxy.cmd +71 -0
- package/.aios-core/infrastructure/scripts/llm-routing/templates/deepseek-proxy.sh +65 -0
- package/.aios-core/infrastructure/scripts/llm-routing/templates/deepseek-usage.cmd +51 -0
- package/.aios-core/infrastructure/scripts/llm-routing/templates/deepseek-usage.sh +16 -0
- package/.aios-core/infrastructure/scripts/llm-routing/usage-tracker/index.js +549 -0
- package/.aios-core/infrastructure/scripts/modification-risk-assessment.js +1 -1
- package/.aios-core/infrastructure/scripts/performance-analyzer.js +1 -1
- package/.aios-core/infrastructure/scripts/pm-adapter.js +134 -134
- package/.aios-core/infrastructure/scripts/repository-detector.js +3 -3
- package/.aios-core/infrastructure/scripts/template-engine.js +1 -1
- package/.aios-core/infrastructure/scripts/template-validator.js +1 -1
- package/.aios-core/infrastructure/scripts/test-generator.js +1 -1
- package/.aios-core/infrastructure/scripts/test-quality-assessment.js +1 -1
- package/.aios-core/infrastructure/scripts/transaction-manager.js +1 -1
- package/.aios-core/infrastructure/scripts/usage-analytics.js +1 -1
- package/.aios-core/infrastructure/scripts/visual-impact-generator.js +2 -2
- package/.aios-core/infrastructure/templates/github-workflows/README.md +109 -109
- package/.aios-core/infrastructure/tests/regression-suite-v2.md +621 -621
- package/.aios-core/infrastructure/tools/README.md +222 -222
- package/.aios-core/infrastructure/tools/cli/github-cli.yaml +200 -200
- package/.aios-core/infrastructure/tools/cli/railway-cli.yaml +260 -260
- package/.aios-core/infrastructure/tools/cli/supabase-cli.yaml +224 -224
- package/.aios-core/infrastructure/tools/local/ffmpeg.yaml +261 -261
- package/.aios-core/infrastructure/tools/mcp/21st-dev-magic.yaml +127 -127
- package/.aios-core/infrastructure/tools/mcp/browser.yaml +103 -103
- package/.aios-core/infrastructure/tools/mcp/clickup.yaml +534 -534
- package/.aios-core/infrastructure/tools/mcp/context7.yaml +78 -78
- package/.aios-core/infrastructure/tools/mcp/desktop-commander.yaml +180 -180
- package/.aios-core/infrastructure/tools/mcp/exa.yaml +103 -103
- package/.aios-core/infrastructure/tools/mcp/google-workspace.yaml +930 -930
- package/.aios-core/infrastructure/tools/mcp/n8n.yaml +551 -551
- package/.aios-core/infrastructure/tools/mcp/supabase.yaml +808 -808
- package/.aios-core/install-manifest.yaml +347 -347
- package/.aios-core/product/README.md +56 -56
- package/.aios-core/product/checklists/accessibility-wcag-checklist.md +80 -0
- package/.aios-core/product/checklists/architect-checklist.md +443 -443
- package/.aios-core/product/checklists/change-checklist.md +182 -182
- package/.aios-core/product/checklists/component-quality-checklist.md +74 -0
- package/.aios-core/product/checklists/database-design-checklist.md +119 -119
- package/.aios-core/product/checklists/dba-predeploy-checklist.md +97 -97
- package/.aios-core/product/checklists/dba-rollback-checklist.md +99 -99
- package/.aios-core/product/checklists/migration-readiness-checklist.md +75 -0
- package/.aios-core/product/checklists/pattern-audit-checklist.md +88 -0
- package/.aios-core/product/checklists/pm-checklist.md +375 -375
- package/.aios-core/product/checklists/po-master-checklist.md +441 -441
- package/.aios-core/product/checklists/pre-push-checklist.md +108 -108
- package/.aios-core/product/checklists/release-checklist.md +122 -122
- package/.aios-core/product/checklists/story-dod-checklist.md +101 -101
- package/.aios-core/product/checklists/story-draft-checklist.md +215 -215
- package/.aios-core/product/data/atomic-design-principles.md +108 -0
- package/.aios-core/product/data/brainstorming-techniques.md +36 -36
- package/.aios-core/product/data/consolidation-algorithms.md +142 -0
- package/.aios-core/product/data/database-best-practices.md +182 -0
- package/.aios-core/product/data/design-token-best-practices.md +107 -0
- package/.aios-core/product/data/elicitation-methods.md +134 -134
- package/.aios-core/product/data/integration-patterns.md +207 -0
- package/.aios-core/product/data/migration-safety-guide.md +329 -0
- package/.aios-core/product/data/mode-selection-best-practices.md +471 -471
- package/.aios-core/product/data/postgres-tuning-guide.md +300 -0
- package/.aios-core/product/data/rls-security-patterns.md +333 -0
- package/.aios-core/product/data/roi-calculation-guide.md +142 -0
- package/.aios-core/product/data/supabase-patterns.md +330 -0
- package/.aios-core/product/data/test-levels-framework.md +148 -148
- package/.aios-core/product/data/test-priorities-matrix.md +174 -174
- package/.aios-core/product/data/wcag-compliance-guide.md +267 -0
- package/.aios-core/product/templates/1mcp-config.yaml +225 -225
- package/.aios-core/product/templates/activation-instructions-inline-greeting.yaml +63 -63
- package/.aios-core/product/templates/activation-instructions-template.md +258 -258
- package/.aios-core/product/templates/agent-template.yaml +120 -120
- package/.aios-core/product/templates/architecture-tmpl.yaml +650 -650
- package/.aios-core/product/templates/brainstorming-output-tmpl.yaml +155 -155
- package/.aios-core/product/templates/brownfield-architecture-tmpl.yaml +475 -475
- package/.aios-core/product/templates/brownfield-prd-tmpl.yaml +279 -279
- package/.aios-core/product/templates/changelog-template.md +134 -134
- package/.aios-core/product/templates/command-rationalization-matrix.md +152 -152
- package/.aios-core/product/templates/competitor-analysis-tmpl.yaml +292 -292
- package/.aios-core/product/templates/design-story-tmpl.yaml +587 -587
- package/.aios-core/product/templates/ds-artifact-analysis.md +70 -70
- package/.aios-core/product/templates/front-end-architecture-tmpl.yaml +205 -205
- package/.aios-core/product/templates/front-end-spec-tmpl.yaml +348 -348
- package/.aios-core/product/templates/fullstack-architecture-tmpl.yaml +804 -804
- package/.aios-core/product/templates/github-pr-template.md +67 -67
- package/.aios-core/product/templates/gordon-mcp.yaml +140 -140
- package/.aios-core/product/templates/ide-rules/antigravity-rules.md +115 -115
- package/.aios-core/product/templates/ide-rules/claude-rules.md +221 -221
- package/.aios-core/product/templates/ide-rules/cline-rules.md +84 -84
- package/.aios-core/product/templates/ide-rules/copilot-rules.md +92 -92
- package/.aios-core/product/templates/ide-rules/cursor-rules.md +115 -115
- package/.aios-core/product/templates/ide-rules/gemini-rules.md +85 -85
- package/.aios-core/product/templates/ide-rules/roo-rules.md +86 -86
- package/.aios-core/product/templates/ide-rules/trae-rules.md +104 -104
- package/.aios-core/product/templates/ide-rules/windsurf-rules.md +80 -80
- package/.aios-core/product/templates/index-strategy-tmpl.yaml +53 -53
- package/.aios-core/product/templates/market-research-tmpl.yaml +251 -251
- package/.aios-core/product/templates/mcp-workflow.js +271 -271
- package/.aios-core/product/templates/migration-plan-tmpl.yaml +1022 -1022
- package/.aios-core/product/templates/migration-strategy-tmpl.md +524 -524
- package/.aios-core/product/templates/personalized-agent-template.md +258 -258
- package/.aios-core/product/templates/personalized-checklist-template.md +340 -340
- package/.aios-core/product/templates/personalized-task-template-v2.md +905 -905
- package/.aios-core/product/templates/personalized-task-template.md +344 -344
- package/.aios-core/product/templates/personalized-template-file.yaml +322 -322
- package/.aios-core/product/templates/personalized-workflow-template.yaml +460 -460
- package/.aios-core/product/templates/prd-tmpl.yaml +201 -201
- package/.aios-core/product/templates/project-brief-tmpl.yaml +220 -220
- package/.aios-core/product/templates/qa-gate-tmpl.yaml +240 -240
- package/.aios-core/product/templates/rls-policies-tmpl.yaml +1203 -1203
- package/.aios-core/product/templates/schema-design-tmpl.yaml +428 -428
- package/.aios-core/product/templates/state-persistence-tmpl.yaml +219 -219
- package/.aios-core/product/templates/story-tmpl.yaml +331 -331
- package/.aios-core/product/templates/task-execution-report.md +495 -495
- package/.aios-core/product/templates/task-template.md +122 -122
- package/.aios-core/product/templates/token-exports-tailwind-tmpl.js +395 -395
- package/.aios-core/product/templates/tokens-schema-tmpl.yaml +305 -305
- package/.aios-core/product/templates/workflow-template.yaml +133 -133
- package/.aios-core/scripts/README.md +354 -354
- package/.aios-core/scripts/aios-doc-template.md +325 -325
- package/.aios-core/scripts/elicitation-engine.js +1 -1
- package/.aios-core/scripts/test-template-system.js +1 -1
- package/.aios-core/scripts/workflow-management.md +69 -69
- package/.aios-core/user-guide.md +1413 -1413
- package/.aios-core/working-in-the-brownfield.md +361 -361
- package/LICENSE +1 -1
- package/README.md +702 -703
- package/bin/aios-init-old.js +3 -3
- package/bin/aios-init-v4.js +1 -1
- package/bin/aios-init.backup-v1.1.4.js +1 -1
- package/bin/aios-init.js +3 -3
- package/bin/aios.js +279 -279
- package/bin/utils/install-errors.js +339 -339
- package/bin/utils/install-transaction.js +445 -445
- package/index.d.ts +18 -18
- package/index.esm.js +20 -20
- package/index.js +6 -6
- package/package.json +8 -10
- package/packages/installer/src/config/templates/env-template.js +27 -4
- package/packages/installer/src/detection/detect-project-type.js +81 -81
- package/packages/installer/tests/integration/wizard-detection.test.js +8 -6
- package/packages/installer/tests/unit/env-template.test.js +8 -8
- package/src/config/ide-configs.js +1 -1
- package/src/wizard/feedback.js +2 -2
- package/src/wizard/index.js +1 -1
- package/src/wizard/validation/report-generator.js +1 -1
- package/src/wizard/validation/troubleshooting-system.js +13 -13
- package/.aios-core/infrastructure/scripts/_archived/final-todo-count.js +0 -122
- package/.aios-core/infrastructure/scripts/_archived/fix-yaml-formatting.js +0 -89
- package/.aios-core/infrastructure/scripts/_archived/migration-generator.js +0 -780
- package/.aios-core/infrastructure/scripts/_archived/migration-path-generator.js +0 -950
- package/.aios-core/infrastructure/scripts/_archived/phase2-entrada-saida-errors.js +0 -425
- package/.aios-core/infrastructure/scripts/_archived/phase2-spot-check.js +0 -132
- package/.aios-core/infrastructure/scripts/_archived/phase3-tools-scripts-validation.js +0 -381
- package/.aios-core/infrastructure/scripts/_archived/phase4-metadata-performance.js +0 -203
- package/.aios-core/infrastructure/scripts/_archived/test-yaml-parsing.js +0 -24
- package/.aios-core/infrastructure/scripts/_archived/verify-yaml-fix.js +0 -51
- package/.aios-core/tasks/find-component.md.legacy +0 -391
- package/.aios-core/tasks/generate-commit-message.md.legacy +0 -426
- package/.aios-core/tasks/generate-migration.md.legacy +0 -382
- package/.aios-core/tasks/rollback-modification.md.legacy +0 -307
- package/.aios-core/tasks/update-tests.md.legacy +0 -283
|
@@ -1,445 +1,445 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* InstallTransaction - Transaction manager for AIOS installer
|
|
3
|
-
*
|
|
4
|
-
* Provides atomic installation operations with automatic rollback on failure.
|
|
5
|
-
* Backs up files before modification and restores them if installation fails.
|
|
6
|
-
*
|
|
7
|
-
* @module bin/utils/install-transaction
|
|
8
|
-
* @see Story 1.9 - Error Handling & Rollback
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const fs = require('fs-extra');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const crypto = require('crypto');
|
|
14
|
-
|
|
15
|
-
// Error classification constants
|
|
16
|
-
const ERROR_TYPES = {
|
|
17
|
-
CRITICAL: 'CRITICAL',
|
|
18
|
-
RECOVERABLE: 'RECOVERABLE',
|
|
19
|
-
WARNING: 'WARNING',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
// Critical errors that trigger immediate rollback
|
|
23
|
-
const CRITICAL_ERRORS = [
|
|
24
|
-
'EACCES', // Permission denied
|
|
25
|
-
'ENOSPC', // No space left on device
|
|
26
|
-
'EROFS', // Read-only file system
|
|
27
|
-
'ENOTDIR', // Not a directory
|
|
28
|
-
'EISDIR', // Is a directory
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
class InstallTransaction {
|
|
32
|
-
/**
|
|
33
|
-
* Create a new installation transaction
|
|
34
|
-
*
|
|
35
|
-
* @param {Object} options - Transaction options
|
|
36
|
-
* @param {string} [options.backupDir] - Custom backup directory path
|
|
37
|
-
* @param {string} [options.logFile] - Custom log file path
|
|
38
|
-
*/
|
|
39
|
-
constructor(options = {}) {
|
|
40
|
-
this.backupDir = options.backupDir || path.join(process.cwd(), '.aios-backup', this._generateTimestamp());
|
|
41
|
-
this.logFile = options.logFile || path.join(process.cwd(), '.aios-install.log');
|
|
42
|
-
this.backups = []; // [{original, backup, hash, isDirectory}]
|
|
43
|
-
this.operations = []; // [{timestamp, level, message}]
|
|
44
|
-
this.isCommitted = false;
|
|
45
|
-
this.isRolledBack = false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Generate timestamp for backup directory naming
|
|
50
|
-
* Format: 2025-01-23_14-30-45-123Z
|
|
51
|
-
*
|
|
52
|
-
* @private
|
|
53
|
-
* @returns {string} ISO timestamp with safe filename characters
|
|
54
|
-
*/
|
|
55
|
-
_generateTimestamp() {
|
|
56
|
-
// Replace colons, dots, and T separator for Windows compatibility
|
|
57
|
-
return new Date().toISOString()
|
|
58
|
-
.replace(/:/g, '-')
|
|
59
|
-
.replace(/\./g, '-')
|
|
60
|
-
.replace('T', '_');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Backup a single file before modification
|
|
65
|
-
*
|
|
66
|
-
* @param {string} filePath - Path to file to backup
|
|
67
|
-
* @throws {Error} If backup operation fails
|
|
68
|
-
*
|
|
69
|
-
* @example
|
|
70
|
-
* await transaction.backup('package.json');
|
|
71
|
-
*/
|
|
72
|
-
async backup(filePath) {
|
|
73
|
-
const originalPath = path.resolve(filePath);
|
|
74
|
-
|
|
75
|
-
// Check if file exists
|
|
76
|
-
if (!(await fs.pathExists(originalPath))) {
|
|
77
|
-
this.log('WARN', `File not found for backup: ${filePath}`);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Prevent symlink attacks (security)
|
|
82
|
-
const stats = await fs.lstat(originalPath);
|
|
83
|
-
if (stats.isSymbolicLink()) {
|
|
84
|
-
throw new Error(`Symlink detected in backup path - potential security risk: ${filePath}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Skip if already backed up
|
|
88
|
-
if (this.backups.find((b) => b.original === originalPath)) {
|
|
89
|
-
this.log('DEBUG', `File already backed up: ${filePath}`);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Generate unique backup filename using path hash to prevent collisions
|
|
94
|
-
// e.g., /app/config.json and /data/config.json won't overwrite each other
|
|
95
|
-
const pathHash = crypto.createHash('sha256').update(originalPath).digest('hex').substring(0, 8);
|
|
96
|
-
const backupFilename = `${path.basename(filePath)}.${pathHash}.backup`;
|
|
97
|
-
const backupPath = path.join(this.backupDir, backupFilename);
|
|
98
|
-
await fs.ensureDir(this.backupDir);
|
|
99
|
-
|
|
100
|
-
// Set restrictive permissions on backup directory (owner only)
|
|
101
|
-
try {
|
|
102
|
-
await fs.chmod(this.backupDir, 0o700);
|
|
103
|
-
} catch (error) {
|
|
104
|
-
// Windows doesn't support chmod - log warning but continue
|
|
105
|
-
this.log('DEBUG', `Could not set backup directory permissions: ${error.message}`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
await fs.copy(originalPath, backupPath);
|
|
109
|
-
|
|
110
|
-
const hash = await this._calculateHash(originalPath);
|
|
111
|
-
this.backups.push({ original: originalPath, backup: backupPath, hash });
|
|
112
|
-
this.log('INFO', `Backed up: ${filePath}`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Backup entire directory recursively
|
|
117
|
-
*
|
|
118
|
-
* @param {string} dirPath - Path to directory to backup
|
|
119
|
-
* @throws {Error} If backup operation fails
|
|
120
|
-
*
|
|
121
|
-
* @example
|
|
122
|
-
* await transaction.backupDirectory('.aios-core/');
|
|
123
|
-
*/
|
|
124
|
-
async backupDirectory(dirPath) {
|
|
125
|
-
const originalPath = path.resolve(dirPath);
|
|
126
|
-
|
|
127
|
-
if (!(await fs.pathExists(originalPath))) {
|
|
128
|
-
this.log('WARN', `Directory not found for backup: ${dirPath}`);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Prevent symlink attacks (security) - use lstat to detect symlinks
|
|
133
|
-
const stats = await fs.lstat(originalPath);
|
|
134
|
-
if (stats.isSymbolicLink()) {
|
|
135
|
-
throw new Error(`Symlink detected in backup path - potential security risk: ${dirPath}`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (!stats.isDirectory()) {
|
|
139
|
-
throw new Error(`Path is not a directory: ${dirPath}`);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Skip if already backed up
|
|
143
|
-
if (this.backups.find((b) => b.original === originalPath)) {
|
|
144
|
-
this.log('DEBUG', `Directory already backed up: ${dirPath}`);
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Generate unique backup dirname using path hash to prevent collisions
|
|
149
|
-
const pathHash = crypto.createHash('sha256').update(originalPath).digest('hex').substring(0, 8);
|
|
150
|
-
const backupDirname = `${path.basename(dirPath)}.${pathHash}`;
|
|
151
|
-
const backupPath = path.join(this.backupDir, backupDirname);
|
|
152
|
-
await fs.ensureDir(this.backupDir);
|
|
153
|
-
|
|
154
|
-
// Set restrictive permissions
|
|
155
|
-
try {
|
|
156
|
-
await fs.chmod(this.backupDir, 0o700);
|
|
157
|
-
} catch (error) {
|
|
158
|
-
this.log('DEBUG', `Could not set backup directory permissions: ${error.message}`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
await fs.copy(originalPath, backupPath, { recursive: true });
|
|
162
|
-
|
|
163
|
-
this.backups.push({ original: originalPath, backup: backupPath, isDirectory: true });
|
|
164
|
-
this.log('INFO', `Backed up directory: ${dirPath}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Calculate SHA-256 hash of file for verification
|
|
169
|
-
*
|
|
170
|
-
* @private
|
|
171
|
-
* @param {string} filePath - Path to file
|
|
172
|
-
* @returns {Promise<string>} Hex-encoded hash
|
|
173
|
-
*/
|
|
174
|
-
async _calculateHash(filePath) {
|
|
175
|
-
const content = await fs.readFile(filePath);
|
|
176
|
-
return crypto.createHash('sha256').update(content).digest('hex');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Verify backup integrity by comparing hashes
|
|
181
|
-
*
|
|
182
|
-
* @private
|
|
183
|
-
* @param {Object} backup - Backup entry {original, backup, hash}
|
|
184
|
-
* @returns {Promise<boolean>} True if backup is valid
|
|
185
|
-
*/
|
|
186
|
-
async _verifyBackup(backup) {
|
|
187
|
-
if (backup.isDirectory) {
|
|
188
|
-
// For directories, just check existence
|
|
189
|
-
return await fs.pathExists(backup.backup);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (!(await fs.pathExists(backup.backup))) {
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const backupHash = await this._calculateHash(backup.backup);
|
|
197
|
-
return backupHash === backup.hash;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Rollback all changes by restoring backups
|
|
202
|
-
*
|
|
203
|
-
* @returns {Promise<boolean>} True if rollback succeeded, false if partial failure
|
|
204
|
-
*
|
|
205
|
-
* @example
|
|
206
|
-
* try {
|
|
207
|
-
* await installAIOS();
|
|
208
|
-
* } catch (error) {
|
|
209
|
-
* await transaction.rollback();
|
|
210
|
-
* }
|
|
211
|
-
*/
|
|
212
|
-
async rollback() {
|
|
213
|
-
if (this.isCommitted) {
|
|
214
|
-
this.log('ERROR', 'Cannot rollback: transaction already committed');
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (this.isRolledBack) {
|
|
219
|
-
this.log('WARN', 'Transaction already rolled back');
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
this.log('ERROR', 'Installation failed. Starting rollback...');
|
|
224
|
-
let rollbackSuccess = true;
|
|
225
|
-
const failedRestores = [];
|
|
226
|
-
|
|
227
|
-
// Restore in reverse order (LIFO - last backup restored first)
|
|
228
|
-
for (const backup of [...this.backups].reverse()) {
|
|
229
|
-
try {
|
|
230
|
-
// Verify backup integrity before restore
|
|
231
|
-
const isValid = await this._verifyBackup(backup);
|
|
232
|
-
if (!isValid) {
|
|
233
|
-
this.log('ERROR', `Backup verification failed for ${backup.original}`);
|
|
234
|
-
failedRestores.push(backup.original);
|
|
235
|
-
rollbackSuccess = false;
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Atomic restore: Copy to temp first, then move to original
|
|
240
|
-
// This prevents data loss if copy fails
|
|
241
|
-
const tempPath = `${backup.original}.restore-temp`;
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
// Copy backup to temporary location
|
|
245
|
-
await fs.copy(backup.backup, tempPath, { recursive: backup.isDirectory });
|
|
246
|
-
|
|
247
|
-
// Remove failed installation artifacts (safe now that restore is ready)
|
|
248
|
-
if (await fs.pathExists(backup.original)) {
|
|
249
|
-
await fs.remove(backup.original);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Atomic move from temp to original
|
|
253
|
-
await fs.move(tempPath, backup.original, { overwrite: true });
|
|
254
|
-
} finally {
|
|
255
|
-
// Cleanup temp file if it still exists (error case)
|
|
256
|
-
if (await fs.pathExists(tempPath)) {
|
|
257
|
-
await fs.remove(tempPath);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
this.log('INFO', `Restored: ${backup.original}`);
|
|
262
|
-
} catch (error) {
|
|
263
|
-
this.log('ERROR', `Failed to restore ${backup.original}: ${error.message}`);
|
|
264
|
-
failedRestores.push(backup.original);
|
|
265
|
-
rollbackSuccess = false;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Cleanup backup directory
|
|
270
|
-
try {
|
|
271
|
-
await fs.remove(this.backupDir);
|
|
272
|
-
this.log('INFO', 'Backup directory cleaned up');
|
|
273
|
-
} catch (error) {
|
|
274
|
-
this.log('WARN', `Failed to cleanup backup directory: ${error.message}`);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
this.isRolledBack = true;
|
|
278
|
-
|
|
279
|
-
if (failedRestores.length > 0) {
|
|
280
|
-
this.log('ERROR', `Rollback completed with errors. Failed to restore: ${failedRestores.join(', ')}`);
|
|
281
|
-
} else {
|
|
282
|
-
this.log('INFO', 'Rollback completed successfully');
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return rollbackSuccess;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Commit transaction and cleanup backups (installation succeeded)
|
|
290
|
-
*
|
|
291
|
-
* @example
|
|
292
|
-
* await transaction.commit();
|
|
293
|
-
* console.log('Installation successful!');
|
|
294
|
-
*/
|
|
295
|
-
async commit() {
|
|
296
|
-
if (this.isRolledBack) {
|
|
297
|
-
this.log('ERROR', 'Cannot commit: transaction already rolled back');
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (this.isCommitted) {
|
|
302
|
-
this.log('WARN', 'Transaction already committed');
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
this.log('INFO', 'Installation successful. Cleaning up backups...');
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
-
await fs.remove(this.backupDir);
|
|
310
|
-
this.log('INFO', 'Backups cleaned up successfully');
|
|
311
|
-
} catch (error) {
|
|
312
|
-
this.log('WARN', `Failed to cleanup backups: ${error.message}`);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
this.isCommitted = true;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Log message with timestamp and level
|
|
320
|
-
* Sanitizes credentials before writing to log file
|
|
321
|
-
*
|
|
322
|
-
* @param {string} level - Log level (INFO, WARN, ERROR, DEBUG)
|
|
323
|
-
* @param {string} message - Log message
|
|
324
|
-
*/
|
|
325
|
-
log(level, message) {
|
|
326
|
-
const timestamp = new Date().toISOString();
|
|
327
|
-
const logLine = `[${timestamp}] [${level}] ${message}\n`;
|
|
328
|
-
|
|
329
|
-
// Sanitize credentials (API keys, tokens, passwords)
|
|
330
|
-
const sanitized = this._sanitizeCredentials(logLine);
|
|
331
|
-
|
|
332
|
-
// Write to log file (append mode)
|
|
333
|
-
try {
|
|
334
|
-
fs.appendFileSync(this.logFile, sanitized);
|
|
335
|
-
} catch (error) {
|
|
336
|
-
// If logging fails, don't crash - just write to console
|
|
337
|
-
console.error(`Failed to write to log file: ${error.message}`);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Store in memory for programmatic access
|
|
341
|
-
this.operations.push({ timestamp, level, message });
|
|
342
|
-
|
|
343
|
-
// Rotate log if it exceeds 10MB
|
|
344
|
-
this._rotateLogIfNeeded();
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Sanitize sensitive data from log messages
|
|
349
|
-
* Prevents API keys, tokens, and passwords from being logged
|
|
350
|
-
*
|
|
351
|
-
* @private
|
|
352
|
-
* @param {string} text - Text to sanitize
|
|
353
|
-
* @returns {string} Sanitized text
|
|
354
|
-
*/
|
|
355
|
-
_sanitizeCredentials(text) {
|
|
356
|
-
// Pattern 1: Long hex strings (API keys, tokens)
|
|
357
|
-
let sanitized = text.replace(/[a-f0-9]{32,}/gi, '[REDACTED]');
|
|
358
|
-
|
|
359
|
-
// Pattern 2: Bearer tokens
|
|
360
|
-
sanitized = sanitized.replace(/Bearer\s+[^\s]+/gi, 'Bearer [REDACTED]');
|
|
361
|
-
|
|
362
|
-
// Pattern 3: Password fields (password=xxx, pwd=xxx)
|
|
363
|
-
sanitized = sanitized.replace(/(password|pwd|token|key|secret|auth)[:=]\s*[^\s,}]+/gi, '$1=[REDACTED]');
|
|
364
|
-
|
|
365
|
-
// Pattern 4: Environment variables that look like secrets
|
|
366
|
-
sanitized = sanitized.replace(/([A-Z_]+_(?:KEY|TOKEN|SECRET|PASSWORD|AUTH))[:=]\s*[^\s,}]+/gi, '$1=[REDACTED]');
|
|
367
|
-
|
|
368
|
-
return sanitized;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Rotate log file if it exceeds 10MB
|
|
373
|
-
* Keeps last 5 logs
|
|
374
|
-
*
|
|
375
|
-
* @private
|
|
376
|
-
*/
|
|
377
|
-
_rotateLogIfNeeded() {
|
|
378
|
-
try {
|
|
379
|
-
// Check if log file exists before trying to get stats
|
|
380
|
-
if (!fs.existsSync(this.logFile)) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const stats = fs.statSync(this.logFile);
|
|
385
|
-
const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB
|
|
386
|
-
const MAX_LOG_FILES = 5;
|
|
387
|
-
|
|
388
|
-
if (stats.size < MAX_LOG_SIZE) {
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Rotate existing logs (log.4 -> log.5, log.3 -> log.4, etc.)
|
|
393
|
-
for (let i = MAX_LOG_FILES - 1; i >= 1; i--) {
|
|
394
|
-
const oldPath = `${this.logFile}.${i}`;
|
|
395
|
-
const newPath = `${this.logFile}.${i + 1}`;
|
|
396
|
-
|
|
397
|
-
if (fs.existsSync(oldPath)) {
|
|
398
|
-
if (i === MAX_LOG_FILES - 1) {
|
|
399
|
-
fs.unlinkSync(oldPath); // Delete oldest log
|
|
400
|
-
} else {
|
|
401
|
-
fs.renameSync(oldPath, newPath);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Move current log to .1
|
|
407
|
-
fs.renameSync(this.logFile, `${this.logFile}.1`);
|
|
408
|
-
|
|
409
|
-
this.log('INFO', 'Log file rotated');
|
|
410
|
-
} catch (error) {
|
|
411
|
-
// Rotation failure is non-critical
|
|
412
|
-
console.error(`Log rotation failed: ${error.message}`);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Classify error type based on error code
|
|
418
|
-
*
|
|
419
|
-
* @param {Error} error - Error object
|
|
420
|
-
* @returns {string} ERROR_TYPES constant (CRITICAL, RECOVERABLE, WARNING)
|
|
421
|
-
*/
|
|
422
|
-
classifyError(error) {
|
|
423
|
-
if (!error.code) {
|
|
424
|
-
return ERROR_TYPES.RECOVERABLE;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
if (CRITICAL_ERRORS.includes(error.code)) {
|
|
428
|
-
return ERROR_TYPES.CRITICAL;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
return ERROR_TYPES.RECOVERABLE;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Check if error is critical (requires rollback)
|
|
436
|
-
*
|
|
437
|
-
* @param {Error} error - Error object
|
|
438
|
-
* @returns {boolean} True if error is critical
|
|
439
|
-
*/
|
|
440
|
-
isCriticalError(error) {
|
|
441
|
-
return this.classifyError(error) === ERROR_TYPES.CRITICAL;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
module.exports = { InstallTransaction, ERROR_TYPES, CRITICAL_ERRORS };
|
|
1
|
+
/**
|
|
2
|
+
* InstallTransaction - Transaction manager for AIOS installer
|
|
3
|
+
*
|
|
4
|
+
* Provides atomic installation operations with automatic rollback on failure.
|
|
5
|
+
* Backs up files before modification and restores them if installation fails.
|
|
6
|
+
*
|
|
7
|
+
* @module bin/utils/install-transaction
|
|
8
|
+
* @see Story 1.9 - Error Handling & Rollback
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs-extra');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const crypto = require('crypto');
|
|
14
|
+
|
|
15
|
+
// Error classification constants
|
|
16
|
+
const ERROR_TYPES = {
|
|
17
|
+
CRITICAL: 'CRITICAL',
|
|
18
|
+
RECOVERABLE: 'RECOVERABLE',
|
|
19
|
+
WARNING: 'WARNING',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Critical errors that trigger immediate rollback
|
|
23
|
+
const CRITICAL_ERRORS = [
|
|
24
|
+
'EACCES', // Permission denied
|
|
25
|
+
'ENOSPC', // No space left on device
|
|
26
|
+
'EROFS', // Read-only file system
|
|
27
|
+
'ENOTDIR', // Not a directory
|
|
28
|
+
'EISDIR', // Is a directory
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
class InstallTransaction {
|
|
32
|
+
/**
|
|
33
|
+
* Create a new installation transaction
|
|
34
|
+
*
|
|
35
|
+
* @param {Object} options - Transaction options
|
|
36
|
+
* @param {string} [options.backupDir] - Custom backup directory path
|
|
37
|
+
* @param {string} [options.logFile] - Custom log file path
|
|
38
|
+
*/
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
this.backupDir = options.backupDir || path.join(process.cwd(), '.aios-backup', this._generateTimestamp());
|
|
41
|
+
this.logFile = options.logFile || path.join(process.cwd(), '.aios-install.log');
|
|
42
|
+
this.backups = []; // [{original, backup, hash, isDirectory}]
|
|
43
|
+
this.operations = []; // [{timestamp, level, message}]
|
|
44
|
+
this.isCommitted = false;
|
|
45
|
+
this.isRolledBack = false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generate timestamp for backup directory naming
|
|
50
|
+
* Format: 2025-01-23_14-30-45-123Z
|
|
51
|
+
*
|
|
52
|
+
* @private
|
|
53
|
+
* @returns {string} ISO timestamp with safe filename characters
|
|
54
|
+
*/
|
|
55
|
+
_generateTimestamp() {
|
|
56
|
+
// Replace colons, dots, and T separator for Windows compatibility
|
|
57
|
+
return new Date().toISOString()
|
|
58
|
+
.replace(/:/g, '-')
|
|
59
|
+
.replace(/\./g, '-')
|
|
60
|
+
.replace('T', '_');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Backup a single file before modification
|
|
65
|
+
*
|
|
66
|
+
* @param {string} filePath - Path to file to backup
|
|
67
|
+
* @throws {Error} If backup operation fails
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* await transaction.backup('package.json');
|
|
71
|
+
*/
|
|
72
|
+
async backup(filePath) {
|
|
73
|
+
const originalPath = path.resolve(filePath);
|
|
74
|
+
|
|
75
|
+
// Check if file exists
|
|
76
|
+
if (!(await fs.pathExists(originalPath))) {
|
|
77
|
+
this.log('WARN', `File not found for backup: ${filePath}`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Prevent symlink attacks (security)
|
|
82
|
+
const stats = await fs.lstat(originalPath);
|
|
83
|
+
if (stats.isSymbolicLink()) {
|
|
84
|
+
throw new Error(`Symlink detected in backup path - potential security risk: ${filePath}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Skip if already backed up
|
|
88
|
+
if (this.backups.find((b) => b.original === originalPath)) {
|
|
89
|
+
this.log('DEBUG', `File already backed up: ${filePath}`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Generate unique backup filename using path hash to prevent collisions
|
|
94
|
+
// e.g., /app/config.json and /data/config.json won't overwrite each other
|
|
95
|
+
const pathHash = crypto.createHash('sha256').update(originalPath).digest('hex').substring(0, 8);
|
|
96
|
+
const backupFilename = `${path.basename(filePath)}.${pathHash}.backup`;
|
|
97
|
+
const backupPath = path.join(this.backupDir, backupFilename);
|
|
98
|
+
await fs.ensureDir(this.backupDir);
|
|
99
|
+
|
|
100
|
+
// Set restrictive permissions on backup directory (owner only)
|
|
101
|
+
try {
|
|
102
|
+
await fs.chmod(this.backupDir, 0o700);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
// Windows doesn't support chmod - log warning but continue
|
|
105
|
+
this.log('DEBUG', `Could not set backup directory permissions: ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await fs.copy(originalPath, backupPath);
|
|
109
|
+
|
|
110
|
+
const hash = await this._calculateHash(originalPath);
|
|
111
|
+
this.backups.push({ original: originalPath, backup: backupPath, hash });
|
|
112
|
+
this.log('INFO', `Backed up: ${filePath}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Backup entire directory recursively
|
|
117
|
+
*
|
|
118
|
+
* @param {string} dirPath - Path to directory to backup
|
|
119
|
+
* @throws {Error} If backup operation fails
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* await transaction.backupDirectory('.aios-core/');
|
|
123
|
+
*/
|
|
124
|
+
async backupDirectory(dirPath) {
|
|
125
|
+
const originalPath = path.resolve(dirPath);
|
|
126
|
+
|
|
127
|
+
if (!(await fs.pathExists(originalPath))) {
|
|
128
|
+
this.log('WARN', `Directory not found for backup: ${dirPath}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Prevent symlink attacks (security) - use lstat to detect symlinks
|
|
133
|
+
const stats = await fs.lstat(originalPath);
|
|
134
|
+
if (stats.isSymbolicLink()) {
|
|
135
|
+
throw new Error(`Symlink detected in backup path - potential security risk: ${dirPath}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!stats.isDirectory()) {
|
|
139
|
+
throw new Error(`Path is not a directory: ${dirPath}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Skip if already backed up
|
|
143
|
+
if (this.backups.find((b) => b.original === originalPath)) {
|
|
144
|
+
this.log('DEBUG', `Directory already backed up: ${dirPath}`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Generate unique backup dirname using path hash to prevent collisions
|
|
149
|
+
const pathHash = crypto.createHash('sha256').update(originalPath).digest('hex').substring(0, 8);
|
|
150
|
+
const backupDirname = `${path.basename(dirPath)}.${pathHash}`;
|
|
151
|
+
const backupPath = path.join(this.backupDir, backupDirname);
|
|
152
|
+
await fs.ensureDir(this.backupDir);
|
|
153
|
+
|
|
154
|
+
// Set restrictive permissions
|
|
155
|
+
try {
|
|
156
|
+
await fs.chmod(this.backupDir, 0o700);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
this.log('DEBUG', `Could not set backup directory permissions: ${error.message}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await fs.copy(originalPath, backupPath, { recursive: true });
|
|
162
|
+
|
|
163
|
+
this.backups.push({ original: originalPath, backup: backupPath, isDirectory: true });
|
|
164
|
+
this.log('INFO', `Backed up directory: ${dirPath}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Calculate SHA-256 hash of file for verification
|
|
169
|
+
*
|
|
170
|
+
* @private
|
|
171
|
+
* @param {string} filePath - Path to file
|
|
172
|
+
* @returns {Promise<string>} Hex-encoded hash
|
|
173
|
+
*/
|
|
174
|
+
async _calculateHash(filePath) {
|
|
175
|
+
const content = await fs.readFile(filePath);
|
|
176
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Verify backup integrity by comparing hashes
|
|
181
|
+
*
|
|
182
|
+
* @private
|
|
183
|
+
* @param {Object} backup - Backup entry {original, backup, hash}
|
|
184
|
+
* @returns {Promise<boolean>} True if backup is valid
|
|
185
|
+
*/
|
|
186
|
+
async _verifyBackup(backup) {
|
|
187
|
+
if (backup.isDirectory) {
|
|
188
|
+
// For directories, just check existence
|
|
189
|
+
return await fs.pathExists(backup.backup);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!(await fs.pathExists(backup.backup))) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const backupHash = await this._calculateHash(backup.backup);
|
|
197
|
+
return backupHash === backup.hash;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Rollback all changes by restoring backups
|
|
202
|
+
*
|
|
203
|
+
* @returns {Promise<boolean>} True if rollback succeeded, false if partial failure
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* try {
|
|
207
|
+
* await installAIOS();
|
|
208
|
+
* } catch (error) {
|
|
209
|
+
* await transaction.rollback();
|
|
210
|
+
* }
|
|
211
|
+
*/
|
|
212
|
+
async rollback() {
|
|
213
|
+
if (this.isCommitted) {
|
|
214
|
+
this.log('ERROR', 'Cannot rollback: transaction already committed');
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (this.isRolledBack) {
|
|
219
|
+
this.log('WARN', 'Transaction already rolled back');
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.log('ERROR', 'Installation failed. Starting rollback...');
|
|
224
|
+
let rollbackSuccess = true;
|
|
225
|
+
const failedRestores = [];
|
|
226
|
+
|
|
227
|
+
// Restore in reverse order (LIFO - last backup restored first)
|
|
228
|
+
for (const backup of [...this.backups].reverse()) {
|
|
229
|
+
try {
|
|
230
|
+
// Verify backup integrity before restore
|
|
231
|
+
const isValid = await this._verifyBackup(backup);
|
|
232
|
+
if (!isValid) {
|
|
233
|
+
this.log('ERROR', `Backup verification failed for ${backup.original}`);
|
|
234
|
+
failedRestores.push(backup.original);
|
|
235
|
+
rollbackSuccess = false;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Atomic restore: Copy to temp first, then move to original
|
|
240
|
+
// This prevents data loss if copy fails
|
|
241
|
+
const tempPath = `${backup.original}.restore-temp`;
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
// Copy backup to temporary location
|
|
245
|
+
await fs.copy(backup.backup, tempPath, { recursive: backup.isDirectory });
|
|
246
|
+
|
|
247
|
+
// Remove failed installation artifacts (safe now that restore is ready)
|
|
248
|
+
if (await fs.pathExists(backup.original)) {
|
|
249
|
+
await fs.remove(backup.original);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Atomic move from temp to original
|
|
253
|
+
await fs.move(tempPath, backup.original, { overwrite: true });
|
|
254
|
+
} finally {
|
|
255
|
+
// Cleanup temp file if it still exists (error case)
|
|
256
|
+
if (await fs.pathExists(tempPath)) {
|
|
257
|
+
await fs.remove(tempPath);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.log('INFO', `Restored: ${backup.original}`);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
this.log('ERROR', `Failed to restore ${backup.original}: ${error.message}`);
|
|
264
|
+
failedRestores.push(backup.original);
|
|
265
|
+
rollbackSuccess = false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Cleanup backup directory
|
|
270
|
+
try {
|
|
271
|
+
await fs.remove(this.backupDir);
|
|
272
|
+
this.log('INFO', 'Backup directory cleaned up');
|
|
273
|
+
} catch (error) {
|
|
274
|
+
this.log('WARN', `Failed to cleanup backup directory: ${error.message}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.isRolledBack = true;
|
|
278
|
+
|
|
279
|
+
if (failedRestores.length > 0) {
|
|
280
|
+
this.log('ERROR', `Rollback completed with errors. Failed to restore: ${failedRestores.join(', ')}`);
|
|
281
|
+
} else {
|
|
282
|
+
this.log('INFO', 'Rollback completed successfully');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return rollbackSuccess;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Commit transaction and cleanup backups (installation succeeded)
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* await transaction.commit();
|
|
293
|
+
* console.log('Installation successful!');
|
|
294
|
+
*/
|
|
295
|
+
async commit() {
|
|
296
|
+
if (this.isRolledBack) {
|
|
297
|
+
this.log('ERROR', 'Cannot commit: transaction already rolled back');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (this.isCommitted) {
|
|
302
|
+
this.log('WARN', 'Transaction already committed');
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
this.log('INFO', 'Installation successful. Cleaning up backups...');
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
await fs.remove(this.backupDir);
|
|
310
|
+
this.log('INFO', 'Backups cleaned up successfully');
|
|
311
|
+
} catch (error) {
|
|
312
|
+
this.log('WARN', `Failed to cleanup backups: ${error.message}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
this.isCommitted = true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Log message with timestamp and level
|
|
320
|
+
* Sanitizes credentials before writing to log file
|
|
321
|
+
*
|
|
322
|
+
* @param {string} level - Log level (INFO, WARN, ERROR, DEBUG)
|
|
323
|
+
* @param {string} message - Log message
|
|
324
|
+
*/
|
|
325
|
+
log(level, message) {
|
|
326
|
+
const timestamp = new Date().toISOString();
|
|
327
|
+
const logLine = `[${timestamp}] [${level}] ${message}\n`;
|
|
328
|
+
|
|
329
|
+
// Sanitize credentials (API keys, tokens, passwords)
|
|
330
|
+
const sanitized = this._sanitizeCredentials(logLine);
|
|
331
|
+
|
|
332
|
+
// Write to log file (append mode)
|
|
333
|
+
try {
|
|
334
|
+
fs.appendFileSync(this.logFile, sanitized);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
// If logging fails, don't crash - just write to console
|
|
337
|
+
console.error(`Failed to write to log file: ${error.message}`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Store in memory for programmatic access
|
|
341
|
+
this.operations.push({ timestamp, level, message });
|
|
342
|
+
|
|
343
|
+
// Rotate log if it exceeds 10MB
|
|
344
|
+
this._rotateLogIfNeeded();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Sanitize sensitive data from log messages
|
|
349
|
+
* Prevents API keys, tokens, and passwords from being logged
|
|
350
|
+
*
|
|
351
|
+
* @private
|
|
352
|
+
* @param {string} text - Text to sanitize
|
|
353
|
+
* @returns {string} Sanitized text
|
|
354
|
+
*/
|
|
355
|
+
_sanitizeCredentials(text) {
|
|
356
|
+
// Pattern 1: Long hex strings (API keys, tokens)
|
|
357
|
+
let sanitized = text.replace(/[a-f0-9]{32,}/gi, '[REDACTED]');
|
|
358
|
+
|
|
359
|
+
// Pattern 2: Bearer tokens
|
|
360
|
+
sanitized = sanitized.replace(/Bearer\s+[^\s]+/gi, 'Bearer [REDACTED]');
|
|
361
|
+
|
|
362
|
+
// Pattern 3: Password fields (password=xxx, pwd=xxx)
|
|
363
|
+
sanitized = sanitized.replace(/(password|pwd|token|key|secret|auth)[:=]\s*[^\s,}]+/gi, '$1=[REDACTED]');
|
|
364
|
+
|
|
365
|
+
// Pattern 4: Environment variables that look like secrets
|
|
366
|
+
sanitized = sanitized.replace(/([A-Z_]+_(?:KEY|TOKEN|SECRET|PASSWORD|AUTH))[:=]\s*[^\s,}]+/gi, '$1=[REDACTED]');
|
|
367
|
+
|
|
368
|
+
return sanitized;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Rotate log file if it exceeds 10MB
|
|
373
|
+
* Keeps last 5 logs
|
|
374
|
+
*
|
|
375
|
+
* @private
|
|
376
|
+
*/
|
|
377
|
+
_rotateLogIfNeeded() {
|
|
378
|
+
try {
|
|
379
|
+
// Check if log file exists before trying to get stats
|
|
380
|
+
if (!fs.existsSync(this.logFile)) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const stats = fs.statSync(this.logFile);
|
|
385
|
+
const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB
|
|
386
|
+
const MAX_LOG_FILES = 5;
|
|
387
|
+
|
|
388
|
+
if (stats.size < MAX_LOG_SIZE) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Rotate existing logs (log.4 -> log.5, log.3 -> log.4, etc.)
|
|
393
|
+
for (let i = MAX_LOG_FILES - 1; i >= 1; i--) {
|
|
394
|
+
const oldPath = `${this.logFile}.${i}`;
|
|
395
|
+
const newPath = `${this.logFile}.${i + 1}`;
|
|
396
|
+
|
|
397
|
+
if (fs.existsSync(oldPath)) {
|
|
398
|
+
if (i === MAX_LOG_FILES - 1) {
|
|
399
|
+
fs.unlinkSync(oldPath); // Delete oldest log
|
|
400
|
+
} else {
|
|
401
|
+
fs.renameSync(oldPath, newPath);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Move current log to .1
|
|
407
|
+
fs.renameSync(this.logFile, `${this.logFile}.1`);
|
|
408
|
+
|
|
409
|
+
this.log('INFO', 'Log file rotated');
|
|
410
|
+
} catch (error) {
|
|
411
|
+
// Rotation failure is non-critical
|
|
412
|
+
console.error(`Log rotation failed: ${error.message}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Classify error type based on error code
|
|
418
|
+
*
|
|
419
|
+
* @param {Error} error - Error object
|
|
420
|
+
* @returns {string} ERROR_TYPES constant (CRITICAL, RECOVERABLE, WARNING)
|
|
421
|
+
*/
|
|
422
|
+
classifyError(error) {
|
|
423
|
+
if (!error.code) {
|
|
424
|
+
return ERROR_TYPES.RECOVERABLE;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (CRITICAL_ERRORS.includes(error.code)) {
|
|
428
|
+
return ERROR_TYPES.CRITICAL;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return ERROR_TYPES.RECOVERABLE;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Check if error is critical (requires rollback)
|
|
436
|
+
*
|
|
437
|
+
* @param {Error} error - Error object
|
|
438
|
+
* @returns {boolean} True if error is critical
|
|
439
|
+
*/
|
|
440
|
+
isCriticalError(error) {
|
|
441
|
+
return this.classifyError(error) === ERROR_TYPES.CRITICAL;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
module.exports = { InstallTransaction, ERROR_TYPES, CRITICAL_ERRORS };
|