intentdna 1.5.13 → 1.5.15

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.
@@ -0,0 +1,653 @@
1
+ # Template Version + Namespace Cleanup Fix
2
+
3
+ ## Metadata
4
+ - Generated: 2026-04-19
5
+ - Status: APPROVED
6
+ - 预计工期: 2-3 小时
7
+ - 发版类型: patch
8
+
9
+ ## 背景
10
+
11
+ 两个独立 bug 需要一起修:
12
+
13
+ ### Bug 1: _template_version 跟 package 版本走
14
+ **问题**: 改了 flutter-rewrite 模板 → 发版 1.5.13 → 所有模板的 _template_version 都变成 1.5.13,但只有 flutter-rewrite 真正改了内容。
15
+
16
+ **根因**: `injectSourceTemplate()` 用 `getPackageVersion()` 而不是模板自身的 `version` 字段。
17
+
18
+ ### Bug 2: cleanStaleDNAFiles 不区分 namespace
19
+ **问题**: 项目用 frw + be 两个模板,`dna sync frw` 会把 be 的 agent/skill 也删掉。
20
+
21
+ **根因**: `cleanStaleDNAFiles()` 只看 `intentdna:managed` 标记,不看 namespace。
22
+
23
+ ---
24
+
25
+ ## 方案
26
+
27
+ ### 方案 A: Template Content Hash Tracking
28
+
29
+ 每个模板用 content hash 追踪版本,不依赖手动 bump version 字段。
30
+
31
+ **流程**:
32
+ 1. `dna init <template>` 时:
33
+ - 计算模板文件内容的 SHA256 hash
34
+ - 写入项目 config: `_template_content_hash: "abc123..."`
35
+ - 保留 `_template_version` 字段(从模板的 `version` 读取,不是 pkg 版本)
36
+
37
+ 2. `dna sync` 时:
38
+ - 读当前模板文件,计算 hash
39
+ - 对比项目 config 的 `_template_content_hash`
40
+ - 不同 → 提示 "Template content changed. Run: dna init --upgrade <template>"
41
+
42
+ 3. `dna init --upgrade <template>` 时:
43
+ - 更新 `_template_content_hash` 为新 hash
44
+ - 更新 `_template_version` 为模板的 `version` 字段
45
+
46
+ **优点**:
47
+ - 不依赖手动 bump version
48
+ - 任何模板内容变化都能检测到
49
+ - 不同模板独立追踪
50
+
51
+ **缺点**:
52
+ - 用户手动改了项目 config(加自定义 gene)也会触发"模板变化"提示
53
+ - 需要明确告知用户:项目 config 是"模板实例",改动后不再追踪上游模板更新
54
+
55
+ ### 方案 B: Namespace-aware Cleanup
56
+
57
+ `cleanStaleDNAFiles()` 只删当前 sync 的 namespace 对应的文件。
58
+
59
+ **实现**:
60
+ ```typescript
61
+ // 当前
62
+ async function cleanStaleDNAFiles(dir: string, type: "agent" | "skill"): Promise<number>
63
+
64
+ // 修改后
65
+ async function cleanStaleDNAFiles(
66
+ dir: string,
67
+ type: "agent" | "skill",
68
+ activeNamespaces: string[] // 当前 sync 的模板的 namespace 列表
69
+ ): Promise<number>
70
+ ```
71
+
72
+ **逻辑**:
73
+ 1. 扫描 `.claude/agents/` 或 `.claude/skills/`
74
+ 2. 对每个文件/目录:
75
+ - 读内容检查 `intentdna:managed` 标记
76
+ - 如果有标记 → 提取文件名前缀(`dna-frw-*` → `frw`)
77
+ - 如果前缀在 `activeNamespaces` 中 → 删除
78
+ - 否则保留
79
+
80
+ **文件名规则**:
81
+ - Agent MD: `dna-{namespace}-{role}.md`
82
+ - Skill dir: `dna-{namespace}-{workflow}/`
83
+
84
+ **边界情况**:
85
+ - 文件名不符合 `dna-{ns}-*` 格式 → 保留(可能是用户手写的)
86
+ - 没有 `intentdna:managed` 标记 → 保留
87
+ - namespace 不在 activeNamespaces → 保留
88
+
89
+ ---
90
+
91
+ ## 实施步骤
92
+
93
+ ### Step 1: Template Content Hash (Bug 1)
94
+
95
+ #### 1.1 新增 hash 计算函数
96
+
97
+ 文件:`src/cli/commands/init.ts`
98
+
99
+ ```typescript
100
+ import { createHash } from "crypto";
101
+
102
+ /**
103
+ * Calculate SHA256 hash of template content.
104
+ * Used for tracking template changes independent of version field.
105
+ */
106
+ function calculateTemplateHash(content: string): string {
107
+ return createHash("sha256").update(content, "utf-8").digest("hex").slice(0, 16);
108
+ }
109
+ ```
110
+
111
+ #### 1.2 修改 injectSourceTemplate
112
+
113
+ 文件:`src/cli/commands/init.ts:91-100`
114
+
115
+ ```typescript
116
+ // 之前
117
+ function injectSourceTemplate(content: string, templateName: string, version?: string): string {
118
+ const marker = /^(namespace:\s*.+)$/m;
119
+ const typeMarker = /^(type:\s*.+)$/m;
120
+ const match = content.match(marker) || content.match(typeMarker);
121
+ const versionLine = version ? `\n_template_version: "${version}"` : "";
122
+ if (match) {
123
+ return content.replace(match[0], `${match[0]}\n_source_template: ${templateName}${versionLine}`);
124
+ }
125
+ return `_source_template: ${templateName}${versionLine}\n${content}`;
126
+ }
127
+
128
+ // 之后
129
+ function injectSourceTemplate(
130
+ content: string,
131
+ templateName: string,
132
+ templateVersion?: string,
133
+ templateHash?: string
134
+ ): string {
135
+ const marker = /^(namespace:\s*.+)$/m;
136
+ const typeMarker = /^(type:\s*.+)$/m;
137
+ const match = content.match(marker) || content.match(typeMarker);
138
+
139
+ const versionLine = templateVersion ? `\n_template_version: "${templateVersion}"` : "";
140
+ const hashLine = templateHash ? `\n_template_content_hash: "${templateHash}"` : "";
141
+ const metadata = `\n_source_template: ${templateName}${versionLine}${hashLine}`;
142
+
143
+ if (match) {
144
+ return content.replace(match[0], `${match[0]}${metadata}`);
145
+ }
146
+ return `_source_template: ${templateName}${versionLine}${hashLine}\n${content}`;
147
+ }
148
+ ```
149
+
150
+ #### 1.3 修改 init 命令调用点
151
+
152
+ 文件:`src/cli/commands/init.ts`
153
+
154
+ 找到两处 `injectSourceTemplate()` 调用:
155
+
156
+ **调用点 1(约 194 行,--upgrade 路径)**:
157
+ ```typescript
158
+ // 之前
159
+ const pkgVersion = await getPackageVersion();
160
+ newContent = injectSourceTemplate(newContent, found.name, pkgVersion ?? undefined);
161
+
162
+ // 之后
163
+ const templateData = parseYAML(newContent) as Record<string, unknown>;
164
+ const templateVersion = typeof templateData.version === "string" ? templateData.version : undefined;
165
+ const templateHash = calculateTemplateHash(newContent);
166
+ newContent = injectSourceTemplate(newContent, found.name, templateVersion, templateHash);
167
+ ```
168
+
169
+ **调用点 2(约 340 行,新建路径)**:
170
+ ```typescript
171
+ // 之前
172
+ const pkgVersion = await getPackageVersion();
173
+ const contentWithSource = injectSourceTemplate(content, found.name, pkgVersion ?? undefined);
174
+
175
+ // 之后
176
+ const templateData = parseYAML(content) as Record<string, unknown>;
177
+ const templateVersion = typeof templateData.version === "string" ? templateData.version : undefined;
178
+ const templateHash = calculateTemplateHash(content);
179
+ const contentWithSource = injectSourceTemplate(content, found.name, templateVersion, templateHash);
180
+ ```
181
+
182
+ #### 1.4 修改 checkTemplateVersions
183
+
184
+ 文件:`src/cli/commands/sync.ts:334-370`
185
+
186
+ ```typescript
187
+ // 之前
188
+ export async function checkTemplateVersions(configPaths: string[]): Promise<TemplateUpgradeInfo[]> {
189
+ const pkgVersion = await getPackageVersion();
190
+ if (!pkgVersion) return [];
191
+
192
+ const upgrades: TemplateUpgradeInfo[] = [];
193
+
194
+ for (const configPath of configPaths) {
195
+ try {
196
+ const content = await readFile(configPath, "utf-8");
197
+ const data = parseYAML(content) as Record<string, unknown>;
198
+ const tmplVersion = typeof data._template_version === "string" ? data._template_version : undefined;
199
+ const tmplName = typeof data._source_template === "string" ? data._source_template : undefined;
200
+
201
+ if (tmplName && !tmplVersion) {
202
+ // Old config without version tracking — suggest upgrade
203
+ upgrades.push({
204
+ configPath,
205
+ templateName: tmplName,
206
+ currentVersion: "unknown",
207
+ availableVersion: pkgVersion,
208
+ });
209
+ process.stderr.write(
210
+ `Upgrade available: "${tmplName}" has no version tag. Run: dna init --upgrade ${tmplName}\n`,
211
+ );
212
+ } else if (tmplVersion && tmplName && compareSemver(pkgVersion, tmplVersion) > 0) {
213
+ // Package is strictly newer than template — suggest upgrade
214
+ upgrades.push({
215
+ configPath,
216
+ templateName: tmplName,
217
+ currentVersion: tmplVersion,
218
+ availableVersion: pkgVersion,
219
+ });
220
+ process.stderr.write(
221
+ `Upgrade available: "${tmplName}" ${tmplVersion} → ${pkgVersion}. Run: dna init --upgrade ${tmplName}\n`,
222
+ );
223
+ }
224
+ } catch {
225
+ // Fail-open
226
+ }
227
+ }
228
+
229
+ return upgrades;
230
+ }
231
+
232
+ // 之后
233
+ export async function checkTemplateVersions(configPaths: string[]): Promise<TemplateUpgradeInfo[]> {
234
+ const upgrades: TemplateUpgradeInfo[] = [];
235
+
236
+ for (const configPath of configPaths) {
237
+ try {
238
+ const content = await readFile(configPath, "utf-8");
239
+ const data = parseYAML(content) as Record<string, unknown>;
240
+ const tmplName = typeof data._source_template === "string" ? data._source_template : undefined;
241
+ const tmplHash = typeof data._template_content_hash === "string" ? data._template_content_hash : undefined;
242
+ const tmplVersion = typeof data._template_version === "string" ? data._template_version : undefined;
243
+
244
+ if (!tmplName) continue;
245
+
246
+ // Find current template file
247
+ const templatePath = await findTemplateFile(tmplName);
248
+ if (!templatePath) continue;
249
+
250
+ const currentTemplateContent = await readFile(templatePath, "utf-8");
251
+ const currentHash = calculateTemplateHash(currentTemplateContent);
252
+
253
+ if (!tmplHash) {
254
+ // Old config without hash tracking — suggest upgrade
255
+ upgrades.push({
256
+ configPath,
257
+ templateName: tmplName,
258
+ currentVersion: tmplVersion ?? "unknown",
259
+ availableVersion: "content-changed",
260
+ });
261
+ process.stderr.write(
262
+ `Upgrade available: "${tmplName}" has no content hash. Run: dna init --upgrade ${tmplName}\n`,
263
+ );
264
+ } else if (currentHash !== tmplHash) {
265
+ // Template content changed
266
+ upgrades.push({
267
+ configPath,
268
+ templateName: tmplName,
269
+ currentVersion: tmplVersion ?? "unknown",
270
+ availableVersion: "content-changed",
271
+ });
272
+ process.stderr.write(
273
+ `Template content changed: "${tmplName}". Run: dna init --upgrade ${tmplName}\n`,
274
+ );
275
+ }
276
+ } catch {
277
+ // Fail-open
278
+ }
279
+ }
280
+
281
+ return upgrades;
282
+ }
283
+
284
+ /**
285
+ * Find template file path by template name.
286
+ * Searches in src/templates/ directory.
287
+ */
288
+ async function findTemplateFile(templateName: string): Promise<string | null> {
289
+ const templatesDir = resolve(__dirname, "..", "..", "templates");
290
+ try {
291
+ const files = await readdir(templatesDir);
292
+ for (const file of files) {
293
+ if (file.endsWith(".dna.yaml") || file.endsWith(".dna.yml")) {
294
+ const fullPath = resolve(templatesDir, file);
295
+ const content = await readFile(fullPath, "utf-8");
296
+ const data = parseYAML(content) as Record<string, unknown>;
297
+ const id = typeof data.id === "string" ? data.id : undefined;
298
+ const name = typeof data.name === "string" ? data.name : undefined;
299
+ if (id === templateName || name === templateName) {
300
+ return fullPath;
301
+ }
302
+ }
303
+ }
304
+ } catch {
305
+ // Fail-open
306
+ }
307
+ return null;
308
+ }
309
+ ```
310
+
311
+ 需要在文件顶部 import:
312
+ ```typescript
313
+ import { createHash } from "crypto";
314
+ ```
315
+
316
+ 并把 `calculateTemplateHash` 函数从 init.ts 移到共享位置或复制一份。
317
+
318
+ ---
319
+
320
+ ### Step 2: Namespace-aware Cleanup (Bug 2)
321
+
322
+ #### 2.1 修改 cleanStaleDNAFiles 签名
323
+
324
+ 文件:`src/cli/commands/sync.ts:126-141`
325
+
326
+ ```typescript
327
+ // 之前
328
+ async function cleanStaleDNAFiles(dir: string, type: "agent" | "skill"): Promise<number>
329
+
330
+ // 之后
331
+ async function cleanStaleDNAFiles(
332
+ dir: string,
333
+ type: "agent" | "skill",
334
+ activeNamespaces: string[]
335
+ ): Promise<number>
336
+ ```
337
+
338
+ #### 2.2 修改 cleanStaleDNAFiles 实现
339
+
340
+ ```typescript
341
+ async function cleanStaleDNAFiles(
342
+ dir: string,
343
+ type: "agent" | "skill",
344
+ activeNamespaces: string[]
345
+ ): Promise<number> {
346
+ let removed = 0;
347
+ try {
348
+ const entries = await readdir(dir, { withFileTypes: true });
349
+ for (const entry of entries) {
350
+ const fullPath = resolve(dir, entry.name);
351
+
352
+ // Extract namespace from filename
353
+ // Agent: dna-{ns}-{role}.md
354
+ // Skill: dna-{ns}-{workflow}/
355
+ const match = entry.name.match(/^dna-([a-z0-9_-]+)-/);
356
+ if (!match) continue; // Not DNA-managed naming pattern
357
+
358
+ const namespace = match[1];
359
+ if (!activeNamespaces.includes(namespace)) {
360
+ // Not in current sync scope, skip
361
+ continue;
362
+ }
363
+
364
+ // Check for intentdna:managed sentinel
365
+ let hasManaged = false;
366
+ if (entry.isFile()) {
367
+ const content = await readFile(fullPath, "utf-8");
368
+ hasManaged = content.includes("intentdna:managed");
369
+ } else if (entry.isDirectory()) {
370
+ // Check SKILL.md inside skill directory
371
+ const skillMd = resolve(fullPath, "SKILL.md");
372
+ try {
373
+ const content = await readFile(skillMd, "utf-8");
374
+ hasManaged = content.includes("intentdna:managed");
375
+ } catch {
376
+ // No SKILL.md or unreadable
377
+ }
378
+ }
379
+
380
+ if (hasManaged) {
381
+ if (entry.isDirectory()) {
382
+ await rm(fullPath, { recursive: true, force: true });
383
+ } else {
384
+ await unlink(fullPath);
385
+ }
386
+ removed++;
387
+ }
388
+ }
389
+ } catch {
390
+ // Fail-open
391
+ }
392
+ return removed;
393
+ }
394
+ ```
395
+
396
+ #### 2.3 修改调用点
397
+
398
+ 文件:`src/cli/commands/sync.ts`
399
+
400
+ 找到 `cleanStaleDNAFiles` 的调用点(约 600-650 行),传入 activeNamespaces:
401
+
402
+ ```typescript
403
+ // 之前
404
+ if (opts.agentsDir) {
405
+ const removed = await cleanStaleDNAFiles(opts.agentsDir, "agent");
406
+ if (removed > 0) process.stderr.write(`Cleaned ${removed} stale agent file(s)\n`);
407
+ }
408
+ if (opts.skillsDir) {
409
+ const removed = await cleanStaleDNAFiles(opts.skillsDir, "skill");
410
+ if (removed > 0) process.stderr.write(`Cleaned ${removed} stale skill dir(s)\n`);
411
+ }
412
+
413
+ // 之后
414
+ // Extract namespaces from all loaded DNAs
415
+ const activeNamespaces = dnas
416
+ .map(d => typeof d.namespace === "string" ? d.namespace : null)
417
+ .filter((ns): ns is string => ns !== null);
418
+
419
+ if (opts.agentsDir) {
420
+ const removed = await cleanStaleDNAFiles(opts.agentsDir, "agent", activeNamespaces);
421
+ if (removed > 0) process.stderr.write(`Cleaned ${removed} stale agent file(s)\n`);
422
+ }
423
+ if (opts.skillsDir) {
424
+ const removed = await cleanStaleDNAFiles(opts.skillsDir, "skill", activeNamespaces);
425
+ if (removed > 0) process.stderr.write(`Cleaned ${removed} stale skill dir(s)\n`);
426
+ }
427
+ ```
428
+
429
+ ---
430
+
431
+ ### Step 3: 测试
432
+
433
+ #### 3.1 单元测试
434
+
435
+ 新建 `test/template-version-hash.test.ts`:
436
+
437
+ ```typescript
438
+ import { describe, it, expect } from "vitest";
439
+ import { calculateTemplateHash } from "../src/cli/commands/init.js";
440
+
441
+ describe("calculateTemplateHash", () => {
442
+ it("returns consistent hash for same content", () => {
443
+ const content = "namespace: test\nversion: 0.1.0";
444
+ const hash1 = calculateTemplateHash(content);
445
+ const hash2 = calculateTemplateHash(content);
446
+ expect(hash1).toBe(hash2);
447
+ });
448
+
449
+ it("returns different hash for different content", () => {
450
+ const content1 = "namespace: test\nversion: 0.1.0";
451
+ const content2 = "namespace: test\nversion: 0.2.0";
452
+ const hash1 = calculateTemplateHash(content1);
453
+ const hash2 = calculateTemplateHash(content2);
454
+ expect(hash1).not.toBe(hash2);
455
+ });
456
+
457
+ it("returns 16-char hex string", () => {
458
+ const hash = calculateTemplateHash("test");
459
+ expect(hash).toMatch(/^[a-f0-9]{16}$/);
460
+ });
461
+ });
462
+ ```
463
+
464
+ 新建 `test/namespace-cleanup.test.ts`:
465
+
466
+ ```typescript
467
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
468
+ import { mkdir, writeFile, readdir, rm } from "fs/promises";
469
+ import { resolve } from "path";
470
+ import { cleanStaleDNAFiles } from "../src/cli/commands/sync.js";
471
+
472
+ describe("cleanStaleDNAFiles namespace filtering", () => {
473
+ const testDir = resolve(__dirname, ".test-cleanup");
474
+ const agentsDir = resolve(testDir, "agents");
475
+
476
+ beforeEach(async () => {
477
+ await mkdir(agentsDir, { recursive: true });
478
+ });
479
+
480
+ afterEach(async () => {
481
+ await rm(testDir, { recursive: true, force: true });
482
+ });
483
+
484
+ it("only removes files matching active namespaces", async () => {
485
+ // Create files for two namespaces
486
+ await writeFile(
487
+ resolve(agentsDir, "dna-frw-surgeon.md"),
488
+ "<!-- intentdna:managed -->\nFRW surgeon"
489
+ );
490
+ await writeFile(
491
+ resolve(agentsDir, "dna-be-handler.md"),
492
+ "<!-- intentdna:managed -->\nBE handler"
493
+ );
494
+
495
+ // Clean only frw namespace
496
+ const removed = await cleanStaleDNAFiles(agentsDir, "agent", ["frw"]);
497
+
498
+ expect(removed).toBe(1);
499
+ const remaining = await readdir(agentsDir);
500
+ expect(remaining).toEqual(["dna-be-handler.md"]);
501
+ });
502
+
503
+ it("preserves files without intentdna:managed", async () => {
504
+ await writeFile(
505
+ resolve(agentsDir, "dna-frw-custom.md"),
506
+ "User-written agent"
507
+ );
508
+
509
+ const removed = await cleanStaleDNAFiles(agentsDir, "agent", ["frw"]);
510
+
511
+ expect(removed).toBe(0);
512
+ const remaining = await readdir(agentsDir);
513
+ expect(remaining).toEqual(["dna-frw-custom.md"]);
514
+ });
515
+
516
+ it("preserves files not matching dna-{ns}- pattern", async () => {
517
+ await writeFile(
518
+ resolve(agentsDir, "custom-agent.md"),
519
+ "<!-- intentdna:managed -->\nCustom"
520
+ );
521
+
522
+ const removed = await cleanStaleDNAFiles(agentsDir, "agent", ["frw"]);
523
+
524
+ expect(removed).toBe(0);
525
+ const remaining = await readdir(agentsDir);
526
+ expect(remaining).toEqual(["custom-agent.md"]);
527
+ });
528
+ });
529
+ ```
530
+
531
+ #### 3.2 集成测试
532
+
533
+ 在 intentdna 项目:
534
+ ```bash
535
+ npm test # 全绿
536
+ ```
537
+
538
+ 在 lwk_flutter_v2-rewrite 项目(单模板场景):
539
+ ```bash
540
+ dna sync
541
+ # 验证:不报错,agent/skill 正常生成
542
+ ```
543
+
544
+ 创建测试项目(多模板场景):
545
+ ```bash
546
+ mkdir /tmp/multi-template-test && cd /tmp/multi-template-test
547
+ dna init flutter-rewrite
548
+ dna init backend-api # 假设有这个模板,或用另一个
549
+ dna sync
550
+ # 验证:两个模板的 agent/skill 都保留
551
+ ```
552
+
553
+ ---
554
+
555
+ ### Step 4: 文档更新
556
+
557
+ #### 4.1 CHANGELOG.md
558
+
559
+ ```markdown
560
+ ## [1.5.14] - 2026-04-19
561
+
562
+ ### Fixed
563
+ - Template version tracking now uses template's own `version` field instead of package version
564
+ - Added `_template_content_hash` to detect template changes independent of version bumps
565
+ - `dna sync` now only cleans stale files from active namespaces, fixing multi-template projects
566
+ - Namespace-aware cleanup prevents accidental deletion of other templates' generated files
567
+
568
+ ### Changed
569
+ - `dna init` now injects `_template_content_hash` alongside `_template_version`
570
+ - `dna sync` detects template content changes via hash comparison
571
+ ```
572
+
573
+ #### 4.2 docs/multi-template.md(新建)
574
+
575
+ ```markdown
576
+ # Multi-Template Projects
577
+
578
+ Intent DNA supports using multiple templates in a single project.
579
+
580
+ ## Setup
581
+
582
+ ```bash
583
+ dna init flutter-rewrite
584
+ dna init backend-api
585
+ ```
586
+
587
+ Each template must have a unique `namespace` field. Namespace collisions are detected and blocked.
588
+
589
+ ## Sync Behavior
590
+
591
+ `dna sync` processes all templates in `.dna/configs/*.yaml` and only cleans files belonging to active namespaces.
592
+
593
+ Example:
594
+ - `dna-frw-*` files belong to `frw` namespace
595
+ - `dna-be-*` files belong to `be` namespace
596
+ - Syncing `frw` template will not delete `be` files
597
+
598
+ ## Template Updates
599
+
600
+ Each template tracks its content independently via `_template_content_hash`. When template content changes:
601
+
602
+ ```bash
603
+ dna sync
604
+ # Output: Template content changed: "flutter-rewrite". Run: dna init --upgrade flutter-rewrite
605
+ ```
606
+
607
+ Run upgrade to update:
608
+ ```bash
609
+ dna init --upgrade flutter-rewrite
610
+ ```
611
+ ```
612
+
613
+ ---
614
+
615
+ ### Step 5: 发版
616
+
617
+ ```bash
618
+ cd /Users/samuel/lawark/intentdna
619
+ npm test # 全绿
620
+ /dna-release patch "fix: template version tracking + namespace-aware cleanup for multi-template projects"
621
+ ```
622
+
623
+ ---
624
+
625
+ ## 验收清单
626
+
627
+ ### Bug 1: Template Version
628
+ - [ ] `calculateTemplateHash()` 实现
629
+ - [ ] `injectSourceTemplate()` 接受 templateVersion + templateHash 参数
630
+ - [ ] init 命令两处调用点更新(读模板 version 字段,不读 pkg 版本)
631
+ - [ ] `checkTemplateVersions()` 改为 hash 对比
632
+ - [ ] `findTemplateFile()` 辅助函数实现
633
+ - [ ] 单元测试通过
634
+
635
+ ### Bug 2: Namespace Cleanup
636
+ - [ ] `cleanStaleDNAFiles()` 接受 activeNamespaces 参数
637
+ - [ ] 文件名 namespace 提取逻辑(`dna-{ns}-*` 正则)
638
+ - [ ] 只删匹配 namespace 的文件
639
+ - [ ] sync 调用点传入 activeNamespaces
640
+ - [ ] 单元测试通过
641
+
642
+ ### 集成验证
643
+ - [ ] intentdna 项目 npm test 全绿
644
+ - [ ] lwk_flutter_v2-rewrite dna sync 正常
645
+ - [ ] 多模板测试项目验证(如果有第二个模板)
646
+
647
+ ### 文档
648
+ - [ ] CHANGELOG.md 更新
649
+ - [ ] docs/multi-template.md 新建(可选)
650
+
651
+ ### 发版
652
+ - [ ] /dna-release patch 执行
653
+ - [ ] v1.5.14 发布
@@ -1,12 +0,0 @@
1
- {
2
- "hooks": {
3
- "PreToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook PreToolUse", "timeout": 5 }] }],
4
- "PostToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook PostToolUse", "timeout": 3 }] }],
5
- "UserPromptSubmit": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook UserPromptSubmit", "timeout": 5 }] }],
6
- "SubagentStop": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook SubagentStop", "timeout": 3 }] }],
7
- "PreCompact": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook PreCompact", "timeout": 3 }] }],
8
- "Notification": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook Notification", "timeout": 3 }] }],
9
- "Stop": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook Stop", "timeout": 3 }] }],
10
- "SessionStart": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook SessionStart", "timeout": 5 }] }]
11
- }
12
- }
@@ -1,16 +0,0 @@
1
- {
2
- "name": "intentdna",
3
- "description": "Declarative policy layer for AI agent governance",
4
- "owner": {
5
- "name": "Samuel",
6
- "email": "wanglushan3344@gmail.com"
7
- },
8
- "plugins": [
9
- {
10
- "name": "intentdna",
11
- "description": "DNA template compilation + runtime enforcement",
12
- "version": "1.5.13",
13
- "source": "./"
14
- }
15
- ]
16
- }
@@ -1,5 +0,0 @@
1
- {
2
- "name": "intentdna",
3
- "version": "1.5.13",
4
- "description": "Declarative policy layer for AI agent governance"
5
- }