opencode-codeops 1.4.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/CHANGELOG.md +179 -0
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/_shared/auto-design.md +129 -0
- package/_shared/layout-convention.md +198 -0
- package/_shared/quality-profile.md +134 -0
- package/_shared/recommendation-hardening.md +166 -0
- package/_shared/scope-expansion-control.md +176 -0
- package/_shared/spec-first-ordering.md +79 -0
- package/_shared/zero-ambiguity-gate.md +311 -0
- package/agent-templates/codebase-scout.md +17 -0
- package/agent-templates/concurrency-auditor.md +5 -0
- package/agent-templates/design-challenger.md +26 -0
- package/agent-templates/financial-integrity-auditor.md +5 -0
- package/agent-templates/perf-auditor.md +23 -0
- package/agent-templates/phase-reviewer.md +54 -0
- package/agent-templates/plan-task-executor-opus.md +46 -0
- package/agent-templates/plan-task-executor.md +43 -0
- package/agent-templates/preflight-auditor.md +45 -0
- package/agent-templates/security-auditor.md +42 -0
- package/agent-templates/semantics-reviewer.md +5 -0
- package/agent-templates/spec-test-author.md +29 -0
- package/agents/concurrency-auditor.md +15 -0
- package/agents/correctness-reviewer.md +66 -0
- package/agents/demanding-executor.md +58 -0
- package/agents/design-challenger.md +38 -0
- package/agents/executor.md +55 -0
- package/agents/explorer.md +29 -0
- package/agents/financial-integrity-auditor.md +15 -0
- package/agents/performance-auditor.md +35 -0
- package/agents/preflight-auditor.md +57 -0
- package/agents/security-auditor.md +54 -0
- package/agents/semantics-reviewer.md +15 -0
- package/agents/spec-test-author.md +41 -0
- package/bin/codeops-worktree +244 -0
- package/bin/index.mjs +106 -0
- package/bin/install-agents.mjs +453 -0
- package/bin/install-skills.mjs +466 -0
- package/bin/lib/opencode-install.mjs +185 -0
- package/install.sh +55 -0
- package/package.json +73 -0
- package/plugin/index.ts +181 -0
- package/references/domains/compiler-and-language.md +28 -0
- package/references/domains/data-and-migration.md +22 -0
- package/references/domains/distributed-and-concurrent.md +26 -0
- package/references/domains/financial-system.md +28 -0
- package/references/domains/selection.md +19 -0
- package/references/domains/web-application.md +23 -0
- package/schemas/codeops-config.schema.json +56 -0
- package/scripts/check-version.mjs +163 -0
- package/scripts/codeops-migrate.sh +355 -0
- package/scripts/codeops-roadmap-compact.sh +232 -0
- package/scripts/codeops-roadmap-sync.sh +275 -0
- package/scripts/codeops_outcomes.py +155 -0
- package/scripts/codeops_plan.py +239 -0
- package/scripts/codeops_plan_migrate.py +318 -0
- package/scripts/codeops_worktree_snapshot.py +99 -0
- package/scripts/install_agents.py +288 -0
- package/scripts/release.mjs +533 -0
- package/skills/analyze-project/SKILL.md +28 -0
- package/skills/clean-comments/SKILL.md +22 -0
- package/skills/exec-plan/SKILL.md +267 -0
- package/skills/exec-plan/commit-modes.md +113 -0
- package/skills/exec-plan/execution-protocol.md +471 -0
- package/skills/git-commit/SKILL.md +35 -0
- package/skills/github-issues/SKILL.md +38 -0
- package/skills/grill-me/SKILL.md +342 -0
- package/skills/make-plan/SKILL.md +282 -0
- package/skills/make-plan/quality-checklist.md +96 -0
- package/skills/make-plan/templates.md +535 -0
- package/skills/make-plan/zero-ambiguity-gate.md +19 -0
- package/skills/make-requirements/SKILL.md +268 -0
- package/skills/make-requirements/discovery-phases.md +255 -0
- package/skills/make-requirements/review-and-add.md +73 -0
- package/skills/make-requirements/templates.md +296 -0
- package/skills/make-requirements/zero-ambiguity-gate.md +18 -0
- package/skills/outcome-review/SKILL.md +34 -0
- package/skills/preflight/SKILL.md +310 -0
- package/skills/preflight/dimensions.md +181 -0
- package/skills/preflight/report-format.md +300 -0
- package/skills/retro-requirements/SKILL.md +218 -0
- package/skills/retro-requirements/confidence-classification.md +45 -0
- package/skills/retro-requirements/phases.md +609 -0
- package/skills/retro-requirements/triage-gate.md +135 -0
- package/skills/roadmap/SKILL.md +381 -0
- package/skills/roadmap/stage-hooks.md +80 -0
- package/skills/roadmap/template.md +200 -0
- package/skills/setup-codeops/SKILL.md +94 -0
- package/skills/setup-codeops/migration.md +106 -0
- package/skills/setup-codeops/scaffold.md +99 -0
- package/skills/setup-routing/SKILL.md +102 -0
- package/skills/setup-routing/routing.md +44 -0
- package/skills/techdocs/SKILL.md +199 -0
- package/skills/techdocs/authoring-and-update.md +178 -0
- package/skills/techdocs/templates.md +655 -0
- package/skills/techdocs/vitepress-setup.md +143 -0
- package/skills/upgrade-plan/SKILL.md +75 -0
- package/skills/upgrade-plan/content-quality-gate.md +35 -0
- package/skills/upgrade-plan/upgrade-checklists.md +107 -0
- package/standards/coding-standards-full.md +124 -0
- package/standards/coding-standards.md +64 -0
- package/standards/output-style.md +17 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Release tool for the single `opencode-codeops` package.
|
|
4
|
+
*
|
|
5
|
+
* CodeOps ships one package, so "the CodeOps version" and "the published
|
|
6
|
+
* package version" are the same value. This script derives the next version
|
|
7
|
+
* from the conventional commit messages written since the last git tag, writes
|
|
8
|
+
* it to `package.json` and `package-lock.json` in one step, records it in
|
|
9
|
+
* `CHANGELOG.md`, commits and tags it, and publishes it. Because the version is
|
|
10
|
+
* written once and then published, the two can never drift.
|
|
11
|
+
*
|
|
12
|
+
* Schema versions that are deliberately independent of the product version —
|
|
13
|
+
* the `codeops/` layout version, the artifact schema stamp, and the auto-design
|
|
14
|
+
* policy version — are not touched by this script.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* node scripts/release.mjs version [--type auto|patch|minor|major] [--dry-run] [--ci] [--no-git-commit] [--git-push]
|
|
18
|
+
* node scripts/release.mjs publish --tag latest|next|beta [--access public] [--dry-run] [--git-push]
|
|
19
|
+
* node scripts/release.mjs release [--type auto|patch|minor|major] --tag latest|next|beta [--dry-run] [--git-push]
|
|
20
|
+
*
|
|
21
|
+
* @module release
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { execFileSync } from "node:child_process"
|
|
25
|
+
import { readFileSync, realpathSync, writeFileSync } from "node:fs"
|
|
26
|
+
import { dirname, join } from "node:path"
|
|
27
|
+
import { fileURLToPath } from "node:url"
|
|
28
|
+
|
|
29
|
+
/** Repository root: this file lives in `<root>/scripts/`. */
|
|
30
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
|
|
31
|
+
|
|
32
|
+
/** Changelog group label for each conventional commit type. */
|
|
33
|
+
const TYPE_LABELS = {
|
|
34
|
+
feat: "Features",
|
|
35
|
+
fix: "Fixes",
|
|
36
|
+
perf: "Performance",
|
|
37
|
+
refactor: "Refactors",
|
|
38
|
+
docs: "Documentation",
|
|
39
|
+
test: "Tests",
|
|
40
|
+
build: "Build",
|
|
41
|
+
ci: "CI",
|
|
42
|
+
chore: "Chores",
|
|
43
|
+
style: "Styles",
|
|
44
|
+
revert: "Reverts",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Bumps a plain semantic version by one position.
|
|
49
|
+
*
|
|
50
|
+
* @param version - Current version, for example `"1.0.0"`
|
|
51
|
+
* @param type - Which position to bump: `"major"`, `"minor"`, or `"patch"`
|
|
52
|
+
* @returns The next version
|
|
53
|
+
* @throws When `version` is not a plain `x.y.z` or `type` is unknown
|
|
54
|
+
* @example
|
|
55
|
+
* semverBump("1.0.0", "minor") // "1.1.0"
|
|
56
|
+
*/
|
|
57
|
+
export function semverBump(version, type) {
|
|
58
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/)
|
|
59
|
+
if (!match) throw new Error(`Not a plain semver version: ${version}`)
|
|
60
|
+
|
|
61
|
+
let major = Number(match[1])
|
|
62
|
+
let minor = Number(match[2])
|
|
63
|
+
let patch = Number(match[3])
|
|
64
|
+
|
|
65
|
+
if (type === "major") {
|
|
66
|
+
major += 1
|
|
67
|
+
minor = 0
|
|
68
|
+
patch = 0
|
|
69
|
+
} else if (type === "minor") {
|
|
70
|
+
minor += 1
|
|
71
|
+
patch = 0
|
|
72
|
+
} else if (type === "patch") {
|
|
73
|
+
patch += 1
|
|
74
|
+
} else {
|
|
75
|
+
throw new Error(`Unknown bump type: ${type}`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return `${major}.${minor}.${patch}`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Tests whether a string is a plain semantic version.
|
|
83
|
+
*
|
|
84
|
+
* @param value - Candidate string
|
|
85
|
+
* @returns True for `x.y.z`
|
|
86
|
+
*/
|
|
87
|
+
export function isValidVersion(value) {
|
|
88
|
+
return /^\d+\.\d+\.\d+$/.test(value)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Parses one commit into the fields the bump rules need.
|
|
93
|
+
*
|
|
94
|
+
* Release commits (`chore(release): ...`) and merge commits are ignored: they
|
|
95
|
+
* are bookkeeping, not user-facing changes. A commit is breaking when the
|
|
96
|
+
* subject carries `!` before the colon or the body contains `BREAKING CHANGE`.
|
|
97
|
+
*
|
|
98
|
+
* @param subject - Commit subject line
|
|
99
|
+
* @param body - Commit body (used only for the breaking-change marker)
|
|
100
|
+
* @returns A parsed commit, or `null` when the commit should be ignored
|
|
101
|
+
*/
|
|
102
|
+
export function parseCommit(subject, body = "") {
|
|
103
|
+
const trimmed = subject.trim()
|
|
104
|
+
if (trimmed.startsWith("Merge ") || trimmed.startsWith("chore(release):")) return null
|
|
105
|
+
|
|
106
|
+
const match = trimmed.match(/^([a-zA-Z]+)(?:\(([^)]*)\))?(!)?:\s*(.*)$/)
|
|
107
|
+
const breaking = Boolean(match && match[3]) || /BREAKING CHANGE/.test(body)
|
|
108
|
+
|
|
109
|
+
if (!match) return { type: "other", scope: null, breaking, text: trimmed }
|
|
110
|
+
|
|
111
|
+
const scope = match[2] || null
|
|
112
|
+
const text = scope ? `${scope}: ${match[4]}` : match[4]
|
|
113
|
+
return { type: match[1].toLowerCase(), scope, breaking, text }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Determines the semantic version bump implied by a set of commits.
|
|
118
|
+
*
|
|
119
|
+
* Rules: a breaking change bumps major, a `feat` bumps minor, and every other
|
|
120
|
+
* conventional type (or an unconventional message) bumps patch.
|
|
121
|
+
*
|
|
122
|
+
* @param commits - Commit records with `subject` and optional `body`
|
|
123
|
+
* @returns `"major"`, `"minor"`, or `"patch"`
|
|
124
|
+
*/
|
|
125
|
+
export function determineBump(commits) {
|
|
126
|
+
const parsed = commits
|
|
127
|
+
.map((commit) => parseCommit(commit.subject, commit.body ?? ""))
|
|
128
|
+
.filter(Boolean)
|
|
129
|
+
|
|
130
|
+
if (parsed.some((commit) => commit.breaking)) return "major"
|
|
131
|
+
if (parsed.some((commit) => commit.type === "feat")) return "minor"
|
|
132
|
+
return "patch"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Builds the Markdown section for one release.
|
|
137
|
+
*
|
|
138
|
+
* @param version - Version being released
|
|
139
|
+
* @param date - Release date as `YYYY-MM-DD`
|
|
140
|
+
* @param commits - Commit records with `subject` and optional `body`
|
|
141
|
+
* @returns A Markdown section ending in a newline
|
|
142
|
+
*/
|
|
143
|
+
export function buildChangelogEntry(version, date, commits) {
|
|
144
|
+
const parsed = commits
|
|
145
|
+
.map((commit) => parseCommit(commit.subject, commit.body ?? ""))
|
|
146
|
+
.filter(Boolean)
|
|
147
|
+
|
|
148
|
+
const lines = [`## ${version} — ${date}`, ""]
|
|
149
|
+
const breaking = parsed.filter((commit) => commit.breaking)
|
|
150
|
+
|
|
151
|
+
if (breaking.length > 0) {
|
|
152
|
+
lines.push("### Breaking Changes", "")
|
|
153
|
+
for (const commit of breaking) lines.push(`- ${commit.text}`)
|
|
154
|
+
lines.push("")
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const grouped = new Map()
|
|
158
|
+
for (const commit of parsed) {
|
|
159
|
+
if (commit.breaking) continue
|
|
160
|
+
const label = TYPE_LABELS[commit.type] ?? "Other"
|
|
161
|
+
if (!grouped.has(label)) grouped.set(label, [])
|
|
162
|
+
grouped.get(label).push(commit.text)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const [label, items] of grouped) {
|
|
166
|
+
lines.push(`### ${label}`, "")
|
|
167
|
+
for (const item of items) lines.push(`- ${item}`)
|
|
168
|
+
lines.push("")
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (parsed.length === 0) lines.push("- No user-facing changes recorded.", "")
|
|
172
|
+
|
|
173
|
+
return `${lines.join("\n")}\n`
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Git and npm operations
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Runs a git command and returns its trimmed stdout.
|
|
182
|
+
*
|
|
183
|
+
* @param args - Git arguments (without the leading `git`)
|
|
184
|
+
* @returns Trimmed command output
|
|
185
|
+
*/
|
|
186
|
+
function git(args) {
|
|
187
|
+
return execFileSync("git", args, { cwd: ROOT, encoding: "utf-8" }).trim()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Runs a command with its output streamed to the terminal.
|
|
192
|
+
*
|
|
193
|
+
* @param command - Executable name
|
|
194
|
+
* @param args - Argument list
|
|
195
|
+
*/
|
|
196
|
+
function run(command, args) {
|
|
197
|
+
execFileSync(command, args, { cwd: ROOT, stdio: "inherit" })
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Returns the most recent tag, or `null` when the repository has none.
|
|
202
|
+
*
|
|
203
|
+
* @returns Tag name or null
|
|
204
|
+
*/
|
|
205
|
+
function lastTag() {
|
|
206
|
+
try {
|
|
207
|
+
return git(["describe", "--tags", "--abbrev=0"]) || null
|
|
208
|
+
} catch {
|
|
209
|
+
return null
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Reads the commits written since the last tag.
|
|
215
|
+
*
|
|
216
|
+
* @returns Commit records with `subject` and `body`
|
|
217
|
+
*/
|
|
218
|
+
function readCommits() {
|
|
219
|
+
const tag = lastTag()
|
|
220
|
+
const range = tag ? `${tag}..HEAD` : "HEAD"
|
|
221
|
+
const raw = git(["log", range, "--pretty=format:%s%x1f%b%x1e"])
|
|
222
|
+
if (!raw) return []
|
|
223
|
+
|
|
224
|
+
return raw
|
|
225
|
+
.split("\x1e")
|
|
226
|
+
.filter((record) => record.trim() !== "")
|
|
227
|
+
.map((record) => {
|
|
228
|
+
const [subject = "", body = ""] = record.split("\x1f")
|
|
229
|
+
return { subject, body }
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Reports whether anything changed since the last tag.
|
|
235
|
+
*
|
|
236
|
+
* @returns True when at least one file changed
|
|
237
|
+
*/
|
|
238
|
+
function changedSinceLastTag() {
|
|
239
|
+
const tag = lastTag()
|
|
240
|
+
if (!tag) return true
|
|
241
|
+
return git(["diff", "--name-only", `${tag}..HEAD`]).split("\n").some(Boolean)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Reads the current package version.
|
|
246
|
+
*
|
|
247
|
+
* @returns The `version` field of `package.json`
|
|
248
|
+
*/
|
|
249
|
+
function currentVersion() {
|
|
250
|
+
return JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8")).version
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Refuses to release with uncommitted changes, so the tag points at exactly the
|
|
255
|
+
* reviewed commit.
|
|
256
|
+
*/
|
|
257
|
+
function assertCleanTree() {
|
|
258
|
+
const status = git(["status", "--porcelain"])
|
|
259
|
+
if (status !== "") {
|
|
260
|
+
throw new Error("working tree is not clean; commit or stash changes before releasing")
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Refuses to release a tag that already exists.
|
|
266
|
+
*
|
|
267
|
+
* @param version - Version being tagged
|
|
268
|
+
*/
|
|
269
|
+
function assertTagAbsent(version) {
|
|
270
|
+
const existing = git(["tag", "--list", `v${version}`])
|
|
271
|
+
if (existing !== "") throw new Error(`tag v${version} already exists`)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Writes the version to `package.json` and `package-lock.json`.
|
|
276
|
+
*
|
|
277
|
+
* `npm version` updates both files together, which is what keeps the package
|
|
278
|
+
* metadata and its lockfile from drifting.
|
|
279
|
+
*
|
|
280
|
+
* @param version - Version to write
|
|
281
|
+
*/
|
|
282
|
+
function applyVersion(version) {
|
|
283
|
+
run("npm", ["version", version, "--no-git-tag-version", "--ignore-scripts"])
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Inserts a release section into an existing changelog.
|
|
288
|
+
*
|
|
289
|
+
* When the changelog has an `## Unreleased` section, it is replaced by the
|
|
290
|
+
* generated entry, so the section appears once under its real version number
|
|
291
|
+
* instead of lingering as an unreleased duplicate. Otherwise the entry is
|
|
292
|
+
* inserted above the newest existing release.
|
|
293
|
+
*
|
|
294
|
+
* @param existing - Current `CHANGELOG.md` contents
|
|
295
|
+
* @param entry - Generated Markdown section
|
|
296
|
+
* @returns Updated changelog contents
|
|
297
|
+
*/
|
|
298
|
+
export function mergeChangelog(existing, entry) {
|
|
299
|
+
const unreleased = /^## Unreleased[^\n]*\n/m.exec(existing)
|
|
300
|
+
|
|
301
|
+
if (unreleased) {
|
|
302
|
+
const start = unreleased.index
|
|
303
|
+
const afterHeading = start + unreleased[0].length
|
|
304
|
+
const nextSection = existing.indexOf("\n## ", afterHeading)
|
|
305
|
+
const end = nextSection === -1 ? existing.length : nextSection + 1
|
|
306
|
+
return existing.slice(0, start) + entry + existing.slice(end)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const firstSection = existing.search(/^## /m)
|
|
310
|
+
return firstSection === -1
|
|
311
|
+
? `${existing.replace(/\s*$/, "")}\n\n${entry}`
|
|
312
|
+
: `${existing.slice(0, firstSection)}${entry}${existing.slice(firstSection)}`
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Inserts the new release section at the top of `CHANGELOG.md`.
|
|
317
|
+
*
|
|
318
|
+
* @param version - Version being released
|
|
319
|
+
* @param commits - Commit records written since the last tag
|
|
320
|
+
*/
|
|
321
|
+
function updateChangelog(version, commits) {
|
|
322
|
+
const path = join(ROOT, "CHANGELOG.md")
|
|
323
|
+
const existing = readFileSync(path, "utf-8")
|
|
324
|
+
const date = new Date().toISOString().slice(0, 10)
|
|
325
|
+
const entry = buildChangelogEntry(version, date, commits)
|
|
326
|
+
|
|
327
|
+
writeFileSync(path, mergeChangelog(existing, entry), "utf-8")
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Commits the version bump and creates the release tag.
|
|
332
|
+
*
|
|
333
|
+
* @param version - Version being released
|
|
334
|
+
* @param options - Commit options
|
|
335
|
+
* @param options.ci - Append `[skip ci]` to avoid a CI loop
|
|
336
|
+
* @param options.noGitCommit - Skip committing and tagging
|
|
337
|
+
*/
|
|
338
|
+
function commitAndTag(version, { ci, noGitCommit }) {
|
|
339
|
+
if (noGitCommit) return
|
|
340
|
+
|
|
341
|
+
run("git", ["add", "package.json", "package-lock.json", "CHANGELOG.md"])
|
|
342
|
+
run("git", ["commit", "-m", `chore(release): v${version}${ci ? " [skip ci]" : ""}`])
|
|
343
|
+
run("git", ["tag", `v${version}`])
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Publishes the current package version to npm.
|
|
348
|
+
*
|
|
349
|
+
* @param details - Publish inputs
|
|
350
|
+
* @param details.tag - npm dist-tag (`latest`, `next`, `beta`, ...)
|
|
351
|
+
* @param details.access - npm access level
|
|
352
|
+
* @param details.dryRun - Run `npm publish --dry-run` and publish nothing
|
|
353
|
+
*/
|
|
354
|
+
export function publish({ tag, access = "public", dryRun = false }) {
|
|
355
|
+
const args = ["publish", "--access", access, "--tag", tag]
|
|
356
|
+
if (dryRun) args.push("--dry-run")
|
|
357
|
+
else if (process.env.CI) args.push("--provenance")
|
|
358
|
+
run("npm", args)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Pushes the release commit and its tags. */
|
|
362
|
+
function gitPush() {
|
|
363
|
+
run("git", ["push", "--follow-tags"])
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Prints command usage. */
|
|
367
|
+
function printUsage() {
|
|
368
|
+
console.log(`Release the single opencode-codeops package.
|
|
369
|
+
|
|
370
|
+
Usage:
|
|
371
|
+
node scripts/release.mjs version [options] Bump the version, changelog, commit, tag
|
|
372
|
+
node scripts/release.mjs publish [options] Publish the current version
|
|
373
|
+
node scripts/release.mjs release [options] version + publish
|
|
374
|
+
|
|
375
|
+
Options:
|
|
376
|
+
--type auto|patch|minor|major Version bump (default: auto, from commit messages)
|
|
377
|
+
--tag latest|next|beta npm dist-tag (required for publish/release)
|
|
378
|
+
--access public|restricted npm access level (default: public)
|
|
379
|
+
--dry-run Show what would happen without writing or publishing
|
|
380
|
+
--ci Add [skip ci] to the release commit
|
|
381
|
+
--no-git-commit Skip the commit and tag (version command only)
|
|
382
|
+
--git-push Push the release commit and tags
|
|
383
|
+
-h, --help Show this help`)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Parses the command line.
|
|
388
|
+
*
|
|
389
|
+
* @param argv - Arguments after the executable
|
|
390
|
+
* @returns The command, parsed options, and an error message when invalid
|
|
391
|
+
*/
|
|
392
|
+
export function parseCli(argv) {
|
|
393
|
+
const options = {
|
|
394
|
+
type: "auto",
|
|
395
|
+
tag: null,
|
|
396
|
+
access: "public",
|
|
397
|
+
dryRun: false,
|
|
398
|
+
ci: false,
|
|
399
|
+
noGitCommit: false,
|
|
400
|
+
gitPush: false,
|
|
401
|
+
}
|
|
402
|
+
let command = "help"
|
|
403
|
+
let index = 0
|
|
404
|
+
|
|
405
|
+
const first = argv[0]
|
|
406
|
+
if (first && !first.startsWith("-")) {
|
|
407
|
+
if (["version", "publish", "release", "help"].includes(first)) {
|
|
408
|
+
command = first
|
|
409
|
+
index = 1
|
|
410
|
+
} else {
|
|
411
|
+
return { command, options, error: `unknown command '${first}'` }
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
for (; index < argv.length; index += 1) {
|
|
416
|
+
const arg = argv[index]
|
|
417
|
+
|
|
418
|
+
if (arg === "--dry-run") options.dryRun = true
|
|
419
|
+
else if (arg === "--ci") options.ci = true
|
|
420
|
+
else if (arg === "--no-git-commit") options.noGitCommit = true
|
|
421
|
+
else if (arg === "--git-push") options.gitPush = true
|
|
422
|
+
else if (arg === "-h" || arg === "--help") command = "help"
|
|
423
|
+
else if (arg === "--type" || arg === "--tag" || arg === "--access") {
|
|
424
|
+
const value = argv[index + 1]
|
|
425
|
+
if (!value || value.startsWith("--")) {
|
|
426
|
+
return { command, options, error: `${arg} requires a value` }
|
|
427
|
+
}
|
|
428
|
+
if (arg === "--type") options.type = value
|
|
429
|
+
else if (arg === "--tag") options.tag = value
|
|
430
|
+
else options.access = value
|
|
431
|
+
index += 1
|
|
432
|
+
} else {
|
|
433
|
+
return { command, options, error: `unknown argument '${arg}'` }
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (!["auto", "patch", "minor", "major"].includes(options.type)) {
|
|
438
|
+
return { command, options, error: `--type must be auto|patch|minor|major` }
|
|
439
|
+
}
|
|
440
|
+
if (options.tag && !/^[A-Za-z0-9._-]+$/.test(options.tag)) {
|
|
441
|
+
return { command, options, error: `invalid --tag value '${options.tag}'` }
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return { command, options }
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Runs the release CLI.
|
|
449
|
+
*
|
|
450
|
+
* @param argv - Arguments after the executable
|
|
451
|
+
* @returns Process exit code
|
|
452
|
+
*/
|
|
453
|
+
export function main(argv) {
|
|
454
|
+
const { command, options, error } = parseCli(argv)
|
|
455
|
+
if (error) {
|
|
456
|
+
console.error(`error: ${error}`)
|
|
457
|
+
return 2
|
|
458
|
+
}
|
|
459
|
+
if (command === "help") {
|
|
460
|
+
printUsage()
|
|
461
|
+
return 0
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
if (command === "version" || command === "release") {
|
|
466
|
+
if (!options.dryRun) assertCleanTree()
|
|
467
|
+
if (!changedSinceLastTag()) {
|
|
468
|
+
console.error("error: no changes since the last tag; nothing to release")
|
|
469
|
+
return 1
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const commits = readCommits()
|
|
473
|
+
const type = options.type === "auto" ? determineBump(commits) : options.type
|
|
474
|
+
const current = currentVersion()
|
|
475
|
+
const next = semverBump(current, type)
|
|
476
|
+
assertTagAbsent(next)
|
|
477
|
+
|
|
478
|
+
console.log(`Version: ${current} -> ${next} (${type})`)
|
|
479
|
+
if (options.dryRun) {
|
|
480
|
+
console.log(`[dry-run] would set ${next}, update CHANGELOG.md, commit and tag v${next}`)
|
|
481
|
+
} else {
|
|
482
|
+
applyVersion(next)
|
|
483
|
+
updateChangelog(next, commits)
|
|
484
|
+
commitAndTag(next, options)
|
|
485
|
+
console.log(`Tagged v${next}`)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (command === "version") {
|
|
489
|
+
if (options.gitPush && !options.dryRun) gitPush()
|
|
490
|
+
return 0
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (!options.tag) {
|
|
494
|
+
console.error("error: release requires --tag <dist-tag>")
|
|
495
|
+
return 2
|
|
496
|
+
}
|
|
497
|
+
publish({ tag: options.tag, access: options.access, dryRun: options.dryRun })
|
|
498
|
+
if (options.gitPush && !options.dryRun) gitPush()
|
|
499
|
+
return 0
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (command === "publish") {
|
|
503
|
+
const tag = options.tag ?? "latest"
|
|
504
|
+
publish({ tag, access: options.access, dryRun: options.dryRun })
|
|
505
|
+
if (options.gitPush && !options.dryRun) gitPush()
|
|
506
|
+
return 0
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
printUsage()
|
|
510
|
+
return 2
|
|
511
|
+
} catch (caught) {
|
|
512
|
+
console.error(`error: ${caught.message}`)
|
|
513
|
+
return 1
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* True when this module is the process entry point.
|
|
519
|
+
*
|
|
520
|
+
* @returns True when this file is the entry point
|
|
521
|
+
*/
|
|
522
|
+
function isMainModule() {
|
|
523
|
+
if (!process.argv[1]) return false
|
|
524
|
+
try {
|
|
525
|
+
return fileURLToPath(import.meta.url) === realpathSync(process.argv[1])
|
|
526
|
+
} catch {
|
|
527
|
+
return false
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (isMainModule()) {
|
|
532
|
+
process.exitCode = main(process.argv.slice(2))
|
|
533
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: analyze-project
|
|
3
|
+
description: Analyze the current repository and create or refresh concise CodeOps-aware AGENTS.md guidance using observed manifests, commands, structure, conventions, verification, and integration-branch facts. Use for analyze project, initialize project guidance, refresh AGENTS.md, or compact project instructions. Preserves hand-authored content and previews consequential rewrites.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Analyze project guidance
|
|
7
|
+
|
|
8
|
+
`/init` can create a basic `AGENTS.md`; this skill adds CodeOps-specific, evidence-grounded guidance without replacing hand-authored policy.
|
|
9
|
+
|
|
10
|
+
## Protocol
|
|
11
|
+
|
|
12
|
+
1. Resolve the Git root, current/default branch, and nested `AGENTS.md` files.
|
|
13
|
+
2. Inspect manifests, build files, CI, test configuration, formatter/linter settings, source layout, package boundaries, and recent commit conventions.
|
|
14
|
+
3. Derive commands only from executable configuration or documented scripts. Never invent a plausible command.
|
|
15
|
+
4. Prepare a concise managed section between:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
<!-- CODEOPS-PROJECT:START -->
|
|
19
|
+
<!-- CODEOPS-PROJECT:END -->
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
5. Include project type, principal languages/frameworks, authoritative build/test/verify commands, high-level structure, generated-file warnings, and CodeOps artifact/config locations.
|
|
23
|
+
6. If the managed section exists, replace only its contents. Preserve all other text byte-for-byte.
|
|
24
|
+
7. On a non-integration branch, preview changes to repository-wide guidance and ask before writing unless repository policy explicitly permits branch-local updates.
|
|
25
|
+
8. In compact mode, remove duplication and stale generated detail from the managed section only. Flag suspected hand-authored bloat; never silently rewrite it.
|
|
26
|
+
9. Validate every recorded command or mark it explicitly unverified.
|
|
27
|
+
|
|
28
|
+
Keep `AGENTS.md` small. Operational routing belongs in `codeops/codeops.json` or `opencode.json`, not prose.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clean-comments
|
|
3
|
+
description: Audit and clean source comments and API documentation without changing behavior. Use for clean comments, clean JSDoc, remove planning references from code, improve API docs, or enforce CodeOps documentation standards. Removes ephemeral requirement/plan identifiers, documents non-trivial public behavior, and supports report-only or references-only modes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Clean source documentation
|
|
7
|
+
|
|
8
|
+
## Safety boundary
|
|
9
|
+
|
|
10
|
+
This is a comment-only workflow. Never alter executable tokens, declarations, types, imports, string literals, generated files, vendored code, snapshots, CodeOps artifacts, or `AGENTS.md`.
|
|
11
|
+
|
|
12
|
+
## Protocol
|
|
13
|
+
|
|
14
|
+
1. Detect languages, documentation conventions, generated directories, and repository guidance.
|
|
15
|
+
2. Scan comments for ephemeral references such as `RD-*`, `AR-*`, `ST-*`, task identifiers, and `codeops/`, `plans/`, or `requirements/` paths.
|
|
16
|
+
3. Identify public/non-trivial entities with missing or misleading documentation, change-history comments, restated code, stale TODOs, and unsupported claims.
|
|
17
|
+
4. In report-only mode, cite findings and proposed scope without editing.
|
|
18
|
+
5. In references-only mode, restate ephemeral rationale in durable domain language without expanding unrelated documentation.
|
|
19
|
+
6. In full mode, document contracts, invariants, units, failure behavior, ownership, examples, and non-obvious rationale where the language/project convention warrants it.
|
|
20
|
+
7. Review the diff to prove only comments changed. Run the repository's normal formatter and verification command.
|
|
21
|
+
|
|
22
|
+
Documentation explains the shipped system, not the temporary process that produced it.
|