@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 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.2`** (documents move into the plugin directory; stories and bugs gain discussion sessions; see [CHANGELOG.md](CHANGELOG.md)).
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.2`**(文档迁入插件目录,故事与缺陷支持讨论会话,变更记录见 [CHANGELOG.md](CHANGELOG.md))。
7
+ 当前版本:**`0.1.3`**(详细设计文档可单独删除,工作台首页显示版本号,变更记录见 [CHANGELOG.md](CHANGELOG.md))。
8
8
 
9
9
  - 数据落在 SQLite,文档落在插件目录(默认 `<dataDir>/docs`),关掉会话不丢进度。
10
10
  - 五个 Agent 预设分别负责需求、故事、缺陷、研发和详细设计,工具边界由预设控制。
11
11
  - 工作台提供列表、详情、流程图、创建页、会话内只读页签,以及受控的删除入口。
12
- - 二十个 `teamflow_*` 工具把访谈与研发结果写回数据库和插件文档。
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.2**(用户已确认) |
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 版本、工具数量(当前 20 个)、预设数量(当前 5 个)与实际一致。
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.2 — generated by scripts/build.mjs, do not edit. */
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.jsx)("h1", { className: "tf-title", children: t("page.title") }),
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: [
@@ -371,18 +371,19 @@ export interface RunFilter {
371
371
  /**
372
372
  * Record types the workbench can physically delete.
373
373
  *
374
- * Detailed-design documents (`design_docs`) are not a target of their own: they
375
- * are records *of* a requirement and are removed with it, because a document
376
- * has no meaning once the requirement that explains it is gone.
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 together with its requirement. */
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;
@@ -63,8 +63,9 @@ export class WorkflowError extends Error {
63
63
  /**
64
64
  * Record types the workbench can physically delete.
65
65
  *
66
- * Detailed-design documents (`design_docs`) are not a target of their own: they
67
- * are records *of* a requirement and are removed with it, because a document
68
- * has no meaning once the requirement that explains it is gone.
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'];
@@ -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' ? 'stories' : target.kind === 'bug' ? 'bugs' : 'requirements';
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
+ }
@@ -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.
@@ -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
@@ -218,6 +218,7 @@
218
218
  "page.retry": "重试",
219
219
  "page.subtitle": "缺陷优先;其下依次是执行中的需求与故事、待开始的工作,以及仍在讨论的需求。",
220
220
  "page.title": "息壤工作台",
221
+ "page.version": "v{version}",
221
222
  "panel": "息壤工作台",
222
223
  "remote.bugId.empty": "缺少缺陷编号。",
223
224
  "remote.doc.notFound": "详细设计 {id} 不存在。",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xirang-ai/dsh-teamflow",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "DeepSeek Harness plugin: Xirang workbench for the requirement → development → defect pipeline.",
5
5
  "license": "MIT",
6
6
  "repository": {