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.
Files changed (102) hide show
  1. package/CHANGELOG.md +179 -0
  2. package/LICENSE +21 -0
  3. package/README.md +171 -0
  4. package/_shared/auto-design.md +129 -0
  5. package/_shared/layout-convention.md +198 -0
  6. package/_shared/quality-profile.md +134 -0
  7. package/_shared/recommendation-hardening.md +166 -0
  8. package/_shared/scope-expansion-control.md +176 -0
  9. package/_shared/spec-first-ordering.md +79 -0
  10. package/_shared/zero-ambiguity-gate.md +311 -0
  11. package/agent-templates/codebase-scout.md +17 -0
  12. package/agent-templates/concurrency-auditor.md +5 -0
  13. package/agent-templates/design-challenger.md +26 -0
  14. package/agent-templates/financial-integrity-auditor.md +5 -0
  15. package/agent-templates/perf-auditor.md +23 -0
  16. package/agent-templates/phase-reviewer.md +54 -0
  17. package/agent-templates/plan-task-executor-opus.md +46 -0
  18. package/agent-templates/plan-task-executor.md +43 -0
  19. package/agent-templates/preflight-auditor.md +45 -0
  20. package/agent-templates/security-auditor.md +42 -0
  21. package/agent-templates/semantics-reviewer.md +5 -0
  22. package/agent-templates/spec-test-author.md +29 -0
  23. package/agents/concurrency-auditor.md +15 -0
  24. package/agents/correctness-reviewer.md +66 -0
  25. package/agents/demanding-executor.md +58 -0
  26. package/agents/design-challenger.md +38 -0
  27. package/agents/executor.md +55 -0
  28. package/agents/explorer.md +29 -0
  29. package/agents/financial-integrity-auditor.md +15 -0
  30. package/agents/performance-auditor.md +35 -0
  31. package/agents/preflight-auditor.md +57 -0
  32. package/agents/security-auditor.md +54 -0
  33. package/agents/semantics-reviewer.md +15 -0
  34. package/agents/spec-test-author.md +41 -0
  35. package/bin/codeops-worktree +244 -0
  36. package/bin/index.mjs +106 -0
  37. package/bin/install-agents.mjs +453 -0
  38. package/bin/install-skills.mjs +466 -0
  39. package/bin/lib/opencode-install.mjs +185 -0
  40. package/install.sh +55 -0
  41. package/package.json +73 -0
  42. package/plugin/index.ts +181 -0
  43. package/references/domains/compiler-and-language.md +28 -0
  44. package/references/domains/data-and-migration.md +22 -0
  45. package/references/domains/distributed-and-concurrent.md +26 -0
  46. package/references/domains/financial-system.md +28 -0
  47. package/references/domains/selection.md +19 -0
  48. package/references/domains/web-application.md +23 -0
  49. package/schemas/codeops-config.schema.json +56 -0
  50. package/scripts/check-version.mjs +163 -0
  51. package/scripts/codeops-migrate.sh +355 -0
  52. package/scripts/codeops-roadmap-compact.sh +232 -0
  53. package/scripts/codeops-roadmap-sync.sh +275 -0
  54. package/scripts/codeops_outcomes.py +155 -0
  55. package/scripts/codeops_plan.py +239 -0
  56. package/scripts/codeops_plan_migrate.py +318 -0
  57. package/scripts/codeops_worktree_snapshot.py +99 -0
  58. package/scripts/install_agents.py +288 -0
  59. package/scripts/release.mjs +533 -0
  60. package/skills/analyze-project/SKILL.md +28 -0
  61. package/skills/clean-comments/SKILL.md +22 -0
  62. package/skills/exec-plan/SKILL.md +267 -0
  63. package/skills/exec-plan/commit-modes.md +113 -0
  64. package/skills/exec-plan/execution-protocol.md +471 -0
  65. package/skills/git-commit/SKILL.md +35 -0
  66. package/skills/github-issues/SKILL.md +38 -0
  67. package/skills/grill-me/SKILL.md +342 -0
  68. package/skills/make-plan/SKILL.md +282 -0
  69. package/skills/make-plan/quality-checklist.md +96 -0
  70. package/skills/make-plan/templates.md +535 -0
  71. package/skills/make-plan/zero-ambiguity-gate.md +19 -0
  72. package/skills/make-requirements/SKILL.md +268 -0
  73. package/skills/make-requirements/discovery-phases.md +255 -0
  74. package/skills/make-requirements/review-and-add.md +73 -0
  75. package/skills/make-requirements/templates.md +296 -0
  76. package/skills/make-requirements/zero-ambiguity-gate.md +18 -0
  77. package/skills/outcome-review/SKILL.md +34 -0
  78. package/skills/preflight/SKILL.md +310 -0
  79. package/skills/preflight/dimensions.md +181 -0
  80. package/skills/preflight/report-format.md +300 -0
  81. package/skills/retro-requirements/SKILL.md +218 -0
  82. package/skills/retro-requirements/confidence-classification.md +45 -0
  83. package/skills/retro-requirements/phases.md +609 -0
  84. package/skills/retro-requirements/triage-gate.md +135 -0
  85. package/skills/roadmap/SKILL.md +381 -0
  86. package/skills/roadmap/stage-hooks.md +80 -0
  87. package/skills/roadmap/template.md +200 -0
  88. package/skills/setup-codeops/SKILL.md +94 -0
  89. package/skills/setup-codeops/migration.md +106 -0
  90. package/skills/setup-codeops/scaffold.md +99 -0
  91. package/skills/setup-routing/SKILL.md +102 -0
  92. package/skills/setup-routing/routing.md +44 -0
  93. package/skills/techdocs/SKILL.md +199 -0
  94. package/skills/techdocs/authoring-and-update.md +178 -0
  95. package/skills/techdocs/templates.md +655 -0
  96. package/skills/techdocs/vitepress-setup.md +143 -0
  97. package/skills/upgrade-plan/SKILL.md +75 -0
  98. package/skills/upgrade-plan/content-quality-gate.md +35 -0
  99. package/skills/upgrade-plan/upgrade-checklists.md +107 -0
  100. package/standards/coding-standards-full.md +124 -0
  101. package/standards/coding-standards.md +64 -0
  102. 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.