sandtable 0.3.0 → 0.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sandtable",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "AI 编程项目可视化指挥面板 — 双视图 dashboard,多源数据融合",
5
5
  "main": "src/cli/sandtable.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -3,7 +3,7 @@ const fs = require('fs');
3
3
  const path = require('path');
4
4
 
5
5
  const ROOT = path.resolve(__dirname);
6
- const PORT = parseInt(process.env.PORT || process.argv[2], 10) || 3000;
6
+ const PORT = parseInt(process.env.PORT || process.argv[2], 10) || 5199;
7
7
 
8
8
  const MIME = {
9
9
  '.html': 'text/html; charset=utf-8',
@@ -8,7 +8,7 @@ const { exec } = require('child_process');
8
8
  const { scan } = require('../scanner/scan');
9
9
  const { build, EVENT_TYPES, classifyEventPriority } = require('../builder/build');
10
10
 
11
- const VERSION = '0.3.0';
11
+ const VERSION = '0.3.1';
12
12
  const command = process.argv[2] || 'help';
13
13
  const root = process.argv[3] || process.cwd();
14
14
 
@@ -194,33 +194,88 @@ function generateRuleContent(format, lang) {
194
194
  }
195
195
  }
196
196
 
197
- // ---- injectToMainDoc: 向 CLAUDE.md / AGENTS.md 追加事件日志规则 ----
198
- function injectToMainDoc(filePath, lang) {
197
+ // ---- injectToMainDoc: 向 CLAUDE.md / AGENTS.md 注入事件日志规则 ----
198
+ // 使用 <!-- sandtable:begin -->...<!-- sandtable:end --> 标记包裹,uninstall 时精确擦除
199
+ function injectToMainDoc(filePath, lang, dryRun) {
199
200
  var isZh = lang === 'zh';
200
- var marker = 'sandtable event-log';
201
+ var beginMark = '<!-- sandtable:begin -->';
202
+ var endMark = '<!-- sandtable:end -->';
201
203
  var existing = '';
202
204
  if (fs.existsSync(filePath)) {
203
205
  existing = fs.readFileSync(filePath, 'utf-8');
204
- if (existing.indexOf(marker) !== -1) return false; // already injected
206
+ if (existing.indexOf(beginMark) !== -1) {
207
+ if (!dryRun) console.log(' - ' + path.basename(filePath) + ' 已有 sandtable 注入标记,跳过');
208
+ return false; // already injected
209
+ }
205
210
  }
211
+
206
212
  var lines = [];
213
+ lines.push('');
214
+ lines.push(beginMark);
207
215
  if (isZh) {
208
- lines.push('');
209
216
  lines.push('## Sandtable 事件日志');
210
217
  lines.push('');
211
218
  lines.push('**每次完成可独立表述的代码/设计/决策动作后,立即调用 `sandtable event-log` 记录。不要等用户提醒。**');
212
219
  lines.push('');
220
+ lines.push('命令速查: `sandtable event-log --type code-change --title "标题" --subtype code-done --impact medium --actor @AI --tags "标签" --thread threadId`');
221
+ lines.push('');
213
222
  lines.push('详细规则见 `.sandtable/rules.md`。');
214
223
  } else {
215
- lines.push('');
216
224
  lines.push('## Sandtable Event Logging');
217
225
  lines.push('');
218
226
  lines.push('**After every significant code/design/decision action, immediately call `sandtable event-log` to record it. Do not wait for a reminder.**');
219
227
  lines.push('');
228
+ lines.push('Quick ref: `sandtable event-log --type code-change --title "title" --subtype code-done --impact medium --actor @AI --tags "tags" --thread threadId`');
229
+ lines.push('');
220
230
  lines.push('See `.sandtable/rules.md` for details.');
221
231
  }
222
- var content = existing + lines.join('\n') + '\n';
223
- writeWithBackup(filePath, content);
232
+ lines.push(endMark);
233
+
234
+ var newContent = existing + lines.join('\n') + '\n';
235
+
236
+ if (dryRun) {
237
+ console.log(' [DRY-RUN] 将注入 ' + path.basename(filePath) + ':');
238
+ console.log(' --- diff ---');
239
+ console.log(' + ' + lines.slice(1, -1).join('\n + '));
240
+ console.log(' --- end ---');
241
+ return true;
242
+ }
243
+
244
+ writeWithBackup(filePath, newContent);
245
+ return true;
246
+ }
247
+
248
+ // ---- removeFromMainDoc: uninstall 时识别标记精确擦除 ----
249
+ function removeFromMainDoc(filePath, dryRun) {
250
+ var beginMark = '<!-- sandtable:begin -->';
251
+ var endMark = '<!-- sandtable:end -->';
252
+
253
+ if (!fs.existsSync(filePath)) return false;
254
+
255
+ var content = fs.readFileSync(filePath, 'utf-8');
256
+ var beginIdx = content.indexOf(beginMark);
257
+ var endIdx = content.indexOf(endMark);
258
+
259
+ if (beginIdx === -1 || endIdx === -1) return false;
260
+
261
+ // Find the newline before beginMark (to clean up the blank line)
262
+ var beforeBegin = content.lastIndexOf('\n', beginIdx);
263
+ if (beforeBegin === -1) beforeBegin = 0;
264
+
265
+ var afterEnd = endIdx + endMark.length;
266
+ // Consume trailing newline if present
267
+ if (content[afterEnd] === '\n') afterEnd++;
268
+
269
+ if (dryRun) {
270
+ console.log(' [DRY-RUN] 将从 ' + path.basename(filePath) + ' 移除注入块:');
271
+ console.log(' --- to remove ---');
272
+ console.log(content.substring(beforeBegin, afterEnd).replace(/^/gm, ' - '));
273
+ console.log(' --- end ---');
274
+ return true;
275
+ }
276
+
277
+ var cleaned = content.substring(0, beforeBegin) + content.substring(afterEnd);
278
+ writeWithBackup(filePath, cleaned);
224
279
  return true;
225
280
  }
226
281
 
@@ -238,8 +293,8 @@ function writeClaudeCodeHook(projectRoot, lang) {
238
293
  script += '【Sandtable 事件日志铁律】\n';
239
294
  script += '本次 session 中,每完成一个独立动作(代码修改/设计定稿/决策拍板/错误修复),\n';
240
295
  script += '必须立即调用 sandtable event-log 记录。禁止事后补录,禁止等用户提醒。\n';
241
- script += '命令速查: sandtable event-log <typeId> "标题" <subtype> <impact> @AI "标签"\n';
242
- script += 'typeId: 1=对齐拍板 2=规格演进 3=代码变更 4=测试质量 5=审批交接 6=运维基建 7=教训沉淀\n';
296
+ script += '命令速查: sandtable event-log --type code-change --title "标题" --subtype code-done --impact medium --actor @AI --tags "标签"\n';
297
+ script += 'type: alignment | spec | code-change | test | approval | ops | lesson\n';
243
298
  script += '详细规则见 .sandtable/rules.md\n';
244
299
  script += '</system-reminder>\n';
245
300
  script += 'SANDBTABLE_EOR\n';
@@ -250,7 +305,8 @@ function writeClaudeCodeHook(projectRoot, lang) {
250
305
  script += '[Sandtable Event Log Rule]\n';
251
306
  script += 'After every significant action (code change, design decision, bug fix) in this session,\n';
252
307
  script += 'immediately call sandtable event-log to record it. No backfilling, no waiting for reminders.\n';
253
- script += 'Quick ref: sandtable event-log <typeId> "title" <subtype> <impact> @AI "tags"\n';
308
+ script += 'Quick ref: sandtable event-log --type code-change --title "title" --subtype code-done --impact medium --actor @AI --tags "tags"\n';
309
+ script += 'type: alignment | spec | code-change | test | approval | ops | lesson\n';
254
310
  script += 'See .sandtable/rules.md for details.\n';
255
311
  script += '</system-reminder>\n';
256
312
  script += 'SANDBTABLE_EOR\n';
@@ -285,7 +341,7 @@ function writeClaudeCodeHook(projectRoot, lang) {
285
341
  }
286
342
 
287
343
  // ---- initCommand: dry-run 报告 or --apply 写入 ----
288
- function initCommand(projectRoot, applyMode, lang, hooksMode) {
344
+ function initCommand(projectRoot, applyMode, lang, hooksMode, injectAgentsMd) {
289
345
  var envs = detectEnvironment(projectRoot);
290
346
  var sandtableDir = path.join(projectRoot, '.sandtable');
291
347
  var configPath = path.join(sandtableDir, 'config.json');
@@ -325,7 +381,11 @@ function initCommand(projectRoot, applyMode, lang, hooksMode) {
325
381
  }
326
382
  if (autoHints.length > 0) {
327
383
  console.log('');
328
- console.log('⚡ --apply 将自动注入:');
384
+ if (injectAgentsMd) {
385
+ console.log('⚡ --inject-agents-md 启用 → 将注入主文档:');
386
+ } else {
387
+ console.log('💡 可选: --inject-agents-md 自动注入事件日志规则到主文档');
388
+ }
329
389
  for (var i = 0; i < autoHints.length; i++) {
330
390
  console.log(' ' + (i + 1) + '. ' + autoHints[i]);
331
391
  }
@@ -352,9 +412,11 @@ function initCommand(projectRoot, applyMode, lang, hooksMode) {
352
412
  if (!applyMode) {
353
413
  console.log('');
354
414
  console.log('以上为 dry-run 报告。要实际写入文件,请运行:');
355
- console.log(' sandtable init --apply' + (lang ? ' --lang ' + lang : '') + (hasClaudeEnv ? ' [--hooks]' : ''));
415
+ console.log(' sandtable init --apply' + (lang ? ' --lang ' + lang : '') + (hasClaudeEnv ? ' [--hooks]' : '') + ' [--inject-agents-md]');
356
416
  console.log('');
357
417
  console.log('已存在文件将在写入前备份为 .bak.<timestamp>');
418
+ console.log('');
419
+ console.log('💡 --inject-agents-md: 注入事件日志规则到 CLAUDE.md/AGENTS.md(使用 <!-- sandtable:begin/end --> 标记包裹,uninstall 可精确擦除)');
358
420
  return;
359
421
  }
360
422
 
@@ -370,7 +432,7 @@ function initCommand(projectRoot, applyMode, lang, hooksMode) {
370
432
 
371
433
  // 2. Write .sandtable/config.json
372
434
  var config = {
373
- version: '0.3.0',
435
+ version: '0.3.1',
374
436
  createdAt: new Date().toISOString(),
375
437
  projectRoot: projectRoot,
376
438
  paths: {
@@ -414,25 +476,30 @@ function initCommand(projectRoot, applyMode, lang, hooksMode) {
414
476
  writeWithBackup(fullPath, content);
415
477
  }
416
478
 
417
- // 6. Inject event-log rule into main doc (CLAUDE.md / AGENTS.md)
479
+ // 6. Inject event-log rule into main doc (only with --inject-agents-md)
418
480
  var mainDocInjected = false;
419
- for (var i = 0; i < envs.length; i++) {
420
- var env = envs[i];
421
- var mainDocPath = null;
422
- if (env.type === 'agents') {
423
- mainDocPath = path.join(projectRoot, 'AGENTS.md');
424
- } else if (env.type === 'claude-md') {
425
- mainDocPath = path.join(projectRoot, 'CLAUDE.md');
426
- } else if (env.type === 'generic') {
427
- mainDocPath = path.join(projectRoot, 'AGENTS.md');
428
- }
429
- if (mainDocPath) {
430
- var injected = injectToMainDoc(mainDocPath, lang);
431
- if (injected) {
432
- console.log(' ✓ 注入事件日志规则 → ' + path.basename(mainDocPath));
433
- mainDocInjected = true;
434
- } else if (fs.existsSync(mainDocPath)) {
435
- console.log(' - ' + path.basename(mainDocPath) + ' 已有 sandtable 规则,跳过');
481
+ if (injectAgentsMd) {
482
+ console.log('');
483
+ console.log('注入主文档 (--inject-agents-md):');
484
+ for (var i = 0; i < envs.length; i++) {
485
+ var env = envs[i];
486
+ var mainDocPath = null;
487
+ if (env.type === 'agents') {
488
+ mainDocPath = path.join(projectRoot, 'AGENTS.md');
489
+ } else if (env.type === 'claude-md') {
490
+ mainDocPath = path.join(projectRoot, 'CLAUDE.md');
491
+ } else if (env.type === 'generic') {
492
+ mainDocPath = path.join(projectRoot, 'AGENTS.md');
493
+ }
494
+ if (mainDocPath) {
495
+ var usedDryRun = !applyMode; // In dry-run, show diff instead of writing
496
+ var injected = injectToMainDoc(mainDocPath, lang, usedDryRun);
497
+ if (injected) {
498
+ if (applyMode) console.log(' ✓ 注入事件日志规则 → ' + path.basename(mainDocPath));
499
+ mainDocInjected = true;
500
+ } else if (fs.existsSync(mainDocPath)) {
501
+ console.log(' - ' + path.basename(mainDocPath) + ' 已有 sandtable 规则,跳过');
502
+ }
436
503
  }
437
504
  }
438
505
  }
@@ -632,12 +699,13 @@ switch (command) {
632
699
  case 'init': {
633
700
  var applyMode = process.argv.indexOf('--apply') !== -1;
634
701
  var hooksMode = process.argv.indexOf('--hooks') !== -1;
702
+ var injectAgentsMd = process.argv.indexOf('--inject-agents-md') !== -1;
635
703
  var langIdx = process.argv.indexOf('--lang');
636
704
  var lang = 'zh';
637
705
  if (langIdx !== -1 && process.argv[langIdx + 1]) {
638
706
  lang = process.argv[langIdx + 1] === 'en' ? 'en' : 'zh';
639
707
  }
640
- initCommand(process.cwd(), applyMode, lang, hooksMode);
708
+ initCommand(process.cwd(), applyMode, lang, hooksMode, injectAgentsMd);
641
709
  break;
642
710
  }
643
711
  case 'summarize': {
@@ -661,109 +729,192 @@ switch (command) {
661
729
  console.log('sandtable v' + VERSION);
662
730
  break;
663
731
  case 'serve': {
664
- var port = parseInt(process.argv[3], 10) || 3000;
732
+ var port = parseInt(process.argv[3], 10) || 5199;
665
733
  var watchMode = process.argv.indexOf('--watch') !== -1;
666
734
  startServer(process.cwd(), port, true, watchMode);
667
735
  break;
668
736
  }
669
- case 'event-log': {
670
- // Record event: sandtable event-log <typeId> <title> [subtype] [impact] [actor] [tags] [refDoc] [threadId] [--tokens <in>,<out>] [--continue <threadId>]
671
-
672
- // Extract named flags first, then build clean positional args
673
- var rawArgs = process.argv.slice(3); // everything after "event-log"
674
- var contThreadId = null;
675
- var tokensVal = null;
676
- var cleanArgs = [];
677
- for (var ai = 0; ai < rawArgs.length; ai++) {
678
- if (rawArgs[ai] === '--continue' && ai + 1 < rawArgs.length) {
679
- contThreadId = rawArgs[ai + 1]; ai++; // skip value
680
- } else if (rawArgs[ai] === '--tokens' && ai + 1 < rawArgs.length) {
681
- tokensVal = rawArgs[ai + 1]; ai++; // skip value
682
- } else {
683
- cleanArgs.push(rawArgs[ai]);
684
- }
685
- }
686
-
687
- var typeId = cleanArgs[0] || '3';
688
- var title = cleanArgs[1] || '';
689
- var subtype = cleanArgs[2] || 'manual';
690
- var impact = cleanArgs[3] || 'medium';
691
- var actor = cleanArgs[4] || 'AI';
692
- var tagsRaw = cleanArgs[5] || '';
693
- var refDoc = cleanArgs[6] || '';
694
- var threadId = contThreadId || cleanArgs[7] || '';
695
-
696
- if (!title) {
697
- console.log('用法: sandtable event-log <typeId> <title> [subtype] [impact] [actor] [tags] [refDoc] [threadId] [--tokens <in>,<out>] [--continue <threadId>]');
698
- console.log(' typeId: 1=对齐与拍板 2=规格演进 3=代码变更 4=测试与质量 5=审批与交接 6=运维与基建 7=教训沉淀');
699
- process.exit(1);
700
- }
701
-
702
- if (!EVENT_TYPES[typeId]) {
703
- console.log('错误:typeId 必须为 1-7');
704
- console.log(' 1=对齐与拍板 2=规格演进 3=代码变更 4=测试与质量 5=审批与交接 6=运维与基建 7=教训沉淀');
705
- process.exit(1);
706
- }
737
+ case 'event-log': {
738
+ // Named flags (v0.3.1+, recommended):
739
+ // sandtable event-log --type <slug> --title <text> [--subtype <text>] [--impact <level>]
740
+ // [--actor <@AI|@user>] [--tags <tags>] [--ref <path>] [--thread <id>]
741
+ // [--tokens-in <n> --tokens-out <n>] [--project <path>]
742
+ //
743
+ // Legacy positional (deprecated):
744
+ // sandtable event-log <typeId> <title> [subtype] [impact] [actor] [tags] [refDoc] [threadId]
745
+ // [--tokens <in>,<out>] [--continue <threadId>]
746
+
747
+ // Type slug typeId mapping
748
+ var TYPE_SLUGS = {
749
+ 'alignment': '1', 'spec': '2', 'code-change': '3',
750
+ 'test': '4', 'approval': '5', 'ops': '6', 'lesson': '7',
751
+ };
752
+ var TYPE_SLUG_NAMES = {
753
+ '1': 'alignment', '2': 'spec', '3': 'code-change',
754
+ '4': 'test', '5': 'approval', '6': 'ops', '7': 'lesson',
755
+ };
756
+
757
+ var rawArgs = process.argv.slice(3);
758
+
759
+ // Detect mode: if first arg starts with --, use named mode
760
+ var isNamedMode = rawArgs.length > 0 && rawArgs[0].indexOf('--') === 0;
761
+
762
+ var typeId, title, subtype, impact, actor, tagsRaw, refDoc, threadId, projectRoot;
763
+ var tokensIn = 0, tokensOut = 0;
764
+
765
+ if (isNamedMode) {
766
+ // ---- Named flag mode ----
767
+ var flagMap = {};
768
+ for (var ai = 0; ai < rawArgs.length; ai++) {
769
+ var arg = rawArgs[ai];
770
+ if (arg.indexOf('--') === 0) {
771
+ var key = arg.replace(/^--/, '');
772
+ var val = (ai + 1 < rawArgs.length && rawArgs[ai + 1].indexOf('--') !== 0) ? rawArgs[ai + 1] : 'true';
773
+ flagMap[key] = val;
774
+ if (val !== 'true') ai++;
775
+ }
776
+ }
707
777
 
708
- // Resolve project root
709
- var projectRoot = cleanArgs[8] || process.cwd();
778
+ var typeSlug = flagMap['type'] || '';
779
+ typeId = TYPE_SLUGS[typeSlug] || '';
780
+ if (!typeId) {
781
+ console.log('错误:--type 必须是以下之一:');
782
+ console.log(' alignment | spec | code-change | test | approval | ops | lesson');
783
+ process.exit(1);
784
+ }
710
785
 
711
- var sandtableDir = path.join(projectRoot, '.sandtable');
712
- if (!fs.existsSync(sandtableDir)) fs.mkdirSync(sandtableDir, { recursive: true });
786
+ title = flagMap['title'] || '';
787
+ subtype = flagMap['subtype'] || 'manual';
788
+ impact = flagMap['impact'] || 'medium';
789
+ actor = flagMap['actor'] || 'AI';
790
+ tagsRaw = flagMap['tags'] || '';
791
+ refDoc = flagMap['ref'] || '';
792
+ threadId = flagMap['thread'] || '';
793
+ tokensIn = parseInt(flagMap['tokens-in'], 10) || 0;
794
+ tokensOut = parseInt(flagMap['tokens-out'], 10) || 0;
795
+ projectRoot = flagMap['project'] || process.cwd();
713
796
 
714
- var tags = tagsRaw ? tagsRaw.split(',').map(function(t) { return t.trim(); }) : [];
715
- var ref = refDoc ? { doc: refDoc } : {};
797
+ } else {
798
+ // ---- Legacy positional mode (deprecated) ----
799
+ var contThreadId = null;
800
+ var tokensVal = null;
801
+ var cleanArgs = [];
802
+ for (var ai = 0; ai < rawArgs.length; ai++) {
803
+ if (rawArgs[ai] === '--continue' && ai + 1 < rawArgs.length) {
804
+ contThreadId = rawArgs[ai + 1]; ai++;
805
+ } else if (rawArgs[ai] === '--tokens' && ai + 1 < rawArgs.length) {
806
+ tokensVal = rawArgs[ai + 1]; ai++;
807
+ } else {
808
+ cleanArgs.push(rawArgs[ai]);
809
+ }
810
+ }
716
811
 
717
- var eventId = 'evt-' + Date.now().toString(36) + '-' + Math.random().toString(36).substring(2, 8);
812
+ typeId = cleanArgs[0] || '3';
813
+ title = cleanArgs[1] || '';
814
+ subtype = cleanArgs[2] || 'manual';
815
+ impact = cleanArgs[3] || 'medium';
816
+ actor = cleanArgs[4] || 'AI';
817
+ tagsRaw = cleanArgs[5] || '';
818
+ refDoc = cleanArgs[6] || '';
819
+ threadId = contThreadId || cleanArgs[7] || '';
820
+ projectRoot = cleanArgs[8] || process.cwd();
821
+
822
+ if (tokensVal) {
823
+ var tokensParts = tokensVal.split(',');
824
+ tokensIn = parseInt(tokensParts[0], 10) || 0;
825
+ tokensOut = parseInt(tokensParts[1], 10) || 0;
826
+ }
827
+ }
718
828
 
719
- var event = {
720
- id: eventId,
721
- timestamp: new Date().toISOString(),
722
- type: EVENT_TYPES[typeId] || '代码变更',
723
- typeId: typeId,
724
- subtype: subtype,
725
- title: title,
726
- ref: ref,
727
- impact: impact,
728
- actor: actor,
729
- tags: tags,
730
- threadId: threadId || null,
731
- };
732
- event.priority = classifyEventPriority(event);
829
+ if (!title) {
830
+ if (isNamedMode) {
831
+ console.log('用法: sandtable event-log --type <slug> --title <text> [options]');
832
+ console.log('');
833
+ console.log('必选:');
834
+ console.log(' --type 事件类型: alignment | spec | code-change | test | approval | ops | lesson');
835
+ console.log(' --title 一句话标题');
836
+ console.log('');
837
+ console.log('可选:');
838
+ console.log(' --subtype 子类型 (如 code-done, review-pass, alignment)');
839
+ console.log(' --impact 影响级别: high | medium | low (默认 medium)');
840
+ console.log(' --actor 执行角色: @AI | @user (默认 @AI)');
841
+ console.log(' --tags 逗号分隔标签');
842
+ console.log(' --ref 关联文档路径');
843
+ console.log(' --thread 线程ID (关联同一线程事件)');
844
+ console.log(' --tokens-in 输入 token 数');
845
+ console.log(' --tokens-out 输出 token 数');
846
+ console.log(' --project 项目根目录 (默认当前目录)');
847
+ console.log('');
848
+ console.log('示例:');
849
+ console.log(' sandtable event-log --type code-change --title "完成登录接口" --subtype code-done --impact medium --actor @AI --tags "登录,API" --thread login-dev');
850
+ console.log(' sandtable event-log --type alignment --title "确定JWT方案" --impact high --actor @user');
851
+ } else {
852
+ console.log('用法: sandtable event-log <typeId> <title> [subtype] [impact] [actor] [tags] [refDoc] [threadId]');
853
+ console.log(' typeId: 1=对齐与拍板 2=规格演进 3=代码变更 4=测试与质量 5=审批与交接 6=运维与基建 7=教训沉淀');
854
+ console.log(' (位置参数已弃用,推荐使用 --type --title 命名参数)');
855
+ }
856
+ process.exit(1);
857
+ }
733
858
 
734
- // Write event-log.jsonl
735
- var logPath = path.join(sandtableDir, 'event-log.jsonl');
736
- fs.appendFileSync(logPath, JSON.stringify(event) + '\n');
859
+ if (!EVENT_TYPES[typeId]) {
860
+ console.log('错误:--type 无效。有效值: alignment | spec | code-change | test | approval | ops | lesson');
861
+ process.exit(1);
862
+ }
737
863
 
738
- // Write audit log
739
- var auditPath = path.join(sandtableDir, 'audit.log');
740
- var auditEntry = JSON.stringify({
741
- timestamp: event.timestamp,
742
- command: 'event-log',
743
- eventId: eventId,
744
- type: event.type,
745
- title: title,
746
- cwd: projectRoot,
747
- });
748
- fs.appendFileSync(auditPath, auditEntry + '\n');
864
+ var sandtableDir = path.join(projectRoot, '.sandtable');
865
+ if (!fs.existsSync(sandtableDir)) fs.mkdirSync(sandtableDir, { recursive: true });
866
+
867
+ var tags = tagsRaw ? tagsRaw.split(',').map(function(t) { return t.trim(); }) : [];
868
+ var ref = refDoc ? { doc: refDoc } : {};
869
+
870
+ var eventId = 'evt-' + Date.now().toString(36) + '-' + Math.random().toString(36).substring(2, 8);
871
+
872
+ var event = {
873
+ id: eventId,
874
+ timestamp: new Date().toISOString(),
875
+ type: EVENT_TYPES[typeId] || '代码变更',
876
+ typeId: typeId,
877
+ subtype: subtype,
878
+ title: title,
879
+ ref: ref,
880
+ impact: impact,
881
+ actor: actor,
882
+ tags: tags,
883
+ threadId: threadId || null,
884
+ };
885
+ event.priority = classifyEventPriority(event);
886
+
887
+ // Write event-log.jsonl
888
+ var logPath = path.join(sandtableDir, 'event-log.jsonl');
889
+ fs.appendFileSync(logPath, JSON.stringify(event) + '\n');
890
+
891
+ // Write audit log
892
+ var auditPath = path.join(sandtableDir, 'audit.log');
893
+ var auditEntry = JSON.stringify({
894
+ timestamp: event.timestamp,
895
+ command: 'event-log',
896
+ eventId: eventId,
897
+ type: event.type,
898
+ title: title,
899
+ cwd: projectRoot,
900
+ });
901
+ fs.appendFileSync(auditPath, auditEntry + '\n');
749
902
 
750
- console.log('event-log: [' + event.priority + '] ' + event.type + '' + title + (threadId ? ' (thread: ' + threadId + ')' : ''));
903
+ var slugName = TYPE_SLUG_NAMES[typeId] || '';
904
+ console.log('event-log: [' + event.priority + '] ' + event.type + ' — ' + title + (threadId ? ' (thread: ' + threadId + ')' : ''));
905
+ console.log(' (提示: 位置参数已弃用,下次推荐: --type ' + slugName + ' --title "...")');
751
906
 
752
- // --tokens <in>,<out> : auto-log token usage for this event
753
- if (tokensVal) {
754
- var tokensParts = tokensVal.split(',');
755
- var tIn = parseInt(tokensParts[0], 10) || 0;
756
- var tOut = parseInt(tokensParts[1], 10) || 0;
757
- if (tIn > 0 || tOut > 0) {
758
- var COST_PER_1K = { input: 0.003, output: 0.006 }; // DeepSeek V4 Pro ¥/1K
759
- var tCost = (tIn / 1000) * COST_PER_1K.input + (tOut / 1000) * COST_PER_1K.output;
907
+ // --tokens-in + --tokens-out: auto-log token usage
908
+ if (tokensIn > 0 || tokensOut > 0) {
909
+ var COST_PER_1K = { input: 0.003, output: 0.006 };
910
+ var tCost = (tokensIn / 1000) * COST_PER_1K.input + (tokensOut / 1000) * COST_PER_1K.output;
760
911
  tCost = Math.round(tCost * 10000) / 10000;
761
912
  var tokenEntry = {
762
913
  timestamp: event.timestamp,
763
914
  skill: 'event-log',
764
- tokensIn: tIn,
765
- tokensOut: tOut,
766
- totalTokens: tIn + tOut,
915
+ tokensIn: tokensIn,
916
+ tokensOut: tokensOut,
917
+ totalTokens: tokensIn + tokensOut,
767
918
  cost: tCost,
768
919
  source: 'event-log',
769
920
  eventId: eventId,
@@ -771,12 +922,11 @@ switch (command) {
771
922
  };
772
923
  var tokenLogPath = path.join(sandtableDir, 'token-log.jsonl');
773
924
  fs.appendFileSync(tokenLogPath, JSON.stringify(tokenEntry) + '\n');
774
- console.log(' token-log: ' + (tIn + tOut).toLocaleString() + ' tokens (est. ¥' + tCost + ')');
925
+ console.log(' token-log: ' + (tokensIn + tokensOut).toLocaleString() + ' tokens (est. ¥' + tCost + ')');
775
926
  }
927
+ break;
776
928
  }
777
- break;
778
- }
779
- case 'token-log': {
929
+ case 'token-log': {
780
930
  var skill = process.argv[3] || '';
781
931
  var tokensIn = parseInt(process.argv[4], 10) || 0;
782
932
  var tokensOut = parseInt(process.argv[5], 10) || 0;
@@ -889,17 +1039,47 @@ switch (command) {
889
1039
  console.log(' ' + (existing[i].isDir ? '[DIR] ' : '[ ] ') + existing[i].rel);
890
1040
  }
891
1041
 
1042
+ // Check for sandtable injection markers in main docs
1043
+ var mainDocs = ['AGENTS.md', 'CLAUDE.md'];
1044
+ var hasInjections = false;
1045
+ for (var i = 0; i < mainDocs.length; i++) {
1046
+ var docPath = path.join(projectRoot, mainDocs[i]);
1047
+ if (fs.existsSync(docPath)) {
1048
+ var docContent = fs.readFileSync(docPath, 'utf-8');
1049
+ if (docContent.indexOf('<!-- sandtable:begin -->') !== -1) {
1050
+ hasInjections = true;
1051
+ if (dryRun) {
1052
+ console.log('');
1053
+ removeFromMainDoc(docPath, true); // dry-run: show diff
1054
+ }
1055
+ }
1056
+ }
1057
+ }
1058
+ if (!hasInjections) {
1059
+ console.log('');
1060
+ console.log('主文档中未找到 sandtable 注入标记。');
1061
+ }
1062
+
892
1063
  if (dryRun) {
893
1064
  console.log('');
894
1065
  console.log('以上为 dry-run 预览。要实际删除,请运行:');
895
1066
  console.log(' sandtable uninstall --apply');
896
1067
  console.log('');
897
1068
  console.log('注意:');
898
- console.log(' - sandtable 不会删除用户主文档(AGENTS.md、CLAUDE.md 等)');
899
- console.log(' - 如果你在 AGENTS.md/CLAUDE.md 中手动添加了 @.sandtable/rules.md 入口行,请自行删除');
1069
+ console.log(' - sandtable 注入标记 (<!-- sandtable:begin/end -->) 可被 uninstall --apply 精确擦除');
1070
+ console.log(' - 如果你在 AGENTS.md/CLAUDE.md 中手动添加了其他 sandtable 内容,请自行删除');
900
1071
  break;
901
1072
  }
902
1073
 
1074
+ // --apply: first clean up injected content from main docs
1075
+ for (var i = 0; i < mainDocs.length; i++) {
1076
+ var docPath = path.join(projectRoot, mainDocs[i]);
1077
+ if (fs.existsSync(docPath)) {
1078
+ var removed = removeFromMainDoc(docPath, false);
1079
+ if (removed) console.log(' ✓ 从 ' + mainDocs[i] + ' 移除 sandtable 注入块');
1080
+ }
1081
+ }
1082
+
903
1083
  // --apply: delete files
904
1084
  console.log('');
905
1085
  console.log('正在删除...');