speccore 7.1.0 → 7.2.1

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 (64) hide show
  1. package/.agents/skills/spec-ask/SKILL.md +10 -5
  2. package/README.md +24 -0
  3. package/dist/cli.js +4 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/analyze.d.ts +6 -0
  6. package/dist/commands/analyze.d.ts.map +1 -1
  7. package/dist/commands/analyze.js +607 -56
  8. package/dist/commands/analyze.js.map +1 -1
  9. package/dist/commands/ask.d.ts +1 -1
  10. package/dist/commands/ask.d.ts.map +1 -1
  11. package/dist/commands/ask.js +2 -2
  12. package/dist/commands/ask.js.map +1 -1
  13. package/dist/commands/config.js +2 -2
  14. package/dist/commands/config.js.map +1 -1
  15. package/dist/commands/dev.js +30 -8
  16. package/dist/commands/dev.js.map +1 -1
  17. package/dist/commands/init.js +16 -276
  18. package/dist/commands/init.js.map +1 -1
  19. package/dist/commands/iteration-from-global.js +2 -2
  20. package/dist/commands/prompts.d.ts +7 -0
  21. package/dist/commands/prompts.d.ts.map +1 -1
  22. package/dist/commands/prompts.js +650 -52
  23. package/dist/commands/prompts.js.map +1 -1
  24. package/dist/commands/status.d.ts.map +1 -1
  25. package/dist/commands/status.js +149 -0
  26. package/dist/commands/status.js.map +1 -1
  27. package/dist/core/ask-engine.d.ts.map +1 -1
  28. package/dist/core/ask-engine.js +58 -3
  29. package/dist/core/ask-engine.js.map +1 -1
  30. package/dist/core/change-impact-global.d.ts +20 -0
  31. package/dist/core/change-impact-global.d.ts.map +1 -0
  32. package/dist/core/change-impact-global.js +130 -0
  33. package/dist/core/change-impact-global.js.map +1 -0
  34. package/dist/core/dev-llm.d.ts +10 -0
  35. package/dist/core/dev-llm.d.ts.map +1 -1
  36. package/dist/core/dev-llm.js.map +1 -1
  37. package/dist/core/doc-cross-reference.d.ts +10 -0
  38. package/dist/core/doc-cross-reference.d.ts.map +1 -0
  39. package/dist/core/doc-cross-reference.js +108 -0
  40. package/dist/core/doc-cross-reference.js.map +1 -0
  41. package/dist/core/doc-quality-gate.d.ts +28 -0
  42. package/dist/core/doc-quality-gate.d.ts.map +1 -0
  43. package/dist/core/doc-quality-gate.js +209 -0
  44. package/dist/core/doc-quality-gate.js.map +1 -0
  45. package/dist/core/incremental-analyzer.js +1 -1
  46. package/dist/core/incremental-analyzer.js.map +1 -1
  47. package/dist/core/intent-recognition.d.ts.map +1 -1
  48. package/dist/core/intent-recognition.js +61 -2
  49. package/dist/core/intent-recognition.js.map +1 -1
  50. package/dist/core/iteration-cache.d.ts +48 -0
  51. package/dist/core/iteration-cache.d.ts.map +1 -0
  52. package/dist/core/iteration-cache.js +148 -0
  53. package/dist/core/iteration-cache.js.map +1 -0
  54. package/dist/core/platform-addition.js +3 -3
  55. package/dist/core/platform-addition.js.map +1 -1
  56. package/dist/core/semantic-locator.d.ts +42 -0
  57. package/dist/core/semantic-locator.d.ts.map +1 -0
  58. package/dist/core/semantic-locator.js +290 -0
  59. package/dist/core/semantic-locator.js.map +1 -0
  60. package/dist/core/structured-extractor.d.ts +89 -0
  61. package/dist/core/structured-extractor.d.ts.map +1 -0
  62. package/dist/core/structured-extractor.js +428 -0
  63. package/dist/core/structured-extractor.js.map +1 -0
  64. package/package.json +1 -1
@@ -15,7 +15,8 @@ async function promptsCommand(options) {
15
15
  const userPrompts = await loadUserPrompts();
16
16
  const allPrompts = [...presets, ...userPrompts];
17
17
  // 生成 HTML 页面
18
- const html = generatePromptsHtml(allPrompts);
18
+ const ctx = await readCurrentContext();
19
+ const html = generatePromptsHtml(attachParamFlags(applyDynamicDefaults(allPrompts, ctx)));
19
20
  const outputPath = options.output || (0, path_1.join)(process.cwd(), 'outputs', 'speccore-prompts.html');
20
21
  await (0, fs_extra_1.ensureDir)((0, path_1.join)(process.cwd(), 'outputs'));
21
22
  await (0, fs_extra_1.writeFile)(outputPath, html);
@@ -60,6 +61,80 @@ async function loadUserPrompts() {
60
61
  catch { }
61
62
  return prompts.sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''));
62
63
  }
64
+ /** 读取当前运行时上下文(.speccore/local/context.json 的 currentIteration/currentTask) */
65
+ async function readCurrentContext() {
66
+ try {
67
+ const ctxPath = (0, path_1.join)(process.cwd(), '.speccore', 'local', 'context.json');
68
+ if (!(await (0, fs_extra_1.pathExists)(ctxPath)))
69
+ return {};
70
+ const data = JSON.parse(await (0, fs_extra_1.readFile)(ctxPath, 'utf-8'));
71
+ return {
72
+ iteration: data.currentIteration || '',
73
+ task: data.currentTask || '',
74
+ };
75
+ }
76
+ catch {
77
+ return {};
78
+ }
79
+ }
80
+ /**
81
+ * 动态填充默认值:迭代名/任务ID 参数的占位符优先取当前运行时上下文,
82
+ * 取不到时保持原 placeholder(如 Q2 / Task-001)。
83
+ */
84
+ function applyDynamicDefaults(prompts, ctx) {
85
+ if (!ctx.iteration && !ctx.task)
86
+ return prompts;
87
+ return prompts.map(p => ({
88
+ ...p,
89
+ params: (p.params || []).map(pr => {
90
+ if (ctx.iteration && pr.key === '迭代名' && pr.placeholder === 'Q2') {
91
+ return { ...pr, placeholder: ctx.iteration };
92
+ }
93
+ if (ctx.task && pr.key === '任务ID' && pr.placeholder === 'Task-001') {
94
+ return { ...pr, placeholder: ctx.task };
95
+ }
96
+ return pr;
97
+ }),
98
+ }));
99
+ }
100
+ /**
101
+ * 为每个参数推断 CLI flag,用于「复制 Skill」生成 flag 风格参数(如 `--task X -I Y`)。
102
+ * 优先从 command 字段解析(`--task {任务ID}` → 任务ID 对应 `--task`),
103
+ * 缺失时回退到 skill 名称的预置映射;两者都没有则视为位置参数(flag 为空)。
104
+ */
105
+ function attachParamFlags(prompts) {
106
+ const SKILL_FLAGS = {
107
+ 'spec-dev': { '迭代名': '-i', '阶段': '--from' },
108
+ 'spec-analyze': { '迭代名': '-I', '文档名': '--deep', '功能名': '--feature', '模块关键词': '--filter' },
109
+ 'spec-split': { '迭代名': '-i', '功能模块': '--filter' },
110
+ 'spec-change': { '迭代名': '-i' },
111
+ 'spec-iteration-create': { '迭代名': '-n', '主题': '--topic', '负责人': '--owner', '需求文档路径': '-f' },
112
+ 'spec-execute': { '迭代名': '-i', '任务ID': '--task', '数量': '--batch-size' },
113
+ 'spec-pr': { '迭代名': '-i', '任务ID': '--task' },
114
+ 'spec-done': { '迭代名': '-i', '任务ID': '--task' },
115
+ 'spec-doc2spec': { '文件路径': '-f', '迭代名': '--iter', '平台': '-p' },
116
+ 'spec-spec2doc': { '迭代名': '-i', '输出文件名': '-o' },
117
+ };
118
+ return prompts.map(p => {
119
+ if (!p.params || p.params.length === 0)
120
+ return p;
121
+ // 从 command 字段解析 flag → 参数 key 映射(单点事实来源)
122
+ const cmdFlags = {};
123
+ if (p.command) {
124
+ const re = /(--?[A-Za-z][\w-]*)\s+["']?\{([^}]+)\}["']?/g;
125
+ let m;
126
+ while ((m = re.exec(p.command)) !== null) {
127
+ cmdFlags[m[2]] = m[1];
128
+ }
129
+ }
130
+ const skillFlags = (p.skill && SKILL_FLAGS[p.skill]) || {};
131
+ const params = p.params.map(pr => ({
132
+ ...pr,
133
+ flag: cmdFlags[pr.key] || skillFlags[pr.key] || '',
134
+ }));
135
+ return { ...p, params };
136
+ });
137
+ }
63
138
  function generatePromptsHtml(prompts) {
64
139
  const categories = [...new Set(prompts.map(p => p.category))];
65
140
  const categoryLabels = {
@@ -67,6 +142,7 @@ function generatePromptsHtml(prompts) {
67
142
  iteration: '迭代管理',
68
143
  analysis: '分析文档',
69
144
  execute: '开发执行',
145
+ governance: '治理运维',
70
146
  custom: '我的'
71
147
  };
72
148
  return `<!DOCTYPE html>
@@ -97,7 +173,32 @@ function generatePromptsHtml(prompts) {
97
173
  padding: 30px;
98
174
  text-align: center;
99
175
  }
100
- .header h1 { font-size: 28px; margin-bottom: 8px; }
176
+ .header h1 {
177
+ font-size: 28px;
178
+ margin-bottom: 8px;
179
+ text-shadow:
180
+ 0 0 10px rgba(255,255,255,0.6),
181
+ 0 0 20px rgba(255,255,255,0.4),
182
+ 0 0 40px rgba(102,126,234,0.6),
183
+ 0 0 80px rgba(118,75,162,0.4);
184
+ animation: glowPulse 2s ease-in-out infinite alternate;
185
+ }
186
+ @keyframes glowPulse {
187
+ from {
188
+ text-shadow:
189
+ 0 0 10px rgba(255,255,255,0.5),
190
+ 0 0 20px rgba(255,255,255,0.3),
191
+ 0 0 40px rgba(102,126,234,0.5),
192
+ 0 0 80px rgba(118,75,162,0.3);
193
+ }
194
+ to {
195
+ text-shadow:
196
+ 0 0 15px rgba(255,255,255,0.9),
197
+ 0 0 30px rgba(255,255,255,0.7),
198
+ 0 0 60px rgba(102,126,234,0.9),
199
+ 0 0 100px rgba(118,75,162,0.7);
200
+ }
201
+ }
101
202
  .header p { opacity: 0.9; font-size: 14px; }
102
203
  .toolbar {
103
204
  padding: 20px 30px;
@@ -164,6 +265,9 @@ function generatePromptsHtml(prompts) {
164
265
  .btn-secondary:hover { background: #dee2e6; }
165
266
  .content {
166
267
  padding: 30px;
268
+ min-height: 520px;
269
+ max-height: 80vh;
270
+ overflow-y: auto;
167
271
  }
168
272
  .section-title {
169
273
  font-size: 18px;
@@ -188,6 +292,7 @@ function generatePromptsHtml(prompts) {
188
292
  height: 100%;
189
293
  display: flex;
190
294
  flex-direction: column;
295
+ position: relative;
191
296
  }
192
297
  .prompt-card:hover {
193
298
  border-color: #667eea;
@@ -246,6 +351,45 @@ function generatePromptsHtml(prompts) {
246
351
  height: 30px;
247
352
  background: linear-gradient(transparent, #f8f9fa);
248
353
  }
354
+ .prompt-section {
355
+ font-size: 11px;
356
+ font-weight: 600;
357
+ color: #868e96;
358
+ margin-bottom: 6px;
359
+ display: flex;
360
+ align-items: center;
361
+ gap: 4px;
362
+ text-transform: uppercase;
363
+ letter-spacing: 0.5px;
364
+ }
365
+ .prompt-say {
366
+ background: #e7f5ff;
367
+ border-radius: 8px;
368
+ padding: 12px;
369
+ font-size: 13px;
370
+ color: #1864ab;
371
+ margin-bottom: 12px;
372
+ line-height: 1.5;
373
+ border-left: 3px solid #339af0;
374
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
375
+ }
376
+ .prompt-cmd {
377
+ background: #212529;
378
+ border-radius: 8px;
379
+ padding: 12px;
380
+ font-size: 12px;
381
+ color: #69db7c;
382
+ margin-bottom: 12px;
383
+ line-height: 1.6;
384
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
385
+ overflow-x: auto;
386
+ white-space: pre-wrap;
387
+ word-break: break-all;
388
+ }
389
+ .prompt-cmd code {
390
+ color: #69db7c;
391
+ font-family: inherit;
392
+ }
249
393
  .prompt-actions {
250
394
  display: flex;
251
395
  gap: 8px;
@@ -255,6 +399,18 @@ function generatePromptsHtml(prompts) {
255
399
  padding: 8px 12px;
256
400
  font-size: 13px;
257
401
  }
402
+ .btn-say {
403
+ background: #e7f5ff;
404
+ color: #1864ab;
405
+ border: 1px solid #74c0fc;
406
+ }
407
+ .btn-say:hover { background: #d0ebff; }
408
+ .btn-cmd {
409
+ background: #212529;
410
+ color: #69db7c;
411
+ border: 1px solid #495057;
412
+ }
413
+ .btn-cmd:hover { background: #343a40; }
258
414
  .badge {
259
415
  display: inline-block;
260
416
  padding: 2px 8px;
@@ -270,6 +426,76 @@ function generatePromptsHtml(prompts) {
270
426
  background: #fff3bf;
271
427
  color: #e67700;
272
428
  }
429
+ .env-hint {
430
+ font-size: 11px;
431
+ color: #868e96;
432
+ margin-bottom: 8px;
433
+ display: flex;
434
+ align-items: center;
435
+ gap: 4px;
436
+ }
437
+ .env-hint.cli {
438
+ color: #2b8a3e;
439
+ }
440
+ .env-hint.ai {
441
+ color: #1971c2;
442
+ }
443
+ .prompt-skill {
444
+ margin-top: 10px;
445
+ padding-top: 10px;
446
+ border-top: 1px dashed rgba(51, 154, 240, 0.3);
447
+ font-size: 12px;
448
+ color: #1864ab;
449
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
450
+ display: flex;
451
+ align-items: center;
452
+ gap: 6px;
453
+ }
454
+ .prompt-skill-tag {
455
+ display: inline-block;
456
+ padding: 2px 8px;
457
+ background: rgba(51, 154, 240, 0.15);
458
+ border-radius: 4px;
459
+ font-size: 11px;
460
+ font-weight: 600;
461
+ cursor: pointer;
462
+ transition: background 0.2s;
463
+ }
464
+ .prompt-skill-tag:hover {
465
+ background: rgba(51, 154, 240, 0.3);
466
+ }
467
+ .btn-skill {
468
+ background: #f3f0ff;
469
+ color: #7048e8;
470
+ border: 1px solid #d0bfff;
471
+ }
472
+ .btn-skill:hover { background: #e5dbff; }
473
+ .prompt-examples {
474
+ margin-top: 10px;
475
+ padding: 10px 12px;
476
+ background: #fff9db;
477
+ border-radius: 8px;
478
+ border-left: 3px solid #fcc419;
479
+ }
480
+ .prompt-examples-title {
481
+ font-size: 11px;
482
+ font-weight: 600;
483
+ color: #e67700;
484
+ margin-bottom: 6px;
485
+ display: flex;
486
+ align-items: center;
487
+ gap: 4px;
488
+ }
489
+ .prompt-examples ul {
490
+ margin: 0;
491
+ padding-left: 16px;
492
+ font-size: 12px;
493
+ color: #495057;
494
+ line-height: 1.6;
495
+ }
496
+ .prompt-examples li {
497
+ margin-bottom: 2px;
498
+ }
273
499
  .empty-state {
274
500
  text-align: center;
275
501
  padding: 60px 20px;
@@ -346,6 +572,147 @@ function generatePromptsHtml(prompts) {
346
572
  gap: 8px;
347
573
  justify-content: flex-end;
348
574
  }
575
+ /* ═══ 场景快捷入口 ═══ */
576
+ .scene-bar {
577
+ padding: 16px 30px;
578
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
579
+ border-bottom: 1px solid #dee2e6;
580
+ display: flex;
581
+ gap: 12px;
582
+ align-items: center;
583
+ flex-wrap: wrap;
584
+ }
585
+ .scene-label {
586
+ font-size: 12px;
587
+ font-weight: 600;
588
+ color: #868e96;
589
+ text-transform: uppercase;
590
+ letter-spacing: 0.5px;
591
+ margin-right: 4px;
592
+ }
593
+ .scene-card {
594
+ display: flex;
595
+ align-items: center;
596
+ gap: 8px;
597
+ padding: 8px 14px;
598
+ background: white;
599
+ border: 1px solid #e9ecef;
600
+ border-radius: 8px;
601
+ cursor: pointer;
602
+ font-size: 13px;
603
+ font-weight: 500;
604
+ color: #495057;
605
+ transition: all 0.2s;
606
+ white-space: nowrap;
607
+ }
608
+ .scene-card:hover {
609
+ border-color: #667eea;
610
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.12);
611
+ transform: translateY(-1px);
612
+ }
613
+ .scene-card.active {
614
+ background: #667eea;
615
+ color: white;
616
+ border-color: #667eea;
617
+ }
618
+ .scene-card .scene-icon {
619
+ font-size: 16px;
620
+ }
621
+ /* ═══ 标签云 ═══ */
622
+ .tag-cloud {
623
+ padding: 12px 30px;
624
+ background: #f8f9fa;
625
+ border-bottom: 1px solid #e9ecef;
626
+ display: flex;
627
+ gap: 8px;
628
+ align-items: center;
629
+ flex-wrap: wrap;
630
+ transition: max-height 0.3s ease;
631
+ overflow: hidden;
632
+ }
633
+ .tag-cloud.collapsed {
634
+ max-height: 48px;
635
+ }
636
+ .tag-cloud.expanded {
637
+ max-height: 500px;
638
+ }
639
+ .tag-toggle-bar {
640
+ text-align: center;
641
+ padding: 0 30px 10px;
642
+ background: #f8f9fa;
643
+ border-bottom: 1px solid #e9ecef;
644
+ }
645
+ .tag-toggle {
646
+ display: inline-flex;
647
+ align-items: center;
648
+ gap: 4px;
649
+ padding: 5px 14px;
650
+ background: #e9ecef;
651
+ border: 1px solid #dee2e6;
652
+ border-radius: 20px;
653
+ font-size: 12px;
654
+ color: #495057;
655
+ cursor: pointer;
656
+ transition: all 0.2s;
657
+ white-space: nowrap;
658
+ }
659
+ .tag-toggle:hover {
660
+ background: #dee2e6;
661
+ }
662
+ .tag-cloud-label {
663
+ font-size: 12px;
664
+ font-weight: 600;
665
+ color: #868e96;
666
+ margin-right: 4px;
667
+ }
668
+ .tag {
669
+ display: inline-flex;
670
+ align-items: center;
671
+ gap: 4px;
672
+ padding: 4px 10px;
673
+ background: white;
674
+ border: 1px solid #e9ecef;
675
+ border-radius: 20px;
676
+ font-size: 12px;
677
+ color: #495057;
678
+ cursor: pointer;
679
+ transition: all 0.2s;
680
+ }
681
+ .tag:hover {
682
+ border-color: #667eea;
683
+ color: #667eea;
684
+ }
685
+ .tag.active {
686
+ background: #667eea;
687
+ color: white;
688
+ border-color: #667eea;
689
+ }
690
+ .tag .tag-count {
691
+ font-size: 10px;
692
+ background: #f1f3f5;
693
+ color: #868e96;
694
+ padding: 1px 5px;
695
+ border-radius: 10px;
696
+ }
697
+ .tag.active .tag-count {
698
+ background: rgba(255,255,255,0.25);
699
+ color: white;
700
+ }
701
+ /* ═══ 卡片标签 ═══ */
702
+ .prompt-tags {
703
+ display: flex;
704
+ gap: 6px;
705
+ flex-wrap: wrap;
706
+ margin-bottom: 12px;
707
+ }
708
+ .prompt-tag {
709
+ display: inline-block;
710
+ padding: 2px 8px;
711
+ background: #f1f3f5;
712
+ border-radius: 4px;
713
+ font-size: 11px;
714
+ color: #868e96;
715
+ }
349
716
  .toast {
350
717
  position: fixed;
351
718
  bottom: 20px;
@@ -371,6 +738,29 @@ function generatePromptsHtml(prompts) {
371
738
  <p>预置模板 + 自定义提示词,一键复制,高效开发</p>
372
739
  </div>
373
740
 
741
+ <!-- 常用场景快捷入口 -->
742
+ <div class="scene-bar" id="sceneBar">
743
+ <span class="scene-label">常用场景</span>
744
+ <div class="scene-card" data-scene="init" onclick="filterByScene('init')">
745
+ <span class="scene-icon">🆕</span><span>首次使用</span>
746
+ </div>
747
+ <div class="scene-card" data-scene="analyze" onclick="filterByScene('analyze')">
748
+ <span class="scene-icon">🔍</span><span>开始分析</span>
749
+ </div>
750
+ <div class="scene-card" data-scene="dev" onclick="filterByScene('dev')">
751
+ <span class="scene-icon">▶️</span><span>开始开发</span>
752
+ </div>
753
+ <div class="scene-card" data-scene="finish" onclick="filterByScene('finish')">
754
+ <span class="scene-icon">✅</span><span>任务收尾</span>
755
+ </div>
756
+ <div class="scene-card" data-scene="daily" onclick="filterByScene('daily')">
757
+ <span class="scene-icon">📅</span><span>日常运维</span>
758
+ </div>
759
+ <div class="scene-card" data-scene="change" onclick="filterByScene('change')">
760
+ <span class="scene-icon">🔧</span><span>需求变更</span>
761
+ </div>
762
+ </div>
763
+
374
764
  <div class="toolbar">
375
765
  <input type="text" class="search-box" id="searchBox" placeholder="搜索提示词名称或内容...">
376
766
  <div class="category-tabs">
@@ -380,6 +770,12 @@ function generatePromptsHtml(prompts) {
380
770
  <button class="btn btn-primary" onclick="openCreateModal()">+ 新建</button>
381
771
  </div>
382
772
 
773
+ <!-- 标签云 -->
774
+ <div class="tag-cloud collapsed" id="tagCloud">
775
+ <span class="tag-cloud-label">🏷️ 标签</span>
776
+ </div>
777
+ <div class="tag-toggle-bar" id="tagToggleBar"></div>
778
+
383
779
  <div class="content" id="content">
384
780
  <!-- 动态渲染 -->
385
781
  </div>
@@ -432,9 +828,82 @@ function generatePromptsHtml(prompts) {
432
828
  let currentCategory = 'all';
433
829
  let searchQuery = '';
434
830
  let editingId = null;
831
+ let currentScene = null;
832
+ let activeTag = null;
833
+ let tagCloudExpanded = false;
834
+
835
+ // 场景 → 标签映射
836
+ const sceneMap = {
837
+ init: ['首次使用', '初始化'],
838
+ analyze: ['分析阶段'],
839
+ dev: ['开发阶段'],
840
+ finish: ['收尾阶段'],
841
+ daily: ['日常', '进度追踪'],
842
+ change: ['变更阶段', '需求调整']
843
+ };
844
+
845
+ // 标签优先级:必要步骤 + 常用命令在前,不常用(收尾、断点等)在后
846
+ const TAG_PRIORITY = [
847
+ '首次使用', '初始化', '分析阶段', '开发阶段', '标准流程',
848
+ '迭代管理', '文档处理', '全局模式', '深度模式', '全自动',
849
+ '日常', '治理', '进度追踪', '变更阶段',
850
+ '收尾阶段', '断点续传', '批量执行', '迭代模式', '按需模式',
851
+ '定向拆分', '增量拆分', '代码提交', '任务管理', '需求调整',
852
+ '变更驱动', '质量门禁'
853
+ ];
854
+
855
+ // 收集所有标签
856
+ function collectTags() {
857
+ const counts = {};
858
+ prompts.forEach(p => {
859
+ (p.tags || []).forEach(tag => {
860
+ counts[tag] = (counts[tag] || 0) + 1;
861
+ });
862
+ });
863
+ return Object.entries(counts).sort((a, b) => {
864
+ const pa = TAG_PRIORITY.indexOf(a[0]);
865
+ const pb = TAG_PRIORITY.indexOf(b[0]);
866
+ const ra = pa === -1 ? TAG_PRIORITY.length : pa;
867
+ const rb = pb === -1 ? TAG_PRIORITY.length : pb;
868
+ if (ra !== rb) return ra - rb;
869
+ return b[1] - a[1];
870
+ });
871
+ }
872
+
873
+ // 渲染标签云
874
+ function renderTagCloud() {
875
+ const tags = collectTags();
876
+ const container = document.getElementById('tagCloud');
877
+ const toggleBar = document.getElementById('tagToggleBar');
878
+ if (!container) return;
879
+
880
+ const needsToggle = tags.length > 6;
881
+ container.className = 'tag-cloud ' + (tagCloudExpanded || !needsToggle ? 'expanded' : 'collapsed');
882
+
883
+ let html = '<span class="tag-cloud-label">🏷️ 标签</span>';
884
+ html += '<span class="tag' + (activeTag ? '' : ' active') + '" onclick="filterByTag(null)">全部</span>';
885
+ tags.forEach(([tag, count]) => {
886
+ const isActive = activeTag === tag;
887
+ html += \`<span class="tag \${isActive ? 'active' : ''}" onclick="filterByTag('\${tag}')">\${tag}<span class="tag-count">\${count}</span></span>\`;
888
+ });
889
+ container.innerHTML = html;
890
+
891
+ if (toggleBar) {
892
+ toggleBar.innerHTML = needsToggle
893
+ ? \`<span class="tag-toggle" onclick="toggleTagCloud()">\${tagCloudExpanded ? '▲ 收起' : '▼ 展开更多标签'}</span>\`
894
+ : '';
895
+ toggleBar.style.display = needsToggle ? 'block' : 'none';
896
+ }
897
+ }
898
+
899
+ function toggleTagCloud() {
900
+ tagCloudExpanded = !tagCloudExpanded;
901
+ renderTagCloud();
902
+ }
435
903
 
436
904
  // 初始化
437
905
  document.addEventListener('DOMContentLoaded', () => {
906
+ renderTagCloud();
438
907
  render();
439
908
  document.getElementById('searchBox').addEventListener('input', (e) => {
440
909
  searchQuery = e.target.value.toLowerCase();
@@ -450,14 +919,34 @@ function generatePromptsHtml(prompts) {
450
919
  });
451
920
  });
452
921
 
922
+ function filterByScene(scene) {
923
+ if (currentScene === scene) {
924
+ currentScene = null;
925
+ } else {
926
+ currentScene = scene;
927
+ }
928
+ document.querySelectorAll('.scene-card').forEach(c => {
929
+ c.classList.toggle('active', c.dataset.scene === currentScene);
930
+ });
931
+ render();
932
+ }
933
+
934
+ function filterByTag(tag) {
935
+ activeTag = tag;
936
+ renderTagCloud();
937
+ render();
938
+ }
939
+
453
940
  function render() {
454
941
  const filtered = prompts.filter(p => {
455
942
  const matchCategory = currentCategory === 'all' || p.category === currentCategory;
456
- const matchSearch = !searchQuery ||
943
+ const matchSearch = !searchQuery ||
457
944
  p.name.toLowerCase().includes(searchQuery) ||
458
945
  p.description.toLowerCase().includes(searchQuery) ||
459
946
  p.prompt.toLowerCase().includes(searchQuery);
460
- return matchCategory && matchSearch;
947
+ const matchScene = !currentScene || (p.tags || []).some(t => sceneMap[currentScene].includes(t));
948
+ const matchTag = !activeTag || (p.tags || []).includes(activeTag);
949
+ return matchCategory && matchSearch && matchScene && matchTag;
461
950
  });
462
951
 
463
952
  const builtin = filtered.filter(p => p.builtin);
@@ -486,11 +975,49 @@ function generatePromptsHtml(prompts) {
486
975
  document.getElementById('content').innerHTML = html;
487
976
  }
488
977
 
978
+ // 生成带参数的 Skill 命令预览(如 /spec-execute --task {任务ID} -i {迭代名})
979
+ function skillCmdPreview(p) {
980
+ if (!p.skill) return '';
981
+ const args = (p.params || []).map(param => {
982
+ return param.flag ? (param.flag + ' {' + param.key + '}') : ('{' + param.key + '}');
983
+ });
984
+ return '/' + p.skill + (args.length ? ' ' + args.join(' ') : '');
985
+ }
986
+
489
987
  function renderCard(p) {
490
- const badge = p.builtin
988
+ const badge = p.builtin
491
989
  ? '<span class="badge badge-builtin">预置</span>'
492
990
  : '<span class="badge badge-custom">自定义</span>';
493
-
991
+
992
+ const hasCommand = p.command && p.command.trim();
993
+ const cmdBlock = hasCommand
994
+ ? \`<div class="prompt-section">⌨️ 触发命令</div><div class="prompt-cmd">\${escapeHtml(p.command)}</div>\`
995
+ : '';
996
+
997
+ const copyCmdBtn = hasCommand
998
+ ? \`<button class="btn btn-cmd" onclick="copyCommand('\${p.id}')">⌨️ 复制命令</button>\`
999
+ : '';
1000
+
1001
+ const copySkillBtn = p.skill
1002
+ ? \`<button class="btn btn-skill" onclick="copySkill('\${p.id}')">⚡ 复制 Skill</button>\`
1003
+ : '';
1004
+
1005
+ const tagsHtml = (p.tags || []).length > 0
1006
+ ? \`<div class="prompt-tags">\${p.tags.map(t => \`<span class="prompt-tag">\${t}</span>\`).join('')}</div>\`
1007
+ : '';
1008
+
1009
+ const env = p.env || (hasCommand ? 'both' : 'ai');
1010
+ const envHintMap = {
1011
+ cli: \`<div class="env-hint cli">💡 纯 CLI 命令(终端直接执行,无 AI 参与)</div>\`,
1012
+ both: \`<div class="env-hint cli">💡 终端 / AI 均可用(AI 中使用有 AI 参与,终端中直接执行)</div>\`,
1013
+ ai: \`<div class="env-hint ai">💡 仅 AI 使用(在 AI 对话框输入,需 AI 参与)</div>\`
1014
+ };
1015
+ const envHint = envHintMap[env] || envHintMap.ai;
1016
+
1017
+ const examplesHtml = (p.examples || []).length > 0
1018
+ ? \`<div class="prompt-examples"><div class="prompt-examples-title">💡 典型场景</div><ul>\${p.examples.map(e => \`<li>\${escapeHtml(e)}</li>\`).join('')}</ul></div>\`
1019
+ : '';
1020
+
494
1021
  return \`
495
1022
  <div class="prompt-card">
496
1023
  <div class="prompt-header">
@@ -500,9 +1027,19 @@ function generatePromptsHtml(prompts) {
500
1027
  <p>\${p.description || ''}</p>
501
1028
  </div>
502
1029
  </div>
503
- <div class="prompt-preview">\${escapeHtml(p.prompt)}</div>
1030
+ \${tagsHtml}
1031
+ \${envHint}
1032
+ \${examplesHtml}
1033
+ <div class="prompt-section">💬 AI 说法</div>
1034
+ <div class="prompt-say">
1035
+ \${escapeHtml(p.prompt)}
1036
+ \${p.skill ? \`<div class="prompt-skill">快捷:<span class="prompt-skill-tag" onclick="copySkill('\${p.id}')">\${skillCmdPreview(p)}</span></div>\` : ''}
1037
+ </div>
1038
+ \${cmdBlock}
504
1039
  <div class="prompt-actions">
505
- <button class="btn btn-primary" onclick="copyPrompt('\${p.id}')">📋 复制</button>
1040
+ <button class="btn btn-say" onclick="copyPrompt('\${p.id}')">💬 复制说法</button>
1041
+ \${copySkillBtn}
1042
+ \${copyCmdBtn}
506
1043
  \${!p.builtin ? \`
507
1044
  <button class="btn btn-secondary" onclick="editPrompt('\${p.id}')">编辑</button>
508
1045
  <button class="btn btn-secondary" onclick="deletePrompt('\${p.id}')">删除</button>
@@ -522,20 +1059,43 @@ function generatePromptsHtml(prompts) {
522
1059
  const p = prompts.find(x => x.id === id);
523
1060
  if (!p) return;
524
1061
 
525
- // 如果有参数,显示自定义弹框
526
1062
  if (p.params && p.params.length > 0) {
527
- showParamModal(p);
1063
+ showParamModal(p, 'say');
528
1064
  } else {
529
- // 无参数,直接复制
530
1065
  const text = '/spec-ask "' + p.prompt + '"';
531
- copyToClipboard(text);
1066
+ copyToClipboard(text, '💬 AI 说法已复制');
532
1067
  }
533
1068
  }
534
1069
 
535
- function showParamModal(p) {
536
- // 创建参数输入弹框
1070
+ function copyCommand(id) {
1071
+ const p = prompts.find(x => x.id === id);
1072
+ if (!p || !p.command) return;
1073
+
1074
+ if (p.params && p.params.length > 0) {
1075
+ showParamModal(p, 'cmd');
1076
+ } else {
1077
+ copyToClipboard(p.command, '⌨️ 命令已复制');
1078
+ }
1079
+ }
1080
+
1081
+ function copySkill(id) {
1082
+ const p = prompts.find(x => x.id === id);
1083
+ if (!p || !p.skill) return;
1084
+
1085
+ if (p.params && p.params.length > 0) {
1086
+ showParamModal(p, 'skill');
1087
+ } else {
1088
+ const text = p.prompt ? '/' + p.skill + ' ' + p.prompt : '/' + p.skill;
1089
+ copyToClipboard(text, '⚡ Skill 命令已复制');
1090
+ }
1091
+ }
1092
+
1093
+ function showParamModal(p, mode) {
1094
+ const isSay = mode === 'say';
1095
+ const isSkill = mode === 'skill';
537
1096
  const modal = document.createElement('div');
538
1097
  modal.className = 'modal active';
1098
+ modal.dataset.mode = mode;
539
1099
  modal.innerHTML = \`
540
1100
  <div class="modal-content" style="max-width: 600px;">
541
1101
  <div class="modal-header">
@@ -547,96 +1107,134 @@ function generatePromptsHtml(prompts) {
547
1107
  \${p.params.map(param => \`
548
1108
  <div class="form-group">
549
1109
  <label>\${param.key} \${param.required ? '<span style="color: #e03131;">*</span>' : ''}</label>
550
- <input type="text" id="param-\${param.key}" placeholder="\${param.placeholder || ''}" value="\${param.placeholder || ''}" oninput="updatePreview('\${p.id}')">
1110
+ <input type="text" id="param-\${param.key}" placeholder="\${param.placeholder || ''}" value="\${param.placeholder || ''}" oninput="updatePreview('\${p.id}', '\${mode}')">
551
1111
  </div>
552
1112
  \`).join('')}
553
1113
  <div class="form-group" style="margin-top: 20px;">
554
- <label style="font-weight: 600; color: #495057;"> 预览</label>
555
- <div id="preview-box" style="background: #f8f9fa; border-radius: 8px; padding: 12px; font-size: 13px; color: #495057; line-height: 1.6; min-height: 60px; border: 1px solid #e9ecef;">
556
- \${escapeHtml(p.prompt)}
1114
+ <label style="font-weight: 600; color: #495057;">\${isSay ? '💬 预览(AI 说法)' : isSkill ? '⚡ 预览(Skill 命令)' : '⌨️ 预览(触发命令)'}</label>
1115
+ <div id="preview-box" style="\${isSay
1116
+ ? 'background: #e7f5ff; border-radius: 8px; padding: 12px; font-size: 13px; color: #1864ab; line-height: 1.6; min-height: 60px; border: 1px solid #74c0fc;'
1117
+ : isSkill
1118
+ ? 'background: #f3f0ff; border-radius: 8px; padding: 12px; font-size: 13px; color: #7048e8; line-height: 1.6; min-height: 60px; border: 1px solid #d0bfff; font-family: monospace;'
1119
+ : 'background: #212529; border-radius: 8px; padding: 12px; font-size: 12px; color: #69db7c; line-height: 1.6; min-height: 60px; border: 1px solid #495057; font-family: monospace;'
1120
+ }">
1121
+ \${escapeHtml(isSay ? p.prompt : isSkill ? skillCmdPreview(p) : (p.command || ''))}
557
1122
  </div>
558
1123
  </div>
559
1124
  </div>
560
1125
  <div class="modal-footer">
561
1126
  <button class="btn btn-secondary" onclick="this.closest('.modal').remove()">取消</button>
562
- <button class="btn btn-primary" onclick="submitParams('\${p.id}')">📋 复制</button>
1127
+ <button class="btn \${isSay ? 'btn-say' : isSkill ? 'btn-skill' : 'btn-cmd'}" onclick="submitParams('\${p.id}', '\${mode}')">\${isSay ? '💬 复制说法' : isSkill ? '⚡ 复制 Skill' : '⌨️ 复制命令'}</button>
563
1128
  </div>
564
1129
  </div>
565
1130
  \`;
566
1131
  document.body.appendChild(modal);
567
1132
 
568
- // 聚焦第一个输入框
569
1133
  setTimeout(() => {
1134
+ updatePreview(p.id, mode);
570
1135
  const firstInput = modal.querySelector('input');
571
1136
  if (firstInput) firstInput.focus();
572
1137
  }, 100);
573
1138
  }
574
1139
 
575
- function updatePreview(id) {
1140
+ function updatePreview(id, mode) {
576
1141
  const p = prompts.find(x => x.id === id);
577
1142
  if (!p) return;
578
1143
 
579
- let text = p.prompt;
580
- p.params.forEach(param => {
581
- const input = document.getElementById('param-' + param.key);
582
- const value = input ? input.value.trim() : '';
583
- if (value) {
584
- text = text.replace(new RegExp('\\{' + param.key + '\\}', 'g'), value);
585
- }
586
- });
1144
+ const isSay = mode === 'say';
1145
+ const isSkill = mode === 'skill';
1146
+ let text = isSay ? p.prompt : isSkill ? ('/' + p.skill) : (p.command || '');
1147
+
1148
+ if (isSkill) {
1149
+ const args = [];
1150
+ p.params.forEach(param => {
1151
+ const input = document.getElementById('param-' + param.key);
1152
+ const value = input ? input.value.trim() : '';
1153
+ if (value) args.push(param.flag ? (param.flag + ' ' + value) : value);
1154
+ });
1155
+ if (args.length > 0) text += ' ' + args.join(' ');
1156
+ } else {
1157
+ p.params.forEach(param => {
1158
+ const input = document.getElementById('param-' + param.key);
1159
+ const value = input ? input.value.trim() : '';
1160
+ if (value) {
1161
+ text = text.replace(new RegExp('\\{' + param.key + '\\}', 'g'), value);
1162
+ }
1163
+ });
1164
+ }
587
1165
 
588
1166
  const previewBox = document.getElementById('preview-box');
589
1167
  if (previewBox) {
590
- previewBox.innerHTML = '<strong>/spec-ask "</strong>' + escapeHtml(text) + '<strong>"</strong>';
1168
+ if (isSay) {
1169
+ previewBox.innerHTML = '<strong>/spec-ask "</strong>' + escapeHtml(text) + '<strong>"</strong>';
1170
+ } else {
1171
+ previewBox.textContent = text;
1172
+ }
591
1173
  }
592
1174
  }
593
1175
 
594
- function submitParams(id) {
1176
+ function submitParams(id, mode) {
595
1177
  const p = prompts.find(x => x.id === id);
596
1178
  if (!p) return;
597
1179
 
598
- let text = p.prompt;
1180
+ const isSay = mode === 'say';
1181
+ const isSkill = mode === 'skill';
1182
+ let text = isSay ? p.prompt : isSkill ? ('/' + p.skill) : (p.command || '');
599
1183
  let allFilled = true;
600
1184
 
601
- p.params.forEach(param => {
602
- const input = document.getElementById('param-' + param.key);
603
- const value = input ? input.value.trim() : '';
604
-
605
- if (param.required && !value) {
606
- allFilled = false;
607
- input.style.borderColor = '#e03131';
608
- } else {
609
- input.style.borderColor = '#e9ecef';
610
- text = text.replace(new RegExp('\\{' + param.key + '\\}', 'g'), value);
611
- }
612
- });
1185
+ if (isSkill) {
1186
+ const args = [];
1187
+ p.params.forEach(param => {
1188
+ const input = document.getElementById('param-' + param.key);
1189
+ const value = input ? input.value.trim() : '';
1190
+
1191
+ if (param.required && !value) {
1192
+ allFilled = false;
1193
+ input.style.borderColor = '#e03131';
1194
+ } else {
1195
+ input.style.borderColor = '#e9ecef';
1196
+ if (value) args.push(param.flag ? (param.flag + ' ' + value) : value);
1197
+ }
1198
+ });
1199
+ if (args.length > 0) text += ' ' + args.join(' ');
1200
+ } else {
1201
+ p.params.forEach(param => {
1202
+ const input = document.getElementById('param-' + param.key);
1203
+ const value = input ? input.value.trim() : '';
1204
+
1205
+ if (param.required && !value) {
1206
+ allFilled = false;
1207
+ input.style.borderColor = '#e03131';
1208
+ } else {
1209
+ input.style.borderColor = '#e9ecef';
1210
+ text = text.replace(new RegExp('\\{' + param.key + '\\}', 'g'), value);
1211
+ }
1212
+ });
1213
+ }
613
1214
 
614
1215
  if (!allFilled) {
615
1216
  showToast('⚠️ 请填写必填参数');
616
1217
  return;
617
1218
  }
618
1219
 
619
- // 关闭弹框
620
1220
  const modal = document.querySelector('.modal.active');
621
1221
  if (modal) modal.remove();
622
1222
 
623
- // 复制
624
- const finalText = '/spec-ask "' + text + '"';
625
- copyToClipboard(finalText);
1223
+ const finalText = isSay ? ('/spec-ask "' + text + '"') : text;
1224
+ copyToClipboard(finalText, isSay ? '💬 AI 说法已复制' : isSkill ? '⚡ Skill 命令已复制' : '⌨️ 命令已复制');
626
1225
  }
627
1226
 
628
- function copyToClipboard(text) {
1227
+ function copyToClipboard(text, msg) {
629
1228
  navigator.clipboard.writeText(text).then(() => {
630
- showToast('✅ 已复制到剪贴板');
1229
+ showToast(msg || '✅ 已复制到剪贴板');
631
1230
  }).catch(() => {
632
- // 降级方案
633
1231
  const textarea = document.createElement('textarea');
634
1232
  textarea.value = text;
635
1233
  document.body.appendChild(textarea);
636
1234
  textarea.select();
637
1235
  document.execCommand('copy');
638
1236
  document.body.removeChild(textarea);
639
- showToast('✅ 已复制到剪贴板');
1237
+ showToast(msg || '✅ 已复制到剪贴板');
640
1238
  });
641
1239
  }
642
1240