aios-core 4.2.14 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/.aios-core/cli/commands/validate/index.js +1 -1
  2. package/.aios-core/core/code-intel/helpers/creation-helper.js +183 -0
  3. package/.aios-core/core/code-intel/helpers/devops-helper.js +166 -0
  4. package/.aios-core/core/code-intel/helpers/planning-helper.js +248 -0
  5. package/.aios-core/core/code-intel/helpers/qa-helper.js +187 -0
  6. package/.aios-core/core/code-intel/helpers/story-helper.js +146 -0
  7. package/.aios-core/core/config/schemas/framework-config.schema.json +155 -7
  8. package/.aios-core/core/config/schemas/project-config.schema.json +329 -15
  9. package/.aios-core/core/config/template-overrides.js +84 -0
  10. package/.aios-core/core/docs/troubleshooting-guide.md +1 -1
  11. package/.aios-core/core/doctor/checks/agent-memory.js +63 -0
  12. package/.aios-core/core/doctor/checks/claude-md.js +56 -0
  13. package/.aios-core/core/doctor/checks/code-intel.js +57 -0
  14. package/.aios-core/core/doctor/checks/commands-count.js +81 -0
  15. package/.aios-core/core/doctor/checks/core-config.js +53 -0
  16. package/.aios-core/core/doctor/checks/entity-registry.js +53 -0
  17. package/.aios-core/core/doctor/checks/git-hooks.js +50 -0
  18. package/.aios-core/core/doctor/checks/graph-dashboard.js +48 -0
  19. package/.aios-core/core/doctor/checks/hooks-claude-count.js +107 -0
  20. package/.aios-core/core/doctor/checks/ide-sync.js +68 -0
  21. package/.aios-core/core/doctor/checks/index.js +46 -0
  22. package/.aios-core/core/doctor/checks/node-version.js +33 -0
  23. package/.aios-core/core/doctor/checks/npm-packages.js +35 -0
  24. package/.aios-core/core/doctor/checks/rules-files.js +61 -0
  25. package/.aios-core/core/doctor/checks/settings-json.js +121 -0
  26. package/.aios-core/core/doctor/checks/skills-count.js +72 -0
  27. package/.aios-core/core/doctor/fix-handler.js +165 -0
  28. package/.aios-core/core/doctor/formatters/json.js +14 -0
  29. package/.aios-core/core/doctor/formatters/text.js +59 -0
  30. package/.aios-core/core/doctor/index.js +94 -0
  31. package/.aios-core/core/graph-dashboard/cli.js +361 -0
  32. package/.aios-core/core/graph-dashboard/data-sources/code-intel-source.js +234 -0
  33. package/.aios-core/core/graph-dashboard/data-sources/metrics-source.js +95 -0
  34. package/.aios-core/core/graph-dashboard/data-sources/registry-source.js +106 -0
  35. package/.aios-core/core/graph-dashboard/formatters/dot-formatter.js +45 -0
  36. package/.aios-core/core/graph-dashboard/formatters/html-formatter.js +1437 -0
  37. package/.aios-core/core/graph-dashboard/formatters/json-formatter.js +13 -0
  38. package/.aios-core/core/graph-dashboard/formatters/mermaid-formatter.js +59 -0
  39. package/.aios-core/core/graph-dashboard/index.js +21 -0
  40. package/.aios-core/core/graph-dashboard/renderers/stats-renderer.js +217 -0
  41. package/.aios-core/core/graph-dashboard/renderers/status-renderer.js +125 -0
  42. package/.aios-core/core/graph-dashboard/renderers/tree-renderer.js +119 -0
  43. package/.aios-core/core/health-check/base-check.js +1 -1
  44. package/.aios-core/core/health-check/check-registry.js +1 -1
  45. package/.aios-core/core/health-check/checks/deployment/build-config.js +1 -1
  46. package/.aios-core/core/health-check/checks/deployment/ci-config.js +1 -1
  47. package/.aios-core/core/health-check/checks/deployment/deployment-readiness.js +1 -1
  48. package/.aios-core/core/health-check/checks/deployment/docker-config.js +1 -1
  49. package/.aios-core/core/health-check/checks/deployment/env-file.js +1 -1
  50. package/.aios-core/core/health-check/checks/deployment/index.js +1 -1
  51. package/.aios-core/core/health-check/checks/index.js +1 -1
  52. package/.aios-core/core/health-check/checks/local/disk-space.js +1 -1
  53. package/.aios-core/core/health-check/checks/local/environment-vars.js +1 -1
  54. package/.aios-core/core/health-check/checks/local/git-install.js +1 -1
  55. package/.aios-core/core/health-check/checks/local/ide-detection.js +1 -1
  56. package/.aios-core/core/health-check/checks/local/index.js +1 -1
  57. package/.aios-core/core/health-check/checks/local/memory.js +1 -1
  58. package/.aios-core/core/health-check/checks/local/network.js +1 -1
  59. package/.aios-core/core/health-check/checks/local/npm-install.js +1 -1
  60. package/.aios-core/core/health-check/checks/local/shell-environment.js +1 -1
  61. package/.aios-core/core/health-check/checks/project/agent-config.js +1 -1
  62. package/.aios-core/core/health-check/checks/project/aios-directory.js +1 -1
  63. package/.aios-core/core/health-check/checks/project/dependencies.js +1 -1
  64. package/.aios-core/core/health-check/checks/project/framework-config.js +1 -1
  65. package/.aios-core/core/health-check/checks/project/index.js +1 -1
  66. package/.aios-core/core/health-check/checks/project/node-version.js +1 -1
  67. package/.aios-core/core/health-check/checks/project/package-json.js +1 -1
  68. package/.aios-core/core/health-check/checks/project/task-definitions.js +1 -1
  69. package/.aios-core/core/health-check/checks/project/workflow-dependencies.js +1 -1
  70. package/.aios-core/core/health-check/checks/repository/branch-protection.js +1 -1
  71. package/.aios-core/core/health-check/checks/repository/commit-history.js +1 -1
  72. package/.aios-core/core/health-check/checks/repository/conflicts.js +1 -1
  73. package/.aios-core/core/health-check/checks/repository/git-repo.js +1 -1
  74. package/.aios-core/core/health-check/checks/repository/git-status.js +1 -1
  75. package/.aios-core/core/health-check/checks/repository/gitignore.js +1 -1
  76. package/.aios-core/core/health-check/checks/repository/index.js +1 -1
  77. package/.aios-core/core/health-check/checks/repository/large-files.js +1 -1
  78. package/.aios-core/core/health-check/checks/repository/lockfile-integrity.js +1 -1
  79. package/.aios-core/core/health-check/checks/services/api-endpoints.js +1 -1
  80. package/.aios-core/core/health-check/checks/services/claude-code.js +1 -1
  81. package/.aios-core/core/health-check/checks/services/gemini-cli.js +1 -1
  82. package/.aios-core/core/health-check/checks/services/github-cli.js +1 -1
  83. package/.aios-core/core/health-check/checks/services/index.js +1 -1
  84. package/.aios-core/core/health-check/checks/services/mcp-integration.js +1 -1
  85. package/.aios-core/core/health-check/engine.js +1 -1
  86. package/.aios-core/core/health-check/healers/backup-manager.js +1 -1
  87. package/.aios-core/core/health-check/healers/index.js +1 -1
  88. package/.aios-core/core/health-check/index.js +9 -2
  89. package/.aios-core/core/health-check/reporters/console.js +1 -1
  90. package/.aios-core/core/health-check/reporters/index.js +1 -1
  91. package/.aios-core/core/health-check/reporters/json.js +1 -1
  92. package/.aios-core/core/health-check/reporters/markdown.js +1 -1
  93. package/.aios-core/core/ids/layer-classifier.js +65 -0
  94. package/.aios-core/core/ids/registry-updater.js +49 -0
  95. package/.aios-core/core/index.esm.js +1 -1
  96. package/.aios-core/core/index.js +1 -1
  97. package/.aios-core/core/session/context-detector.js +2 -7
  98. package/.aios-core/core/synapse/context/context-tracker.js +9 -1
  99. package/.aios-core/core/synapse/engine.js +33 -13
  100. package/.aios-core/core/synapse/runtime/hook-runtime.js +40 -2
  101. package/.aios-core/core/synapse/session/session-manager.js +3 -2
  102. package/.aios-core/core/synapse/utils/atomic-write.js +79 -0
  103. package/.aios-core/core-config.yaml +34 -1
  104. package/.aios-core/data/aios-kb.md +2 -2
  105. package/.aios-core/data/capability-detection.js +290 -0
  106. package/.aios-core/data/entity-registry.yaml +10424 -2127
  107. package/.aios-core/data/mcp-discipline.js +166 -0
  108. package/.aios-core/data/mcp-tool-examples.yaml +215 -0
  109. package/.aios-core/data/tok2-validation.js +168 -0
  110. package/.aios-core/data/tok3-token-comparison.js +123 -0
  111. package/.aios-core/data/tool-registry.yaml +648 -0
  112. package/.aios-core/data/tool-search-validation.js +174 -0
  113. package/.aios-core/development/agents/analyst/MEMORY.md +33 -0
  114. package/.aios-core/development/agents/architect/MEMORY.md +39 -0
  115. package/.aios-core/development/agents/data-engineer/MEMORY.md +32 -0
  116. package/.aios-core/development/agents/dev/MEMORY.md +46 -0
  117. package/.aios-core/development/agents/dev.md +1 -1
  118. package/.aios-core/development/agents/devops/MEMORY.md +39 -0
  119. package/.aios-core/development/agents/devops.md +22 -0
  120. package/.aios-core/development/agents/pm/MEMORY.md +38 -0
  121. package/.aios-core/development/agents/po/MEMORY.md +45 -0
  122. package/.aios-core/development/agents/qa/MEMORY.md +42 -0
  123. package/.aios-core/development/agents/qa.md +1 -1
  124. package/.aios-core/development/agents/sm/MEMORY.md +31 -0
  125. package/.aios-core/development/agents/ux/MEMORY.md +31 -0
  126. package/.aios-core/development/checklists/issue-triage-checklist.md +35 -0
  127. package/.aios-core/development/checklists/memory-audit-checklist.md +53 -0
  128. package/.aios-core/development/scripts/issue-triage.js +171 -0
  129. package/.aios-core/development/scripts/populate-entity-registry.js +412 -19
  130. package/.aios-core/development/scripts/unified-activation-pipeline.js +31 -10
  131. package/.aios-core/development/tasks/analyze-project-structure.md +48 -0
  132. package/.aios-core/development/tasks/brownfield-create-epic.md +41 -0
  133. package/.aios-core/development/tasks/create-doc.md +44 -0
  134. package/.aios-core/development/tasks/create-next-story.md +10 -0
  135. package/.aios-core/development/tasks/dev-develop-story.md +1 -1
  136. package/.aios-core/development/tasks/github-devops-github-pr-automation.md +49 -0
  137. package/.aios-core/development/tasks/github-devops-pre-push-quality-gate.md +63 -0
  138. package/.aios-core/development/tasks/github-issue-triage.md +118 -0
  139. package/.aios-core/development/tasks/health-check.yaml +206 -171
  140. package/.aios-core/development/tasks/kb-mode-interaction.md +3 -3
  141. package/.aios-core/development/tasks/plan-create-context.md +47 -1
  142. package/.aios-core/development/tasks/plan-create-implementation.md +55 -0
  143. package/.aios-core/development/tasks/pr-automation.md +5 -5
  144. package/.aios-core/development/tasks/qa-gate.md +48 -0
  145. package/.aios-core/development/tasks/qa-review-story.md +24 -1
  146. package/.aios-core/development/tasks/resolve-github-issue.md +608 -0
  147. package/.aios-core/development/tasks/review-contributor-pr.md +152 -0
  148. package/.aios-core/development/tasks/setup-llm-routing.md +1 -1
  149. package/.aios-core/development/tasks/spec-research-dependencies.md +4 -0
  150. package/.aios-core/development/tasks/triage-github-issues.md +356 -0
  151. package/.aios-core/development/tasks/validate-agents.md +4 -0
  152. package/.aios-core/development/tasks/validate-next-story.md +10 -0
  153. package/.aios-core/development/templates/agent-handoff-tmpl.yaml +48 -0
  154. package/.aios-core/development/templates/code-intel-integration-pattern.md +199 -0
  155. package/.aios-core/development/templates/ptc-entity-validation.md +113 -0
  156. package/.aios-core/development/templates/ptc-qa-gate.md +100 -0
  157. package/.aios-core/development/templates/ptc-research-aggregation.md +94 -0
  158. package/.aios-core/development/templates/service-template/README.md.hbs +158 -158
  159. package/.aios-core/development/templates/service-template/__tests__/index.test.ts.hbs +237 -237
  160. package/.aios-core/development/templates/service-template/client.ts.hbs +403 -403
  161. package/.aios-core/development/templates/service-template/errors.ts.hbs +182 -182
  162. package/.aios-core/development/templates/service-template/index.ts.hbs +120 -120
  163. package/.aios-core/development/templates/service-template/package.json.hbs +87 -87
  164. package/.aios-core/development/templates/service-template/types.ts.hbs +145 -145
  165. package/.aios-core/development/templates/squad/agent-template.md +11 -0
  166. package/.aios-core/development/templates/squad/task-template.md +21 -0
  167. package/.aios-core/development/templates/squad-template/LICENSE +21 -21
  168. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +1 -1
  169. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.2-SUMMARY.md +1 -1
  170. package/.aios-core/framework-config.yaml +8 -0
  171. package/.aios-core/index.esm.js +1 -1
  172. package/.aios-core/index.js +1 -1
  173. package/.aios-core/infrastructure/integrations/ai-providers/index.js +1 -1
  174. package/.aios-core/infrastructure/schemas/task-v3-schema.json +6 -0
  175. package/.aios-core/infrastructure/scripts/collect-tool-usage.js +311 -0
  176. package/.aios-core/infrastructure/scripts/generate-optimization-report.js +497 -0
  177. package/.aios-core/infrastructure/scripts/generate-settings-json.js +300 -0
  178. package/.aios-core/infrastructure/scripts/git-config-detector.js +65 -9
  179. package/.aios-core/infrastructure/scripts/ide-sync/index.js +3 -1
  180. package/.aios-core/infrastructure/scripts/ide-sync/transformers/github-copilot.js +184 -0
  181. package/.aios-core/infrastructure/scripts/repository-detector.js +3 -3
  182. package/.aios-core/infrastructure/templates/aios-sync.yaml.template +182 -182
  183. package/.aios-core/infrastructure/templates/coderabbit.yaml.template +279 -279
  184. package/.aios-core/infrastructure/templates/github-workflows/ci.yml.template +169 -169
  185. package/.aios-core/infrastructure/templates/github-workflows/pr-automation.yml.template +330 -330
  186. package/.aios-core/infrastructure/templates/github-workflows/release.yml.template +196 -196
  187. package/.aios-core/infrastructure/templates/gitignore/gitignore-aios-base.tmpl +63 -63
  188. package/.aios-core/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +18 -18
  189. package/.aios-core/infrastructure/templates/gitignore/gitignore-node.tmpl +85 -85
  190. package/.aios-core/infrastructure/templates/gitignore/gitignore-python.tmpl +145 -145
  191. package/.aios-core/install-manifest.yaml +541 -249
  192. package/.aios-core/lib/build.json +1 -0
  193. package/.aios-core/local-config.yaml.template +71 -71
  194. package/.aios-core/monitor/hooks/lib/__init__.py +1 -1
  195. package/.aios-core/monitor/hooks/lib/enrich.py +58 -58
  196. package/.aios-core/monitor/hooks/lib/send_event.py +47 -47
  197. package/.aios-core/monitor/hooks/notification.py +29 -29
  198. package/.aios-core/monitor/hooks/post_tool_use.py +45 -45
  199. package/.aios-core/monitor/hooks/pre_compact.py +29 -29
  200. package/.aios-core/monitor/hooks/pre_tool_use.py +40 -40
  201. package/.aios-core/monitor/hooks/stop.py +29 -29
  202. package/.aios-core/monitor/hooks/subagent_stop.py +29 -29
  203. package/.aios-core/monitor/hooks/user_prompt_submit.py +38 -38
  204. package/.aios-core/product/templates/adr.hbs +125 -125
  205. package/.aios-core/product/templates/dbdr.hbs +241 -241
  206. package/.aios-core/product/templates/epic.hbs +212 -212
  207. package/.aios-core/product/templates/ide-rules/claude-rules.md +77 -0
  208. package/.aios-core/product/templates/pmdr.hbs +186 -186
  209. package/.aios-core/product/templates/prd-v2.0.hbs +216 -216
  210. package/.aios-core/product/templates/prd.hbs +201 -201
  211. package/.aios-core/product/templates/story.hbs +263 -263
  212. package/.aios-core/product/templates/task.hbs +170 -170
  213. package/.aios-core/product/templates/tmpl-comment-on-examples.sql +158 -158
  214. package/.aios-core/product/templates/tmpl-migration-script.sql +91 -91
  215. package/.aios-core/product/templates/tmpl-rls-granular-policies.sql +104 -104
  216. package/.aios-core/product/templates/tmpl-rls-kiss-policy.sql +10 -10
  217. package/.aios-core/product/templates/tmpl-rls-roles.sql +135 -135
  218. package/.aios-core/product/templates/tmpl-rls-simple.sql +77 -77
  219. package/.aios-core/product/templates/tmpl-rls-tenant.sql +152 -152
  220. package/.aios-core/product/templates/tmpl-rollback-script.sql +77 -77
  221. package/.aios-core/product/templates/tmpl-seed-data.sql +140 -140
  222. package/.aios-core/product/templates/tmpl-smoke-test.sql +16 -16
  223. package/.aios-core/product/templates/tmpl-staging-copy-merge.sql +139 -139
  224. package/.aios-core/product/templates/tmpl-stored-proc.sql +140 -140
  225. package/.aios-core/product/templates/tmpl-trigger.sql +152 -152
  226. package/.aios-core/product/templates/tmpl-view-materialized.sql +133 -133
  227. package/.aios-core/product/templates/tmpl-view.sql +177 -177
  228. package/.aios-core/scripts/pm.sh +0 -0
  229. package/.aios-core/user-guide.md +15 -15
  230. package/.aios-core/utils/filters/constants.js +10 -0
  231. package/.aios-core/utils/filters/content-filter.js +223 -0
  232. package/.aios-core/utils/filters/field-filter.js +126 -0
  233. package/.aios-core/utils/filters/index.js +180 -0
  234. package/.aios-core/utils/filters/schema-filter.js +157 -0
  235. package/.claude/CLAUDE.md +62 -0
  236. package/.claude/hooks/enforce-architecture-first.py +196 -196
  237. package/.claude/hooks/enforce-git-push-authority.sh +33 -0
  238. package/.claude/hooks/mind-clone-governance.py +192 -192
  239. package/.claude/hooks/read-protection.py +151 -151
  240. package/.claude/hooks/slug-validation.py +176 -176
  241. package/.claude/hooks/sql-governance.py +182 -182
  242. package/.claude/hooks/synapse-engine.cjs +28 -5
  243. package/.claude/hooks/write-path-validation.py +194 -194
  244. package/.claude/rules/agent-authority.md +105 -0
  245. package/.claude/rules/agent-handoff.md +97 -0
  246. package/.claude/rules/agent-memory-imports.md +15 -0
  247. package/.claude/rules/coderabbit-integration.md +101 -0
  248. package/.claude/rules/ids-principles.md +119 -0
  249. package/.claude/rules/story-lifecycle.md +145 -0
  250. package/.claude/rules/tool-examples.md +64 -0
  251. package/.claude/rules/tool-response-filtering.md +57 -0
  252. package/.claude/rules/workflow-execution.md +150 -0
  253. package/LICENSE +33 -33
  254. package/bin/aios-graph.js +9 -0
  255. package/bin/aios-init.js +2 -2
  256. package/bin/aios-minimal.js +0 -0
  257. package/bin/aios.js +17 -221
  258. package/bin/utils/detect-fsmonitor.js +70 -0
  259. package/bin/utils/framework-guard.js +238 -0
  260. package/bin/utils/validate-publish.js +108 -0
  261. package/package.json +6 -3
  262. package/packages/aios-install/bin/aios-install.js +0 -0
  263. package/packages/aios-install/bin/edmcp.js +0 -0
  264. package/packages/aios-pro-cli/bin/aios-pro.js +2 -0
  265. package/packages/installer/src/installer/brownfield-upgrader.js +68 -5
  266. package/packages/installer/src/merger/index.js +3 -0
  267. package/packages/installer/src/merger/strategies/index.js +6 -0
  268. package/packages/installer/src/merger/strategies/yaml-merger.js +181 -0
  269. package/packages/installer/src/updater/index.js +4 -4
  270. package/packages/installer/src/wizard/i18n.js +321 -3
  271. package/packages/installer/src/wizard/ide-config-generator.js +152 -25
  272. package/packages/installer/src/wizard/index.js +119 -1
  273. package/packages/installer/src/wizard/pro-setup.js +137 -121
  274. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +261 -0
  275. package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +192 -0
  276. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +551 -0
  277. package/packages/installer/tests/unit/doctor/doctor-orchestrator.test.js +134 -0
  278. package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +186 -0
  279. package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +309 -0
  280. package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +230 -0
  281. package/packages/installer/tests/unit/merger/strategies.test.js +2 -2
  282. package/packages/installer/tests/unit/merger/yaml-merger.test.js +327 -0
  283. package/scripts/check-markdown-links.py +352 -352
  284. package/scripts/dashboard-parallel-dev.sh +0 -0
  285. package/scripts/dashboard-parallel-phase3.sh +0 -0
  286. package/scripts/dashboard-parallel-phase4.sh +0 -0
  287. package/scripts/install-monitor-hooks.sh +0 -0
  288. package/scripts/package-synapse.js +2 -1
@@ -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.yaml');
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.yaml')).toBe(false);
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
+ });