aios-core 4.2.15 → 4.4.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/cli/commands/validate/index.js +1 -1
- package/.aios-core/core/code-intel/code-intel-client.js +19 -5
- package/.aios-core/core/code-intel/helpers/creation-helper.js +183 -0
- package/.aios-core/core/code-intel/helpers/devops-helper.js +166 -0
- package/.aios-core/core/code-intel/helpers/planning-helper.js +248 -0
- package/.aios-core/core/code-intel/helpers/qa-helper.js +187 -0
- package/.aios-core/core/code-intel/helpers/story-helper.js +146 -0
- package/.aios-core/core/code-intel/hook-runtime.js +186 -0
- package/.aios-core/core/code-intel/index.js +2 -0
- package/.aios-core/core/code-intel/providers/code-graph-provider.js +8 -0
- package/.aios-core/core/code-intel/providers/provider-interface.js +9 -0
- package/.aios-core/core/code-intel/providers/registry-provider.js +515 -0
- package/.aios-core/core/config/schemas/framework-config.schema.json +155 -7
- package/.aios-core/core/config/schemas/project-config.schema.json +329 -15
- package/.aios-core/core/config/template-overrides.js +84 -0
- package/.aios-core/core/docs/troubleshooting-guide.md +1 -1
- package/.aios-core/core/doctor/checks/agent-memory.js +63 -0
- package/.aios-core/core/doctor/checks/claude-md.js +56 -0
- package/.aios-core/core/doctor/checks/code-intel.js +131 -0
- package/.aios-core/core/doctor/checks/commands-count.js +81 -0
- package/.aios-core/core/doctor/checks/core-config.js +53 -0
- package/.aios-core/core/doctor/checks/entity-registry.js +53 -0
- package/.aios-core/core/doctor/checks/git-hooks.js +50 -0
- package/.aios-core/core/doctor/checks/graph-dashboard.js +48 -0
- package/.aios-core/core/doctor/checks/hooks-claude-count.js +118 -0
- package/.aios-core/core/doctor/checks/ide-sync.js +85 -0
- package/.aios-core/core/doctor/checks/index.js +46 -0
- package/.aios-core/core/doctor/checks/node-version.js +33 -0
- package/.aios-core/core/doctor/checks/npm-packages.js +35 -0
- package/.aios-core/core/doctor/checks/rules-files.js +61 -0
- package/.aios-core/core/doctor/checks/settings-json.js +121 -0
- package/.aios-core/core/doctor/checks/skills-count.js +72 -0
- package/.aios-core/core/doctor/fix-handler.js +165 -0
- package/.aios-core/core/doctor/formatters/json.js +14 -0
- package/.aios-core/core/doctor/formatters/text.js +59 -0
- package/.aios-core/core/doctor/index.js +94 -0
- package/.aios-core/core/graph-dashboard/cli.js +361 -0
- package/.aios-core/core/graph-dashboard/data-sources/code-intel-source.js +234 -0
- package/.aios-core/core/graph-dashboard/data-sources/metrics-source.js +95 -0
- package/.aios-core/core/graph-dashboard/data-sources/registry-source.js +106 -0
- package/.aios-core/core/graph-dashboard/formatters/dot-formatter.js +45 -0
- package/.aios-core/core/graph-dashboard/formatters/html-formatter.js +1437 -0
- package/.aios-core/core/graph-dashboard/formatters/json-formatter.js +13 -0
- package/.aios-core/core/graph-dashboard/formatters/mermaid-formatter.js +59 -0
- package/.aios-core/core/graph-dashboard/index.js +21 -0
- package/.aios-core/core/graph-dashboard/renderers/stats-renderer.js +217 -0
- package/.aios-core/core/graph-dashboard/renderers/status-renderer.js +125 -0
- package/.aios-core/core/graph-dashboard/renderers/tree-renderer.js +119 -0
- package/.aios-core/core/health-check/base-check.js +1 -1
- package/.aios-core/core/health-check/check-registry.js +1 -1
- package/.aios-core/core/health-check/checks/deployment/build-config.js +1 -1
- package/.aios-core/core/health-check/checks/deployment/ci-config.js +1 -1
- package/.aios-core/core/health-check/checks/deployment/deployment-readiness.js +1 -1
- package/.aios-core/core/health-check/checks/deployment/docker-config.js +1 -1
- package/.aios-core/core/health-check/checks/deployment/env-file.js +1 -1
- package/.aios-core/core/health-check/checks/deployment/index.js +1 -1
- package/.aios-core/core/health-check/checks/index.js +1 -1
- package/.aios-core/core/health-check/checks/local/disk-space.js +1 -1
- package/.aios-core/core/health-check/checks/local/environment-vars.js +1 -1
- package/.aios-core/core/health-check/checks/local/git-install.js +1 -1
- package/.aios-core/core/health-check/checks/local/ide-detection.js +1 -1
- package/.aios-core/core/health-check/checks/local/index.js +1 -1
- package/.aios-core/core/health-check/checks/local/memory.js +1 -1
- package/.aios-core/core/health-check/checks/local/network.js +1 -1
- package/.aios-core/core/health-check/checks/local/npm-install.js +1 -1
- package/.aios-core/core/health-check/checks/local/shell-environment.js +1 -1
- package/.aios-core/core/health-check/checks/project/agent-config.js +1 -1
- package/.aios-core/core/health-check/checks/project/aios-directory.js +1 -1
- package/.aios-core/core/health-check/checks/project/dependencies.js +1 -1
- package/.aios-core/core/health-check/checks/project/framework-config.js +1 -1
- package/.aios-core/core/health-check/checks/project/index.js +1 -1
- package/.aios-core/core/health-check/checks/project/node-version.js +1 -1
- package/.aios-core/core/health-check/checks/project/package-json.js +1 -1
- package/.aios-core/core/health-check/checks/project/task-definitions.js +1 -1
- package/.aios-core/core/health-check/checks/project/workflow-dependencies.js +1 -1
- package/.aios-core/core/health-check/checks/repository/branch-protection.js +1 -1
- package/.aios-core/core/health-check/checks/repository/commit-history.js +1 -1
- package/.aios-core/core/health-check/checks/repository/conflicts.js +1 -1
- package/.aios-core/core/health-check/checks/repository/git-repo.js +1 -1
- package/.aios-core/core/health-check/checks/repository/git-status.js +1 -1
- package/.aios-core/core/health-check/checks/repository/gitignore.js +1 -1
- package/.aios-core/core/health-check/checks/repository/index.js +1 -1
- package/.aios-core/core/health-check/checks/repository/large-files.js +1 -1
- package/.aios-core/core/health-check/checks/repository/lockfile-integrity.js +1 -1
- package/.aios-core/core/health-check/checks/services/api-endpoints.js +1 -1
- package/.aios-core/core/health-check/checks/services/claude-code.js +1 -1
- package/.aios-core/core/health-check/checks/services/gemini-cli.js +1 -1
- package/.aios-core/core/health-check/checks/services/github-cli.js +1 -1
- package/.aios-core/core/health-check/checks/services/index.js +1 -1
- package/.aios-core/core/health-check/checks/services/mcp-integration.js +1 -1
- package/.aios-core/core/health-check/engine.js +1 -1
- package/.aios-core/core/health-check/healers/backup-manager.js +1 -1
- package/.aios-core/core/health-check/healers/index.js +1 -1
- package/.aios-core/core/health-check/index.js +9 -2
- package/.aios-core/core/health-check/reporters/console.js +1 -1
- package/.aios-core/core/health-check/reporters/index.js +1 -1
- package/.aios-core/core/health-check/reporters/json.js +1 -1
- package/.aios-core/core/health-check/reporters/markdown.js +1 -1
- package/.aios-core/core/ids/layer-classifier.js +65 -0
- package/.aios-core/core/ids/registry-updater.js +49 -0
- package/.aios-core/core/index.esm.js +1 -1
- package/.aios-core/core/index.js +1 -1
- package/.aios-core/core/session/context-detector.js +2 -7
- package/.aios-core/core/synapse/context/context-tracker.js +9 -1
- package/.aios-core/core/synapse/engine.js +33 -13
- package/.aios-core/core/synapse/memory/memory-bridge.js +17 -43
- package/.aios-core/core/synapse/memory/synapse-memory-provider.js +201 -0
- package/.aios-core/core/synapse/runtime/hook-runtime.js +40 -2
- package/.aios-core/core/synapse/session/session-manager.js +3 -2
- package/.aios-core/core/synapse/utils/atomic-write.js +79 -0
- package/.aios-core/core-config.yaml +34 -1
- package/.aios-core/data/aios-kb.md +2 -2
- package/.aios-core/data/capability-detection.js +290 -0
- package/.aios-core/data/entity-registry.yaml +10450 -2129
- package/.aios-core/data/mcp-discipline.js +166 -0
- package/.aios-core/data/mcp-tool-examples.yaml +215 -0
- package/.aios-core/data/tok2-validation.js +168 -0
- package/.aios-core/data/tok3-token-comparison.js +123 -0
- package/.aios-core/data/tool-registry.yaml +648 -0
- package/.aios-core/data/tool-search-validation.js +174 -0
- package/.aios-core/data/workflow-chains.yaml +156 -0
- package/.aios-core/development/agents/aios-master.md +17 -10
- package/.aios-core/development/agents/analyst/MEMORY.md +33 -0
- package/.aios-core/development/agents/analyst.md +17 -10
- package/.aios-core/development/agents/architect/MEMORY.md +39 -0
- package/.aios-core/development/agents/architect.md +17 -10
- package/.aios-core/development/agents/data-engineer/MEMORY.md +32 -0
- package/.aios-core/development/agents/data-engineer.md +17 -10
- package/.aios-core/development/agents/dev/MEMORY.md +46 -0
- package/.aios-core/development/agents/dev.md +18 -11
- package/.aios-core/development/agents/devops/MEMORY.md +39 -0
- package/.aios-core/development/agents/devops.md +44 -10
- package/.aios-core/development/agents/pm/MEMORY.md +38 -0
- package/.aios-core/development/agents/pm.md +17 -10
- package/.aios-core/development/agents/po/MEMORY.md +45 -0
- package/.aios-core/development/agents/po.md +17 -10
- package/.aios-core/development/agents/qa/MEMORY.md +42 -0
- package/.aios-core/development/agents/qa.md +18 -11
- package/.aios-core/development/agents/sm/MEMORY.md +31 -0
- package/.aios-core/development/agents/sm.md +17 -10
- package/.aios-core/development/agents/squad-creator.md +18 -9
- package/.aios-core/development/agents/ux/MEMORY.md +31 -0
- package/.aios-core/development/agents/ux-design-expert.md +16 -9
- package/.aios-core/development/checklists/issue-triage-checklist.md +35 -0
- package/.aios-core/development/checklists/memory-audit-checklist.md +53 -0
- package/.aios-core/development/scripts/issue-triage.js +171 -0
- package/.aios-core/development/scripts/populate-entity-registry.js +412 -19
- package/.aios-core/development/scripts/unified-activation-pipeline.js +31 -10
- package/.aios-core/development/tasks/analyze-project-structure.md +48 -0
- package/.aios-core/development/tasks/apply-qa-fixes.md +7 -0
- package/.aios-core/development/tasks/architect-analyze-impact.md +8 -1
- package/.aios-core/development/tasks/brownfield-create-epic.md +41 -0
- package/.aios-core/development/tasks/brownfield-create-story.md +7 -0
- package/.aios-core/development/tasks/build-autonomous.md +7 -0
- package/.aios-core/development/tasks/create-deep-research-prompt.md +7 -0
- package/.aios-core/development/tasks/create-doc.md +44 -0
- package/.aios-core/development/tasks/create-next-story.md +17 -0
- package/.aios-core/development/tasks/create-suite.md +7 -0
- package/.aios-core/development/tasks/dev-develop-story.md +9 -1
- package/.aios-core/development/tasks/execute-checklist.md +7 -0
- package/.aios-core/development/tasks/github-devops-github-pr-automation.md +56 -0
- package/.aios-core/development/tasks/github-devops-pre-push-quality-gate.md +70 -0
- package/.aios-core/development/tasks/github-issue-triage.md +118 -0
- package/.aios-core/development/tasks/health-check.yaml +206 -171
- package/.aios-core/development/tasks/kb-mode-interaction.md +3 -3
- package/.aios-core/development/tasks/plan-create-context.md +47 -1
- package/.aios-core/development/tasks/plan-create-implementation.md +55 -0
- package/.aios-core/development/tasks/po-close-story.md +7 -0
- package/.aios-core/development/tasks/pr-automation.md +5 -5
- package/.aios-core/development/tasks/qa-create-fix-request.md +7 -0
- package/.aios-core/development/tasks/qa-fix-issues.md +7 -0
- package/.aios-core/development/tasks/qa-gate.md +56 -0
- package/.aios-core/development/tasks/qa-review-story.md +32 -1
- package/.aios-core/development/tasks/release-management.md +7 -0
- package/.aios-core/development/tasks/resolve-github-issue.md +608 -0
- package/.aios-core/development/tasks/review-contributor-pr.md +152 -0
- package/.aios-core/development/tasks/setup-llm-routing.md +1 -1
- package/.aios-core/development/tasks/spec-critique.md +8 -0
- package/.aios-core/development/tasks/spec-gather-requirements.md +7 -0
- package/.aios-core/development/tasks/spec-research-dependencies.md +4 -0
- package/.aios-core/development/tasks/spec-write-spec.md +5 -0
- package/.aios-core/development/tasks/triage-github-issues.md +356 -0
- package/.aios-core/development/tasks/validate-agents.md +4 -0
- package/.aios-core/development/tasks/validate-next-story.md +17 -0
- package/.aios-core/development/templates/agent-handoff-tmpl.yaml +48 -0
- package/.aios-core/development/templates/code-intel-integration-pattern.md +199 -0
- package/.aios-core/development/templates/ptc-entity-validation.md +113 -0
- package/.aios-core/development/templates/ptc-qa-gate.md +100 -0
- package/.aios-core/development/templates/ptc-research-aggregation.md +94 -0
- package/.aios-core/development/templates/service-template/README.md.hbs +158 -158
- package/.aios-core/development/templates/service-template/__tests__/index.test.ts.hbs +237 -237
- package/.aios-core/development/templates/service-template/client.ts.hbs +403 -403
- package/.aios-core/development/templates/service-template/errors.ts.hbs +182 -182
- package/.aios-core/development/templates/service-template/index.ts.hbs +120 -120
- package/.aios-core/development/templates/service-template/package.json.hbs +87 -87
- package/.aios-core/development/templates/service-template/types.ts.hbs +145 -145
- package/.aios-core/development/templates/squad/agent-template.md +11 -0
- package/.aios-core/development/templates/squad/task-template.md +21 -0
- package/.aios-core/development/templates/squad-template/LICENSE +21 -21
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +1 -1
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.2-SUMMARY.md +1 -1
- package/.aios-core/framework-config.yaml +8 -0
- package/.aios-core/index.esm.js +1 -1
- package/.aios-core/index.js +1 -1
- package/.aios-core/infrastructure/integrations/ai-providers/index.js +1 -1
- package/.aios-core/infrastructure/schemas/task-v3-schema.json +6 -0
- package/.aios-core/infrastructure/scripts/collect-tool-usage.js +311 -0
- package/.aios-core/infrastructure/scripts/generate-optimization-report.js +497 -0
- package/.aios-core/infrastructure/scripts/generate-settings-json.js +300 -0
- package/.aios-core/infrastructure/scripts/git-config-detector.js +65 -9
- package/.aios-core/infrastructure/scripts/ide-sync/index.js +3 -1
- package/.aios-core/infrastructure/scripts/ide-sync/transformers/github-copilot.js +184 -0
- package/.aios-core/infrastructure/scripts/repository-detector.js +3 -3
- package/.aios-core/infrastructure/templates/aios-sync.yaml.template +182 -182
- package/.aios-core/infrastructure/templates/coderabbit.yaml.template +279 -279
- package/.aios-core/infrastructure/templates/github-workflows/ci.yml.template +169 -169
- package/.aios-core/infrastructure/templates/github-workflows/pr-automation.yml.template +330 -330
- package/.aios-core/infrastructure/templates/github-workflows/release.yml.template +196 -196
- package/.aios-core/infrastructure/templates/gitignore/gitignore-aios-base.tmpl +63 -63
- package/.aios-core/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +18 -18
- package/.aios-core/infrastructure/templates/gitignore/gitignore-node.tmpl +85 -85
- package/.aios-core/infrastructure/templates/gitignore/gitignore-python.tmpl +145 -145
- package/.aios-core/install-manifest.yaml +613 -305
- package/.aios-core/lib/build.json +1 -0
- package/.aios-core/local-config.yaml.template +71 -71
- package/.aios-core/monitor/hooks/lib/__init__.py +1 -1
- package/.aios-core/monitor/hooks/lib/enrich.py +58 -58
- package/.aios-core/monitor/hooks/lib/send_event.py +47 -47
- package/.aios-core/monitor/hooks/notification.py +29 -29
- package/.aios-core/monitor/hooks/post_tool_use.py +45 -45
- package/.aios-core/monitor/hooks/pre_compact.py +29 -29
- package/.aios-core/monitor/hooks/pre_tool_use.py +40 -40
- package/.aios-core/monitor/hooks/stop.py +29 -29
- package/.aios-core/monitor/hooks/subagent_stop.py +29 -29
- package/.aios-core/monitor/hooks/user_prompt_submit.py +38 -38
- package/.aios-core/product/templates/adr.hbs +125 -125
- package/.aios-core/product/templates/dbdr.hbs +241 -241
- package/.aios-core/product/templates/epic.hbs +212 -212
- package/.aios-core/product/templates/ide-rules/claude-rules.md +125 -0
- package/.aios-core/product/templates/pmdr.hbs +186 -186
- package/.aios-core/product/templates/prd-v2.0.hbs +216 -216
- package/.aios-core/product/templates/prd.hbs +201 -201
- package/.aios-core/product/templates/story.hbs +263 -263
- package/.aios-core/product/templates/task.hbs +170 -170
- package/.aios-core/product/templates/tmpl-comment-on-examples.sql +158 -158
- package/.aios-core/product/templates/tmpl-migration-script.sql +91 -91
- package/.aios-core/product/templates/tmpl-rls-granular-policies.sql +104 -104
- package/.aios-core/product/templates/tmpl-rls-kiss-policy.sql +10 -10
- package/.aios-core/product/templates/tmpl-rls-roles.sql +135 -135
- package/.aios-core/product/templates/tmpl-rls-simple.sql +77 -77
- package/.aios-core/product/templates/tmpl-rls-tenant.sql +152 -152
- package/.aios-core/product/templates/tmpl-rollback-script.sql +77 -77
- package/.aios-core/product/templates/tmpl-seed-data.sql +140 -140
- package/.aios-core/product/templates/tmpl-smoke-test.sql +16 -16
- package/.aios-core/product/templates/tmpl-staging-copy-merge.sql +139 -139
- package/.aios-core/product/templates/tmpl-stored-proc.sql +140 -140
- package/.aios-core/product/templates/tmpl-trigger.sql +152 -152
- package/.aios-core/product/templates/tmpl-view-materialized.sql +133 -133
- package/.aios-core/product/templates/tmpl-view.sql +177 -177
- package/.aios-core/scripts/pm.sh +0 -0
- package/.aios-core/user-guide.md +15 -15
- package/.aios-core/utils/filters/constants.js +10 -0
- package/.aios-core/utils/filters/content-filter.js +223 -0
- package/.aios-core/utils/filters/field-filter.js +126 -0
- package/.aios-core/utils/filters/index.js +180 -0
- package/.aios-core/utils/filters/schema-filter.js +157 -0
- package/.claude/CLAUDE.md +62 -0
- package/.claude/hooks/enforce-architecture-first.py +196 -196
- package/.claude/hooks/enforce-git-push-authority.sh +33 -0
- package/.claude/hooks/mind-clone-governance.py +192 -192
- package/.claude/hooks/read-protection.py +151 -151
- package/.claude/hooks/slug-validation.py +176 -176
- package/.claude/hooks/sql-governance.py +182 -182
- package/.claude/hooks/synapse-engine.cjs +28 -5
- package/.claude/hooks/write-path-validation.py +194 -194
- package/.claude/rules/agent-authority.md +105 -0
- package/.claude/rules/agent-handoff.md +97 -0
- package/.claude/rules/agent-memory-imports.md +15 -0
- package/.claude/rules/coderabbit-integration.md +101 -0
- package/.claude/rules/ids-principles.md +119 -0
- package/.claude/rules/story-lifecycle.md +145 -0
- package/.claude/rules/tool-examples.md +64 -0
- package/.claude/rules/tool-response-filtering.md +57 -0
- package/.claude/rules/workflow-execution.md +150 -0
- package/LICENSE +33 -33
- package/bin/aios-graph.js +9 -0
- package/bin/aios-init.js +2 -2
- package/bin/aios-minimal.js +0 -0
- package/bin/aios.js +17 -221
- package/bin/utils/detect-fsmonitor.js +70 -0
- package/bin/utils/framework-guard.js +238 -0
- package/bin/utils/validate-publish.js +108 -0
- package/package.json +6 -3
- package/packages/aios-install/bin/aios-install.js +0 -0
- package/packages/aios-install/bin/edmcp.js +0 -0
- package/packages/aios-pro-cli/bin/aios-pro.js +2 -0
- package/packages/installer/src/config/templates/core-config-template.js +25 -0
- package/packages/installer/src/installer/brownfield-upgrader.js +68 -5
- package/packages/installer/src/merger/index.js +3 -0
- package/packages/installer/src/merger/strategies/index.js +6 -0
- package/packages/installer/src/merger/strategies/yaml-merger.js +181 -0
- package/packages/installer/src/updater/index.js +4 -4
- package/packages/installer/src/wizard/i18n.js +321 -3
- package/packages/installer/src/wizard/ide-config-generator.js +173 -25
- package/packages/installer/src/wizard/index.js +119 -1
- package/packages/installer/src/wizard/pro-setup.js +137 -121
- package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +271 -0
- package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +192 -0
- package/packages/installer/tests/unit/doctor/doctor-checks.test.js +610 -0
- package/packages/installer/tests/unit/doctor/doctor-orchestrator.test.js +134 -0
- package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +186 -0
- package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +309 -0
- package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +230 -0
- package/packages/installer/tests/unit/merger/strategies.test.js +2 -2
- package/packages/installer/tests/unit/merger/yaml-merger.test.js +327 -0
- package/scripts/check-markdown-links.py +352 -352
- package/scripts/dashboard-parallel-dev.sh +0 -0
- package/scripts/dashboard-parallel-phase3.sh +0 -0
- package/scripts/dashboard-parallel-phase4.sh +0 -0
- package/scripts/install-monitor-hooks.sh +0 -0
- package/scripts/package-synapse.js +2 -1
- package/pro/README.md +0 -66
- package/pro/license/degradation.js +0 -220
- package/pro/license/errors.js +0 -450
- package/pro/license/feature-gate.js +0 -354
- package/pro/license/index.js +0 -181
- package/pro/license/license-api.js +0 -651
- package/pro/license/license-cache.js +0 -523
- package/pro/license/license-crypto.js +0 -303
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IDE Sync Integration Tests (Story INS-4.5)
|
|
5
|
+
*
|
|
6
|
+
* Verifies that the installer calls commandSync and commandValidate
|
|
7
|
+
* via the adapter pattern (save cwd, chdir, finally restore).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
// Mock the ide-sync module before requiring wizard
|
|
13
|
+
const mockCommandSync = jest.fn();
|
|
14
|
+
const mockCommandValidate = jest.fn();
|
|
15
|
+
|
|
16
|
+
jest.mock('../../../../../.aios-core/infrastructure/scripts/ide-sync/index', () => ({
|
|
17
|
+
commandSync: mockCommandSync,
|
|
18
|
+
commandValidate: mockCommandValidate,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
// We need to verify that the wizard source code has the correct integration
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const WIZARD_PATH = path.join(
|
|
24
|
+
__dirname, '..', '..', '..', 'src', 'wizard', 'index.js'
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
describe('IDE Sync Integration (Story INS-4.5)', () => {
|
|
28
|
+
let wizardSource;
|
|
29
|
+
|
|
30
|
+
beforeAll(() => {
|
|
31
|
+
wizardSource = fs.readFileSync(WIZARD_PATH, 'utf8');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('AC1: IDE sync called via adapter pattern', () => {
|
|
35
|
+
test('wizard imports commandSync and commandValidate from ide-sync', () => {
|
|
36
|
+
expect(wizardSource).toContain(
|
|
37
|
+
"const { commandSync, commandValidate } = require('../../../../.aios-core/infrastructure/scripts/ide-sync/index')"
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('wizard uses programmatic API (not child_process.exec)', () => {
|
|
42
|
+
// Should NOT shell out to ide-sync
|
|
43
|
+
expect(wizardSource).not.toMatch(/child_process.*ide-sync/);
|
|
44
|
+
expect(wizardSource).not.toMatch(/exec\(.*ide-sync/);
|
|
45
|
+
expect(wizardSource).not.toMatch(/spawn\(.*ide-sync/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('adapter pattern: saves cwd before calling commandSync', () => {
|
|
49
|
+
expect(wizardSource).toContain('const savedCwd = process.cwd()');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('adapter pattern: uses explicit targetProjectRoot variable (not bare process.cwd())', () => {
|
|
53
|
+
expect(wizardSource).toContain('const targetProjectRoot = process.cwd()');
|
|
54
|
+
expect(wizardSource).toContain('process.chdir(targetProjectRoot)');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('adapter pattern: restores cwd in finally block', () => {
|
|
58
|
+
// The finally block should restore savedCwd
|
|
59
|
+
expect(wizardSource).toContain('process.chdir(savedCwd)');
|
|
60
|
+
expect(wizardSource).toMatch(/finally\s*\{[^}]*process\.chdir\(savedCwd\)/s);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('commandSync called with { quiet: true }', () => {
|
|
64
|
+
expect(wizardSource).toContain("await commandSync({ quiet: true })");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('does NOT pass projectRoot or ides as parameters to commandSync', () => {
|
|
68
|
+
// commandSync uses process.cwd() internally — no projectRoot param
|
|
69
|
+
expect(wizardSource).not.toMatch(/commandSync\(\{[^}]*projectRoot/);
|
|
70
|
+
expect(wizardSource).not.toMatch(/commandSync\(\{[^}]*ides/);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('AC3: Graceful failure', () => {
|
|
75
|
+
test('sync failure is caught and does not propagate', () => {
|
|
76
|
+
// The try/catch should set ideSyncStatus to failed, not throw
|
|
77
|
+
expect(wizardSource).toContain("answers.ideSyncStatus = 'failed'");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('failure message suggests aios doctor --fix', () => {
|
|
81
|
+
expect(wizardSource).toContain("aios doctor --fix");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('install summary includes sync status on success', () => {
|
|
85
|
+
expect(wizardSource).toContain("answers.ideSyncStatus = 'synced'");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('install summary includes sync status on failure', () => {
|
|
89
|
+
expect(wizardSource).toContain("answers.ideSyncStatus = 'failed'");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('AC4: Validate sync output', () => {
|
|
94
|
+
test('commandValidate called after commandSync', () => {
|
|
95
|
+
// commandValidate should appear after commandSync in the source
|
|
96
|
+
const syncIndex = wizardSource.indexOf('await commandSync({ quiet: true })');
|
|
97
|
+
const validateIndex = wizardSource.indexOf('await commandValidate(');
|
|
98
|
+
expect(syncIndex).toBeGreaterThan(-1);
|
|
99
|
+
expect(validateIndex).toBeGreaterThan(-1);
|
|
100
|
+
expect(validateIndex).toBeGreaterThan(syncIndex);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('commandValidate uses same adapter pattern (within same finally block)', () => {
|
|
104
|
+
// Both commandSync and commandValidate should be within the same
|
|
105
|
+
// saved cwd / finally block
|
|
106
|
+
const savedCwdIndex = wizardSource.indexOf('const savedCwd = process.cwd()');
|
|
107
|
+
const syncIndex = wizardSource.indexOf('await commandSync({ quiet: true })');
|
|
108
|
+
const validateIndex = wizardSource.indexOf('await commandValidate(');
|
|
109
|
+
const finallyIndex = wizardSource.indexOf('process.chdir(savedCwd)');
|
|
110
|
+
|
|
111
|
+
// All should be in order: savedCwd < sync < validate < finally restore
|
|
112
|
+
expect(savedCwdIndex).toBeLessThan(syncIndex);
|
|
113
|
+
expect(syncIndex).toBeLessThan(validateIndex);
|
|
114
|
+
expect(validateIndex).toBeLessThan(finallyIndex);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('validation drift logged as WARN not ERROR', () => {
|
|
118
|
+
expect(wizardSource).toContain("answers.ideSyncValidation = 'drift'");
|
|
119
|
+
// Should use console.warn, not console.error for drift
|
|
120
|
+
expect(wizardSource).toMatch(/console\.warn\(.*drift/i);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('commandValidate console output suppressed (quiet workaround)', () => {
|
|
124
|
+
// commandValidate does not support quiet — wizard suppresses console.log
|
|
125
|
+
expect(wizardSource).toContain('const _origLog = console.log');
|
|
126
|
+
expect(wizardSource).toContain('console.log = () => {}');
|
|
127
|
+
// console.log must be restored in a finally block
|
|
128
|
+
expect(wizardSource).toContain('console.log = _origLog');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('AC5: Behavioral tests with mocks', () => {
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
mockCommandSync.mockReset();
|
|
135
|
+
mockCommandValidate.mockReset();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('commandSync is called with { quiet: true } when invoked', async () => {
|
|
139
|
+
mockCommandSync.mockResolvedValue(undefined);
|
|
140
|
+
mockCommandValidate.mockResolvedValue(undefined);
|
|
141
|
+
|
|
142
|
+
// Simulate the adapter pattern call sequence
|
|
143
|
+
const savedCwd = process.cwd();
|
|
144
|
+
try {
|
|
145
|
+
await mockCommandSync({ quiet: true });
|
|
146
|
+
await mockCommandValidate({ quiet: true });
|
|
147
|
+
} finally {
|
|
148
|
+
process.chdir(savedCwd);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
expect(mockCommandSync).toHaveBeenCalledWith({ quiet: true });
|
|
152
|
+
expect(mockCommandValidate).toHaveBeenCalledWith({ quiet: true });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('commandSync failure does not throw — install continues', async () => {
|
|
156
|
+
mockCommandSync.mockRejectedValue(new Error('sync failed'));
|
|
157
|
+
|
|
158
|
+
let ideSyncStatus = 'unknown';
|
|
159
|
+
const savedCwd = process.cwd();
|
|
160
|
+
try {
|
|
161
|
+
process.chdir(process.cwd());
|
|
162
|
+
await mockCommandSync({ quiet: true });
|
|
163
|
+
ideSyncStatus = 'synced';
|
|
164
|
+
} catch (syncError) {
|
|
165
|
+
ideSyncStatus = 'failed';
|
|
166
|
+
} finally {
|
|
167
|
+
process.chdir(savedCwd);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Install should continue — status is failed, no uncaught throw
|
|
171
|
+
expect(ideSyncStatus).toBe('failed');
|
|
172
|
+
expect(process.cwd()).toBe(savedCwd);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('cwd is restored even when commandSync throws', async () => {
|
|
176
|
+
mockCommandSync.mockRejectedValue(new Error('sync failed'));
|
|
177
|
+
const originalCwd = process.cwd();
|
|
178
|
+
|
|
179
|
+
const savedCwd = process.cwd();
|
|
180
|
+
try {
|
|
181
|
+
process.chdir(process.cwd());
|
|
182
|
+
await mockCommandSync({ quiet: true });
|
|
183
|
+
} catch {
|
|
184
|
+
// Expected — sync failed
|
|
185
|
+
} finally {
|
|
186
|
+
process.chdir(savedCwd);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
expect(process.cwd()).toBe(originalCwd);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('commandValidate called after successful sync', async () => {
|
|
193
|
+
mockCommandSync.mockResolvedValue(undefined);
|
|
194
|
+
mockCommandValidate.mockResolvedValue(undefined);
|
|
195
|
+
|
|
196
|
+
const savedCwd = process.cwd();
|
|
197
|
+
let validateCalled = false;
|
|
198
|
+
try {
|
|
199
|
+
process.chdir(process.cwd());
|
|
200
|
+
await mockCommandSync({ quiet: true });
|
|
201
|
+
await mockCommandValidate({ quiet: true });
|
|
202
|
+
validateCalled = true;
|
|
203
|
+
} finally {
|
|
204
|
+
process.chdir(savedCwd);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
expect(validateCalled).toBe(true);
|
|
208
|
+
expect(mockCommandValidate).toHaveBeenCalled();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('commandValidate NOT called when commandSync fails', async () => {
|
|
212
|
+
mockCommandSync.mockRejectedValue(new Error('sync failed'));
|
|
213
|
+
|
|
214
|
+
const savedCwd = process.cwd();
|
|
215
|
+
try {
|
|
216
|
+
process.chdir(process.cwd());
|
|
217
|
+
await mockCommandSync({ quiet: true });
|
|
218
|
+
// This line should NOT be reached
|
|
219
|
+
await mockCommandValidate({ quiet: true });
|
|
220
|
+
} catch {
|
|
221
|
+
// Expected — sync failed, validate not called
|
|
222
|
+
} finally {
|
|
223
|
+
process.chdir(savedCwd);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
expect(mockCommandSync).toHaveBeenCalled();
|
|
227
|
+
expect(mockCommandValidate).not.toHaveBeenCalled();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -41,7 +41,7 @@ describe('getMergeStrategy', () => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
it('should return ReplaceMerger for unknown file types', () => {
|
|
44
|
-
const merger = getMergeStrategy('config.
|
|
44
|
+
const merger = getMergeStrategy('config.toml');
|
|
45
45
|
expect(merger).toBeInstanceOf(ReplaceMerger);
|
|
46
46
|
});
|
|
47
47
|
|
|
@@ -71,7 +71,7 @@ describe('hasMergeStrategy', () => {
|
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
it('should return false for unsupported files', () => {
|
|
74
|
-
expect(hasMergeStrategy('config.
|
|
74
|
+
expect(hasMergeStrategy('config.toml')).toBe(false);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
it('should return false for .json files', () => {
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* YAML Merger Strategy Tests (Story INS-4.7)
|
|
5
|
+
*
|
|
6
|
+
* Validates Phase 1 merge rules:
|
|
7
|
+
* - New keys added from source (framework)
|
|
8
|
+
* - User values preserved on conflict (target wins)
|
|
9
|
+
* - Deprecated keys kept with warning
|
|
10
|
+
* - Output is valid YAML
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const yaml = require('js-yaml');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
|
|
17
|
+
const { YamlMerger } = require(path.join(
|
|
18
|
+
__dirname, '..', '..', '..', 'src', 'merger', 'strategies', 'yaml-merger.js'
|
|
19
|
+
));
|
|
20
|
+
|
|
21
|
+
describe('YamlMerger (Story INS-4.7)', () => {
|
|
22
|
+
let merger;
|
|
23
|
+
|
|
24
|
+
beforeAll(() => {
|
|
25
|
+
merger = new YamlMerger();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('AC1: Strategy interface', () => {
|
|
29
|
+
test('extends BaseMerger with name "yaml"', () => {
|
|
30
|
+
expect(merger.name).toBe('yaml');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('canMerge returns true for valid YAML', () => {
|
|
34
|
+
expect(merger.canMerge('key: value\n', 'other: data\n')).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('canMerge returns false for invalid YAML', () => {
|
|
38
|
+
expect(merger.canMerge('key: value\n', '{{invalid')).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('merge is async and returns MergeResult', async () => {
|
|
42
|
+
const result = await merger.merge('a: 1\n', 'a: 1\n');
|
|
43
|
+
expect(result).toHaveProperty('content');
|
|
44
|
+
expect(result).toHaveProperty('stats');
|
|
45
|
+
expect(result).toHaveProperty('changes');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('getDescription returns meaningful text', () => {
|
|
49
|
+
expect(merger.getDescription()).toContain('YAML');
|
|
50
|
+
expect(merger.getDescription()).toContain('Phase 1');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('AC1: Strategy registration', () => {
|
|
55
|
+
test('.yaml extension registered in strategies/index.js', () => {
|
|
56
|
+
const { hasMergeStrategy, getMergeStrategy } = require(path.join(
|
|
57
|
+
__dirname, '..', '..', '..', 'src', 'merger', 'strategies', 'index.js'
|
|
58
|
+
));
|
|
59
|
+
|
|
60
|
+
expect(hasMergeStrategy('config.yaml')).toBe(true);
|
|
61
|
+
const strategy = getMergeStrategy('config.yaml');
|
|
62
|
+
expect(strategy.name).toBe('yaml');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('.yml extension also registered', () => {
|
|
66
|
+
const { hasMergeStrategy } = require(path.join(
|
|
67
|
+
__dirname, '..', '..', '..', 'src', 'merger', 'strategies', 'index.js'
|
|
68
|
+
));
|
|
69
|
+
|
|
70
|
+
expect(hasMergeStrategy('config.yml')).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('YamlMerger exported from merger/index.js', () => {
|
|
74
|
+
const mergerModule = require(path.join(
|
|
75
|
+
__dirname, '..', '..', '..', 'src', 'merger', 'index.js'
|
|
76
|
+
));
|
|
77
|
+
|
|
78
|
+
expect(mergerModule.YamlMerger).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('AC2: Phase 1 merge rules', () => {
|
|
83
|
+
test('new key in source → added to merged output', async () => {
|
|
84
|
+
const source = yaml.dump({ existingKey: 'a', newFeature: { enabled: true } });
|
|
85
|
+
const target = yaml.dump({ existingKey: 'a' });
|
|
86
|
+
|
|
87
|
+
const result = await merger.merge(source, target);
|
|
88
|
+
const merged = yaml.load(result.content);
|
|
89
|
+
|
|
90
|
+
expect(merged.newFeature).toEqual({ enabled: true });
|
|
91
|
+
expect(result.stats.added).toBeGreaterThanOrEqual(1);
|
|
92
|
+
|
|
93
|
+
const addedChange = result.changes.find(
|
|
94
|
+
c => c.type === 'added' && c.identifier === 'newFeature'
|
|
95
|
+
);
|
|
96
|
+
expect(addedChange).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('key in both with same value → target value preserved', async () => {
|
|
100
|
+
const source = yaml.dump({ key: 'same' });
|
|
101
|
+
const target = yaml.dump({ key: 'same' });
|
|
102
|
+
|
|
103
|
+
const result = await merger.merge(source, target);
|
|
104
|
+
const merged = yaml.load(result.content);
|
|
105
|
+
|
|
106
|
+
expect(merged.key).toBe('same');
|
|
107
|
+
expect(result.stats.preserved).toBeGreaterThanOrEqual(1);
|
|
108
|
+
|
|
109
|
+
const preservedChange = result.changes.find(
|
|
110
|
+
c => c.type === 'preserved' && c.identifier === 'key'
|
|
111
|
+
);
|
|
112
|
+
expect(preservedChange).toBeDefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('conflict (different values) → target wins', async () => {
|
|
116
|
+
const source = yaml.dump({ setting: 'framework-default' });
|
|
117
|
+
const target = yaml.dump({ setting: 'user-custom' });
|
|
118
|
+
|
|
119
|
+
const result = await merger.merge(source, target);
|
|
120
|
+
const merged = yaml.load(result.content);
|
|
121
|
+
|
|
122
|
+
expect(merged.setting).toBe('user-custom');
|
|
123
|
+
expect(result.stats.conflicts).toBeGreaterThanOrEqual(1);
|
|
124
|
+
|
|
125
|
+
const conflictChange = result.changes.find(
|
|
126
|
+
c => c.type === 'conflict' && c.identifier === 'setting'
|
|
127
|
+
);
|
|
128
|
+
expect(conflictChange).toBeDefined();
|
|
129
|
+
expect(conflictChange.reason).toContain('Keeping user value');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('deprecated key (in target, not in source) → kept with warning', async () => {
|
|
133
|
+
const source = yaml.dump({ current: true });
|
|
134
|
+
const target = yaml.dump({ current: true, legacyKey: 'old-value' });
|
|
135
|
+
|
|
136
|
+
const result = await merger.merge(source, target);
|
|
137
|
+
const merged = yaml.load(result.content);
|
|
138
|
+
|
|
139
|
+
expect(merged.legacyKey).toBe('old-value');
|
|
140
|
+
|
|
141
|
+
const deprecatedChange = result.changes.find(
|
|
142
|
+
c => c.type === 'conflict' && c.identifier === 'legacyKey'
|
|
143
|
+
);
|
|
144
|
+
expect(deprecatedChange).toBeDefined();
|
|
145
|
+
expect(deprecatedChange.reason).toContain('Deprecated');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('output is valid YAML', async () => {
|
|
149
|
+
const source = yaml.dump({ a: 1, b: { c: 2 }, d: [1, 2, 3] });
|
|
150
|
+
const target = yaml.dump({ a: 99, e: 'user' });
|
|
151
|
+
|
|
152
|
+
const result = await merger.merge(source, target);
|
|
153
|
+
|
|
154
|
+
expect(() => yaml.load(result.content)).not.toThrow();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('all changes in MergeResult.changes array (no separate warnings)', async () => {
|
|
158
|
+
const source = yaml.dump({ a: 1, b: 2 });
|
|
159
|
+
const target = yaml.dump({ a: 99, c: 3 });
|
|
160
|
+
|
|
161
|
+
const result = await merger.merge(source, target);
|
|
162
|
+
|
|
163
|
+
expect(result).not.toHaveProperty('warnings');
|
|
164
|
+
expect(Array.isArray(result.changes)).toBe(true);
|
|
165
|
+
expect(result.changes.length).toBeGreaterThan(0);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('AC2: Deep merge', () => {
|
|
170
|
+
test('nested new keys added at depth', async () => {
|
|
171
|
+
const source = yaml.dump({
|
|
172
|
+
boundary: { frameworkProtection: true, newSetting: 'added' },
|
|
173
|
+
});
|
|
174
|
+
const target = yaml.dump({
|
|
175
|
+
boundary: { frameworkProtection: false },
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const result = await merger.merge(source, target);
|
|
179
|
+
const merged = yaml.load(result.content);
|
|
180
|
+
|
|
181
|
+
expect(merged.boundary.frameworkProtection).toBe(false); // user wins
|
|
182
|
+
expect(merged.boundary.newSetting).toBe('added'); // new key added
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('nested conflict preserves user value', async () => {
|
|
186
|
+
const source = yaml.dump({
|
|
187
|
+
pvMindContext: { location: 'default-path' },
|
|
188
|
+
});
|
|
189
|
+
const target = yaml.dump({
|
|
190
|
+
pvMindContext: { location: 'user-custom-path' },
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const result = await merger.merge(source, target);
|
|
194
|
+
const merged = yaml.load(result.content);
|
|
195
|
+
|
|
196
|
+
expect(merged.pvMindContext.location).toBe('user-custom-path');
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('AC4: User config preservation', () => {
|
|
201
|
+
test('custom pvMindContext.location preserved after upgrade', async () => {
|
|
202
|
+
const source = yaml.dump({
|
|
203
|
+
project: { type: 'EXISTING_AIOS', version: '2.2.0' },
|
|
204
|
+
pvMindContext: { location: 'default' },
|
|
205
|
+
});
|
|
206
|
+
const target = yaml.dump({
|
|
207
|
+
project: { type: 'EXISTING_AIOS', version: '2.1.0' },
|
|
208
|
+
pvMindContext: { location: '/my/custom/path' },
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const result = await merger.merge(source, target);
|
|
212
|
+
const merged = yaml.load(result.content);
|
|
213
|
+
|
|
214
|
+
expect(merged.pvMindContext.location).toBe('/my/custom/path');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('new framework key added alongside preserved user config', async () => {
|
|
218
|
+
const source = yaml.dump({
|
|
219
|
+
existing: 'value',
|
|
220
|
+
someNewFeature: { enabled: true },
|
|
221
|
+
});
|
|
222
|
+
const target = yaml.dump({
|
|
223
|
+
existing: 'value',
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const result = await merger.merge(source, target);
|
|
227
|
+
const merged = yaml.load(result.content);
|
|
228
|
+
|
|
229
|
+
expect(merged.existing).toBe('value');
|
|
230
|
+
expect(merged.someNewFeature).toEqual({ enabled: true });
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('AC5: Boundary section preservation', () => {
|
|
235
|
+
test('user-customized boundary paths NOT removed', async () => {
|
|
236
|
+
const source = yaml.dump({
|
|
237
|
+
boundary: {
|
|
238
|
+
frameworkProtection: true,
|
|
239
|
+
protected: ['.aios-core/core/'],
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
const target = yaml.dump({
|
|
243
|
+
boundary: {
|
|
244
|
+
frameworkProtection: false,
|
|
245
|
+
protected: ['.aios-core/core/', 'my-custom-path/'],
|
|
246
|
+
exceptions: ['my-exception/'],
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const result = await merger.merge(source, target);
|
|
251
|
+
const merged = yaml.load(result.content);
|
|
252
|
+
|
|
253
|
+
// User boundary values preserved (target wins on arrays — no deep array merge)
|
|
254
|
+
expect(merged.boundary.frameworkProtection).toBe(false);
|
|
255
|
+
expect(merged.boundary.protected).toContain('my-custom-path/');
|
|
256
|
+
expect(merged.boundary.exceptions).toContain('my-exception/');
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
describe('AC6: Edge cases', () => {
|
|
261
|
+
test('empty source → target preserved as-is', async () => {
|
|
262
|
+
const source = '';
|
|
263
|
+
const target = yaml.dump({ user: 'config' });
|
|
264
|
+
|
|
265
|
+
const result = await merger.merge(source, target);
|
|
266
|
+
const merged = yaml.load(result.content);
|
|
267
|
+
|
|
268
|
+
expect(merged.user).toBe('config');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('empty target → source keys added', async () => {
|
|
272
|
+
const source = yaml.dump({ framework: 'config' });
|
|
273
|
+
const target = '';
|
|
274
|
+
|
|
275
|
+
const result = await merger.merge(source, target);
|
|
276
|
+
const merged = yaml.load(result.content);
|
|
277
|
+
|
|
278
|
+
expect(merged.framework).toBe('config');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('arrays treated as scalar (target wins, not merged)', async () => {
|
|
282
|
+
const source = yaml.dump({ list: [1, 2, 3] });
|
|
283
|
+
const target = yaml.dump({ list: [4, 5] });
|
|
284
|
+
|
|
285
|
+
const result = await merger.merge(source, target);
|
|
286
|
+
const merged = yaml.load(result.content);
|
|
287
|
+
|
|
288
|
+
expect(merged.list).toEqual([4, 5]); // target wins
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('Brownfield Upgrader Integration (Story INS-4.7)', () => {
|
|
294
|
+
test('brownfield-upgrader imports YamlMerger', () => {
|
|
295
|
+
const upgraderSource = fs.readFileSync(
|
|
296
|
+
path.join(__dirname, '..', '..', '..', 'src', 'installer', 'brownfield-upgrader.js'),
|
|
297
|
+
'utf8'
|
|
298
|
+
);
|
|
299
|
+
expect(upgraderSource).toContain('YamlMerger');
|
|
300
|
+
expect(upgraderSource).toContain('yaml-merger.js');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('upgrader has core-config.yaml merge exception in userModifiedFiles loop', () => {
|
|
304
|
+
const upgraderSource = fs.readFileSync(
|
|
305
|
+
path.join(__dirname, '..', '..', '..', 'src', 'installer', 'brownfield-upgrader.js'),
|
|
306
|
+
'utf8'
|
|
307
|
+
);
|
|
308
|
+
expect(upgraderSource).toContain("file.path.endsWith('core-config.yaml')");
|
|
309
|
+
expect(upgraderSource).toContain('merger.merge(sourceContent, targetContent)');
|
|
310
|
+
expect(upgraderSource).toContain('.backup-');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('upgrader still skips non-yaml user-modified files', () => {
|
|
314
|
+
const upgraderSource = fs.readFileSync(
|
|
315
|
+
path.join(__dirname, '..', '..', '..', 'src', 'installer', 'brownfield-upgrader.js'),
|
|
316
|
+
'utf8'
|
|
317
|
+
);
|
|
318
|
+
expect(upgraderSource).toContain('User modified - preserving local changes');
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
describe('Existing strategies still work', () => {
|
|
323
|
+
test('strategies.test.js file exists (regression guard)', () => {
|
|
324
|
+
const testPath = path.join(__dirname, 'strategies.test.js');
|
|
325
|
+
expect(fs.existsSync(testPath)).toBe(true);
|
|
326
|
+
});
|
|
327
|
+
});
|