campfire-cli 0.1.0__py3-none-any.whl

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 (154) hide show
  1. campfire_cli/__init__.py +3 -0
  2. campfire_cli/alembic/__init__.py +1 -0
  3. campfire_cli/alembic/env.py +37 -0
  4. campfire_cli/alembic/script.py.mako +22 -0
  5. campfire_cli/alembic/versions/001_initial_schema.py +81 -0
  6. campfire_cli/alembic/versions/002_workspace_project_registry.py +47 -0
  7. campfire_cli/alembic/versions/003_unified_workspace_state.py +113 -0
  8. campfire_cli/alembic/versions/004_restructure_workspace_domain.py +23 -0
  9. campfire_cli/alembic/versions/005_decision_channel.py +71 -0
  10. campfire_cli/alembic/versions/006_workspace_topology_index.py +61 -0
  11. campfire_cli/alembic/versions/007_workspace_adoption.py +38 -0
  12. campfire_cli/alembic/versions/__init__.py +1 -0
  13. campfire_cli/app/SPEC.md +12 -0
  14. campfire_cli/app/__init__.py +1 -0
  15. campfire_cli/app/base/SPEC.md +3 -0
  16. campfire_cli/app/base/__init__.py +1 -0
  17. campfire_cli/app/base/cli/base_cli.py +37 -0
  18. campfire_cli/app/base/repository/base_repository.py +17 -0
  19. campfire_cli/app/base/schema/base_schema.py +18 -0
  20. campfire_cli/app/base/service/base_service.py +129 -0
  21. campfire_cli/app/decision/SPEC.md +16 -0
  22. campfire_cli/app/decision/__init__.py +1 -0
  23. campfire_cli/app/decision/cli/decision_cli.py +112 -0
  24. campfire_cli/app/decision/repository/decision_repository.py +199 -0
  25. campfire_cli/app/decision/schema/decision_schema.py +56 -0
  26. campfire_cli/app/decision/service/decision_projection_service.py +151 -0
  27. campfire_cli/app/decision/service/decision_service.py +124 -0
  28. campfire_cli/app/document/SPEC.md +51 -0
  29. campfire_cli/app/document/__init__.py +1 -0
  30. campfire_cli/app/document/cli/__init__.py +1 -0
  31. campfire_cli/app/document/cli/document_cli.py +63 -0
  32. campfire_cli/app/document/cli/profile_cli.py +65 -0
  33. campfire_cli/app/document/cli/type_cli.py +49 -0
  34. campfire_cli/app/document/repository/__init__.py +1 -0
  35. campfire_cli/app/document/repository/document_profile_repository.py +17 -0
  36. campfire_cli/app/document/repository/document_type_repository.py +17 -0
  37. campfire_cli/app/document/service/__init__.py +1 -0
  38. campfire_cli/app/document/service/document_profile_service.py +66 -0
  39. campfire_cli/app/document/service/document_rule_service.py +316 -0
  40. campfire_cli/app/document/service/document_scanner.py +38 -0
  41. campfire_cli/app/document/service/document_service.py +139 -0
  42. campfire_cli/app/document/service/document_type_service.py +28 -0
  43. campfire_cli/app/document/service/frontmatter_apply.py +98 -0
  44. campfire_cli/app/document/service/frontmatter_formatter.py +104 -0
  45. campfire_cli/app/document/service/frontmatter_plan.py +75 -0
  46. campfire_cli/app/document/service/profile_registry.py +166 -0
  47. campfire_cli/app/document/service/type_apply.py +185 -0
  48. campfire_cli/app/document/service/type_plan.py +80 -0
  49. campfire_cli/app/maintenance/SPEC.md +22 -0
  50. campfire_cli/app/maintenance/__init__.py +1 -0
  51. campfire_cli/app/maintenance/cli/maintenance_cli.py +103 -0
  52. campfire_cli/app/maintenance/repository/maintenance_repository.py +115 -0
  53. campfire_cli/app/maintenance/schema/maintenance_schema.py +109 -0
  54. campfire_cli/app/maintenance/service/archive_service.py +255 -0
  55. campfire_cli/app/maintenance/service/link_service.py +91 -0
  56. campfire_cli/app/maintenance/service/maintenance_protocol.py +25 -0
  57. campfire_cli/app/maintenance/service/maintenance_service.py +461 -0
  58. campfire_cli/app/maintenance/service/moc_service.py +310 -0
  59. campfire_cli/app/maintenance/service/plan_service.py +411 -0
  60. campfire_cli/app/skill/SPEC.md +3 -0
  61. campfire_cli/app/skill/__init__.py +1 -0
  62. campfire_cli/app/skill/cli/skill_cli.py +44 -0
  63. campfire_cli/app/skill/repository/skill_repository.py +33 -0
  64. campfire_cli/app/skill/schema/skill_schema.py +18 -0
  65. campfire_cli/app/skill/service/skill_service.py +146 -0
  66. campfire_cli/app/workspace/SPEC.md +25 -0
  67. campfire_cli/app/workspace/__init__.py +1 -0
  68. campfire_cli/app/workspace/cli/__init__.py +1 -0
  69. campfire_cli/app/workspace/cli/adoption_cli.py +83 -0
  70. campfire_cli/app/workspace/cli/config_cli.py +30 -0
  71. campfire_cli/app/workspace/cli/domain_cli.py +101 -0
  72. campfire_cli/app/workspace/cli/project_cli.py +135 -0
  73. campfire_cli/app/workspace/cli/restructure_cli.py +133 -0
  74. campfire_cli/app/workspace/cli/space_cli.py +65 -0
  75. campfire_cli/app/workspace/cli/workspace_cli.py +145 -0
  76. campfire_cli/app/workspace/repository/__init__.py +1 -0
  77. campfire_cli/app/workspace/repository/adoption_repository.py +55 -0
  78. campfire_cli/app/workspace/repository/manifest_repository.py +38 -0
  79. campfire_cli/app/workspace/repository/restructure_repository.py +93 -0
  80. campfire_cli/app/workspace/repository/workspace_repository.py +125 -0
  81. campfire_cli/app/workspace/schema/__init__.py +1 -0
  82. campfire_cli/app/workspace/schema/adoption_schema.py +47 -0
  83. campfire_cli/app/workspace/schema/restructure_schema.py +64 -0
  84. campfire_cli/app/workspace/schema/workspace_schema.py +225 -0
  85. campfire_cli/app/workspace/service/__init__.py +1 -0
  86. campfire_cli/app/workspace/service/adoption_protocol.py +11 -0
  87. campfire_cli/app/workspace/service/adoption_service.py +364 -0
  88. campfire_cli/app/workspace/service/config_service.py +189 -0
  89. campfire_cli/app/workspace/service/domain_restructure_service.py +316 -0
  90. campfire_cli/app/workspace/service/project_service.py +389 -0
  91. campfire_cli/app/workspace/service/restructure_protocol.py +19 -0
  92. campfire_cli/app/workspace/service/restructure_service.py +392 -0
  93. campfire_cli/app/workspace/service/restructure_verifier.py +83 -0
  94. campfire_cli/app/workspace/service/structure_service.py +494 -0
  95. campfire_cli/app/workspace/service/workspace_protocol.py +24 -0
  96. campfire_cli/app/workspace/service/workspace_service.py +304 -0
  97. campfire_cli/common/SPEC.md +12 -0
  98. campfire_cli/common/__init__.py +1 -0
  99. campfire_cli/common/database/__init__.py +4 -0
  100. campfire_cli/common/database/migrations.py +18 -0
  101. campfire_cli/common/database/models.py +215 -0
  102. campfire_cli/common/database/session.py +30 -0
  103. campfire_cli/common/documents/__init__.py +1 -0
  104. campfire_cli/common/documents/document_types.py +119 -0
  105. campfire_cli/common/documents/frontmatter_schema.py +42 -0
  106. campfire_cli/common/documents/markdown.py +66 -0
  107. campfire_cli/common/exceptions/__init__.py +7 -0
  108. campfire_cli/common/exceptions/base.py +19 -0
  109. campfire_cli/common/filesystem/__init__.py +6 -0
  110. campfire_cli/common/filesystem/atomic.py +30 -0
  111. campfire_cli/common/filesystem/locking.py +55 -0
  112. campfire_cli/common/governance/__init__.py +13 -0
  113. campfire_cli/common/governance/issues.py +38 -0
  114. campfire_cli/common/governance/locking.py +31 -0
  115. campfire_cli/common/governance/snapshots.py +26 -0
  116. campfire_cli/common/hashing.py +10 -0
  117. campfire_cli/common/reports/__init__.py +1 -0
  118. campfire_cli/common/reports/json_report.py +8 -0
  119. campfire_cli/common/reports/markdown_report.py +29 -0
  120. campfire_cli/config/SPEC.md +5 -0
  121. campfire_cli/config/defaults.py +64 -0
  122. campfire_cli/config/settings.py +50 -0
  123. campfire_cli/container.py +130 -0
  124. campfire_cli/main.py +133 -0
  125. campfire_cli/resources/__init__.py +1 -0
  126. campfire_cli/resources/bases/Agent/346/211/247/350/241/214/345/267/245/344/275/234/345/217/260.base +35 -0
  127. campfire_cli/resources/bases/SPEC.md +3 -0
  128. campfire_cli/resources/bases//344/273/273/345/212/241/345/267/245/344/275/234/345/217/260.base +49 -0
  129. campfire_cli/resources/bases//345/206/263/347/255/226/345/267/245/344/275/234/345/217/260.base +40 -0
  130. campfire_cli/resources/bases//345/276/205/345/275/222/346/241/243/346/226/207/346/241/243.base +28 -0
  131. campfire_cli/resources/bases//351/241/271/347/233/256/346/226/207/346/241/243.base +35 -0
  132. campfire_cli/resources/defaults/__init__.py +1 -0
  133. campfire_cli/resources/defaults/config.yml +458 -0
  134. campfire_cli/resources/skills/SPEC.md +3 -0
  135. campfire_cli/resources/skills/campfire-context-bootstrap/SKILL.md +62 -0
  136. campfire_cli/resources/skills/campfire-context-bootstrap/agents/openai.yaml +3 -0
  137. campfire_cli/resources/skills/campfire-conversation-router/SKILL.md +53 -0
  138. campfire_cli/resources/skills/campfire-conversation-router/agents/openai.yaml +3 -0
  139. campfire_cli/resources/skills/campfire-conversation-router/references/inbox-handoff.md +19 -0
  140. campfire_cli/resources/skills/campfire-conversation-router/references/learning-intake.md +31 -0
  141. campfire_cli/resources/skills/campfire-conversation-router/references/task-intake.md +28 -0
  142. campfire_cli/resources/skills/campfire-document-capture/SKILL.md +109 -0
  143. campfire_cli/resources/skills/campfire-inbox-triage/SKILL.md +23 -0
  144. campfire_cli/resources/skills/campfire-weekly-report-writing/SKILL.md +21 -0
  145. campfire_cli/resources/skills/campfire-weekly-report-writing/agents/openai.yaml +3 -0
  146. campfire_cli/resources/skills/campfire-workspace-adoption/SKILL.md +42 -0
  147. campfire_cli/resources/skills/campfire-workspace-maintenance/SKILL.md +74 -0
  148. 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 +23 -0
  149. campfire_cli/resources/skills/campfire-workspace-restructure/SKILL.md +52 -0
  150. campfire_cli-0.1.0.dist-info/METADATA +141 -0
  151. campfire_cli-0.1.0.dist-info/RECORD +154 -0
  152. campfire_cli-0.1.0.dist-info/WHEEL +4 -0
  153. campfire_cli-0.1.0.dist-info/entry_points.txt +2 -0
  154. campfire_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,3 @@
1
+ """Campfire CLI package."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1 @@
1
+ """Packaged Alembic migration environment."""
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from sqlalchemy import engine_from_config, pool
4
+
5
+ from alembic import context
6
+ from campfire_cli.common.database.models import Base
7
+
8
+ config = context.config
9
+ target_metadata = Base.metadata
10
+
11
+
12
+ def run_migrations_offline() -> None:
13
+ context.configure(
14
+ url=config.get_main_option("sqlalchemy.url"),
15
+ target_metadata=target_metadata,
16
+ literal_binds=True,
17
+ )
18
+ with context.begin_transaction():
19
+ context.run_migrations()
20
+
21
+
22
+ def run_migrations_online() -> None:
23
+ connectable = engine_from_config(
24
+ config.get_section(config.config_ini_section, {}),
25
+ prefix="sqlalchemy.",
26
+ poolclass=pool.NullPool,
27
+ )
28
+ with connectable.connect() as connection:
29
+ context.configure(connection=connection, target_metadata=target_metadata)
30
+ with context.begin_transaction():
31
+ context.run_migrations()
32
+
33
+
34
+ if context.is_offline_mode():
35
+ run_migrations_offline()
36
+ else:
37
+ run_migrations_online()
@@ -0,0 +1,22 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+ """
7
+ from alembic import op
8
+ import sqlalchemy as sa
9
+ ${imports if imports else ""}
10
+
11
+ revision = ${repr(up_revision)}
12
+ down_revision = ${repr(down_revision)}
13
+ branch_labels = ${repr(branch_labels)}
14
+ depends_on = ${repr(depends_on)}
15
+
16
+
17
+ def upgrade() -> None:
18
+ ${upgrades if upgrades else "pass"}
19
+
20
+
21
+ def downgrade() -> None:
22
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,81 @@
1
+ """Create Campfire state tables."""
2
+
3
+ import sqlalchemy as sa
4
+
5
+ from alembic import op
6
+
7
+ revision = "001"
8
+ down_revision = None
9
+ branch_labels = None
10
+ depends_on = None
11
+
12
+
13
+ def upgrade() -> None:
14
+ op.create_table(
15
+ "documents",
16
+ sa.Column("id", sa.Integer(), primary_key=True),
17
+ sa.Column("path", sa.Text(), nullable=False),
18
+ sa.Column("content_hash", sa.String(64), nullable=False),
19
+ sa.Column("document_type", sa.String(64)),
20
+ sa.Column("domain_id", sa.String(128)),
21
+ sa.Column("status", sa.String(32)),
22
+ sa.Column("exists", sa.Boolean(), nullable=False),
23
+ sa.Column("indexed_at", sa.DateTime(), nullable=False),
24
+ sa.UniqueConstraint("path"),
25
+ )
26
+ op.create_index("ix_documents_path", "documents", ["path"])
27
+ op.create_table(
28
+ "governance_issues",
29
+ sa.Column("id", sa.Integer(), primary_key=True),
30
+ sa.Column("path", sa.Text(), nullable=False),
31
+ sa.Column("code", sa.String(96), nullable=False),
32
+ sa.Column("detail", sa.Text(), nullable=False),
33
+ sa.Column("severity", sa.String(16), nullable=False),
34
+ sa.Column("status", sa.String(16), nullable=False),
35
+ sa.UniqueConstraint("path", "code", "detail"),
36
+ )
37
+ op.create_index("ix_governance_issues_code", "governance_issues", ["code"])
38
+ op.create_table(
39
+ "maintenance_runs",
40
+ sa.Column("id", sa.Integer(), primary_key=True),
41
+ sa.Column("run_id", sa.String(36), nullable=False, unique=True),
42
+ sa.Column("status", sa.String(24), nullable=False),
43
+ sa.Column("scanned_count", sa.Integer(), nullable=False),
44
+ sa.Column("issue_count", sa.Integer(), nullable=False),
45
+ sa.Column("started_at", sa.DateTime(), nullable=False),
46
+ sa.Column("finished_at", sa.DateTime()),
47
+ )
48
+ op.create_table(
49
+ "migration_batches",
50
+ sa.Column("id", sa.Integer(), primary_key=True),
51
+ sa.Column("batch_uuid", sa.String(36), nullable=False, unique=True),
52
+ sa.Column("batch_name", sa.String(128), nullable=False, unique=True),
53
+ sa.Column("scope", sa.Text(), nullable=False),
54
+ sa.Column("status", sa.String(24), nullable=False),
55
+ sa.Column("config_hash", sa.String(64), nullable=False),
56
+ sa.Column("created_at", sa.DateTime(), nullable=False),
57
+ )
58
+ op.create_table(
59
+ "migration_items",
60
+ sa.Column("id", sa.Integer(), primary_key=True),
61
+ sa.Column("batch_id", sa.Integer(), sa.ForeignKey("migration_batches.id"), nullable=False),
62
+ sa.Column("item_uuid", sa.String(36), nullable=False, unique=True),
63
+ sa.Column("source_path", sa.Text(), nullable=False),
64
+ sa.Column("target_path", sa.Text(), nullable=False),
65
+ sa.Column("source_hash", sa.String(64), nullable=False),
66
+ sa.Column("action", sa.String(32), nullable=False),
67
+ sa.Column("proposed_type", sa.String(64)),
68
+ sa.Column("reason", sa.Text(), nullable=False),
69
+ sa.Column("confidence", sa.String(16), nullable=False),
70
+ sa.Column("approved", sa.Boolean(), nullable=False),
71
+ sa.Column("execution_status", sa.String(24), nullable=False),
72
+ )
73
+ op.create_index("ix_migration_items_batch_id", "migration_items", ["batch_id"])
74
+
75
+
76
+ def downgrade() -> None:
77
+ op.drop_table("migration_items")
78
+ op.drop_table("migration_batches")
79
+ op.drop_table("maintenance_runs")
80
+ op.drop_table("governance_issues")
81
+ op.drop_table("documents")
@@ -0,0 +1,47 @@
1
+ """Create the SQLite-backed Workspace and Project registry."""
2
+
3
+ import sqlalchemy as sa
4
+
5
+ from alembic import op
6
+
7
+ revision = "002"
8
+ down_revision = "001"
9
+ branch_labels = None
10
+ depends_on = None
11
+
12
+
13
+ def upgrade() -> None:
14
+ op.create_table(
15
+ "workspaces",
16
+ sa.Column("id", sa.String(63), primary_key=True),
17
+ sa.Column("path", sa.Text(), nullable=False, unique=True),
18
+ sa.Column("is_default", sa.Boolean(), nullable=False),
19
+ sa.Column("status", sa.String(24), nullable=False),
20
+ sa.Column("created_at", sa.DateTime(), nullable=False),
21
+ sa.Column("updated_at", sa.DateTime(), nullable=False),
22
+ )
23
+ op.create_index("ix_workspaces_is_default", "workspaces", ["is_default"])
24
+ op.create_table(
25
+ "projects",
26
+ sa.Column("id", sa.String(63), primary_key=True),
27
+ sa.Column(
28
+ "workspace_id",
29
+ sa.String(63),
30
+ sa.ForeignKey("workspaces.id", ondelete="CASCADE"),
31
+ nullable=False,
32
+ ),
33
+ sa.Column("name", sa.String(160), nullable=False),
34
+ sa.Column("document_domain", sa.Text(), nullable=False),
35
+ sa.Column("git_remote_url", sa.Text()),
36
+ sa.Column("local_path", sa.Text()),
37
+ sa.Column("default_branch", sa.String(160)),
38
+ sa.Column("status", sa.String(24), nullable=False),
39
+ sa.Column("created_at", sa.DateTime(), nullable=False),
40
+ sa.Column("updated_at", sa.DateTime(), nullable=False),
41
+ )
42
+ op.create_index("ix_projects_workspace_id", "projects", ["workspace_id"])
43
+
44
+
45
+ def downgrade() -> None:
46
+ op.drop_table("projects")
47
+ op.drop_table("workspaces")
@@ -0,0 +1,113 @@
1
+ """Move all operational state into the workspace-scoped global database."""
2
+
3
+ import sqlalchemy as sa
4
+
5
+ from alembic import op
6
+
7
+ revision = "003"
8
+ down_revision = "002"
9
+ branch_labels = None
10
+ depends_on = None
11
+
12
+
13
+ def workspace_column() -> sa.Column:
14
+ return sa.Column(
15
+ "workspace_id",
16
+ sa.String(63),
17
+ sa.ForeignKey("workspaces.id", ondelete="CASCADE"),
18
+ nullable=False,
19
+ )
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Versions before 0.9 never wrote operational state to the global database.
24
+ # Recreate these empty legacy tables with explicit Workspace ownership.
25
+ for table in (
26
+ "migration_items",
27
+ "migration_batches",
28
+ "maintenance_runs",
29
+ "governance_issues",
30
+ "documents",
31
+ ):
32
+ op.drop_table(table)
33
+
34
+ op.create_table(
35
+ "documents",
36
+ sa.Column("id", sa.Integer(), primary_key=True),
37
+ workspace_column(),
38
+ sa.Column("path", sa.Text(), nullable=False),
39
+ sa.Column("content_hash", sa.String(64), nullable=False),
40
+ sa.Column("document_type", sa.String(64)),
41
+ sa.Column("domain_id", sa.String(128)),
42
+ sa.Column("status", sa.String(32)),
43
+ sa.Column("exists", sa.Boolean(), nullable=False),
44
+ sa.Column("indexed_at", sa.DateTime(), nullable=False),
45
+ sa.UniqueConstraint("workspace_id", "path"),
46
+ )
47
+ op.create_index("ix_documents_workspace_id", "documents", ["workspace_id"])
48
+ op.create_index("ix_documents_path", "documents", ["path"])
49
+ op.create_table(
50
+ "governance_issues",
51
+ sa.Column("id", sa.Integer(), primary_key=True),
52
+ workspace_column(),
53
+ sa.Column("path", sa.Text(), nullable=False),
54
+ sa.Column("code", sa.String(96), nullable=False),
55
+ sa.Column("detail", sa.Text(), nullable=False),
56
+ sa.Column("severity", sa.String(16), nullable=False),
57
+ sa.Column("status", sa.String(16), nullable=False),
58
+ sa.UniqueConstraint("workspace_id", "path", "code", "detail"),
59
+ )
60
+ op.create_index("ix_governance_issues_workspace_id", "governance_issues", ["workspace_id"])
61
+ op.create_index("ix_governance_issues_code", "governance_issues", ["code"])
62
+ op.create_table(
63
+ "maintenance_runs",
64
+ sa.Column("id", sa.Integer(), primary_key=True),
65
+ workspace_column(),
66
+ sa.Column("run_id", sa.String(36), nullable=False, unique=True),
67
+ sa.Column("status", sa.String(24), nullable=False),
68
+ sa.Column("scanned_count", sa.Integer(), nullable=False),
69
+ sa.Column("issue_count", sa.Integer(), nullable=False),
70
+ sa.Column("started_at", sa.DateTime(), nullable=False),
71
+ sa.Column("finished_at", sa.DateTime()),
72
+ )
73
+ op.create_index("ix_maintenance_runs_workspace_id", "maintenance_runs", ["workspace_id"])
74
+ op.create_table(
75
+ "migration_batches",
76
+ sa.Column("id", sa.Integer(), primary_key=True),
77
+ workspace_column(),
78
+ sa.Column("batch_uuid", sa.String(36), nullable=False, unique=True),
79
+ sa.Column("batch_name", sa.String(128), nullable=False),
80
+ sa.Column("scope", sa.Text(), nullable=False),
81
+ sa.Column("status", sa.String(24), nullable=False),
82
+ sa.Column("config_hash", sa.String(64), nullable=False),
83
+ sa.Column("created_at", sa.DateTime(), nullable=False),
84
+ sa.UniqueConstraint("workspace_id", "batch_name"),
85
+ )
86
+ op.create_index("ix_migration_batches_workspace_id", "migration_batches", ["workspace_id"])
87
+ op.create_table(
88
+ "migration_items",
89
+ sa.Column("id", sa.Integer(), primary_key=True),
90
+ workspace_column(),
91
+ sa.Column(
92
+ "batch_id",
93
+ sa.Integer(),
94
+ sa.ForeignKey("migration_batches.id", ondelete="CASCADE"),
95
+ nullable=False,
96
+ ),
97
+ sa.Column("item_uuid", sa.String(36), nullable=False, unique=True),
98
+ sa.Column("source_path", sa.Text(), nullable=False),
99
+ sa.Column("target_path", sa.Text(), nullable=False),
100
+ sa.Column("source_hash", sa.String(64), nullable=False),
101
+ sa.Column("action", sa.String(32), nullable=False),
102
+ sa.Column("proposed_type", sa.String(64)),
103
+ sa.Column("reason", sa.Text(), nullable=False),
104
+ sa.Column("confidence", sa.String(16), nullable=False),
105
+ sa.Column("approved", sa.Boolean(), nullable=False),
106
+ sa.Column("execution_status", sa.String(24), nullable=False),
107
+ )
108
+ op.create_index("ix_migration_items_workspace_id", "migration_items", ["workspace_id"])
109
+ op.create_index("ix_migration_items_batch_id", "migration_items", ["batch_id"])
110
+
111
+
112
+ def downgrade() -> None:
113
+ raise RuntimeError("The unified database migration is intentionally irreversible")
@@ -0,0 +1,23 @@
1
+ """Rename the former migration persistence model to Workspace restructure."""
2
+
3
+ from alembic import op
4
+
5
+ revision = "004"
6
+ down_revision = "003"
7
+ branch_labels = None
8
+ depends_on = None
9
+
10
+
11
+ def upgrade() -> None:
12
+ op.rename_table("migration_batches", "restructure_batches")
13
+ op.rename_table("migration_items", "restructure_items")
14
+ op.drop_index("ix_migration_batches_workspace_id", table_name="restructure_batches")
15
+ op.drop_index("ix_migration_items_workspace_id", table_name="restructure_items")
16
+ op.drop_index("ix_migration_items_batch_id", table_name="restructure_items")
17
+ op.create_index("ix_restructure_batches_workspace_id", "restructure_batches", ["workspace_id"])
18
+ op.create_index("ix_restructure_items_workspace_id", "restructure_items", ["workspace_id"])
19
+ op.create_index("ix_restructure_items_batch_id", "restructure_items", ["batch_id"])
20
+
21
+
22
+ def downgrade() -> None:
23
+ raise RuntimeError("The Workspace restructure rename is intentionally irreversible")
@@ -0,0 +1,71 @@
1
+ """Add persistent Decision state and append-only events."""
2
+
3
+ import sqlalchemy as sa
4
+
5
+ from alembic import op
6
+
7
+ revision = "005"
8
+ down_revision = "004"
9
+ branch_labels = None
10
+ depends_on = None
11
+
12
+
13
+ def upgrade() -> None:
14
+ op.create_table(
15
+ "decisions",
16
+ sa.Column("id", sa.String(36), primary_key=True),
17
+ sa.Column(
18
+ "workspace_id",
19
+ sa.String(63),
20
+ sa.ForeignKey("workspaces.id", ondelete="CASCADE"),
21
+ nullable=False,
22
+ ),
23
+ sa.Column("dedupe_key", sa.String(160), nullable=False),
24
+ sa.Column("question", sa.Text(), nullable=False),
25
+ sa.Column("context", sa.Text(), nullable=False),
26
+ sa.Column("recommendation", sa.Text(), nullable=False),
27
+ sa.Column("options_json", sa.Text(), nullable=False),
28
+ sa.Column("related_documents_json", sa.Text(), nullable=False),
29
+ sa.Column("source_type", sa.String(64), nullable=False),
30
+ sa.Column("source_id", sa.String(160)),
31
+ sa.Column("session_provider", sa.String(64)),
32
+ sa.Column("session_id", sa.String(200)),
33
+ sa.Column("status", sa.String(24), nullable=False),
34
+ sa.Column("answer", sa.Text()),
35
+ sa.Column("answered_by", sa.String(160)),
36
+ sa.Column("cancellation_reason", sa.Text()),
37
+ sa.Column("created_at", sa.DateTime(), nullable=False),
38
+ sa.Column("updated_at", sa.DateTime(), nullable=False),
39
+ sa.Column("answered_at", sa.DateTime()),
40
+ sa.Column("closed_at", sa.DateTime()),
41
+ sa.UniqueConstraint("workspace_id", "dedupe_key"),
42
+ )
43
+ op.create_index("ix_decisions_workspace_id", "decisions", ["workspace_id"])
44
+ op.create_index("ix_decisions_status", "decisions", ["status"])
45
+ op.create_table(
46
+ "decision_events",
47
+ sa.Column("id", sa.Integer(), primary_key=True),
48
+ sa.Column(
49
+ "workspace_id",
50
+ sa.String(63),
51
+ sa.ForeignKey("workspaces.id", ondelete="CASCADE"),
52
+ nullable=False,
53
+ ),
54
+ sa.Column(
55
+ "decision_id",
56
+ sa.String(36),
57
+ sa.ForeignKey("decisions.id", ondelete="CASCADE"),
58
+ nullable=False,
59
+ ),
60
+ sa.Column("event_type", sa.String(64), nullable=False),
61
+ sa.Column("actor", sa.String(160)),
62
+ sa.Column("payload_json", sa.Text(), nullable=False),
63
+ sa.Column("created_at", sa.DateTime(), nullable=False),
64
+ )
65
+ op.create_index("ix_decision_events_workspace_id", "decision_events", ["workspace_id"])
66
+ op.create_index("ix_decision_events_decision_id", "decision_events", ["decision_id"])
67
+ op.create_index("ix_decision_events_event_type", "decision_events", ["event_type"])
68
+
69
+
70
+ def downgrade() -> None:
71
+ raise RuntimeError("Decision history is intentionally irreversible")
@@ -0,0 +1,61 @@
1
+ """Add rebuildable Space and Domain topology indexes."""
2
+
3
+ import sqlalchemy as sa
4
+
5
+ from alembic import op
6
+
7
+ revision = "006"
8
+ down_revision = "005"
9
+ branch_labels = None
10
+ depends_on = None
11
+
12
+
13
+ def workspace_column() -> sa.Column:
14
+ return sa.Column(
15
+ "workspace_id",
16
+ sa.String(63),
17
+ sa.ForeignKey("workspaces.id", ondelete="CASCADE"),
18
+ nullable=False,
19
+ )
20
+
21
+
22
+ def upgrade() -> None:
23
+ op.create_table(
24
+ "spaces",
25
+ sa.Column("id", sa.Integer(), primary_key=True),
26
+ workspace_column(),
27
+ sa.Column("space_id", sa.String(128), nullable=False),
28
+ sa.Column("name", sa.String(160), nullable=False),
29
+ sa.Column("path", sa.Text(), nullable=False),
30
+ sa.Column("space_type", sa.String(64), nullable=False),
31
+ sa.Column("status", sa.String(24), nullable=False),
32
+ sa.Column("source_hash", sa.String(64), nullable=False),
33
+ sa.Column("indexed_at", sa.DateTime(), nullable=False),
34
+ sa.UniqueConstraint("workspace_id", "space_id"),
35
+ )
36
+ op.create_index("ix_spaces_workspace_id", "spaces", ["workspace_id"])
37
+ op.create_table(
38
+ "domains",
39
+ sa.Column("id", sa.Integer(), primary_key=True),
40
+ workspace_column(),
41
+ sa.Column("domain_id", sa.String(128), nullable=False),
42
+ sa.Column("space_id", sa.String(128), nullable=False),
43
+ sa.Column("parent_domain_id", sa.String(128)),
44
+ sa.Column("project_id", sa.String(63)),
45
+ sa.Column("name", sa.String(160), nullable=False),
46
+ sa.Column("path", sa.Text(), nullable=False),
47
+ sa.Column("domain_type", sa.String(64), nullable=False),
48
+ sa.Column("governance", sa.String(64), nullable=False),
49
+ sa.Column("moc", sa.Text(), nullable=False),
50
+ sa.Column("status", sa.String(24), nullable=False),
51
+ sa.Column("source_hash", sa.String(64), nullable=False),
52
+ sa.Column("indexed_at", sa.DateTime(), nullable=False),
53
+ sa.UniqueConstraint("workspace_id", "domain_id"),
54
+ )
55
+ op.create_index("ix_domains_workspace_id", "domains", ["workspace_id"])
56
+ op.create_index("ix_domains_space_id", "domains", ["space_id"])
57
+
58
+
59
+ def downgrade() -> None:
60
+ op.drop_table("domains")
61
+ op.drop_table("spaces")
@@ -0,0 +1,38 @@
1
+ """Add Workspace folder adoption batches."""
2
+
3
+ import sqlalchemy as sa
4
+
5
+ from alembic import op
6
+
7
+ revision = "007"
8
+ down_revision = "006"
9
+ branch_labels = None
10
+ depends_on = None
11
+
12
+
13
+ def upgrade() -> None:
14
+ op.create_table(
15
+ "adoption_batches",
16
+ sa.Column("id", sa.Integer(), primary_key=True),
17
+ sa.Column(
18
+ "workspace_id",
19
+ sa.String(63),
20
+ sa.ForeignKey("workspaces.id", ondelete="CASCADE"),
21
+ nullable=False,
22
+ ),
23
+ sa.Column("batch_name", sa.String(128), nullable=False),
24
+ sa.Column("source_path", sa.Text(), nullable=False),
25
+ sa.Column("source_kind", sa.String(16), nullable=False),
26
+ sa.Column("staging_path", sa.Text()),
27
+ sa.Column("status", sa.String(24), nullable=False),
28
+ sa.Column("inventory_json", sa.Text(), nullable=False),
29
+ sa.Column("plan_json", sa.Text()),
30
+ sa.Column("created_at", sa.DateTime(), nullable=False),
31
+ sa.Column("updated_at", sa.DateTime(), nullable=False),
32
+ sa.UniqueConstraint("workspace_id", "batch_name"),
33
+ )
34
+ op.create_index("ix_adoption_batches_workspace_id", "adoption_batches", ["workspace_id"])
35
+
36
+
37
+ def downgrade() -> None:
38
+ op.drop_table("adoption_batches")
@@ -0,0 +1 @@
1
+ """Campfire database revisions."""
@@ -0,0 +1,12 @@
1
+ # App SPEC
2
+
3
+ 业务模块采用 `CLI → Service → Protocol ← Implementation` 单向依赖。CLI 只解析参数、选择输出格式并翻译异常;Service 实现用例;Protocol 由消费方定义;Repository 负责 SQLite 或文件持久化,不作语义判断。
4
+
5
+ 当前模块:
6
+
7
+ - `document/`:文档自身、文档规则以及面向用户和 Agent 的文档命令。
8
+ - `decision/`:需要人类或高级 Agent 回答的持久判断、状态机、事件历史和只读 Vault 投影。
9
+ - `maintenance/`:新增与变更文档的持续维护,归档属于其用例。
10
+ - `workspace/`:注册、初始化、解析和选择多个 Workspace,并通过扁平的 Restructure Service 执行一次性、大规模、批次化结构重构。
11
+ - `skill/`:Campfire SOP Skill 的发现、加载、校验和同步。
12
+ - `base/`:Obsidian Base 标准治理视图。
@@ -0,0 +1 @@
1
+ """Campfire business modules."""
@@ -0,0 +1,3 @@
1
+ # Base App SPEC
2
+
3
+ 管理包内 SSOT Obsidian Base 与 Vault `治理视图/` 的确定性同步。只管理配置声明的 Base,不修改 Markdown,不删除未知或孤立 Base。
@@ -0,0 +1 @@
1
+ """Obsidian Base management application module."""
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ import typer
6
+
7
+ base_cli = typer.Typer(
8
+ help="管理 Obsidian Base 标准治理视图", context_settings={"help_option_names": ["-h", "--help"]}
9
+ )
10
+
11
+
12
+ def emit(result: object) -> None:
13
+ typer.echo(json.dumps(result.model_dump(mode="json"), ensure_ascii=False, indent=2))
14
+
15
+
16
+ @base_cli.command("list")
17
+ def list_bases(ctx: typer.Context) -> None:
18
+ """列出包内 SSOT Base 及 Vault 同步状态。"""
19
+ emit(ctx.obj.base.list())
20
+
21
+
22
+ @base_cli.command("show")
23
+ def show(ctx: typer.Context, name: str, source: str = typer.Option("ssot", "--source")) -> None:
24
+ """读取一个 Base;source 可选 ssot 或 vault。"""
25
+ emit(ctx.obj.base.show(name, source))
26
+
27
+
28
+ @base_cli.command("check")
29
+ def check(ctx: typer.Context) -> None:
30
+ """只读检查 Base 配置、YAML 和同步状态。"""
31
+ emit(ctx.obj.base.check())
32
+
33
+
34
+ @base_cli.command("sync")
35
+ def sync(ctx: typer.Context, dry_run: bool = typer.Option(False, "--dry-run")) -> None:
36
+ """将托管 Base 从包内 SSOT 确定性同步到 Vault。"""
37
+ emit(ctx.obj.base.sync(dry_run))
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib.resources import files
4
+ from pathlib import Path
5
+
6
+ from campfire_cli.common.filesystem import atomic_write
7
+
8
+
9
+ class BaseRepository:
10
+ def source_root(self) -> Path:
11
+ return Path(str(files("campfire_cli.resources") / "bases"))
12
+
13
+ def read(self, path: Path) -> str:
14
+ return path.read_text(encoding="utf-8")
15
+
16
+ def write(self, path: Path, content: str) -> None:
17
+ atomic_write(path, content)
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class BaseInfo(BaseModel):
7
+ name: str
8
+ path: str
9
+ status: str
10
+ views: list[str] = Field(default_factory=list)
11
+
12
+
13
+ class BaseResult(BaseModel):
14
+ status: str
15
+ bases: list[BaseInfo] = Field(default_factory=list)
16
+ operations: list[dict[str, str]] = Field(default_factory=list)
17
+ issues: list[dict[str, str]] = Field(default_factory=list)
18
+ content: str | None = None