campfire-cli 0.1.9__tar.gz → 0.1.10__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 (197) hide show
  1. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/ARCHITECTURE.md +2 -2
  2. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/COMMAND.md +1 -1
  3. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/PKG-INFO +4 -4
  4. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/README.md +3 -3
  5. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/SPEC.md +1 -1
  6. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/pyproject.toml +1 -1
  7. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/__init__.py +1 -1
  8. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/cli/document_cli.py +5 -1
  9. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/schema/document_schema.py +3 -1
  10. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/document_apply_service.py +86 -48
  11. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/document_move_service.py +5 -50
  12. campfire_cli-0.1.10/src/campfire_cli/app/document/service/document_relocation.py +59 -0
  13. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/maintenance/service/maintenance_service.py +17 -3
  14. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/SPEC.md +2 -2
  15. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/cli/restructure_cli.py +7 -2
  16. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/service/restructure_service.py +1 -1
  17. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/agent_hints.py +9 -7
  18. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/defaults/config.yml +3 -0
  19. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/SPEC.md +6 -3
  20. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-context-bootstrap/SKILL.md +4 -2
  21. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-document-capture/SKILL.md +4 -2
  22. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-task-management/SKILL.md +4 -9
  23. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-workspace-maintenance/SKILL.md +4 -3
  24. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-workspace-restructure/SKILL.md +1 -1
  25. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/integration/test_cli.py +153 -0
  26. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/unit/test_document_apply.py +215 -2
  27. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/uv.lock +1 -1
  28. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/.github/workflows/ci.yml +0 -0
  29. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/.github/workflows/release.yml +0 -0
  30. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/.gitignore +0 -0
  31. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/AGENTS.md +0 -0
  32. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/DECISIONS.md +0 -0
  33. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/LICENSE +0 -0
  34. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/alembic/SPEC.md +0 -0
  35. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/alembic/env.py +0 -0
  36. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/alembic/script.py.mako +0 -0
  37. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/alembic/versions/001_initial_schema.py +0 -0
  38. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/alembic.ini +0 -0
  39. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/scripts/SPEC.md +0 -0
  40. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/alembic/__init__.py +0 -0
  41. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/alembic/env.py +0 -0
  42. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/alembic/script.py.mako +0 -0
  43. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/alembic/versions/001_initial_schema.py +0 -0
  44. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/alembic/versions/002_workspace_project_registry.py +0 -0
  45. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/alembic/versions/003_unified_workspace_state.py +0 -0
  46. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/alembic/versions/004_restructure_workspace_domain.py +0 -0
  47. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/alembic/versions/005_decision_channel.py +0 -0
  48. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/alembic/versions/006_workspace_topology_index.py +0 -0
  49. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/alembic/versions/007_workspace_adoption.py +0 -0
  50. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/alembic/versions/008_remove_adoption_batches.py +0 -0
  51. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/alembic/versions/__init__.py +0 -0
  52. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/SPEC.md +0 -0
  53. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/__init__.py +0 -0
  54. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/base/SPEC.md +0 -0
  55. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/base/__init__.py +0 -0
  56. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/base/cli/base_cli.py +0 -0
  57. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/base/repository/base_repository.py +0 -0
  58. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/base/schema/base_schema.py +0 -0
  59. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/base/schema/operation_schema.py +0 -0
  60. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/base/service/base_service.py +0 -0
  61. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/decision/SPEC.md +0 -0
  62. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/decision/__init__.py +0 -0
  63. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/decision/cli/decision_cli.py +0 -0
  64. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/decision/repository/decision_repository.py +0 -0
  65. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/decision/schema/decision_schema.py +0 -0
  66. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/decision/service/decision_projection_service.py +0 -0
  67. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/decision/service/decision_service.py +0 -0
  68. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/SPEC.md +0 -0
  69. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/__init__.py +0 -0
  70. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/cli/__init__.py +0 -0
  71. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/cli/profile_cli.py +0 -0
  72. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/cli/type_cli.py +0 -0
  73. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/repository/__init__.py +0 -0
  74. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/repository/document_profile_repository.py +0 -0
  75. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/repository/document_type_repository.py +0 -0
  76. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/schema/__init__.py +0 -0
  77. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/__init__.py +0 -0
  78. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/document_profile_service.py +0 -0
  79. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/document_rule_service.py +0 -0
  80. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/document_scanner.py +0 -0
  81. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/document_service.py +0 -0
  82. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/document_type_service.py +0 -0
  83. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/frontmatter_apply.py +0 -0
  84. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/frontmatter_formatter.py +0 -0
  85. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/frontmatter_plan.py +0 -0
  86. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/kanban_service.py +0 -0
  87. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/profile_registry.py +0 -0
  88. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/type_apply.py +0 -0
  89. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/document/service/type_plan.py +0 -0
  90. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/maintenance/SPEC.md +0 -0
  91. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/maintenance/__init__.py +0 -0
  92. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/maintenance/cli/maintenance_cli.py +0 -0
  93. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/maintenance/repository/maintenance_repository.py +0 -0
  94. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/maintenance/schema/maintenance_schema.py +0 -0
  95. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/maintenance/service/archive_service.py +0 -0
  96. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/maintenance/service/link_service.py +0 -0
  97. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/maintenance/service/maintenance_protocol.py +0 -0
  98. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/maintenance/service/moc_service.py +0 -0
  99. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/skill/SPEC.md +0 -0
  100. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/skill/__init__.py +0 -0
  101. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/skill/cli/skill_cli.py +0 -0
  102. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/skill/repository/skill_repository.py +0 -0
  103. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/skill/schema/skill_schema.py +0 -0
  104. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/skill/service/skill_service.py +0 -0
  105. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/__init__.py +0 -0
  106. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/cli/__init__.py +0 -0
  107. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/cli/config_cli.py +0 -0
  108. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/cli/domain_cli.py +0 -0
  109. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/cli/project_cli.py +0 -0
  110. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/cli/space_cli.py +0 -0
  111. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/cli/workspace_cli.py +0 -0
  112. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/repository/__init__.py +0 -0
  113. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/repository/manifest_repository.py +0 -0
  114. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/repository/restructure_repository.py +0 -0
  115. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/repository/workspace_repository.py +0 -0
  116. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/schema/__init__.py +0 -0
  117. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/schema/adoption_schema.py +0 -0
  118. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/schema/restructure_schema.py +0 -0
  119. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/schema/workspace_schema.py +0 -0
  120. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/service/__init__.py +0 -0
  121. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/service/adoption_service.py +0 -0
  122. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/service/config_service.py +0 -0
  123. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/service/domain_restructure_service.py +0 -0
  124. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/service/project_service.py +0 -0
  125. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/service/restructure_protocol.py +0 -0
  126. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/service/restructure_verifier.py +0 -0
  127. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/service/structure_service.py +0 -0
  128. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/service/workspace_protocol.py +0 -0
  129. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/app/workspace/service/workspace_service.py +0 -0
  130. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/SPEC.md +0 -0
  131. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/__init__.py +0 -0
  132. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/database/__init__.py +0 -0
  133. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/database/migrations.py +0 -0
  134. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/database/models.py +0 -0
  135. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/database/session.py +0 -0
  136. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/documents/__init__.py +0 -0
  137. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/documents/document_types.py +0 -0
  138. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/documents/domain_context.py +0 -0
  139. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/documents/frontmatter_schema.py +0 -0
  140. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/documents/markdown.py +0 -0
  141. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/exceptions/__init__.py +0 -0
  142. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/exceptions/base.py +0 -0
  143. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/filesystem/__init__.py +0 -0
  144. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/filesystem/atomic.py +0 -0
  145. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/filesystem/change_set.py +0 -0
  146. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/filesystem/cwd.py +0 -0
  147. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/filesystem/locking.py +0 -0
  148. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/governance/__init__.py +0 -0
  149. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/governance/issues.py +0 -0
  150. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/governance/locking.py +0 -0
  151. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/governance/snapshots.py +0 -0
  152. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/hashing.py +0 -0
  153. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/package_version.py +0 -0
  154. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/reports/__init__.py +0 -0
  155. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/reports/json_report.py +0 -0
  156. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/common/reports/markdown_report.py +0 -0
  157. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/config/SPEC.md +0 -0
  158. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/config/defaults.py +0 -0
  159. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/config/settings.py +0 -0
  160. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/container.py +0 -0
  161. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/main.py +0 -0
  162. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/__init__.py +0 -0
  163. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/bases/Agent/346/211/247/350/241/214/345/267/245/344/275/234/345/217/260.base" +0 -0
  164. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/bases/SPEC.md +0 -0
  165. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/bases//344/273/273/345/212/241/345/267/245/344/275/234/345/217/260.base" +0 -0
  166. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/bases//345/206/263/347/255/226/345/267/245/344/275/234/345/217/260.base" +0 -0
  167. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/bases//345/276/205/345/275/222/346/241/243/346/226/207/346/241/243.base" +0 -0
  168. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/bases//351/241/271/347/233/256/346/226/207/346/241/243.base" +0 -0
  169. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/defaults/__init__.py +0 -0
  170. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-context-bootstrap/agents/openai.yaml +0 -0
  171. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-conversation-router/SKILL.md +0 -0
  172. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-conversation-router/agents/openai.yaml +0 -0
  173. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-conversation-router/references/inbox-handoff.md +0 -0
  174. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-conversation-router/references/learning-intake.md +0 -0
  175. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-conversation-router/references/task-intake.md +0 -0
  176. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-inbox-triage/SKILL.md +0 -0
  177. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-kanban-board/SKILL.md +0 -0
  178. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-weekly-report-writing/SKILL.md +0 -0
  179. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-weekly-report-writing/agents/openai.yaml +0 -0
  180. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-workspace-adoption/SKILL.md +0 -0
  181. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-workspace-maintenance/references//344/273/273/345/212/241/346/255/243/346/226/207/347/273/223/346/236/204.md" +0 -0
  182. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/src/campfire_cli/resources/skills/campfire-workspace-restructure/references/restructure-spec.md +0 -0
  183. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/SPEC.md +0 -0
  184. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/conftest.py +0 -0
  185. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/perf/test_read_counts.py +0 -0
  186. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/perf/test_resolve_subprocess_count.py +0 -0
  187. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/unit/test_agent_hints.py +0 -0
  188. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/unit/test_atomic_write.py +0 -0
  189. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/unit/test_common_governance.py +0 -0
  190. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/unit/test_config.py +0 -0
  191. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/unit/test_domain_context.py +0 -0
  192. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/unit/test_file_change_executor.py +0 -0
  193. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/unit/test_frontmatter.py +0 -0
  194. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/unit/test_kanban_service.py +0 -0
  195. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/unit/test_migrations.py +0 -0
  196. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/unit/test_package_version.py +0 -0
  197. {campfire_cli-0.1.9 → campfire_cli-0.1.10}/tests/unit/test_repository_boundaries.py +0 -0
@@ -118,7 +118,7 @@ Campfire 不是“Markdown 版 kubectl”,而是面向人机协作场景组合
118
118
  命令设计遵循以下约束:
119
119
 
120
120
  1. **对象域优先**:公共入口采用 `campfire <object-domain> <verb>`,例如 `document apply`、`maintenance check`;动词在所属业务对象内保持单义,不为同一行为保留多个别名。
121
- 2. **意图原子、派生显式**:`document apply` 只创建或更新目标文档,`document move` 原子完成单文档移动、目标 Profile 对齐和确定性引用修复;命令只在 MOC、关系页或索引可能变化时返回一个最小 scope 的 `maintenance sync`。
121
+ 2. **意图原子、派生显式**:`document apply` 创建或更新文档,并在显式类型变化时将类型、文件名前缀和确定性引用作为同一变更提交;`document move` 原子完成单文档跨 Domain 移动、目标 Profile 对齐和确定性引用修复。命令只在 MOC、关系页或索引可能变化时返回零或一个可直接执行的最小 scope `maintenance sync`。
122
122
  3. **契约声明式,变更显式**:Profile 是字段、类型、枚举、顺序和条件必填的 SSOT。Agent 提交业务值,CLI 解析有效 Profile 并拒绝猜测;已有文档只修改明确给出的字段或正文操作。
123
123
  4. **写入先证明安全**:写命令默认预览,显式确认后才提交;提交时在锁内复核快照或期望哈希,多文件变更作为一个 ChangeSet 执行,失败回滚,避免静默覆盖和部分写入。
124
124
  5. **人类与 Agent 共用一个契约**:命令和结果只有一套语义。JSON 状态、issues、missing fields 与 follow-up 供 Agent 稳定消费,`tree` 和分层 `-h` 供人类与 Agent 渐进发现,不维护第二套参数目录。
@@ -128,7 +128,7 @@ Campfire 不是“Markdown 版 kubectl”,而是面向人机协作场景组合
128
128
 
129
129
  横切关注点与业务 SOP 不使用同一种复用手段。哈希复核、写锁、原子替换和失败恢复由显式 ChangeSet Executor 复用;写命令返回的 `follow_up` 由 Skill 消费。不为了复用 Maintenance 而引入 AOP 切面、命令总线、全局钩子或隐式中间件。
130
130
 
131
- 因此,`document apply` 的准确含义是“对一个 Markdown 文档应用经 Profile 校验的显式意图”,不是“把完整声明持续调谐到某个服务端状态”。用户或 Agent 可以直接 edit 已有正文;无论通过 apply 还是 edit 写入,跨文档派生结果都由后续显式 Maintenance 收敛。
131
+ 因此,`document apply` 的准确含义是“对一个 Markdown 文档应用经 Profile 校验的显式意图”,不是“把完整声明持续调谐到某个服务端状态”。创建时文件名可省略类型前缀;显式变更类型时,CLI 负责同步类型、前缀和可确定引用。用户或 Agent 可以直接 edit 唯一路径下的已有正文;只有新建文档、修改 Frontmatter/类型/归属或需要派生治理时才进入 CLI 工作流。
132
132
 
133
133
  ## 5. SSOT 与派生数据
134
134
 
@@ -46,4 +46,4 @@ campfire maintenance archive apply [--scope <path>] --confirm
46
46
 
47
47
  Space、Domain、Project 的 `create`/`adopt`,以及文档移动、重构、归档写操作默认先预览;审查通过后才追加 `--confirm`。已注册 Workspace 只通过根级 `campfire --workspace <id> ...` 选择;已声明 Space、Domain 和 Project 使用稳定 ID,CLI 自行推导路径和父子关系。路径参数只用于新位置、具体文件、外部输入和显式扫描范围。
48
48
 
49
- `document apply` 可新建、更新或为已有正文补齐 Frontmatter;`document move` 可在任意已声明 Domain 之间移动单篇文档并对齐可确定的 `domain`/`project`。目标 Profile 缺少语义字段时返回 `missing_fields`,调用方用 `--set`/`--unset` 补齐。只有实际写入成功的结果才可返回一个实际需要的 scoped `maintenance sync` follow-up;预览、阻塞或无派生影响时返回空列表。具体参数以对应命令的 `-h` 为准。
49
+ `document apply` 可新建、更新、为已有正文补齐 Frontmatter,或通过显式 `--type` 原子转换文档类型。创建和类型转换时,CLI 根据文档类型推导文件名前缀,并在结果的 `target` 中返回最终路径;调用方只提交标题或现有路径,不手工同步类型与前缀。`document move` 可在任意已声明 Domain 之间移动单篇文档并对齐可确定的 `domain`/`project`。目标 Profile 缺少语义字段时返回 `missing_fields`,调用方补齐明确业务值。只有实际写入成功的结果才可返回零或一个、且可直接执行的 scoped `maintenance sync` follow-up;Domain 内部路径会归一化为对应 Domain,预览、阻塞或无派生影响时返回空列表。具体参数以对应命令的 `-h` 为准。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: campfire-cli
3
- Version: 0.1.9
3
+ Version: 0.1.10
4
4
  Summary: Local-first shared context and governance CLI for humans and agents
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -94,7 +94,7 @@ campfire maintenance sync [--dry-run] [--scope] # 刷新 MOC 与相关文档页
94
94
  campfire maintenance archive check / apply # 归档候选检查与执行
95
95
 
96
96
  campfire document inspect / check / format # 单篇文档查看、校验、格式化
97
- campfire document apply / move # 单篇文档创建更新、跨 Domain 移动
97
+ campfire document apply / move # 创建更新、类型转换、跨 Domain 移动
98
98
  campfire document profile list / show / resolve # Frontmatter Profile 规则
99
99
  campfire document type list # 文档类型与前缀
100
100
 
@@ -139,12 +139,12 @@ campfire workspace restructure verify --batch move-001
139
139
  - **校验分工**:`maintenance check` 统一负责正式文档的 Schema 与枚举校验,并刷新可重建索引;结构声明由 `workspace space/domain check` 校验;`maintenance sync` 只因结构、MOC、路径或并发安全问题阻塞,单篇文档问题不阻止其他领域刷新。
140
140
  - **Frontmatter Profile**:声明式一层继承(`base` 或 `base → knowledge/project-doc/task`),`document profile show` 展示编译后的完整规则;Formatter 只按有效 Profile 排序并保留值,不允许字段由 Validator 报告、不自动删除。
141
141
  - **并发与提交安全**:写入前在治理锁内复核内容哈希,外部变化返回 `concurrent-change` / `source-hash-changed`,拒绝覆盖;多文件写入和路径移动经同一 ChangeSet 提交,失败恢复到执行前。
142
- - **按需后续治理**:写入命令只在实际写入成功且派生内容可能变化时返回一个最小 scope 的 `maintenance sync`;预览和阻塞结果的 `follow_up` 为空。Skill 消费该结果,没有 follow-up 就结束,不固定追加 dry-run 或全量 check。
142
+ - **按需后续治理**:写入命令只在实际写入成功且派生内容可能变化时返回零或一个、且可直接执行的最小 scope `maintenance sync`;预览和阻塞结果的 `follow_up` 为空。位于 Domain 内部的 scope 由 CLI 归一化为有效 Domain。Skill 消费该结果,没有 follow-up 就结束,不固定追加 dry-run 或全量 check。
143
143
  - **配置两层模型**:产品默认契约在包内 `resources/defaults/config.yml`(SSOT),用户只在 `~/.campfire/config.yml` 写覆盖项;Mapping 递归合并,`campfire workspace config check` 验证有效配置。
144
144
 
145
145
  ## Agent 协作
146
146
 
147
- 全局 Skill(`campfire skill list` 查看托管清单,`campfire skill sync` 手动同步)定义了 Agent 的标准工作流:首次读写先 `workspace resolve` 并读取 Workspace 的 `AGENTS.md`;单文档用 `document apply/move`,批量结构调整用 `workspace restructure`,归档用 `maintenance archive`。有歧义的分类和重构进入 Decision,不由 Agent 擅自决定。
147
+ 全局 Skill(`campfire skill list` 查看托管清单,`campfire skill sync` 手动同步)定义了 Agent 的标准工作流:已给出唯一文件路径的正文读取或小改直接使用文件工具;新建文档、修改 Frontmatter/类型/归属或执行结构治理时才加载 bootstrap,并使用 `document apply/move` 等原子命令。批量结构调整用 `workspace restructure`,归档用 `maintenance archive`。有歧义的分类和重构进入 Decision,不由 Agent 擅自决定。
148
148
 
149
149
  ## 许可
150
150
 
@@ -78,7 +78,7 @@ campfire maintenance sync [--dry-run] [--scope] # 刷新 MOC 与相关文档页
78
78
  campfire maintenance archive check / apply # 归档候选检查与执行
79
79
 
80
80
  campfire document inspect / check / format # 单篇文档查看、校验、格式化
81
- campfire document apply / move # 单篇文档创建更新、跨 Domain 移动
81
+ campfire document apply / move # 创建更新、类型转换、跨 Domain 移动
82
82
  campfire document profile list / show / resolve # Frontmatter Profile 规则
83
83
  campfire document type list # 文档类型与前缀
84
84
 
@@ -123,12 +123,12 @@ campfire workspace restructure verify --batch move-001
123
123
  - **校验分工**:`maintenance check` 统一负责正式文档的 Schema 与枚举校验,并刷新可重建索引;结构声明由 `workspace space/domain check` 校验;`maintenance sync` 只因结构、MOC、路径或并发安全问题阻塞,单篇文档问题不阻止其他领域刷新。
124
124
  - **Frontmatter Profile**:声明式一层继承(`base` 或 `base → knowledge/project-doc/task`),`document profile show` 展示编译后的完整规则;Formatter 只按有效 Profile 排序并保留值,不允许字段由 Validator 报告、不自动删除。
125
125
  - **并发与提交安全**:写入前在治理锁内复核内容哈希,外部变化返回 `concurrent-change` / `source-hash-changed`,拒绝覆盖;多文件写入和路径移动经同一 ChangeSet 提交,失败恢复到执行前。
126
- - **按需后续治理**:写入命令只在实际写入成功且派生内容可能变化时返回一个最小 scope 的 `maintenance sync`;预览和阻塞结果的 `follow_up` 为空。Skill 消费该结果,没有 follow-up 就结束,不固定追加 dry-run 或全量 check。
126
+ - **按需后续治理**:写入命令只在实际写入成功且派生内容可能变化时返回零或一个、且可直接执行的最小 scope `maintenance sync`;预览和阻塞结果的 `follow_up` 为空。位于 Domain 内部的 scope 由 CLI 归一化为有效 Domain。Skill 消费该结果,没有 follow-up 就结束,不固定追加 dry-run 或全量 check。
127
127
  - **配置两层模型**:产品默认契约在包内 `resources/defaults/config.yml`(SSOT),用户只在 `~/.campfire/config.yml` 写覆盖项;Mapping 递归合并,`campfire workspace config check` 验证有效配置。
128
128
 
129
129
  ## Agent 协作
130
130
 
131
- 全局 Skill(`campfire skill list` 查看托管清单,`campfire skill sync` 手动同步)定义了 Agent 的标准工作流:首次读写先 `workspace resolve` 并读取 Workspace 的 `AGENTS.md`;单文档用 `document apply/move`,批量结构调整用 `workspace restructure`,归档用 `maintenance archive`。有歧义的分类和重构进入 Decision,不由 Agent 擅自决定。
131
+ 全局 Skill(`campfire skill list` 查看托管清单,`campfire skill sync` 手动同步)定义了 Agent 的标准工作流:已给出唯一文件路径的正文读取或小改直接使用文件工具;新建文档、修改 Frontmatter/类型/归属或执行结构治理时才加载 bootstrap,并使用 `document apply/move` 等原子命令。批量结构调整用 `workspace restructure`,归档用 `maintenance archive`。有歧义的分类和重构进入 Decision,不由 Agent 擅自决定。
132
132
 
133
133
  ## 许可
134
134
 
@@ -107,7 +107,7 @@ Campfire 使用语义化版本:
107
107
 
108
108
  - SQLite `spaces`、`domains`、`documents` 只允许作为可重建本机投影,不得反向覆盖 Markdown SSOT。
109
109
  - `setup` 与 `maintenance check` 必须自动刷新完整拓扑和文档索引。
110
- - 写命令只在实际写入成功且派生状态可能变化时返回一个 scoped `maintenance sync` follow-up;预览和阻塞结果必须返回空列表;
110
+ - 写命令只在实际写入成功且派生状态可能变化时返回零或一个可直接执行的 scoped `maintenance sync` follow-up;Domain 内部路径必须归一化为对应 Domain,预览和阻塞结果必须返回空列表;
111
111
  调用方只执行实际返回的 follow-up,不固定追加 check 或全量扫描。
112
112
  - `workspace rebuild` 默认只预览;`--confirm` 后从 Manifest 和 Markdown 完整替换派生索引。
113
113
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "campfire-cli"
3
- version = "0.1.9"
3
+ version = "0.1.10"
4
4
  description = "Local-first shared context and governance CLI for humans and agents"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """Campfire CLI package."""
2
2
 
3
- __version__ = "0.1.9"
3
+ __version__ = "0.1.10"
@@ -102,7 +102,11 @@ def apply_document(
102
102
  expected_hash: str | None = typer.Option(None, "--expected-hash"),
103
103
  confirm: bool = typer.Option(False, "--confirm"),
104
104
  ) -> None:
105
- """预览或应用一篇 Profile 合法文档的创建与补丁。"""
105
+ """预览或应用一篇 Profile 合法文档的创建、补丁与显式类型变更。
106
+
107
+ 创建时文件名可省略类型前缀;CLI 根据 --type 返回并写入最终 target。
108
+ 已有文档的 --type 发生变化时,同一原子操作同步文件名和引用。
109
+ """
106
110
  body = body_file.read_text(encoding="utf-8") if body_file else None
107
111
  invoke(
108
112
  lambda: service(ctx).apply(
@@ -21,11 +21,13 @@ class DocumentApplyRequest(BaseModel):
21
21
  class DocumentApplyResult(BaseModel):
22
22
  status: Literal["planned", "needs-input", "blocked", "applied"]
23
23
  workspace_id: str
24
- action: Literal["create", "update"]
24
+ action: Literal["create", "update", "retype"]
25
25
  path: str
26
+ target: str
26
27
  profile: str
27
28
  expected_hash: str
28
29
  write_performed: bool = False
30
+ updated_references: list[str] = Field(default_factory=list)
29
31
  issues: list[dict[str, Any]] = Field(default_factory=list)
30
32
  missing_fields: list[str] = Field(default_factory=list)
31
33
  follow_up: list[CommandFollowUp] = Field(default_factory=list)
@@ -12,11 +12,13 @@ from campfire_cli.app.document.schema import (
12
12
  DocumentApplyRequest,
13
13
  DocumentApplyResult,
14
14
  )
15
+ from campfire_cli.app.document.service.document_relocation import prepare_document_relocation
15
16
  from campfire_cli.app.document.service.document_rule_service import DocumentRuleService
16
17
  from campfire_cli.app.document.service.frontmatter_formatter import render_patch
17
18
  from campfire_cli.app.document.service.profile_registry import ProfileRegistry
18
19
  from campfire_cli.common.documents.document_types import prefixed_name
19
20
  from campfire_cli.common.documents.domain_context import (
21
+ DomainContext,
20
22
  DomainContextError,
21
23
  resolve_domain_context,
22
24
  )
@@ -42,14 +44,16 @@ class DocumentApplyService:
42
44
  self._executor = FileChangeExecutor(settings.vault_root, settings.state_root)
43
45
 
44
46
  def apply(self, request: DocumentApplyRequest) -> DocumentApplyResult:
45
- path = safe_path(self._settings.vault_root, request.path)
46
- if path.suffix.lower() != ".md":
47
+ source = safe_path(self._settings.vault_root, request.path)
48
+ if source.suffix.lower() != ".md":
47
49
  raise ConfigurationError("document apply 目标必须是 Markdown 文件")
48
- exists = path.is_file()
49
- original = path.read_text(encoding="utf-8") if exists else ""
50
+ exists = source.is_file()
51
+ original = source.read_text(encoding="utf-8") if exists else ""
50
52
  parsed = parse_document(original)
51
53
 
52
54
  current_type = parsed.frontmatter.get("type")
55
+ if "type" in request.values:
56
+ raise GovernanceBlockedError("文档类型只能通过 --type 表达")
53
57
  document_type = request.document_type or (
54
58
  current_type if isinstance(current_type, str) else None
55
59
  )
@@ -57,24 +61,25 @@ class DocumentApplyService:
57
61
  raise ConfigurationError("创建文档时必须提供 --type")
58
62
  if document_type not in self._settings.document_types.get("types", {}):
59
63
  raise ConfigurationError(f"未知文档类型:{document_type}")
60
- if (
61
- exists
62
- and parsed.has_frontmatter
63
- and request.document_type
64
- and current_type != request.document_type
65
- ):
66
- raise GovernanceBlockedError("不允许通过 document apply 改变已有文档的 type")
67
64
 
68
- expected_name = prefixed_name(path.name, document_type, self._settings.document_types)
69
- if expected_name != path.name:
70
- raise ConfigurationError(f"文件名应为:{expected_name}")
71
- if request.values.get("type", document_type) != document_type:
72
- raise GovernanceBlockedError("--set type 与有效文档类型不一致;请使用专用重构命令")
65
+ changes_type = (
66
+ exists and request.document_type is not None and current_type != document_type
67
+ )
68
+ action = "create" if not exists else "retype" if changes_type else "update"
69
+ target = (
70
+ source.with_name(
71
+ prefixed_name(source.name, document_type, self._settings.document_types)
72
+ )
73
+ if not exists or changes_type
74
+ else source
75
+ )
76
+ target_name = target.relative_to(self._settings.vault_root).as_posix()
77
+ context = self._domain_context(target)
73
78
 
74
79
  today = date.today().isoformat()
75
80
  frontmatter = dict(parsed.frontmatter)
76
81
  if not exists or not parsed.has_frontmatter:
77
- frontmatter.update(self._creation_defaults(path, document_type, today))
82
+ frontmatter.update(self._creation_defaults(target, context, document_type, today))
78
83
  for key in ("project", "domain"):
79
84
  if key in request.values and request.values[key] != frontmatter.get(key):
80
85
  raise GovernanceBlockedError(
@@ -83,15 +88,15 @@ class DocumentApplyService:
83
88
  frontmatter.update(request.values)
84
89
  frontmatter["type"] = document_type
85
90
  frontmatter["updated"] = today
86
- profile = self._profiles.resolve(document_type, frontmatter, path)
87
- actual_hash = file_sha256(path) if exists else "missing"
91
+ profile = self._profiles.resolve(document_type, frontmatter, target)
92
+ actual_hash = file_sha256(source) if exists else "missing"
88
93
 
89
94
  unknown = sorted(set(request.values) - set(profile.allowed))
90
95
  if unknown:
91
96
  return self._result(
92
97
  request,
93
- exists,
94
- parsed.has_frontmatter,
98
+ target_name,
99
+ action,
95
100
  profile.name,
96
101
  actual_hash,
97
102
  "blocked",
@@ -107,17 +112,20 @@ class DocumentApplyService:
107
112
 
108
113
  next_body = self._next_body(request, parsed.body, exists)
109
114
  if exists and parsed.has_frontmatter:
115
+ patch = {**request.values, "updated": today}
116
+ if changes_type:
117
+ patch["type"] = document_type
110
118
  rendered, render_errors = render_patch(
111
119
  original,
112
- {**request.values, "updated": today},
120
+ patch,
113
121
  next_body,
114
122
  list(profile.field_order),
115
123
  )
116
124
  if render_errors:
117
125
  return self._result(
118
126
  request,
119
- exists,
120
- parsed.has_frontmatter,
127
+ target_name,
128
+ action,
121
129
  profile.name,
122
130
  actual_hash,
123
131
  "blocked",
@@ -125,7 +133,9 @@ class DocumentApplyService:
125
133
  )
126
134
  else:
127
135
  rendered = render_document(frontmatter, next_body, list(profile.field_order))
128
- issues = self._rules.check_content(self._settings.vault_root, path, rendered)
136
+ issues = self._rules.check_content(self._settings.vault_root, target, rendered)
137
+ if target != source and target.exists():
138
+ issues.insert(0, {"code": "target-exists", "path": target_name})
129
139
  missing_codes = {"frontmatter-field-missing", "frontmatter-field-empty"}
130
140
  for issue in issues:
131
141
  field = issue.get("field")
@@ -143,8 +153,8 @@ class DocumentApplyService:
143
153
  status = "needs-input" if missing else "blocked" if issues else "planned"
144
154
  result = self._result(
145
155
  request,
146
- exists,
147
- parsed.has_frontmatter,
156
+ target_name,
157
+ action,
148
158
  profile.name,
149
159
  actual_hash,
150
160
  status,
@@ -161,22 +171,44 @@ class DocumentApplyService:
161
171
  }
162
172
  )
163
173
 
164
- self._executor.execute(
165
- FileChangeSet(
166
- writes=(FileWrite(path, rendered),),
174
+ updated_references: list[str] = []
175
+ if exists and target != source:
176
+ writes, updated_references, expected = prepare_document_relocation(
177
+ self._settings.vault_root, source, target, rendered
178
+ )
179
+ expected[source] = actual_hash
180
+ expected[target] = None
181
+ change_set = FileChangeSet(
182
+ writes=tuple(writes),
183
+ deletes=(source,),
167
184
  label="document apply",
168
- expected={path: None if actual_hash == "missing" else actual_hash},
185
+ expected=expected,
169
186
  )
170
- )
187
+ else:
188
+ change_set = FileChangeSet(
189
+ writes=(FileWrite(target, rendered),),
190
+ label="document apply",
191
+ expected={target: None if not exists else actual_hash},
192
+ )
193
+ self._executor.execute(change_set)
171
194
  return result.model_copy(
172
195
  update={
173
196
  "status": "applied",
174
197
  "write_performed": True,
175
- "follow_up": self._follow_up(request, exists, parsed.has_frontmatter),
198
+ "updated_references": updated_references,
199
+ "follow_up": self._follow_up(
200
+ request, exists, parsed.has_frontmatter, context, changes_type
201
+ ),
176
202
  }
177
203
  )
178
204
 
179
- def _creation_defaults(self, path: Path, document_type: str, today: str) -> dict[str, Any]:
205
+ def _creation_defaults(
206
+ self,
207
+ path: Path,
208
+ context: DomainContext,
209
+ document_type: str,
210
+ today: str,
211
+ ) -> dict[str, Any]:
180
212
  prefix = self._settings.document_types["types"][document_type]["prefix"]
181
213
  values: dict[str, Any] = {
182
214
  "name": path.stem[len(prefix) :] if path.stem.startswith(prefix) else path.stem,
@@ -186,16 +218,6 @@ class DocumentApplyService:
186
218
  "updated": today,
187
219
  "tags": [],
188
220
  }
189
- try:
190
- context = resolve_domain_context(
191
- self._settings.vault_root,
192
- path,
193
- self._settings.governance.get("domain_marker", "_领域.md"),
194
- )
195
- except DomainContextError as exc:
196
- raise ConfigurationError(
197
- f"无法解析目标 Domain 上下文:{exc.code}: {exc.detail}"
198
- ) from exc
199
221
  values["domain"] = context.domain_id
200
222
  if context.project_id:
201
223
  values["project"] = context.project_id
@@ -224,8 +246,8 @@ class DocumentApplyService:
224
246
  def _result(
225
247
  self,
226
248
  request: DocumentApplyRequest,
227
- exists: bool,
228
- had_frontmatter: bool,
249
+ target: str,
250
+ action: str,
229
251
  profile: str,
230
252
  expected_hash: str,
231
253
  status: str,
@@ -235,8 +257,9 @@ class DocumentApplyService:
235
257
  return DocumentApplyResult(
236
258
  status=status,
237
259
  workspace_id=self._settings.workspace_id,
238
- action="update" if exists else "create",
260
+ action=action,
239
261
  path=request.path,
262
+ target=target,
240
263
  profile=profile,
241
264
  expected_hash=expected_hash,
242
265
  issues=issues,
@@ -248,16 +271,31 @@ class DocumentApplyService:
248
271
  request: DocumentApplyRequest,
249
272
  exists: bool,
250
273
  had_frontmatter: bool,
274
+ context: DomainContext,
275
+ changes_type: bool,
251
276
  ) -> list[CommandFollowUp]:
252
277
  derived_fields = {"name", "type", "status", "lifecycle", "domain", "project", "related"}
253
278
  needs_sync = not exists or not had_frontmatter
254
279
  needs_sync = needs_sync or bool(set(request.values) & derived_fields)
255
280
  needs_sync = needs_sync or request.body is not None
281
+ needs_sync = needs_sync or changes_type
256
282
  return (
257
283
  maintenance_sync_follow_up(
258
284
  self._settings.workspace_id,
259
- [Path(request.path).parent.as_posix()],
285
+ [context.root.relative_to(self._settings.vault_root).as_posix()],
260
286
  )
261
287
  if needs_sync
262
288
  else []
263
289
  )
290
+
291
+ def _domain_context(self, path: Path) -> DomainContext:
292
+ try:
293
+ return resolve_domain_context(
294
+ self._settings.vault_root,
295
+ path,
296
+ self._settings.governance.get("domain_marker", "_领域.md"),
297
+ )
298
+ except DomainContextError as exc:
299
+ raise ConfigurationError(
300
+ f"无法解析目标 Domain 上下文:{exc.code}: {exc.detail}"
301
+ ) from exc
@@ -3,18 +3,13 @@ from __future__ import annotations
3
3
  from datetime import date
4
4
  from pathlib import Path
5
5
  from typing import Any
6
- from urllib.parse import quote
7
6
 
8
7
  from campfire_cli.app.base.schema.operation_schema import maintenance_sync_follow_up
9
8
  from campfire_cli.app.document.schema import DocumentMoveResult
9
+ from campfire_cli.app.document.service.document_relocation import prepare_document_relocation
10
10
  from campfire_cli.app.document.service.document_rule_service import DocumentRuleService
11
11
  from campfire_cli.app.document.service.frontmatter_formatter import render_patch
12
12
  from campfire_cli.app.document.service.profile_registry import ProfileRegistry
13
- from campfire_cli.app.document.service.type_apply import (
14
- rebase_markdown_links,
15
- rewrite_markdown_links,
16
- rewrite_wikilinks,
17
- )
18
13
  from campfire_cli.common.documents.document_types import prefixed_name
19
14
  from campfire_cli.common.documents.domain_context import (
20
15
  DomainContext,
@@ -23,7 +18,7 @@ from campfire_cli.common.documents.domain_context import (
23
18
  )
24
19
  from campfire_cli.common.documents.markdown import parse_document
25
20
  from campfire_cli.common.exceptions import ConfigurationError
26
- from campfire_cli.common.filesystem import FileChangeExecutor, FileChangeSet, FileWrite, safe_path
21
+ from campfire_cli.common.filesystem import FileChangeExecutor, FileChangeSet, safe_path
27
22
  from campfire_cli.common.hashing import file_sha256
28
23
  from campfire_cli.config.settings import WorkspaceSettings
29
24
 
@@ -224,7 +219,9 @@ class DocumentMoveService:
224
219
  if issues or not confirm:
225
220
  return result
226
221
 
227
- writes, updated_references, expected = self._prepare_writes(source, target, rendered)
222
+ writes, updated_references, expected = prepare_document_relocation(
223
+ self._settings.vault_root, source, target, rendered
224
+ )
228
225
  expected[source] = actual_hash
229
226
  expected[target] = None
230
227
  self._executor.execute(
@@ -244,36 +241,6 @@ class DocumentMoveService:
244
241
  }
245
242
  )
246
243
 
247
- def _prepare_writes(
248
- self, source: Path, target: Path, moved_text: str
249
- ) -> tuple[list[FileWrite], list[str], dict[Path, str | None]]:
250
- references = self._reference_files()
251
- unique_stem = (
252
- sum(path.stem == source.stem for path in references if path.suffix == ".md") == 1
253
- )
254
- source_relative = source.relative_to(self._settings.vault_root).as_posix()
255
- target_relative = target.relative_to(self._settings.vault_root).as_posix()
256
- writes: dict[Path, str] = {}
257
- changed: list[str] = []
258
- expected = {path: file_sha256(path) for path in references}
259
- for reference in references:
260
- text = moved_text if reference == source else reference.read_text(encoding="utf-8")
261
- updated = text.replace(source_relative, target_relative).replace(
262
- quote(source_relative), quote(target_relative)
263
- )
264
- if unique_stem and source.stem != target.stem:
265
- updated = rewrite_wikilinks(updated, source.stem, target.stem)
266
- if reference.suffix.lower() == ".md":
267
- updated = rewrite_markdown_links(updated, reference, source, target)
268
- if reference == source and source.parent != target.parent:
269
- updated = rebase_markdown_links(updated, source, target)
270
- output = target if reference == source else reference
271
- if reference == source or updated != text:
272
- writes[output] = updated
273
- if reference != source and updated != text:
274
- changed.append(reference.relative_to(self._settings.vault_root).as_posix())
275
- return [FileWrite(path, text) for path, text in writes.items()], changed, expected
276
-
277
244
  def _domain_context(self, path: Path) -> tuple[DomainContext | None, str | None]:
278
245
  try:
279
246
  return (
@@ -294,15 +261,3 @@ class DocumentMoveService:
294
261
  *self._settings.governance.get("project_reserved_directories", []),
295
262
  }
296
263
  return any(part in reserved or part.startswith(".") for part in relative.parts)
297
-
298
- def _reference_files(self) -> list[Path]:
299
- ignored = {".git", ".campfire"}
300
- return sorted(
301
- path
302
- for path in self._settings.vault_root.rglob("*")
303
- if path.is_file()
304
- and path.suffix.lower() in {".md", ".canvas"}
305
- and not any(
306
- part in ignored for part in path.relative_to(self._settings.vault_root).parts
307
- )
308
- )
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from urllib.parse import quote
5
+
6
+ from campfire_cli.app.document.service.type_apply import (
7
+ rebase_markdown_links,
8
+ rewrite_markdown_links,
9
+ rewrite_wikilinks,
10
+ )
11
+ from campfire_cli.common.filesystem import FileWrite
12
+ from campfire_cli.common.hashing import file_sha256
13
+
14
+
15
+ def prepare_document_relocation(
16
+ vault_root: Path,
17
+ source: Path,
18
+ target: Path,
19
+ moved_text: str,
20
+ ) -> tuple[list[FileWrite], list[str], dict[Path, str | None]]:
21
+ """Prepare one document relocation and every deterministic reference rewrite."""
22
+
23
+ references = _reference_files(vault_root)
24
+ unique_stem = sum(
25
+ path.stem == source.stem for path in references if path.suffix == ".md"
26
+ ) == 1
27
+ source_relative = source.relative_to(vault_root).as_posix()
28
+ target_relative = target.relative_to(vault_root).as_posix()
29
+ writes: dict[Path, str] = {}
30
+ changed: list[str] = []
31
+ expected = {path: file_sha256(path) for path in references}
32
+ for reference in references:
33
+ text = moved_text if reference == source else reference.read_text(encoding="utf-8")
34
+ updated = text.replace(source_relative, target_relative).replace(
35
+ quote(source_relative), quote(target_relative)
36
+ )
37
+ if unique_stem and source.stem != target.stem:
38
+ updated = rewrite_wikilinks(updated, source.stem, target.stem)
39
+ if reference.suffix.lower() == ".md":
40
+ updated = rewrite_markdown_links(updated, reference, source, target)
41
+ if reference == source and source.parent != target.parent:
42
+ updated = rebase_markdown_links(updated, source, target)
43
+ output = target if reference == source else reference
44
+ if reference == source or updated != text:
45
+ writes[output] = updated
46
+ if reference != source and updated != text:
47
+ changed.append(reference.relative_to(vault_root).as_posix())
48
+ return [FileWrite(path, text) for path, text in writes.items()], changed, expected
49
+
50
+
51
+ def _reference_files(vault_root: Path) -> list[Path]:
52
+ ignored = {".git", ".campfire"}
53
+ return sorted(
54
+ path
55
+ for path in vault_root.rglob("*")
56
+ if path.is_file()
57
+ and path.suffix.lower() in {".md", ".canvas"}
58
+ and not any(part in ignored for part in path.relative_to(vault_root).parts)
59
+ )
@@ -153,14 +153,28 @@ class MaintenanceService:
153
153
  scope_path = (self._settings.vault_root / scope).resolve()
154
154
  if not self._is_within_workspace(scope_path):
155
155
  return self._sync_blocked("scope-outside-workspace", scope, scope)
156
- domains = [
156
+ exact = [domain for domain in domains if domain.path == scope_path]
157
+ if not exact and not scope_path.exists():
158
+ return self._sync_blocked("scope-missing", scope, scope)
159
+ containing = [domain for domain in domains if domain.path in scope_path.parents]
160
+ if exact:
161
+ scope_path = exact[0].path
162
+ elif containing:
163
+ scope_path = max(containing, key=lambda domain: len(domain.path.parts)).path
164
+ scope = scope_path.relative_to(self._settings.vault_root).as_posix()
165
+ descendants = [
157
166
  domain
158
167
  for domain in domains
159
168
  if domain.path == scope_path or scope_path in domain.path.parents
160
169
  ]
170
+ space_marker = self._settings.governance.get("space_marker", "_空间.md")
171
+ empty_governance_root = scope_path == self._settings.vault_root or (
172
+ scope_path / space_marker
173
+ ).is_file()
174
+ if not descendants and not empty_governance_root:
175
+ return self._sync_blocked("scope-unmanaged", scope, scope)
176
+ domains = descendants
161
177
  issues = [issue for issue in issues if self._path_matches_scope(issue["path"], scope)]
162
- if not domains and not scope_path.exists():
163
- return self._sync_blocked("scope-missing", scope, scope)
164
178
  if issues:
165
179
  return MaintenanceResult(
166
180
  status="blocked",
@@ -10,9 +10,9 @@
10
10
 
11
11
  `workspace config check` 统一校验包内产品默认值和当前 Workspace 的有效治理契约,包括目录与 Space 冲突、文档类型前缀、Profile 引用和继承、归档策略以及托管资源列表。
12
12
 
13
- `workspace restructure` 负责原子命令无法表达的批量文档映射、领域拆分和 Frontmatter Patch:冻结范围、生成带内容哈希的审批计划、更新引用并验证迁移事实。Domain 的 rename/move/merge/delete/rekey 直接位于 `workspace domain`;move 使用目标 Space/Domain id 推导路径,merge 一次完成内容迁移和源领域移除,delete 只删除逻辑空领域。单篇文档移动属于 `document move`,以目标 Domain id 代替目标路径。纯移动不得重写文档内容;`verify` 只验证本批次 source/target 结果。该能力属于 Workspace Service,不设独立 App,也不用于日常增量维护。
13
+ `workspace restructure` 负责原子命令无法表达的批量文档映射、领域拆分和 Frontmatter Patch:冻结范围、生成带内容哈希的审批计划、更新引用并验证迁移事实。Domain 的 rename/move/merge/delete/rekey 直接位于 `workspace domain`;move 使用目标 Space/Domain id 推导路径,merge 一次完成内容迁移和源领域移除,delete 只删除逻辑空领域。单篇文档移动属于 `document move`,以目标 Domain id 代替目标路径。无 `--spec` 时只推断可确定的文档类型与文件名前缀规范化;没有变化返回 `up-to-date`,任意路径映射或 Frontmatter Patch 必须使用已文档化的 spec。纯移动不得重写文档内容;`verify` 只验证本批次 source/target 结果。该能力属于 Workspace Service,不设独立 App,也不用于日常增量维护。
14
14
 
15
- Adoption、文档批量 Restructure 与 Domain Restructure 均使用共享 ChangeSet Executor 提交文件写入和路径移动,并在协调的 Repository 写入失败时补偿恢复。Workspace Service 不调用 Maintenance Service;实际写入成功的结果只在必要时以轻量 `follow_up` 返回一个 scoped `maintenance sync`,预览和阻塞结果不返回可执行后续。
15
+ Adoption、文档批量 Restructure 与 Domain Restructure 均使用共享 ChangeSet Executor 提交文件写入和路径移动,并在协调的 Repository 写入失败时补偿恢复。Workspace Service 不调用 Maintenance Service;实际写入成功的结果只在必要时以轻量 `follow_up` 返回零或一个可直接执行的 scoped `maintenance sync`,预览和阻塞结果不返回可执行后续。
16
16
 
17
17
  Space 是 Workspace 根下以 `_空间.md` 声明的顶级内容容器;Domain 位于 Space 内,以 `_领域.md` 声明并可任意嵌套。子 Domain 必须处于父 Domain 路径下、属于同一 Space 并继承 governance。`_收件箱` 与 `治理视图` 是系统区域,不是 Space。Maintenance 只消费本模块发现的结构,不维护第二套领域规则。
18
18
 
@@ -42,10 +42,15 @@ def plan(
42
42
  spec: Path | None = typer.Option(
43
43
  None,
44
44
  "--spec",
45
- help="YAML/JSON:operations[{source,target?,frontmatter?,reason?,approved?}]",
45
+ help="YAML/JSON 意图规格文件",
46
46
  ),
47
47
  ) -> None:
48
- """生成带源哈希且默认未审批的逐文件计划。"""
48
+ """生成带源哈希且默认未审批的逐文件计划。
49
+
50
+ 无 --spec:只规范化类型与文件名。
51
+ 移动或 Frontmatter Patch:使用 operations spec。
52
+ 完整格式见 Restructure Skill reference。
53
+ """
49
54
  emit(invoke(lambda: service(ctx).plan(batch, spec)))
50
55
 
51
56
 
@@ -318,7 +318,7 @@ class RestructureService:
318
318
  ) -> RestructureResult:
319
319
  approved_count = sum(item.approved for item in items)
320
320
  return RestructureResult(
321
- status="planned",
321
+ status="planned" if items else "up-to-date",
322
322
  batch=batch,
323
323
  item_count=len(items),
324
324
  inventory_count=inventory_count,