headlesscode 1.0.2

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 (232) hide show
  1. package/ATTRIBUTION.md +53 -0
  2. package/CODE_OF_CONDUCT.md +130 -0
  3. package/CONTRIBUTING.md +107 -0
  4. package/LICENSE +202 -0
  5. package/README.md +486 -0
  6. package/SECURITY.md +211 -0
  7. package/bin/headlesscode.mjs +83 -0
  8. package/package.json +63 -0
  9. package/shared/prompts/review-mode-prompt-short.md +93 -0
  10. package/shared/prompts/review-mode-prompt.md +281 -0
  11. package/shared/rules-code/rules.md +22 -0
  12. package/shared/stacks/cpp/rules.md +30 -0
  13. package/shared/stacks/fastapi/rules.md +30 -0
  14. package/shared/stacks/javascript/rules.md +37 -0
  15. package/shared/stacks/postgresql/rules.md +31 -0
  16. package/shared/stacks/python/rules.md +35 -0
  17. package/shared/stacks/react/rules.md +11 -0
  18. package/shared/stacks/typescript/rules.md +10 -0
  19. package/src/budget/budget.ts +221 -0
  20. package/src/budget/concurrency.ts +126 -0
  21. package/src/budget/cost.ts +309 -0
  22. package/src/budget/index.ts +8 -0
  23. package/src/checkpoints/cli.ts +256 -0
  24. package/src/checkpoints/service.ts +227 -0
  25. package/src/cli.ts +1535 -0
  26. package/src/cloud/docker-provider.ts +334 -0
  27. package/src/cloud/provider.ts +300 -0
  28. package/src/codeintel/call-graph.ts +78 -0
  29. package/src/codeintel/find-references.ts +123 -0
  30. package/src/codeintel/go-to-definition.ts +193 -0
  31. package/src/codeintel/handlers.ts +190 -0
  32. package/src/codeintel/import-graph.ts +173 -0
  33. package/src/codeintel/outline.ts +180 -0
  34. package/src/codeintel/position.ts +77 -0
  35. package/src/codeintel/program.ts +350 -0
  36. package/src/codeintel/rename-symbol.ts +213 -0
  37. package/src/codeintel/tools.ts +280 -0
  38. package/src/codemap/build.ts +135 -0
  39. package/src/codemap/cli.ts +190 -0
  40. package/src/codemap/extract.ts +339 -0
  41. package/src/codemap/files.ts +236 -0
  42. package/src/codemap/fingerprint.ts +65 -0
  43. package/src/codemap/flows.ts +62 -0
  44. package/src/codemap/html.ts +451 -0
  45. package/src/codemap/lock.ts +80 -0
  46. package/src/codemap/types.ts +101 -0
  47. package/src/codesearch/airunner-embedder.ts +185 -0
  48. package/src/codesearch/chunk.ts +339 -0
  49. package/src/codesearch/cli.ts +223 -0
  50. package/src/codesearch/embedder.ts +332 -0
  51. package/src/codesearch/files.ts +280 -0
  52. package/src/codesearch/index.ts +469 -0
  53. package/src/codesearch/ollama-embedder.ts +205 -0
  54. package/src/codesearch/search.ts +141 -0
  55. package/src/codesearch/types.ts +100 -0
  56. package/src/config/mode-models.ts +218 -0
  57. package/src/dashboard/aggregate.ts +364 -0
  58. package/src/dashboard/chat-thread.ts +141 -0
  59. package/src/dashboard/checkpoints.ts +124 -0
  60. package/src/dashboard/cli.ts +193 -0
  61. package/src/dashboard/codemap.ts +44 -0
  62. package/src/dashboard/files.ts +121 -0
  63. package/src/dashboard/page.ts +2803 -0
  64. package/src/dashboard/self-improvement-metrics.ts +282 -0
  65. package/src/dashboard/server.ts +1103 -0
  66. package/src/dashboard/session-launch.ts +310 -0
  67. package/src/dashboard/timeline.ts +273 -0
  68. package/src/dashboard/tool-exec.ts +107 -0
  69. package/src/dashboard/trend-cli.ts +141 -0
  70. package/src/dashboard/trend.ts +413 -0
  71. package/src/decision-proxy/cli.ts +261 -0
  72. package/src/decision-proxy/proxy.ts +569 -0
  73. package/src/deploy/gate-cli.ts +147 -0
  74. package/src/deploy/gate.ts +254 -0
  75. package/src/engine/condense.ts +512 -0
  76. package/src/engine/events.ts +428 -0
  77. package/src/engine/handoff.ts +71 -0
  78. package/src/engine/lazy-tools.ts +160 -0
  79. package/src/engine/local-explore.ts +653 -0
  80. package/src/engine/logger.ts +96 -0
  81. package/src/engine/loop.ts +5517 -0
  82. package/src/engine/parser.ts +347 -0
  83. package/src/engine/prompt.ts +860 -0
  84. package/src/engine/reports.ts +47 -0
  85. package/src/engine/stacks.ts +448 -0
  86. package/src/engine/types.ts +291 -0
  87. package/src/engine/usage.ts +186 -0
  88. package/src/github/app-auth.ts +161 -0
  89. package/src/github/cli.ts +448 -0
  90. package/src/github/installations.ts +133 -0
  91. package/src/github/pr.ts +321 -0
  92. package/src/github/provision.ts +118 -0
  93. package/src/github/push.ts +122 -0
  94. package/src/index-util.ts +50 -0
  95. package/src/index.ts +81 -0
  96. package/src/init/cli.ts +248 -0
  97. package/src/init/gitignore.ts +74 -0
  98. package/src/llm/ollama.ts +308 -0
  99. package/src/llm/openrouter.ts +868 -0
  100. package/src/llm/preflight.ts +367 -0
  101. package/src/llm/transcript-capture.ts +84 -0
  102. package/src/memory/embed.ts +110 -0
  103. package/src/memory/index.ts +22 -0
  104. package/src/memory/local.ts +259 -0
  105. package/src/memory/summarizer.ts +283 -0
  106. package/src/memory/types.ts +153 -0
  107. package/src/memory/uwuchat.ts +157 -0
  108. package/src/migrate/cli.ts +115 -0
  109. package/src/orchestrator/analyze-cli.ts +104 -0
  110. package/src/orchestrator/auto-split.ts +206 -0
  111. package/src/orchestrator/cleanup.ts +1003 -0
  112. package/src/orchestrator/cli.ts +3571 -0
  113. package/src/orchestrator/cost-estimate.ts +564 -0
  114. package/src/orchestrator/cost-history-cli.ts +242 -0
  115. package/src/orchestrator/cost-history.ts +397 -0
  116. package/src/orchestrator/git-sync.ts +250 -0
  117. package/src/orchestrator/index.ts +153 -0
  118. package/src/orchestrator/log-analysis.ts +0 -0
  119. package/src/orchestrator/merge-check.ts +108 -0
  120. package/src/orchestrator/pipeline.ts +411 -0
  121. package/src/orchestrator/resume.ts +1940 -0
  122. package/src/orchestrator/reviewer.ts +503 -0
  123. package/src/orchestrator/split.ts +296 -0
  124. package/src/orchestrator/state.ts +542 -0
  125. package/src/orchestrator/status.ts +697 -0
  126. package/src/orchestrator/verification-gate.ts +134 -0
  127. package/src/orchestrator/watch.ts +898 -0
  128. package/src/permissions/commands.ts +1083 -0
  129. package/src/permissions/config.ts +241 -0
  130. package/src/permissions/index.ts +12 -0
  131. package/src/permissions/protected-files.ts +96 -0
  132. package/src/permissions/store-protection.ts +272 -0
  133. package/src/project-store.ts +648 -0
  134. package/src/projects/cli.ts +382 -0
  135. package/src/qa/qa.ts +487 -0
  136. package/src/tools/browser/handler.ts +346 -0
  137. package/src/tools/browser/service.ts +406 -0
  138. package/src/tools/browser/smoke.ts +78 -0
  139. package/src/tools/browser/tool.ts +99 -0
  140. package/src/tools/executor.ts +2575 -0
  141. package/src/tools/language-detect.ts +183 -0
  142. package/src/tools/output-summarizer.ts +369 -0
  143. package/src/tools/run-tests.ts +302 -0
  144. package/src/tools/set-indentation-tool.ts +49 -0
  145. package/src/tools/test-selection.ts +160 -0
  146. package/src/vendor/tests/smoke.ts +103 -0
  147. package/src/vendor/zoo-code/VENDOR-NOTES.md +213 -0
  148. package/src/vendor/zoo-code/shim/anthropic.ts +71 -0
  149. package/src/vendor/zoo-code/shim/openai.d.ts +60 -0
  150. package/src/vendor/zoo-code/shim/os-name.ts +18 -0
  151. package/src/vendor/zoo-code/shim/strip-bom.ts +14 -0
  152. package/src/vendor/zoo-code/shim/vscode.ts +76 -0
  153. package/src/vendor/zoo-code/src/core/config/CustomModesManager.ts +1015 -0
  154. package/src/vendor/zoo-code/src/core/diff/strategies/multi-search-replace.ts +670 -0
  155. package/src/vendor/zoo-code/src/core/prompts/sections/capabilities.ts +46 -0
  156. package/src/vendor/zoo-code/src/core/prompts/sections/custom-instructions.ts +559 -0
  157. package/src/vendor/zoo-code/src/core/prompts/sections/index.ts +10 -0
  158. package/src/vendor/zoo-code/src/core/prompts/sections/markdown-formatting.ts +7 -0
  159. package/src/vendor/zoo-code/src/core/prompts/sections/modes.ts +35 -0
  160. package/src/vendor/zoo-code/src/core/prompts/sections/objective.ts +13 -0
  161. package/src/vendor/zoo-code/src/core/prompts/sections/rules.ts +95 -0
  162. package/src/vendor/zoo-code/src/core/prompts/sections/skills.ts +105 -0
  163. package/src/vendor/zoo-code/src/core/prompts/sections/system-info.ts +30 -0
  164. package/src/vendor/zoo-code/src/core/prompts/sections/tool-use-guidelines.ts +9 -0
  165. package/src/vendor/zoo-code/src/core/prompts/sections/tool-use.ts +7 -0
  166. package/src/vendor/zoo-code/src/core/prompts/system.ts +176 -0
  167. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/access_mcp_resource.ts +41 -0
  168. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_diff.ts +40 -0
  169. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_patch.ts +61 -0
  170. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/ask_followup_question.ts +62 -0
  171. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/attempt_completion.ts +33 -0
  172. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts +43 -0
  173. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/converters.ts +109 -0
  174. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit.ts +48 -0
  175. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit_file.ts +72 -0
  176. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/execute_command.ts +54 -0
  177. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/generate_image.ts +51 -0
  178. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/index.ts +75 -0
  179. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/list_files.ts +41 -0
  180. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/mcp_server.ts +75 -0
  181. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/new_task.ts +39 -0
  182. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_command_output.ts +81 -0
  183. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_file.ts +169 -0
  184. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/run_slash_command.ts +31 -0
  185. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_files.ts +50 -0
  186. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_replace.ts +51 -0
  187. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/skill.ts +33 -0
  188. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/switch_mode.ts +31 -0
  189. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/update_todo_list.ts +54 -0
  190. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/write_to_file.ts +40 -0
  191. package/src/vendor/zoo-code/src/core/prompts/types.ts +12 -0
  192. package/src/vendor/zoo-code/src/i18n/index.ts +19 -0
  193. package/src/vendor/zoo-code/src/integrations/misc/extract-text.ts +81 -0
  194. package/src/vendor/zoo-code/src/services/checkpoints/RepoPerTaskCheckpointService.ts +15 -0
  195. package/src/vendor/zoo-code/src/services/checkpoints/ShadowCheckpointService.ts +553 -0
  196. package/src/vendor/zoo-code/src/services/checkpoints/excludes.ts +212 -0
  197. package/src/vendor/zoo-code/src/services/checkpoints/index.ts +3 -0
  198. package/src/vendor/zoo-code/src/services/checkpoints/types.ts +35 -0
  199. package/src/vendor/zoo-code/src/services/code-index/manager.ts +19 -0
  200. package/src/vendor/zoo-code/src/services/mcp/McpHub.ts +36 -0
  201. package/src/vendor/zoo-code/src/services/roo-config/index.ts +441 -0
  202. package/src/vendor/zoo-code/src/services/search/file-search.ts +143 -0
  203. package/src/vendor/zoo-code/src/services/skills/SkillsManager.ts +20 -0
  204. package/src/vendor/zoo-code/src/shared/globalFileNames.ts +9 -0
  205. package/src/vendor/zoo-code/src/shared/language.ts +43 -0
  206. package/src/vendor/zoo-code/src/shared/modes.ts +257 -0
  207. package/src/vendor/zoo-code/src/shared/tools.ts +385 -0
  208. package/src/vendor/zoo-code/src/utils/fs.ts +39 -0
  209. package/src/vendor/zoo-code/src/utils/globalContext.ts +22 -0
  210. package/src/vendor/zoo-code/src/utils/json-schema.ts +16 -0
  211. package/src/vendor/zoo-code/src/utils/logging.ts +21 -0
  212. package/src/vendor/zoo-code/src/utils/mcp-name.ts +190 -0
  213. package/src/vendor/zoo-code/src/utils/object.ts +18 -0
  214. package/src/vendor/zoo-code/src/utils/path.ts +94 -0
  215. package/src/vendor/zoo-code/src/utils/shell.ts +376 -0
  216. package/src/vendor/zoo-code/src/utils/text-normalization.ts +99 -0
  217. package/src/vendor/zoo-code/types/global-settings.ts +19 -0
  218. package/src/vendor/zoo-code/types/index.ts +22 -0
  219. package/src/vendor/zoo-code/types/message.ts +375 -0
  220. package/src/vendor/zoo-code/types/mode.ts +241 -0
  221. package/src/vendor/zoo-code/types/todo.ts +19 -0
  222. package/src/vendor/zoo-code/types/tool-params.ts +116 -0
  223. package/src/vendor/zoo-code/types/tool.ts +67 -0
  224. package/src/vendor/zoo-code/types/vscode.ts +84 -0
  225. package/src/vision/describe.ts +242 -0
  226. package/src/vision/tool.ts +91 -0
  227. package/src/watcher/cli.ts +369 -0
  228. package/src/watcher/github.ts +304 -0
  229. package/src/watcher/index.ts +59 -0
  230. package/src/watcher/state.ts +254 -0
  231. package/src/watcher/watch.ts +562 -0
  232. package/tsconfig.json +18 -0
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Recursive self-improvement progress metrics (issue #145) — v1, scoped
3
+ * to a LOCAL dev-machine dashboard (no external deploy): computes a set
4
+ * of real, backfillable proxies for "is this loop actually getting
5
+ * better at running itself," from data this repo already has on disk
6
+ * plus `git log` and `gh issue list` — no new data collection required.
7
+ *
8
+ * Five metrics, each independently computable (a missing data source
9
+ * degrades that ONE section, never the whole response):
10
+ *
11
+ * 1. Session outcome rate over time (success/error/budget %, hourly
12
+ * buckets) — from `.headlesscode/usage/*.jsonl` via aggregate.ts's
13
+ * `buildSummary`. The most direct "is it working" signal the
14
+ * original issue named.
15
+ * 2. Standing guardrails landed, cumulative — commits whose message
16
+ * references a real issue number (`(#123)` or `#123`), from
17
+ * `git log`. A proxy for "the system is learning from its own
18
+ * failures," even though each fix is still human/Claude-authored.
19
+ * 3. Issue lifecycle (filed vs. closed over time, running open count)
20
+ * — from `gh issue list --state all`. Requires `gh` + network;
21
+ * degrades to `undefined` (not an error) when unavailable.
22
+ * 4. Iteration efficiency — mean iterations per SUCCESSFUL session,
23
+ * hourly — are successful runs getting cheaper/more direct over
24
+ * time, not just more numerous.
25
+ * 5. Session volume over time — raw dispatch count per hour, the
26
+ * activity-level context the other four metrics need to be read
27
+ * against (a success-rate spike on 2 sessions means less than one
28
+ * on 20).
29
+ *
30
+ * Pure + fs/exec-only (mirrors aggregate.ts's own stated ethos): no
31
+ * `node:http` here, so it's unit-testable without the dashboard server.
32
+ */
33
+
34
+ import { execFile } from "node:child_process"
35
+ import { promisify } from "node:util"
36
+
37
+ import { buildSummary, type SessionRow } from "./aggregate.js"
38
+
39
+ const execFileP = promisify(execFile)
40
+
41
+ export interface HourlyOutcomeBucket {
42
+ /** ISO-8601 hour bucket start, e.g. "2026-08-21T22:00:00.000Z". */
43
+ hour: string
44
+ success: number
45
+ error: number
46
+ budget: number
47
+ total: number
48
+ successRate: number
49
+ }
50
+
51
+ export interface GuardrailCommit {
52
+ sha: string
53
+ date: string
54
+ subject: string
55
+ issueRefs: number[]
56
+ }
57
+
58
+ export interface IssueLifecycleHour {
59
+ hour: string
60
+ filed: number
61
+ closed: number
62
+ }
63
+
64
+ export interface IssueLifecycle {
65
+ hours: IssueLifecycleHour[]
66
+ currentOpenCount: number
67
+ currentClosedCount: number
68
+ totalTracked: number
69
+ }
70
+
71
+ export interface IterationEfficiencyHour {
72
+ hour: string
73
+ meanIterations: number
74
+ successCount: number
75
+ }
76
+
77
+ export interface SessionVolumeHour {
78
+ hour: string
79
+ count: number
80
+ }
81
+
82
+ export interface SelfImprovementMetrics {
83
+ generatedAt: string
84
+ repo: string
85
+ totalSessions: number
86
+ overallSuccessRate: number
87
+ sessionOutcomesByHour: HourlyOutcomeBucket[]
88
+ standingGuardrails: {
89
+ commits: GuardrailCommit[]
90
+ cumulativeByHour: Array<{ hour: string; cumulative: number }>
91
+ totalCount: number
92
+ }
93
+ issueLifecycle: IssueLifecycle | undefined
94
+ iterationEfficiencyByHour: IterationEfficiencyHour[]
95
+ sessionVolumeByHour: SessionVolumeHour[]
96
+ }
97
+
98
+ /** Floor an ISO timestamp to its containing hour, returned as an ISO string. */
99
+ function hourBucket(iso: string): string {
100
+ const d = new Date(iso)
101
+ d.setUTCMinutes(0, 0, 0)
102
+ return d.toISOString()
103
+ }
104
+
105
+ function sortedHours(hours: Iterable<string>): string[] {
106
+ return [...new Set(hours)].sort()
107
+ }
108
+
109
+ export function computeSessionOutcomesByHour(sessions: SessionRow[]): HourlyOutcomeBucket[] {
110
+ const buckets = new Map<string, { success: number; error: number; budget: number }>()
111
+ for (const s of sessions) {
112
+ const hour = hourBucket(s.startedAt)
113
+ const bucket = buckets.get(hour) ?? { success: 0, error: 0, budget: 0 }
114
+ if (s.status === "success") bucket.success++
115
+ else if (s.status === "budget") bucket.budget++
116
+ else bucket.error++
117
+ buckets.set(hour, bucket)
118
+ }
119
+ return sortedHours(buckets.keys()).map((hour) => {
120
+ const b = buckets.get(hour)
121
+ if (!b) {
122
+ throw new Error(`[self-improvement-metrics] internal: no bucket for hour ${hour}`)
123
+ }
124
+ const total = b.success + b.error + b.budget
125
+ return { hour, ...b, total, successRate: total > 0 ? b.success / total : 0 }
126
+ })
127
+ }
128
+
129
+ export function computeIterationEfficiencyByHour(sessions: SessionRow[]): IterationEfficiencyHour[] {
130
+ const buckets = new Map<string, { sum: number; count: number }>()
131
+ for (const s of sessions) {
132
+ if (s.status !== "success") continue
133
+ const hour = hourBucket(s.startedAt)
134
+ const bucket = buckets.get(hour) ?? { sum: 0, count: 0 }
135
+ bucket.sum += s.iterations
136
+ bucket.count++
137
+ buckets.set(hour, bucket)
138
+ }
139
+ return sortedHours(buckets.keys()).map((hour) => {
140
+ const b = buckets.get(hour)
141
+ if (!b) {
142
+ throw new Error(`[self-improvement-metrics] internal: no bucket for hour ${hour}`)
143
+ }
144
+ return { hour, meanIterations: b.count > 0 ? b.sum / b.count : 0, successCount: b.count }
145
+ })
146
+ }
147
+
148
+ export function computeSessionVolumeByHour(sessions: SessionRow[]): SessionVolumeHour[] {
149
+ const buckets = new Map<string, number>()
150
+ for (const s of sessions) {
151
+ const hour = hourBucket(s.startedAt)
152
+ buckets.set(hour, (buckets.get(hour) ?? 0) + 1)
153
+ }
154
+ return sortedHours(buckets.keys()).map((hour) => ({ hour, count: buckets.get(hour) ?? 0 }))
155
+ }
156
+
157
+ /** Extract unique issue numbers referenced in a commit subject, e.g. "fix(x): y (#141)" -> [141]. */
158
+ export function extractIssueRefs(subject: string): number[] {
159
+ const matches = subject.match(/#(\d+)/g) ?? []
160
+ return [...new Set(matches.map((m) => Number(m.slice(1))))]
161
+ }
162
+
163
+ /**
164
+ * Standing guardrails: commits whose subject references a real issue
165
+ * number, on the current branch's history. Deliberately subject-only (not
166
+ * full commit body) — the body of a large fix commit often cites OTHER
167
+ * issue numbers as background/evidence (see this repo's own commits from
168
+ * 2026-08-21), which would inflate the count with references that aren't
169
+ * actually "this commit closes/addresses that issue."
170
+ */
171
+ export async function computeStandingGuardrails(
172
+ repoRoot: string,
173
+ ): Promise<{ commits: GuardrailCommit[]; cumulativeByHour: Array<{ hour: string; cumulative: number }>; totalCount: number }> {
174
+ let stdout: string
175
+ try {
176
+ ;({ stdout } = await execFileP("git", ["log", "--pretty=format:%H|%aI|%s", "--no-merges"], { cwd: repoRoot, maxBuffer: 1024 * 1024 * 16 }))
177
+ } catch {
178
+ return { commits: [], cumulativeByHour: [], totalCount: 0 }
179
+ }
180
+ const commits: GuardrailCommit[] = []
181
+ for (const line of stdout.split("\n")) {
182
+ if (!line.trim()) continue
183
+ const [sha, date, ...rest] = line.split("|")
184
+ const subject = rest.join("|")
185
+ const issueRefs = extractIssueRefs(subject)
186
+ if (issueRefs.length > 0) {
187
+ commits.push({ sha, date, subject, issueRefs })
188
+ }
189
+ }
190
+ // `git log`'s default order is newest-first; reverse to oldest-first
191
+ // BEFORE the stable sort so two commits sharing the same author-date
192
+ // second (the %aI format's granularity) preserve real chronological
193
+ // order instead of staying in git log's newest-first order for that tie.
194
+ commits.reverse()
195
+ commits.sort((a, b) => a.date.localeCompare(b.date))
196
+ const cumulativeByHour: Array<{ hour: string; cumulative: number }> = []
197
+ let running = 0
198
+ const hourTotals = new Map<string, number>()
199
+ for (const c of commits) {
200
+ const hour = hourBucket(c.date)
201
+ hourTotals.set(hour, (hourTotals.get(hour) ?? 0) + 1)
202
+ }
203
+ for (const hour of sortedHours(hourTotals.keys())) {
204
+ running += hourTotals.get(hour) ?? 0
205
+ cumulativeByHour.push({ hour, cumulative: running })
206
+ }
207
+ return { commits, cumulativeByHour, totalCount: commits.length }
208
+ }
209
+
210
+ interface GhIssueRecord {
211
+ number: number
212
+ createdAt: string
213
+ closedAt: string | null
214
+ state: string
215
+ }
216
+
217
+ /**
218
+ * Issue lifecycle via `gh issue list`. Returns undefined (not an error)
219
+ * when `gh` isn't installed/authenticated or the repo has no GitHub
220
+ * remote — this metric is optional, the other four don't depend on it.
221
+ */
222
+ export async function computeIssueLifecycle(repoRoot: string): Promise<IssueLifecycle | undefined> {
223
+ let stdout: string
224
+ try {
225
+ ;({ stdout } = await execFileP(
226
+ "gh",
227
+ ["issue", "list", "--state", "all", "--limit", "500", "--json", "number,createdAt,closedAt,state"],
228
+ { cwd: repoRoot, maxBuffer: 1024 * 1024 * 16 },
229
+ ))
230
+ } catch {
231
+ return undefined
232
+ }
233
+ let records: GhIssueRecord[]
234
+ try {
235
+ records = JSON.parse(stdout) as GhIssueRecord[]
236
+ } catch {
237
+ return undefined
238
+ }
239
+ const filedByHour = new Map<string, number>()
240
+ const closedByHour = new Map<string, number>()
241
+ let currentOpenCount = 0
242
+ let currentClosedCount = 0
243
+ for (const r of records) {
244
+ const filedHour = hourBucket(r.createdAt)
245
+ filedByHour.set(filedHour, (filedByHour.get(filedHour) ?? 0) + 1)
246
+ if (r.state === "OPEN") {
247
+ currentOpenCount++
248
+ } else {
249
+ currentClosedCount++
250
+ if (r.closedAt) {
251
+ const closedHour = hourBucket(r.closedAt)
252
+ closedByHour.set(closedHour, (closedByHour.get(closedHour) ?? 0) + 1)
253
+ }
254
+ }
255
+ }
256
+ const allHours = sortedHours([...filedByHour.keys(), ...closedByHour.keys()])
257
+ const hours = allHours.map((hour) => ({ hour, filed: filedByHour.get(hour) ?? 0, closed: closedByHour.get(hour) ?? 0 }))
258
+ return { hours, currentOpenCount, currentClosedCount, totalTracked: records.length }
259
+ }
260
+
261
+ export async function computeSelfImprovementMetrics(repoRoot: string): Promise<SelfImprovementMetrics> {
262
+ const summary = await buildSummary(repoRoot)
263
+ const sessions = summary.sessions
264
+ const successCount = sessions.filter((s) => s.status === "success").length
265
+
266
+ const [standingGuardrails, issueLifecycle] = await Promise.all([
267
+ computeStandingGuardrails(repoRoot),
268
+ computeIssueLifecycle(repoRoot),
269
+ ])
270
+
271
+ return {
272
+ generatedAt: new Date().toISOString(),
273
+ repo: repoRoot,
274
+ totalSessions: sessions.length,
275
+ overallSuccessRate: sessions.length > 0 ? successCount / sessions.length : 0,
276
+ sessionOutcomesByHour: computeSessionOutcomesByHour(sessions),
277
+ standingGuardrails,
278
+ issueLifecycle,
279
+ iterationEfficiencyByHour: computeIterationEfficiencyByHour(sessions),
280
+ sessionVolumeByHour: computeSessionVolumeByHour(sessions),
281
+ }
282
+ }