@gobing-ai/spur 0.3.41 → 0.3.42

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 (185) hide show
  1. package/.claude-plugin/marketplace.json +15 -0
  2. package/README.md +15 -1
  3. package/config/templates/AGENTS.md +5 -3
  4. package/config/workflows/task-pipeline.yaml +103 -12
  5. package/package.json +15 -12
  6. package/plugins/README.md +655 -0
  7. package/plugins/sp/README.md +621 -0
  8. package/plugins/sp/agents/expert-spur.md +109 -0
  9. package/plugins/sp/agents/super-coder.md +126 -0
  10. package/plugins/sp/agents/super-planner.md +283 -0
  11. package/plugins/sp/agents/super-reviewer.md +154 -0
  12. package/plugins/sp/commands/dev-arch.md +30 -0
  13. package/plugins/sp/commands/dev-brainstorm.md +40 -0
  14. package/plugins/sp/commands/dev-changelog.md +28 -0
  15. package/plugins/sp/commands/dev-daily.md +29 -0
  16. package/plugins/sp/commands/dev-debug.md +29 -0
  17. package/plugins/sp/commands/dev-dogfood.md +33 -0
  18. package/plugins/sp/commands/dev-featurechange.md +130 -0
  19. package/plugins/sp/commands/dev-find-conflict.md +51 -0
  20. package/plugins/sp/commands/dev-find-issue.md +76 -0
  21. package/plugins/sp/commands/dev-find-next.md +60 -0
  22. package/plugins/sp/commands/dev-fixall.md +33 -0
  23. package/plugins/sp/commands/dev-gitmsg.md +28 -0
  24. package/plugins/sp/commands/dev-gtd.md +97 -0
  25. package/plugins/sp/commands/dev-handover.md +26 -0
  26. package/plugins/sp/commands/dev-idea.md +50 -0
  27. package/plugins/sp/commands/dev-next.md +31 -0
  28. package/plugins/sp/commands/dev-parallel.md +30 -0
  29. package/plugins/sp/commands/dev-plan.md +46 -0
  30. package/plugins/sp/commands/dev-refine.md +48 -0
  31. package/plugins/sp/commands/dev-refineall.md +65 -0
  32. package/plugins/sp/commands/dev-refresh.md +32 -0
  33. package/plugins/sp/commands/dev-reverse.md +32 -0
  34. package/plugins/sp/commands/dev-review.md +31 -0
  35. package/plugins/sp/commands/dev-run.md +63 -0
  36. package/plugins/sp/commands/dev-runall.md +80 -0
  37. package/plugins/sp/commands/dev-simplify.md +39 -0
  38. package/plugins/sp/commands/dev-unit.md +29 -0
  39. package/plugins/sp/commands/dev-verify.md +48 -0
  40. package/plugins/sp/commands/dev-verifyall.md +72 -0
  41. package/plugins/sp/commands/dev-wrap.md +30 -0
  42. package/plugins/sp/commands/dev-wrapall.md +33 -0
  43. package/plugins/sp/commands/rule-add.md +18 -0
  44. package/plugins/sp/commands/rule-refine.md +18 -0
  45. package/plugins/sp/commands/rule-scan.md +18 -0
  46. package/plugins/sp/commands/spur-init.md +22 -0
  47. package/plugins/sp/commands/workflow-add.md +18 -0
  48. package/plugins/sp/commands/workflow-refine.md +18 -0
  49. package/plugins/sp/hooks/agent-hint.ts +60 -0
  50. package/plugins/sp/hooks/careful-guard.ts +147 -0
  51. package/plugins/sp/hooks/context-post-tool.ts +325 -0
  52. package/plugins/sp/hooks/context-session-start.ts +182 -0
  53. package/plugins/sp/hooks/context-session-stop.ts +95 -0
  54. package/plugins/sp/hooks/hooks.json +51 -0
  55. package/plugins/sp/hooks/pi/guard-extension.ts +341 -0
  56. package/plugins/sp/hooks/task-write-guard.ts +84 -0
  57. package/plugins/sp/plugin.json +8 -0
  58. package/plugins/sp/scripts/batch-preflight.ts +204 -0
  59. package/plugins/sp/scripts/daily-summary/daily-summary.ts +606 -0
  60. package/plugins/sp/scripts/daily-summary/logger.ts +28 -0
  61. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +363 -0
  62. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +168 -0
  63. package/plugins/sp/scripts/feature-sync-bounded.ts +451 -0
  64. package/plugins/sp/scripts/stage-registry-adapter.ts +1341 -0
  65. package/plugins/sp/scripts/task-size-precheck.ts +202 -0
  66. package/plugins/sp/scripts/validate-commands.ts +651 -0
  67. package/plugins/sp/scripts/validate-flag-contracts.ts +875 -0
  68. package/plugins/sp/skills/brainstorm/SKILL.md +361 -0
  69. package/plugins/sp/skills/brainstorm/agents/openai.yaml +14 -0
  70. package/plugins/sp/skills/brainstorm/examples/ideation-example.md +209 -0
  71. package/plugins/sp/skills/brainstorm/metadata.openclaw +7 -0
  72. package/plugins/sp/skills/brainstorm/references/grilling-interview.md +68 -0
  73. package/plugins/sp/skills/brainstorm/references/workflows.md +255 -0
  74. package/plugins/sp/skills/branch-workflow/SKILL.md +109 -0
  75. package/plugins/sp/skills/branch-workflow/references/branch-lifecycle.md +69 -0
  76. package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +100 -0
  77. package/plugins/sp/skills/code-implementation/SKILL.md +176 -0
  78. package/plugins/sp/skills/code-implementation/references/debugging.md +64 -0
  79. package/plugins/sp/skills/code-implementation/references/implementation-patterns.md +113 -0
  80. package/plugins/sp/skills/code-improvement/SKILL.md +231 -0
  81. package/plugins/sp/skills/code-improvement/references/deepening-signals.md +205 -0
  82. package/plugins/sp/skills/code-review/SKILL.md +112 -0
  83. package/plugins/sp/skills/code-review/references/receiving-code-review.md +70 -0
  84. package/plugins/sp/skills/code-review/references/review-lenses.md +117 -0
  85. package/plugins/sp/skills/code-review/references/self-review-checklist.md +53 -0
  86. package/plugins/sp/skills/code-review/references/verification-before-completion.md +68 -0
  87. package/plugins/sp/skills/code-simplification/SKILL.md +225 -0
  88. package/plugins/sp/skills/code-testing/SKILL.md +108 -0
  89. package/plugins/sp/skills/code-testing/references/stacks/bun-ts.md +165 -0
  90. package/plugins/sp/skills/code-testing/references/stacks/go.md +88 -0
  91. package/plugins/sp/skills/code-testing/references/stacks/python.md +117 -0
  92. package/plugins/sp/skills/code-testing/references/test-loop-breaker.md +37 -0
  93. package/plugins/sp/skills/code-testing/references/test-output-discipline.md +52 -0
  94. package/plugins/sp/skills/code-testing/references/unit-testing.md +212 -0
  95. package/plugins/sp/skills/code-verification/SKILL.md +535 -0
  96. package/plugins/sp/skills/code-verification/references/code-improvement.md +39 -0
  97. package/plugins/sp/skills/code-verification/references/secu-review.md +94 -0
  98. package/plugins/sp/skills/code-verification/references/verdict-schema.md +141 -0
  99. package/plugins/sp/skills/conflict-finding/SKILL.md +254 -0
  100. package/plugins/sp/skills/conflict-finding/references/authority-resolution.md +258 -0
  101. package/plugins/sp/skills/conflict-finding/references/comparison-protocol.md +277 -0
  102. package/plugins/sp/skills/conflict-finding/references/finding-contract.md +346 -0
  103. package/plugins/sp/skills/conflict-finding/references/remediation-routing.md +276 -0
  104. package/plugins/sp/skills/daily-summary/SKILL.md +171 -0
  105. package/plugins/sp/skills/daily-summary/agents/openai.yaml +13 -0
  106. package/plugins/sp/skills/doc-evolve/SKILL.md +175 -0
  107. package/plugins/sp/skills/doc-evolve/references/operations.md +93 -0
  108. package/plugins/sp/skills/dogfood-testing/SKILL.md +608 -0
  109. package/plugins/sp/skills/dogfood-testing/references/monitor-ledger.md +211 -0
  110. package/plugins/sp/skills/dogfood-testing/references/report-template.md +351 -0
  111. package/plugins/sp/skills/doubt-driven-development/SKILL.md +136 -0
  112. package/plugins/sp/skills/functional-review/SKILL.md +364 -0
  113. package/plugins/sp/skills/functional-review/references/verdict-schema.md +133 -0
  114. package/plugins/sp/skills/indexed-context/SKILL.md +164 -0
  115. package/plugins/sp/skills/issue-finding/SKILL.md +424 -0
  116. package/plugins/sp/skills/issue-finding/agents/openai.yaml +15 -0
  117. package/plugins/sp/skills/issue-finding/examples/expected-findings.json +27 -0
  118. package/plugins/sp/skills/issue-finding/examples/session-test-loop.jsonl +23 -0
  119. package/plugins/sp/skills/issue-finding/references/session-formats.md +121 -0
  120. package/plugins/sp/skills/next-feature/SKILL.md +125 -0
  121. package/plugins/sp/skills/next-feature/references/handoff-routing.md +92 -0
  122. package/plugins/sp/skills/next-feature/references/proposal-contract.md +49 -0
  123. package/plugins/sp/skills/next-feature/references/ranking-rubric.md +52 -0
  124. package/plugins/sp/skills/next-feature/references/signal-derivation.md +67 -0
  125. package/plugins/sp/skills/next-router/SKILL.md +181 -0
  126. package/plugins/sp/skills/next-router/references/messages.md +90 -0
  127. package/plugins/sp/skills/next-router/references/routing-table.md +216 -0
  128. package/plugins/sp/skills/parallel-execution/SKILL.md +148 -0
  129. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +100 -0
  130. package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +101 -0
  131. package/plugins/sp/skills/parallel-execution/references/result-synthesis.md +94 -0
  132. package/plugins/sp/skills/reverse-engineering/SKILL.md +375 -0
  133. package/plugins/sp/skills/reverse-engineering/agents/openai.yaml +18 -0
  134. package/plugins/sp/skills/source-driven-development/SKILL.md +124 -0
  135. package/plugins/sp/skills/spec-decomposition/SKILL.md +117 -0
  136. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +539 -0
  137. package/plugins/sp/skills/spur-cli/SKILL.md +133 -0
  138. package/plugins/sp/skills/spur-cli/references/agent.md +179 -0
  139. package/plugins/sp/skills/spur-cli/references/features/acceptance-criteria.md +82 -0
  140. package/plugins/sp/skills/spur-cli/references/features/hierarchy-mece.md +177 -0
  141. package/plugins/sp/skills/spur-cli/references/features/roadmap-priority.md +84 -0
  142. package/plugins/sp/skills/spur-cli/references/features/verbs.md +122 -0
  143. package/plugins/sp/skills/spur-cli/references/features.md +234 -0
  144. package/plugins/sp/skills/spur-cli/references/init.md +119 -0
  145. package/plugins/sp/skills/spur-cli/references/message.md +98 -0
  146. package/plugins/sp/skills/spur-cli/references/rules/authoring-rules.md +212 -0
  147. package/plugins/sp/skills/spur-cli/references/rules/fine-tuning.md +148 -0
  148. package/plugins/sp/skills/spur-cli/references/rules/operations.md +228 -0
  149. package/plugins/sp/skills/spur-cli/references/rules/validation-and-extension.md +129 -0
  150. package/plugins/sp/skills/spur-cli/references/rules.md +209 -0
  151. package/plugins/sp/skills/spur-cli/references/serve.md +58 -0
  152. package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +98 -0
  153. package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +95 -0
  154. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +361 -0
  155. package/plugins/sp/skills/spur-cli/references/tasks.md +278 -0
  156. package/plugins/sp/skills/spur-cli/references/team.md +133 -0
  157. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +226 -0
  158. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +211 -0
  159. package/plugins/sp/skills/spur-cli/references/workflows/validation-and-extension.md +129 -0
  160. package/plugins/sp/skills/spur-cli/references/workflows.md +355 -0
  161. package/plugins/sp/skills/spur-dev/SKILL.md +224 -0
  162. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +196 -0
  163. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +664 -0
  164. package/plugins/sp/skills/spur-dev/references/decision-brief.md +80 -0
  165. package/plugins/sp/skills/spur-dev/references/dev-operations.md +546 -0
  166. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +164 -0
  167. package/plugins/sp/skills/spur-dev/references/execution-batch.md +727 -0
  168. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +350 -0
  169. package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +191 -0
  170. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +417 -0
  171. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +171 -0
  172. package/plugins/sp/skills/spur-dev/references/glossary.md +95 -0
  173. package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +80 -0
  174. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +88 -0
  175. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +302 -0
  176. package/plugins/sp/skills/spur-dev/references/product-planning.md +206 -0
  177. package/plugins/sp/skills/spur-dev/references/section-batching.md +24 -0
  178. package/plugins/sp/skills/sys-architecture/SKILL.md +155 -0
  179. package/plugins/sp/skills/sys-architecture/references/decision-method.md +137 -0
  180. package/plugins/sp/skills/sys-architecture/references/upkeep-survey.md +75 -0
  181. package/plugins/sp/skills/sys-debugging/SKILL.md +228 -0
  182. package/plugins/sp/skills/sys-debugging/references/debugging-protocol.md +62 -0
  183. package/plugins/sp/skills/test-driven-development/SKILL.md +248 -0
  184. package/plugins/sp/skills/wayfinder/SKILL.md +292 -0
  185. package/spur.js +6093 -5180
@@ -0,0 +1,606 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * sp:daily-summary — Daily Summary Report Generator
4
+ *
5
+ * Generates structured markdown summaries from:
6
+ * - Token usage data (via ccusage CLI)
7
+ * - Git history (commits, changes)
8
+ * - User annotations (learnings, issues, pending)
9
+ */
10
+
11
+ import { existsSync, mkdirSync, readlinkSync, writeFileSync } from 'node:fs';
12
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
13
+ import { logger } from './logger';
14
+
15
+ // ─── Types ───────────────────────────────────────────────────────────────────
16
+
17
+ export interface CliOptions {
18
+ date: string;
19
+ dryRun: boolean;
20
+ outputPath?: string;
21
+ skipGit: boolean;
22
+ skipCcusage: boolean;
23
+ }
24
+
25
+ interface CcusageData {
26
+ daily?: Array<{
27
+ date: string;
28
+ inputTokens: number;
29
+ outputTokens: number;
30
+ cacheCreationTokens: number;
31
+ cacheReadTokens: number;
32
+ totalTokens: number;
33
+ totalCost: number;
34
+ modelsUsed: string[];
35
+ }>;
36
+ totals: {
37
+ inputTokens: number;
38
+ outputTokens: number;
39
+ cacheCreationTokens: number;
40
+ cacheReadTokens: number;
41
+ totalTokens: number;
42
+ totalCost: number;
43
+ };
44
+ }
45
+
46
+ export interface GitCommit {
47
+ hash: string;
48
+ date: string;
49
+ message: string;
50
+ filesChanged: number;
51
+ insertions: number;
52
+ deletions: number;
53
+ }
54
+
55
+ export interface UserAnnotations {
56
+ learnings: string;
57
+ issuesFixed: string;
58
+ pending: string;
59
+ }
60
+
61
+ export interface DailySummary {
62
+ date: string;
63
+ platforms: string[];
64
+ tokenUsage?: {
65
+ inputTokens: number;
66
+ outputTokens: number;
67
+ cacheTokens: number;
68
+ totalTokens: number;
69
+ costUsd: number;
70
+ };
71
+ gitActivity?: {
72
+ commitCount: number;
73
+ filesChanged: number;
74
+ insertions: number;
75
+ deletions: number;
76
+ };
77
+ commits: GitCommit[];
78
+ annotations: UserAnnotations;
79
+ /** Path to the newest history report artifact (R7), resolved from the
80
+ * `.spur/reports/history/latest.json` pointer. Omitted when no report exists. */
81
+ historyReportPath?: string;
82
+ generatedAt: string;
83
+ }
84
+
85
+ // ─── Constants ────────────────────────────────────────────────────────────────
86
+
87
+ const DAILY_DIR = 'docs/daily';
88
+ const DEFAULT_DATE = 'today';
89
+
90
+ // ─── CLI Argument Parsing ────────────────────────────────────────────────────
91
+
92
+ export function parseArgs(argv: string[] = process.argv.slice(2)): CliOptions {
93
+ const args = argv;
94
+ const options: CliOptions = {
95
+ date: DEFAULT_DATE,
96
+ dryRun: false,
97
+ skipGit: false,
98
+ skipCcusage: false,
99
+ };
100
+
101
+ for (let i = 0; i < args.length; i++) {
102
+ const arg = args[i];
103
+ if (arg === '--date' && i + 1 < args.length) {
104
+ options.date = args[++i];
105
+ } else if (arg === '--dry-run') {
106
+ options.dryRun = true;
107
+ } else if (arg === '--output' && i + 1 < args.length) {
108
+ options.outputPath = args[++i];
109
+ } else if (arg === '--no-git') {
110
+ options.skipGit = true;
111
+ } else if (arg === '--no-ccusage') {
112
+ options.skipCcusage = true;
113
+ } else if (arg === '--help' || arg === '-h') {
114
+ printUsage();
115
+ process.exit(0);
116
+ }
117
+ }
118
+
119
+ // Resolve date
120
+ if (options.date === 'today') {
121
+ options.date = todayLocal();
122
+ } else if (options.date === 'yesterday') {
123
+ options.date = yesterdayLocal();
124
+ }
125
+
126
+ return options;
127
+ }
128
+
129
+ export function printUsage(): void {
130
+ console.log(`
131
+ sp:daily-summary — Generate daily summary reports
132
+
133
+ Usage: daily-summary.ts [options]
134
+
135
+ Options:
136
+ --date YYYY-MM-DD Date for summary (default: today, also: yesterday)
137
+ --dry-run Show summary without writing file
138
+ --output <path> Write to custom path
139
+ --no-git Skip git history collection
140
+ --no-ccusage Skip token usage collection
141
+ --help, -h Show this help
142
+
143
+ Examples:
144
+ daily-summary.ts # Today's summary
145
+ daily-summary.ts --date yesterday # Yesterday's summary
146
+ daily-summary.ts --dry-run # Preview without writing
147
+ `);
148
+ }
149
+
150
+ // ─── Date Helpers ─────────────────────────────────────────────────────────────
151
+
152
+ /** Return today's date as YYYY-MM-DD in the system (git) timezone. */
153
+ export function todayLocal(): string {
154
+ // Spawn 'date' to get system timezone date, since JS runtime
155
+ // may have a different TZ (e.g. bun test forces UTC).
156
+ const proc = Bun.spawnSync(['date', '+%Y-%m-%d']);
157
+ return proc.stdout.toString().trim();
158
+ }
159
+
160
+ /** Return yesterday's date as YYYY-MM-DD in the system (git) timezone. */
161
+ export function yesterdayLocal(): string {
162
+ // Use portable epoch math via date command
163
+ const epochProc = Bun.spawnSync(['date', '+%s']);
164
+ const epoch = parseInt(epochProc.stdout.toString().trim(), 10);
165
+ const yesterdayEpoch = epoch - 86400;
166
+ // Try BSD -r first, then GNU -d @
167
+ let proc = Bun.spawnSync(['date', '-r', String(yesterdayEpoch), '+%Y-%m-%d']);
168
+ if (proc.exitCode !== 0) {
169
+ proc = Bun.spawnSync(['date', '-d', `@${yesterdayEpoch}`, '+%Y-%m-%d']);
170
+ }
171
+ return proc.stdout.toString().trim();
172
+ }
173
+
174
+ export function getDateRange(dateStr: string): { start: string; end: string } {
175
+ // Date is YYYY-MM-DD format
176
+ const date = new Date(`${dateStr}T00:00:00`);
177
+ const start = `${dateStr} 00:00:00`;
178
+ const endDate = new Date(date);
179
+ endDate.setDate(endDate.getDate() + 1);
180
+ const end = `${endDate.toISOString().slice(0, 10)} 00:00:00`;
181
+ return { start, end };
182
+ }
183
+
184
+ // ─── Ccusage Integration ─────────────────────────────────────────────────────
185
+
186
+ export async function getCcusageData(date: string): Promise<CcusageData | null> {
187
+ try {
188
+ // Check if ccusage is available
189
+ const env = { ...process.env };
190
+ const ccusageCheck = Bun.spawn(['ccusage', '--version'], {
191
+ stdout: 'pipe',
192
+ stderr: 'pipe',
193
+ env,
194
+ });
195
+ await new Response(ccusageCheck.stdout).text();
196
+ await ccusageCheck.exited;
197
+
198
+ // Get daily data for the date
199
+ const since = `${date}T00:00:00`;
200
+ const until = `${date}T23:59:59`;
201
+
202
+ const proc = Bun.spawn(['ccusage', 'daily', '--since', since, '--until', until, '--json'], {
203
+ stdout: 'pipe',
204
+ stderr: 'pipe',
205
+ env,
206
+ });
207
+
208
+ const output = await new Response(proc.stdout).text();
209
+ const exitCode = await proc.exited;
210
+
211
+ if (exitCode !== 0) {
212
+ const errorOutput = await new Response(proc.stderr).text();
213
+ logger.warn(`ccusage error: ${errorOutput}`);
214
+ return null;
215
+ }
216
+
217
+ const data = JSON.parse(output) as CcusageData;
218
+ return data;
219
+ } catch (error) {
220
+ logger.warn(`Failed to get ccusage data: ${error}`);
221
+ return null;
222
+ }
223
+ }
224
+
225
+ // ─── Git Integration ─────────────────────────────────────────────────────────
226
+
227
+ export async function getGitCommits(date: string): Promise<GitCommit[]> {
228
+ try {
229
+ const { start, end } = getDateRange(date);
230
+
231
+ const proc = Bun.spawn(
232
+ ['git', 'log', '--since', start, '--until', end, '--pretty=format:%H|%ad|%s', '--date=iso', '--numstat'],
233
+ { stdout: 'pipe', stderr: 'pipe' },
234
+ );
235
+
236
+ const output = await new Response(proc.stdout).text();
237
+ const exitCode = await proc.exited;
238
+
239
+ if (exitCode !== 0) {
240
+ logger.warn('Failed to get git commits');
241
+ return [];
242
+ }
243
+
244
+ const commits: GitCommit[] = [];
245
+ const lines = output.trim().split('\n');
246
+
247
+ let currentCommit: Partial<GitCommit> | null = null;
248
+
249
+ for (const line of lines) {
250
+ if (!line.trim()) continue;
251
+
252
+ // Check if this is a commit line (contains | separator)
253
+ if (line.includes('|')) {
254
+ const parts = line.split('|');
255
+ if (parts.length >= 3) {
256
+ // Save previous commit if exists
257
+ if (currentCommit?.hash) {
258
+ commits.push(currentCommit as GitCommit);
259
+ }
260
+
261
+ currentCommit = {
262
+ hash: parts[0],
263
+ date: parts[1],
264
+ message: parts[2],
265
+ filesChanged: 0,
266
+ insertions: 0,
267
+ deletions: 0,
268
+ };
269
+ }
270
+ } else if (currentCommit && line.includes('\t')) {
271
+ // This is a numstat line (files changed)
272
+ const parts = line.split('\t');
273
+ if (parts.length >= 3) {
274
+ const insertions = parseInt(parts[0], 10) || 0;
275
+ const deletions = parseInt(parts[1], 10) || 0;
276
+ currentCommit.filesChanged = (currentCommit.filesChanged ?? 0) + 1;
277
+ currentCommit.insertions = (currentCommit.insertions ?? 0) + insertions;
278
+ currentCommit.deletions = (currentCommit.deletions ?? 0) + deletions;
279
+ }
280
+ }
281
+ }
282
+
283
+ // Don't forget the last commit
284
+ if (currentCommit?.hash) {
285
+ commits.push(currentCommit as GitCommit);
286
+ }
287
+
288
+ return commits;
289
+ } catch (error) {
290
+ logger.warn(`Failed to get git commits: ${error}`);
291
+ return [];
292
+ }
293
+ }
294
+
295
+ // ─── User Input ─────────────────────────────────────────────────────────────
296
+
297
+ export async function promptUser(): Promise<UserAnnotations> {
298
+ if (process.env.SP_DAILY_SUMMARY_NO_PROMPT === '1') {
299
+ return { learnings: '', issuesFixed: '', pending: '' };
300
+ }
301
+ if (process.env.RD3_DAILY_SUMMARY_NO_PROMPT === '1') {
302
+ logger.warn('[deprecate] RD3_DAILY_SUMMARY_NO_PROMPT is deprecated; use SP_DAILY_SUMMARY_NO_PROMPT');
303
+ return { learnings: '', issuesFixed: '', pending: '' };
304
+ }
305
+
306
+ console.log(`\n📊 Daily Summary — ${todayLocal()}`);
307
+ console.log('═'.repeat(50));
308
+ console.log('\nPlease provide the following (press Enter to skip):\n');
309
+
310
+ if (!process.stdin.isTTY) {
311
+ const chunks: Buffer[] = [];
312
+ for await (const chunk of process.stdin) {
313
+ chunks.push(chunk as Buffer);
314
+ }
315
+ const buffered = Buffer.concat(chunks).toString('utf-8');
316
+ const [learnings = '', issuesFixed = '', pending = ''] = buffered.split('\n');
317
+ return {
318
+ learnings: learnings.trim(),
319
+ issuesFixed: issuesFixed.trim(),
320
+ pending: pending.trim(),
321
+ };
322
+ }
323
+
324
+ const readline = await import('node:readline');
325
+ const rl = readline.createInterface({
326
+ input: process.stdin,
327
+ output: process.stdout,
328
+ });
329
+
330
+ const question = (prompt: string): Promise<string> =>
331
+ new Promise((resolve) => {
332
+ rl.question(prompt, (answer) => {
333
+ resolve(answer.trim());
334
+ });
335
+ });
336
+
337
+ const learnings = await question('1. What did you learn today? (optional)\n > ');
338
+ const issuesFixed = await question('\n2. What issues did you fix? (optional)\n > ');
339
+ const pending = await question("\n3. What's pending for tomorrow? (optional)\n > ");
340
+
341
+ rl.close();
342
+
343
+ return {
344
+ learnings,
345
+ issuesFixed,
346
+ pending,
347
+ };
348
+ }
349
+
350
+ // ─── Markdown Generation ─────────────────────────────────────────────────────
351
+
352
+ export function generateMarkdown(summary: DailySummary): string {
353
+ const lines: string[] = [];
354
+
355
+ // Header
356
+ lines.push(`# Daily Summary — ${summary.date}`);
357
+ lines.push('');
358
+ lines.push(`**Generated:** ${summary.generatedAt}`);
359
+ lines.push('');
360
+
361
+ // Meta section
362
+ lines.push('## Meta');
363
+ lines.push('');
364
+ lines.push(`- **Date:** ${summary.date}`);
365
+ lines.push(`- **Platforms:** ${summary.platforms.join(', ') || 'unknown'}`);
366
+ lines.push('');
367
+
368
+ // Token Usage
369
+ if (summary.tokenUsage) {
370
+ const tu = summary.tokenUsage;
371
+ lines.push('## Token Usage');
372
+ lines.push('');
373
+ lines.push(`| Metric | Value |`);
374
+ lines.push(`|--------|-------|`);
375
+ lines.push(`| Input Tokens | ${tu.inputTokens.toLocaleString()} |`);
376
+ lines.push(`| Output Tokens | ${tu.outputTokens.toLocaleString()} |`);
377
+ lines.push(`| Cache Tokens | ${tu.cacheTokens.toLocaleString()} |`);
378
+ lines.push(`| Total Tokens | ${tu.totalTokens.toLocaleString()} |`);
379
+ lines.push(`| Estimated Cost | $${tu.costUsd.toFixed(4)} |`);
380
+ lines.push('');
381
+
382
+ // Calculate cache hit rate
383
+ if (tu.inputTokens > 0) {
384
+ const cacheHitRate = (tu.cacheTokens / (tu.inputTokens + tu.cacheTokens)) * 100;
385
+ lines.push(`**Cache Hit Rate:** ${cacheHitRate.toFixed(1)}%`);
386
+ lines.push('');
387
+ }
388
+ }
389
+
390
+ // Git Activity
391
+ if (summary.gitActivity) {
392
+ const ga = summary.gitActivity;
393
+ lines.push('## Git Activity');
394
+ lines.push('');
395
+ lines.push(`| Metric | Value |`);
396
+ lines.push(`|--------|-------|`);
397
+ lines.push(`| Commits | ${ga.commitCount} |`);
398
+ lines.push(`| Files Changed | ${ga.filesChanged} |`);
399
+ lines.push(`| Insertions | +${ga.insertions} |`);
400
+ lines.push(`| Deletions | -${ga.deletions} |`);
401
+ lines.push('');
402
+ }
403
+
404
+ // Commits
405
+ if (summary.commits.length > 0) {
406
+ lines.push('## Commits');
407
+ lines.push('');
408
+ for (const commit of summary.commits.slice(0, 10)) {
409
+ const shortHash = commit.hash.slice(0, 7);
410
+ lines.push(`- \`${shortHash}\` ${commit.message}`);
411
+ }
412
+ if (summary.commits.length > 10) {
413
+ lines.push(`- ... and ${summary.commits.length - 10} more commits`);
414
+ }
415
+ lines.push('');
416
+ }
417
+
418
+ // Annotations
419
+ const { learnings, issuesFixed, pending } = summary.annotations;
420
+
421
+ if (learnings) {
422
+ lines.push('## Learnings');
423
+ lines.push('');
424
+ lines.push(learnings);
425
+ lines.push('');
426
+ }
427
+
428
+ if (issuesFixed) {
429
+ lines.push('## Issues Fixed');
430
+ lines.push('');
431
+ lines.push(issuesFixed);
432
+ lines.push('');
433
+ }
434
+
435
+ if (pending) {
436
+ lines.push('## Pending');
437
+ lines.push('');
438
+ lines.push(pending);
439
+ lines.push('');
440
+ }
441
+
442
+ // History report path (R7 — surfaces the newest nightly-run artifact).
443
+ if (summary.historyReportPath) {
444
+ lines.push('## History Report');
445
+ lines.push('');
446
+ lines.push(`- **Newest artifact:** ${summary.historyReportPath}`);
447
+ lines.push('');
448
+ }
449
+
450
+ // Footer
451
+ lines.push('---');
452
+ lines.push('');
453
+ lines.push(`*Generated by sp:daily-summary at ${summary.generatedAt}*`);
454
+
455
+ return lines.join('\n');
456
+ }
457
+
458
+ // ─── File Output ─────────────────────────────────────────────────────────────
459
+
460
+ export function ensureDir(path: string): void {
461
+ if (!existsSync(path)) {
462
+ mkdirSync(path, { recursive: true });
463
+ }
464
+ }
465
+
466
+ export function writeSummary(markdown: string, options: CliOptions): string {
467
+ const filename = `summary_${options.date.replace(/-/g, '')}.md`;
468
+ const outputPath = options.outputPath || join(DAILY_DIR, filename);
469
+
470
+ ensureDir(join(outputPath, '..'));
471
+
472
+ writeFileSync(outputPath, markdown, 'utf-8');
473
+
474
+ return outputPath;
475
+ }
476
+
477
+ // ─── Main ────────────────────────────────────────────────────────────────────
478
+
479
+ /**
480
+ * Resolve the newest history report artifact path by following the
481
+ * `.spur/reports/history/latest.json` symlink (task 0471 R7). Returns
482
+ * `undefined` when no report exists — the daily summary simply omits the
483
+ * section rather than failing.
484
+ */
485
+ function resolveHistoryReportPath(): string | undefined {
486
+ const pointer = resolve(process.cwd(), '.spur', 'reports', 'history', 'latest.json');
487
+ if (!existsSync(pointer)) {
488
+ return undefined;
489
+ }
490
+ try {
491
+ const target = readlinkSync(pointer);
492
+ const resolved = isAbsolute(target) ? target : resolve(dirname(pointer), target);
493
+ return existsSync(resolved) ? resolved : undefined;
494
+ } catch {
495
+ // Not a symlink or unreadable — treat as absent.
496
+ return undefined;
497
+ }
498
+ }
499
+
500
+ export async function buildDailySummary(options: CliOptions): Promise<DailySummary> {
501
+ const platforms: string[] = [];
502
+
503
+ // Get token usage from ccusage
504
+ let tokenUsage: DailySummary['tokenUsage'];
505
+
506
+ if (!options.skipCcusage) {
507
+ const ccusageData = await getCcusageData(options.date);
508
+ if (ccusageData?.totals) {
509
+ const totals = ccusageData.totals;
510
+ tokenUsage = {
511
+ inputTokens: totals.inputTokens,
512
+ outputTokens: totals.outputTokens,
513
+ cacheTokens: totals.cacheCreationTokens + totals.cacheReadTokens,
514
+ totalTokens: totals.totalTokens,
515
+ costUsd: totals.totalCost,
516
+ };
517
+ platforms.push('Claude Code');
518
+ }
519
+ }
520
+
521
+ // Get git history
522
+ let gitActivity: DailySummary['gitActivity'];
523
+ let commits: GitCommit[] = [];
524
+
525
+ if (!options.skipGit) {
526
+ commits = await getGitCommits(options.date);
527
+ if (commits.length > 0) {
528
+ gitActivity = commits.reduce(
529
+ (acc, commit) => ({
530
+ commitCount: acc.commitCount + 1,
531
+ filesChanged: acc.filesChanged + (commit.filesChanged || 0),
532
+ insertions: acc.insertions + (commit.insertions || 0),
533
+ deletions: acc.deletions + (commit.deletions || 0),
534
+ }),
535
+ { commitCount: 0, filesChanged: 0, insertions: 0, deletions: 0 },
536
+ );
537
+ platforms.push('Git');
538
+ }
539
+ }
540
+
541
+ // R7 — surface the newest history report artifact path (if present).
542
+ const historyReportPath = resolveHistoryReportPath();
543
+
544
+ // Get user annotations
545
+ const annotations = await promptUser();
546
+
547
+ const result: DailySummary = {
548
+ date: options.date,
549
+ platforms,
550
+ commits,
551
+ annotations,
552
+ historyReportPath,
553
+ generatedAt: new Date().toISOString().replace('T', ' ').slice(0, 19),
554
+ };
555
+
556
+ if (tokenUsage !== undefined) {
557
+ result.tokenUsage = tokenUsage;
558
+ }
559
+ if (gitActivity !== undefined) {
560
+ result.gitActivity = gitActivity;
561
+ }
562
+
563
+ return result;
564
+ }
565
+
566
+ export async function main(): Promise<void> {
567
+ const options = parseArgs();
568
+
569
+ logger.info(`Generating daily summary for ${options.date}...`);
570
+
571
+ try {
572
+ const summary = await buildDailySummary(options);
573
+ const markdown = generateMarkdown(summary);
574
+
575
+ if (options.dryRun) {
576
+ console.log(`\n${markdown}\n`);
577
+ logger.info('(dry-run) Summary not written to file');
578
+ } else {
579
+ const outputPath = writeSummary(markdown, options);
580
+ console.log(`\n${markdown}\n`);
581
+ console.log(`\n✅ Summary written to: ${outputPath}`);
582
+ }
583
+
584
+ // Print summary stats
585
+ console.log('\n📊 Summary Statistics:');
586
+ console.log(` Date: ${summary.date}`);
587
+ console.log(` Platforms: ${summary.platforms.join(', ') || 'none'}`);
588
+ if (summary.tokenUsage) {
589
+ console.log(` Tokens: ${summary.tokenUsage.totalTokens.toLocaleString()}`);
590
+ console.log(` Cost: $${summary.tokenUsage.costUsd.toFixed(4)}`);
591
+ }
592
+ if (summary.gitActivity) {
593
+ console.log(` Commits: ${summary.gitActivity.commitCount}`);
594
+ }
595
+ } catch (error) {
596
+ logger.error(`Failed to generate summary: ${error}`);
597
+ process.exit(1);
598
+ }
599
+ }
600
+
601
+ if (import.meta.main) {
602
+ main().catch((error) => {
603
+ logger.error(`Daily summary failed: ${error}`);
604
+ process.exit(1);
605
+ });
606
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Minimal self-contained logger for the embedded daily-summary script.
3
+ *
4
+ * The skill script stays self-contained (no CLI extraction, no monorepo coupling), so it carries its
5
+ * own tiny logger rather than importing the host plugin's. API mirrors the logger subset the script
6
+ * and test use: `logger.{info,warn,error}` and `enableLogger(console, file)`.
7
+ */
8
+
9
+ type Channels = { console: boolean; file: boolean };
10
+
11
+ const state: Channels = { console: true, file: false };
12
+
13
+ /** Toggle output channels. Only the console channel is honoured here (no file appender by design). */
14
+ export function enableLogger(consoleOn: boolean, fileOn: boolean): void {
15
+ state.console = consoleOn;
16
+ state.file = fileOn;
17
+ }
18
+
19
+ function emit(stream: 'log' | 'warn' | 'error', message: string): void {
20
+ if (!state.console) return;
21
+ console[stream](message);
22
+ }
23
+
24
+ export const logger = {
25
+ info: (message: string): void => emit('log', message),
26
+ warn: (message: string): void => emit('warn', message),
27
+ error: (message: string): void => emit('error', message),
28
+ };