@xirang-ai/dsh-teamflow 0.1.2 → 0.1.3
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 +2 -2
- package/README.md +2 -2
- package/RELEASE.md +2 -2
- package/cordis.patch.yml +4 -3
- package/lib/client.js +18 -2
- package/lib/store/model.d.ts +9 -5
- package/lib/store/model.js +5 -4
- package/lib/store/workflow.js +65 -3
- package/lib/tools/delete-design-doc.d.ts +24 -0
- package/lib/tools/delete-design-doc.js +109 -0
- package/lib/tools/index.d.ts +1 -1
- package/lib/tools/index.js +5 -1
- package/locale/en.json +1 -0
- package/locale/zh.json +1 -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.3`** (detailed-design documents can be deleted individually; the workbench header shows the plugin version; see [CHANGELOG.md](CHANGELOG.md)).
|
|
8
8
|
|
|
9
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
|
-
- Twenty `teamflow_*` tools write interview and development results back to the database and plugin documents.
|
|
12
|
+
- Twenty-one `teamflow_*` tools write interview and development results back to the database and plugin documents.
|
|
13
13
|
|
|
14
14
|
## Requirements
|
|
15
15
|
|
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.3`**(详细设计文档可单独删除,工作台首页显示版本号,变更记录见 [CHANGELOG.md](CHANGELOG.md))。
|
|
8
8
|
|
|
9
9
|
- 数据落在 SQLite,文档落在插件目录(默认 `<dataDir>/docs`),关掉会话不丢进度。
|
|
10
10
|
- 五个 Agent 预设分别负责需求、故事、缺陷、研发和详细设计,工具边界由预设控制。
|
|
11
11
|
- 工作台提供列表、详情、流程图、创建页、会话内只读页签,以及受控的删除入口。
|
|
12
|
-
-
|
|
12
|
+
- 二十一个 `teamflow_*` 工具把访谈与研发结果写回数据库和插件文档。
|
|
13
13
|
|
|
14
14
|
## 环境要求
|
|
15
15
|
|
package/RELEASE.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
| 项 | 值 |
|
|
11
11
|
| --- | --- |
|
|
12
|
-
| 目标版本号 | **0.1.
|
|
12
|
+
| 目标版本号 | **0.1.3**(用户已确认) |
|
|
13
13
|
| 发布分支 / commit | 发布时填写 |
|
|
14
14
|
| 发布日期 | 发布时填写 |
|
|
15
15
|
| 发布人 | 发布时填写 |
|
|
@@ -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 版本、工具数量(当前 21 个)、预设数量(当前 5 个)与实际一致。
|
|
129
129
|
- [ ] 本文件已按本次发版更新;AGENTS.md 的阅读路径与文档索引没有死链。
|
|
130
130
|
|
|
131
131
|
## 5. 代码、敏感信息与数据卫生
|
package/cordis.patch.yml
CHANGED
|
@@ -867,7 +867,7 @@
|
|
|
867
867
|
3. 需要确认某份详设该挂哪些故事时,用 teamflow_get_requirement 返回的故事列表核对标题与编号。你读不到故事正文,所以不要评论故事的验收标准细节。
|
|
868
868
|
4. 读完后先用两三句话概括:现有详设的结构、覆盖范围、与概要设计的对应关系。然后只问真正缺失或互相冲突的信息。
|
|
869
869
|
5. 讨论要围绕:接口、数据、状态机、异常与边界、权限、验收方式,以及文档之间的边界和一致性。
|
|
870
|
-
6. 文档粒度按 kind 自动划分:流程用 flow、数据用 data、接口用 interface、页面用 page、规则用 rule。一份文档只讲一件事;同一 kind
|
|
870
|
+
6. 文档粒度按 kind 自动划分:流程用 flow、数据用 data、接口用 interface、页面用 page、规则用 rule。一份文档只讲一件事;同一 kind 同一主题已有文档时更新既有文档,不要新建副本;用户明确要求「删旧建新」时,先删除旧文档再新建。这套分类由你按内容自动判定,不设确认闸口。
|
|
871
871
|
7. 一次最多问 3 个问题;能选择就给选项;不重复询问用户已经说过的内容。
|
|
872
872
|
|
|
873
873
|
## 不做
|
|
@@ -884,7 +884,7 @@
|
|
|
884
884
|
|
|
885
885
|
遇到下列情况先停下说明并等用户回答,不要自行处置:
|
|
886
886
|
|
|
887
|
-
1. 写回前:先按「结论 / 理由 / 影响 / 待办」给出摘要,并明确问用户是否写回;用户确认后才调用 teamflow_create_design_doc 或 teamflow_update_design_doc
|
|
887
|
+
1. 写回前:先按「结论 / 理由 / 影响 / 待办」给出摘要,并明确问用户是否写回;用户确认后才调用 teamflow_create_design_doc 或 teamflow_update_design_doc。删除文档前必须单独说明「正文文件与故事覆盖关系会一并删除、不可恢复」,得到明确同意后才调用 teamflow_delete_design_doc,一次只删一份。
|
|
888
888
|
2. 概要设计自相矛盾、或与用户新说法冲突时:指出冲突的具体两处,问以哪个为准,不要静默按一方写。
|
|
889
889
|
3. 需求已完成、已归档或已取消时:说明当前状态,问是否仍要在其上继续设计。
|
|
890
890
|
4. 用户要求改概设正文或拆故事时:说明不在你的职责内,并指出该走哪个入口。
|
|
@@ -903,7 +903,7 @@
|
|
|
903
903
|
|
|
904
904
|
1. 按职责内第 5 条的七个维度推进讨论,一次最多 3 个问题。
|
|
905
905
|
2. 形成结论时给出「结论 / 理由 / 影响 / 待办」摘要,并明确问是否写回。
|
|
906
|
-
3. 修改已有文档 → teamflow_update_design_doc(传文档 id);新增文档 → teamflow_create_design_doc(定 kind、sortOrder、storyIds
|
|
906
|
+
3. 修改已有文档 → teamflow_update_design_doc(传文档 id);新增文档 → teamflow_create_design_doc(定 kind、sortOrder、storyIds);删除旧文档 → teamflow_delete_design_doc(传文档 id,先说明不可恢复)。
|
|
907
907
|
4. 写回后说明写了哪几份、各自路径,以及仍然未决的事项。
|
|
908
908
|
|
|
909
909
|
# 输入输出约定
|
|
@@ -920,6 +920,7 @@
|
|
|
920
920
|
2. 不要求用户手工粘贴文档内容。
|
|
921
921
|
3. 不在同一份文档里塞进多件互不相关的事。
|
|
922
922
|
4. 不用占位式 TODO 当作讨论结论。
|
|
923
|
+
5. 不一次删除多份文档,不用删除代替改写。
|
|
923
924
|
- id: teamflow-tools
|
|
924
925
|
name: '@xirang-ai/dsh-teamflow/tools'
|
|
925
926
|
config:
|
package/lib/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @xirang-ai/dsh-teamflow 0.1.
|
|
1
|
+
/* @xirang-ai/dsh-teamflow 0.1.3 — generated by scripts/build.mjs, do not edit. */
|
|
2
2
|
window.__ModuleLoader__.load({
|
|
3
3
|
id: "@xirang-ai/dsh-teamflow",
|
|
4
4
|
factory: (require) => {
|
|
@@ -426,6 +426,7 @@ window.__ModuleLoader__.load({
|
|
|
426
426
|
"page.retry": "Retry",
|
|
427
427
|
"page.subtitle": "Defects come first, then running work, work waiting to start, and requirements still in discussion.",
|
|
428
428
|
"page.title": "Xirang Workbench",
|
|
429
|
+
"page.version": "v{version}",
|
|
429
430
|
panel: "Xirang Workbench",
|
|
430
431
|
"remote.bugId.empty": "A bug id is required.",
|
|
431
432
|
"remote.doc.notFound": "Detailed-design document {id} does not exist.",
|
|
@@ -766,6 +767,7 @@ window.__ModuleLoader__.load({
|
|
|
766
767
|
"page.retry": "重试",
|
|
767
768
|
"page.subtitle": "缺陷优先;其下依次是执行中的需求与故事、待开始的工作,以及仍在讨论的需求。",
|
|
768
769
|
"page.title": "息壤工作台",
|
|
770
|
+
"page.version": "v{version}",
|
|
769
771
|
panel: "息壤工作台",
|
|
770
772
|
"remote.bugId.empty": "缺少缺陷编号。",
|
|
771
773
|
"remote.doc.notFound": "详细设计 {id} 不存在。",
|
|
@@ -1242,6 +1244,14 @@ window.__ModuleLoader__.load({
|
|
|
1242
1244
|
.tf-head { display: flex; align-items: flex-start; gap: 16px; padding: 20px 0 14px; }
|
|
1243
1245
|
.tf-head-main { min-width: 0; flex: 1 1 auto; }
|
|
1244
1246
|
.tf-title { margin: 0; font-size: 18px; font-weight: 650; letter-spacing: -0.01em; }
|
|
1247
|
+
.tf-version {
|
|
1248
|
+
margin-left: 7px;
|
|
1249
|
+
font-size: 11px;
|
|
1250
|
+
font-weight: 500;
|
|
1251
|
+
color: var(--dsw-alias-label-tertiary, inherit);
|
|
1252
|
+
font-variant-numeric: tabular-nums;
|
|
1253
|
+
white-space: nowrap;
|
|
1254
|
+
}
|
|
1245
1255
|
.tf-sub { margin: 3px 0 0; font-size: 12px; color: var(--dsw-alias-label-tertiary, inherit); }
|
|
1246
1256
|
.tf-head-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; padding-top: 2px; }
|
|
1247
1257
|
.tf-error {
|
|
@@ -3240,6 +3250,9 @@ window.__ModuleLoader__.load({
|
|
|
3240
3250
|
return title !== void 0 && title.trim() !== "" ? title : sessionId;
|
|
3241
3251
|
}
|
|
3242
3252
|
|
|
3253
|
+
// src/client/version.ts
|
|
3254
|
+
var PLUGIN_VERSION = "0.1.3";
|
|
3255
|
+
|
|
3243
3256
|
// src/dev.ts
|
|
3244
3257
|
var DEV_PRESET = "xirang-dev";
|
|
3245
3258
|
function buildDevPrompt(story, requirement) {
|
|
@@ -15074,7 +15087,10 @@ window.__ModuleLoader__.load({
|
|
|
15074
15087
|
view.kind === "list" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
15075
15088
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("header", { className: "tf-head", children: [
|
|
15076
15089
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tf-head-main", children: [
|
|
15077
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.
|
|
15090
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("h1", { className: "tf-title", children: [
|
|
15091
|
+
t("page.title"),
|
|
15092
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "tf-version", children: t("page.version", { version: PLUGIN_VERSION }) })
|
|
15093
|
+
] }),
|
|
15078
15094
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-sub", children: t("page.subtitle") })
|
|
15079
15095
|
] }),
|
|
15080
15096
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tf-head-actions", children: [
|
package/lib/store/model.d.ts
CHANGED
|
@@ -371,18 +371,19 @@ export interface RunFilter {
|
|
|
371
371
|
/**
|
|
372
372
|
* Record types the workbench can physically delete.
|
|
373
373
|
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
374
|
+
* A detailed-design document (`design_doc`) is deletable on its own as well as
|
|
375
|
+
* through its requirement: rewriting a document from scratch starts by removing
|
|
376
|
+
* the old one, and only then does a fresh document make sense. Deleting one
|
|
377
|
+
* removes its `design_doc_stories` coverage links with it.
|
|
377
378
|
*/
|
|
378
|
-
export declare const DELETABLE_KINDS: readonly ["bug", "story", "requirement"];
|
|
379
|
+
export declare const DELETABLE_KINDS: readonly ["bug", "story", "requirement", "design_doc"];
|
|
379
380
|
export type DeletableKind = (typeof DELETABLE_KINDS)[number];
|
|
380
381
|
/** One record the user asked to delete. */
|
|
381
382
|
export interface DeleteTarget {
|
|
382
383
|
readonly kind: DeletableKind;
|
|
383
384
|
readonly id: string;
|
|
384
385
|
}
|
|
385
|
-
/** A detailed-design document that will be deleted
|
|
386
|
+
/** A detailed-design document that will be deleted with its requirement. */
|
|
386
387
|
export interface DeleteDocRef {
|
|
387
388
|
readonly id: string;
|
|
388
389
|
readonly title: string;
|
|
@@ -406,6 +407,7 @@ export interface DeletePreviewItem {
|
|
|
406
407
|
readonly id: string;
|
|
407
408
|
readonly found: boolean;
|
|
408
409
|
readonly title: string;
|
|
410
|
+
/** Record status; a detailed design reports the status of its requirement. */
|
|
409
411
|
readonly status: string;
|
|
410
412
|
readonly workspaceId: string;
|
|
411
413
|
readonly deletable: boolean;
|
|
@@ -439,6 +441,8 @@ export interface DeleteOutcome {
|
|
|
439
441
|
readonly title: string;
|
|
440
442
|
readonly ok: boolean;
|
|
441
443
|
readonly removedDocs: number;
|
|
444
|
+
/** `design_doc_stories` links removed with a detailed design; 0 otherwise. */
|
|
445
|
+
readonly removedLinks: number;
|
|
442
446
|
readonly removedRuns: number;
|
|
443
447
|
readonly error: {
|
|
444
448
|
readonly code: string;
|
package/lib/store/model.js
CHANGED
|
@@ -63,8 +63,9 @@ export class WorkflowError extends Error {
|
|
|
63
63
|
/**
|
|
64
64
|
* Record types the workbench can physically delete.
|
|
65
65
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
66
|
+
* A detailed-design document (`design_doc`) is deletable on its own as well as
|
|
67
|
+
* through its requirement: rewriting a document from scratch starts by removing
|
|
68
|
+
* the old one, and only then does a fresh document make sense. Deleting one
|
|
69
|
+
* removes its `design_doc_stories` coverage links with it.
|
|
69
70
|
*/
|
|
70
|
-
export const DELETABLE_KINDS = ['bug', 'story', 'requirement'];
|
|
71
|
+
export const DELETABLE_KINDS = ['bug', 'story', 'requirement', 'design_doc'];
|
package/lib/store/workflow.js
CHANGED
|
@@ -1278,6 +1278,7 @@ export class WorkflowStore {
|
|
|
1278
1278
|
#deleteOne(target, preview) {
|
|
1279
1279
|
const staged = [];
|
|
1280
1280
|
let committed = false;
|
|
1281
|
+
let removedLinks = 0;
|
|
1281
1282
|
try {
|
|
1282
1283
|
const outcome = withTransaction(this.#db, () => {
|
|
1283
1284
|
// Re-check inside the write transaction: a run may have started between
|
|
@@ -1305,12 +1306,28 @@ export class WorkflowStore {
|
|
|
1305
1306
|
else if (target.kind === 'bug') {
|
|
1306
1307
|
this.#db.prepare('DELETE FROM runs WHERE bug_id = ?').run(target.id);
|
|
1307
1308
|
}
|
|
1309
|
+
else if (target.kind === 'design_doc') {
|
|
1310
|
+
// Coverage links cascade from `design_docs`, but delete them
|
|
1311
|
+
// explicitly so the removed count is known and does not depend on
|
|
1312
|
+
// the connection's foreign-keys pragma.
|
|
1313
|
+
const links = this.#db
|
|
1314
|
+
.prepare('SELECT COUNT(*) AS n FROM design_doc_stories WHERE doc_id = ?')
|
|
1315
|
+
.get(target.id);
|
|
1316
|
+
removedLinks = Number(links?.['n'] ?? 0);
|
|
1317
|
+
this.#db.prepare('DELETE FROM design_doc_stories WHERE doc_id = ?').run(target.id);
|
|
1318
|
+
}
|
|
1308
1319
|
else {
|
|
1309
1320
|
// `design_doc_stories` is ON DELETE CASCADE from this table, and
|
|
1310
1321
|
// `requirement_sessions` cascades from `requirements`.
|
|
1311
1322
|
this.#db.prepare('DELETE FROM design_docs WHERE requirement_id = ?').run(target.id);
|
|
1312
1323
|
}
|
|
1313
|
-
const table = target.kind === 'story'
|
|
1324
|
+
const table = target.kind === 'story'
|
|
1325
|
+
? 'stories'
|
|
1326
|
+
: target.kind === 'bug'
|
|
1327
|
+
? 'bugs'
|
|
1328
|
+
: target.kind === 'design_doc'
|
|
1329
|
+
? 'design_docs'
|
|
1330
|
+
: 'requirements';
|
|
1314
1331
|
const changes = Number(this.#db.prepare(`DELETE FROM ${table} WHERE id = ?`).run(target.id).changes);
|
|
1315
1332
|
if (changes === 0) {
|
|
1316
1333
|
throw new WorkflowError('not_found', `${target.kind} ${target.id} does not exist`);
|
|
@@ -1320,7 +1337,8 @@ export class WorkflowStore {
|
|
|
1320
1337
|
id: target.id,
|
|
1321
1338
|
title: current.title,
|
|
1322
1339
|
ok: true,
|
|
1323
|
-
removedDocs: current.docs.length,
|
|
1340
|
+
removedDocs: target.kind === 'design_doc' ? 1 : current.docs.length,
|
|
1341
|
+
removedLinks,
|
|
1324
1342
|
removedRuns: current.runCount,
|
|
1325
1343
|
error: null,
|
|
1326
1344
|
};
|
|
@@ -1344,6 +1362,7 @@ export class WorkflowStore {
|
|
|
1344
1362
|
title: preview.title,
|
|
1345
1363
|
ok: false,
|
|
1346
1364
|
removedDocs: 0,
|
|
1365
|
+
removedLinks: 0,
|
|
1347
1366
|
removedRuns: 0,
|
|
1348
1367
|
error: {
|
|
1349
1368
|
code: error instanceof WorkflowError ? error.code : 'delete_failed',
|
|
@@ -1363,7 +1382,7 @@ export class WorkflowStore {
|
|
|
1363
1382
|
}
|
|
1364
1383
|
}
|
|
1365
1384
|
#summary(items) {
|
|
1366
|
-
const counts = { bug: 0, story: 0, requirement: 0 };
|
|
1385
|
+
const counts = { bug: 0, story: 0, requirement: 0, design_doc: 0 };
|
|
1367
1386
|
for (const item of items)
|
|
1368
1387
|
counts[item.kind] += 1;
|
|
1369
1388
|
return {
|
|
@@ -1414,6 +1433,47 @@ export class WorkflowStore {
|
|
|
1414
1433
|
runCount: 0,
|
|
1415
1434
|
};
|
|
1416
1435
|
}
|
|
1436
|
+
if (target.kind === 'design_doc') {
|
|
1437
|
+
const doc = this.getDesignDoc(target.id);
|
|
1438
|
+
if (doc === null)
|
|
1439
|
+
return this.#missingPreview(target);
|
|
1440
|
+
const requirement = this.getRequirement(doc.requirementId);
|
|
1441
|
+
if (requirement === null)
|
|
1442
|
+
return this.#missingPreview(target);
|
|
1443
|
+
// A document has no lifecycle of its own, so only the stories it covers
|
|
1444
|
+
// can make its deletion unsafe: an executing story still reads it.
|
|
1445
|
+
const coveredStoryIds = this.listDocStoryIds(doc.id);
|
|
1446
|
+
const activeRun = this.#activeRunForStories(coveredStoryIds);
|
|
1447
|
+
const busyStory = coveredStoryIds
|
|
1448
|
+
.map((storyId) => this.getStory(storyId))
|
|
1449
|
+
.find((story) => story !== null && (story.status === 'developing' || story.status === 'self_testing'));
|
|
1450
|
+
const blocked = activeRun !== null
|
|
1451
|
+
? {
|
|
1452
|
+
code: 'has_active_run',
|
|
1453
|
+
message: `design document ${doc.id} is covered by a story whose run ${activeRun} is still running; cancel the run before deleting`,
|
|
1454
|
+
}
|
|
1455
|
+
: busyStory === null || busyStory === undefined
|
|
1456
|
+
? null
|
|
1457
|
+
: {
|
|
1458
|
+
code: 'invalid_transition',
|
|
1459
|
+
message: `design document ${doc.id} is covered by story ${busyStory.id}, which is ${busyStory.status}; only documents covered by idle stories can be deleted`,
|
|
1460
|
+
};
|
|
1461
|
+
return {
|
|
1462
|
+
kind: 'design_doc',
|
|
1463
|
+
id: doc.id,
|
|
1464
|
+
found: true,
|
|
1465
|
+
title: doc.title,
|
|
1466
|
+
status: requirement.status,
|
|
1467
|
+
workspaceId: requirement.workspaceId,
|
|
1468
|
+
deletable: blocked === null,
|
|
1469
|
+
reasonCode: blocked?.code ?? null,
|
|
1470
|
+
reason: blocked?.message ?? null,
|
|
1471
|
+
activeRunId: activeRun,
|
|
1472
|
+
docs: [],
|
|
1473
|
+
stories: [],
|
|
1474
|
+
runCount: 0,
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1417
1477
|
if (target.kind === 'story') {
|
|
1418
1478
|
const story = this.getStory(target.id);
|
|
1419
1479
|
if (story === null)
|
|
@@ -1519,6 +1579,8 @@ export class WorkflowStore {
|
|
|
1519
1579
|
return [this.#requireStory(target.id).contentPath];
|
|
1520
1580
|
if (target.kind === 'bug')
|
|
1521
1581
|
return [this.#requireBug(target.id).contentPath];
|
|
1582
|
+
if (target.kind === 'design_doc')
|
|
1583
|
+
return [this.#requireDesignDoc(target.id).contentPath];
|
|
1522
1584
|
const requirement = this.#requireRequirement(target.id);
|
|
1523
1585
|
return [
|
|
1524
1586
|
requirement.contentPath,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { WorkflowStore } from '../store/workflow.ts';
|
|
2
|
+
import type { ToolEnvironment } from './shared.ts';
|
|
3
|
+
/** What one successful detailed-design deletion removed, as the model sees it. */
|
|
4
|
+
export interface DeletedDesignDoc {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly requirementId: string;
|
|
7
|
+
readonly title: string;
|
|
8
|
+
readonly kind: string;
|
|
9
|
+
/** Coverage links removed from `design_doc_stories`. */
|
|
10
|
+
readonly removedLinks: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Preview and then delete one detailed-design document.
|
|
14
|
+
*
|
|
15
|
+
* The preview is where a refusal becomes an actionable sentence instead of a
|
|
16
|
+
* bare `ok: false`, because `deleteRecords` reports per-record failures instead
|
|
17
|
+
* of throwing.
|
|
18
|
+
* @param workflow - the open workbench store.
|
|
19
|
+
* @param rawId - document id, trimmed before lookup.
|
|
20
|
+
* @returns the removed document's identity and how many coverage links went with it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function deleteDesignDoc(workflow: WorkflowStore, rawId: string): DeletedDesignDoc;
|
|
23
|
+
/** Build the detailed-design deletion tool bound to one Host context. */
|
|
24
|
+
export declare function deleteDesignDocTool(environment: ToolEnvironment): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `teamflow_delete_design_doc` — permanently remove one detailed-design document.
|
|
3
|
+
*
|
|
4
|
+
* This is the "delete the old document, then write a new one" path the detail
|
|
5
|
+
* Agent needs when a rewrite is not an in-place revision. Deletion goes through
|
|
6
|
+
* the workbench's preview-then-delete path, so the Agent and the Web UI share
|
|
7
|
+
* the same refusal rules: a document covered by an executing story is refused
|
|
8
|
+
* with a sentence the user can act on. The body file and every
|
|
9
|
+
* `design_doc_stories` coverage link are removed with the row; there is no undo.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
14
|
+
/** Turn a deletion preview into the refusal the Agent should relay. */
|
|
15
|
+
function refusal(item) {
|
|
16
|
+
const head = `teamflow: 不能删除详细设计 ${item.id}《${item.title}》`;
|
|
17
|
+
if (item.reasonCode === 'has_active_run') {
|
|
18
|
+
const run = item.activeRunId === null ? '' : ` ${item.activeRunId}`;
|
|
19
|
+
return `${head}:覆盖它的故事仍有研发运行${run} 尚未结束;请先取消该运行再删除。`;
|
|
20
|
+
}
|
|
21
|
+
if (item.reasonCode === 'invalid_transition') {
|
|
22
|
+
return `${head}:覆盖它的故事正在执行(developing / self_testing);只有覆盖故事都空闲时才能删除详设。`;
|
|
23
|
+
}
|
|
24
|
+
return `${head}:${item.reason ?? '当前状态不允许删除'}。`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Preview and then delete one detailed-design document.
|
|
28
|
+
*
|
|
29
|
+
* The preview is where a refusal becomes an actionable sentence instead of a
|
|
30
|
+
* bare `ok: false`, because `deleteRecords` reports per-record failures instead
|
|
31
|
+
* of throwing.
|
|
32
|
+
* @param workflow - the open workbench store.
|
|
33
|
+
* @param rawId - document id, trimmed before lookup.
|
|
34
|
+
* @returns the removed document's identity and how many coverage links went with it.
|
|
35
|
+
*/
|
|
36
|
+
export function deleteDesignDoc(workflow, rawId) {
|
|
37
|
+
const id = rawId.trim();
|
|
38
|
+
if (id.length === 0)
|
|
39
|
+
throw new Error('teamflow: 详细设计 id 不能为空。');
|
|
40
|
+
// Read the row before it is gone: the requirement id and kind are not part
|
|
41
|
+
// of the deletion outcome.
|
|
42
|
+
const doc = workflow.getDesignDoc(id);
|
|
43
|
+
if (doc === null)
|
|
44
|
+
throw new Error(`teamflow: 详细设计 ${id} 不存在。`);
|
|
45
|
+
const preview = workflow.previewDeletions([{ kind: 'design_doc', id }]);
|
|
46
|
+
const item = preview.items[0];
|
|
47
|
+
if (item === undefined)
|
|
48
|
+
throw new Error(`teamflow: 无法预览详细设计 ${id} 的删除。`);
|
|
49
|
+
if (!item.found)
|
|
50
|
+
throw new Error(`teamflow: 详细设计 ${id} 不存在。`);
|
|
51
|
+
if (!item.deletable)
|
|
52
|
+
throw new Error(refusal(item));
|
|
53
|
+
const report = workflow.deleteRecords([{ kind: 'design_doc', id }]);
|
|
54
|
+
const outcome = report.results[0];
|
|
55
|
+
if (outcome === undefined)
|
|
56
|
+
throw new Error(`teamflow: 删除详细设计 ${id} 没有返回结果。`);
|
|
57
|
+
if (!outcome.ok) {
|
|
58
|
+
throw new Error(`teamflow: 删除详细设计 ${id} 失败:${outcome.error?.message ?? '未知错误'}。`);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
id: outcome.id,
|
|
62
|
+
requirementId: doc.requirementId,
|
|
63
|
+
title: outcome.title,
|
|
64
|
+
kind: doc.kind,
|
|
65
|
+
removedLinks: outcome.removedLinks,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Build the detailed-design deletion tool bound to one Host context. */
|
|
69
|
+
export function deleteDesignDocTool(environment) {
|
|
70
|
+
return defineTool({
|
|
71
|
+
name: 'teamflow_delete_design_doc',
|
|
72
|
+
description: 'Permanently delete one detailed-design document together with its Markdown body and every story-coverage link. Use this when the user asks to remove the old document and write a new one, or to drop a document that no longer belongs. A document covered by a story that is developing or self-testing is refused. Ask the user to confirm the title first: this cannot be undone.',
|
|
73
|
+
parameters: {
|
|
74
|
+
id: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
required: true,
|
|
77
|
+
description: 'Document id from teamflow_get_requirement or teamflow_get_design_doc.',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
output: {
|
|
81
|
+
schema: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
additionalProperties: false,
|
|
84
|
+
properties: {
|
|
85
|
+
id: { type: 'string', required: true },
|
|
86
|
+
requirementId: { type: 'string', required: true },
|
|
87
|
+
title: { type: 'string', required: true },
|
|
88
|
+
kind: { type: 'string', required: true },
|
|
89
|
+
removedLinks: { type: 'number', required: true },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
render: (_args, value) => [
|
|
93
|
+
{
|
|
94
|
+
type: 'text',
|
|
95
|
+
text: [
|
|
96
|
+
`已删除详细设计 ${value.id}《${value.title}》(${value.kind}),所属需求 ${value.requirementId}。`,
|
|
97
|
+
value.removedLinks === 0
|
|
98
|
+
? '正文文件已一并删除;此操作不可恢复。'
|
|
99
|
+
: `正文文件与 ${value.removedLinks} 条故事覆盖关系已一并删除;此操作不可恢复。`,
|
|
100
|
+
].join('\n'),
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
async execute(args, exec) {
|
|
105
|
+
exec.signal.throwIfAborted();
|
|
106
|
+
return deleteDesignDoc(environment.database.workflow, args.id);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
package/lib/tools/index.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type ToolProfile = (typeof TOOL_PROFILES)[number];
|
|
|
26
26
|
* Tool names per profile.
|
|
27
27
|
*
|
|
28
28
|
* `requirement` collects the requirement and its outline design;
|
|
29
|
-
* `detail` writes detailed designs while reading the outline;
|
|
29
|
+
* `detail` writes and deletes detailed designs while reading the outline;
|
|
30
30
|
* `split` writes stories while reading detailed designs;
|
|
31
31
|
* `dev` executes a story with every document read-only;
|
|
32
32
|
* `bug` collects and merges defects.
|
package/lib/tools/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createBugTool } from "./create-bug.js";
|
|
|
5
5
|
import { createDesignDocTool } from "./create-design-doc.js";
|
|
6
6
|
import { createRequirementTool } from "./create-requirement.js";
|
|
7
7
|
import { createStoryTool } from "./create-story.js";
|
|
8
|
+
import { deleteDesignDocTool } from "./delete-design-doc.js";
|
|
8
9
|
import { deleteStoryTool } from "./delete-story.js";
|
|
9
10
|
import { finishRunTool } from "./finish-run.js";
|
|
10
11
|
import { getBugTool } from "./get-bug.js";
|
|
@@ -37,7 +38,7 @@ export const TOOL_PROFILES = [
|
|
|
37
38
|
* Tool names per profile.
|
|
38
39
|
*
|
|
39
40
|
* `requirement` collects the requirement and its outline design;
|
|
40
|
-
* `detail` writes detailed designs while reading the outline;
|
|
41
|
+
* `detail` writes and deletes detailed designs while reading the outline;
|
|
41
42
|
* `split` writes stories while reading detailed designs;
|
|
42
43
|
* `dev` executes a story with every document read-only;
|
|
43
44
|
* `bug` collects and merges defects.
|
|
@@ -59,6 +60,7 @@ export const PROFILE_TOOL_NAMES = {
|
|
|
59
60
|
'teamflow_get_design_doc',
|
|
60
61
|
'teamflow_create_design_doc',
|
|
61
62
|
'teamflow_update_design_doc',
|
|
63
|
+
'teamflow_delete_design_doc',
|
|
62
64
|
'teamflow_list_records',
|
|
63
65
|
'teamflow_list_workspaces',
|
|
64
66
|
],
|
|
@@ -97,6 +99,7 @@ export const PROFILE_TOOL_NAMES = {
|
|
|
97
99
|
'teamflow_get_requirement',
|
|
98
100
|
'teamflow_create_design_doc',
|
|
99
101
|
'teamflow_update_design_doc',
|
|
102
|
+
'teamflow_delete_design_doc',
|
|
100
103
|
'teamflow_get_design_doc',
|
|
101
104
|
'teamflow_link_requirement_session',
|
|
102
105
|
'teamflow_link_session',
|
|
@@ -142,6 +145,7 @@ export function apply(ctx, config) {
|
|
|
142
145
|
getRequirementTool(environment),
|
|
143
146
|
createDesignDocTool(environment),
|
|
144
147
|
updateDesignDocTool(environment),
|
|
148
|
+
deleteDesignDocTool(environment),
|
|
145
149
|
getDesignDocTool(environment),
|
|
146
150
|
linkRequirementSessionTool(environment),
|
|
147
151
|
linkSessionTool(environment),
|
package/locale/en.json
CHANGED
|
@@ -218,6 +218,7 @@
|
|
|
218
218
|
"page.retry": "Retry",
|
|
219
219
|
"page.subtitle": "Defects come first, then running work, work waiting to start, and requirements still in discussion.",
|
|
220
220
|
"page.title": "Xirang Workbench",
|
|
221
|
+
"page.version": "v{version}",
|
|
221
222
|
"panel": "Xirang Workbench",
|
|
222
223
|
"remote.bugId.empty": "A bug id is required.",
|
|
223
224
|
"remote.doc.notFound": "Detailed-design document {id} does not exist.",
|
package/locale/zh.json
CHANGED