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.
- campfire_cli/__init__.py +3 -0
- campfire_cli/alembic/__init__.py +1 -0
- campfire_cli/alembic/env.py +37 -0
- campfire_cli/alembic/script.py.mako +22 -0
- campfire_cli/alembic/versions/001_initial_schema.py +81 -0
- campfire_cli/alembic/versions/002_workspace_project_registry.py +47 -0
- campfire_cli/alembic/versions/003_unified_workspace_state.py +113 -0
- campfire_cli/alembic/versions/004_restructure_workspace_domain.py +23 -0
- campfire_cli/alembic/versions/005_decision_channel.py +71 -0
- campfire_cli/alembic/versions/006_workspace_topology_index.py +61 -0
- campfire_cli/alembic/versions/007_workspace_adoption.py +38 -0
- campfire_cli/alembic/versions/__init__.py +1 -0
- campfire_cli/app/SPEC.md +12 -0
- campfire_cli/app/__init__.py +1 -0
- campfire_cli/app/base/SPEC.md +3 -0
- campfire_cli/app/base/__init__.py +1 -0
- campfire_cli/app/base/cli/base_cli.py +37 -0
- campfire_cli/app/base/repository/base_repository.py +17 -0
- campfire_cli/app/base/schema/base_schema.py +18 -0
- campfire_cli/app/base/service/base_service.py +129 -0
- campfire_cli/app/decision/SPEC.md +16 -0
- campfire_cli/app/decision/__init__.py +1 -0
- campfire_cli/app/decision/cli/decision_cli.py +112 -0
- campfire_cli/app/decision/repository/decision_repository.py +199 -0
- campfire_cli/app/decision/schema/decision_schema.py +56 -0
- campfire_cli/app/decision/service/decision_projection_service.py +151 -0
- campfire_cli/app/decision/service/decision_service.py +124 -0
- campfire_cli/app/document/SPEC.md +51 -0
- campfire_cli/app/document/__init__.py +1 -0
- campfire_cli/app/document/cli/__init__.py +1 -0
- campfire_cli/app/document/cli/document_cli.py +63 -0
- campfire_cli/app/document/cli/profile_cli.py +65 -0
- campfire_cli/app/document/cli/type_cli.py +49 -0
- campfire_cli/app/document/repository/__init__.py +1 -0
- campfire_cli/app/document/repository/document_profile_repository.py +17 -0
- campfire_cli/app/document/repository/document_type_repository.py +17 -0
- campfire_cli/app/document/service/__init__.py +1 -0
- campfire_cli/app/document/service/document_profile_service.py +66 -0
- campfire_cli/app/document/service/document_rule_service.py +316 -0
- campfire_cli/app/document/service/document_scanner.py +38 -0
- campfire_cli/app/document/service/document_service.py +139 -0
- campfire_cli/app/document/service/document_type_service.py +28 -0
- campfire_cli/app/document/service/frontmatter_apply.py +98 -0
- campfire_cli/app/document/service/frontmatter_formatter.py +104 -0
- campfire_cli/app/document/service/frontmatter_plan.py +75 -0
- campfire_cli/app/document/service/profile_registry.py +166 -0
- campfire_cli/app/document/service/type_apply.py +185 -0
- campfire_cli/app/document/service/type_plan.py +80 -0
- campfire_cli/app/maintenance/SPEC.md +22 -0
- campfire_cli/app/maintenance/__init__.py +1 -0
- campfire_cli/app/maintenance/cli/maintenance_cli.py +103 -0
- campfire_cli/app/maintenance/repository/maintenance_repository.py +115 -0
- campfire_cli/app/maintenance/schema/maintenance_schema.py +109 -0
- campfire_cli/app/maintenance/service/archive_service.py +255 -0
- campfire_cli/app/maintenance/service/link_service.py +91 -0
- campfire_cli/app/maintenance/service/maintenance_protocol.py +25 -0
- campfire_cli/app/maintenance/service/maintenance_service.py +461 -0
- campfire_cli/app/maintenance/service/moc_service.py +310 -0
- campfire_cli/app/maintenance/service/plan_service.py +411 -0
- campfire_cli/app/skill/SPEC.md +3 -0
- campfire_cli/app/skill/__init__.py +1 -0
- campfire_cli/app/skill/cli/skill_cli.py +44 -0
- campfire_cli/app/skill/repository/skill_repository.py +33 -0
- campfire_cli/app/skill/schema/skill_schema.py +18 -0
- campfire_cli/app/skill/service/skill_service.py +146 -0
- campfire_cli/app/workspace/SPEC.md +25 -0
- campfire_cli/app/workspace/__init__.py +1 -0
- campfire_cli/app/workspace/cli/__init__.py +1 -0
- campfire_cli/app/workspace/cli/adoption_cli.py +83 -0
- campfire_cli/app/workspace/cli/config_cli.py +30 -0
- campfire_cli/app/workspace/cli/domain_cli.py +101 -0
- campfire_cli/app/workspace/cli/project_cli.py +135 -0
- campfire_cli/app/workspace/cli/restructure_cli.py +133 -0
- campfire_cli/app/workspace/cli/space_cli.py +65 -0
- campfire_cli/app/workspace/cli/workspace_cli.py +145 -0
- campfire_cli/app/workspace/repository/__init__.py +1 -0
- campfire_cli/app/workspace/repository/adoption_repository.py +55 -0
- campfire_cli/app/workspace/repository/manifest_repository.py +38 -0
- campfire_cli/app/workspace/repository/restructure_repository.py +93 -0
- campfire_cli/app/workspace/repository/workspace_repository.py +125 -0
- campfire_cli/app/workspace/schema/__init__.py +1 -0
- campfire_cli/app/workspace/schema/adoption_schema.py +47 -0
- campfire_cli/app/workspace/schema/restructure_schema.py +64 -0
- campfire_cli/app/workspace/schema/workspace_schema.py +225 -0
- campfire_cli/app/workspace/service/__init__.py +1 -0
- campfire_cli/app/workspace/service/adoption_protocol.py +11 -0
- campfire_cli/app/workspace/service/adoption_service.py +364 -0
- campfire_cli/app/workspace/service/config_service.py +189 -0
- campfire_cli/app/workspace/service/domain_restructure_service.py +316 -0
- campfire_cli/app/workspace/service/project_service.py +389 -0
- campfire_cli/app/workspace/service/restructure_protocol.py +19 -0
- campfire_cli/app/workspace/service/restructure_service.py +392 -0
- campfire_cli/app/workspace/service/restructure_verifier.py +83 -0
- campfire_cli/app/workspace/service/structure_service.py +494 -0
- campfire_cli/app/workspace/service/workspace_protocol.py +24 -0
- campfire_cli/app/workspace/service/workspace_service.py +304 -0
- campfire_cli/common/SPEC.md +12 -0
- campfire_cli/common/__init__.py +1 -0
- campfire_cli/common/database/__init__.py +4 -0
- campfire_cli/common/database/migrations.py +18 -0
- campfire_cli/common/database/models.py +215 -0
- campfire_cli/common/database/session.py +30 -0
- campfire_cli/common/documents/__init__.py +1 -0
- campfire_cli/common/documents/document_types.py +119 -0
- campfire_cli/common/documents/frontmatter_schema.py +42 -0
- campfire_cli/common/documents/markdown.py +66 -0
- campfire_cli/common/exceptions/__init__.py +7 -0
- campfire_cli/common/exceptions/base.py +19 -0
- campfire_cli/common/filesystem/__init__.py +6 -0
- campfire_cli/common/filesystem/atomic.py +30 -0
- campfire_cli/common/filesystem/locking.py +55 -0
- campfire_cli/common/governance/__init__.py +13 -0
- campfire_cli/common/governance/issues.py +38 -0
- campfire_cli/common/governance/locking.py +31 -0
- campfire_cli/common/governance/snapshots.py +26 -0
- campfire_cli/common/hashing.py +10 -0
- campfire_cli/common/reports/__init__.py +1 -0
- campfire_cli/common/reports/json_report.py +8 -0
- campfire_cli/common/reports/markdown_report.py +29 -0
- campfire_cli/config/SPEC.md +5 -0
- campfire_cli/config/defaults.py +64 -0
- campfire_cli/config/settings.py +50 -0
- campfire_cli/container.py +130 -0
- campfire_cli/main.py +133 -0
- campfire_cli/resources/__init__.py +1 -0
- campfire_cli/resources/bases/Agent/346/211/247/350/241/214/345/267/245/344/275/234/345/217/260.base +35 -0
- campfire_cli/resources/bases/SPEC.md +3 -0
- campfire_cli/resources/bases//344/273/273/345/212/241/345/267/245/344/275/234/345/217/260.base +49 -0
- campfire_cli/resources/bases//345/206/263/347/255/226/345/267/245/344/275/234/345/217/260.base +40 -0
- campfire_cli/resources/bases//345/276/205/345/275/222/346/241/243/346/226/207/346/241/243.base +28 -0
- campfire_cli/resources/bases//351/241/271/347/233/256/346/226/207/346/241/243.base +35 -0
- campfire_cli/resources/defaults/__init__.py +1 -0
- campfire_cli/resources/defaults/config.yml +458 -0
- campfire_cli/resources/skills/SPEC.md +3 -0
- campfire_cli/resources/skills/campfire-context-bootstrap/SKILL.md +62 -0
- campfire_cli/resources/skills/campfire-context-bootstrap/agents/openai.yaml +3 -0
- campfire_cli/resources/skills/campfire-conversation-router/SKILL.md +53 -0
- campfire_cli/resources/skills/campfire-conversation-router/agents/openai.yaml +3 -0
- campfire_cli/resources/skills/campfire-conversation-router/references/inbox-handoff.md +19 -0
- campfire_cli/resources/skills/campfire-conversation-router/references/learning-intake.md +31 -0
- campfire_cli/resources/skills/campfire-conversation-router/references/task-intake.md +28 -0
- campfire_cli/resources/skills/campfire-document-capture/SKILL.md +109 -0
- campfire_cli/resources/skills/campfire-inbox-triage/SKILL.md +23 -0
- campfire_cli/resources/skills/campfire-weekly-report-writing/SKILL.md +21 -0
- campfire_cli/resources/skills/campfire-weekly-report-writing/agents/openai.yaml +3 -0
- campfire_cli/resources/skills/campfire-workspace-adoption/SKILL.md +42 -0
- campfire_cli/resources/skills/campfire-workspace-maintenance/SKILL.md +74 -0
- 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
- campfire_cli/resources/skills/campfire-workspace-restructure/SKILL.md +52 -0
- campfire_cli-0.1.0.dist-info/METADATA +141 -0
- campfire_cli-0.1.0.dist-info/RECORD +154 -0
- campfire_cli-0.1.0.dist-info/WHEEL +4 -0
- campfire_cli-0.1.0.dist-info/entry_points.txt +2 -0
- campfire_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from campfire_cli.app.decision.repository.decision_repository import SqliteDecisionRepository
|
|
7
|
+
from campfire_cli.app.decision.schema.decision_schema import (
|
|
8
|
+
DecisionListResult,
|
|
9
|
+
DecisionResult,
|
|
10
|
+
DecisionSyncResult,
|
|
11
|
+
)
|
|
12
|
+
from campfire_cli.app.decision.service.decision_projection_service import (
|
|
13
|
+
DecisionProjectionService,
|
|
14
|
+
)
|
|
15
|
+
from campfire_cli.common.exceptions import ConfigurationError
|
|
16
|
+
|
|
17
|
+
KEY_RE = re.compile(r"[a-z0-9][a-z0-9-]{0,159}")
|
|
18
|
+
STATUSES = {"pending", "answered", "closed", "cancelled"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DecisionService:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
repository: SqliteDecisionRepository,
|
|
25
|
+
projection: DecisionProjectionService,
|
|
26
|
+
) -> None:
|
|
27
|
+
self._repository = repository
|
|
28
|
+
self._projection = projection
|
|
29
|
+
|
|
30
|
+
def create(
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
key: str,
|
|
34
|
+
question: str,
|
|
35
|
+
context: str = "",
|
|
36
|
+
recommendation: str = "",
|
|
37
|
+
options: list[str] | None = None,
|
|
38
|
+
related_documents: list[str] | None = None,
|
|
39
|
+
source_type: str,
|
|
40
|
+
source_id: str | None = None,
|
|
41
|
+
session_provider: str | None = None,
|
|
42
|
+
session_id: str | None = None,
|
|
43
|
+
actor: str | None = None,
|
|
44
|
+
) -> DecisionResult:
|
|
45
|
+
if not KEY_RE.fullmatch(key):
|
|
46
|
+
raise ConfigurationError("Decision key 只能使用小写字母、数字和连字符")
|
|
47
|
+
if not question.strip() or not source_type.strip():
|
|
48
|
+
raise ConfigurationError("Decision question 和 source-type 不能为空")
|
|
49
|
+
decision = self._repository.create_or_refresh(
|
|
50
|
+
str(uuid4()),
|
|
51
|
+
{
|
|
52
|
+
"dedupe_key": key,
|
|
53
|
+
"question": question.strip(),
|
|
54
|
+
"context": context.strip(),
|
|
55
|
+
"recommendation": recommendation.strip(),
|
|
56
|
+
"options": [item.strip() for item in options or [] if item.strip()],
|
|
57
|
+
"related_documents": list(dict.fromkeys(related_documents or [])),
|
|
58
|
+
"source_type": source_type.strip(),
|
|
59
|
+
"source_id": source_id,
|
|
60
|
+
"session_provider": session_provider,
|
|
61
|
+
"session_id": session_id,
|
|
62
|
+
},
|
|
63
|
+
actor,
|
|
64
|
+
)
|
|
65
|
+
projection = self._sync_projection()
|
|
66
|
+
events = self._repository.events(decision.id)
|
|
67
|
+
return DecisionResult(
|
|
68
|
+
status="created" if len(events) == 1 else "refreshed",
|
|
69
|
+
decision=decision,
|
|
70
|
+
events=events,
|
|
71
|
+
projection_operations=projection.operations,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def list(self, status: str | None = None) -> DecisionListResult:
|
|
75
|
+
if status is not None and status not in STATUSES:
|
|
76
|
+
raise ConfigurationError(f"未知 Decision 状态:{status}")
|
|
77
|
+
return DecisionListResult(decisions=self._repository.list(status))
|
|
78
|
+
|
|
79
|
+
def show(self, decision_id: str) -> DecisionResult:
|
|
80
|
+
return DecisionResult(
|
|
81
|
+
status="ok",
|
|
82
|
+
decision=self._repository.get(decision_id),
|
|
83
|
+
events=self._repository.events(decision_id),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def answer(self, decision_id: str, answer: str, answered_by: str) -> DecisionResult:
|
|
87
|
+
if not answer.strip() or not answered_by.strip():
|
|
88
|
+
raise ConfigurationError("Decision answer 和 answered-by 不能为空")
|
|
89
|
+
decision = self._repository.answer(decision_id, answer.strip(), answered_by.strip())
|
|
90
|
+
projection = self._sync_projection()
|
|
91
|
+
return DecisionResult(
|
|
92
|
+
status="answered",
|
|
93
|
+
decision=decision,
|
|
94
|
+
events=self._repository.events(decision_id),
|
|
95
|
+
projection_operations=projection.operations,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def close(self, decision_id: str, actor: str | None = None) -> DecisionResult:
|
|
99
|
+
decision = self._repository.close(decision_id, actor)
|
|
100
|
+
projection = self._sync_projection()
|
|
101
|
+
return DecisionResult(
|
|
102
|
+
status="closed",
|
|
103
|
+
decision=decision,
|
|
104
|
+
events=self._repository.events(decision_id),
|
|
105
|
+
projection_operations=projection.operations,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def cancel(self, decision_id: str, reason: str, actor: str | None = None) -> DecisionResult:
|
|
109
|
+
if not reason.strip():
|
|
110
|
+
raise ConfigurationError("Decision cancellation reason 不能为空")
|
|
111
|
+
decision = self._repository.cancel(decision_id, reason.strip(), actor)
|
|
112
|
+
projection = self._sync_projection()
|
|
113
|
+
return DecisionResult(
|
|
114
|
+
status="cancelled",
|
|
115
|
+
decision=decision,
|
|
116
|
+
events=self._repository.events(decision_id),
|
|
117
|
+
projection_operations=projection.operations,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def sync(self) -> DecisionSyncResult:
|
|
121
|
+
return self._sync_projection()
|
|
122
|
+
|
|
123
|
+
def _sync_projection(self) -> DecisionSyncResult:
|
|
124
|
+
return self._projection.sync(self._repository.list())
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Document App SPEC
|
|
2
|
+
|
|
3
|
+
本模块承载面向用户和 Agent 的文档业务能力,包括单篇检查与格式化、类型契约、Profile 契约、命名和 Frontmatter 治理。
|
|
4
|
+
|
|
5
|
+
Document App 负责文档自身及其规则;Maintenance App 负责跨文档统一计划、执行、验收和派生内容同步;Workspace Restructure 用例负责存量结构重构。纯解析、序列化和路径安全等无业务状态的原子能力保留在 `common/documents/`。
|
|
6
|
+
|
|
7
|
+
## 当前能力
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
document/
|
|
11
|
+
├── cli/
|
|
12
|
+
│ ├── document_cli.py
|
|
13
|
+
│ ├── profile_cli.py
|
|
14
|
+
│ └── type_cli.py
|
|
15
|
+
├── service/
|
|
16
|
+
│ ├── document_profile_service.py
|
|
17
|
+
│ ├── document_type_service.py
|
|
18
|
+
│ ├── document_service.py
|
|
19
|
+
│ ├── document_rule_service.py
|
|
20
|
+
│ ├── document_scanner.py
|
|
21
|
+
│ ├── profile_registry.py
|
|
22
|
+
│ ├── type_plan.py
|
|
23
|
+
│ ├── type_apply.py
|
|
24
|
+
│ ├── frontmatter_plan.py
|
|
25
|
+
│ ├── frontmatter_apply.py
|
|
26
|
+
│ └── frontmatter_formatter.py
|
|
27
|
+
└── repository/
|
|
28
|
+
├── document_profile_repository.py
|
|
29
|
+
└── document_type_repository.py
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- `profile`:定义并解析不同文档的属性契约。
|
|
33
|
+
- `type`:检查、规划和执行单选类型与文件命名治理。
|
|
34
|
+
- `frontmatter`:检查、规划、执行和格式化文档属性。
|
|
35
|
+
- `rule`:统一解释类型、Profile、枚举与跨字段不变量。
|
|
36
|
+
- `inspect`:向 Agent 返回单篇文档的 Domain、类型、有效 Profile 和具体问题。
|
|
37
|
+
- `scanner`:统一解释受管根、忽略目录和豁免文件;Document 与 Maintenance 共用。
|
|
38
|
+
|
|
39
|
+
Document Service 可以被 Maintenance 和 Workspace Restructure 编排,但不得反向依赖它们。批量计划文件、运行记录、MOC、Base、归档和重构批次不属于 Document App。
|
|
40
|
+
|
|
41
|
+
包内 `config.yml` 的默认 Profile 与用户级 `~/.campfire/config.yml` 覆盖共同构成当前治理契约。
|
|
42
|
+
|
|
43
|
+
Profile 只允许一层 `base` 继承。Service 将声明配置编译为完整 `EffectiveProfile`;Validator、Formatter 和 CLI 必须消费同一结果,不得复制字段与枚举。
|
|
44
|
+
|
|
45
|
+
标准字段用于格式化、补全和校验。默认 `unknown_fields: preserve`:已有业务扩展字段不参与标准排序但必须原样保留,不因尚未建立专属 Profile 而制造全库噪声;确需封闭字段集合的 Profile 可显式设为 `report`。
|
|
46
|
+
|
|
47
|
+
Document 不维护配置副本,也不提供契约同步命令。升级 Python 包会更新默认契约;用户自定义类型和 Profile 只写入 `~/.campfire/config.yml`,并通过 `workspace config check` 验证。
|
|
48
|
+
|
|
49
|
+
`_空间.md`、`_领域.md` 等声明文件由 Workspace App 按结构契约校验,不是普通 Document,不得套用 base Profile。Document 的单篇命令遇到豁免文件时返回 `not-applicable` 和对应的 Workspace 检查入口。
|
|
50
|
+
|
|
51
|
+
Document Rule 必须把属性顺序错误作为正式 Issue 暴露;顺序取自同一个 Effective Profile。`document format --path <文档>` 默认只预览字段顺序变化,追加 `--confirm` 后才写入;它不新增、删除或修改属性值。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Document application."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Document CLI adapters."""
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from campfire_cli.app.document.cli.profile_cli import profile_cli
|
|
11
|
+
from campfire_cli.app.document.cli.type_cli import type_cli
|
|
12
|
+
from campfire_cli.app.document.service.document_service import DocumentService
|
|
13
|
+
from campfire_cli.app.workspace.repository.workspace_repository import SqliteWorkspaceRepository
|
|
14
|
+
from campfire_cli.app.workspace.service.workspace_service import WorkspaceService
|
|
15
|
+
from campfire_cli.common.exceptions import AppError
|
|
16
|
+
from campfire_cli.config.settings import WorkspaceSettings, campfire_home
|
|
17
|
+
|
|
18
|
+
document_cli = typer.Typer(
|
|
19
|
+
help="创建、检查和维护文档及其规则",
|
|
20
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
21
|
+
)
|
|
22
|
+
document_cli.add_typer(profile_cli, name="profile")
|
|
23
|
+
document_cli.add_typer(type_cli, name="type")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def service(ctx: typer.Context) -> DocumentService:
|
|
27
|
+
selector = ctx.find_root().params.get("workspace")
|
|
28
|
+
root = campfire_home()
|
|
29
|
+
resolution = WorkspaceService(root, SqliteWorkspaceRepository(root)).resolve(
|
|
30
|
+
selector, Path.cwd()
|
|
31
|
+
)
|
|
32
|
+
settings = WorkspaceSettings.load(resolution.workspace_id, Path(resolution.workspace))
|
|
33
|
+
return DocumentService(settings)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def invoke(operation: Callable[[], dict[str, Any]]) -> None:
|
|
37
|
+
try:
|
|
38
|
+
typer.echo(json.dumps(operation(), ensure_ascii=False, indent=2))
|
|
39
|
+
except AppError as exc:
|
|
40
|
+
typer.echo(json.dumps({"status": "error", "message": str(exc)}, ensure_ascii=False))
|
|
41
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@document_cli.command("check")
|
|
45
|
+
def check(ctx: typer.Context, path: str = typer.Option(..., "--path")) -> None:
|
|
46
|
+
"""检查一篇 Markdown 文档是否符合当前类型与 Profile 契约。"""
|
|
47
|
+
invoke(lambda: service(ctx).check(path))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@document_cli.command("inspect")
|
|
51
|
+
def inspect(ctx: typer.Context, path: str = typer.Option(..., "--path")) -> None:
|
|
52
|
+
"""返回 Agent 治理单篇文档所需的类型、Profile、领域和问题上下文。"""
|
|
53
|
+
invoke(lambda: service(ctx).inspect(path))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@document_cli.command("format")
|
|
57
|
+
def format_document(
|
|
58
|
+
ctx: typer.Context,
|
|
59
|
+
path: str = typer.Option(..., "--path"),
|
|
60
|
+
confirm: bool = typer.Option(False, "--confirm"),
|
|
61
|
+
) -> None:
|
|
62
|
+
"""预览或执行一篇文档的 Frontmatter 字段排序。"""
|
|
63
|
+
invoke(lambda: service(ctx).format(path, confirm))
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from campfire_cli.app.document.repository.document_profile_repository import (
|
|
11
|
+
DocumentProfileRepository,
|
|
12
|
+
)
|
|
13
|
+
from campfire_cli.app.document.service.document_profile_service import (
|
|
14
|
+
DocumentProfileService,
|
|
15
|
+
)
|
|
16
|
+
from campfire_cli.app.workspace.repository.workspace_repository import SqliteWorkspaceRepository
|
|
17
|
+
from campfire_cli.app.workspace.service.workspace_service import WorkspaceService
|
|
18
|
+
from campfire_cli.common.exceptions import AppError
|
|
19
|
+
from campfire_cli.config.settings import WorkspaceSettings, campfire_home
|
|
20
|
+
|
|
21
|
+
profile_cli = typer.Typer(
|
|
22
|
+
help="查看和解析文档 Profile 契约",
|
|
23
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def service(ctx: typer.Context) -> DocumentProfileService:
|
|
28
|
+
selector = ctx.find_root().params.get("workspace")
|
|
29
|
+
root = campfire_home()
|
|
30
|
+
resolution = WorkspaceService(root, SqliteWorkspaceRepository(root)).resolve(
|
|
31
|
+
selector, Path.cwd()
|
|
32
|
+
)
|
|
33
|
+
settings = WorkspaceSettings.load(resolution.workspace_id, Path(resolution.workspace))
|
|
34
|
+
return DocumentProfileService(
|
|
35
|
+
settings.workspace_id,
|
|
36
|
+
settings.vault_root,
|
|
37
|
+
settings.document_types,
|
|
38
|
+
DocumentProfileRepository(settings.state_root),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def invoke(operation: Callable[[], dict[str, Any]]) -> None:
|
|
43
|
+
try:
|
|
44
|
+
typer.echo(json.dumps(operation(), ensure_ascii=False, indent=2))
|
|
45
|
+
except AppError as exc:
|
|
46
|
+
typer.echo(json.dumps({"status": "error", "message": str(exc)}, ensure_ascii=False))
|
|
47
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@profile_cli.command("list")
|
|
51
|
+
def list_profiles(ctx: typer.Context) -> None:
|
|
52
|
+
"""列出所有有效 Profile。"""
|
|
53
|
+
invoke(lambda: service(ctx).list_profiles())
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@profile_cli.command("show")
|
|
57
|
+
def show_profile(ctx: typer.Context, name: str) -> None:
|
|
58
|
+
"""显示继承合并后的完整 Profile。"""
|
|
59
|
+
invoke(lambda: service(ctx).show_profile(name))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@profile_cli.command("resolve")
|
|
63
|
+
def resolve(ctx: typer.Context, path: str = typer.Option(..., "--path")) -> None:
|
|
64
|
+
"""解析一篇文档最终使用的 Profile。"""
|
|
65
|
+
invoke(lambda: service(ctx).resolve(path))
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from campfire_cli.app.document.repository.document_type_repository import (
|
|
11
|
+
DocumentTypeRepository,
|
|
12
|
+
)
|
|
13
|
+
from campfire_cli.app.document.service.document_type_service import DocumentTypeService
|
|
14
|
+
from campfire_cli.app.workspace.repository.workspace_repository import SqliteWorkspaceRepository
|
|
15
|
+
from campfire_cli.app.workspace.service.workspace_service import WorkspaceService
|
|
16
|
+
from campfire_cli.common.exceptions import AppError
|
|
17
|
+
from campfire_cli.config.settings import WorkspaceSettings, campfire_home
|
|
18
|
+
|
|
19
|
+
type_cli = typer.Typer(
|
|
20
|
+
help="查看文档类型与文件名前缀契约",
|
|
21
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def service(ctx: typer.Context) -> DocumentTypeService:
|
|
26
|
+
selector = ctx.find_root().params.get("workspace")
|
|
27
|
+
root = campfire_home()
|
|
28
|
+
resolution = WorkspaceService(root, SqliteWorkspaceRepository(root)).resolve(
|
|
29
|
+
selector, Path.cwd()
|
|
30
|
+
)
|
|
31
|
+
settings = WorkspaceSettings.load(resolution.workspace_id, Path(resolution.workspace))
|
|
32
|
+
return DocumentTypeService(
|
|
33
|
+
settings.workspace_id,
|
|
34
|
+
DocumentTypeRepository(settings.state_root),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def invoke(operation: Callable[[], dict[str, Any]]) -> None:
|
|
39
|
+
try:
|
|
40
|
+
typer.echo(json.dumps(operation(), ensure_ascii=False, indent=2))
|
|
41
|
+
except AppError as exc:
|
|
42
|
+
typer.echo(json.dumps({"status": "error", "message": str(exc)}, ensure_ascii=False))
|
|
43
|
+
raise typer.Exit(exc.exit_code) from exc
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@type_cli.command("list")
|
|
47
|
+
def list_types(ctx: typer.Context) -> None:
|
|
48
|
+
"""列出当前 Workspace 的单选文档类型和文件名前缀。"""
|
|
49
|
+
invoke(lambda: service(ctx).list_types())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Document repositories."""
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Persistence for the active Workspace document Profile contract."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from campfire_cli.config.defaults import effective_config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DocumentProfileRepository:
|
|
13
|
+
def __init__(self, state_root: Path) -> None:
|
|
14
|
+
self._path = state_root.parents[1] / "config.yml"
|
|
15
|
+
|
|
16
|
+
def load(self) -> dict[str, Any]:
|
|
17
|
+
return deepcopy(effective_config(self._path)["frontmatter_schema"])
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Persistence for the active Workspace document type contract."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from campfire_cli.config.defaults import effective_config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DocumentTypeRepository:
|
|
13
|
+
def __init__(self, state_root: Path) -> None:
|
|
14
|
+
self._path = state_root.parents[1] / "config.yml"
|
|
15
|
+
|
|
16
|
+
def load(self) -> dict[str, Any]:
|
|
17
|
+
return deepcopy(effective_config(self._path)["document_types"])
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Document application services."""
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from campfire_cli.app.document.repository.document_profile_repository import (
|
|
7
|
+
DocumentProfileRepository,
|
|
8
|
+
)
|
|
9
|
+
from campfire_cli.app.document.service.profile_registry import ProfileRegistry
|
|
10
|
+
from campfire_cli.common.documents.markdown import parse_document
|
|
11
|
+
from campfire_cli.common.exceptions import ConfigurationError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DocumentProfileService:
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
workspace_id: str,
|
|
18
|
+
vault_root: Path,
|
|
19
|
+
type_config: dict[str, Any],
|
|
20
|
+
repository: DocumentProfileRepository,
|
|
21
|
+
) -> None:
|
|
22
|
+
self._workspace_id = workspace_id
|
|
23
|
+
self._vault_root = vault_root
|
|
24
|
+
self._type_config = type_config
|
|
25
|
+
self._repository = repository
|
|
26
|
+
|
|
27
|
+
def list_profiles(self) -> dict[str, Any]:
|
|
28
|
+
profiles = self._profiles()
|
|
29
|
+
return {
|
|
30
|
+
"status": "ok",
|
|
31
|
+
"workspace_id": self._workspace_id,
|
|
32
|
+
"profiles": [
|
|
33
|
+
{
|
|
34
|
+
"name": profile.name,
|
|
35
|
+
"field_count": len(profile.field_order),
|
|
36
|
+
"required_count": len(profile.required),
|
|
37
|
+
}
|
|
38
|
+
for profile in profiles.list()
|
|
39
|
+
],
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def show_profile(self, name: str) -> dict[str, Any]:
|
|
43
|
+
return {
|
|
44
|
+
"status": "ok",
|
|
45
|
+
"workspace_id": self._workspace_id,
|
|
46
|
+
"profile": self._profiles().get(name).model_dump(),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def resolve(self, relative_path: str) -> dict[str, Any]:
|
|
50
|
+
path = (self._vault_root / relative_path).resolve()
|
|
51
|
+
if self._vault_root != path and self._vault_root not in path.parents:
|
|
52
|
+
raise ConfigurationError("path 必须位于当前 Workspace 内")
|
|
53
|
+
if not path.is_file():
|
|
54
|
+
raise ConfigurationError(f"文档不存在:{relative_path}")
|
|
55
|
+
frontmatter = parse_document(path.read_text(encoding="utf-8")).frontmatter
|
|
56
|
+
profile = self._profiles().resolve(frontmatter.get("type"), frontmatter, path)
|
|
57
|
+
return {
|
|
58
|
+
"status": "ok",
|
|
59
|
+
"workspace_id": self._workspace_id,
|
|
60
|
+
"path": path.relative_to(self._vault_root).as_posix(),
|
|
61
|
+
"type": frontmatter.get("type"),
|
|
62
|
+
"profile": profile.model_dump(),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def _profiles(self) -> ProfileRegistry:
|
|
66
|
+
return ProfileRegistry(self._type_config, self._repository.load())
|