@xirang-ai/dsh-teamflow 0.1.1 → 0.1.2
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.
- package/README.en.md +15 -11
- package/README.md +15 -11
- package/RELEASE.md +6 -6
- package/cordis.patch.yml +2 -0
- package/lib/client.js +179 -38
- package/lib/index.d.ts +7 -1
- package/lib/index.js +10 -4
- package/lib/remote/contract.d.ts +34 -1
- package/lib/remote/contract.js +16 -0
- package/lib/remote/service.d.ts +15 -2
- package/lib/remote/service.js +49 -6
- package/lib/requirement-discussion.d.ts +23 -0
- package/lib/requirement-discussion.js +26 -0
- package/lib/store/db.d.ts +8 -3
- package/lib/store/db.js +34 -2
- package/lib/store/docs.d.ts +9 -11
- package/lib/store/docs.js +25 -52
- package/lib/store/model.d.ts +23 -0
- package/lib/store/model.js +4 -0
- package/lib/store/paths.d.ts +20 -0
- package/lib/store/paths.js +54 -1
- package/lib/store/schema.js +71 -14
- package/lib/store/workflow.d.ts +50 -25
- package/lib/store/workflow.js +210 -97
- package/lib/tools/get-bug.js +1 -1
- package/lib/tools/get-design-doc.js +1 -1
- package/lib/tools/get-requirement.js +1 -1
- package/lib/tools/get-story.js +1 -1
- package/lib/tools/index.js +7 -0
- package/lib/tools/link-session.d.ts +3 -0
- package/lib/tools/link-session.js +87 -0
- package/locale/en.json +2 -0
- package/locale/zh.json +2 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
Teamflow (`@xirang-ai/dsh-teamflow`) is a [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) plugin that turns **requirements → high-level design → detailed design → stories → development runs → bug fixes** into a persistent, resumable pipeline. It also adds a team workbench called **Xirang Workbench** to the DSH Web UI.
|
|
6
6
|
|
|
7
|
-
Version: **`0.1.
|
|
7
|
+
Version: **`0.1.2`** (documents move into the plugin directory; stories and bugs gain discussion sessions; see [CHANGELOG.md](CHANGELOG.md)).
|
|
8
8
|
|
|
9
|
-
- Data lives in SQLite; documents live in the
|
|
9
|
+
- Data lives in SQLite; documents live in the plugin directory (default `<dataDir>/docs`), so closing a session does not lose progress.
|
|
10
10
|
- Five agent presets cover requirements, stories, bugs, development, and detailed design. Each preset controls its own tool boundary.
|
|
11
11
|
- The workbench provides lists, detail pages, a flow chart, a create page, a read-only session tab, and guarded deletion.
|
|
12
|
-
-
|
|
12
|
+
- Twenty `teamflow_*` tools write interview and development results back to the database and plugin documents.
|
|
13
13
|
|
|
14
14
|
## Requirements
|
|
15
15
|
|
|
@@ -67,7 +67,7 @@ dsh plugin --profile web remove @xirang-ai/dsh-teamflow
|
|
|
67
67
|
|
|
68
68
|
- **Lists**: four views (open / in progress / ready / completed) across requirements, standalone stories, and bugs.
|
|
69
69
|
- **Requirement detail**: high-level design, detailed-design file tree, story list, execution flow chart, linked sessions, and a properties panel.
|
|
70
|
-
- **Story / bug detail**: body, status, run history, and manual actions such as run, cancel, and complete.
|
|
70
|
+
- **Story / bug detail**: body, status, run history, discussion sessions, and manual actions such as discuss, run, cancel, and complete.
|
|
71
71
|
- **Three permission tiers**: the **Run** button on a story row, the story page, and the bug page has an arrow menu for **Normal run / Read-only run / Full permission run**. The main button always starts a normal run; the choice is not remembered, so picking full permission once does not carry over. The session starts with the matching sandbox mode and approval policy, and its header shows a short Normal / Read-only / Full badge (sessions started before this feature have no record and show no badge).
|
|
72
72
|
- **Create page**: pick a type and workspace to start a session without choosing an agent manually. Files can be attached too: the attachment card is both a click target and a drop zone, and a drop goes through the same rules as a picker selection (images are checked against the same rules the session composer uses — type, per-file size, count and total taken from `ctx.attachments.imageLimits`, never a second list defined here; other file types carry no extra limits), with a reason for every refused file. Each picked file can be removed, and the submission sends them as attachments on the new conversation's first message next to the workspace and description. The submit button is held back until uploading finishes so no attachment is lost.
|
|
73
73
|
- **Composer type picker**: an empty session switches between requirement / story / bug; a discussion session started from a requirement detail also offers requirement & outline / detailed design / story splitting.
|
|
@@ -79,14 +79,16 @@ dsh plugin --profile web remove @xirang-ai/dsh-teamflow
|
|
|
79
79
|
| Item | Location |
|
|
80
80
|
| --- | --- |
|
|
81
81
|
| Database | `$DSH_HOME/xirang-ai/dsh-teamflow/teamflow.sqlite` |
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
|
|
|
82
|
+
| Document root | `$DSH_HOME/xirang-ai/dsh-teamflow/docs` (override with `docsDir`) |
|
|
83
|
+
| Requirement / high-level design | `<docs>/requirements/<req>/requirement.md` |
|
|
84
|
+
| Detailed design | `<docs>/requirements/<req>/docs/<doc>.md` |
|
|
85
|
+
| Story | `<docs>/stories/<story>/story.md` |
|
|
86
|
+
| Bug | `<docs>/bugs/<bug>/bug.md` |
|
|
86
87
|
|
|
87
|
-
-
|
|
88
|
-
- The schema version lives in `PRAGMA user_version` and is currently `
|
|
89
|
-
- Back up `teamflow.sqlite` and
|
|
88
|
+
- Documents live next to the database, both owned by the plugin: the user's project repository never gets a `.xirang-ai/` directory and its `.gitignore` is never rewritten. Keep the plugin directory when switching workspaces or machines.
|
|
89
|
+
- The schema version lives in `PRAGMA user_version` and is currently `17`. Migrations run at startup; a database newer than the installed plugin refuses to start.
|
|
90
|
+
- Back up `teamflow.sqlite` and `docs/` (or the configured `docsDir`) before upgrading or reinstalling.
|
|
91
|
+
- Older versions stored bodies under the workspace's `.xirang-ai/dsh-teamflow/`. Upgrading neither moves nor reads those documents; move them into the document root by hand if you still need them.
|
|
90
92
|
|
|
91
93
|
## Configuration
|
|
92
94
|
|
|
@@ -95,6 +97,7 @@ The bundle config layer provides these defaults:
|
|
|
95
97
|
| Field | Type | Default | Description |
|
|
96
98
|
| --- | --- | --- | --- |
|
|
97
99
|
| `dataDir` | string | `''` | Data directory; empty means `$DSH_HOME/xirang-ai/dsh-teamflow`. Supports `~`. |
|
|
100
|
+
| `docsDir` | string | `''` | Document root; empty means `<dataDir>/docs`. Accepts absolute paths, paths relative to the working directory, and `~/`. |
|
|
98
101
|
| `busyTimeoutMs` | number | `5000` | SQLite `busy_timeout` for concurrent writers (0–60000). |
|
|
99
102
|
|
|
100
103
|
To override them, edit the user config layer of the current profile, for example `$DSH_HOME/profiles/web/cordis.patch.yml`:
|
|
@@ -104,6 +107,7 @@ To override them, edit the user config layer of the current profile, for example
|
|
|
104
107
|
name: "@xirang-ai/dsh-teamflow"
|
|
105
108
|
config:
|
|
106
109
|
dataDir: "~/teamflow-data"
|
|
110
|
+
docsDir: "~/teamflow-docs"
|
|
107
111
|
busyTimeoutMs: 10000
|
|
108
112
|
```
|
|
109
113
|
|
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
Teamflow(`@xirang-ai/dsh-teamflow`)是 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(下称 DSH)的插件:把**需求 → 概要设计 → 详细设计 → 故事拆分 → 研发执行 → 缺陷修复**做成一条可持久化、可断点续跑的流水线,并在 Web 端提供团队工作台「息壤工作台」。
|
|
6
6
|
|
|
7
|
-
当前版本:**`0.1.
|
|
7
|
+
当前版本:**`0.1.2`**(文档迁入插件目录,故事与缺陷支持讨论会话,变更记录见 [CHANGELOG.md](CHANGELOG.md))。
|
|
8
8
|
|
|
9
|
-
- 数据落在 SQLite
|
|
9
|
+
- 数据落在 SQLite,文档落在插件目录(默认 `<dataDir>/docs`),关掉会话不丢进度。
|
|
10
10
|
- 五个 Agent 预设分别负责需求、故事、缺陷、研发和详细设计,工具边界由预设控制。
|
|
11
11
|
- 工作台提供列表、详情、流程图、创建页、会话内只读页签,以及受控的删除入口。
|
|
12
|
-
-
|
|
12
|
+
- 二十个 `teamflow_*` 工具把访谈与研发结果写回数据库和插件文档。
|
|
13
13
|
|
|
14
14
|
## 环境要求
|
|
15
15
|
|
|
@@ -67,7 +67,7 @@ dsh plugin --profile web remove @xirang-ai/dsh-teamflow
|
|
|
67
67
|
|
|
68
68
|
- **列表**:未完成 / 执行中 / 待开始 / 已完成四个视图;需求、独立故事、缺陷分组展示。
|
|
69
69
|
- **需求详情**:概设正文、详设文件树、故事列表、执行流程图、关联会话和属性面板。
|
|
70
|
-
- **故事 /
|
|
70
|
+
- **故事 / 缺陷详情**:正文、状态、运行记录、讨论会话列表,以及讨论、执行、取消、完成等人工动作。
|
|
71
71
|
- **三档权限执行**:需求详情的故事行、故事页、缺陷页三处的「执行」按钮右侧带箭头菜单,可选「普通执行 / 仅查看执行 / 完全权限执行」。主按钮直接按「普通执行」启动;默认档不记忆,上一次选「完全权限执行」也不会留到下一次。会话以对应沙箱模式与审批策略启动,会话头部显示「普通 / 仅查看 / 完全权限」短标签(旧会话没有记录,不显示)。
|
|
72
72
|
- **创建页**:选类型和工作区后直接开新会话,不需要手动挑 Agent。还可以附带文件:附件区整块可点击选择,也可以把文件直接拖进附件卡片,拖放与点选走同一套规则(图片按宿主会话上传的同一套规则校验,类型、单张大小、条数与总大小取自 `ctx.attachments.imageLimits`,不在本页另立白名单;其它类型的文件不设额外限制),不合规的文件逐条给出原因;已选文件可逐个移除,提交时与工作区、描述一起作为第一条消息的附件发进新会话。附件上传完成前提交按钮会拦住,避免丢附件。
|
|
73
73
|
- **输入框「创建类型」**:空白会话下可切换需求 / 故事 / 缺陷;从需求详情发起的讨论会话还会显示「需求与概要设计」/「详细设计」/「故事拆分」,用于继续对应的讨论。
|
|
@@ -79,14 +79,16 @@ dsh plugin --profile web remove @xirang-ai/dsh-teamflow
|
|
|
79
79
|
| 项 | 位置 |
|
|
80
80
|
| --- | --- |
|
|
81
81
|
| 数据库 | `$DSH_HOME/xirang-ai/dsh-teamflow/teamflow.sqlite` |
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
|
|
|
82
|
+
| 文档根 | `$DSH_HOME/xirang-ai/dsh-teamflow/docs`(可用 `docsDir` 改到别处) |
|
|
83
|
+
| 需求 / 概设 | `<docs>/requirements/<req>/requirement.md` |
|
|
84
|
+
| 详细设计 | `<docs>/requirements/<req>/docs/<doc>.md` |
|
|
85
|
+
| 故事 | `<docs>/stories/<story>/story.md` |
|
|
86
|
+
| 缺陷 | `<docs>/bugs/<bug>/bug.md` |
|
|
86
87
|
|
|
87
|
-
-
|
|
88
|
-
- 数据结构版本写在 `PRAGMA user_version`,当前为 `
|
|
89
|
-
-
|
|
88
|
+
- 文档与数据库同根,都归插件目录所有:用户项目仓库里不会出现 `.xirang-ai/`,也不会改写工作区 `.gitignore`。换工作区或换机器时,保留插件目录里的数据库与文档即可。
|
|
89
|
+
- 数据结构版本写在 `PRAGMA user_version`,当前为 `17`;启动时自动迁移,结构版本高于当前插件时拒绝启动。
|
|
90
|
+
- 升级或重装前备份 `teamflow.sqlite` 与 `docs/`(自定义了 `docsDir` 时备份该目录)。
|
|
91
|
+
- 旧版本把正文放在工作区 `.xirang-ai/dsh-teamflow/` 下;升级后这些文档不会被自动搬迁,也不会再按旧路径读取,需要时请手动移入文档根。
|
|
90
92
|
|
|
91
93
|
## 配置
|
|
92
94
|
|
|
@@ -95,6 +97,7 @@ dsh plugin --profile web remove @xirang-ai/dsh-teamflow
|
|
|
95
97
|
| 字段 | 类型 | 默认 | 说明 |
|
|
96
98
|
| --- | --- | --- | --- |
|
|
97
99
|
| `dataDir` | string | `''` | 数据目录;留空表示 `$DSH_HOME/xirang-ai/dsh-teamflow`,支持 `~` |
|
|
100
|
+
| `docsDir` | string | `''` | 文档根;留空表示 `<dataDir>/docs`,支持绝对路径、相对路径与 `~/` |
|
|
98
101
|
| `busyTimeoutMs` | number | `5000` | SQLite `busy_timeout`,多会话并发写时的等待上限(0–60000) |
|
|
99
102
|
|
|
100
103
|
要覆盖默认值,编辑当前 profile 的用户配置层,例如 `$DSH_HOME/profiles/web/cordis.patch.yml`:
|
|
@@ -104,6 +107,7 @@ dsh plugin --profile web remove @xirang-ai/dsh-teamflow
|
|
|
104
107
|
name: "@xirang-ai/dsh-teamflow"
|
|
105
108
|
config:
|
|
106
109
|
dataDir: "~/teamflow-data"
|
|
110
|
+
docsDir: "~/teamflow-docs"
|
|
107
111
|
busyTimeoutMs: 10000
|
|
108
112
|
```
|
|
109
113
|
|
package/RELEASE.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
| 项 | 值 |
|
|
11
11
|
| --- | --- |
|
|
12
|
-
| 目标版本号 | **0.1.
|
|
12
|
+
| 目标版本号 | **0.1.2**(用户已确认) |
|
|
13
13
|
| 发布分支 / commit | 发布时填写 |
|
|
14
14
|
| 发布日期 | 发布时填写 |
|
|
15
15
|
| 发布人 | 发布时填写 |
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
| dist-tag | `latest` |
|
|
18
18
|
|
|
19
19
|
- [ ] 版本号已由开发者明确确认,且 `package.json`、`lib/client.js` banner、README / CHANGELOG 中的版本声明一致。
|
|
20
|
-
- [ ] 发布后打 `v<version>` tag,并在 GitHub Release 写清配置、`engines.dsh` 与数据结构版本(当前 schema `
|
|
20
|
+
- [ ] 发布后打 `v<version>` tag,并在 GitHub Release 写清配置、`engines.dsh` 与数据结构版本(当前 schema `17`)。
|
|
21
21
|
|
|
22
22
|
## 1. 与 DSH 官方 tag 的兼容性核对
|
|
23
23
|
|
|
@@ -78,7 +78,7 @@ npm pack --dry-run --json
|
|
|
78
78
|
|
|
79
79
|
- [ ] 仓库内 `pnpm peers check` 无 `missing peer` / `unmet peer`。
|
|
80
80
|
- [ ] 干净 profile 里 `pnpm peers check` 对 `@deepseek-ai/dsh-*` 宿主包报 `missing peer` 属预期:这些实例由正在运行的 DSH 提供,不要为了消除警告把它们改成 optional;以真实启动成功为准。
|
|
81
|
-
- [ ] `npm pack --dry-run` 的
|
|
81
|
+
- [ ] `npm pack --dry-run` 的 95 个文件中没有 `src/`、`test/`、`docs/`、`design/`、`.tmp/`、`.devhome/`、`.xirang-ai/`、`*.sqlite*`。
|
|
82
82
|
- [ ] `prepublishOnly` 已配置为 `pnpm run verify`,干净 checkout 发布时会先构建。
|
|
83
83
|
|
|
84
84
|
## 3. 干净 profile 安装与真实启动
|
|
@@ -108,7 +108,7 @@ sqlite3 "$DSH_HOME/xirang-ai/dsh-teamflow/teamflow.sqlite" \
|
|
|
108
108
|
'PRAGMA user_version; SELECT key, value FROM meta; SELECT key, name FROM roles;'
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
- [ ] `PRAGMA user_version =
|
|
111
|
+
- [ ] `PRAGMA user_version = 17`。
|
|
112
112
|
- [ ] `meta.plugin_version` 等于 `package.json` 版本。
|
|
113
113
|
- [ ] `roles` 含 `product` / `dev`。
|
|
114
114
|
- [ ] 至少真实调用一次 `teamflow_*` 工具(含一次错误分支)。
|
|
@@ -125,7 +125,7 @@ sqlite3 "$DSH_HOME/xirang-ai/dsh-teamflow/teamflow.sqlite" \
|
|
|
125
125
|
- [ ] 开发端口统一为 `3091`(与本文件、AGENTS.md §5.1 一致)。
|
|
126
126
|
- [ ] README 中的链接均可访问;仓库根的 `docs/` 仅作为提示词一致性报告、快照、Demo 走查结论与历次发版检查留痕保留,不进入 npm 包。
|
|
127
127
|
- [ ] 配置说明指向 **profile 配置层** 的 `id: teamflow` 覆盖,而不是让用户改 `node_modules` 里的包内文件。
|
|
128
|
-
- [ ] 版本号、schema 版本、工具数量(当前
|
|
128
|
+
- [ ] 版本号、schema 版本、工具数量(当前 20 个)、预设数量(当前 5 个)与实际一致。
|
|
129
129
|
- [ ] 本文件已按本次发版更新;AGENTS.md 的阅读路径与文档索引没有死链。
|
|
130
130
|
|
|
131
131
|
## 5. 代码、敏感信息与数据卫生
|
|
@@ -157,7 +157,7 @@ find . -maxdepth 2 \( -name '*.sqlite*' -o -name '.tmp' -o -name '.devhome' -o -
|
|
|
157
157
|
- [ ] Agent 人设 / prompt 中的语言与目标用户一致;新增语言时同时补字典、`normalizeLocale` 与测试。
|
|
158
158
|
- [ ] `pnpm test` 中的 locale 测试通过。
|
|
159
159
|
|
|
160
|
-
当前核对(2026-11-11):en / zh 各
|
|
160
|
+
当前核对(2026-11-11):en / zh 各 336 个键,集合一致、无空值;en 无中文残留;`src/remote` 的错误均走 `translate()`;客户端硬编码中文只出现在注释与验收标准正则中。
|
|
161
161
|
|
|
162
162
|
## 7. Git、npm 与发布
|
|
163
163
|
|
package/cordis.patch.yml
CHANGED
package/lib/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @xirang-ai/dsh-teamflow 0.1.
|
|
1
|
+
/* @xirang-ai/dsh-teamflow 0.1.2 — generated by scripts/build.mjs, do not edit. */
|
|
2
2
|
window.__ModuleLoader__.load({
|
|
3
3
|
id: "@xirang-ai/dsh-teamflow",
|
|
4
4
|
factory: (require) => {
|
|
@@ -341,6 +341,7 @@ window.__ModuleLoader__.load({
|
|
|
341
341
|
"delete.statusMissing": "—",
|
|
342
342
|
"delete.summary": "{total} record(s), {deletable} deletable",
|
|
343
343
|
"detail.branch": "Branch",
|
|
344
|
+
"detail.bugDiscussion": "Discuss this bug in a new session",
|
|
344
345
|
"detail.bugBody": "What happened",
|
|
345
346
|
"detail.completedAt": "Completed",
|
|
346
347
|
"detail.description": "Description",
|
|
@@ -356,6 +357,7 @@ window.__ModuleLoader__.load({
|
|
|
356
357
|
"detail.discussionNewDetail": "Discuss the detailed design in a new conversation",
|
|
357
358
|
"detail.discussionNewSplit": "Split Stories in a new conversation",
|
|
358
359
|
"detail.discussionOpen": "Open",
|
|
360
|
+
"detail.storyDiscussion": "Discuss this story in a new session",
|
|
359
361
|
"detail.discussionStarting": "Creating conversation…",
|
|
360
362
|
"detail.docs": "Detailed design",
|
|
361
363
|
"detail.docsEmpty": "No detailed design written yet.",
|
|
@@ -679,6 +681,7 @@ window.__ModuleLoader__.load({
|
|
|
679
681
|
"delete.statusMissing": "—",
|
|
680
682
|
"delete.summary": "共 {total} 条,可删除 {deletable} 条",
|
|
681
683
|
"detail.branch": "分支",
|
|
684
|
+
"detail.bugDiscussion": "在新会话中进行缺陷讨论",
|
|
682
685
|
"detail.bugBody": "缺陷说明",
|
|
683
686
|
"detail.completedAt": "完成时间",
|
|
684
687
|
"detail.description": "故事说明",
|
|
@@ -694,6 +697,7 @@ window.__ModuleLoader__.load({
|
|
|
694
697
|
"detail.discussionNewDetail": "在新会话中进行详细设计讨论",
|
|
695
698
|
"detail.discussionNewSplit": "在新会话中进行故事拆分",
|
|
696
699
|
"detail.discussionOpen": "打开",
|
|
700
|
+
"detail.storyDiscussion": "在新会话中进行故事讨论",
|
|
697
701
|
"detail.discussionStarting": "正在创建会话…",
|
|
698
702
|
"detail.docs": "详细设计",
|
|
699
703
|
"detail.docsEmpty": "还没有写详细设计。",
|
|
@@ -897,6 +901,7 @@ window.__ModuleLoader__.load({
|
|
|
897
901
|
cancelStory: "cancelStory",
|
|
898
902
|
completeBug: "completeBug",
|
|
899
903
|
prepareRequirementSession: "prepareRequirementSession",
|
|
904
|
+
prepareDiscussionSession: "prepareDiscussionSession",
|
|
900
905
|
sessionPermissionTier: "sessionPermissionTier",
|
|
901
906
|
previewDelete: "previewDelete",
|
|
902
907
|
deleteRecords: "deleteRecords",
|
|
@@ -1034,6 +1039,21 @@ window.__ModuleLoader__.load({
|
|
|
1034
1039
|
]),
|
|
1035
1040
|
result: JSON_CODEC
|
|
1036
1041
|
}),
|
|
1042
|
+
Object.freeze({
|
|
1043
|
+
id: `${PACKAGE_NAME}#${METHODS.prepareDiscussionSession}`,
|
|
1044
|
+
service: SERVICE_KEY,
|
|
1045
|
+
namespace: NAMESPACE,
|
|
1046
|
+
method: METHODS.prepareDiscussionSession,
|
|
1047
|
+
invocation: { kind: "direct" },
|
|
1048
|
+
parameters: Object.freeze([
|
|
1049
|
+
parameter("subjectType", "Discussion subject: requirement, story or bug"),
|
|
1050
|
+
parameter("subjectId", "Requirement, story or defect the discussion belongs to"),
|
|
1051
|
+
parameter("sessionId", "Newly created conversation to link"),
|
|
1052
|
+
parameter("kind", "Discussion kind: discuss for stories and defects, the requirement kinds otherwise"),
|
|
1053
|
+
LOCALE_PARAMETER
|
|
1054
|
+
]),
|
|
1055
|
+
result: JSON_CODEC
|
|
1056
|
+
}),
|
|
1037
1057
|
Object.freeze({
|
|
1038
1058
|
id: `${PACKAGE_NAME}#${METHODS.sessionPermissionTier}`,
|
|
1039
1059
|
service: SERVICE_KEY,
|
|
@@ -1151,6 +1171,15 @@ window.__ModuleLoader__.load({
|
|
|
1151
1171
|
}
|
|
1152
1172
|
return ["【继续讨论需求与概要设计】", "", "我的补充:"].join("\n");
|
|
1153
1173
|
}
|
|
1174
|
+
var STORY_DISCUSSION_PRESET = "xirang-story";
|
|
1175
|
+
var BUG_DISCUSSION_PRESET = "xirang-bug";
|
|
1176
|
+
function presetForRecordDiscussion(kind) {
|
|
1177
|
+
return kind === "bug" ? BUG_DISCUSSION_PRESET : STORY_DISCUSSION_PRESET;
|
|
1178
|
+
}
|
|
1179
|
+
function buildRecordDiscussionPrompt(kind, title) {
|
|
1180
|
+
const heading = kind === "bug" ? "【继续讨论缺陷】" : "【继续讨论故事】";
|
|
1181
|
+
return [heading, title, "", "我的补充:"].join("\n");
|
|
1182
|
+
}
|
|
1154
1183
|
|
|
1155
1184
|
// src/client/composer-kind.ts
|
|
1156
1185
|
var COMPOSER_KINDS = [...CREATE_KINDS, "design", "detail", "split"];
|
|
@@ -2886,6 +2915,13 @@ window.__ModuleLoader__.load({
|
|
|
2886
2915
|
const pad = (value2) => String(value2).padStart(2, "0");
|
|
2887
2916
|
return `${pad(at.getMonth() + 1)}-${pad(at.getDate())} ${pad(at.getHours())}:${pad(at.getMinutes())}`;
|
|
2888
2917
|
}
|
|
2918
|
+
function discussionNodes(sessions) {
|
|
2919
|
+
return sessions.map((session) => ({
|
|
2920
|
+
sessionId: session.sessionId,
|
|
2921
|
+
kind: session.kind,
|
|
2922
|
+
at: session.linkedAt ?? session.preparedAt ?? session.createdAt
|
|
2923
|
+
}));
|
|
2924
|
+
}
|
|
2889
2925
|
var EMPTY_BOARD = { requirements: [], standalone: [], bugs: [] };
|
|
2890
2926
|
var STORY_RUNNING = ["developing", "self_testing"];
|
|
2891
2927
|
var STORY_DONE = ["done", "completed", "cancelled"];
|
|
@@ -3016,7 +3052,8 @@ window.__ModuleLoader__.load({
|
|
|
3016
3052
|
acceptance: parsed.acceptance,
|
|
3017
3053
|
bodyMissing: story.bodyMissing,
|
|
3018
3054
|
...last === void 0 ? {} : { lastRun: { status: last.status, at: last.at } },
|
|
3019
|
-
runs
|
|
3055
|
+
runs,
|
|
3056
|
+
sessions: discussionNodes(story.sessions)
|
|
3020
3057
|
};
|
|
3021
3058
|
if (story.requirementId === null) {
|
|
3022
3059
|
standalone.push({ kind: "story", id: story.id, workspaceId: node.workspaceId, story: node });
|
|
@@ -3037,12 +3074,8 @@ window.__ModuleLoader__.load({
|
|
|
3037
3074
|
const finished = stories.length > 0 && stories.every((story) => story.status === "completed");
|
|
3038
3075
|
const state = requirement.status === "archived" ? "done" : running ? "running" : requirement.status === "completed" || finished ? "done" : "ready";
|
|
3039
3076
|
const branch = runs.find((run) => run.branch.length > 0)?.branch;
|
|
3040
|
-
const sessions =
|
|
3041
|
-
(session) =>
|
|
3042
|
-
sessionId: session.sessionId,
|
|
3043
|
-
kind: session.kind,
|
|
3044
|
-
at: session.linkedAt ?? session.preparedAt ?? session.createdAt
|
|
3045
|
-
})
|
|
3077
|
+
const sessions = discussionNodes(
|
|
3078
|
+
snapshot.requirementSessions.filter((session) => session.requirementId === requirement.id)
|
|
3046
3079
|
);
|
|
3047
3080
|
const latestDesign = sessions.find((session) => session.kind === "design");
|
|
3048
3081
|
return {
|
|
@@ -3081,7 +3114,8 @@ window.__ModuleLoader__.load({
|
|
|
3081
3114
|
updatedAt: bug.updatedAt,
|
|
3082
3115
|
...last?.sessionId === void 0 ? {} : { sessionId: last.sessionId },
|
|
3083
3116
|
...bug.createSessionId === null ? {} : { createSessionId: bug.createSessionId },
|
|
3084
|
-
runs
|
|
3117
|
+
runs,
|
|
3118
|
+
sessions: discussionNodes(bug.sessions)
|
|
3085
3119
|
};
|
|
3086
3120
|
});
|
|
3087
3121
|
return { requirements, standalone, bugs };
|
|
@@ -14108,6 +14142,7 @@ window.__ModuleLoader__.load({
|
|
|
14108
14142
|
const [createBusy, setCreateBusy] = (0, import_react10.useState)(false);
|
|
14109
14143
|
const [createError, setCreateError] = (0, import_react10.useState)(null);
|
|
14110
14144
|
const [discussionBusy, setDiscussionBusy] = (0, import_react10.useState)(null);
|
|
14145
|
+
const [recordDiscussionBusy, setRecordDiscussionBusy] = (0, import_react10.useState)(null);
|
|
14111
14146
|
const [cleanup, setCleanup] = (0, import_react10.useState)(false);
|
|
14112
14147
|
const [selected2, setSelected] = (0, import_react10.useState)(/* @__PURE__ */ new Set());
|
|
14113
14148
|
const [confirm, setConfirm] = (0, import_react10.useState)(null);
|
|
@@ -14611,14 +14646,59 @@ window.__ModuleLoader__.load({
|
|
|
14611
14646
|
},
|
|
14612
14647
|
[data, openConversation2, showToast, t]
|
|
14613
14648
|
);
|
|
14649
|
+
const startRecordDiscussion = (0, import_react10.useCallback)(
|
|
14650
|
+
(subject) => {
|
|
14651
|
+
const open = openConversation2;
|
|
14652
|
+
if (open === void 0) {
|
|
14653
|
+
showToast(t("toast.startUnavailable"));
|
|
14654
|
+
return;
|
|
14655
|
+
}
|
|
14656
|
+
setRecordDiscussionBusy(subject.kind);
|
|
14657
|
+
open(subject.workspaceId, buildRecordDiscussionPrompt(subject.kind, subject.title), {
|
|
14658
|
+
agentPreset: presetForRecordDiscussion(subject.kind),
|
|
14659
|
+
autoSend: false,
|
|
14660
|
+
composerKind: subject.kind
|
|
14661
|
+
}).then(async (outcome) => {
|
|
14662
|
+
if (outcome.status === "unavailable") {
|
|
14663
|
+
if (mounted.current) {
|
|
14664
|
+
showToast(outcome.message ?? t("toast.discussionFailed", { title: subject.title }));
|
|
14665
|
+
}
|
|
14666
|
+
return;
|
|
14667
|
+
}
|
|
14668
|
+
if (outcome.sessionId === void 0) {
|
|
14669
|
+
if (mounted.current) {
|
|
14670
|
+
showToast(t("toast.discussionDrafted", { title: subject.title }));
|
|
14671
|
+
}
|
|
14672
|
+
return;
|
|
14673
|
+
}
|
|
14674
|
+
try {
|
|
14675
|
+
await data.prepareDiscussionSession(subject.kind, subject.id, outcome.sessionId, "discuss");
|
|
14676
|
+
if (!mounted.current) return;
|
|
14677
|
+
showToast(
|
|
14678
|
+
outcome.status === "drafted" ? t("toast.discussionDrafted", { title: subject.title }) : t("toast.discussionStarted", { title: subject.title })
|
|
14679
|
+
);
|
|
14680
|
+
} catch (failure) {
|
|
14681
|
+
if (!mounted.current) return;
|
|
14682
|
+
showToast(
|
|
14683
|
+
t("toast.discussionLinkFailed", {
|
|
14684
|
+
title: subject.title,
|
|
14685
|
+
message: failureMessage(failure)
|
|
14686
|
+
})
|
|
14687
|
+
);
|
|
14688
|
+
}
|
|
14689
|
+
}).catch(() => {
|
|
14690
|
+
if (mounted.current) showToast(t("toast.discussionFailed", { title: subject.title }));
|
|
14691
|
+
}).finally(() => {
|
|
14692
|
+
if (mounted.current) setRecordDiscussionBusy(null);
|
|
14693
|
+
});
|
|
14694
|
+
},
|
|
14695
|
+
[data, openConversation2, showToast, t]
|
|
14696
|
+
);
|
|
14614
14697
|
const requirementAction = (0, import_react10.useCallback)(
|
|
14615
14698
|
(requirement) => {
|
|
14616
14699
|
if (requirement.state === "running") {
|
|
14617
14700
|
return { label: t("row.open"), tone: "live", icon: ICONS.live };
|
|
14618
14701
|
}
|
|
14619
|
-
if (requirement.state === "done") {
|
|
14620
|
-
return { label: t("row.history"), tone: "ghost", icon: ICONS.ghost };
|
|
14621
|
-
}
|
|
14622
14702
|
const lone = loneReadyStory(requirement);
|
|
14623
14703
|
return lone === void 0 ? null : { label: t("row.start"), tone: "soft", icon: ICONS.soft };
|
|
14624
14704
|
},
|
|
@@ -14713,11 +14793,7 @@ window.__ModuleLoader__.load({
|
|
|
14713
14793
|
}
|
|
14714
14794
|
};
|
|
14715
14795
|
const storyAction = (0, import_react10.useCallback)(
|
|
14716
|
-
(story) => {
|
|
14717
|
-
if (story.state === "running") return { label: t("row.open"), tone: "live", icon: ICONS.live };
|
|
14718
|
-
if (story.state === "done") return { label: t("row.history"), tone: "ghost", icon: ICONS.ghost };
|
|
14719
|
-
return { label: t("row.start"), tone: "soft", icon: ICONS.soft };
|
|
14720
|
-
},
|
|
14796
|
+
(story) => story.state === "ready" ? { label: t("row.start"), tone: "soft", icon: ICONS.soft } : null,
|
|
14721
14797
|
[t]
|
|
14722
14798
|
);
|
|
14723
14799
|
const runStoryAction = (0, import_react10.useCallback)(
|
|
@@ -15243,7 +15319,14 @@ window.__ModuleLoader__.load({
|
|
|
15243
15319
|
onDelete: () => openDelete([{ kind: "bug", id: activeBug.id }]),
|
|
15244
15320
|
onComplete: () => setDecision({ kind: "complete-bug", bug: activeBug, busy: false, error: null }),
|
|
15245
15321
|
onStartFix: (tier) => startBugFix(activeBug, tier),
|
|
15246
|
-
onCancelFix: () => cancelBugFix(activeBug)
|
|
15322
|
+
onCancelFix: () => cancelBugFix(activeBug),
|
|
15323
|
+
onNewDiscussion: () => startRecordDiscussion({
|
|
15324
|
+
kind: "bug",
|
|
15325
|
+
id: activeBug.id,
|
|
15326
|
+
title: activeBug.title,
|
|
15327
|
+
workspaceId: activeBug.workspaceId
|
|
15328
|
+
}),
|
|
15329
|
+
discussionBusy: recordDiscussionBusy === "bug"
|
|
15247
15330
|
}
|
|
15248
15331
|
) : null,
|
|
15249
15332
|
view.kind === "story" && activeStory !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
@@ -15263,7 +15346,14 @@ window.__ModuleLoader__.load({
|
|
|
15263
15346
|
onCancelRun: () => setDecision({ kind: "cancel-run", story: activeStory, busy: false, error: null }),
|
|
15264
15347
|
onOpenSession: (sessionId) => openSession(sessionId),
|
|
15265
15348
|
onOpenPath: openFile,
|
|
15266
|
-
onMissing: (path) => showToast(t("doc.linkMissing", { path }))
|
|
15349
|
+
onMissing: (path) => showToast(t("doc.linkMissing", { path })),
|
|
15350
|
+
onNewDiscussion: () => startRecordDiscussion({
|
|
15351
|
+
kind: "story",
|
|
15352
|
+
id: activeStory.id,
|
|
15353
|
+
title: activeStory.title,
|
|
15354
|
+
workspaceId: activeStory.workspaceId
|
|
15355
|
+
}),
|
|
15356
|
+
discussionBusy: recordDiscussionBusy === "story"
|
|
15267
15357
|
}
|
|
15268
15358
|
) : null
|
|
15269
15359
|
] }) }),
|
|
@@ -16140,6 +16230,8 @@ window.__ModuleLoader__.load({
|
|
|
16140
16230
|
onComplete,
|
|
16141
16231
|
onStartFix,
|
|
16142
16232
|
onCancelFix,
|
|
16233
|
+
onNewDiscussion,
|
|
16234
|
+
discussionBusy = false,
|
|
16143
16235
|
readOnly = false
|
|
16144
16236
|
}) {
|
|
16145
16237
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
@@ -16166,27 +16258,42 @@ window.__ModuleLoader__.load({
|
|
|
16166
16258
|
readOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { className: "tf-btn tf-danger", "data-tone": "ghost", type: "button", onClick: onDelete, style: { flex: "0 0 auto" }, children: [
|
|
16167
16259
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TrashGlyph, {}),
|
|
16168
16260
|
t("delete.action")
|
|
16169
|
-
] })
|
|
16170
|
-
readOnly || bug.sessionId === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
16171
|
-
"button",
|
|
16172
|
-
{
|
|
16173
|
-
className: "tf-btn",
|
|
16174
|
-
"data-tone": "live",
|
|
16175
|
-
type: "button",
|
|
16176
|
-
onClick: () => onOpenSession(bug.sessionId),
|
|
16177
|
-
style: { flex: "0 0 auto" },
|
|
16178
|
-
children: [
|
|
16179
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatGlyph, {}),
|
|
16180
|
-
t("row.open")
|
|
16181
|
-
]
|
|
16182
|
-
}
|
|
16183
|
-
)
|
|
16261
|
+
] })
|
|
16184
16262
|
] }),
|
|
16185
16263
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tf-grid", children: [
|
|
16186
16264
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
|
|
16187
16265
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "tf-card", children: [
|
|
16188
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
16189
|
-
|
|
16266
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
16267
|
+
SectionHead,
|
|
16268
|
+
{
|
|
16269
|
+
title: t("detail.bugBody"),
|
|
16270
|
+
action: readOnly || onNewDiscussion === void 0 ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
16271
|
+
"button",
|
|
16272
|
+
{
|
|
16273
|
+
className: "tf-btn tf-btn-sm",
|
|
16274
|
+
"data-tone": "soft",
|
|
16275
|
+
type: "button",
|
|
16276
|
+
disabled: discussionBusy,
|
|
16277
|
+
onClick: onNewDiscussion,
|
|
16278
|
+
children: [
|
|
16279
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PlusGlyph, {}),
|
|
16280
|
+
discussionBusy ? t("detail.discussionStarting") : t("detail.bugDiscussion")
|
|
16281
|
+
]
|
|
16282
|
+
}
|
|
16283
|
+
)
|
|
16284
|
+
}
|
|
16285
|
+
),
|
|
16286
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "tf-doc-body", children: bug.bodyMissing ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-card-hint", children: t("body.missingGeneric") }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarkdownBody, { text: bug.content.join("\n"), t }) }),
|
|
16287
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
16288
|
+
DiscussionSessions,
|
|
16289
|
+
{
|
|
16290
|
+
t,
|
|
16291
|
+
sessions: bug.sessions,
|
|
16292
|
+
sessionTitle,
|
|
16293
|
+
onOpenSession,
|
|
16294
|
+
readOnly
|
|
16295
|
+
}
|
|
16296
|
+
)
|
|
16190
16297
|
] }),
|
|
16191
16298
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "tf-card", children: [
|
|
16192
16299
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
@@ -16262,6 +16369,8 @@ window.__ModuleLoader__.load({
|
|
|
16262
16369
|
onOpenSession,
|
|
16263
16370
|
onOpenPath,
|
|
16264
16371
|
onMissing,
|
|
16372
|
+
onNewDiscussion,
|
|
16373
|
+
discussionBusy = false,
|
|
16265
16374
|
readOnly = false
|
|
16266
16375
|
}) {
|
|
16267
16376
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
@@ -16311,7 +16420,26 @@ window.__ModuleLoader__.load({
|
|
|
16311
16420
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tf-grid", children: [
|
|
16312
16421
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
|
|
16313
16422
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "tf-card", children: [
|
|
16314
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
16423
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
16424
|
+
SectionHead,
|
|
16425
|
+
{
|
|
16426
|
+
title: t("detail.storyBody"),
|
|
16427
|
+
action: readOnly || onNewDiscussion === void 0 ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
16428
|
+
"button",
|
|
16429
|
+
{
|
|
16430
|
+
className: "tf-btn tf-btn-sm",
|
|
16431
|
+
"data-tone": "soft",
|
|
16432
|
+
type: "button",
|
|
16433
|
+
disabled: discussionBusy,
|
|
16434
|
+
onClick: onNewDiscussion,
|
|
16435
|
+
children: [
|
|
16436
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PlusGlyph, {}),
|
|
16437
|
+
discussionBusy ? t("detail.discussionStarting") : t("detail.storyDiscussion")
|
|
16438
|
+
]
|
|
16439
|
+
}
|
|
16440
|
+
)
|
|
16441
|
+
}
|
|
16442
|
+
),
|
|
16315
16443
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "tf-doc-body", children: story.bodyMissing ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-card-hint", style: { margin: 0 }, children: t("body.missing", { path: story.path }) }) : story.body.trim() === "" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-card-hint", style: { margin: 0 }, children: t("detail.noDescription") }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
16316
16444
|
MarkdownBody,
|
|
16317
16445
|
{
|
|
@@ -16321,7 +16449,17 @@ window.__ModuleLoader__.load({
|
|
|
16321
16449
|
onOpenPath,
|
|
16322
16450
|
onMissing
|
|
16323
16451
|
}
|
|
16324
|
-
) })
|
|
16452
|
+
) }),
|
|
16453
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
16454
|
+
DiscussionSessions,
|
|
16455
|
+
{
|
|
16456
|
+
t,
|
|
16457
|
+
sessions: story.sessions,
|
|
16458
|
+
sessionTitle,
|
|
16459
|
+
onOpenSession,
|
|
16460
|
+
readOnly
|
|
16461
|
+
}
|
|
16462
|
+
)
|
|
16325
16463
|
] }),
|
|
16326
16464
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "tf-card", children: [
|
|
16327
16465
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
@@ -17091,6 +17229,9 @@ window.__ModuleLoader__.load({
|
|
|
17091
17229
|
prepareRequirementSession: async (requirementId, sessionId, kind) => callRemote(
|
|
17092
17230
|
(await ready.promise).prepareRequirementSession(requirementId, sessionId, kind, locale())
|
|
17093
17231
|
),
|
|
17232
|
+
prepareDiscussionSession: async (subjectType, subjectId, sessionId, kind) => callRemote(
|
|
17233
|
+
(await ready.promise).prepareDiscussionSession(subjectType, subjectId, sessionId, kind, locale())
|
|
17234
|
+
),
|
|
17094
17235
|
sessionPermissionTier: async (sessionId) => callRemote((await ready.promise).sessionPermissionTier(sessionId, locale())),
|
|
17095
17236
|
previewDelete: async (targets) => callRemote((await ready.promise).previewDelete(targets, locale())),
|
|
17096
17237
|
deleteRecords: async (targets) => callRemote((await ready.promise).deleteRecords(targets, locale())),
|
package/lib/index.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export interface Config {
|
|
|
16
16
|
* which is the only location this plugin owns.
|
|
17
17
|
*/
|
|
18
18
|
dataDir: string;
|
|
19
|
+
/**
|
|
20
|
+
* Document root override. Empty uses `<dataDir>/docs`, next to the database;
|
|
21
|
+
* relative paths resolve against the working directory and `~/` is expanded.
|
|
22
|
+
*/
|
|
23
|
+
docsDir: string;
|
|
19
24
|
/** SQLite busy timeout in milliseconds; a second instance waits this long for the write lock. */
|
|
20
25
|
busyTimeoutMs: number;
|
|
21
26
|
}
|
|
@@ -32,7 +37,8 @@ export declare function apply(ctx: Context, config: Config): void;
|
|
|
32
37
|
export type { TeamflowDatabase, StartupReport, Role } from './store/db.ts';
|
|
33
38
|
export type { SchemaComment } from './store/schema.ts';
|
|
34
39
|
export { WorkflowStore } from './store/workflow.ts';
|
|
35
|
-
export {
|
|
40
|
+
export { docPath, ensureDocTree, resolveDocPath } from './store/docs.ts';
|
|
41
|
+
export { DOCS_DIRNAME, defaultDocsDir, resolveDocsDir } from './store/paths.ts';
|
|
36
42
|
export type { DocKind as DocFileKind } from './store/docs.ts';
|
|
37
43
|
export type { WorkflowStoreOptions } from './store/workflow.ts';
|
|
38
44
|
export { WorkflowError } from './store/model.ts';
|
package/lib/index.js
CHANGED
|
@@ -7,6 +7,9 @@ export const Config = Schema.object({
|
|
|
7
7
|
dataDir: Schema.string()
|
|
8
8
|
.default('')
|
|
9
9
|
.description('Workbench data directory. Empty uses $DSH_HOME/xirang-ai/dsh-teamflow.'),
|
|
10
|
+
docsDir: Schema.string()
|
|
11
|
+
.default('')
|
|
12
|
+
.description('Document root for requirement, design, story and bug bodies. Empty uses <dataDir>/docs; relative paths and ~/ are supported.'),
|
|
10
13
|
busyTimeoutMs: Schema.number()
|
|
11
14
|
.min(0)
|
|
12
15
|
.max(60_000)
|
|
@@ -28,11 +31,13 @@ export function apply(ctx, config) {
|
|
|
28
31
|
error: (message) => logger.error(message),
|
|
29
32
|
};
|
|
30
33
|
ctx.effect(() => {
|
|
31
|
-
// Document bodies live
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
+
// Document bodies live below the plugin-owned document root, not the
|
|
35
|
+
// database and not the user's workspace; the panel reads them by
|
|
36
|
+
// `content_path` relative to that one root. The registry stays wired for
|
|
37
|
+
// workspace display only.
|
|
34
38
|
const database = openTeamflowDatabase({
|
|
35
39
|
dataDir: config.dataDir,
|
|
40
|
+
docsDir: config.docsDir,
|
|
36
41
|
busyTimeoutMs: config.busyTimeoutMs,
|
|
37
42
|
// Resolved lazily: the optional registry may register after this effect runs.
|
|
38
43
|
workspaceRoot: (workspaceId) => ctx.get('workspaceRegistry')?.get(workspaceId)?.path,
|
|
@@ -66,6 +71,7 @@ export function apply(ctx, config) {
|
|
|
66
71
|
}, 'teamflow: workbench database');
|
|
67
72
|
}
|
|
68
73
|
export { WorkflowStore } from "./store/workflow.js";
|
|
69
|
-
export {
|
|
74
|
+
export { docPath, ensureDocTree, resolveDocPath } from "./store/docs.js";
|
|
75
|
+
export { DOCS_DIRNAME, defaultDocsDir, resolveDocsDir } from "./store/paths.js";
|
|
70
76
|
export { WorkflowError } from "./store/model.js";
|
|
71
77
|
export { MAX_DELETE_TARGETS } from "./store/workflow.js";
|