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 +1 -1
- package/server.js +1 -1
- package/src/cli/sandtable.js +307 -127
package/package.json
CHANGED
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) ||
|
|
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',
|
package/src/cli/sandtable.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
223
|
-
|
|
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
|
|
242
|
-
script += '
|
|
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
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
479
|
+
// 6. Inject event-log rule into main doc (only with --inject-agents-md)
|
|
418
480
|
var mainDocInjected = false;
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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) ||
|
|
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
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
-
|
|
709
|
-
|
|
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
|
-
|
|
712
|
-
|
|
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
|
-
|
|
715
|
-
|
|
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
|
-
|
|
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
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
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
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
|
|
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
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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:
|
|
765
|
-
tokensOut:
|
|
766
|
-
totalTokens:
|
|
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: ' + (
|
|
925
|
+
console.log(' token-log: ' + (tokensIn + tokensOut).toLocaleString() + ' tokens (est. ¥' + tCost + ')');
|
|
775
926
|
}
|
|
927
|
+
break;
|
|
776
928
|
}
|
|
777
|
-
|
|
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
|
|
899
|
-
console.log(' - 如果你在 AGENTS.md/CLAUDE.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('正在删除...');
|