agentera 3.0.0-dev.23 → 3.0.0-dev.26

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 (376) hide show
  1. package/README.md +18 -22
  2. package/bundle/CHANGELOG.md +25 -27
  3. package/bundle/README.md +22 -42
  4. package/bundle/UPGRADE.md +71 -179
  5. package/bundle/references/adapters/package-manifest-interface-model.yaml +41 -240
  6. package/bundle/references/adapters/package-registry.yaml +9 -145
  7. package/bundle/references/adapters/package-surface-characterization.md +13 -38
  8. package/bundle/references/adapters/runtime-lifecycle-adapters.yaml +5 -219
  9. package/bundle/references/adapters/runtime-lifecycle-authority.yaml +2 -29
  10. package/bundle/references/adapters/runtime-lifecycle-operation-contract.yaml +1 -1
  11. package/bundle/references/artifacts/state-storage-authority.yaml +729 -412
  12. package/bundle/references/cli/agent-ready-state-contract.yaml +12 -12
  13. package/bundle/references/cli/app-lifecycle-vocabulary.yaml +3 -4
  14. package/bundle/references/cli/bundle-skill-vocabulary.yaml +9 -18
  15. package/bundle/references/cli/capability-tool-classification.yaml +2 -2
  16. package/bundle/references/cli/prime-consumer-compatibility.yaml +28 -98
  17. package/bundle/references/cli/routing-execution-vocabulary.yaml +4 -22
  18. package/bundle/references/cli/update-channels.yaml +4 -2
  19. package/bundle/references/cli/upgrade-repair-wording.md +8 -28
  20. package/bundle/references/cli/v3-handoff-manifest.schema.yaml +2 -3
  21. package/bundle/references/cli/vocabulary.md +22 -27
  22. package/bundle/references/meta/documentation-inventory.md +1 -1
  23. package/bundle/skills/agentera/SKILL.md +28 -14
  24. package/bundle/skills/agentera/capabilities/audit/schemas/artifacts.yaml +12 -12
  25. package/bundle/skills/agentera/capabilities/audit/schemas/validation.yaml +2 -2
  26. package/bundle/skills/agentera/capabilities/build/schemas/artifacts.yaml +12 -12
  27. package/bundle/skills/agentera/capabilities/build/schemas/validation.yaml +8 -8
  28. package/bundle/skills/agentera/capabilities/design/schemas/artifacts.yaml +6 -6
  29. package/bundle/skills/agentera/capabilities/discuss/schemas/artifacts.yaml +7 -7
  30. package/bundle/skills/agentera/capabilities/document/schemas/artifacts.yaml +12 -12
  31. package/bundle/skills/agentera/capabilities/optimize/schemas/artifacts.yaml +9 -9
  32. package/bundle/skills/agentera/capabilities/orchestrate/schemas/artifacts.yaml +11 -11
  33. package/bundle/skills/agentera/capabilities/plan/schemas/artifacts.yaml +9 -9
  34. package/bundle/skills/agentera/capabilities/profile/schemas/artifacts.yaml +2 -2
  35. package/bundle/skills/agentera/capabilities/research/schemas/artifacts.yaml +3 -3
  36. package/bundle/skills/agentera/capabilities/status/schemas/artifacts.yaml +11 -11
  37. package/bundle/skills/agentera/capabilities/vision/schemas/artifacts.yaml +8 -8
  38. package/bundle/skills/agentera/schemas/artifacts/decisions.yaml +15 -2
  39. package/bundle/skills/agentera/schemas/artifacts/docs.yaml +7 -12
  40. package/bundle/skills/agentera/schemas/artifacts/experiments.yaml +21 -21
  41. package/bundle/skills/agentera/schemas/artifacts/health.yaml +15 -2
  42. package/bundle/skills/agentera/schemas/artifacts/plan.yaml +36 -15
  43. package/bundle/skills/agentera/schemas/artifacts/progress.yaml +15 -2
  44. package/bundle/skills/agentera/schemas/artifacts/todo.yaml +4 -4
  45. package/dist/capabilities/audit/instructions.js +13 -1
  46. package/dist/capabilities/audit/instructions.js.map +1 -1
  47. package/dist/capabilities/build/instructions.js +14 -1
  48. package/dist/capabilities/build/instructions.js.map +1 -1
  49. package/dist/capabilities/discuss/instructions.js +11 -1
  50. package/dist/capabilities/discuss/instructions.js.map +1 -1
  51. package/dist/capabilities/document/instructions.js +9 -1
  52. package/dist/capabilities/document/instructions.js.map +1 -1
  53. package/dist/capabilities/index.js +7 -7
  54. package/dist/capabilities/index.js.map +1 -1
  55. package/dist/capabilities/optimize/instructions.js +10 -1
  56. package/dist/capabilities/optimize/instructions.js.map +1 -1
  57. package/dist/capabilities/orchestrate/instructions.js +13 -2
  58. package/dist/capabilities/orchestrate/instructions.js.map +1 -1
  59. package/dist/capabilities/plan/instructions.js +10 -1
  60. package/dist/capabilities/plan/instructions.js.map +1 -1
  61. package/dist/cli/capabilityContext/bespoke.js +8 -2
  62. package/dist/cli/capabilityContext/bespoke.js.map +1 -1
  63. package/dist/cli/capabilityContext/closeout.js +3 -3
  64. package/dist/cli/capabilityContext/closeout.js.map +1 -1
  65. package/dist/cli/capabilityContext/contract.js +10 -15
  66. package/dist/cli/capabilityContext/contract.js.map +1 -1
  67. package/dist/cli/capabilityContext/evidence.js +55 -43
  68. package/dist/cli/capabilityContext/evidence.js.map +1 -1
  69. package/dist/cli/capabilityContext/planState.js +5 -5
  70. package/dist/cli/capabilityContext/planState.js.map +1 -1
  71. package/dist/cli/capabilityContext/progress.js +1 -1
  72. package/dist/cli/capabilityContext/progress.js.map +1 -1
  73. package/dist/cli/capabilityContext/shared.js +8 -2
  74. package/dist/cli/capabilityContext/shared.js.map +1 -1
  75. package/dist/cli/capabilityContext/slim.js +2 -1
  76. package/dist/cli/capabilityContext/slim.js.map +1 -1
  77. package/dist/cli/capabilityContext/startup.js +19 -8
  78. package/dist/cli/capabilityContext/startup.js.map +1 -1
  79. package/dist/cli/capabilityContext/types.js +4 -4
  80. package/dist/cli/capabilityContext/types.js.map +1 -1
  81. package/dist/cli/commands/doctor.js +6 -64
  82. package/dist/cli/commands/doctor.js.map +1 -1
  83. package/dist/cli/commands/durability.js +27 -30
  84. package/dist/cli/commands/durability.js.map +1 -1
  85. package/dist/cli/commands/entityMigrate.js +128 -0
  86. package/dist/cli/commands/entityMigrate.js.map +1 -0
  87. package/dist/cli/commands/prime/briefOrientation.js +7 -22
  88. package/dist/cli/commands/prime/briefOrientation.js.map +1 -1
  89. package/dist/cli/commands/prime/bundleStatus.js +8 -2
  90. package/dist/cli/commands/prime/bundleStatus.js.map +1 -1
  91. package/dist/cli/commands/prime/collectEntityOrientation.js +161 -0
  92. package/dist/cli/commands/prime/collectEntityOrientation.js.map +1 -0
  93. package/dist/cli/commands/prime/collectOrientationState.js +32 -59
  94. package/dist/cli/commands/prime/collectOrientationState.js.map +1 -1
  95. package/dist/cli/commands/prime/orientationOutput.js +4 -12
  96. package/dist/cli/commands/prime/orientationOutput.js.map +1 -1
  97. package/dist/cli/commands/prime.js +2 -5
  98. package/dist/cli/commands/prime.js.map +1 -1
  99. package/dist/cli/commands/query.js +43 -49
  100. package/dist/cli/commands/query.js.map +1 -1
  101. package/dist/cli/commands/schema.js +26 -83
  102. package/dist/cli/commands/schema.js.map +1 -1
  103. package/dist/cli/commands/state/decisions.js +14 -47
  104. package/dist/cli/commands/state/decisions.js.map +1 -1
  105. package/dist/cli/commands/state/docs.js +58 -84
  106. package/dist/cli/commands/state/docs.js.map +1 -1
  107. package/dist/cli/commands/state/experimentRecords.js +27 -44
  108. package/dist/cli/commands/state/experimentRecords.js.map +1 -1
  109. package/dist/cli/commands/state/experiments.js +11 -64
  110. package/dist/cli/commands/state/experiments.js.map +1 -1
  111. package/dist/cli/commands/state/get.js +74 -88
  112. package/dist/cli/commands/state/get.js.map +1 -1
  113. package/dist/cli/commands/state/health.js +11 -112
  114. package/dist/cli/commands/state/health.js.map +1 -1
  115. package/dist/cli/commands/state/index.js +11 -2
  116. package/dist/cli/commands/state/index.js.map +1 -1
  117. package/dist/cli/commands/state/list.js +30 -13
  118. package/dist/cli/commands/state/list.js.map +1 -1
  119. package/dist/cli/commands/state/objective.js +21 -64
  120. package/dist/cli/commands/state/objective.js.map +1 -1
  121. package/dist/cli/commands/state/plan.js +11 -376
  122. package/dist/cli/commands/state/plan.js.map +1 -1
  123. package/dist/cli/commands/state/planTasks.js +58 -80
  124. package/dist/cli/commands/state/planTasks.js.map +1 -1
  125. package/dist/cli/commands/state/plans.js +27 -38
  126. package/dist/cli/commands/state/plans.js.map +1 -1
  127. package/dist/cli/commands/state/progress.js +11 -44
  128. package/dist/cli/commands/state/progress.js.map +1 -1
  129. package/dist/cli/commands/state/shared.js.map +1 -1
  130. package/dist/cli/commands/state/todo.js +27 -106
  131. package/dist/cli/commands/state/todo.js.map +1 -1
  132. package/dist/cli/commands/state/write.js +57 -4
  133. package/dist/cli/commands/state/write.js.map +1 -1
  134. package/dist/cli/commands/upgrade.js +99 -35
  135. package/dist/cli/commands/upgrade.js.map +1 -1
  136. package/dist/cli/commands/upgradeVerify.js +52 -14
  137. package/dist/cli/commands/upgradeVerify.js.map +1 -1
  138. package/dist/cli/commands/validate.js +66 -9
  139. package/dist/cli/commands/validate.js.map +1 -1
  140. package/dist/cli/dispatch/check.js +13 -13
  141. package/dist/cli/dispatch/check.js.map +1 -1
  142. package/dist/cli/dispatch/index.js +35 -28
  143. package/dist/cli/dispatch/index.js.map +1 -1
  144. package/dist/cli/dispatch/lifecycle.js +23 -43
  145. package/dist/cli/dispatch/lifecycle.js.map +1 -1
  146. package/dist/cli/dispatch/prime.js +15 -0
  147. package/dist/cli/dispatch/prime.js.map +1 -1
  148. package/dist/cli/dispatch/state.js +7 -1
  149. package/dist/cli/dispatch/state.js.map +1 -1
  150. package/dist/cli/help.js +90 -112
  151. package/dist/cli/help.js.map +1 -1
  152. package/dist/cli/migrationRequired.js +87 -0
  153. package/dist/cli/migrationRequired.js.map +1 -0
  154. package/dist/cli/orientation/attention.js +3 -99
  155. package/dist/cli/orientation/attention.js.map +1 -1
  156. package/dist/cli/orientation.js +10 -7
  157. package/dist/cli/orientation.js.map +1 -1
  158. package/dist/cli/planArtifacts.js +192 -42
  159. package/dist/cli/planArtifacts.js.map +1 -1
  160. package/dist/cli/prime-blob.js +5 -5
  161. package/dist/cli/prime-blob.js.map +1 -1
  162. package/dist/cli/stateQuery.js +6 -7
  163. package/dist/cli/stateQuery.js.map +1 -1
  164. package/dist/core/skillIdentity.js +24 -0
  165. package/dist/core/skillIdentity.js.map +1 -0
  166. package/dist/hooks/common.js +1 -1
  167. package/dist/hooks/common.js.map +1 -1
  168. package/dist/hooks/compaction/status.js +27 -2
  169. package/dist/hooks/compaction/status.js.map +1 -1
  170. package/dist/hooks/cursorSessionStart.js +4 -19
  171. package/dist/hooks/cursorSessionStart.js.map +1 -1
  172. package/dist/hooks/projectHookInput.js +26 -0
  173. package/dist/hooks/projectHookInput.js.map +1 -0
  174. package/dist/hooks/sessionStart.js +58 -212
  175. package/dist/hooks/sessionStart.js.map +1 -1
  176. package/dist/hooks/sessionStop.js +4 -12
  177. package/dist/hooks/sessionStop.js.map +1 -1
  178. package/dist/hooks/validateArtifact/traversal.js +1 -1
  179. package/dist/hooks/validateArtifact/traversal.js.map +1 -1
  180. package/dist/registries/artifactRegistry.js +24 -14
  181. package/dist/registries/artifactRegistry.js.map +1 -1
  182. package/dist/registries/packageRegistry.js +58 -232
  183. package/dist/registries/packageRegistry.js.map +1 -1
  184. package/dist/release/releaseMetadata.js +1 -13
  185. package/dist/release/releaseMetadata.js.map +1 -1
  186. package/dist/runtime/lifecycleAdapterContract.js +4 -4
  187. package/dist/runtime/lifecycleAdapterContract.js.map +1 -1
  188. package/dist/runtime/lifecycleAdapters.js +14 -2
  189. package/dist/runtime/lifecycleAdapters.js.map +1 -1
  190. package/dist/runtime/lifecycleAuthority.js +5 -43
  191. package/dist/runtime/lifecycleAuthority.js.map +1 -1
  192. package/dist/runtime/lifecycleOperationContract.js +3 -2
  193. package/dist/runtime/lifecycleOperationContract.js.map +1 -1
  194. package/dist/runtime/lifecyclePublication.js +21 -0
  195. package/dist/runtime/lifecyclePublication.js.map +1 -1
  196. package/dist/setup/opencode.js +20 -12
  197. package/dist/setup/opencode.js.map +1 -1
  198. package/dist/setup/sharedSkill.js +46 -0
  199. package/dist/setup/sharedSkill.js.map +1 -0
  200. package/dist/setup/smokeChecks.js +16 -43
  201. package/dist/setup/smokeChecks.js.map +1 -1
  202. package/dist/state/archivePublication.js +3 -2
  203. package/dist/state/archivePublication.js.map +1 -1
  204. package/dist/state/decisionEntities.js +282 -0
  205. package/dist/state/decisionEntities.js.map +1 -0
  206. package/dist/state/decisionOverlay.js +2 -2
  207. package/dist/state/decisionOverlay.js.map +1 -1
  208. package/dist/state/decisionRevision.js +44 -34
  209. package/dist/state/decisionRevision.js.map +1 -1
  210. package/dist/state/directRetrieval.js.map +1 -1
  211. package/dist/state/durability.js +81 -226
  212. package/dist/state/durability.js.map +1 -1
  213. package/dist/state/entityCutover.js +294 -0
  214. package/dist/state/entityCutover.js.map +1 -0
  215. package/dist/state/entityCutoverGit.js +128 -0
  216. package/dist/state/entityCutoverGit.js.map +1 -0
  217. package/dist/state/entityMigrationPreview.js +803 -0
  218. package/dist/state/entityMigrationPreview.js.map +1 -0
  219. package/dist/state/entityPublicationContext.js +612 -0
  220. package/dist/state/entityPublicationContext.js.map +1 -0
  221. package/dist/state/entityStorage.js +826 -0
  222. package/dist/state/entityStorage.js.map +1 -0
  223. package/dist/state/experimentIdentity.js +23 -11
  224. package/dist/state/experimentIdentity.js.map +1 -1
  225. package/dist/state/healthEntities.js +230 -0
  226. package/dist/state/healthEntities.js.map +1 -0
  227. package/dist/state/healthEntityValidation.js +6 -0
  228. package/dist/state/healthEntityValidation.js.map +1 -0
  229. package/dist/state/legacyEntityCutoverEvidence.js +66 -0
  230. package/dist/state/legacyEntityCutoverEvidence.js.map +1 -0
  231. package/dist/state/listRetrieval.js.map +1 -1
  232. package/dist/state/objectiveExperimentEntities.js +323 -0
  233. package/dist/state/objectiveExperimentEntities.js.map +1 -0
  234. package/dist/state/planEntities.js +362 -0
  235. package/dist/state/planEntities.js.map +1 -0
  236. package/dist/state/progressEntities.js +384 -0
  237. package/dist/state/progressEntities.js.map +1 -0
  238. package/dist/state/projectRoot.js +63 -0
  239. package/dist/state/projectRoot.js.map +1 -0
  240. package/dist/state/retrievalAuthority.js +13 -6
  241. package/dist/state/retrievalAuthority.js.map +1 -1
  242. package/dist/state/safeProjectFile.js +153 -0
  243. package/dist/state/safeProjectFile.js.map +1 -0
  244. package/dist/state/startupProjection.js +3 -2
  245. package/dist/state/startupProjection.js.map +1 -1
  246. package/dist/state/stateMode.js +83 -0
  247. package/dist/state/stateMode.js.map +1 -0
  248. package/dist/state/todoDocsEntities.js +225 -0
  249. package/dist/state/todoDocsEntities.js.map +1 -0
  250. package/dist/state/todoDocsEntityValidation.js +26 -0
  251. package/dist/state/todoDocsEntityValidation.js.map +1 -0
  252. package/dist/state/write/candidateMutation.js +5 -55
  253. package/dist/state/write/candidateMutation.js.map +1 -1
  254. package/dist/state/write/explain.js +119 -28
  255. package/dist/state/write/explain.js.map +1 -1
  256. package/dist/state/write/lock.js +794 -87
  257. package/dist/state/write/lock.js.map +1 -1
  258. package/dist/state/write/mutation.js +5 -2
  259. package/dist/state/write/mutation.js.map +1 -1
  260. package/dist/state/write/operations.js +52 -7
  261. package/dist/state/write/operations.js.map +1 -1
  262. package/dist/state/write/planEvaluation.js +107 -0
  263. package/dist/state/write/planEvaluation.js.map +1 -0
  264. package/dist/state/write/planPublication.js +11 -55
  265. package/dist/state/write/planPublication.js.map +1 -1
  266. package/dist/state/write/transaction.js +32 -455
  267. package/dist/state/write/transaction.js.map +1 -1
  268. package/dist/upgrade/appContentRefresh.js +43 -28
  269. package/dist/upgrade/appContentRefresh.js.map +1 -1
  270. package/dist/upgrade/compatibility.js +4 -4
  271. package/dist/upgrade/compatibility.js.map +1 -1
  272. package/dist/upgrade/doctor.js +5 -1
  273. package/dist/upgrade/doctor.js.map +1 -1
  274. package/dist/upgrade/installedHooksRetirement.js +46 -39
  275. package/dist/upgrade/installedHooksRetirement.js.map +1 -1
  276. package/dist/upgrade/legacyAgentCleanup.js +132 -32
  277. package/dist/upgrade/legacyAgentCleanup.js.map +1 -1
  278. package/dist/upgrade/lifecycleUpgrade.js +65 -378
  279. package/dist/upgrade/lifecycleUpgrade.js.map +1 -1
  280. package/dist/upgrade/migrateArtifactsV2ToV3.js +164 -66
  281. package/dist/upgrade/migrateArtifactsV2ToV3.js.map +1 -1
  282. package/dist/upgrade/migrationPublication.js +123 -0
  283. package/dist/upgrade/migrationPublication.js.map +1 -0
  284. package/dist/upgrade/projectIntegration.js +46 -113
  285. package/dist/upgrade/projectIntegration.js.map +1 -1
  286. package/dist/upgrade/projectIntegrationDecision.js +6 -96
  287. package/dist/upgrade/projectIntegrationDecision.js.map +1 -1
  288. package/dist/upgrade/runtimeMigration.js +222 -402
  289. package/dist/upgrade/runtimeMigration.js.map +1 -1
  290. package/dist/upgrade/upgradeCommands.js +5 -4
  291. package/dist/upgrade/upgradeCommands.js.map +1 -1
  292. package/dist/upgrade/upgradeLock.js +73 -41
  293. package/dist/upgrade/upgradeLock.js.map +1 -1
  294. package/dist/upgrade/upgradeOrchestrator.js +181 -145
  295. package/dist/upgrade/upgradeOrchestrator.js.map +1 -1
  296. package/dist/upgrade/v3CapabilitySurface.js +2 -2
  297. package/dist/validate/appHomeContract.js +0 -4
  298. package/dist/validate/appHomeContract.js.map +1 -1
  299. package/dist/validate/crossCapability.js +2 -2
  300. package/dist/validate/crossCapability.js.map +1 -1
  301. package/package.json +8 -6
  302. package/bundle/.codex-plugin/plugin.json +0 -92
  303. package/bundle/.cursor/agents/agentera.md +0 -7
  304. package/bundle/.cursor/hooks.json +0 -32
  305. package/bundle/.cursor-plugin/plugin.json +0 -34
  306. package/bundle/.github/hooks/postToolUse.json +0 -9
  307. package/bundle/.github/hooks/preToolUse.json +0 -8
  308. package/bundle/.github/hooks/sessionEnd.json +0 -8
  309. package/bundle/.github/hooks/sessionStart.json +0 -8
  310. package/bundle/.github/plugin/plugin.json +0 -7
  311. package/bundle/.opencode/agents/agentera.md +0 -12
  312. package/bundle/.opencode/commands/agentera.md +0 -5
  313. package/bundle/.opencode/package.json +0 -31
  314. package/bundle/.opencode/plugins/agentera.js +0 -820
  315. package/bundle/agents/openai.yaml +0 -87
  316. package/bundle/hooks/codex-hooks.json +0 -31
  317. package/bundle/hooks/codex-plugin-hooks.json +0 -31
  318. package/bundle/plugin.json +0 -13
  319. package/bundle/references/adapters/cursor.md +0 -224
  320. package/bundle/references/adapters/opencode.md +0 -529
  321. package/bundle/references/adapters/runtime-adapter-characterization.md +0 -44
  322. package/bundle/references/adapters/runtime-adapter-interface-model.yaml +0 -200
  323. package/bundle/references/adapters/runtime-adapter-registry.yaml +0 -387
  324. package/bundle/references/adapters/runtime-feature-parity.md +0 -129
  325. package/dist/cli/commands/backfill.js +0 -194
  326. package/dist/cli/commands/backfill.js.map +0 -1
  327. package/dist/cli/commands/migrate.js +0 -677
  328. package/dist/cli/commands/migrate.js.map +0 -1
  329. package/dist/registries/runtimeAdapterRegistry.js +0 -324
  330. package/dist/registries/runtimeAdapterRegistry.js.map +0 -1
  331. package/dist/setup/copilot.js +0 -227
  332. package/dist/setup/copilot.js.map +0 -1
  333. package/dist/setup/cursor.js +0 -129
  334. package/dist/setup/cursor.js.map +0 -1
  335. package/dist/setup/cursorSurfaces.js +0 -67
  336. package/dist/setup/cursorSurfaces.js.map +0 -1
  337. package/dist/setup/doctor/core.js +0 -296
  338. package/dist/setup/doctor/core.js.map +0 -1
  339. package/dist/setup/doctor/diagnostics.js +0 -201
  340. package/dist/setup/doctor/diagnostics.js.map +0 -1
  341. package/dist/setup/doctor/opencode.js +0 -301
  342. package/dist/setup/doctor/opencode.js.map +0 -1
  343. package/dist/setup/doctor/report.js +0 -425
  344. package/dist/setup/doctor/report.js.map +0 -1
  345. package/dist/setup/doctor.js +0 -10
  346. package/dist/setup/doctor.js.map +0 -1
  347. package/dist/state/gitBackfill.js +0 -561
  348. package/dist/state/gitBackfill.js.map +0 -1
  349. package/dist/state/gitBackfillAuthority.js +0 -141
  350. package/dist/state/gitBackfillAuthority.js.map +0 -1
  351. package/dist/state/gitBackfillHistory.js +0 -278
  352. package/dist/state/gitBackfillHistory.js.map +0 -1
  353. package/dist/state/gitBackfillOutput.js +0 -209
  354. package/dist/state/gitBackfillOutput.js.map +0 -1
  355. package/dist/state/legacyMigration.js +0 -516
  356. package/dist/state/legacyMigration.js.map +0 -1
  357. package/dist/state/legacyMigrationParser.js +0 -489
  358. package/dist/state/legacyMigrationParser.js.map +0 -1
  359. package/dist/state/migrationAuthority.js +0 -413
  360. package/dist/state/migrationAuthority.js.map +0 -1
  361. package/dist/state/migrationEnrichment.js +0 -35
  362. package/dist/state/migrationEnrichment.js.map +0 -1
  363. package/dist/upgrade/migrateArtifactsV1ToV2.js +0 -568
  364. package/dist/upgrade/migrateArtifactsV1ToV2.js.map +0 -1
  365. package/dist/upgrade/upgradeSnapshot.js +0 -93
  366. package/dist/upgrade/upgradeSnapshot.js.map +0 -1
  367. package/dist/validate/lifecycleAdapters/legacyPythonParity.js +0 -93
  368. package/dist/validate/lifecycleAdapters/legacyPythonParity.js.map +0 -1
  369. package/dist/validate/lifecycleAdapters/lifecycleMain.js +0 -154
  370. package/dist/validate/lifecycleAdapters/lifecycleMain.js.map +0 -1
  371. package/dist/validate/lifecycleAdapters/nodeFormChecks.js +0 -481
  372. package/dist/validate/lifecycleAdapters/nodeFormChecks.js.map +0 -1
  373. package/dist/validate/lifecycleAdapters/shared.js +0 -198
  374. package/dist/validate/lifecycleAdapters/shared.js.map +0 -1
  375. package/dist/validate/lifecycleAdapters.js +0 -10
  376. package/dist/validate/lifecycleAdapters.js.map +0 -1
@@ -0,0 +1,803 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import YAML from "yaml";
5
+ import { resolveSourceRoot } from "../core/sourceRoot.js";
6
+ import { loadYamlMapping } from "../core/yaml.js";
7
+ import { discoverPlanArtifacts, planDocumentParts } from "../cli/planArtifacts.js";
8
+ import { parseTodoMarkdownListItem } from "../cli/todoMarkdown.js";
9
+ import { assertRealpathBoundary, docsPathOverridesFromBytes, loadArtifactRecord, resolveArtifactPath } from "../registries/artifactRegistry.js";
10
+ import { canonicalRecordJson, validateStateRecord } from "./archiveDiscovery.js";
11
+ import { canonicalEntityEnvelopeBytes, validateCanonicalEntityTargets } from "./entityStorage.js";
12
+ import { discoverObjectiveArtifacts, inspectExperimentIdentities } from "./experimentIdentity.js";
13
+ import { decisionRevisionContract, decisionRevisionViolations } from "./decisionRevision.js";
14
+ import { assertValidatedProjectRoot, validateRealProjectRoot } from "./projectRoot.js";
15
+ import { todoDocsRecordViolations } from "./todoDocsEntityValidation.js";
16
+ import { yamlArchiveEntry } from "../hooks/compaction/retention.js";
17
+ import { truncateWords } from "../hooks/compaction/dryRun.js";
18
+ import { legacyIdentity } from "./legacyIdentity.js";
19
+ import { projectPathIsStable as migrationPathIsStable, readProjectFileSnapshot, resolveProjectDescriptorPath, snapshotProjectPath as snapshotMigrationPath, } from "./safeProjectFile.js";
20
+ const MAX_OUTPUT_BYTES = 32_768;
21
+ const DEFAULT_LIMIT = 100;
22
+ const MAX_LIMIT = 1000;
23
+ const NUMBERED = {
24
+ progress: { file: ".agentera/progress.yaml", collection: "cycles", boundary: "progress_cycle" },
25
+ decisions: { file: ".agentera/decisions.yaml", collection: "decisions", boundary: "decision" },
26
+ health: { file: ".agentera/health.yaml", collection: "audits", boundary: "health_audit" },
27
+ };
28
+ const BLOCKING = new Set(["irrecoverable_summary_only", "duplicate", "conflict", "corrupt", "unsupported"]);
29
+ const INVENTORY_ORDER = "artifact_then_boundary_then_source_identity_then_source_path";
30
+ const INVENTORY_FILTER = "complete_declared_inventory";
31
+ const AUTHORITY_PATH = "references/artifacts/state-storage-authority.yaml";
32
+ const PLAN_ARCHIVE_SOURCE = /^\.agentera\/archive\/PLAN-[^/]+\.ya?ml$/i;
33
+ function mapping(value) {
34
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
35
+ }
36
+ function hash(value) {
37
+ return createHash("sha256").update(value).digest("hex");
38
+ }
39
+ function relative(root, target) {
40
+ return path.relative(root, target).replaceAll(path.sep, "/");
41
+ }
42
+ /** Migration-only source seam: pin one project file to a verified descriptor. */
43
+ function readMigrationSource(root, relativePath, descriptorPathResolver) {
44
+ return { relative: relativePath, ...readProjectFileSnapshot(root, relativePath, descriptorPathResolver) };
45
+ }
46
+ function directoryFiles(root, validatedRoot, relativeRoot, accept, descriptorPathResolver) {
47
+ const absoluteRoot = path.join(root, relativeRoot);
48
+ const inventoryPath = (directory) => `${relative(root, directory).replace(/\/$/, "")}/`;
49
+ const visit = (directory, isRoot = false) => {
50
+ assertValidatedProjectRoot(validatedRoot);
51
+ const directorySnapshot = snapshotMigrationPath(root, relative(root, directory), "directory");
52
+ if (directorySnapshot.kind !== "stable") {
53
+ if (directorySnapshot.kind === "missing")
54
+ return isRoot ? { files: [], changed: null } : { files: [], changed: inventoryPath(directory) };
55
+ if (path.resolve(directorySnapshot.absolute) !== path.resolve(directory))
56
+ return { files: [], changed: inventoryPath(directory) };
57
+ if (directorySnapshot.reason === "symlink")
58
+ throw new Error(`inventory root '${directory}' is a symbolic link; replace it with a real directory inside project '${root}' and retry`);
59
+ if (directorySnapshot.reason === "type")
60
+ throw new Error(`inventory root '${directory}' is not a directory; replace it with a real directory inside project '${root}' and retry`);
61
+ throw new Error(`inventory root '${directory}' cannot be inspected inside project '${root}'; replace it with a readable real directory and retry`);
62
+ }
63
+ let entries;
64
+ try {
65
+ entries = fs.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
66
+ }
67
+ catch {
68
+ throw new Error(`inventory root '${directory}' cannot be read inside project '${root}'; replace it with a readable real directory and retry`);
69
+ }
70
+ if (!migrationPathIsStable(directorySnapshot))
71
+ return { files: [], changed: inventoryPath(directory) };
72
+ const files = [];
73
+ for (const entry of entries) {
74
+ const absolute = path.join(directory, entry.name);
75
+ const candidate = relative(root, absolute);
76
+ if (accept(candidate)) {
77
+ files.push(readMigrationSource(validatedRoot, candidate, descriptorPathResolver));
78
+ }
79
+ else if (entry.isSymbolicLink()) {
80
+ throw new Error(`inventory root '${absolute}' is a symbolic link; replace it with a real directory or file inside project '${root}' and retry`);
81
+ }
82
+ else if (entry.isDirectory()) {
83
+ const nested = visit(absolute);
84
+ if (nested.changed)
85
+ return nested;
86
+ files.push(...nested.files);
87
+ }
88
+ }
89
+ assertValidatedProjectRoot(validatedRoot);
90
+ if (!migrationPathIsStable(directorySnapshot))
91
+ return { files: [], changed: inventoryPath(directory) };
92
+ return { files, changed: null };
93
+ };
94
+ const enumeration = visit(absoluteRoot, true);
95
+ return enumeration.changed ? [{ relative: enumeration.changed, bytes: null, kind: "unsafe" }] : enumeration.files;
96
+ }
97
+ function collectSources(root, validatedRoot, todoPath, docsSnapshot, descriptorPathResolver) {
98
+ const exact = [
99
+ todoPath, "CHANGELOG.md", "DESIGN.md", ".agentera/vision.yaml", ".agentera/docs.yaml", ".agentera/progress.yaml", ".agentera/decisions.yaml", ".agentera/health.yaml", ".agentera/plan.yaml",
100
+ ".agentera/overlays/decisions.yaml", ".agentera/revisions/decisions.yaml", ".agentera/archive/recovery/projection-correlation.yaml",
101
+ ].map((candidate) => candidate === docsSnapshot.relative ? docsSnapshot : readMigrationSource(validatedRoot, candidate, descriptorPathResolver));
102
+ const archives = directoryFiles(root, validatedRoot, ".agentera/archive", (candidate) => /^\.agentera\/archive\/(progress|decisions|health)\/.+/.test(candidate)
103
+ || PLAN_ARCHIVE_SOURCE.test(candidate), descriptorPathResolver);
104
+ const objectives = [".agentera/optimize", ".agentera/optimera"].flatMap((directory) => directoryFiles(root, validatedRoot, directory, (candidate) => /\/(objective|experiments)\.yaml$/.test(candidate), descriptorPathResolver));
105
+ const collected = [...exact, ...archives, ...objectives];
106
+ const missingRoots = [".agentera/archive/", ".agentera/optimize/", ".agentera/optimera/"]
107
+ .filter((directory) => {
108
+ if (collected.some((source) => source.kind === "unsafe" && source.relative === directory))
109
+ return false;
110
+ try {
111
+ return !fs.lstatSync(path.join(root, directory)).isDirectory();
112
+ }
113
+ catch {
114
+ return true;
115
+ }
116
+ })
117
+ .map((directory) => ({ relative: directory, bytes: null, kind: "missing" }));
118
+ const byPath = new Map([...collected, ...missingRoots].map((source) => [source.relative, source]));
119
+ return [...byPath.values()].sort((a, b) => a.relative.localeCompare(b.relative));
120
+ }
121
+ function authorityBinding(sourceRoot) {
122
+ const authorityPath = path.join(sourceRoot, AUTHORITY_PATH);
123
+ const bytes = fs.readFileSync(authorityPath);
124
+ const authority = loadYamlMapping(bytes.toString("utf8"));
125
+ if (typeof authority.schema_version !== "string" || !mapping(authority.entity_migration)) {
126
+ throw new Error("active entity migration authority is missing its schema version or entity_migration contract");
127
+ }
128
+ return { authority_schema_version: authority.schema_version, authority_sha256: hash(bytes) };
129
+ }
130
+ export function entityMigrationAuthorityProjection(sourceRoot = resolveSourceRoot()) {
131
+ const authorityPath = path.join(sourceRoot, AUTHORITY_PATH);
132
+ const authority = loadYamlMapping(fs.readFileSync(authorityPath, "utf8"));
133
+ if (!mapping(authority.entity_migration))
134
+ throw new Error(`state storage authority '${authorityPath}' has no entity migration contract`);
135
+ return { authority: AUTHORITY_PATH, ...authority.entity_migration };
136
+ }
137
+ function sourceFingerprint(files) {
138
+ return hash(canonicalRecordJson(files.map((file) => ({
139
+ path: file.relative,
140
+ presence: file.kind,
141
+ size: file.bytes?.byteLength ?? 0,
142
+ sha256: file.bytes ? hash(file.bytes) : null,
143
+ mode: file.kind === "file" ? file.mode : null,
144
+ dev: file.kind === "file" ? String(file.dev) : null,
145
+ ino: file.kind === "file" ? String(file.ino) : null,
146
+ type: file.kind === "file" ? file.type : null,
147
+ }))));
148
+ }
149
+ function parseYaml(source) {
150
+ if (source.kind !== "file" || !source.bytes)
151
+ throw new Error(source.kind === "unsafe" ? "source path is a symbolic link or non-file" : "source is missing");
152
+ return loadYamlMapping(source.bytes.toString("utf8"));
153
+ }
154
+ function recovery(project, pathName) {
155
+ return `Repair '${pathName}', then run agentera state migrate entities --project '${project.replaceAll("'", "'\\''")}' --dry-run --format json.`;
156
+ }
157
+ function unsafeSourceMessage(pathName) {
158
+ return `source path '${pathName}' is a symbolic link, non-file, or unreadable; replace it with a readable regular file inside the project`;
159
+ }
160
+ function inventoryFailureObservations(files, observations) {
161
+ for (const source of files.filter((candidate) => candidate.kind === "unsafe" && candidate.relative.endsWith("/"))) {
162
+ observations.push({
163
+ key: `migration_inventory:${source.relative}`,
164
+ artifact: "state",
165
+ boundary: "migration_inventory",
166
+ path: source.relative,
167
+ provenance: "source_inventory",
168
+ record: null,
169
+ detail: "corrupt",
170
+ relationships: [],
171
+ message: `inventory directory '${source.relative}' changed while it was enumerated; no candidate names were accepted`,
172
+ });
173
+ }
174
+ }
175
+ function numberedObservations(root, sourceRoot, files, observations) {
176
+ const correlationSource = files.find((source) => source.relative === ".agentera/archive/recovery/projection-correlation.yaml");
177
+ let correlationManifest = null;
178
+ if (correlationSource?.kind === "file") {
179
+ try {
180
+ const manifest = parseYaml(correlationSource);
181
+ const entries = Array.isArray(manifest.entries) ? manifest.entries.map(mapping) : [];
182
+ const validEntries = entries.filter((entry) => Boolean(entry));
183
+ const digest = hash(canonicalRecordJson({ schemaVersion: "agentera.projectionCorrelationSet.v1", entries: validEntries }));
184
+ if (manifest.schemaVersion !== "agentera.projectionCorrelationManifest.v1" || manifest.recovery_set_digest !== digest || validEntries.length !== entries.length)
185
+ throw new Error("projection correlation manifest schema or digest is invalid");
186
+ correlationManifest = new Map(validEntries.map((entry) => [String(entry.identity), entry]));
187
+ }
188
+ catch (error) {
189
+ observations.push({ key: "projection_correlation:manifest", artifact: "state", boundary: "migration_inventory", path: correlationSource.relative, provenance: "source_inventory", record: null, detail: "corrupt", relationships: [], message: error.message });
190
+ }
191
+ }
192
+ else if (correlationSource?.kind === "unsafe") {
193
+ observations.push({ key: "projection_correlation:manifest", artifact: "state", boundary: "migration_inventory", path: correlationSource.relative, provenance: "source_inventory", record: null, detail: "corrupt", relationships: [], message: unsafeSourceMessage(correlationSource.relative) });
194
+ }
195
+ for (const [artifact, declaration] of Object.entries(NUMBERED)) {
196
+ const current = files.find((source) => source.relative === declaration.file);
197
+ if (current?.kind === "file") {
198
+ try {
199
+ const document = parseYaml(current);
200
+ const currentValues = document[declaration.collection];
201
+ if (!Array.isArray(currentValues))
202
+ throw new Error(`field '${declaration.collection}' must be a list`);
203
+ const collections = [[declaration.collection, currentValues]];
204
+ if (Array.isArray(document.archive))
205
+ collections.push(["archive", document.archive]);
206
+ collections.flatMap(([collection, values]) => values.map((value, index) => ({ collection, value, index }))).forEach(({ collection, value, index }) => {
207
+ const record = mapping(value) ?? (typeof value === "string" ? { summary: value } : null);
208
+ const identity = legacyIdentity(record, artifact, "number");
209
+ const number = identity.number;
210
+ const key = number ? `${artifact}:${number}` : `${artifact}:${collection}[${index}]`;
211
+ const valid = record && typeof record.number === "number" ? validateStateRecord(sourceRoot, artifact, record).length === 0 : false;
212
+ const summary = Boolean(record && (typeof record.summary === "string" || record.detail_availability === "summary_only"));
213
+ const text = typeof record?.summary === "string" ? record.summary : "";
214
+ const decisionRefs = artifact === "decisions" ? [...text.matchAll(/\bD([1-9][0-9]*)\b/g)] : [];
215
+ const residue = artifact === "decisions" && decisionRefs.length > 1 && !number;
216
+ observations.push({ key: residue ? `historical_projection_residue:${declaration.file}:${collection}[${index}]` : key, artifact, boundary: residue ? "historical_projection_residue" : declaration.boundary, path: declaration.file, provenance: residue ? "historical_projection_residue" : "current_projection", record, detail: valid ? "full" : summary ? "summary" : "corrupt", relationships: [], message: valid || summary ? undefined : "projection record does not satisfy the declared full-detail schema", ...(residue ? { migrationProvenance: { classification: "historical_projection_residue", path: declaration.file, collection, index, content_sha256: hash(canonicalRecordJson(record)), text, inbound_structured_references: 0 } } : {}) });
217
+ if (artifact === "decisions" && mapping(record?.satisfaction)) {
218
+ observations.push({ key: `decision_satisfaction:${key}`, artifact: "decisions", boundary: "decision_satisfaction", path: declaration.file, provenance: "current_projection", record: mapping(record?.satisfaction), detail: "full", relationships: [{ field: "decision", target: key }] });
219
+ }
220
+ });
221
+ }
222
+ catch (error) {
223
+ observations.push({ key: `${artifact}:projection`, artifact, boundary: declaration.boundary, path: declaration.file, provenance: "current_projection", record: null, detail: "corrupt", relationships: [], message: error.message });
224
+ }
225
+ }
226
+ else if (current?.kind === "unsafe") {
227
+ observations.push({ key: `${artifact}:projection`, artifact, boundary: declaration.boundary, path: declaration.file, provenance: "current_projection", record: null, detail: "corrupt", relationships: [], message: unsafeSourceMessage(declaration.file) });
228
+ }
229
+ for (const source of files.filter((candidate) => candidate.relative.startsWith(`.agentera/archive/${artifact}/`))) {
230
+ const match = new RegExp(`^\\.agentera/archive/${artifact}/([1-9][0-9]*)\\.yaml$`).exec(source.relative);
231
+ if (!match) {
232
+ observations.push({ key: `unsupported:${source.relative}`, artifact, boundary: declaration.boundary, path: source.relative, provenance: "archive", record: null, detail: "corrupt", relationships: [], message: "unsupported archive candidate name" });
233
+ continue;
234
+ }
235
+ const key = `${artifact}:${match[1]}`;
236
+ try {
237
+ const envelope = parseYaml(source);
238
+ const record = mapping(envelope.record);
239
+ if (!record || envelope.artifact_id !== artifact || envelope.entry_number !== Number(match[1]))
240
+ throw new Error("archive envelope identity does not match its path");
241
+ const expected = hash(canonicalRecordJson(record));
242
+ if (envelope.record_sha256 !== expected)
243
+ throw new Error("archive record_sha256 does not match record bytes");
244
+ if (mapping(envelope.recovery_provenance)) {
245
+ const correlation = correlationManifest?.get(key);
246
+ if (!correlation)
247
+ throw new Error("recovered archive is absent from the immutable projection correlation manifest");
248
+ if (correlation.artifact !== artifact || correlation.identity !== key || correlation.archive_sha256 !== hash(source.bytes)
249
+ || mapping(correlation.overlay)?.final_record_sha256 !== expected)
250
+ throw new Error("recovered archive does not match its immutable projection correlation manifest entry");
251
+ }
252
+ const violations = validateStateRecord(sourceRoot, artifact, record);
253
+ if (violations.length > 0)
254
+ throw new Error(`archive record is invalid: ${violations.join("; ")}`);
255
+ observations.push({ key, artifact, boundary: declaration.boundary, path: source.relative, provenance: "verified_archive", record, detail: "full", relationships: [], ...(mapping(envelope.recovery_provenance) ? { migrationProvenance: mapping(envelope.recovery_provenance) } : {}) });
256
+ if (artifact === "decisions" && mapping(record.satisfaction)) {
257
+ observations.push({
258
+ key: `decision_satisfaction:${key}`,
259
+ artifact: "decisions",
260
+ boundary: "decision_satisfaction",
261
+ path: source.relative,
262
+ provenance: "verified_archive",
263
+ record: mapping(record.satisfaction),
264
+ detail: "full",
265
+ relationships: [{ field: "decision", target: key }],
266
+ ...(mapping(envelope.recovery_provenance) ? { migrationProvenance: { kind: "nested_verified_archive_evidence", source: mapping(envelope.recovery_provenance) } } : {}),
267
+ });
268
+ }
269
+ }
270
+ catch (error) {
271
+ observations.push({ key, artifact, boundary: declaration.boundary, path: source.relative, provenance: "archive", record: null, detail: "corrupt", relationships: [], message: error.message });
272
+ }
273
+ }
274
+ }
275
+ }
276
+ function decisionEvidence(sourceRoot, files, observations) {
277
+ const overlay = files.find((source) => source.relative === ".agentera/overlays/decisions.yaml");
278
+ if (overlay?.kind === "file") {
279
+ try {
280
+ const document = parseYaml(overlay);
281
+ for (const [identity, value] of Object.entries(document).sort(([a], [b]) => a.localeCompare(b))) {
282
+ const record = mapping(value);
283
+ const match = /^decisions:([1-9][0-9]*)$/.exec(identity);
284
+ const satisfaction = mapping(record?.satisfaction);
285
+ observations.push({ key: `decision_satisfaction:${identity}`, artifact: "decisions", boundary: "decision_satisfaction", path: overlay.relative, provenance: "overlay", record: satisfaction, detail: match && satisfaction ? "full" : "corrupt", relationships: [{ field: "decision", target: match ? `decisions:${match[1]}` : null }], message: match && satisfaction ? undefined : "decision satisfaction overlay has an unsupported identity or shape" });
286
+ }
287
+ }
288
+ catch (error) {
289
+ observations.push({ key: "decision_satisfaction:overlay", artifact: "decisions", boundary: "decision_satisfaction", path: overlay.relative, provenance: "overlay", record: null, detail: "corrupt", relationships: [{ field: "decision", target: null }], message: error.message });
290
+ }
291
+ }
292
+ else if (overlay?.kind === "unsafe") {
293
+ observations.push({ key: "decision_satisfaction:overlay", artifact: "decisions", boundary: "decision_satisfaction", path: overlay.relative, provenance: "overlay", record: null, detail: "corrupt", relationships: [], message: unsafeSourceMessage(overlay.relative) });
294
+ }
295
+ const revisions = files.find((source) => source.relative === ".agentera/revisions/decisions.yaml");
296
+ if (revisions?.kind === "file") {
297
+ try {
298
+ const document = parseYaml(revisions);
299
+ const contract = decisionRevisionContract(sourceRoot);
300
+ for (const [identity, value] of Object.entries(document).sort(([left], [right]) => left.localeCompare(right))) {
301
+ const target = new RegExp(`^${contract.identityPrefix}:[1-9][0-9]*$`).test(identity) ? identity : null;
302
+ if (!Array.isArray(value)) {
303
+ observations.push({ key: `decision_revision:${identity}`, artifact: "decisions", boundary: "decision_revision", path: revisions.relative, provenance: "revision", record: null, detail: "corrupt", relationships: [{ field: "decision", target }], message: `${identity} must be an ordered list of revision records` });
304
+ continue;
305
+ }
306
+ value.forEach((candidate, index) => {
307
+ const record = mapping(candidate);
308
+ const violations = decisionRevisionViolations({ [identity]: [candidate] }, contract);
309
+ observations.push({ key: `decision_revision:${identity}:${index}`, artifact: "decisions", boundary: "decision_revision", path: revisions.relative, provenance: "revision", record, detail: record && violations.length === 0 ? "full" : "corrupt", relationships: [{ field: "decision", target }], message: violations.length === 0 ? undefined : violations.join("; ") });
310
+ });
311
+ }
312
+ }
313
+ catch (error) {
314
+ observations.push({ key: "decision_revision:document", artifact: "decisions", boundary: "decision_revision", path: revisions.relative, provenance: "revision", record: null, detail: "corrupt", relationships: [{ field: "decision", target: null }], message: error.message });
315
+ }
316
+ }
317
+ else if (revisions?.kind === "unsafe") {
318
+ observations.push({ key: "decision_revision:document", artifact: "decisions", boundary: "decision_revision", path: revisions.relative, provenance: "revision", record: null, detail: "corrupt", relationships: [], message: unsafeSourceMessage(revisions.relative) });
319
+ }
320
+ }
321
+ function planObservations(root, files, observations) {
322
+ const active = path.join(root, ".agentera", "plan.yaml");
323
+ const activeSource = files.find((source) => source.relative === ".agentera/plan.yaml");
324
+ const archiveBytes = new Map(files.flatMap((source) => source.kind === "file" && PLAN_ARCHIVE_SOURCE.test(source.relative)
325
+ ? [[path.join(root, source.relative), source.bytes]]
326
+ : []));
327
+ const discovery = discoverPlanArtifacts(active, {
328
+ activeBytes: activeSource?.kind === "file" ? activeSource.bytes : null,
329
+ archiveBytes,
330
+ });
331
+ const seen = new Set();
332
+ if (activeSource?.kind === "unsafe") {
333
+ observations.push({ key: "plan:document", artifact: "plan", boundary: "plan", path: activeSource.relative, provenance: "current_canonical", record: null, detail: "corrupt", relationships: [], message: unsafeSourceMessage(activeSource.relative) });
334
+ }
335
+ for (const identity of discovery.identities) {
336
+ const marker = `${identity.stableId}\0${identity.artifact.path}`;
337
+ if (seen.has(marker))
338
+ continue;
339
+ seen.add(marker);
340
+ const sourcePath = relative(root, identity.artifact.path);
341
+ const archived = identity.artifact.path !== active;
342
+ const source = files.find((candidate) => candidate.relative === sourcePath);
343
+ let sourceStatus = String((mapping(identity.artifact.data.header) ?? {}).status ?? "");
344
+ if (source?.kind === "file") {
345
+ const sourceDocument = parseYaml(source);
346
+ sourceStatus = String((mapping(sourceDocument.header) ?? {}).status ?? "");
347
+ }
348
+ const planRecord = structuredClone(identity.artifact.data);
349
+ const migratedHeader = mapping(planRecord.header);
350
+ if (archived && migratedHeader)
351
+ migratedHeader.status = "archived";
352
+ const migrationProvenance = archived ? {
353
+ ...identity.artifact.migrationProvenance,
354
+ source_path: sourcePath,
355
+ source_lifecycle: "verified_plan_archive",
356
+ source_status: sourceStatus,
357
+ target_status: "archived",
358
+ } : identity.artifact.migrationProvenance ? { ...identity.artifact.migrationProvenance, source_path: sourcePath } : undefined;
359
+ const classificationDetail = identity.ambiguous ? "corrupt" : "full";
360
+ observations.push({ key: identity.stableId, artifact: "plan", boundary: "plan", path: sourcePath, provenance: archived ? "verified_plan_archive" : "current_canonical", record: planRecord, detail: classificationDetail, relationships: [], message: identity.ambiguous ? "plan identity resolves to conflicting documents" : undefined, ...(migrationProvenance ? { migrationProvenance } : {}) });
361
+ const parts = planDocumentParts(identity.artifact.data);
362
+ parts.tasks.forEach((task, index) => {
363
+ const number = task.number;
364
+ const taskKey = typeof number === "number" && Number.isSafeInteger(number) && number > 0 ? `${identity.stableId}/task:${number}` : `${identity.stableId}/task:index:${index}`;
365
+ const depends = Array.isArray(task.depends_on) ? task.depends_on : [];
366
+ observations.push({
367
+ key: taskKey,
368
+ artifact: "plan",
369
+ boundary: "plan_task",
370
+ path: sourcePath,
371
+ provenance: identity.artifact.path === active ? "current_canonical" : "verified_plan_archive",
372
+ record: task,
373
+ detail: typeof number === "number" ? classificationDetail : "corrupt",
374
+ relationships: [
375
+ { field: "plan", target: identity.stableId },
376
+ ...depends.map((dependency) => { const normalized = /^Task ([1-9][0-9]*)$/.exec(String(dependency))?.[1] ?? String(dependency); return { field: "depends_on", target: /^[1-9][0-9]*$/.test(normalized) ? `${identity.stableId}/task:${normalized}` : null }; }),
377
+ ],
378
+ message: typeof number === "number" ? undefined : "plan task has no positive structured number",
379
+ ...(identity.artifact.migrationProvenance ? { migrationProvenance: { ...identity.artifact.migrationProvenance, source_path: sourcePath, source_task_index: index } } : {}),
380
+ });
381
+ });
382
+ }
383
+ for (const diagnostic of discovery.diagnostics.filter((item) => !seen.has(`${item.path}\0diagnostic`))) {
384
+ if (discovery.identities.some((identity) => identity.artifact.path === diagnostic.path))
385
+ continue;
386
+ const sourcePath = relative(root, diagnostic.path);
387
+ observations.push({ key: `plan:corrupt:${sourcePath}`, artifact: "plan", boundary: "plan", path: sourcePath, provenance: "plan_candidate", record: null, detail: "corrupt", relationships: [], message: diagnostic.message });
388
+ }
389
+ for (const source of files.filter((candidate) => PLAN_ARCHIVE_SOURCE.test(candidate.relative))) {
390
+ if (!discovery.identities.some((identity) => relative(root, identity.artifact.path) === source.relative) && !observations.some((entry) => entry.path === source.relative)) {
391
+ observations.push({ key: `plan:corrupt:${source.relative}`, artifact: "plan", boundary: "plan", path: source.relative, provenance: "plan_candidate", record: null, detail: "corrupt", relationships: [], message: source.kind === "unsafe" ? unsafeSourceMessage(source.relative) : "plan archive could not be validated" });
392
+ }
393
+ }
394
+ }
395
+ function objectiveObservations(root, files, observations) {
396
+ const sourceBytes = new Map(files.flatMap((source) => source.kind === "file" && /^\.agentera\/(optimize|optimera)\/.+\/(objective|experiments)\.yaml$/.test(source.relative)
397
+ ? [[path.join(root, source.relative), source.bytes]]
398
+ : []));
399
+ const discovery = discoverObjectiveArtifacts(root, sourceBytes);
400
+ for (const objective of discovery.objectives) {
401
+ for (const objectivePath of objective.paths) {
402
+ const sourcePath = relative(root, objectivePath);
403
+ const source = files.find((candidate) => candidate.relative === sourcePath);
404
+ let document = null;
405
+ try {
406
+ if (source?.kind === "file")
407
+ document = parseYaml(source);
408
+ }
409
+ catch { /* discovery diagnostic accounts for it */ }
410
+ observations.push({ key: objective.stableId, artifact: "objective", boundary: "objective", path: sourcePath, provenance: sourcePath.startsWith(".agentera/optimize/") ? "current_canonical" : "legacy_objective_root", record: document, detail: objective.ambiguous || !document ? "corrupt" : "full", relationships: [], message: objective.ambiguous ? "objective identity is ambiguous" : undefined });
411
+ }
412
+ if (objective.ambiguous)
413
+ continue;
414
+ const objectivePath = objective.paths[0];
415
+ const experimentsRelative = relative(root, path.join(path.dirname(objectivePath), "experiments.yaml"));
416
+ const experiments = files.find((source) => source.relative === experimentsRelative);
417
+ if (experiments?.kind === "file") {
418
+ try {
419
+ const document = parseYaml(experiments);
420
+ for (const experiment of inspectExperimentIdentities(objective.stableId, document).entries) {
421
+ const key = experiment.stableId ?? `${objective.stableId}/experiment:${experiment.provenance.collection}[${experiment.provenance.index}]`;
422
+ observations.push({ key, artifact: "experiments", boundary: "experiment", path: experiments.relative, provenance: "experiment_projection", record: experiment.data, detail: experiment.addressable ? "full" : experiment.compatibility === "legacy_missing_identity" ? "summary" : "corrupt", relationships: [{ field: "objective", target: objective.stableId }], message: experiment.caveats.join("; ") || undefined });
423
+ }
424
+ }
425
+ catch (error) {
426
+ observations.push({ key: `${objective.stableId}/experiments`, artifact: "experiments", boundary: "experiment", path: experiments.relative, provenance: "experiment_projection", record: null, detail: "corrupt", relationships: [{ field: "objective", target: objective.stableId }], message: error.message });
427
+ }
428
+ }
429
+ else if (experiments?.kind === "unsafe") {
430
+ observations.push({ key: `${objective.stableId}/experiments`, artifact: "experiments", boundary: "experiment", path: experiments.relative, provenance: "experiment_projection", record: null, detail: "corrupt", relationships: [{ field: "objective", target: objective.stableId }], message: unsafeSourceMessage(experiments.relative) });
431
+ }
432
+ }
433
+ for (const diagnostic of discovery.diagnostics) {
434
+ for (const candidate of diagnostic.candidate_paths) {
435
+ if (observations.some((entry) => entry.path === relative(root, candidate)))
436
+ continue;
437
+ observations.push({ key: `objective:corrupt:${relative(root, candidate)}`, artifact: "objective", boundary: "objective", path: relative(root, candidate), provenance: "objective_candidate", record: null, detail: "corrupt", relationships: [], message: diagnostic.message });
438
+ }
439
+ }
440
+ for (const source of files.filter((candidate) => candidate.kind === "unsafe" && /\/(objective|experiments)\.yaml$/.test(candidate.relative))) {
441
+ if (observations.some((entry) => entry.path === source.relative))
442
+ continue;
443
+ const artifact = source.relative.endsWith("/objective.yaml") ? "objective" : "experiments";
444
+ observations.push({ key: `${artifact}:corrupt:${source.relative}`, artifact, boundary: artifact === "objective" ? "objective" : "experiment", path: source.relative, provenance: artifact === "objective" ? "objective_candidate" : "experiment_projection", record: null, detail: "corrupt", relationships: [], message: unsafeSourceMessage(source.relative) });
445
+ }
446
+ }
447
+ function todoAndDocs(root, todoPath, files, observations) {
448
+ const todo = files.find((source) => source.relative === todoPath);
449
+ if (todo?.kind === "file" && todo.bytes) {
450
+ let section = "normal";
451
+ todo.bytes.toString("utf8").split(/\r?\n/).forEach((line, index) => {
452
+ const heading = line.trim().match(/^##\s+(.+)$/)?.[1]?.toLowerCase();
453
+ if (heading)
454
+ section = heading.includes("critical") ? "critical" : heading.includes("degraded") ? "degraded" : heading.includes("annoying") ? "annoying" : heading.includes("resolved") ? "resolved" : heading.includes("normal") ? "normal" : section;
455
+ const item = parseTodoMarkdownListItem(line.trim());
456
+ if (!item)
457
+ return;
458
+ const severity = section === "resolved" ? "normal" : section;
459
+ const record = { severity, status: item.status, description: item.description };
460
+ const violations = todoDocsRecordViolations("todo_item", record);
461
+ observations.push({ key: `${todoPath}:line:${index + 1}`, artifact: "todo", boundary: "todo_item", path: todoPath, provenance: "current_canonical", record, detail: violations.length ? "corrupt" : "full", relationships: [], message: violations.length ? violations.join("; ") : undefined });
462
+ });
463
+ }
464
+ else if (todo?.kind === "unsafe") {
465
+ observations.push({ key: "todo:document", artifact: "todo", boundary: "todo_item", path: todoPath, provenance: "current_canonical", record: null, detail: "corrupt", relationships: [], message: unsafeSourceMessage(todoPath) });
466
+ }
467
+ const docs = files.find((source) => source.relative === ".agentera/docs.yaml");
468
+ if (docs?.kind === "file") {
469
+ try {
470
+ const document = parseYaml(docs);
471
+ if (document.index !== undefined && !Array.isArray(document.index))
472
+ throw new Error("docs index must be a list");
473
+ (Array.isArray(document.index) ? document.index : []).forEach((value, index) => {
474
+ const sourceRecord = mapping(value);
475
+ const originalStatus = sourceRecord?.status;
476
+ const normalizedStatus = originalStatus === "draft" ? "intent" : originalStatus === "archived" ? "stale" : originalStatus;
477
+ const record = sourceRecord ? { ...structuredClone(sourceRecord), ...(normalizedStatus !== originalStatus ? { status: normalizedStatus } : {}) } : null;
478
+ const identity = typeof record?.path === "string" ? `docs:path:${record.path}` : `docs:index:${index}`;
479
+ const violations = record ? todoDocsRecordViolations("documentation_inventory_entry", record) : ["documentation inventory entry must be a mapping"];
480
+ observations.push({ key: identity, artifact: "docs", boundary: "documentation_inventory_entry", path: docs.relative, provenance: "current_canonical", record, detail: violations.length ? "corrupt" : "full", relationships: [], message: violations.length ? violations.join("; ") : undefined, ...(normalizedStatus !== originalStatus ? { migrationProvenance: { kind: "legacy_docs_status_normalization", source_path: docs.relative, index, ...(sourceRecord?.path !== undefined ? { original_path: sourceRecord.path } : {}), ...(originalStatus !== undefined ? { original_status: originalStatus } : {}), ...(normalizedStatus !== undefined ? { normalized_status: normalizedStatus } : {}) } } : {}) });
481
+ });
482
+ }
483
+ catch (error) {
484
+ observations.push({ key: "docs:document", artifact: "docs", boundary: "documentation_inventory_entry", path: docs.relative, provenance: "current_canonical", record: null, detail: "corrupt", relationships: [], message: error.message });
485
+ }
486
+ }
487
+ else if (docs?.kind === "unsafe") {
488
+ observations.push({ key: "docs:document", artifact: "docs", boundary: "documentation_inventory_entry", path: docs.relative, provenance: "current_canonical", record: null, detail: "corrupt", relationships: [], message: unsafeSourceMessage(docs.relative) });
489
+ }
490
+ }
491
+ function preservedSingletons(files) {
492
+ const declarations = [
493
+ { boundary: "vision", source_path: ".agentera/vision.yaml" },
494
+ { boundary: "design", source_path: "DESIGN.md" },
495
+ { boundary: "changelog", source_path: "CHANGELOG.md" },
496
+ { boundary: "docs_mapping", source_path: ".agentera/docs.yaml", preserved_sections: ["last_audit", "conventions", "mapping", "coverage", "audit_log"] },
497
+ { boundary: "profile", source_path: "$AGENTERA_PROFILE_DIR/PROFILE.md" },
498
+ { boundary: "runtime_local_session_state", source_path: "runtime-local" },
499
+ ];
500
+ return declarations.map((declaration) => {
501
+ const source = files.find((candidate) => candidate.relative === declaration.source_path);
502
+ return { ...declaration, presence: source?.kind ?? "missing", content_sha256: source?.bytes ? hash(source.bytes) : null };
503
+ });
504
+ }
505
+ function previewId(fingerprint, key) {
506
+ const bytes = createHash("sha256").update(`agentera.entity-preview.v1\0${fingerprint}\0${key}`, "utf8").digest();
507
+ return Array.from(bytes.subarray(0, 10), (byte) => String.fromCharCode(97 + byte % 26)).join("");
508
+ }
509
+ function classify(group) {
510
+ if (group.some((item) => item.provenance === "historical_projection_residue"))
511
+ return "historical_projection_residue";
512
+ if (group.some((item) => item.key.startsWith("unsupported:")))
513
+ return "unsupported";
514
+ if (group.some((item) => item.detail === "corrupt"))
515
+ return "corrupt";
516
+ if (group.every((item) => item.detail === "summary"))
517
+ return "irrecoverable_summary_only";
518
+ const full = group.filter((item) => item.detail === "full" && item.record);
519
+ const bodies = new Set(full.map((item) => canonicalRecordJson(item.record)));
520
+ if (bodies.size > 1)
521
+ return "duplicate";
522
+ const projections = full.filter((item) => item.provenance === "current_projection");
523
+ const archive = group.find((item) => item.provenance === "verified_archive" && item.record);
524
+ const summaries = group.filter((item) => item.detail === "summary" && item.record);
525
+ if (archive && summaries.length) {
526
+ const recovery = archive.migrationProvenance;
527
+ const recorded = mapping(recovery?.current_projection)?.record_sha256;
528
+ const proven = summaries.every((item) => exactProjectionMirror(item.artifact, archive.record, item.record, recorded));
529
+ return proven ? "verified_full" : "conflict";
530
+ }
531
+ if (archive || group.some((item) => item.provenance === "verified_plan_archive"))
532
+ return "verified_full";
533
+ if (group.every((item) => item.provenance === "current_projection" || item.provenance === "experiment_projection"))
534
+ return "recoverable_degraded_full_projection";
535
+ return "verified_full";
536
+ }
537
+ function exactProjectionMirror(artifact, full, summary, recorded) {
538
+ if (recorded === hash(canonicalRecordJson(summary)))
539
+ return true;
540
+ if (artifact === "health" && typeof summary.summary === "string" && typeof full.number === "number" && typeof full.date === "string" && typeof full.trajectory === "string") {
541
+ return summary.summary === `Audit ${full.number} · ${full.date} (${truncateWords(full.trajectory, 10)})`;
542
+ }
543
+ if (artifact !== "progress" && artifact !== "decisions" && artifact !== "health")
544
+ return false;
545
+ return canonicalRecordJson(yamlArchiveEntry(artifact, full)) === canonicalRecordJson(summary);
546
+ }
547
+ function canonicalMigrationRecord(boundary, source, relationships) {
548
+ const record = structuredClone(source);
549
+ for (const field of ["id", "artifact", "number", "stable_id", "artifact_id", "entry_number", "task_number", "plan_id", "objective_id", "experiment_number"])
550
+ delete record[field];
551
+ if (boundary === "decision")
552
+ delete record.satisfaction;
553
+ if (boundary === "plan" || boundary === "objective") {
554
+ const header = record.header;
555
+ if (mapping(header))
556
+ delete header.id;
557
+ }
558
+ if (boundary === "plan") {
559
+ delete record.tasks;
560
+ delete record.previous_plan_archived;
561
+ const header = record.header;
562
+ if (mapping(header)) {
563
+ const planHeader = header;
564
+ if (planHeader.status === "active")
565
+ planHeader.status = "open";
566
+ if (planHeader.status === "completed")
567
+ planHeader.status = "complete";
568
+ }
569
+ }
570
+ for (const relationship of relationships) {
571
+ if (relationship.field === "depends_on") {
572
+ record.depends_on = relationships.filter(({ field }) => field === "depends_on").map(({ target_id }) => target_id);
573
+ }
574
+ else {
575
+ record[relationship.field] = relationship.target_id;
576
+ }
577
+ }
578
+ return record;
579
+ }
580
+ function buildEntries(project, fingerprint, observations) {
581
+ const groups = new Map();
582
+ for (const observation of observations)
583
+ groups.set(observation.key, [...(groups.get(observation.key) ?? []), observation]);
584
+ const ids = new Map();
585
+ for (const key of [...groups.keys()].sort())
586
+ ids.set(key, previewId(fingerprint, key));
587
+ const collisions = new Set();
588
+ const idOwners = new Map();
589
+ for (const [key, id] of ids) {
590
+ const owner = idOwners.get(id);
591
+ if (owner) {
592
+ collisions.add(owner);
593
+ collisions.add(key);
594
+ }
595
+ else
596
+ idOwners.set(id, key);
597
+ }
598
+ const entries = [...groups.entries()].map(([key, group]) => {
599
+ let classification = classify(group);
600
+ if (collisions.has(key))
601
+ classification = "conflict";
602
+ const primary = group[0];
603
+ const content = group.find((item) => item.provenance === "verified_archive" && item.record)?.record ?? group.find((item) => item.record)?.record ?? null;
604
+ const contentSha256s = [...new Set(group.flatMap((item) => item.record ? [hash(canonicalRecordJson(item.record))] : []))].sort();
605
+ const mirrored = contentSha256s.length === 1 && group.filter((item) => item.record).length > 1;
606
+ const id = ids.get(key);
607
+ const relationshipSources = new Map(group.flatMap((item) => item.relationships).map((relationship) => [`${relationship.field}\0${relationship.target ?? ""}`, relationship])).values();
608
+ const relationships = [...relationshipSources].map((relationship) => ({
609
+ field: relationship.field,
610
+ target_source_identity: relationship.target,
611
+ target_id: relationship.target ? ids.get(relationship.target) ?? null : null,
612
+ status: relationship.target && groups.has(relationship.target) ? "resolved" : "unresolved",
613
+ }));
614
+ const record = content ? canonicalMigrationRecord(primary.boundary, content, relationships) : {};
615
+ const residue = classification === "historical_projection_residue";
616
+ const blocked = BLOCKING.has(classification) || relationships.some((relationship) => relationship.status === "unresolved");
617
+ return {
618
+ source_identity: key,
619
+ source_paths: [...new Set(group.map((item) => item.path))].sort(),
620
+ artifact: primary.artifact,
621
+ boundary: primary.boundary,
622
+ classification,
623
+ detail_availability: classification === "irrecoverable_summary_only" ? "summary_only" : content ? "full" : "unavailable",
624
+ provenance: [...new Set([...group.map((item) => item.provenance), ...(mirrored ? ["mirrored"] : [])])].sort(),
625
+ content_sha256: contentSha256s.length === 1 ? contentSha256s[0] : null,
626
+ content_sha256s: contentSha256s,
627
+ physical_record_count: group.length,
628
+ proposed_target: blocked || residue ? null : { id, path: `.agentera/entities/${primary.artifact}/${primary.boundary}/${id}.yaml` },
629
+ target_sha256: null,
630
+ relationships,
631
+ record,
632
+ recovery: blocked ? recovery(project, primary.path) : "none",
633
+ ...(group.some((item) => item.migrationProvenance) ? { migration_provenance: group.flatMap((item) => item.migrationProvenance ? [item.migrationProvenance] : []) } : {}),
634
+ };
635
+ }).sort((a, b) => `${a.artifact}\0${a.boundary}\0${a.source_identity}\0${a.source_paths[0]}`.localeCompare(`${b.artifact}\0${b.boundary}\0${b.source_identity}\0${b.source_paths[0]}`));
636
+ const decisions = new Map(entries.filter(({ boundary, proposed_target }) => boundary === "decision" && proposed_target).map((entry) => [entry.proposed_target.id, entry.record]));
637
+ const groupedRevisions = new Map();
638
+ for (const entry of entries.filter(({ boundary }) => boundary === "decision_revision")) {
639
+ const decision = entry.relationships.find(({ field }) => field === "decision")?.target_id ?? "";
640
+ groupedRevisions.set(decision, [...(groupedRevisions.get(decision) ?? []), entry]);
641
+ }
642
+ for (const [decision, revisions] of groupedRevisions) {
643
+ let effective = structuredClone(decisions.get(decision) ?? {});
644
+ for (const entry of revisions.sort((left, right) => left.source_identity.localeCompare(right.source_identity, undefined, { numeric: true }))) {
645
+ const source = entry.record;
646
+ const changes = mapping(source.changes) ? structuredClone(source.changes) : Object.fromEntries(Object.entries(source).filter(([field]) => !["decision", "date", "provenance", "base_sha256"].includes(field)));
647
+ entry.record = { decision, date: source.date, provenance: source.provenance, base_sha256: hash(canonicalRecordJson(effective)), changes };
648
+ effective = { ...effective, ...structuredClone(changes) };
649
+ }
650
+ }
651
+ return entries;
652
+ }
653
+ export function validateEntityMigrationTargets(project, entries, sourceRoot) {
654
+ const targets = entries.flatMap((entry) => entry.proposed_target && entry.artifact && entry.boundary ? [{ sourceIdentity: entry.source_identity, path: entry.proposed_target.path, id: entry.proposed_target.id, artifact: entry.artifact, boundary: entry.boundary, record: entry.record }] : []);
655
+ const diagnostics = validateCanonicalEntityTargets(project, targets, sourceRoot);
656
+ for (const entry of entries) {
657
+ if (!entry.proposed_target || !entry.artifact)
658
+ continue;
659
+ const actual = hash(canonicalEntityEnvelopeBytes({ id: entry.proposed_target.id, artifact: entry.artifact, record: entry.record }));
660
+ if (entry.target_sha256 !== actual)
661
+ diagnostics.push({ sourceIdentity: entry.source_identity, path: entry.proposed_target.path, message: `canonical target bytes have SHA-256 '${actual}', not manifest binding '${entry.target_sha256 ?? "missing"}'` });
662
+ }
663
+ return diagnostics;
664
+ }
665
+ function migrationInventory(projectRoot, sourceRoot, resolveDescriptorPath) {
666
+ const project = path.resolve(projectRoot);
667
+ const validatedProject = validateRealProjectRoot(project);
668
+ const authority = authorityBinding(sourceRoot);
669
+ const todoRecord = loadArtifactRecord("todo");
670
+ if (!todoRecord)
671
+ throw new Error("artifact registry is missing the canonical 'todo' record");
672
+ const docsSnapshot = { relative: ".agentera/docs.yaml", ...readProjectFileSnapshot(validatedProject, ".agentera/docs.yaml", resolveDescriptorPath) };
673
+ const overrides = docsSnapshot.kind === "file" ? docsPathOverridesFromBytes(docsSnapshot.bytes) : {};
674
+ const todoAbsolute = todoRecord.displayName in overrides ? resolveArtifactPath(todoRecord, project, { docsPathOverrides: overrides }) : path.join(project, todoRecord.defaultPath);
675
+ const todoPath = relative(project, todoAbsolute);
676
+ if (todoPath !== todoRecord.defaultPath)
677
+ assertRealpathBoundary(project, todoAbsolute, todoRecord.artifactId);
678
+ const files = collectSources(project, validatedProject, todoPath, docsSnapshot, resolveDescriptorPath);
679
+ const fingerprint = sourceFingerprint(files);
680
+ const observations = [];
681
+ inventoryFailureObservations(files, observations);
682
+ numberedObservations(project, sourceRoot, files, observations);
683
+ decisionEvidence(sourceRoot, files, observations);
684
+ planObservations(project, files, observations);
685
+ objectiveObservations(project, files, observations);
686
+ todoAndDocs(project, todoPath, files, observations);
687
+ const preserved = preservedSingletons(files);
688
+ const completeInventory = buildEntries(project, fingerprint, observations);
689
+ const preservedResidues = completeInventory.filter((entry) => entry.classification === "historical_projection_residue");
690
+ const completeEntries = completeInventory.filter((entry) => entry.classification !== "historical_projection_residue");
691
+ for (const entry of completeEntries) {
692
+ if (entry.proposed_target && entry.artifact)
693
+ entry.target_sha256 = hash(canonicalEntityEnvelopeBytes({ id: entry.proposed_target.id, artifact: entry.artifact, record: entry.record }));
694
+ }
695
+ const targetDiagnostics = validateEntityMigrationTargets(project, completeEntries, sourceRoot);
696
+ for (const diagnostic of targetDiagnostics) {
697
+ const entry = completeEntries.find((candidate) => candidate.source_identity === diagnostic.sourceIdentity);
698
+ if (!entry)
699
+ continue;
700
+ entry.classification = "corrupt";
701
+ entry.proposed_target = null;
702
+ entry.target_sha256 = null;
703
+ entry.recovery = recovery(project, entry.source_paths[0]);
704
+ }
705
+ const classes = ["verified_full", "recoverable_degraded_full_projection", "irrecoverable_summary_only", "duplicate", "conflict", "corrupt", "unsupported", "historical_projection_residue"];
706
+ const counts = Object.fromEntries(classes.map((classification) => [classification, completeInventory.filter((entry) => entry.classification === classification).length]));
707
+ counts.total = completeInventory.length;
708
+ counts.publishable_entities = completeEntries.filter(({ proposed_target }) => proposed_target !== null).length;
709
+ counts.logical_identities = completeInventory.length;
710
+ counts.physical_records = observations.length;
711
+ counts.mirrors = completeInventory.reduce((total, entry) => total + (entry.provenance.includes("mirrored") ? entry.physical_record_count - 1 : 0), 0);
712
+ counts.duplicates = completeInventory.reduce((total, entry) => total + (entry.classification === "duplicate" ? entry.physical_record_count - 1 : 0), 0);
713
+ counts.conflicts = counts.duplicate + counts.conflict;
714
+ counts.relationships = completeEntries.reduce((total, entry) => total + entry.relationships.length, 0);
715
+ counts.unresolved_relationships = completeEntries.reduce((total, entry) => total + entry.relationships.filter((relationship) => relationship.status === "unresolved").length, 0);
716
+ counts.blockers = completeEntries.filter((entry) => BLOCKING.has(entry.classification) || entry.relationships.some((relationship) => relationship.status === "unresolved")).length;
717
+ const diagnostics = completeEntries.flatMap((entry) => [
718
+ ...(BLOCKING.has(entry.classification) ? [{ classification: entry.classification, path: entry.source_paths[0], source_identity: entry.source_identity, message: observations.find((item) => item.key === entry.source_identity)?.message ?? `${entry.classification} source requires explicit recovery`, recovery: entry.recovery }] : []),
719
+ ...entry.relationships.filter((relationship) => relationship.status === "unresolved").map((relationship) => {
720
+ const target = relationship.target_source_identity ?? "missing structured reference";
721
+ return { classification: "unresolved_relationship", path: entry.source_paths[0], source_identity: entry.source_identity, relationship_field: relationship.field, target_source_identity: relationship.target_source_identity, message: `source '${entry.source_identity}' relationship '${relationship.field}' references unresolved target '${target}'`, recovery: `Repair relationship '${relationship.field}' on '${entry.source_identity}' to reference an inventoried source identity instead of '${target}', then run agentera state migrate entities --project '${project.replaceAll("'", "'\\''")}' --dry-run --format json.` };
722
+ }),
723
+ ...targetDiagnostics.filter(({ sourceIdentity }) => sourceIdentity === entry.source_identity).map((diagnostic) => ({ classification: "corrupt", path: entry.source_paths[0], source_identity: entry.source_identity, message: `target_invalid: ${diagnostic.message}`, recovery: entry.recovery })),
724
+ ]);
725
+ const digestEntries = completeEntries.map(({ record: _record, ...entry }) => entry);
726
+ const digestResidues = preservedResidues.map(({ record: _record, ...entry }) => entry);
727
+ const digestBody = { source_fingerprint: fingerprint, authority, selectors: { project, filter: INVENTORY_FILTER, order: INVENTORY_ORDER }, entries: digestEntries, preserved_residues: digestResidues, preserved_singletons: preserved, counts, diagnostics };
728
+ const previewDigest = hash(canonicalRecordJson(digestBody));
729
+ const sources = files.map((file) => {
730
+ return { path: file.relative, presence: file.kind === "file" ? "file" : "missing", size: file.bytes?.byteLength ?? 0, sha256: file.bytes ? hash(file.bytes) : null, mode: file.kind === "file" ? file.mode : null, dev: file.kind === "file" ? String(file.dev) : null, ino: file.kind === "file" ? String(file.ino) : null, type: file.kind === "file" ? file.type : null, bytes_base64: file.bytes?.toString("base64") ?? null };
731
+ });
732
+ return { project, source_fingerprint: fingerprint, preview_digest: previewDigest, ...authority, preserved_singletons: preserved, entries: completeEntries, preserved_residues: preservedResidues, sources, counts, diagnostics };
733
+ }
734
+ export function planEntityMigration(projectRoot, sourceRoot, options = {}) {
735
+ return migrationInventory(projectRoot, sourceRoot, options.resolveDescriptorPath ?? resolveProjectDescriptorPath);
736
+ }
737
+ export function previewEntityMigration(projectRoot, sourceRoot, options = {}) {
738
+ const inventory = migrationInventory(projectRoot, sourceRoot, options.resolveDescriptorPath ?? resolveProjectDescriptorPath);
739
+ const { project, source_fingerprint: fingerprint, preview_digest: previewDigest, preserved_singletons: preserved, counts, diagnostics } = inventory;
740
+ const authority = { authority_schema_version: inventory.authority_schema_version, authority_sha256: inventory.authority_sha256 };
741
+ const completeEntries = inventory.entries.map(({ record: _record, ...entry }) => entry);
742
+ const preservedResidues = inventory.preserved_residues.map(({ record: _record, ...entry }) => entry);
743
+ const requestedLimit = Math.max(1, Math.min(options.limit ?? DEFAULT_LIMIT, MAX_LIMIT));
744
+ const restartCommand = `agentera state migrate entities --project '${project.replaceAll("'", "'\\''")}' --limit ${requestedLimit} --dry-run --format json`;
745
+ if (options.after !== undefined && (options.sourceFingerprint !== fingerprint || options.previewDigest !== previewDigest)) {
746
+ throw new EntityMigrationContinuationError(restartCommand);
747
+ }
748
+ const start = options.after === undefined ? 0 : completeEntries.findIndex((entry) => entry.source_identity === options.after) + 1;
749
+ if (options.after !== undefined && start === 0)
750
+ throw new Error(`pagination cursor '${options.after}' is not a source identity in the bound inventory; restart without --after`);
751
+ let entries = completeEntries.slice(start, start + requestedLimit);
752
+ const diagnosticsForEntries = () => {
753
+ const identities = new Set(entries.map((entry) => entry.source_identity));
754
+ return diagnostics.filter((diagnostic) => identities.has(diagnostic.source_identity));
755
+ };
756
+ let omissionReason = start > 0 || start + entries.length < completeEntries.length ? "result_limit" : null;
757
+ const quotedProject = project.replaceAll("'", "'\\''");
758
+ const base = { schemaVersion: "agentera.entityMigrationPreview.v1", command: "state migrate entities", status: counts.blockers > 0 ? "blocked" : "ready", mode: "preview", project, read_only: true, mutation_intent: false, mutation_performed: false, source_fingerprint: fingerprint, preview_digest: previewDigest, preserved_singletons: preserved, preserved_residues: preservedResidues, counts, source_contract: { authority: AUTHORITY_PATH, ...authority, zero_write: true, scalar_truncation: "forbidden", apply_owner: "full development-channel upgrade --yes" } };
759
+ const serializedBytes = () => {
760
+ const pageDiagnostics = diagnosticsForEntries();
761
+ const nextAfter = start + entries.length < completeEntries.length ? entries.at(-1)?.source_identity ?? null : null;
762
+ const command = nextAfter ? `agentera state migrate entities --project '${quotedProject}' --after '${nextAfter.replaceAll("'", "'\\''")}' --source-fingerprint ${fingerprint} --preview-digest ${previewDigest} --limit ${requestedLimit} --dry-run --format json` : restartCommand;
763
+ const body = { ...base, entries, diagnostics: pageDiagnostics, omitted: start > 0 || entries.length < completeEntries.length, omitted_count: completeEntries.length - entries.length, diagnostics_omitted_count: diagnostics.length - pageDiagnostics.length, omission_reason: omissionReason, page_after: options.after ?? null, next_after: nextAfter, retrieval: { command } };
764
+ return Math.max(Buffer.byteLength(JSON.stringify(body, null, 2), "utf8"), Buffer.byteLength(YAML.stringify(body), "utf8"));
765
+ };
766
+ while (entries.length > 0 && serializedBytes() > MAX_OUTPUT_BYTES) {
767
+ entries = entries.slice(0, -1);
768
+ omissionReason = "output_byte_budget";
769
+ }
770
+ if (entries.length === 0 && start < completeEntries.length)
771
+ throw new Error("the next whole migration entry exceeds the 32768-byte output budget; repair that source before retrying");
772
+ const outputDiagnostics = diagnosticsForEntries();
773
+ const nextAfter = start + entries.length < completeEntries.length ? entries.at(-1)?.source_identity ?? null : null;
774
+ const retrieval = { command: nextAfter ? `agentera state migrate entities --project '${quotedProject}' --after '${nextAfter.replaceAll("'", "'\\''")}' --source-fingerprint ${fingerprint} --preview-digest ${previewDigest} --limit ${requestedLimit} --dry-run --format json` : restartCommand };
775
+ return { ...base, status: base.status, mode: "preview", entries, diagnostics: outputDiagnostics, omitted: start > 0 || entries.length < completeEntries.length, omitted_count: completeEntries.length - entries.length, diagnostics_omitted_count: diagnostics.length - outputDiagnostics.length, omission_reason: omissionReason, page_after: options.after ?? null, next_after: nextAfter, retrieval };
776
+ }
777
+ export class EntityMigrationContinuationError extends Error {
778
+ restartCommand;
779
+ classification = "continuation_changed";
780
+ constructor(restartCommand) {
781
+ super(`entity migration continuation no longer matches its source, migration authority, selectors, or order; restart with ${restartCommand}`);
782
+ this.restartCommand = restartCommand;
783
+ this.name = "EntityMigrationContinuationError";
784
+ }
785
+ }
786
+ export class EntityMigrationBindingError extends Error {
787
+ current;
788
+ classification = "source_changed";
789
+ restartCommand;
790
+ constructor(current) {
791
+ const restartCommand = `agentera state migrate entities --project '${current.project.replaceAll("'", "'\\''")}' --dry-run --format json`;
792
+ super(`entity migration source or migration authority changed after preview; rerun ${restartCommand}`);
793
+ this.current = current;
794
+ this.restartCommand = restartCommand;
795
+ this.name = "EntityMigrationBindingError";
796
+ }
797
+ }
798
+ export function assertEntityMigrationBinding(expectedSourceFingerprint, expectedPreviewDigest, current, effect) {
799
+ if (current.source_fingerprint !== expectedSourceFingerprint || current.preview_digest !== expectedPreviewDigest)
800
+ throw new EntityMigrationBindingError(current);
801
+ return effect();
802
+ }
803
+ //# sourceMappingURL=entityMigrationPreview.js.map