opencode-overclock 0.3.0 → 0.5.0
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/README.md +252 -111
- package/package.json +6 -4
- package/skills/codebase-design/DEEPENING.md +35 -0
- package/skills/codebase-design/DESIGN-IT-TWICE.md +34 -0
- package/skills/codebase-design/SKILL.md +93 -0
- package/skills/diagnosing-bugs/SKILL.md +123 -0
- package/skills/domain-modeling/ADR-FORMAT.md +55 -0
- package/skills/domain-modeling/CONTEXT-FORMAT.md +32 -0
- package/skills/domain-modeling/SKILL.md +102 -0
- package/skills/doubt/SKILL.md +80 -0
- package/skills/grilling/SKILL.md +96 -0
- package/skills/source-discipline/SKILL.md +78 -0
- package/skills/tdd/SKILL.md +87 -0
- package/skills/to-spec/SKILL.md +69 -0
- package/skills/to-spec/SPEC-TEMPLATE.md +50 -0
- package/skills/to-tickets/SKILL.md +74 -0
- package/skills/to-tickets/TICKET-TEMPLATE.md +41 -0
- package/src/bridge.ts +1 -0
- package/src/buddy/companion.ts +104 -5
- package/src/buddy/sprites.ts +4 -4
- package/src/buddy/tui.ts +175 -65
- package/src/core/bridge.ts +34 -0
- package/src/core/lifecycle.ts +67 -0
- package/src/core/policy.ts +128 -0
- package/src/core/summary.ts +33 -0
- package/src/core/types.ts +193 -0
- package/src/features/buddy.ts +1 -2
- package/src/features/guard.ts +421 -37
- package/src/features/index.ts +18 -4
- package/src/features/recovery.ts +153 -0
- package/src/features/safety.ts +147 -0
- package/src/features/sched.ts +183 -89
- package/src/features/tasks.ts +134 -33
- package/src/features/truncator.ts +116 -0
- package/src/features/usage.ts +46 -65
- package/src/features/workflow.ts +256 -0
- package/src/index.ts +96 -67
- package/src/lib/busy.ts +1 -25
- package/src/lib/exec.ts +13 -0
- package/src/lib/inject.ts +10 -56
- package/src/lib/mirror.ts +13 -0
- package/src/lib/probe.ts +1 -15
- package/src/lib/state.ts +10 -39
- package/src/lib/tmux.ts +1 -0
- package/src/lib/ui.ts +208 -0
- package/src/merge.ts +2 -66
- package/src/platform/probe.ts +25 -0
- package/src/platform/process/exec.ts +317 -0
- package/src/platform/process/tmux.ts +60 -0
- package/src/platform/session/busy.ts +33 -0
- package/src/platform/session/inject.ts +89 -0
- package/src/platform/session/notify.ts +20 -0
- package/src/platform/storage/state.ts +99 -0
- package/src/platform/storage/store.ts +61 -0
- package/src/summary.ts +1 -0
- package/src/tools.ts +8 -244
- package/src/tui.ts +57 -186
- package/src/types.ts +1 -73
- package/src/v2/context.ts +470 -0
- package/src/v2/host.ts +120 -0
- package/src/v2/loader.ts +150 -0
- package/src/workflow/agents/codebase-researcher.ts +27 -0
- package/src/workflow/agents/design-explorer.ts +33 -0
- package/src/workflow/agents/doubt-reviewer.ts +26 -0
- package/src/workflow/agents/engineering-coach.ts +23 -0
- package/src/workflow/agents/performance-auditor.ts +29 -0
- package/src/workflow/agents/security-auditor.ts +23 -0
- package/src/workflow/agents/spec-reviewer.ts +15 -0
- package/src/workflow/agents/standards-reviewer.ts +24 -0
- package/src/workflow/agents/test-engineer.ts +28 -0
- package/src/workflow/catalog.ts +210 -0
- package/src/workflow/templates/build.ts +47 -0
- package/src/workflow/templates/define.ts +45 -0
- package/src/workflow/templates/diagnose.ts +58 -0
- package/src/workflow/templates/plan.ts +52 -0
- package/src/workflow/templates/ship.ts +64 -0
- package/src/buddy/reactions.ts +0 -41
- package/src/buddy/types.ts +0 -30
- package/src/config.ts +0 -19
- package/src/features/checkpoints.ts +0 -128
- package/src/features/sandbox.ts +0 -104
- package/src/validate.ts +0 -197
package/src/validate.ts
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import type { ConfigIssue, FeatureModule, OptionType } from "./types.ts"
|
|
2
|
-
import type { ToolPolicy } from "./tools.ts"
|
|
3
|
-
|
|
4
|
-
export type { ConfigIssue }
|
|
5
|
-
|
|
6
|
-
/** Levenshtein, capped -- only used to turn a typo into a "did you mean". */
|
|
7
|
-
function distance(a: string, b: string): number {
|
|
8
|
-
const prev = Array.from({ length: b.length + 1 }, (_, i) => i)
|
|
9
|
-
const cur = new Array<number>(b.length + 1)
|
|
10
|
-
for (let i = 1; i <= a.length; i++) {
|
|
11
|
-
cur[0] = i
|
|
12
|
-
for (let j = 1; j <= b.length; j++) {
|
|
13
|
-
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1))
|
|
14
|
-
}
|
|
15
|
-
prev.splice(0, prev.length, ...cur)
|
|
16
|
-
}
|
|
17
|
-
return prev[b.length]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Closest known name within edit distance 2, else undefined. */
|
|
21
|
-
function nearest(input: string, known: readonly string[]): string | undefined {
|
|
22
|
-
let best: string | undefined
|
|
23
|
-
let bestD = 3
|
|
24
|
-
for (const k of known) {
|
|
25
|
-
const d = distance(input.toLowerCase(), k.toLowerCase())
|
|
26
|
-
if (d < bestD) {
|
|
27
|
-
bestD = d
|
|
28
|
-
best = k
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return best
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function unknownKey(input: string, known: readonly string[], what: string): string {
|
|
35
|
-
const guess = nearest(input, known)
|
|
36
|
-
if (guess) return `unknown ${what} "${input}" -- did you mean "${guess}"?`
|
|
37
|
-
return `unknown ${what} "${input}". Known: ${known.join(", ")}`
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function typeOf(v: unknown): OptionType | "null" {
|
|
41
|
-
if (v === null) return "null"
|
|
42
|
-
if (Array.isArray(v)) return "array"
|
|
43
|
-
const t = typeof v
|
|
44
|
-
if (t === "boolean" || t === "number" || t === "string" || t === "object") return t
|
|
45
|
-
return "object"
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
49
|
-
return typeof v === "object" && v !== null && !Array.isArray(v)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Check the `toolNames` remap. A rename that silently does nothing is the worst outcome
|
|
54
|
-
* here: the proxy keeps rejecting the tool and the config looks correct. So an unknown
|
|
55
|
-
* source name is an issue, and two sources aiming at one target is an issue -- the merge
|
|
56
|
-
* would keep only the last.
|
|
57
|
-
*/
|
|
58
|
-
function validateToolNames(toolNames: unknown, features: readonly FeatureModule[]): ConfigIssue[] {
|
|
59
|
-
if (toolNames === undefined) return []
|
|
60
|
-
if (!isPlainObject(toolNames)) {
|
|
61
|
-
return [{ path: "toolNames", message: `"toolNames" must be an object, got ${typeOf(toolNames)}` }]
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const issues: ConfigIssue[] = []
|
|
65
|
-
const declared = features.flatMap((f) => f.tools ?? [])
|
|
66
|
-
const targets = new Map<string, string>()
|
|
67
|
-
|
|
68
|
-
for (const [from, to] of Object.entries(toolNames)) {
|
|
69
|
-
if (!declared.includes(from)) {
|
|
70
|
-
issues.push({ path: `toolNames.${from}`, message: unknownKey(from, declared, "tool") })
|
|
71
|
-
continue
|
|
72
|
-
}
|
|
73
|
-
if (typeof to !== "string" || to.trim() === "") {
|
|
74
|
-
issues.push({
|
|
75
|
-
path: `toolNames.${from}`,
|
|
76
|
-
message: `must be a non-empty string, got ${typeOf(to)}`,
|
|
77
|
-
})
|
|
78
|
-
continue
|
|
79
|
-
}
|
|
80
|
-
const prior = targets.get(to)
|
|
81
|
-
if (prior) {
|
|
82
|
-
issues.push({
|
|
83
|
-
path: `toolNames.${from}`,
|
|
84
|
-
message: `"${to}" is already the target of "${prior}" -- only one would survive the merge`,
|
|
85
|
-
})
|
|
86
|
-
continue
|
|
87
|
-
}
|
|
88
|
-
targets.set(to, from)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return issues
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Check overclock.json against the feature registry.
|
|
96
|
-
*
|
|
97
|
-
* Exists because an unrecognised key is otherwise a silent no-op: `killOnExist: true`
|
|
98
|
-
* reads as "option not set", the feature runs with defaults, and nothing complains.
|
|
99
|
-
* Returns every issue found -- callers warn, never throw. A bad config degrades to
|
|
100
|
-
* defaults rather than taking the plugin down.
|
|
101
|
-
*/
|
|
102
|
-
export function validateConfig(config: unknown, features: readonly FeatureModule[]): ConfigIssue[] {
|
|
103
|
-
const issues: ConfigIssue[] = []
|
|
104
|
-
if (!isPlainObject(config)) {
|
|
105
|
-
return [{ path: "", message: `config must be an object, got ${typeOf(config)}` }]
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const TOP = ["features", "toolNames", "toolAllowlist"]
|
|
109
|
-
for (const key of Object.keys(config)) {
|
|
110
|
-
if (!TOP.includes(key)) issues.push({ path: key, message: unknownKey(key, TOP, "top-level key") })
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
issues.push(...validateToolNames(config.toolNames, features))
|
|
114
|
-
|
|
115
|
-
const { features: featuresCfg } = config
|
|
116
|
-
if (featuresCfg === undefined) return issues
|
|
117
|
-
if (!isPlainObject(featuresCfg)) {
|
|
118
|
-
issues.push({
|
|
119
|
-
path: "features",
|
|
120
|
-
message: `"features" must be an object, got ${typeOf(featuresCfg)}`,
|
|
121
|
-
})
|
|
122
|
-
return issues
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const names = features.map((f) => f.name)
|
|
126
|
-
for (const [name, setting] of Object.entries(featuresCfg)) {
|
|
127
|
-
const feature = features.find((f) => f.name === name)
|
|
128
|
-
if (!feature) {
|
|
129
|
-
issues.push({ path: `features.${name}`, message: unknownKey(name, names, "feature") })
|
|
130
|
-
continue
|
|
131
|
-
}
|
|
132
|
-
if (typeof setting === "boolean") continue
|
|
133
|
-
if (!isPlainObject(setting)) {
|
|
134
|
-
issues.push({
|
|
135
|
-
path: `features.${name}`,
|
|
136
|
-
message: `must be true, false, or an options object -- got ${typeOf(setting)}`,
|
|
137
|
-
})
|
|
138
|
-
continue
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const schema = feature.options ?? {}
|
|
142
|
-
const optionNames = Object.keys(schema)
|
|
143
|
-
for (const [key, value] of Object.entries(setting)) {
|
|
144
|
-
const expected = schema[key]
|
|
145
|
-
if (!expected) {
|
|
146
|
-
issues.push({
|
|
147
|
-
path: `features.${name}.${key}`,
|
|
148
|
-
message: optionNames.length
|
|
149
|
-
? unknownKey(key, optionNames, "option")
|
|
150
|
-
: `"${name}" takes no options, got "${key}"`,
|
|
151
|
-
})
|
|
152
|
-
continue
|
|
153
|
-
}
|
|
154
|
-
const actual = typeOf(value)
|
|
155
|
-
if (actual !== expected) {
|
|
156
|
-
issues.push({
|
|
157
|
-
path: `features.${name}.${key}`,
|
|
158
|
-
message: `expected ${expected}, got ${actual}`,
|
|
159
|
-
})
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return issues
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* One-line inventory of what this plugin just added to the session.
|
|
169
|
-
*
|
|
170
|
-
* Not about context cost -- the full tool surface is only ~800 tokens. It is about
|
|
171
|
-
* capability: installing overclock hands the agent background shell execution and
|
|
172
|
-
* recurring scheduling, and that should not be something a user discovers by accident.
|
|
173
|
-
*/
|
|
174
|
-
export function summarise(
|
|
175
|
-
enabled: readonly FeatureModule[],
|
|
176
|
-
skipped: readonly string[],
|
|
177
|
-
policy: ToolPolicy = { rename: {}, withheld: new Set() },
|
|
178
|
-
): string {
|
|
179
|
-
const { rename, withheld } = policy
|
|
180
|
-
const offered = enabled.flatMap((f) => (f.tools ?? []).filter((t) => !withheld.has(t)))
|
|
181
|
-
// Report the name the model is actually offered, not the declared one -- under a remap the
|
|
182
|
-
// declared name appears nowhere on the wire, so listing it would misdescribe the session.
|
|
183
|
-
const parts = enabled.map((f) => {
|
|
184
|
-
const names = (f.tools ?? []).filter((t) => !withheld.has(t)).map((t) => rename[t] ?? t)
|
|
185
|
-
return `${f.name}${names.length ? ` (${names.join(", ")})` : ""}`
|
|
186
|
-
})
|
|
187
|
-
const plural = (n: number, word: string) => `${n} ${word}${n === 1 ? "" : "s"}`
|
|
188
|
-
let line = `${plural(enabled.length, "module")}, ${plural(offered.length, "tool")}: ${parts.join(" · ")}`
|
|
189
|
-
const applied = offered.filter((t) => rename[t] && rename[t] !== t).map((t) => `${t}->${rename[t]}`)
|
|
190
|
-
if (applied.length) line += ` | renamed: ${applied.join(", ")}`
|
|
191
|
-
// Withheld tools are the one case where the session is quietly less capable than the config
|
|
192
|
-
// implies, so they are named here rather than left to the issue log alone.
|
|
193
|
-
const held = enabled.flatMap((f) => (f.tools ?? []).filter((t) => withheld.has(t)))
|
|
194
|
-
if (held.length) line += ` | withheld: ${held.join(", ")}`
|
|
195
|
-
if (skipped.length) line += ` | skipped: ${skipped.join(", ")}`
|
|
196
|
-
return line
|
|
197
|
-
}
|