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.
- package/ATTRIBUTION.md +53 -0
- package/CODE_OF_CONDUCT.md +130 -0
- package/CONTRIBUTING.md +107 -0
- package/LICENSE +202 -0
- package/README.md +486 -0
- package/SECURITY.md +211 -0
- package/bin/headlesscode.mjs +83 -0
- package/package.json +63 -0
- package/shared/prompts/review-mode-prompt-short.md +93 -0
- package/shared/prompts/review-mode-prompt.md +281 -0
- package/shared/rules-code/rules.md +22 -0
- package/shared/stacks/cpp/rules.md +30 -0
- package/shared/stacks/fastapi/rules.md +30 -0
- package/shared/stacks/javascript/rules.md +37 -0
- package/shared/stacks/postgresql/rules.md +31 -0
- package/shared/stacks/python/rules.md +35 -0
- package/shared/stacks/react/rules.md +11 -0
- package/shared/stacks/typescript/rules.md +10 -0
- package/src/budget/budget.ts +221 -0
- package/src/budget/concurrency.ts +126 -0
- package/src/budget/cost.ts +309 -0
- package/src/budget/index.ts +8 -0
- package/src/checkpoints/cli.ts +256 -0
- package/src/checkpoints/service.ts +227 -0
- package/src/cli.ts +1535 -0
- package/src/cloud/docker-provider.ts +334 -0
- package/src/cloud/provider.ts +300 -0
- package/src/codeintel/call-graph.ts +78 -0
- package/src/codeintel/find-references.ts +123 -0
- package/src/codeintel/go-to-definition.ts +193 -0
- package/src/codeintel/handlers.ts +190 -0
- package/src/codeintel/import-graph.ts +173 -0
- package/src/codeintel/outline.ts +180 -0
- package/src/codeintel/position.ts +77 -0
- package/src/codeintel/program.ts +350 -0
- package/src/codeintel/rename-symbol.ts +213 -0
- package/src/codeintel/tools.ts +280 -0
- package/src/codemap/build.ts +135 -0
- package/src/codemap/cli.ts +190 -0
- package/src/codemap/extract.ts +339 -0
- package/src/codemap/files.ts +236 -0
- package/src/codemap/fingerprint.ts +65 -0
- package/src/codemap/flows.ts +62 -0
- package/src/codemap/html.ts +451 -0
- package/src/codemap/lock.ts +80 -0
- package/src/codemap/types.ts +101 -0
- package/src/codesearch/airunner-embedder.ts +185 -0
- package/src/codesearch/chunk.ts +339 -0
- package/src/codesearch/cli.ts +223 -0
- package/src/codesearch/embedder.ts +332 -0
- package/src/codesearch/files.ts +280 -0
- package/src/codesearch/index.ts +469 -0
- package/src/codesearch/ollama-embedder.ts +205 -0
- package/src/codesearch/search.ts +141 -0
- package/src/codesearch/types.ts +100 -0
- package/src/config/mode-models.ts +218 -0
- package/src/dashboard/aggregate.ts +364 -0
- package/src/dashboard/chat-thread.ts +141 -0
- package/src/dashboard/checkpoints.ts +124 -0
- package/src/dashboard/cli.ts +193 -0
- package/src/dashboard/codemap.ts +44 -0
- package/src/dashboard/files.ts +121 -0
- package/src/dashboard/page.ts +2803 -0
- package/src/dashboard/self-improvement-metrics.ts +282 -0
- package/src/dashboard/server.ts +1103 -0
- package/src/dashboard/session-launch.ts +310 -0
- package/src/dashboard/timeline.ts +273 -0
- package/src/dashboard/tool-exec.ts +107 -0
- package/src/dashboard/trend-cli.ts +141 -0
- package/src/dashboard/trend.ts +413 -0
- package/src/decision-proxy/cli.ts +261 -0
- package/src/decision-proxy/proxy.ts +569 -0
- package/src/deploy/gate-cli.ts +147 -0
- package/src/deploy/gate.ts +254 -0
- package/src/engine/condense.ts +512 -0
- package/src/engine/events.ts +428 -0
- package/src/engine/handoff.ts +71 -0
- package/src/engine/lazy-tools.ts +160 -0
- package/src/engine/local-explore.ts +653 -0
- package/src/engine/logger.ts +96 -0
- package/src/engine/loop.ts +5517 -0
- package/src/engine/parser.ts +347 -0
- package/src/engine/prompt.ts +860 -0
- package/src/engine/reports.ts +47 -0
- package/src/engine/stacks.ts +448 -0
- package/src/engine/types.ts +291 -0
- package/src/engine/usage.ts +186 -0
- package/src/github/app-auth.ts +161 -0
- package/src/github/cli.ts +448 -0
- package/src/github/installations.ts +133 -0
- package/src/github/pr.ts +321 -0
- package/src/github/provision.ts +118 -0
- package/src/github/push.ts +122 -0
- package/src/index-util.ts +50 -0
- package/src/index.ts +81 -0
- package/src/init/cli.ts +248 -0
- package/src/init/gitignore.ts +74 -0
- package/src/llm/ollama.ts +308 -0
- package/src/llm/openrouter.ts +868 -0
- package/src/llm/preflight.ts +367 -0
- package/src/llm/transcript-capture.ts +84 -0
- package/src/memory/embed.ts +110 -0
- package/src/memory/index.ts +22 -0
- package/src/memory/local.ts +259 -0
- package/src/memory/summarizer.ts +283 -0
- package/src/memory/types.ts +153 -0
- package/src/memory/uwuchat.ts +157 -0
- package/src/migrate/cli.ts +115 -0
- package/src/orchestrator/analyze-cli.ts +104 -0
- package/src/orchestrator/auto-split.ts +206 -0
- package/src/orchestrator/cleanup.ts +1003 -0
- package/src/orchestrator/cli.ts +3571 -0
- package/src/orchestrator/cost-estimate.ts +564 -0
- package/src/orchestrator/cost-history-cli.ts +242 -0
- package/src/orchestrator/cost-history.ts +397 -0
- package/src/orchestrator/git-sync.ts +250 -0
- package/src/orchestrator/index.ts +153 -0
- package/src/orchestrator/log-analysis.ts +0 -0
- package/src/orchestrator/merge-check.ts +108 -0
- package/src/orchestrator/pipeline.ts +411 -0
- package/src/orchestrator/resume.ts +1940 -0
- package/src/orchestrator/reviewer.ts +503 -0
- package/src/orchestrator/split.ts +296 -0
- package/src/orchestrator/state.ts +542 -0
- package/src/orchestrator/status.ts +697 -0
- package/src/orchestrator/verification-gate.ts +134 -0
- package/src/orchestrator/watch.ts +898 -0
- package/src/permissions/commands.ts +1083 -0
- package/src/permissions/config.ts +241 -0
- package/src/permissions/index.ts +12 -0
- package/src/permissions/protected-files.ts +96 -0
- package/src/permissions/store-protection.ts +272 -0
- package/src/project-store.ts +648 -0
- package/src/projects/cli.ts +382 -0
- package/src/qa/qa.ts +487 -0
- package/src/tools/browser/handler.ts +346 -0
- package/src/tools/browser/service.ts +406 -0
- package/src/tools/browser/smoke.ts +78 -0
- package/src/tools/browser/tool.ts +99 -0
- package/src/tools/executor.ts +2575 -0
- package/src/tools/language-detect.ts +183 -0
- package/src/tools/output-summarizer.ts +369 -0
- package/src/tools/run-tests.ts +302 -0
- package/src/tools/set-indentation-tool.ts +49 -0
- package/src/tools/test-selection.ts +160 -0
- package/src/vendor/tests/smoke.ts +103 -0
- package/src/vendor/zoo-code/VENDOR-NOTES.md +213 -0
- package/src/vendor/zoo-code/shim/anthropic.ts +71 -0
- package/src/vendor/zoo-code/shim/openai.d.ts +60 -0
- package/src/vendor/zoo-code/shim/os-name.ts +18 -0
- package/src/vendor/zoo-code/shim/strip-bom.ts +14 -0
- package/src/vendor/zoo-code/shim/vscode.ts +76 -0
- package/src/vendor/zoo-code/src/core/config/CustomModesManager.ts +1015 -0
- package/src/vendor/zoo-code/src/core/diff/strategies/multi-search-replace.ts +670 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/capabilities.ts +46 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/custom-instructions.ts +559 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/index.ts +10 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/markdown-formatting.ts +7 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/modes.ts +35 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/objective.ts +13 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/rules.ts +95 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/skills.ts +105 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/system-info.ts +30 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/tool-use-guidelines.ts +9 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/tool-use.ts +7 -0
- package/src/vendor/zoo-code/src/core/prompts/system.ts +176 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/access_mcp_resource.ts +41 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_diff.ts +40 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_patch.ts +61 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/ask_followup_question.ts +62 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/attempt_completion.ts +33 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts +43 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/converters.ts +109 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit.ts +48 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit_file.ts +72 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/execute_command.ts +54 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/generate_image.ts +51 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/index.ts +75 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/list_files.ts +41 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/mcp_server.ts +75 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/new_task.ts +39 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_command_output.ts +81 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_file.ts +169 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/run_slash_command.ts +31 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_files.ts +50 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_replace.ts +51 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/skill.ts +33 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/switch_mode.ts +31 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/update_todo_list.ts +54 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/write_to_file.ts +40 -0
- package/src/vendor/zoo-code/src/core/prompts/types.ts +12 -0
- package/src/vendor/zoo-code/src/i18n/index.ts +19 -0
- package/src/vendor/zoo-code/src/integrations/misc/extract-text.ts +81 -0
- package/src/vendor/zoo-code/src/services/checkpoints/RepoPerTaskCheckpointService.ts +15 -0
- package/src/vendor/zoo-code/src/services/checkpoints/ShadowCheckpointService.ts +553 -0
- package/src/vendor/zoo-code/src/services/checkpoints/excludes.ts +212 -0
- package/src/vendor/zoo-code/src/services/checkpoints/index.ts +3 -0
- package/src/vendor/zoo-code/src/services/checkpoints/types.ts +35 -0
- package/src/vendor/zoo-code/src/services/code-index/manager.ts +19 -0
- package/src/vendor/zoo-code/src/services/mcp/McpHub.ts +36 -0
- package/src/vendor/zoo-code/src/services/roo-config/index.ts +441 -0
- package/src/vendor/zoo-code/src/services/search/file-search.ts +143 -0
- package/src/vendor/zoo-code/src/services/skills/SkillsManager.ts +20 -0
- package/src/vendor/zoo-code/src/shared/globalFileNames.ts +9 -0
- package/src/vendor/zoo-code/src/shared/language.ts +43 -0
- package/src/vendor/zoo-code/src/shared/modes.ts +257 -0
- package/src/vendor/zoo-code/src/shared/tools.ts +385 -0
- package/src/vendor/zoo-code/src/utils/fs.ts +39 -0
- package/src/vendor/zoo-code/src/utils/globalContext.ts +22 -0
- package/src/vendor/zoo-code/src/utils/json-schema.ts +16 -0
- package/src/vendor/zoo-code/src/utils/logging.ts +21 -0
- package/src/vendor/zoo-code/src/utils/mcp-name.ts +190 -0
- package/src/vendor/zoo-code/src/utils/object.ts +18 -0
- package/src/vendor/zoo-code/src/utils/path.ts +94 -0
- package/src/vendor/zoo-code/src/utils/shell.ts +376 -0
- package/src/vendor/zoo-code/src/utils/text-normalization.ts +99 -0
- package/src/vendor/zoo-code/types/global-settings.ts +19 -0
- package/src/vendor/zoo-code/types/index.ts +22 -0
- package/src/vendor/zoo-code/types/message.ts +375 -0
- package/src/vendor/zoo-code/types/mode.ts +241 -0
- package/src/vendor/zoo-code/types/todo.ts +19 -0
- package/src/vendor/zoo-code/types/tool-params.ts +116 -0
- package/src/vendor/zoo-code/types/tool.ts +67 -0
- package/src/vendor/zoo-code/types/vscode.ts +84 -0
- package/src/vision/describe.ts +242 -0
- package/src/vision/tool.ts +91 -0
- package/src/watcher/cli.ts +369 -0
- package/src/watcher/github.ts +304 -0
- package/src/watcher/index.ts +59 -0
- package/src/watcher/state.ts +254 -0
- package/src/watcher/watch.ts +562 -0
- 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
|
+
}
|