project-agent 0.2.2__tar.gz → 0.2.4__tar.gz

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 (173) hide show
  1. {project_agent-0.2.2 → project_agent-0.2.4}/PKG-INFO +1 -1
  2. {project_agent-0.2.2 → project_agent-0.2.4}/pyproject.toml +1 -1
  3. project_agent-0.2.4/src/project_agent/application/context/add_project_member.py +50 -0
  4. project_agent-0.2.4/src/project_agent/application/context/get_project_context.py +128 -0
  5. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/project_summary.py +18 -2
  6. project_agent-0.2.4/src/project_agent/application/context/set_project_owner.py +59 -0
  7. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/doc/render.py +99 -17
  8. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/doc/sync.py +9 -2
  9. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/doc/writeback.py +5 -2
  10. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/doc_hub/manage.py +4 -2
  11. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/doc_hub/read.py +1 -0
  12. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/create_version.py +2 -2
  13. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/finalize_version_closure.py +9 -0
  14. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/propose_change.py +6 -0
  15. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/read.py +18 -83
  16. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/signoff_proposal.py +2 -0
  17. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/update_version.py +3 -0
  18. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/report/build.py +173 -76
  19. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/scheduled/run.py +252 -36
  20. project_agent-0.2.4/src/project_agent/application/shared/project_participants.py +123 -0
  21. project_agent-0.2.4/src/project_agent/application/shared/user_refs.py +105 -0
  22. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/doc/models.py +6 -3
  23. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/mind/models.py +7 -13
  24. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/monitoring/models.py +26 -17
  25. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/project_context/models.py +38 -1
  26. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/report/models.py +21 -4
  27. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/scheduled/models.py +10 -1
  28. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/shared/documents.py +2 -1
  29. project_agent-0.2.4/src/project_agent/domain/shared/project_participants.py +14 -0
  30. project_agent-0.2.4/src/project_agent/domain/shared/user_refs.py +31 -0
  31. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/infrastructure/clock/workday_clock.py +15 -0
  32. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/infrastructure/db/repositories.py +58 -8
  33. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/infrastructure/db/schema.py +93 -28
  34. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/infrastructure/db/tables.py +5 -2
  35. project_agent-0.2.4/src/project_agent/infrastructure/feature_flags.py +18 -0
  36. project_agent-0.2.4/src/project_agent/interface/cli/commands/_monitoring_support.py +6 -0
  37. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/context.py +291 -8
  38. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/doc.py +55 -5
  39. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/doc_hub.py +16 -8
  40. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/mind.py +73 -1
  41. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/monitoring.py +3 -4
  42. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/report.py +4 -3
  43. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/scheduled.py +15 -2
  44. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/task_event_notify.py +1 -1
  45. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/helptext.py +2 -2
  46. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent.egg-info/PKG-INFO +1 -1
  47. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent.egg-info/SOURCES.txt +12 -0
  48. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_bootstrap_checkpoint.py +3 -1
  49. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_cli_help.py +39 -2
  50. project_agent-0.2.4/tests/test_context_add_project_member.py +217 -0
  51. project_agent-0.2.4/tests/test_context_get_project.py +241 -0
  52. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_context_get_project_pending_states.py +4 -19
  53. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_context_list_projects.py +75 -11
  54. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_context_list_tasks.py +9 -9
  55. project_agent-0.2.4/tests/test_context_project_binding.py +154 -0
  56. project_agent-0.2.4/tests/test_context_project_summary.py +837 -0
  57. project_agent-0.2.4/tests/test_context_set_project_owner.py +174 -0
  58. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_db_migration.py +67 -0
  59. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_db_schema.py +13 -7
  60. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_db_session.py +31 -0
  61. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_doc_hub.py +5 -1
  62. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_doc_render_planned_versions.py +3 -0
  63. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_finalize_version_closure.py +77 -0
  64. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_mind_governance.py +59 -0
  65. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_mind_proposal_extensions.py +9 -4
  66. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_mind_read.py +185 -6
  67. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_monitoring_run.py +85 -0
  68. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_project_group_boundary_guard.py +110 -1
  69. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_project_group_routing_contracts.py +48 -25
  70. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_report_build.py +179 -14
  71. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_runtime_bundles.py +18 -0
  72. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_runtime_output_hygiene_contracts.py +1 -16
  73. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_scheduled_run.py +498 -56
  74. project_agent-0.2.2/src/project_agent/application/context/get_project_context.py +0 -59
  75. project_agent-0.2.2/tests/test_context_project_summary.py +0 -322
  76. {project_agent-0.2.2 → project_agent-0.2.4}/README.md +0 -0
  77. {project_agent-0.2.2 → project_agent-0.2.4}/setup.cfg +0 -0
  78. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/__init__.py +0 -0
  79. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/__init__.py +0 -0
  80. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/bootstrap/__init__.py +0 -0
  81. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/bootstrap/checkpoint.py +0 -0
  82. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/bootstrap/init.py +0 -0
  83. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/bootstrap/loop_state.py +0 -0
  84. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/__init__.py +0 -0
  85. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/binding_normalization.py +0 -0
  86. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/get_project_owner.py +0 -0
  87. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/get_task.py +0 -0
  88. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/get_user_context.py +0 -0
  89. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/list_projects.py +0 -0
  90. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/list_tasks.py +0 -0
  91. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/resolve_binding.py +0 -0
  92. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/resolve_group.py +0 -0
  93. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/resolve_user.py +0 -0
  94. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/search_user.py +0 -0
  95. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/context/upsert_user_identity.py +0 -0
  96. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/doc/__init__.py +0 -0
  97. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/doc_hub/__init__.py +0 -0
  98. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/loop/__init__.py +0 -0
  99. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/loop/enqueue.py +0 -0
  100. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/loop/run.py +0 -0
  101. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/__init__.py +0 -0
  102. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/delete_task.py +0 -0
  103. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/milestones.py +0 -0
  104. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/planned_versions.py +0 -0
  105. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/set_tracking.py +0 -0
  106. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/snooze_task_reminder.py +0 -0
  107. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/switch_version.py +0 -0
  108. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/mind/write_task_update.py +0 -0
  109. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/monitoring/__init__.py +0 -0
  110. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/monitoring/evaluate_followups.py +0 -0
  111. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/monitoring/plan.py +0 -0
  112. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/monitoring/run.py +0 -0
  113. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/report/__init__.py +0 -0
  114. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/scheduled/__init__.py +0 -0
  115. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/version_closure/__init__.py +0 -0
  116. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/application/version_closure/evaluate.py +0 -0
  117. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/__init__.py +0 -0
  118. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/bootstrap/__init__.py +0 -0
  119. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/bootstrap/models.py +0 -0
  120. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/doc/__init__.py +0 -0
  121. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/doc_hub/__init__.py +0 -0
  122. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/doc_hub/models.py +0 -0
  123. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/loop/__init__.py +0 -0
  124. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/loop/models.py +0 -0
  125. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/mind/__init__.py +0 -0
  126. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/monitoring/__init__.py +0 -0
  127. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/monitoring/evaluator.py +0 -0
  128. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/project_context/__init__.py +0 -0
  129. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/report/__init__.py +0 -0
  130. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/scheduled/__init__.py +0 -0
  131. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/shared/__init__.py +0 -0
  132. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/shared/models.py +0 -0
  133. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/shared/project_mind_derived.py +0 -0
  134. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/shared/project_mind_readiness.py +0 -0
  135. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/shared/statuses.py +0 -0
  136. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/user/__init__.py +0 -0
  137. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/user/models.py +0 -0
  138. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/version_closure/__init__.py +0 -0
  139. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/version_closure/evaluator.py +0 -0
  140. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/domain/version_closure/models.py +0 -0
  141. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/infrastructure/__init__.py +0 -0
  142. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/infrastructure/clock/__init__.py +0 -0
  143. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/infrastructure/db/__init__.py +0 -0
  144. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/infrastructure/db/migration.py +0 -0
  145. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/infrastructure/db/session.py +0 -0
  146. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/__init__.py +0 -0
  147. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/__init__.py +0 -0
  148. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/app.py +0 -0
  149. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/__init__.py +0 -0
  150. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/bootstrap.py +0 -0
  151. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/db.py +0 -0
  152. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/loop.py +0 -0
  153. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/project_boundary.py +0 -0
  154. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/upgrade.py +0 -0
  155. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent/interface/cli/commands/version_closure.py +0 -0
  156. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent.egg-info/dependency_links.txt +0 -0
  157. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent.egg-info/entry_points.txt +0 -0
  158. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent.egg-info/requires.txt +0 -0
  159. {project_agent-0.2.2 → project_agent-0.2.4}/src/project_agent.egg-info/top_level.txt +0 -0
  160. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_cli_version.py +0 -0
  161. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_context_get_project_owner.py +0 -0
  162. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_context_resolve_binding.py +0 -0
  163. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_context_search_user.py +0 -0
  164. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_context_upsert_user_identity.py +0 -0
  165. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_loop_jobs.py +0 -0
  166. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_mind_proposal_signoff.py +0 -0
  167. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_monitoring_plan.py +0 -0
  168. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_overview_sync_hints.py +0 -0
  169. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_pending_state_idempotency.py +0 -0
  170. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_release_versioning.py +0 -0
  171. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_report_persistence.py +0 -0
  172. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_version_closure_trigger.py +0 -0
  173. {project_agent-0.2.2 → project_agent-0.2.4}/tests/test_version_status_auto_sync.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: project-agent
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Project Agent Python CLI core
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: typer<1,>=0.12
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "project-agent"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "Project Agent Python CLI core"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [
@@ -0,0 +1,50 @@
1
+ from typing import Any, Protocol
2
+
3
+ from project_agent.domain.project_context.models import AddProjectMemberResult, ProjectParticipantSummary
4
+
5
+
6
+ class ProjectStore(Protocol):
7
+ def find_by_id(self, project_id: str) -> Any: ...
8
+
9
+
10
+ class UserStore(Protocol):
11
+ def find_by_id(self, user_id: str) -> Any: ...
12
+
13
+
14
+ class ParticipantWriteStore(Protocol):
15
+ def upsert_participant(self, payload: dict) -> dict: ...
16
+
17
+
18
+ def add_project_member(
19
+ *,
20
+ project_id: str,
21
+ user_id: str,
22
+ project_store: ProjectStore,
23
+ user_store: UserStore,
24
+ participant_store: ParticipantWriteStore,
25
+ ) -> AddProjectMemberResult:
26
+ if not project_store.find_by_id(project_id):
27
+ raise ValueError(f"project not found: {project_id}")
28
+ if not user_store.find_by_id(user_id):
29
+ raise ValueError(f"user not found: {user_id}")
30
+
31
+ participant = participant_store.upsert_participant(
32
+ {
33
+ "project_id": project_id,
34
+ "user_id": user_id,
35
+ "role": "member",
36
+ "status": "active",
37
+ }
38
+ )
39
+ return AddProjectMemberResult(
40
+ status="ok",
41
+ participant=ProjectParticipantSummary(
42
+ participant_id=participant["participant_id"],
43
+ project_id=participant["project_id"],
44
+ user_id=participant["user_id"],
45
+ role=participant["role"],
46
+ status=participant["status"],
47
+ created_at=participant.get("created_at"),
48
+ updated_at=participant.get("updated_at"),
49
+ ),
50
+ )
@@ -0,0 +1,128 @@
1
+ from typing import Any, Protocol
2
+
3
+ from project_agent.application.shared.project_participants import build_project_participant_summaries
4
+ from project_agent.application.shared.user_refs import build_structured_user_ref
5
+ from project_agent.domain.project_context.models import ProjectContext
6
+ from project_agent.domain.shared.project_mind_derived import build_project_intro, collect_participants
7
+
8
+
9
+ class ProjectStore(Protocol):
10
+ def find_by_id(self, project_id: str) -> Any: ...
11
+
12
+
13
+ class BindingStore(Protocol):
14
+ def list_by_project_id(self, project_id: str) -> list[Any]: ...
15
+
16
+
17
+ class PendingStateStore(Protocol):
18
+ def list_open_by_project_id(self, project_id: str) -> list[Any]: ...
19
+
20
+
21
+ class DocumentStore(Protocol):
22
+ def list_by_project_id(self, project_id: str) -> list[Any]: ...
23
+
24
+
25
+ class ParticipantStore(Protocol):
26
+ def list_by_project_id(self, project_id: str) -> list[Any]: ...
27
+
28
+
29
+ class VersionStore(Protocol):
30
+ def list_by_project_id(self, project_id: str) -> list[Any]: ...
31
+
32
+
33
+ class TaskStore(Protocol):
34
+ def list_by_project_id(self, project_id: str, task_id: str | None = None) -> list[Any]: ...
35
+
36
+
37
+ class UserStore(Protocol):
38
+ def find_by_id(self, user_id: str) -> Any: ...
39
+
40
+
41
+ class UserIdentityStore(Protocol):
42
+ def list_by_user_id(self, user_id: str) -> list[Any]: ...
43
+
44
+
45
+ def get_project_context(
46
+ *,
47
+ project_id: str,
48
+ channel: str | None = None,
49
+ project_store: ProjectStore,
50
+ binding_store: BindingStore | None = None,
51
+ pending_state_store: PendingStateStore | None = None,
52
+ document_store: DocumentStore | None = None,
53
+ participant_store: ParticipantStore | None = None,
54
+ version_store: VersionStore | None = None,
55
+ task_store: TaskStore | None = None,
56
+ user_store: UserStore | None = None,
57
+ identity_store: UserIdentityStore | None = None,
58
+ ) -> ProjectContext | None:
59
+ project = project_store.find_by_id(project_id)
60
+
61
+ if not project:
62
+ return None
63
+
64
+ bindings = []
65
+ if binding_store is not None:
66
+ bindings = binding_store.list_by_project_id(project_id)
67
+
68
+ pending_states = []
69
+ if pending_state_store is not None:
70
+ pending_states = pending_state_store.list_open_by_project_id(project_id)
71
+
72
+ documents = []
73
+ if document_store is not None:
74
+ documents = document_store.list_by_project_id(project_id)
75
+
76
+ participant_rows = []
77
+ if participant_store is not None:
78
+ participant_rows = participant_store.list_by_project_id(project_id)
79
+
80
+ versions = []
81
+ if version_store is not None:
82
+ versions = version_store.list_by_project_id(project_id)
83
+
84
+ tasks = []
85
+ if task_store is not None:
86
+ tasks = task_store.list_by_project_id(project_id)
87
+
88
+ owner_user = build_structured_user_ref(
89
+ user_id=project.get("owner_user_id"),
90
+ user_store=user_store,
91
+ user_identity_store=identity_store,
92
+ identity_cache={},
93
+ user_cache={},
94
+ )
95
+ user_cache: dict[str, dict | None] = {}
96
+ identity_cache: dict[str, list[dict]] = {}
97
+ participant_summaries = build_project_participant_summaries(
98
+ participant_user_ids=collect_participants(
99
+ versions=versions,
100
+ tasks=tasks,
101
+ participants=participant_rows,
102
+ ),
103
+ participant_rows=participant_rows,
104
+ owner_user_id=project.get("owner_user_id"),
105
+ user_store=user_store,
106
+ user_identity_store=identity_store,
107
+ channel=channel,
108
+ user_cache=user_cache,
109
+ identity_cache=identity_cache,
110
+ )
111
+
112
+ return ProjectContext(
113
+ project_id=project_id,
114
+ owner_user=owner_user,
115
+ primary_goal=project.get("primary_goal"),
116
+ project_intro=build_project_intro(project=project, versions=versions, tasks=tasks),
117
+ lifecycle_status=project["lifecycle_status"],
118
+ mind_stage=project["mind_stage"],
119
+ current_revision_id=project.get("current_revision_id"),
120
+ current_version_id=project.get("current_version_id"),
121
+ tracking_mode=project.get("tracking_mode") or "active",
122
+ tracking_paused_reason=project.get("tracking_paused_reason"),
123
+ tracking_paused_at=project.get("tracking_paused_at"),
124
+ bindings=bindings,
125
+ participants=participant_summaries,
126
+ pending_states=pending_states,
127
+ documents=documents,
128
+ )
@@ -45,7 +45,7 @@ def build_project_summary(
45
45
  project_name=project["project_name"],
46
46
  team_task=_build_team_task_summary(tasks, today=today, tz=tz),
47
47
  personal=_build_personal_task_summary(tasks, user_id=user_id, today=today, tz=tz),
48
- updated_at=project.get("last_updated_at"),
48
+ updated_at=_utc_millis(project.get("last_updated_at")),
49
49
  )
50
50
 
51
51
 
@@ -62,9 +62,18 @@ def _build_team_task_summary(
62
62
  today: date,
63
63
  tz: ZoneInfo,
64
64
  ) -> TeamTaskSummary:
65
+ new_assigned_today_tasks = [
66
+ task for task in tasks if _is_today(_task_value(task, "created_at"), today, tz)
67
+ ]
68
+ new_assigned_today = len(new_assigned_today_tasks)
69
+ if new_assigned_today_tasks and all(
70
+ is_done_status(_task_value(task, "task_status")) for task in new_assigned_today_tasks
71
+ ):
72
+ new_assigned_today = 0
73
+
65
74
  return TeamTaskSummary(
66
75
  status="HAS_TASK" if tasks else "NO_TASK",
67
- new_assigned_today=sum(1 for task in tasks if _is_today(_task_value(task, "created_at"), today, tz)),
76
+ new_assigned_today=new_assigned_today,
68
77
  completed_today=sum(
69
78
  1
70
79
  for task in tasks
@@ -142,3 +151,10 @@ def _parse_datetime(value: Any, *, tz: ZoneInfo) -> datetime | None:
142
151
  if parsed.tzinfo is None:
143
152
  parsed = parsed.replace(tzinfo=UTC)
144
153
  return parsed.astimezone(tz)
154
+
155
+
156
+ def _utc_millis(value: Any) -> int | None:
157
+ parsed = _parse_datetime(value, tz=ZoneInfo("UTC"))
158
+ if parsed is None:
159
+ return None
160
+ return int(parsed.timestamp() * 1000)
@@ -0,0 +1,59 @@
1
+ from typing import Any, Protocol
2
+
3
+ from project_agent.domain.project_context.models import SetProjectOwnerResult
4
+
5
+
6
+ class ProjectStore(Protocol):
7
+ def find_by_id(self, project_id: str) -> Any: ...
8
+
9
+ def update_owner_user_id(self, *, project_id: str, owner_user_id: str) -> dict: ...
10
+
11
+
12
+ class UserStore(Protocol):
13
+ def find_by_id(self, user_id: str) -> Any: ...
14
+
15
+
16
+ class ParticipantWriteStore(Protocol):
17
+ def upsert_participant(self, payload: dict) -> dict: ...
18
+
19
+
20
+ def set_project_owner(
21
+ *,
22
+ project_id: str,
23
+ user_id: str,
24
+ project_store: ProjectStore,
25
+ user_store: UserStore,
26
+ participant_store: ParticipantWriteStore,
27
+ ) -> SetProjectOwnerResult:
28
+ project = project_store.find_by_id(project_id)
29
+ if not project:
30
+ raise ValueError(f"project not found: {project_id}")
31
+ if not user_store.find_by_id(user_id):
32
+ raise ValueError(f"user not found: {user_id}")
33
+
34
+ previous_owner_user_id = project.get("owner_user_id")
35
+ project_store.update_owner_user_id(project_id=project_id, owner_user_id=user_id)
36
+ participant_store.upsert_participant(
37
+ {
38
+ "project_id": project_id,
39
+ "user_id": user_id,
40
+ "role": "owner",
41
+ "status": "active",
42
+ }
43
+ )
44
+ if previous_owner_user_id and previous_owner_user_id != user_id:
45
+ participant_store.upsert_participant(
46
+ {
47
+ "project_id": project_id,
48
+ "user_id": previous_owner_user_id,
49
+ "role": "member",
50
+ "status": "active",
51
+ }
52
+ )
53
+
54
+ return SetProjectOwnerResult(
55
+ status="ok",
56
+ project_id=project_id,
57
+ owner_user_id=user_id,
58
+ previous_owner_user_id=previous_owner_user_id,
59
+ )
@@ -34,6 +34,10 @@ class ParticipantStore(Protocol):
34
34
  def list_by_project_id(self, project_id: str) -> list[Any]: ...
35
35
 
36
36
 
37
+ class UserStore(Protocol):
38
+ def find_by_id(self, user_id: str) -> Any: ...
39
+
40
+
37
41
  class DocumentStore(Protocol):
38
42
  def list_by_project_id(self, project_id: str) -> list[Any]: ...
39
43
 
@@ -74,9 +78,12 @@ def render_project_document(
74
78
  document_store: DocumentStore | None = None,
75
79
  doc_hub_entry_store: DocHubEntryStore | None = None,
76
80
  channel: str | None = None,
81
+ provider: str | None = None,
82
+ user_store: UserStore | None = None,
77
83
  user_identity_store: Any | None = None,
78
84
  ) -> DocRenderResult | None:
79
85
  del template_ref
86
+ provider = provider or channel
80
87
 
81
88
  project = project_store.find_by_id(project_id)
82
89
  if not project:
@@ -97,7 +104,9 @@ def render_project_document(
97
104
  tasks=tasks,
98
105
  version_id=version_id,
99
106
  )
100
- _enrich_sections_with_channel_identities(sections, channel, user_identity_store)
107
+ _enrich_sections_with_user_display_values(sections, user_store)
108
+ if provider == "feishu":
109
+ _enrich_sections_with_channel_identities(sections, "feishu", user_identity_store)
101
110
  return _with_render_hash(DocRenderResult(
102
111
  project_id=project_id,
103
112
  doc_kind=doc_kind,
@@ -190,7 +199,9 @@ def render_project_document(
190
199
  ),
191
200
  ]
192
201
  title = project["project_name"]
193
- _enrich_sections_with_channel_identities(sections, channel, user_identity_store)
202
+ _enrich_sections_with_user_display_values(sections, user_store)
203
+ if provider == "feishu":
204
+ _enrich_sections_with_channel_identities(sections, "feishu", user_identity_store)
194
205
  return _with_render_hash(DocRenderResult(
195
206
  project_id=project_id,
196
207
  doc_kind=doc_kind,
@@ -204,7 +215,7 @@ def render_project_document(
204
215
  project_id=project_id,
205
216
  doc_hub_entry_store=doc_hub_entry_store,
206
217
  )
207
- sections = _build_docs_index_sections(entries=entries, relations=relations, channel=channel)
218
+ sections = _build_docs_index_sections(entries=entries, relations=relations, provider=provider)
208
219
  title = f"{project['project_name']} Docs Index"
209
220
  return _with_render_hash(DocRenderResult(
210
221
  project_id=project_id,
@@ -222,11 +233,11 @@ def render_project_document(
222
233
  planned_versions = _planned_versions(versions=versions, current_version_id=current_version_id)
223
234
  past_versions = _past_versions(versions=versions)
224
235
  documents = document_store.list_by_project_id(project_id) if document_store is not None else []
225
- snapshot_links = _version_snapshot_links(documents=documents, channel=channel)
236
+ snapshot_links = _version_snapshot_links(documents=documents, provider=provider)
226
237
  docs_index_url = _single_document_link(
227
238
  documents=documents,
228
239
  doc_kind="docs_index",
229
- channel=channel,
240
+ provider=provider,
230
241
  )
231
242
  sections = _build_overview_sections(
232
243
  project=project,
@@ -241,7 +252,9 @@ def render_project_document(
241
252
  docs_index_url=docs_index_url,
242
253
  )
243
254
  title = f"{project['project_name']} Overview"
244
- _enrich_sections_with_channel_identities(sections, channel, user_identity_store)
255
+ _enrich_sections_with_user_display_values(sections, user_store)
256
+ if provider == "feishu":
257
+ _enrich_sections_with_channel_identities(sections, "feishu", user_identity_store)
245
258
  return _with_render_hash(DocRenderResult(
246
259
  project_id=project_id,
247
260
  doc_kind=doc_kind,
@@ -265,18 +278,38 @@ def _load_doc_hub_index_inputs(
265
278
 
266
279
 
267
280
  def _with_render_hash(result: DocRenderResult) -> DocRenderResult:
268
- payload = result.model_dump(mode="json", by_alias=True, exclude={"render_hash", "status"})
281
+ payload = _strip_render_hash_ignored_hints(
282
+ result.model_dump(mode="json", by_alias=True, exclude={"render_hash", "status"})
283
+ )
269
284
  encoded = json.dumps(payload, ensure_ascii=True, sort_keys=True, separators=(",", ":"))
270
285
  return result.model_copy(update={"render_hash": hashlib.sha256(encoded.encode("utf-8")).hexdigest()})
271
286
 
272
287
 
288
+ def _strip_render_hash_ignored_hints(value: Any) -> Any:
289
+ if isinstance(value, dict):
290
+ return {
291
+ key: _strip_render_hash_ignored_hints(item)
292
+ for key, item in value.items()
293
+ if key
294
+ not in {
295
+ "displayValue",
296
+ "userDisplayNames",
297
+ "task_owner_display_value",
298
+ "planned_version_owner_display_value",
299
+ }
300
+ }
301
+ if isinstance(value, list):
302
+ return [_strip_render_hash_ignored_hints(item) for item in value]
303
+ return value
304
+
305
+
273
306
  def _build_docs_index_sections(
274
307
  *,
275
308
  entries: list[Any],
276
309
  relations: list[Any],
277
- channel: str | None,
310
+ provider: str | None,
278
311
  ) -> list[DocSection]:
279
- index_payload = _build_docs_index_payload(entries=entries, relations=relations, channel=channel)
312
+ index_payload = _build_docs_index_payload(entries=entries, relations=relations, provider=provider)
280
313
  sections = [
281
314
  DocSection(
282
315
  heading="Registered Documents",
@@ -297,13 +330,13 @@ def _build_docs_index_payload(
297
330
  *,
298
331
  entries: list[Any],
299
332
  relations: list[Any],
300
- channel: str | None,
333
+ provider: str | None,
301
334
  ) -> dict[str, list[dict]]:
302
335
  rows = []
303
336
  filtered_entry_ids: set[str] = set()
304
337
  entry_titles_by_id: dict[str, str] = {}
305
338
  for entry in entries:
306
- if channel is not None and entry.get("provider") != channel:
339
+ if provider is not None and entry.get("provider") != provider:
307
340
  continue
308
341
  entry_id = entry.get("entry_id")
309
342
  filtered_entry_ids.add(entry_id)
@@ -341,6 +374,45 @@ def _build_docs_index_payload(
341
374
  return {"documents": rows, "relations": relation_rows}
342
375
 
343
376
 
377
+ def _enrich_sections_with_user_display_values(
378
+ sections: list[DocSection],
379
+ user_store: UserStore | None,
380
+ ) -> None:
381
+ if user_store is None:
382
+ return
383
+
384
+ user_ids: set[str] = set()
385
+ _collect_user_ids(sections, user_ids)
386
+ if not user_ids:
387
+ return
388
+
389
+ display_names: dict[str, str] = {}
390
+ for uid in user_ids:
391
+ user = user_store.find_by_id(uid)
392
+ display_names[uid] = (user or {}).get("display_name") or uid
393
+
394
+ for section in sections:
395
+ for field in section.payload.get("fields", []):
396
+ value = field.get("value", "")
397
+ if not isinstance(value, str):
398
+ continue
399
+ uids = [token for token in value.split(", ") if token.startswith("usr_")]
400
+ if not uids:
401
+ continue
402
+ names = [display_names.get(uid, uid) for uid in uids]
403
+ field["displayValue"] = ", ".join(names)
404
+ if len(names) > 1:
405
+ field["userDisplayNames"] = names
406
+ for item in section.payload.get("tasks", []):
407
+ owner = item.get("task_owner", "")
408
+ if isinstance(owner, str) and owner.startswith("usr_"):
409
+ item["task_owner_display_value"] = display_names.get(owner, owner)
410
+ for item in section.payload.get("versions", []):
411
+ owner = item.get("planned_version_owner", "")
412
+ if isinstance(owner, str) and owner.startswith("usr_"):
413
+ item["planned_version_owner_display_value"] = display_names.get(owner, owner)
414
+
415
+
344
416
  def _enrich_sections_with_channel_identities(
345
417
  sections: list[DocSection],
346
418
  channel: str | None,
@@ -535,6 +607,11 @@ def _build_overview_sections(
535
607
  "label": "Goal",
536
608
  "value": (current_version or {}).get("version_goal") or "TBD",
537
609
  },
610
+ {
611
+ "name": "version_summary",
612
+ "label": "Version Summary",
613
+ "value": (current_version or {}).get("version_summary") or "TBD",
614
+ },
538
615
  {
539
616
  "name": "version_owner",
540
617
  "label": "Owner",
@@ -693,6 +770,7 @@ def _build_planned_versions_payload(*, planned_versions: list[dict], current_ver
693
770
  "planned_version_name": "TBD",
694
771
  "planned_version_milestone": "TBD",
695
772
  "planned_version_goal": "TBD",
773
+ "planned_version_summary": "TBD",
696
774
  "planned_version_owner": "TBD",
697
775
  "planned_version_target_start_date": "TBD",
698
776
  "planned_version_target_start_date_note": "(待补充)",
@@ -710,6 +788,7 @@ def _build_planned_versions_payload(*, planned_versions: list[dict], current_ver
710
788
  "planned_version_name": version.get("version_name") or "TBD",
711
789
  "planned_version_milestone": _display_milestone(version),
712
790
  "planned_version_goal": version.get("version_goal") or "TBD",
791
+ "planned_version_summary": version.get("version_summary") or "TBD",
713
792
  "planned_version_owner": version.get("owner_user_id") or "TBD",
714
793
  "planned_version_target_start_date": version.get("start_date") or "TBD",
715
794
  "planned_version_target_start_date_note": _planned_start_note(
@@ -765,6 +844,7 @@ def _build_version_snapshot(
765
844
  "fields": [
766
845
  {"name": "version_name", "label": "Version Name", "value": version.get("version_name") or "TBD"},
767
846
  {"name": "version_goal", "label": "Goal", "value": version.get("version_goal") or "TBD"},
847
+ {"name": "version_summary", "label": "Version Summary", "value": version.get("version_summary") or "TBD"},
768
848
  {"name": "version_owner", "label": "Owner", "value": version.get("owner_user_id") or "TBD"},
769
849
  {"name": "start_date", "label": "Start Date", "value": version.get("start_date") or "TBD"},
770
850
  {
@@ -875,18 +955,19 @@ def _build_past_version_snapshots_payload(past_versions: list[dict], *, snapshot
875
955
  }
876
956
 
877
957
 
878
- def _version_snapshot_links(*, documents: list[dict], channel: str | None) -> dict[str, str]:
958
+ def _version_snapshot_links(*, documents: list[dict], provider: str | None) -> dict[str, str]:
879
959
  links: dict[str, str] = {}
880
960
  for document in sorted(
881
961
  documents,
882
962
  key=lambda item: (
883
- item.get("channel") or "",
963
+ item.get("provider") or item.get("channel") or "",
884
964
  item.get("document_id") or "",
885
965
  ),
886
966
  ):
887
967
  if document.get("doc_kind") != "version_snapshot":
888
968
  continue
889
- if channel is not None and document.get("channel") != channel:
969
+ document_provider = document.get("provider") or document.get("channel")
970
+ if provider is not None and document_provider != provider:
890
971
  continue
891
972
  version_id = document.get("version_id")
892
973
  if not version_id or version_id in links:
@@ -897,17 +978,18 @@ def _version_snapshot_links(*, documents: list[dict], channel: str | None) -> di
897
978
  return links
898
979
 
899
980
 
900
- def _single_document_link(*, documents: list[dict], doc_kind: str, channel: str | None) -> str:
981
+ def _single_document_link(*, documents: list[dict], doc_kind: str, provider: str | None) -> str:
901
982
  for document in sorted(
902
983
  documents,
903
984
  key=lambda item: (
904
- item.get("channel") or "",
985
+ item.get("provider") or item.get("channel") or "",
905
986
  item.get("document_id") or "",
906
987
  ),
907
988
  ):
908
989
  if document.get("doc_kind") != doc_kind:
909
990
  continue
910
- if channel is not None and document.get("channel") != channel:
991
+ document_provider = document.get("provider") or document.get("channel")
992
+ if provider is not None and document_provider != provider:
911
993
  continue
912
994
  url = document.get("url")
913
995
  if url and document.get("last_verified_render_hash"):
@@ -40,7 +40,8 @@ def sync_project_document(
40
40
  project_id: str,
41
41
  doc_kind: str,
42
42
  version_id: str | None,
43
- channel: str,
43
+ provider: str,
44
+ channel: str | None,
44
45
  trigger_kind: str,
45
46
  template_ref: str | None,
46
47
  force: bool,
@@ -50,6 +51,7 @@ def sync_project_document(
50
51
  document_store: DocumentStore,
51
52
  doc_hub_entry_store: DocHubEntryStore | None = None,
52
53
  participant_store: ParticipantStore | None = None,
54
+ user_store: Any | None = None,
53
55
  user_identity_store: Any | None = None,
54
56
  ) -> DocSyncResult | None:
55
57
  del trigger_kind
@@ -63,7 +65,7 @@ def sync_project_document(
63
65
  for document in document_store.list_by_project_id(project_id):
64
66
  if (
65
67
  document["doc_kind"] == doc_kind
66
- and document["channel"] == channel
68
+ and (document.get("provider") or document.get("channel")) == provider
67
69
  and document.get("version_id") == version_id
68
70
  ):
69
71
  existing_document = document
@@ -85,6 +87,8 @@ def sync_project_document(
85
87
  document_store=document_store,
86
88
  doc_hub_entry_store=doc_hub_entry_store,
87
89
  channel=channel,
90
+ provider=provider,
91
+ user_store=user_store,
88
92
  user_identity_store=user_identity_store,
89
93
  )
90
94
 
@@ -100,6 +104,7 @@ def sync_project_document(
100
104
  project_id=project_id,
101
105
  doc_kind=doc_kind,
102
106
  version_id=version_id,
107
+ provider=provider,
103
108
  channel=channel,
104
109
  source_revision_id=source_revision_id,
105
110
  last_synced_revision_id=last_synced_revision_id,
@@ -116,6 +121,7 @@ def sync_project_document(
116
121
  project_id=project_id,
117
122
  doc_kind=doc_kind,
118
123
  version_id=version_id,
124
+ provider=provider,
119
125
  channel=channel,
120
126
  source_revision_id=source_revision_id,
121
127
  last_synced_revision_id=last_synced_revision_id,
@@ -126,6 +132,7 @@ def sync_project_document(
126
132
  project_id=project_id,
127
133
  doc_kind=doc_kind,
128
134
  version_id=version_id,
135
+ provider=provider,
129
136
  channel=channel,
130
137
  last_synced_revision_id=source_revision_id,
131
138
  render_hash=render.render_hash if render is not None else None,
@@ -21,7 +21,8 @@ def writeback_document(
21
21
  project_id: str,
22
22
  doc_kind: str,
23
23
  version_id: str | None,
24
- channel: str,
24
+ provider: str,
25
+ channel: str | None,
25
26
  external_doc_id: str | None,
26
27
  url: str | None,
27
28
  last_synced_revision_id: str | None,
@@ -48,6 +49,7 @@ def writeback_document(
48
49
  "project_id": project_id,
49
50
  "doc_kind": doc_kind,
50
51
  "version_id": version_id,
52
+ "provider": provider,
51
53
  "channel": channel,
52
54
  "external_doc_id": external_doc_id,
53
55
  "url": url,
@@ -60,7 +62,8 @@ def writeback_document(
60
62
  document_id=document["document_id"],
61
63
  doc_kind=document["doc_kind"],
62
64
  version_id=document.get("version_id"),
63
- channel=document["channel"],
65
+ provider=document["provider"],
66
+ channel=document.get("channel"),
64
67
  external_doc_id=document.get("external_doc_id"),
65
68
  url=document.get("url"),
66
69
  last_synced_revision_id=document.get("last_synced_revision_id"),
@@ -248,7 +248,7 @@ def ensure_system_documents(
248
248
  document_rows_by_kind = {
249
249
  (document["doc_kind"], document.get("version_id")): document
250
250
  for document in existing_documents
251
- if document.get("channel") == provider
251
+ if (document.get("provider") or document.get("channel")) == provider
252
252
  }
253
253
 
254
254
  entries: list[DocHubEntry] = []
@@ -270,7 +270,9 @@ def ensure_system_documents(
270
270
  entries.append(_doc_hub_entry(entry))
271
271
 
272
272
  for document in existing_documents:
273
- if document["doc_kind"] != "version_snapshot" or document.get("channel") != provider:
273
+ if document["doc_kind"] != "version_snapshot":
274
+ continue
275
+ if (document.get("provider") or document.get("channel")) != provider:
274
276
  continue
275
277
  version_id = document.get("version_id")
276
278
  title = _system_doc_title(
@@ -248,6 +248,7 @@ def _proposal_kwargs(fact: CandidateFact) -> dict[str, Any]:
248
248
  "task_dependencies": _optional_list_payload_value(payload, "task_dependencies"),
249
249
  "version_name": _payload_value(payload, "version_name"),
250
250
  "version_goal": _payload_value(payload, "version_goal"),
251
+ "version_summary": _payload_value(payload, "version_summary"),
251
252
  "version_start_date": _payload_value(payload, "version_start_date"),
252
253
  "target_end_date": _payload_value(payload, "target_end_date"),
253
254
  "version_deliverables": _list_payload_value(payload, "version_deliverables"),