@gobing-ai/knowledge-kit 0.0.28 → 0.0.30

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 (97) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +114 -38
  3. package/package.json +1 -1
  4. package/plugins/generations/daily-article-gen/dist/index.js +45 -5
  5. package/plugins/generations/daily-article-gen/src/index.ts +59 -4
  6. package/plugins/generations/dailynews-gen/dist/index.js +46 -9
  7. package/plugins/generations/dailynews-gen/src/index.ts +36 -1
  8. package/plugins/generations/dailynews-gen/src/script-builder.ts +15 -12
  9. package/plugins/generations/dailynews-gen/src/script-plan.ts +21 -3
  10. package/plugins/generations/episode-plan-gen/dist/index.js +55 -4
  11. package/plugins/generations/episode-plan-gen/src/index.ts +80 -2
  12. package/plugins/generations/image-gen/assets/fonts/NotoSansSC-Bold.otf +0 -0
  13. package/plugins/generations/image-gen/assets/fonts/NotoSansSC-Regular.otf +0 -0
  14. package/plugins/generations/image-gen/assets/fonts/OFL.txt +92 -0
  15. package/plugins/generations/image-gen/assets/fonts/SOURCE.md +15 -0
  16. package/plugins/generations/image-gen/dist/index.js +700 -22
  17. package/plugins/generations/image-gen/package.json +1 -0
  18. package/plugins/generations/image-gen/presets/formats/daily-news-cover.yaml +2 -2
  19. package/plugins/generations/image-gen/presets/formats/xhs-card.yaml +19 -11
  20. package/plugins/generations/image-gen/presets/styles/xhs-knowledge.yaml +2 -1
  21. package/plugins/generations/image-gen/scripts/compose-e2e-artifact.ts +135 -0
  22. package/plugins/generations/image-gen/src/bytes.ts +116 -0
  23. package/plugins/generations/image-gen/src/compose/card.ts +294 -0
  24. package/plugins/generations/image-gen/src/compose/chromium.ts +147 -0
  25. package/plugins/generations/image-gen/src/compose/cmap.ts +177 -0
  26. package/plugins/generations/image-gen/src/index.ts +211 -13
  27. package/plugins/generations/image-gen/src/job.ts +12 -0
  28. package/plugins/generations/image-gen/src/presets.ts +53 -13
  29. package/plugins/generations/image-gen/src/providers/minimax.ts +12 -4
  30. package/plugins/generations/image-gen/src/providers/types.ts +12 -1
  31. package/plugins/kk/plugin.json +1 -1
  32. package/plugins/kk/scripts/daily-voice-style.zh.md +27 -14
  33. package/plugins/kk/scripts/itc-stages.ts +21 -3
  34. package/plugins/kk/scripts/kk-workflow-stages.ts +461 -10
  35. package/plugins/kk/scripts/topic-boundary.json +96 -0
  36. package/plugins/kk/scripts/validate-voicescript.ts +15 -0
  37. package/plugins/kk/skills/content-judge/SKILL.md +30 -0
  38. package/plugins/kk/skills/content-judge/references/workflow-integration.md +9 -2
  39. package/plugins/kk/skills/content-safety/SKILL.md +163 -0
  40. package/plugins/kk/skills/content-safety/references/platforms/apple-podcasts.md +36 -0
  41. package/plugins/kk/skills/content-safety/references/platforms/netease-music.md +31 -0
  42. package/plugins/kk/skills/content-safety/references/platforms/wechatmp.md +32 -0
  43. package/plugins/kk/skills/content-safety/references/platforms/xiaoyuzhou.md +35 -0
  44. package/plugins/kk/skills/publish/SKILL.md +1 -1
  45. package/plugins/kk/skills/xhs-images/SKILL.md +11 -7
  46. package/plugins/kk/skills/xhs-images/references/card-layouts.md +4 -4
  47. package/plugins/kk/workflows/judge-gated-publish-example.yaml +18 -10
  48. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +139 -24
  49. package/plugins/kk/workflows/kk-itc.yaml +21 -8
  50. package/plugins/publishings/podcast-pub/dist/index.js +4 -0
  51. package/plugins/publishings/podcast-pub/src/index.ts +2 -0
  52. package/plugins/publishings/podcast-pub/src/show-notes.ts +6 -0
  53. package/plugins/publishings/xhs-pub/dist/index.js +1 -1
  54. package/plugins/publishings/xhs-pub/src/automation-guard.ts +2 -2
  55. package/plugins/publishings/xhs-pub/src/index.ts +1 -1
  56. package/plugins/publishings/xhs-pub/src/profile-check.ts +2 -2
  57. package/plugins/sp/lib/env.ts +0 -46
  58. package/plugins/sp/lib/inline-run.generated.mjs +0 -1489
  59. package/plugins/sp/scripts/batch-preflight.mjs +0 -346
  60. package/plugins/sp/scripts/batch-preflight.ts +0 -459
  61. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +0 -615
  62. package/plugins/sp/scripts/daily-summary/daily-summary.ts +0 -846
  63. package/plugins/sp/scripts/daily-summary/logger.ts +0 -28
  64. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +0 -223
  65. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +0 -367
  66. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +0 -132
  67. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +0 -169
  68. package/plugins/sp/scripts/feature-dev-precheck.mjs +0 -171
  69. package/plugins/sp/scripts/feature-dev-precheck.ts +0 -238
  70. package/plugins/sp/scripts/feature-sync-bounded.mjs +0 -285
  71. package/plugins/sp/scripts/feature-sync-bounded.ts +0 -478
  72. package/plugins/sp/scripts/feature-verification-steps.ts +0 -275
  73. package/plugins/sp/scripts/history-anatomy-cache.mjs +0 -902
  74. package/plugins/sp/scripts/history-anatomy-cache.ts +0 -1028
  75. package/plugins/sp/scripts/idea-handoff.mjs +0 -22
  76. package/plugins/sp/scripts/idea-handoff.ts +0 -44
  77. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +0 -185
  78. package/plugins/sp/scripts/inline-run-setup.ts +0 -722
  79. package/plugins/sp/scripts/pr-reviewing.mjs +0 -769
  80. package/plugins/sp/scripts/pr-reviewing.ts +0 -925
  81. package/plugins/sp/scripts/quality-gate.mjs +0 -179
  82. package/plugins/sp/scripts/quality-gate.ts +0 -217
  83. package/plugins/sp/scripts/script-contract-check.ts +0 -319
  84. package/plugins/sp/scripts/stage-registry-adapter.ts +0 -1533
  85. package/plugins/sp/scripts/surface-drift-inventory.ts +0 -929
  86. package/plugins/sp/scripts/task-evidence-precheck.ts +0 -181
  87. package/plugins/sp/scripts/task-size-precheck.ts +0 -175
  88. package/plugins/sp/scripts/transition-shim-check.ts +0 -238
  89. package/plugins/sp/scripts/validate-commands.ts +0 -689
  90. package/plugins/sp/scripts/validate-flag-contracts.ts +0 -878
  91. package/plugins/sp/scripts/verify-answer-lint.ts +0 -530
  92. package/plugins/sp/scripts/workflow-step-profile.mjs +0 -316
  93. package/plugins/sp/scripts/workflow-step-profile.ts +0 -456
  94. package/plugins/sp/scripts/wrapup-drift-probe.mjs +0 -190
  95. package/plugins/sp/scripts/wrapup-drift-probe.ts +0 -258
  96. package/plugins/sp/scripts/wrapup-steps.mjs +0 -444
  97. package/plugins/sp/scripts/wrapup-steps.ts +0 -552
@@ -1,615 +0,0 @@
1
- #!/usr/bin/env node
2
- // @bun
3
- import { createRequire } from 'node:module';
4
-
5
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
6
-
7
- // plugins/sp/scripts/daily-summary/daily-summary.ts
8
- import { spawn, spawnSync } from 'node:child_process';
9
- import { existsSync, mkdirSync, readlinkSync, writeFileSync } from 'node:fs';
10
- import { dirname, isAbsolute, join, resolve } from 'node:path';
11
-
12
- // plugins/sp/scripts/daily-summary/logger.ts
13
- var state = { console: true, file: false };
14
- function emit(stream, message) {
15
- if (!state.console) return;
16
- console[stream](message);
17
- }
18
- var logger = {
19
- info: (message) => emit('log', message),
20
- warn: (message) => emit('warn', message),
21
- error: (message) => emit('error', message),
22
- };
23
-
24
- // plugins/sp/scripts/daily-summary/daily-summary.ts
25
- var DAILY_DIR = 'docs/daily';
26
- var DEFAULT_DATE = 'today';
27
- function parseArgs(argv = process.argv.slice(2)) {
28
- const args = argv;
29
- const options = {
30
- date: DEFAULT_DATE,
31
- dryRun: false,
32
- skipGit: false,
33
- skipCcusage: false,
34
- };
35
- for (let i = 0; i < args.length; i++) {
36
- const arg = args[i];
37
- if (arg === '--date' && i + 1 < args.length) {
38
- options.date = args[++i];
39
- } else if (arg === '--dry-run') {
40
- options.dryRun = true;
41
- } else if (arg === '--output' && i + 1 < args.length) {
42
- options.outputPath = args[++i];
43
- } else if (arg === '--no-git') {
44
- options.skipGit = true;
45
- } else if (arg === '--no-ccusage') {
46
- options.skipCcusage = true;
47
- } else if (arg === '--help' || arg === '-h') {
48
- printUsage();
49
- process.exit(0);
50
- }
51
- }
52
- if (options.date === 'today') {
53
- options.date = todayLocal();
54
- } else if (options.date === 'yesterday') {
55
- options.date = yesterdayLocal();
56
- }
57
- return options;
58
- }
59
- function printUsage() {
60
- console.log(`
61
- sp:daily-summary \u2014 Generate daily summary reports
62
-
63
- Usage: daily-summary.ts [options]
64
-
65
- Options:
66
- --date YYYY-MM-DD Date for summary (default: today, also: yesterday)
67
- --dry-run Show summary without writing file
68
- --output <path> Write to custom path
69
- --no-git Skip git history collection
70
- --no-ccusage Skip token usage collection
71
- --help, -h Show this help
72
-
73
- Examples:
74
- daily-summary.ts # Today's summary
75
- daily-summary.ts --date yesterday # Yesterday's summary
76
- daily-summary.ts --dry-run # Preview without writing
77
- `);
78
- }
79
- function todayLocal() {
80
- const proc = spawnSync('date', ['+%Y-%m-%d'], { encoding: 'utf8' });
81
- return (proc.stdout ?? '').trim();
82
- }
83
- function yesterdayLocal() {
84
- const epochProc = spawnSync('date', ['+%s'], { encoding: 'utf8' });
85
- const epoch = parseInt((epochProc.stdout ?? '').trim(), 10);
86
- const yesterdayEpoch = epoch - 86400;
87
- let proc = spawnSync('date', ['-r', String(yesterdayEpoch), '+%Y-%m-%d'], { encoding: 'utf8' });
88
- if ((proc.status ?? 1) !== 0) {
89
- proc = spawnSync('date', ['-d', `@${yesterdayEpoch}`, '+%Y-%m-%d'], { encoding: 'utf8' });
90
- }
91
- return (proc.stdout ?? '').trim();
92
- }
93
- function getDateRange(dateStr) {
94
- const date = new Date(`${dateStr}T00:00:00`);
95
- const start = `${dateStr} 00:00:00`;
96
- const endDate = new Date(date);
97
- endDate.setDate(endDate.getDate() + 1);
98
- const end = `${endDate.toISOString().slice(0, 10)} 00:00:00`;
99
- return { start, end };
100
- }
101
- var defaultProcessSpawner = (cmd, args, env) => {
102
- return new Promise((resolve2, reject) => {
103
- try {
104
- const proc = spawn(cmd, args, {
105
- stdio: ['ignore', 'pipe', 'pipe'],
106
- env: env ?? process.env,
107
- });
108
- const stdoutChunks = [];
109
- const stderrChunks = [];
110
- proc.stdout.on('data', (chunk) => stdoutChunks.push(Buffer.from(chunk)));
111
- proc.stderr.on('data', (chunk) => stderrChunks.push(Buffer.from(chunk)));
112
- proc.on('close', (exitCode) => {
113
- resolve2({
114
- stdout: Buffer.concat(stdoutChunks).toString('utf8'),
115
- stderr: Buffer.concat(stderrChunks).toString('utf8'),
116
- exitCode: exitCode ?? 0,
117
- });
118
- });
119
- proc.on('error', (err) => {
120
- reject(err);
121
- });
122
- } catch (err) {
123
- reject(err);
124
- }
125
- });
126
- };
127
- var processSpawner = defaultProcessSpawner;
128
- function setProcessSpawner(next) {
129
- processSpawner = next ?? defaultProcessSpawner;
130
- }
131
- async function getCcusageData(date) {
132
- try {
133
- const env = { ...process.env };
134
- const ccusageCheck = await processSpawner('ccusage', ['--version'], env);
135
- if (ccusageCheck.exitCode !== 0) {
136
- return null;
137
- }
138
- const since = `${date}T00:00:00`;
139
- const until = `${date}T23:59:59`;
140
- const proc = await processSpawner('ccusage', ['daily', '--since', since, '--until', until, '--json'], env);
141
- if (proc.exitCode !== 0) {
142
- logger.warn(`ccusage error: ${proc.stderr}`);
143
- return null;
144
- }
145
- const data = JSON.parse(proc.stdout);
146
- return data;
147
- } catch (error) {
148
- logger.warn(`Failed to get ccusage data: ${error}`);
149
- return null;
150
- }
151
- }
152
- async function getSpurHistoryHealth(date, dbPath = '.spur/spur.db') {
153
- try {
154
- const resolvedPath = resolve(process.cwd(), dbPath);
155
- if (!existsSync(resolvedPath)) {
156
- return null;
157
- }
158
- const { Database } = await import('bun:sqlite');
159
- const db = new Database(resolvedPath, { readonly: true });
160
- try {
161
- const loopTable = db
162
- .query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
163
- .get('history_board_loop_findings');
164
- let loops = [];
165
- if (loopTable) {
166
- const rows = db
167
- .query(`SELECT tool_name, args_digest, repeats, session_id, first_seq, last_seq, started_at
168
- FROM history_board_loop_findings
169
- WHERE started_at IS NULL OR started_at LIKE ?
170
- ORDER BY repeats DESC
171
- LIMIT 20`)
172
- .all(`${date}%`);
173
- loops = rows.map((r) => ({
174
- toolName: r.tool_name,
175
- argsDigest: r.args_digest || 'repeated execution',
176
- repeats: r.repeats,
177
- sessionId: r.session_id,
178
- fromSeq: r.first_seq,
179
- toSeq: r.last_seq,
180
- wastedTokens: r.repeats * 250,
181
- }));
182
- }
183
- let toolCalls = 0;
184
- let toolErrors = 0;
185
- const toolTable = db
186
- .query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
187
- .get('history_board_tool_5m');
188
- if (toolTable) {
189
- const stats = db
190
- .query(`SELECT SUM(calls) AS calls, SUM(errors) AS errors
191
- FROM history_board_tool_5m
192
- WHERE bucket_start LIKE ?`)
193
- .get(`${date}%`);
194
- toolCalls = stats?.calls ?? 0;
195
- toolErrors = stats?.errors ?? 0;
196
- }
197
- const redundantCalls = loops.reduce((acc, l) => acc + Math.max(0, l.repeats - 1), 0);
198
- const wastedTokens = loops.reduce((acc, l) => acc + l.wastedTokens, 0);
199
- const errorRatePct = toolCalls > 0 ? (toolErrors / toolCalls) * 100 : 0;
200
- const remediationProposals = [];
201
- for (const lp of loops.slice(0, 5)) {
202
- const cleanTool = lp.toolName.replace(/[^a-zA-Z0-9_-]/g, '_');
203
- const key = `repetition:${cleanTool}:${lp.argsDigest.slice(0, 16)}`;
204
- const title = `Break execution loop in ${lp.toolName} (${lp.repeats} repeats)`;
205
- const command = `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`;
206
- remediationProposals.push({ key, title, command });
207
- }
208
- if (toolCalls > 0 && errorRatePct > 10) {
209
- const key = 'reliability:tooling:high-error-rate';
210
- const title = `Investigate high tool error rate (${errorRatePct.toFixed(1)}%)`;
211
- const command = `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`;
212
- remediationProposals.push({ key, title, command });
213
- }
214
- return {
215
- toolCalls,
216
- toolErrors,
217
- errorRatePct,
218
- loops,
219
- redundantCalls,
220
- wastedTokens,
221
- remediationProposals,
222
- };
223
- } finally {
224
- db.close();
225
- }
226
- } catch {
227
- return null;
228
- }
229
- }
230
- async function getGitCommits(date) {
231
- try {
232
- const { start, end } = getDateRange(date);
233
- const proc = await processSpawner('git', [
234
- 'log',
235
- '--since',
236
- start,
237
- '--until',
238
- end,
239
- '--pretty=format:%H|%ad|%s',
240
- '--date=iso',
241
- '--numstat',
242
- ]);
243
- if (proc.exitCode !== 0) {
244
- logger.warn('Failed to get git commits');
245
- return [];
246
- }
247
- const commits = [];
248
- const lines = proc.stdout.trim().split(`
249
- `);
250
- let currentCommit = null;
251
- for (const line of lines) {
252
- if (!line.trim()) continue;
253
- if (line.includes('|')) {
254
- const parts = line.split('|');
255
- if (parts.length >= 3) {
256
- if (currentCommit?.hash) {
257
- commits.push(currentCommit);
258
- }
259
- currentCommit = {
260
- hash: parts[0],
261
- date: parts[1],
262
- message: parts[2],
263
- filesChanged: 0,
264
- insertions: 0,
265
- deletions: 0,
266
- };
267
- }
268
- } else if (currentCommit && line.includes('\t')) {
269
- const parts = line.split('\t');
270
- if (parts.length >= 3) {
271
- const insertions = parseInt(parts[0], 10) || 0;
272
- const deletions = parseInt(parts[1], 10) || 0;
273
- currentCommit.filesChanged = (currentCommit.filesChanged ?? 0) + 1;
274
- currentCommit.insertions = (currentCommit.insertions ?? 0) + insertions;
275
- currentCommit.deletions = (currentCommit.deletions ?? 0) + deletions;
276
- }
277
- }
278
- }
279
- if (currentCommit?.hash) {
280
- commits.push(currentCommit);
281
- }
282
- return commits;
283
- } catch (error) {
284
- logger.warn(`Failed to get git commits: ${error}`);
285
- return [];
286
- }
287
- }
288
- async function promptUser() {
289
- if (process.env.SP_DAILY_SUMMARY_NO_PROMPT === '1') {
290
- return { learnings: '', issuesFixed: '', pending: '' };
291
- }
292
- if (process.env.RD3_DAILY_SUMMARY_NO_PROMPT === '1') {
293
- logger.warn('[deprecate] RD3_DAILY_SUMMARY_NO_PROMPT is deprecated; use SP_DAILY_SUMMARY_NO_PROMPT');
294
- return { learnings: '', issuesFixed: '', pending: '' };
295
- }
296
- console.log(`
297
- \uD83D\uDCCA Daily Summary \u2014 ${todayLocal()}`);
298
- console.log('\u2550'.repeat(50));
299
- console.log(`
300
- Please provide the following (press Enter to skip):
301
- `);
302
- if (!process.stdin.isTTY) {
303
- const chunks = [];
304
- for await (const chunk of process.stdin) {
305
- chunks.push(chunk);
306
- }
307
- const buffered = Buffer.concat(chunks).toString('utf-8');
308
- const [learnings2 = '', issuesFixed2 = '', pending2 = ''] = buffered.split(`
309
- `);
310
- return {
311
- learnings: learnings2.trim(),
312
- issuesFixed: issuesFixed2.trim(),
313
- pending: pending2.trim(),
314
- };
315
- }
316
- const readline = await import('node:readline');
317
- const rl = readline.createInterface({
318
- input: process.stdin,
319
- output: process.stdout,
320
- });
321
- const question = (prompt) =>
322
- new Promise((resolve2) => {
323
- rl.question(prompt, (answer) => {
324
- resolve2(answer.trim());
325
- });
326
- });
327
- const learnings = await question(`1. What did you learn today? (optional)
328
- > `);
329
- const issuesFixed = await question(`
330
- 2. What issues did you fix? (optional)
331
- > `);
332
- const pending = await question(`
333
- 3. What's pending for tomorrow? (optional)
334
- > `);
335
- rl.close();
336
- return {
337
- learnings,
338
- issuesFixed,
339
- pending,
340
- };
341
- }
342
- function generateMarkdown(summary) {
343
- const lines = [];
344
- lines.push(`# Daily Summary \u2014 ${summary.date}`);
345
- lines.push('');
346
- lines.push(`**Generated:** ${summary.generatedAt}`);
347
- lines.push('');
348
- lines.push('## Meta');
349
- lines.push('');
350
- lines.push(`- **Date:** ${summary.date}`);
351
- lines.push(`- **Platforms:** ${summary.platforms.join(', ') || 'unknown'}`);
352
- lines.push('');
353
- if (summary.tokenUsage) {
354
- const tu = summary.tokenUsage;
355
- lines.push('## Token Usage');
356
- lines.push('');
357
- lines.push(`| Metric | Value |`);
358
- lines.push(`|--------|-------|`);
359
- lines.push(`| Input Tokens | ${tu.inputTokens.toLocaleString()} |`);
360
- lines.push(`| Output Tokens | ${tu.outputTokens.toLocaleString()} |`);
361
- lines.push(`| Cache Tokens | ${tu.cacheTokens.toLocaleString()} |`);
362
- lines.push(`| Total Tokens | ${tu.totalTokens.toLocaleString()} |`);
363
- lines.push(`| Estimated Cost | $${tu.costUsd.toFixed(4)} |`);
364
- lines.push('');
365
- if (tu.inputTokens > 0) {
366
- const cacheHitRate = (tu.cacheTokens / (tu.inputTokens + tu.cacheTokens)) * 100;
367
- lines.push(`**Cache Hit Rate:** ${cacheHitRate.toFixed(1)}%`);
368
- lines.push('');
369
- }
370
- }
371
- if (summary.gitActivity) {
372
- const ga = summary.gitActivity;
373
- lines.push('## Git Activity');
374
- lines.push('');
375
- lines.push(`| Metric | Value |`);
376
- lines.push(`|--------|-------|`);
377
- lines.push(`| Commits | ${ga.commitCount} |`);
378
- lines.push(`| Files Changed | ${ga.filesChanged} |`);
379
- lines.push(`| Insertions | +${ga.insertions} |`);
380
- lines.push(`| Deletions | -${ga.deletions} |`);
381
- lines.push('');
382
- }
383
- if (summary.commits.length > 0) {
384
- lines.push('## Commits');
385
- lines.push('');
386
- for (const commit of summary.commits.slice(0, 10)) {
387
- const shortHash = commit.hash.slice(0, 7);
388
- lines.push(`- \`${shortHash}\` ${commit.message}`);
389
- }
390
- if (summary.commits.length > 10) {
391
- lines.push(`- ... and ${summary.commits.length - 10} more commits`);
392
- }
393
- lines.push('');
394
- }
395
- const { learnings, issuesFixed, pending } = summary.annotations;
396
- if (learnings) {
397
- lines.push('## Learnings');
398
- lines.push('');
399
- lines.push(learnings);
400
- lines.push('');
401
- }
402
- if (issuesFixed) {
403
- lines.push('## Issues Fixed');
404
- lines.push('');
405
- lines.push(issuesFixed);
406
- lines.push('');
407
- }
408
- if (pending) {
409
- lines.push('## Pending');
410
- lines.push('');
411
- lines.push(pending);
412
- lines.push('');
413
- }
414
- if (summary.historyHealth) {
415
- const hh = summary.historyHealth;
416
- lines.push('## Execution Loops & Health Findings');
417
- lines.push('');
418
- if (hh.loops.length === 0 && hh.toolCalls === 0) {
419
- lines.push('- **Status:** \u2705 Clean \u2014 No execution loops or tool calls recorded for this date.');
420
- lines.push('');
421
- } else {
422
- lines.push('| Metric | Value |');
423
- lines.push('|--------|-------|');
424
- lines.push(`| Tool Invocations | ${hh.toolCalls.toLocaleString()} |`);
425
- lines.push(`| Tool Errors | ${hh.toolErrors.toLocaleString()} (${hh.errorRatePct.toFixed(1)}%) |`);
426
- lines.push(`| Detected Loops (Repeats \u2265 3) | ${hh.loops.length} |`);
427
- lines.push(`| Redundant Invocations | ${hh.redundantCalls.toLocaleString()} |`);
428
- lines.push(`| Estimated Wasted Tokens | ${hh.wastedTokens.toLocaleString()} |`);
429
- lines.push('');
430
- if (hh.loops.length > 0) {
431
- lines.push('### Detected Execution Loops');
432
- lines.push('');
433
- for (const lp of hh.loops.slice(0, 10)) {
434
- const seqInfo = lp.fromSeq && lp.toSeq ? ` (steps #${lp.fromSeq} \u2192 #${lp.toSeq})` : '';
435
- const argsHint =
436
- lp.argsDigest === '74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b'
437
- ? 'empty/unrecorded arguments'
438
- : lp.argsDigest.length > 28
439
- ? `${lp.argsDigest.slice(0, 24)}...`
440
- : lp.argsDigest;
441
- lines.push(
442
- `- \`${lp.toolName || 'unknown'}\` \xD7 **${lp.repeats} repeats** in session \`${lp.sessionId}\`${seqInfo}`,
443
- );
444
- lines.push(` - *Args hint:* \`${argsHint}\` (~${lp.wastedTokens.toLocaleString()} wasted tokens)`);
445
- }
446
- lines.push('');
447
- }
448
- if (hh.remediationProposals.length > 0) {
449
- lines.push('### Auto-Healing Remediation Proposals');
450
- lines.push('');
451
- lines.push('To remediate root causes and prevent recurring token waste, execute:');
452
- lines.push('');
453
- lines.push('```bash');
454
- for (const prop of hh.remediationProposals) {
455
- lines.push(`# ${prop.title} [${prop.key}]`);
456
- lines.push(prop.command);
457
- lines.push('');
458
- }
459
- lines.push('```');
460
- lines.push('');
461
- }
462
- }
463
- }
464
- if (summary.historyReportPath) {
465
- lines.push('## History Report');
466
- lines.push('');
467
- lines.push(`- **Newest artifact:** ${summary.historyReportPath}`);
468
- lines.push('');
469
- }
470
- lines.push('---');
471
- lines.push('');
472
- lines.push(`*Generated by sp:daily-summary at ${summary.generatedAt}*`);
473
- return lines.join(`
474
- `);
475
- }
476
- function ensureDir(path) {
477
- if (!existsSync(path)) {
478
- mkdirSync(path, { recursive: true });
479
- }
480
- }
481
- function writeSummary(markdown, options) {
482
- const filename = `summary_${options.date.replace(/-/g, '')}.md`;
483
- const outputPath = options.outputPath || join(DAILY_DIR, filename);
484
- ensureDir(join(outputPath, '..'));
485
- writeFileSync(outputPath, markdown, 'utf-8');
486
- return outputPath;
487
- }
488
- function resolveHistoryReportPath() {
489
- const pointer = resolve(process.cwd(), '.spur', 'reports', 'history', 'latest.json');
490
- if (!existsSync(pointer)) {
491
- return;
492
- }
493
- try {
494
- const target = readlinkSync(pointer);
495
- const resolved = isAbsolute(target) ? target : resolve(dirname(pointer), target);
496
- return existsSync(resolved) ? resolved : undefined;
497
- } catch {
498
- return;
499
- }
500
- }
501
- async function buildDailySummary(options) {
502
- const platforms = [];
503
- let tokenUsage;
504
- if (!options.skipCcusage) {
505
- const ccusageData = await getCcusageData(options.date);
506
- if (ccusageData?.totals) {
507
- const totals = ccusageData.totals;
508
- tokenUsage = {
509
- inputTokens: totals.inputTokens,
510
- outputTokens: totals.outputTokens,
511
- cacheTokens: totals.cacheCreationTokens + totals.cacheReadTokens,
512
- totalTokens: totals.totalTokens,
513
- costUsd: totals.totalCost,
514
- };
515
- platforms.push('Claude Code');
516
- }
517
- }
518
- let gitActivity;
519
- let commits = [];
520
- if (!options.skipGit) {
521
- commits = await getGitCommits(options.date);
522
- if (commits.length > 0) {
523
- gitActivity = commits.reduce(
524
- (acc, commit) => ({
525
- commitCount: acc.commitCount + 1,
526
- filesChanged: acc.filesChanged + (commit.filesChanged || 0),
527
- insertions: acc.insertions + (commit.insertions || 0),
528
- deletions: acc.deletions + (commit.deletions || 0),
529
- }),
530
- { commitCount: 0, filesChanged: 0, insertions: 0, deletions: 0 },
531
- );
532
- platforms.push('Git');
533
- }
534
- }
535
- const historyReportPath = resolveHistoryReportPath();
536
- const annotations = await promptUser();
537
- const result = {
538
- date: options.date,
539
- platforms,
540
- commits,
541
- annotations,
542
- historyReportPath,
543
- generatedAt: new Date().toISOString().replace('T', ' ').slice(0, 19),
544
- };
545
- if (tokenUsage !== undefined) {
546
- result.tokenUsage = tokenUsage;
547
- }
548
- if (gitActivity !== undefined) {
549
- result.gitActivity = gitActivity;
550
- }
551
- const historyHealth = await getSpurHistoryHealth(options.date);
552
- if (historyHealth && (historyHealth.loops.length > 0 || historyHealth.toolCalls > 0)) {
553
- result.historyHealth = historyHealth;
554
- platforms.push('Spur History');
555
- }
556
- return result;
557
- }
558
- async function main() {
559
- const options = parseArgs();
560
- logger.info(`Generating daily summary for ${options.date}...`);
561
- try {
562
- const summary = await buildDailySummary(options);
563
- const markdown = generateMarkdown(summary);
564
- if (options.dryRun) {
565
- console.log(`
566
- ${markdown}
567
- `);
568
- logger.info('(dry-run) Summary not written to file');
569
- } else {
570
- const outputPath = writeSummary(markdown, options);
571
- console.log(`
572
- ${markdown}
573
- `);
574
- console.log(`
575
- \u2705 Summary written to: ${outputPath}`);
576
- }
577
- console.log(`
578
- \uD83D\uDCCA Summary Statistics:`);
579
- console.log(` Date: ${summary.date}`);
580
- console.log(` Platforms: ${summary.platforms.join(', ') || 'none'}`);
581
- if (summary.tokenUsage) {
582
- console.log(` Tokens: ${summary.tokenUsage.totalTokens.toLocaleString()}`);
583
- console.log(` Cost: $${summary.tokenUsage.costUsd.toFixed(4)}`);
584
- }
585
- if (summary.gitActivity) {
586
- console.log(` Commits: ${summary.gitActivity.commitCount}`);
587
- }
588
- } catch (error) {
589
- logger.error(`Failed to generate summary: ${error}`);
590
- process.exit(1);
591
- }
592
- }
593
- main().catch((error) => {
594
- logger.error(`Daily summary failed: ${error}`);
595
- process.exit(1);
596
- });
597
-
598
- export {
599
- buildDailySummary,
600
- defaultProcessSpawner,
601
- ensureDir,
602
- generateMarkdown,
603
- getCcusageData,
604
- getDateRange,
605
- getGitCommits,
606
- getSpurHistoryHealth,
607
- main,
608
- parseArgs,
609
- printUsage,
610
- promptUser,
611
- setProcessSpawner,
612
- todayLocal,
613
- writeSummary,
614
- yesterdayLocal,
615
- };