autosnippet 3.1.15 → 3.2.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.
Files changed (41) hide show
  1. package/README.md +1 -0
  2. package/bin/cli.js +242 -23
  3. package/dashboard/dist/assets/{icons-CC5R_iwL.js → icons-18VxiaCT.js} +108 -98
  4. package/dashboard/dist/assets/index-BJiuaVPD.css +1 -0
  5. package/dashboard/dist/assets/{index-WmnJCXq4.js → index-CRH5Umim.js} +50 -50
  6. package/dashboard/dist/index.html +3 -3
  7. package/lib/cli/SetupService.js +152 -21
  8. package/lib/domain/task/Task.js +214 -0
  9. package/lib/domain/task/TaskDependency.js +48 -0
  10. package/lib/domain/task/TaskIdGenerator.js +83 -0
  11. package/lib/domain/task/index.js +6 -0
  12. package/lib/external/mcp/McpServer.js +4 -4
  13. package/lib/external/mcp/handlers/task.js +295 -0
  14. package/lib/external/mcp/tools.js +100 -3
  15. package/lib/http/HttpServer.js +8 -0
  16. package/lib/http/routes/guard.js +283 -0
  17. package/lib/http/routes/task.js +282 -0
  18. package/lib/infrastructure/config/Paths.js +18 -8
  19. package/lib/infrastructure/database/migrations/002_add_tasks.js +88 -0
  20. package/lib/injection/ServiceContainer.js +58 -0
  21. package/lib/repository/task/TaskRepository.impl.js +398 -0
  22. package/lib/service/cursor/AgentInstructionsGenerator.js +28 -9
  23. package/lib/service/cursor/CursorDeliveryPipeline.js +42 -20
  24. package/lib/service/cursor/KnowledgeCompressor.js +40 -0
  25. package/lib/service/cursor/TokenBudget.js +2 -2
  26. package/lib/service/guard/GuardFeedbackLoop.js +17 -2
  27. package/lib/service/knowledge/KnowledgeService.js +6 -0
  28. package/lib/service/task/TaskGraphService.js +410 -0
  29. package/lib/service/task/TaskKnowledgeBridge.js +86 -0
  30. package/lib/service/task/TaskReadyEngine.js +127 -0
  31. package/lib/shared/constants.js +3 -3
  32. package/package.json +1 -1
  33. package/skills/autosnippet-intent/SKILL.md +4 -1
  34. package/skills/autosnippet-recipes/SKILL.md +17 -2
  35. package/templates/claude-hooks.yaml +19 -0
  36. package/templates/copilot-instructions.md +33 -1
  37. package/templates/cursor-rules/autosnippet-conventions.mdc +12 -0
  38. package/templates/cursor-rules/autosnippet-workflow.mdc +43 -0
  39. package/templates/guard-ci.yml +1 -0
  40. package/templates/pre-commit-guard.sh +2 -1
  41. package/dashboard/dist/assets/index-6iola4rb.css +0 -1
@@ -5,13 +5,13 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>AutoSnippet Dashboard</title>
8
- <script type="module" crossorigin src="/assets/index-WmnJCXq4.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-CRH5Umim.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/yaml-qRaU8Ldn.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-Ck-HBmg5.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/axios-C0Zqfgkc.js">
12
- <link rel="modulepreload" crossorigin href="/assets/icons-CC5R_iwL.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/icons-18VxiaCT.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/framer-motion-DOATyqla.js">
14
- <link rel="stylesheet" crossorigin href="/assets/index-6iola4rb.css">
14
+ <link rel="stylesheet" crossorigin href="/assets/index-BJiuaVPD.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -5,7 +5,8 @@
5
5
  *
6
6
  * Step 1 .autosnippet/ 运行时目录 + config.json + .gitignore
7
7
  * Step 2 AutoSnippet/ 子仓库(核心数据 + 权限能力 + skills/)
8
- * Step 3 IDE 集成(VSCode MCP + Cursor MCP + copilot-instructions + cursor-rules + skills-template)
8
+ * Step 3 IDE 集成(VSCode MCP + Cursor MCP + copilot-instructions + cursor-rules
9
+ * + skills-template + cursor-workflow + claude-hooks + guard-ci + pre-commit-hook)
9
10
  * Step 4 SQLite 数据库 + V1 数据迁移
10
11
  * Step 5 平台相关初始化(macOS → Xcode Snippets)
11
12
  *
@@ -432,24 +433,26 @@ export class SetupService {
432
433
  /* ═══ Step 3: IDE 集成 ═══════════════════════════════ */
433
434
 
434
435
  stepIDE() {
436
+ const configured = [];
437
+
438
+ // MCP 配置(总是写入/合并,不会跳过)
435
439
  this._configureVSCodeMCP();
440
+ configured.push('vscode-mcp');
436
441
  this._configureCursorMCP();
437
- this._copyCopilotInstructions();
438
- this._generateAgentInstructionFiles();
439
- this._copyCursorRules();
440
- this._copySkillsTemplate();
442
+ configured.push('cursor-mcp');
443
+
444
+ // 模板文件(可能因已存在/用户文件而跳过)
445
+ if (this._copyCopilotInstructions()) configured.push('copilot-instructions');
446
+ if (this._generateAgentInstructionFiles()) configured.push('agent-instructions');
447
+ if (this._copyCursorRules()) configured.push('cursor-rules');
448
+ if (this._copySkillsTemplate()) configured.push('skills-template');
449
+ if (this._copyCursorWorkflow()) configured.push('cursor-workflow');
450
+ if (this._copyClaudeHooks()) configured.push('claude-hooks');
451
+ if (this._copyGuardCI()) configured.push('guard-ci');
452
+ if (this._installPreCommitHook()) configured.push('pre-commit-hook');
441
453
  // NOTE: .qoder/ .trae/ 不再自动创建,用户可通过 `asd mirror` 按需生成
442
454
 
443
455
  const extResult = this._installVSCodeExtension();
444
-
445
- const configured = [
446
- 'vscode-mcp',
447
- 'cursor-mcp',
448
- 'copilot-instructions',
449
- 'agent-instructions',
450
- 'cursor-rules',
451
- 'skills-template',
452
- ];
453
456
  if (extResult) {
454
457
  configured.push(...extResult);
455
458
  }
@@ -711,23 +714,24 @@ export class SetupService {
711
714
  _copyCopilotInstructions() {
712
715
  const src = join(REPO_ROOT, 'templates', 'copilot-instructions.md');
713
716
  if (!existsSync(src)) {
714
- return;
717
+ return false;
715
718
  }
716
719
 
717
720
  const destDir = join(this.projectRoot, '.github');
718
721
  const dest = join(destDir, 'copilot-instructions.md');
719
722
  if (existsSync(dest) && !this.force) {
720
- return;
723
+ return false;
721
724
  }
722
725
 
723
726
  // 即使 --force,也不覆盖用户原有的非 AutoSnippet 文件
724
727
  const { canWrite } = checkWriteSafety(dest);
725
728
  if (!canWrite) {
726
- return;
729
+ return false;
727
730
  }
728
731
 
729
732
  mkdirSync(destDir, { recursive: true });
730
733
  copyFileSync(src, dest);
734
+ return true;
731
735
  }
732
736
 
733
737
  /**
@@ -737,6 +741,7 @@ export class SetupService {
737
741
  */
738
742
  _generateAgentInstructionFiles() {
739
743
  const projectName = this.projectRoot.split('/').pop();
744
+ let wrote = false;
740
745
 
741
746
  // AGENTS.md
742
747
  const agentsPath = join(this.projectRoot, 'AGENTS.md');
@@ -771,6 +776,7 @@ export class SetupService {
771
776
  '',
772
777
  ].join('\n');
773
778
  writeFileSync(agentsPath, agentsContent);
779
+ wrote = true;
774
780
  } else {
775
781
  }
776
782
  }
@@ -803,44 +809,169 @@ export class SetupService {
803
809
  '',
804
810
  ].join('\n');
805
811
  writeFileSync(claudePath, claudeContent);
812
+ wrote = true;
806
813
  } else {
807
814
  }
808
815
  }
816
+ return wrote;
809
817
  }
810
818
 
811
819
  /** @private .cursor/rules/autosnippet-conventions.mdc */
812
820
  _copyCursorRules() {
813
821
  const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-conventions.mdc');
814
822
  if (!existsSync(src)) {
815
- return;
823
+ return false;
816
824
  }
817
825
 
818
826
  const destDir = join(this.projectRoot, '.cursor', 'rules');
819
827
  const dest = join(destDir, 'autosnippet-conventions.mdc');
820
828
  if (existsSync(dest) && !this.force) {
821
- return;
829
+ return false;
822
830
  }
823
831
 
824
832
  mkdirSync(destDir, { recursive: true });
825
833
  copyFileSync(src, dest);
834
+ return true;
826
835
  }
827
836
 
828
837
  /** @private .cursor/rules/autosnippet-skills.mdc — Project Skills 索引模板 */
829
838
  _copySkillsTemplate() {
830
839
  const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-skills.mdc');
831
840
  if (!existsSync(src)) {
832
- return;
841
+ return false;
833
842
  }
834
843
 
835
844
  const destDir = join(this.projectRoot, '.cursor', 'rules');
836
845
  const dest = join(destDir, 'autosnippet-skills.mdc');
837
846
  if (existsSync(dest) && !this.force) {
838
- return;
847
+ return false;
848
+ }
849
+
850
+ mkdirSync(destDir, { recursive: true });
851
+ copyFileSync(src, dest);
852
+ return true;
853
+ }
854
+ /** @private .cursor/rules/autosnippet-workflow.mdc — TaskGraph & Guard 工作流 */
855
+ _copyCursorWorkflow() {
856
+ const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-workflow.mdc');
857
+ if (!existsSync(src)) {
858
+ return false;
859
+ }
860
+
861
+ const destDir = join(this.projectRoot, '.cursor', 'rules');
862
+ const dest = join(destDir, 'autosnippet-workflow.mdc');
863
+ if (existsSync(dest) && !this.force) {
864
+ return false;
865
+ }
866
+
867
+ mkdirSync(destDir, { recursive: true });
868
+ copyFileSync(src, dest);
869
+ return true;
870
+ }
871
+
872
+ /**
873
+ * @private .claude/hooks.yaml — Claude Code SessionStart hook
874
+ * 会话开始时自动 prime TaskGraph 上下文
875
+ */
876
+ _copyClaudeHooks() {
877
+ const src = join(REPO_ROOT, 'templates', 'claude-hooks.yaml');
878
+ if (!existsSync(src)) {
879
+ return false;
880
+ }
881
+
882
+ const claudeDir = join(this.projectRoot, '.claude');
883
+ const dest = join(claudeDir, 'hooks.yaml');
884
+ if (existsSync(dest) && !this.force) {
885
+ return false;
886
+ }
887
+
888
+ // 即使 --force,也不覆盖用户自己的 hooks 文件
889
+ const { canWrite } = checkWriteSafety(dest);
890
+ if (!canWrite) {
891
+ return false;
892
+ }
893
+
894
+ mkdirSync(claudeDir, { recursive: true });
895
+ copyFileSync(src, dest);
896
+ return true;
897
+ }
898
+
899
+ /**
900
+ * @private .github/workflows/autosnippet-guard.yml — Guard CI/CD workflow
901
+ * 在 push/PR 时自动运行 Guard 合规检查
902
+ */
903
+ _copyGuardCI() {
904
+ const src = join(REPO_ROOT, 'templates', 'guard-ci.yml');
905
+ if (!existsSync(src)) {
906
+ return false;
907
+ }
908
+
909
+ const destDir = join(this.projectRoot, '.github', 'workflows');
910
+ const dest = join(destDir, 'autosnippet-guard.yml');
911
+ if (existsSync(dest) && !this.force) {
912
+ return false;
913
+ }
914
+
915
+ const { canWrite } = checkWriteSafety(dest);
916
+ if (!canWrite) {
917
+ return false;
839
918
  }
840
919
 
841
920
  mkdirSync(destDir, { recursive: true });
842
921
  copyFileSync(src, dest);
922
+ return true;
843
923
  }
924
+
925
+ /**
926
+ * @private .git/hooks/pre-commit — Guard pre-commit hook
927
+ * 如果项目使用 husky,则安装到 .husky/pre-commit
928
+ * 否则安装到 .git/hooks/pre-commit
929
+ * 已存在 pre-commit hook 时不覆盖(避免破坏用户现有 hook)
930
+ */
931
+ _installPreCommitHook() {
932
+ const src = join(REPO_ROOT, 'templates', 'pre-commit-guard.sh');
933
+ if (!existsSync(src)) {
934
+ return false;
935
+ }
936
+
937
+ // 优先检查 husky
938
+ const huskyDir = join(this.projectRoot, '.husky');
939
+ const gitHooksDir = join(this.projectRoot, '.git', 'hooks');
940
+
941
+ let dest;
942
+ if (existsSync(huskyDir)) {
943
+ // 项目使用 husky — 安装到 .husky/pre-commit
944
+ dest = join(huskyDir, 'pre-commit');
945
+ } else if (existsSync(join(this.projectRoot, '.git'))) {
946
+ // 普通 git 项目 — 安装到 .git/hooks/pre-commit
947
+ dest = join(gitHooksDir, 'pre-commit');
948
+ mkdirSync(gitHooksDir, { recursive: true });
949
+ } else {
950
+ // 非 git 项目,跳过
951
+ return false;
952
+ }
953
+
954
+ // 已存在 pre-commit hook 时不覆盖(可能是用户自己的 hook)
955
+ if (existsSync(dest) && !this.force) {
956
+ return false;
957
+ }
958
+
959
+ // 即使 --force,也不覆盖用户自己的 pre-commit hook
960
+ const { canWrite } = checkWriteSafety(dest);
961
+ if (!canWrite) {
962
+ return false;
963
+ }
964
+
965
+ copyFileSync(src, dest);
966
+ // 确保可执行
967
+ try {
968
+ execSync(`chmod +x "${dest}"`, { stdio: 'pipe' });
969
+ } catch {
970
+ /* Windows 不需要 chmod */
971
+ }
972
+ return true;
973
+ }
974
+
844
975
  /** @private 镜像 .cursor/rules/ 中的 autosnippet-* 文件到目标 IDE 目录(Qoder / Trae 兼容)
845
976
  * 只复制 autosnippet- 前缀的文件,不触碰用户自己创建的规则 */
846
977
  _mirrorCursorToIDE(targetDirName) {
@@ -0,0 +1,214 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ /**
4
+ * Task — 任务实体
5
+ *
6
+ * 参考 Beads Issue 结构,裁剪为 AutoSnippet 所需的核心字段。
7
+ *
8
+ * 字段分组:
9
+ * Core Identification → id, parentId, contentHash
10
+ * Content → title, description, design, acceptance
11
+ * Status & Workflow → status, priority, taskType
12
+ * Assignment → assignee, claimedAt
13
+ * Timestamps → createdAt, updatedAt, closedAt
14
+ * Knowledge Bridge → knowledgeContext (运行时,不持久化)
15
+ */
16
+ export class Task {
17
+ constructor(props = {}) {
18
+ // ── Core Identification ──
19
+ this.id = props.id || null;
20
+ this.parentId = props.parentId || null;
21
+ this.childSeq = props.childSeq || 0;
22
+
23
+ // ── Content ──
24
+ this.title = props.title || '';
25
+ this.description = props.description || '';
26
+ this.design = props.design || '';
27
+ this.acceptance = props.acceptance || '';
28
+ this.notes = props.notes || '';
29
+
30
+ // ── Status & Workflow ──
31
+ this.status = props.status || 'open';
32
+ this.priority = props.priority ?? 2;
33
+ this.taskType = props.taskType || 'task';
34
+ this.closeReason = props.closeReason || '';
35
+ this.failCount = props.failCount || 0;
36
+ this.lastFailReason = props.lastFailReason || '';
37
+
38
+ // ── Content Hash (去重) ──
39
+ this.contentHash = props.contentHash || null;
40
+
41
+ // ── Assignment ──
42
+ this.assignee = props.assignee || '';
43
+ this.createdBy = props.createdBy || 'agent';
44
+
45
+ // ── Timestamps ──
46
+ this.createdAt = props.createdAt || Math.floor(Date.now() / 1000);
47
+ this.updatedAt = props.updatedAt || Math.floor(Date.now() / 1000);
48
+ this.closedAt = props.closedAt || null;
49
+
50
+ // ── Knowledge Bridge (运行时填充, 不持久化) ──
51
+ this.knowledgeContext = props.knowledgeContext || null;
52
+
53
+ // ── Metadata (可扩展 JSON) ──
54
+ this.metadata = props.metadata || {};
55
+ }
56
+
57
+ // ── 生命周期方法 ──
58
+
59
+ claim(assignee = 'agent') {
60
+ if (this.status === 'closed') {
61
+ throw new Error('Cannot claim a closed task');
62
+ }
63
+ this.status = 'in_progress';
64
+ this.assignee = assignee;
65
+ this.updatedAt = Math.floor(Date.now() / 1000);
66
+ }
67
+
68
+ close(reason = 'Completed') {
69
+ this.status = 'closed';
70
+ this.closeReason = reason;
71
+ this.closedAt = Math.floor(Date.now() / 1000);
72
+ this.updatedAt = this.closedAt;
73
+ }
74
+
75
+ reopen() {
76
+ this.status = 'open';
77
+ this.closedAt = null;
78
+ this.closeReason = '';
79
+ this.updatedAt = Math.floor(Date.now() / 1000);
80
+ }
81
+
82
+ defer(reason = '') {
83
+ this.status = 'deferred';
84
+ if (reason) {
85
+ this.notes = `[deferred] ${reason}`;
86
+ }
87
+ this.updatedAt = Math.floor(Date.now() / 1000);
88
+ }
89
+
90
+ fail(reason) {
91
+ if (this.status === 'closed') {
92
+ throw new Error('Cannot fail a closed task');
93
+ }
94
+ this.status = 'open';
95
+ this.failCount += 1;
96
+ this.lastFailReason = reason || 'Unknown failure';
97
+ this.assignee = '';
98
+ this.updatedAt = Math.floor(Date.now() / 1000);
99
+ }
100
+
101
+ /**
102
+ * 计算内容哈希(用于去重检测)
103
+ * 相同标题+描述+类型 = 重复任务
104
+ */
105
+ computeContentHash() {
106
+ const content = `${this.title}|${this.description}|${this.taskType}`;
107
+ this.contentHash = createHash('sha256').update(content).digest('hex').substring(0, 12);
108
+ return this.contentHash;
109
+ }
110
+
111
+ // ── 校验 ──
112
+
113
+ validate() {
114
+ if (!this.title || this.title.length === 0) {
115
+ throw new Error('title is required');
116
+ }
117
+ if (this.title.length > 500) {
118
+ throw new Error(`title must be 500 characters or less (got ${this.title.length})`);
119
+ }
120
+ if (typeof this.priority !== 'number' || !Number.isInteger(this.priority) || this.priority < 0 || this.priority > 4) {
121
+ throw new Error(`priority must be an integer between 0 and 4 (got ${this.priority})`);
122
+ }
123
+ const validStatuses = ['open', 'in_progress', 'deferred', 'closed'];
124
+ if (!validStatuses.includes(this.status)) {
125
+ throw new Error(`invalid status: ${this.status}`);
126
+ }
127
+ const validTypes = ['epic', 'task', 'bug', 'chore'];
128
+ if (!validTypes.includes(this.taskType)) {
129
+ throw new Error(`invalid task type: ${this.taskType}`);
130
+ }
131
+ if (this.status === 'closed' && !this.closedAt) {
132
+ throw new Error('closed tasks must have closedAt timestamp');
133
+ }
134
+ return true;
135
+ }
136
+
137
+ /**
138
+ * 是否有效(快捷校验)
139
+ */
140
+ isValid() {
141
+ try {
142
+ this.validate();
143
+ return true;
144
+ } catch {
145
+ return false;
146
+ }
147
+ }
148
+
149
+ toJSON() {
150
+ return {
151
+ id: this.id,
152
+ parentId: this.parentId,
153
+ title: this.title,
154
+ description: this.description,
155
+ design: this.design,
156
+ acceptance: this.acceptance,
157
+ notes: this.notes,
158
+ status: this.status,
159
+ priority: this.priority,
160
+ taskType: this.taskType,
161
+ closeReason: this.closeReason,
162
+ assignee: this.assignee,
163
+ createdBy: this.createdBy,
164
+ createdAt: this.createdAt,
165
+ updatedAt: this.updatedAt,
166
+ closedAt: this.closedAt,
167
+ contentHash: this.contentHash,
168
+ failCount: this.failCount,
169
+ lastFailReason: this.lastFailReason,
170
+ metadata: this.metadata,
171
+ ...(this.knowledgeContext ? { knowledgeContext: this.knowledgeContext } : {}),
172
+ };
173
+ }
174
+
175
+ static fromJSON(data) {
176
+ if (!data) return new Task();
177
+ return new Task(data);
178
+ }
179
+
180
+ /**
181
+ * 从数据库行构造 Task(snake_case → camelCase)
182
+ */
183
+ static fromRow(row) {
184
+ if (!row) return null;
185
+ return new Task({
186
+ id: row.id,
187
+ parentId: row.parent_id,
188
+ childSeq: row.child_seq,
189
+ title: row.title,
190
+ description: row.description,
191
+ design: row.design,
192
+ acceptance: row.acceptance,
193
+ notes: row.notes,
194
+ status: row.status,
195
+ priority: row.priority,
196
+ taskType: row.task_type,
197
+ closeReason: row.close_reason,
198
+ contentHash: row.content_hash,
199
+ failCount: row.fail_count,
200
+ lastFailReason: row.last_fail_reason,
201
+ assignee: row.assignee,
202
+ createdBy: row.created_by,
203
+ createdAt: row.created_at,
204
+ updatedAt: row.updated_at,
205
+ closedAt: row.closed_at,
206
+ metadata: (() => {
207
+ if (typeof row.metadata !== 'string') return row.metadata || {};
208
+ try { return JSON.parse(row.metadata); } catch { return {}; }
209
+ })(),
210
+ });
211
+ }
212
+ }
213
+
214
+ export default Task;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * 任务依赖类型
3
+ *
4
+ * Phase 1 引入 6 种核心依赖类型,按 "是否影响就绪" 分为两组。
5
+ *
6
+ * 阻塞型: blocks, waits-for → 影响 ready 计算
7
+ * 结构型: parent-child → 仅层次关系
8
+ * 关联型: discovered-from, related, knowledge-ref → 不影响 ready
9
+ */
10
+ export const DepType = Object.freeze({
11
+ // ── 阻塞型(影响 ready 计算)──
12
+ BLOCKS: 'blocks',
13
+ WAITS_FOR: 'waits-for',
14
+
15
+ // ── 结构型(建立层次,不影响 ready)──
16
+ PARENT_CHILD: 'parent-child',
17
+
18
+ // ── 关联型(不影响 ready,构建知识/因果图谱)──
19
+ DISCOVERED_FROM: 'discovered-from',
20
+ RELATED: 'related',
21
+ KNOWLEDGE_REF: 'knowledge-ref', // AutoSnippet 独有:关联知识条目
22
+ });
23
+
24
+ /** 所有合法的依赖类型值列表 */
25
+ const ALL_DEP_TYPES = Object.values(DepType);
26
+
27
+ /** 影响就绪计算的依赖类型集合 */
28
+ const BLOCKING_TYPES = new Set([DepType.BLOCKS, DepType.WAITS_FOR]);
29
+
30
+ /**
31
+ * 判断依赖类型是否影响就绪计算
32
+ * @param {string} depType
33
+ * @returns {boolean}
34
+ */
35
+ export function affectsReadyWork(depType) {
36
+ return BLOCKING_TYPES.has(depType);
37
+ }
38
+
39
+ /**
40
+ * 判断依赖类型是否合法
41
+ * @param {string} depType
42
+ * @returns {boolean}
43
+ */
44
+ export function isValidDepType(depType) {
45
+ return ALL_DEP_TYPES.includes(depType);
46
+ }
47
+
48
+ export default DepType;
@@ -0,0 +1,83 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+
4
+ /**
5
+ * TaskIdGenerator — 短 Hash ID 生成器
6
+ *
7
+ * 渐进式长度扩展(随任务数增长自动加长):
8
+ * 0-500 tasks → 4 字符 (16^4 = 65,536 组合)
9
+ * 500-1500 → 5 字符 (16^5 = 1,048,576)
10
+ * 1500+ → 6 字符 (16^6 = 16,777,216)
11
+ *
12
+ * 前缀 'asd-',与 Beads 的 'bd-' 区分。
13
+ */
14
+ export class TaskIdGenerator {
15
+ /**
16
+ * @param {import('better-sqlite3').Database} db — raw SQLite handle
17
+ */
18
+ constructor(db) {
19
+ this._db = db;
20
+ this._prefix = 'asd';
21
+ }
22
+
23
+ /**
24
+ * 生成新的短 Hash ID
25
+ * @returns {string} 如 'asd-a1b2'
26
+ */
27
+ generate() {
28
+ const taskCount = this._getTaskCount();
29
+ const hashLen = taskCount < 500 ? 4 : taskCount < 1500 ? 5 : 6;
30
+
31
+ // 尝试生成无冲突 ID(最多 10 次)
32
+ for (let attempt = 0; attempt < 10; attempt++) {
33
+ const uuid = uuidv4();
34
+ const hash = createHash('sha256').update(uuid).digest('hex');
35
+ const shortHash = hash.substring(0, hashLen);
36
+ const id = `${this._prefix}-${shortHash}`;
37
+
38
+ if (!this._exists(id)) {
39
+ return id;
40
+ }
41
+ }
42
+
43
+ // 回退到 6 位 + 冲突检查 + 终极 8 位兜底
44
+ const uuid = uuidv4();
45
+ const hash = createHash('sha256').update(uuid).digest('hex');
46
+ const fallbackId = `${this._prefix}-${hash.substring(0, 6)}`;
47
+ if (!this._exists(fallbackId)) return fallbackId;
48
+ return `${this._prefix}-${hash.substring(0, 8)}`;
49
+ }
50
+
51
+ /**
52
+ * 生成子任务 ID
53
+ * asd-a3f8 → asd-a3f8.1, asd-a3f8.2, ...
54
+ * @param {string} parentId
55
+ * @returns {string}
56
+ */
57
+ generateChild(parentId) {
58
+ const parent = this._db.prepare('SELECT child_seq FROM tasks WHERE id = ?').get(parentId);
59
+
60
+ if (!parent) {
61
+ throw new Error(`Parent task not found: ${parentId}`);
62
+ }
63
+
64
+ const nextSeq = (parent.child_seq || 0) + 1;
65
+ this._db.prepare('UPDATE tasks SET child_seq = ? WHERE id = ?').run(nextSeq, parentId);
66
+
67
+ return `${parentId}.${nextSeq}`;
68
+ }
69
+
70
+ /** @private */
71
+ _getTaskCount() {
72
+ const row = this._db.prepare('SELECT COUNT(*) as cnt FROM tasks').get();
73
+ return row?.cnt || 0;
74
+ }
75
+
76
+ /** @private */
77
+ _exists(id) {
78
+ const row = this._db.prepare('SELECT 1 FROM tasks WHERE id = ?').get(id);
79
+ return !!row;
80
+ }
81
+ }
82
+
83
+ export default TaskIdGenerator;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Task Domain — 统一导出
3
+ */
4
+ export { Task } from './Task.js';
5
+ export { DepType, affectsReadyWork, isValidDepType } from './TaskDependency.js';
6
+ export { TaskIdGenerator } from './TaskIdGenerator.js';
@@ -4,7 +4,7 @@
4
4
  * Model Context Protocol (stdio transport)
5
5
  * 提供给 IDE AI Agent (Cursor/VSCode Copilot) 的工具集
6
6
  *
7
- * V3.1 整合:39 → 19 工具(15 agent + 4 admin)
7
+ * V3.1 整合:39 → 20 工具(16 agent + 4 admin)
8
8
  * 通过 ASD_MCP_TIER 环境变量控制可见工具集(agent/admin)
9
9
  *
10
10
  * 冷启动双路径:
@@ -38,8 +38,7 @@ import * as systemHandlers from './handlers/system.js';
38
38
  // ─── External Agent Bootstrap 新 handler ──────────────────────
39
39
 
40
40
  import { bootstrapExternal } from './handlers/bootstrap-external.js';
41
- import { dimensionComplete } from './handlers/dimension-complete-external.js';
42
- import { wikiFinalize, wikiPlan } from './handlers/wiki-external.js';
41
+ import { dimensionComplete } from './handlers/dimension-complete-external.js';import { taskHandler } from './handlers/task.js';import { wikiFinalize, wikiPlan } from './handlers/wiki-external.js';
43
42
 
44
43
  // ─── McpServer 类 ─────────────────────────────────────────────
45
44
 
@@ -180,7 +179,7 @@ export class McpServer {
180
179
  */
181
180
  _resolveHandler(name) {
182
181
  const HANDLER_MAP = {
183
- // ── Agent 层 (15) ──
182
+ // ── Agent 层 (16) ──
184
183
  autosnippet_health: (ctx) => systemHandlers.health(ctx),
185
184
  autosnippet_capabilities: () => systemHandlers.capabilities(),
186
185
  autosnippet_search: (ctx, args) => consolidated.consolidatedSearch(ctx, args),
@@ -193,6 +192,7 @@ export class McpServer {
193
192
  knowledgeHandlers.submitKnowledgeBatch(ctx, args),
194
193
  autosnippet_save_document: (ctx, args) => knowledgeHandlers.saveDocument(ctx, args),
195
194
  autosnippet_skill: (ctx, args) => consolidated.consolidatedSkill(ctx, args),
195
+ autosnippet_task: (ctx, args) => taskHandler(ctx, args),
196
196
  // ── External Agent Bootstrap (v3.1) ──
197
197
  autosnippet_bootstrap: (ctx, _args) => bootstrapExternal(ctx),
198
198
  autosnippet_dimension_complete: (ctx, args) => dimensionComplete(ctx, args),