oh-my-opencode 5.0.0-beta.17 → 5.0.0-beta.18
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/dist/cli/index.js +367 -16
- package/dist/cli/runtime-commands.d.ts +1 -1
- package/dist/cli/worktree-sweep/classify.d.ts +16 -0
- package/dist/cli/worktree-sweep/format.d.ts +8 -0
- package/dist/cli/worktree-sweep/git.d.ts +19 -0
- package/dist/cli/worktree-sweep/index.d.ts +7 -0
- package/dist/cli/worktree-sweep/options.d.ts +2 -0
- package/dist/cli/worktree-sweep/parse-worktree-list.d.ts +8 -0
- package/dist/cli/worktree-sweep/sweep.d.ts +2 -0
- package/dist/cli/worktree-sweep/types.d.ts +65 -0
- package/dist/cli/worktree-sweep/worktree-sweep.d.ts +2 -0
- package/dist/cli-node/index.js +367 -16
- package/dist/index.js +14 -14
- package/dist/skills/review-work/SKILL.md +3 -3
- package/package.json +14 -14
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +3 -3
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
- package/packages/shared-skills/skills/review-work/SKILL.md +3 -3
package/dist/cli/index.js
CHANGED
|
@@ -54,7 +54,7 @@ var package_default;
|
|
|
54
54
|
var init_package = __esm(() => {
|
|
55
55
|
package_default = {
|
|
56
56
|
name: "oh-my-opencode",
|
|
57
|
-
version: "5.0.0-beta.
|
|
57
|
+
version: "5.0.0-beta.18",
|
|
58
58
|
description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
|
59
59
|
main: "./dist/index.js",
|
|
60
60
|
types: "dist/index.d.ts",
|
|
@@ -235,7 +235,7 @@ var init_package = __esm(() => {
|
|
|
235
235
|
zod: "^4.4.3"
|
|
236
236
|
},
|
|
237
237
|
devDependencies: {
|
|
238
|
-
"@code-yeongyu/senpi": "2026.8.
|
|
238
|
+
"@code-yeongyu/senpi": "2026.8.23",
|
|
239
239
|
"@oh-my-opencode/agents-md-core": "workspace:*",
|
|
240
240
|
"@oh-my-opencode/ast-grep-mcp": "workspace:*",
|
|
241
241
|
"@oh-my-opencode/boulder-state": "workspace:*",
|
|
@@ -276,18 +276,18 @@ var init_package = __esm(() => {
|
|
|
276
276
|
typescript: "^7.0.2"
|
|
277
277
|
},
|
|
278
278
|
optionalDependencies: {
|
|
279
|
-
"oh-my-opencode-darwin-arm64": "5.0.0-beta.
|
|
280
|
-
"oh-my-opencode-darwin-x64": "5.0.0-beta.
|
|
281
|
-
"oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.
|
|
282
|
-
"oh-my-opencode-linux-arm64": "5.0.0-beta.
|
|
283
|
-
"oh-my-opencode-linux-arm64-musl": "5.0.0-beta.
|
|
284
|
-
"oh-my-opencode-linux-x64": "5.0.0-beta.
|
|
285
|
-
"oh-my-opencode-linux-x64-baseline": "5.0.0-beta.
|
|
286
|
-
"oh-my-opencode-linux-x64-musl": "5.0.0-beta.
|
|
287
|
-
"oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.
|
|
288
|
-
"oh-my-opencode-windows-arm64": "5.0.0-beta.
|
|
289
|
-
"oh-my-opencode-windows-x64": "5.0.0-beta.
|
|
290
|
-
"oh-my-opencode-windows-x64-baseline": "5.0.0-beta.
|
|
279
|
+
"oh-my-opencode-darwin-arm64": "5.0.0-beta.18",
|
|
280
|
+
"oh-my-opencode-darwin-x64": "5.0.0-beta.18",
|
|
281
|
+
"oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.18",
|
|
282
|
+
"oh-my-opencode-linux-arm64": "5.0.0-beta.18",
|
|
283
|
+
"oh-my-opencode-linux-arm64-musl": "5.0.0-beta.18",
|
|
284
|
+
"oh-my-opencode-linux-x64": "5.0.0-beta.18",
|
|
285
|
+
"oh-my-opencode-linux-x64-baseline": "5.0.0-beta.18",
|
|
286
|
+
"oh-my-opencode-linux-x64-musl": "5.0.0-beta.18",
|
|
287
|
+
"oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.18",
|
|
288
|
+
"oh-my-opencode-windows-arm64": "5.0.0-beta.18",
|
|
289
|
+
"oh-my-opencode-windows-x64": "5.0.0-beta.18",
|
|
290
|
+
"oh-my-opencode-windows-x64-baseline": "5.0.0-beta.18"
|
|
291
291
|
},
|
|
292
292
|
overrides: {
|
|
293
293
|
"@earendil-works/pi-agent-core": "0.84.2",
|
|
@@ -94330,7 +94330,7 @@ var package_default2;
|
|
|
94330
94330
|
var init_package2 = __esm(() => {
|
|
94331
94331
|
package_default2 = {
|
|
94332
94332
|
name: "@oh-my-opencode/omo-codex",
|
|
94333
|
-
version: "5.0.0-beta.
|
|
94333
|
+
version: "5.0.0-beta.18",
|
|
94334
94334
|
type: "module",
|
|
94335
94335
|
private: true,
|
|
94336
94336
|
description: "Codex harness adapter for oh-my-openagent. Vendored Codex plugin namespace (omo) + TypeScript installer + telemetry.",
|
|
@@ -103302,7 +103302,7 @@ function localLauncherCmdPath(homeDir = homedir10()) {
|
|
|
103302
103302
|
}
|
|
103303
103303
|
function renderLocalLauncher(options) {
|
|
103304
103304
|
const brand = {
|
|
103305
|
-
name: "
|
|
103305
|
+
name: "OmO",
|
|
103306
103306
|
displayVersion: options.version ?? "local",
|
|
103307
103307
|
configDir: ".omo",
|
|
103308
103308
|
flatLayout: false,
|
|
@@ -113922,6 +113922,333 @@ async function refreshModelCapabilities(options, deps = {}) {
|
|
|
113922
113922
|
}
|
|
113923
113923
|
}
|
|
113924
113924
|
|
|
113925
|
+
// packages/omo-opencode/src/cli/worktree-sweep/classify.ts
|
|
113926
|
+
import path15 from "path";
|
|
113927
|
+
var DEFAULT_EXCLUDE_PREFIXES = [
|
|
113928
|
+
"~/.codex/worktrees",
|
|
113929
|
+
"~/.codex-gui-cli-remote/worktrees"
|
|
113930
|
+
];
|
|
113931
|
+
function expandHome(value, home) {
|
|
113932
|
+
if (value === "~")
|
|
113933
|
+
return home;
|
|
113934
|
+
if (value.startsWith("~/"))
|
|
113935
|
+
return path15.join(home, value.slice(2));
|
|
113936
|
+
return value;
|
|
113937
|
+
}
|
|
113938
|
+
function isExcludedPath(worktreePath, prefixes, home) {
|
|
113939
|
+
const target = path15.resolve(expandHome(worktreePath, home));
|
|
113940
|
+
return prefixes.some((prefix) => {
|
|
113941
|
+
const resolved = path15.resolve(expandHome(prefix, home));
|
|
113942
|
+
return target === resolved || target.startsWith(`${resolved}${path15.sep}`);
|
|
113943
|
+
});
|
|
113944
|
+
}
|
|
113945
|
+
function worktreeRef(record3) {
|
|
113946
|
+
return record3.branch ?? record3.head ?? "";
|
|
113947
|
+
}
|
|
113948
|
+
function classifyWorktree(input) {
|
|
113949
|
+
const { record: record3 } = input;
|
|
113950
|
+
const ref = worktreeRef(record3);
|
|
113951
|
+
if (record3.locked)
|
|
113952
|
+
return { path: record3.path, ref, decision: "KEEP", reason: "locked" };
|
|
113953
|
+
if (!input.pathExists)
|
|
113954
|
+
return { path: record3.path, ref, decision: "PRUNE" };
|
|
113955
|
+
if (input.external)
|
|
113956
|
+
return { path: record3.path, ref, decision: "KEEP", reason: "external" };
|
|
113957
|
+
if (!input.merged && !input.agedOut) {
|
|
113958
|
+
return { path: record3.path, ref, decision: "KEEP", reason: "unmerged" };
|
|
113959
|
+
}
|
|
113960
|
+
if (input.dirty)
|
|
113961
|
+
return { path: record3.path, ref, decision: "KEEP", reason: "dirty" };
|
|
113962
|
+
return { path: record3.path, ref, decision: "SWEEP" };
|
|
113963
|
+
}
|
|
113964
|
+
// packages/omo-opencode/src/cli/worktree-sweep/format.ts
|
|
113965
|
+
function formatClassification(classification) {
|
|
113966
|
+
const head = classification.decision === "KEEP" && classification.reason !== undefined ? `KEEP(${classification.reason})` : classification.decision;
|
|
113967
|
+
return classification.ref.length > 0 ? `${head} ${classification.path} ${classification.ref}` : `${head} ${classification.path}`;
|
|
113968
|
+
}
|
|
113969
|
+
function formatSummary(result) {
|
|
113970
|
+
const mode = result.apply ? "apply" : "dry-run";
|
|
113971
|
+
const removed2 = result.repos.reduce((total, repo) => total + repo.removed.length, 0);
|
|
113972
|
+
const failed = result.repos.reduce((total, repo) => total + repo.failed.length, 0);
|
|
113973
|
+
return `SUMMARY mode=${mode} repos=${result.repos.length} sweep=${result.sweepCount} keep=${result.keepCount} prune=${result.pruneCount} removed=${removed2} failed=${failed}`;
|
|
113974
|
+
}
|
|
113975
|
+
function formatResult(result) {
|
|
113976
|
+
const lines = [];
|
|
113977
|
+
for (const repo of result.repos) {
|
|
113978
|
+
lines.push(`REPO ${repo.repo} default=${repo.defaultBranch}`);
|
|
113979
|
+
for (const classification of repo.classifications) {
|
|
113980
|
+
lines.push(formatClassification(classification));
|
|
113981
|
+
}
|
|
113982
|
+
for (const failure of repo.failed) {
|
|
113983
|
+
lines.push(`FAILED ${failure.path} ${failure.error}`);
|
|
113984
|
+
}
|
|
113985
|
+
}
|
|
113986
|
+
lines.push(formatSummary(result));
|
|
113987
|
+
return lines;
|
|
113988
|
+
}
|
|
113989
|
+
// packages/omo-opencode/src/cli/worktree-sweep/options.ts
|
|
113990
|
+
function parseOlderThanDays(value) {
|
|
113991
|
+
const parsed = Number(value);
|
|
113992
|
+
if (!Number.isFinite(parsed) || parsed < 0 || !Number.isInteger(parsed)) {
|
|
113993
|
+
throw new Error(`--older-than must be a non-negative integer number of days, got: ${value}`);
|
|
113994
|
+
}
|
|
113995
|
+
return parsed;
|
|
113996
|
+
}
|
|
113997
|
+
// packages/omo-opencode/src/cli/worktree-sweep/parse-worktree-list.ts
|
|
113998
|
+
import { normalize } from "path";
|
|
113999
|
+
function emptyRecord() {
|
|
114000
|
+
return { detached: false, locked: false, prunable: false };
|
|
114001
|
+
}
|
|
114002
|
+
function finalize2(record3) {
|
|
114003
|
+
if (record3.path === undefined || record3.path.length === 0)
|
|
114004
|
+
return;
|
|
114005
|
+
return {
|
|
114006
|
+
path: normalize(record3.path),
|
|
114007
|
+
...record3.head === undefined ? {} : { head: record3.head },
|
|
114008
|
+
...record3.branch === undefined ? {} : { branch: record3.branch },
|
|
114009
|
+
detached: record3.detached,
|
|
114010
|
+
locked: record3.locked,
|
|
114011
|
+
...record3.lockReason === undefined ? {} : { lockReason: record3.lockReason },
|
|
114012
|
+
prunable: record3.prunable
|
|
114013
|
+
};
|
|
114014
|
+
}
|
|
114015
|
+
function applyLine(record3, line) {
|
|
114016
|
+
if (line.startsWith("worktree ")) {
|
|
114017
|
+
record3.path = line.slice("worktree ".length);
|
|
114018
|
+
return;
|
|
114019
|
+
}
|
|
114020
|
+
if (line.startsWith("HEAD ")) {
|
|
114021
|
+
record3.head = line.slice("HEAD ".length);
|
|
114022
|
+
return;
|
|
114023
|
+
}
|
|
114024
|
+
if (line.startsWith("branch ")) {
|
|
114025
|
+
record3.branch = line.slice("branch ".length).replace(/^refs\/heads\//, "");
|
|
114026
|
+
return;
|
|
114027
|
+
}
|
|
114028
|
+
if (line === "detached") {
|
|
114029
|
+
record3.detached = true;
|
|
114030
|
+
return;
|
|
114031
|
+
}
|
|
114032
|
+
if (line === "locked" || line.startsWith("locked ")) {
|
|
114033
|
+
record3.locked = true;
|
|
114034
|
+
const reason = line.slice("locked".length).trim();
|
|
114035
|
+
if (reason.length > 0)
|
|
114036
|
+
record3.lockReason = reason;
|
|
114037
|
+
return;
|
|
114038
|
+
}
|
|
114039
|
+
if (line === "prunable" || line.startsWith("prunable ")) {
|
|
114040
|
+
record3.prunable = true;
|
|
114041
|
+
}
|
|
114042
|
+
}
|
|
114043
|
+
function parseWorktreeList(porcelain) {
|
|
114044
|
+
const records = [];
|
|
114045
|
+
let current = emptyRecord();
|
|
114046
|
+
for (const rawLine of porcelain.split(`
|
|
114047
|
+
`)) {
|
|
114048
|
+
const line = rawLine.replace(/\r$/, "");
|
|
114049
|
+
if (line.length === 0) {
|
|
114050
|
+
const finalized = finalize2(current);
|
|
114051
|
+
if (finalized)
|
|
114052
|
+
records.push(finalized);
|
|
114053
|
+
current = emptyRecord();
|
|
114054
|
+
continue;
|
|
114055
|
+
}
|
|
114056
|
+
applyLine(current, line);
|
|
114057
|
+
}
|
|
114058
|
+
const trailing = finalize2(current);
|
|
114059
|
+
if (trailing)
|
|
114060
|
+
records.push(trailing);
|
|
114061
|
+
return records;
|
|
114062
|
+
}
|
|
114063
|
+
function linkedWorktrees(records) {
|
|
114064
|
+
return records.slice(1);
|
|
114065
|
+
}
|
|
114066
|
+
// packages/omo-opencode/src/cli/worktree-sweep/sweep.ts
|
|
114067
|
+
import { existsSync as existsSync66 } from "fs";
|
|
114068
|
+
import { stat as stat8 } from "fs/promises";
|
|
114069
|
+
import os7 from "os";
|
|
114070
|
+
import { normalize as normalize2 } from "path";
|
|
114071
|
+
|
|
114072
|
+
// packages/omo-opencode/src/cli/worktree-sweep/git.ts
|
|
114073
|
+
init_runtime();
|
|
114074
|
+
async function runGit(cwd, args) {
|
|
114075
|
+
const child = spawn({ cmd: ["git", "-C", cwd, ...args], stdout: "pipe", stderr: "pipe" });
|
|
114076
|
+
const [code, stdout2, stderr] = await Promise.all([
|
|
114077
|
+
child.exited,
|
|
114078
|
+
new Response(child.stdout).text(),
|
|
114079
|
+
new Response(child.stderr).text()
|
|
114080
|
+
]);
|
|
114081
|
+
return { code, stdout: stdout2, stderr };
|
|
114082
|
+
}
|
|
114083
|
+
async function listWorktreesPorcelain(repo) {
|
|
114084
|
+
const result = await runGit(repo, ["worktree", "list", "--porcelain"]);
|
|
114085
|
+
if (result.code !== 0) {
|
|
114086
|
+
throw new Error(result.stderr.trim() || `git worktree list failed in ${repo}`);
|
|
114087
|
+
}
|
|
114088
|
+
return result.stdout;
|
|
114089
|
+
}
|
|
114090
|
+
async function detectDefaultBranch(repo) {
|
|
114091
|
+
const symbolic = await runGit(repo, [
|
|
114092
|
+
"symbolic-ref",
|
|
114093
|
+
"--quiet",
|
|
114094
|
+
"--short",
|
|
114095
|
+
"refs/remotes/origin/HEAD"
|
|
114096
|
+
]);
|
|
114097
|
+
if (symbolic.code === 0) {
|
|
114098
|
+
const value = symbolic.stdout.trim();
|
|
114099
|
+
if (value.length > 0)
|
|
114100
|
+
return value.replace(/^[^/]*\//, "");
|
|
114101
|
+
}
|
|
114102
|
+
const local = await runGit(repo, [
|
|
114103
|
+
"branch",
|
|
114104
|
+
"--list",
|
|
114105
|
+
"main",
|
|
114106
|
+
"master",
|
|
114107
|
+
"--format=%(refname:short)"
|
|
114108
|
+
]);
|
|
114109
|
+
if (local.code === 0) {
|
|
114110
|
+
for (const candidate2 of ["main", "master"]) {
|
|
114111
|
+
if (local.stdout.split(`
|
|
114112
|
+
`).map((line) => line.trim()).includes(candidate2)) {
|
|
114113
|
+
return candidate2;
|
|
114114
|
+
}
|
|
114115
|
+
}
|
|
114116
|
+
}
|
|
114117
|
+
return;
|
|
114118
|
+
}
|
|
114119
|
+
async function isMerged(repo, ref, defaultBranch) {
|
|
114120
|
+
if (ref.length === 0)
|
|
114121
|
+
return false;
|
|
114122
|
+
const result = await runGit(repo, ["merge-base", "--is-ancestor", ref, defaultBranch]);
|
|
114123
|
+
return result.code === 0;
|
|
114124
|
+
}
|
|
114125
|
+
async function isDirty(worktreePath) {
|
|
114126
|
+
const result = await runGit(worktreePath, ["status", "--porcelain"]);
|
|
114127
|
+
if (result.code !== 0)
|
|
114128
|
+
return true;
|
|
114129
|
+
return result.stdout.trim().length > 0;
|
|
114130
|
+
}
|
|
114131
|
+
async function removeWorktree(repo, worktreePath) {
|
|
114132
|
+
return runGit(repo, ["worktree", "remove", worktreePath]);
|
|
114133
|
+
}
|
|
114134
|
+
async function pruneWorktrees(repo) {
|
|
114135
|
+
return runGit(repo, ["worktree", "prune"]);
|
|
114136
|
+
}
|
|
114137
|
+
async function resolveRepoRoot(cwd) {
|
|
114138
|
+
const result = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
114139
|
+
if (result.code !== 0)
|
|
114140
|
+
return;
|
|
114141
|
+
const value = result.stdout.trim();
|
|
114142
|
+
return value.length > 0 ? value : undefined;
|
|
114143
|
+
}
|
|
114144
|
+
|
|
114145
|
+
// packages/omo-opencode/src/cli/worktree-sweep/sweep.ts
|
|
114146
|
+
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
114147
|
+
async function isOlderThan(worktreePath, days, now) {
|
|
114148
|
+
if (days <= 0)
|
|
114149
|
+
return false;
|
|
114150
|
+
try {
|
|
114151
|
+
const stats = await stat8(worktreePath);
|
|
114152
|
+
return now - stats.mtimeMs > days * DAY_MS;
|
|
114153
|
+
} catch {
|
|
114154
|
+
return false;
|
|
114155
|
+
}
|
|
114156
|
+
}
|
|
114157
|
+
async function classifyRecord(repo, record3, defaultBranch, options) {
|
|
114158
|
+
const pathExists3 = existsSync66(record3.path);
|
|
114159
|
+
const external2 = isExcludedPath(record3.path, options.excludePrefixes, options.home);
|
|
114160
|
+
if (record3.locked || !pathExists3 || external2) {
|
|
114161
|
+
return classifyWorktree({
|
|
114162
|
+
record: record3,
|
|
114163
|
+
pathExists: pathExists3,
|
|
114164
|
+
merged: false,
|
|
114165
|
+
agedOut: false,
|
|
114166
|
+
dirty: false,
|
|
114167
|
+
external: external2
|
|
114168
|
+
});
|
|
114169
|
+
}
|
|
114170
|
+
const merged = await isMerged(repo, worktreeRef(record3), defaultBranch);
|
|
114171
|
+
const agedOut = merged ? false : await isOlderThan(record3.path, options.olderThanDays, options.now);
|
|
114172
|
+
const dirty = merged || agedOut ? await isDirty(record3.path) : false;
|
|
114173
|
+
return classifyWorktree({ record: record3, pathExists: pathExists3, merged, agedOut, dirty, external: external2 });
|
|
114174
|
+
}
|
|
114175
|
+
async function sweepRepo(repo, options) {
|
|
114176
|
+
const root = normalize2(await resolveRepoRoot(repo) ?? repo);
|
|
114177
|
+
const defaultBranch = await detectDefaultBranch(root);
|
|
114178
|
+
if (defaultBranch === undefined) {
|
|
114179
|
+
throw new Error(`Could not determine the default branch for ${root}`);
|
|
114180
|
+
}
|
|
114181
|
+
const records = linkedWorktrees(parseWorktreeList(await listWorktreesPorcelain(root)));
|
|
114182
|
+
const classifications = [];
|
|
114183
|
+
for (const record3 of records) {
|
|
114184
|
+
classifications.push(await classifyRecord(root, record3, defaultBranch, options));
|
|
114185
|
+
}
|
|
114186
|
+
const removed2 = [];
|
|
114187
|
+
const failed = [];
|
|
114188
|
+
if (options.apply) {
|
|
114189
|
+
for (const classification of classifications) {
|
|
114190
|
+
if (classification.decision !== "SWEEP")
|
|
114191
|
+
continue;
|
|
114192
|
+
const result = await removeWorktree(root, classification.path);
|
|
114193
|
+
if (result.code === 0)
|
|
114194
|
+
removed2.push(classification.path);
|
|
114195
|
+
else {
|
|
114196
|
+
failed.push({
|
|
114197
|
+
path: classification.path,
|
|
114198
|
+
error: result.stderr.trim() || `git worktree remove exited with ${result.code}`
|
|
114199
|
+
});
|
|
114200
|
+
}
|
|
114201
|
+
}
|
|
114202
|
+
await pruneWorktrees(root);
|
|
114203
|
+
}
|
|
114204
|
+
return { repo: root, defaultBranch, classifications, removed: removed2, failed };
|
|
114205
|
+
}
|
|
114206
|
+
async function sweepWorktrees(options = {}) {
|
|
114207
|
+
const apply = options.apply === true;
|
|
114208
|
+
const olderThanDays = options.olderThanDays ?? 0;
|
|
114209
|
+
const excludePrefixes = options.excludePrefixes ?? DEFAULT_EXCLUDE_PREFIXES;
|
|
114210
|
+
const home = os7.homedir();
|
|
114211
|
+
const now = Date.now();
|
|
114212
|
+
const repos = options.repos && options.repos.length > 0 ? options.repos : [options.cwd ?? process.cwd()];
|
|
114213
|
+
const reports = [];
|
|
114214
|
+
for (const repo of repos) {
|
|
114215
|
+
reports.push(await sweepRepo(repo, { apply, olderThanDays, excludePrefixes, home, now }));
|
|
114216
|
+
}
|
|
114217
|
+
const all = reports.flatMap((report) => report.classifications);
|
|
114218
|
+
return {
|
|
114219
|
+
apply,
|
|
114220
|
+
olderThanDays,
|
|
114221
|
+
repos: reports,
|
|
114222
|
+
sweepCount: all.filter((item) => item.decision === "SWEEP").length,
|
|
114223
|
+
keepCount: all.filter((item) => item.decision === "KEEP").length,
|
|
114224
|
+
pruneCount: all.filter((item) => item.decision === "PRUNE").length
|
|
114225
|
+
};
|
|
114226
|
+
}
|
|
114227
|
+
// packages/omo-opencode/src/cli/worktree-sweep/worktree-sweep.ts
|
|
114228
|
+
async function worktreeSweep(options = {}) {
|
|
114229
|
+
let result;
|
|
114230
|
+
try {
|
|
114231
|
+
result = await sweepWorktrees(options);
|
|
114232
|
+
} catch (error51) {
|
|
114233
|
+
process.stderr.write(`[omo] worktree-sweep failed: ${error51 instanceof Error ? error51.message : String(error51)}
|
|
114234
|
+
`);
|
|
114235
|
+
return 1;
|
|
114236
|
+
}
|
|
114237
|
+
if (options.json === true) {
|
|
114238
|
+
process.stdout.write(JSON.stringify(result, null, 2));
|
|
114239
|
+
process.stdout.write(`
|
|
114240
|
+
`);
|
|
114241
|
+
} else {
|
|
114242
|
+
process.stdout.write(formatResult(result).join(`
|
|
114243
|
+
`));
|
|
114244
|
+
process.stdout.write(`
|
|
114245
|
+
`);
|
|
114246
|
+
}
|
|
114247
|
+
const failedTotal = result.repos.reduce((total, repo) => total + repo.failed.length, 0);
|
|
114248
|
+
if (failedTotal > 0)
|
|
114249
|
+
return 1;
|
|
114250
|
+
return 0;
|
|
114251
|
+
}
|
|
113925
114252
|
// packages/omo-opencode/src/cli/runtime-commands.ts
|
|
113926
114253
|
init_shared();
|
|
113927
114254
|
init_package();
|
|
@@ -113946,6 +114273,30 @@ function configureRuntimeCommands(program2) {
|
|
|
113946
114273
|
});
|
|
113947
114274
|
process.exit(exitCode);
|
|
113948
114275
|
});
|
|
114276
|
+
program2.command("worktree-sweep").description("Report (and optionally remove) stale linked git worktrees").option("--apply", "Actually remove SWEEP worktrees and prune stale metadata (default is dry-run)").option("--older-than <days>", "Sweep unmerged worktrees older than N days (0 = merged-only)", (value) => {
|
|
114277
|
+
try {
|
|
114278
|
+
return parseOlderThanDays(value);
|
|
114279
|
+
} catch (error51) {
|
|
114280
|
+
throw new InvalidArgumentError(error51 instanceof Error ? error51.message : String(error51));
|
|
114281
|
+
}
|
|
114282
|
+
}).option("--repo <path>", "Repository to sweep (repeatable)", (value, previous = []) => [...previous, value]).option("--json", "Output structured JSON result").addHelpText("after", `
|
|
114283
|
+
Examples:
|
|
114284
|
+
$ omo-agent-toolkit worktree-sweep
|
|
114285
|
+
$ omo-agent-toolkit worktree-sweep --older-than=14
|
|
114286
|
+
$ omo-agent-toolkit worktree-sweep --repo /path/to/repo --repo /path/to/other --apply
|
|
114287
|
+
|
|
114288
|
+
Lines: SWEEP <path> <ref> | KEEP(<reason>) <path> <ref> | PRUNE <path> <ref>
|
|
114289
|
+
KEEP reasons: locked, external, unmerged, dirty. Dry-run is the default; --apply
|
|
114290
|
+
removes with 'git worktree remove' (never --force) and then prunes.
|
|
114291
|
+
`).action(async (options) => {
|
|
114292
|
+
const exitCode = await worktreeSweep({
|
|
114293
|
+
apply: options.apply === true,
|
|
114294
|
+
olderThanDays: options.olderThan ?? 0,
|
|
114295
|
+
repos: options.repo,
|
|
114296
|
+
json: options.json ?? false
|
|
114297
|
+
});
|
|
114298
|
+
process.exit(exitCode);
|
|
114299
|
+
});
|
|
113949
114300
|
program2.command("ulw-loop [args...]").allowUnknownOption().passThroughOptions().description("Run the Codex LazyCodex ulw-loop CLI").action(async (args = []) => {
|
|
113950
114301
|
const exitCode = await codexUlwLoop(args);
|
|
113951
114302
|
process.exit(exitCode);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Command } from "commander";
|
|
2
2
|
export declare function configureRuntimeCommands(program: Command): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ClassificationInput, WorktreeClassification, WorktreeRecord } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Externally-owned worktree roots. These are managed by other applications, so
|
|
4
|
+
* sweeping them would delete state we do not own.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_EXCLUDE_PREFIXES: readonly string[];
|
|
7
|
+
export declare function expandHome(value: string, home: string): string;
|
|
8
|
+
export declare function isExcludedPath(worktreePath: string, prefixes: readonly string[], home: string): boolean;
|
|
9
|
+
/** Branch name when attached, otherwise the detached HEAD sha. */
|
|
10
|
+
export declare function worktreeRef(record: WorktreeRecord): string;
|
|
11
|
+
/**
|
|
12
|
+
* Single source of truth for sweep decisions. Mirrors the validated `git-wt-cl`
|
|
13
|
+
* prototype: protection wins over eligibility, and eligibility requires both a
|
|
14
|
+
* merged (or aged-out) ref and a clean tree.
|
|
15
|
+
*/
|
|
16
|
+
export declare function classifyWorktree(input: ClassificationInput): WorktreeClassification;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WorktreeClassification, WorktreeSweepResult } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* One machine-parseable line per worktree:
|
|
4
|
+
* `SWEEP <path> <ref>` / `KEEP(<reason>) <path> <ref>` / `PRUNE <path> <ref>`
|
|
5
|
+
*/
|
|
6
|
+
export declare function formatClassification(classification: WorktreeClassification): string;
|
|
7
|
+
export declare function formatSummary(result: WorktreeSweepResult): string;
|
|
8
|
+
export declare function formatResult(result: WorktreeSweepResult): string[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface GitResult {
|
|
2
|
+
readonly code: number;
|
|
3
|
+
readonly stdout: string;
|
|
4
|
+
readonly stderr: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function runGit(cwd: string, args: readonly string[]): Promise<GitResult>;
|
|
7
|
+
export declare function listWorktreesPorcelain(repo: string): Promise<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Default branch detection: `origin/HEAD` symbolic ref first, then a local
|
|
10
|
+
* `main`, then `master`. Mirrors the prototype's fallback chain.
|
|
11
|
+
*/
|
|
12
|
+
export declare function detectDefaultBranch(repo: string): Promise<string | undefined>;
|
|
13
|
+
/** `git merge-base --is-ancestor <ref> <default>` — the merged oracle. */
|
|
14
|
+
export declare function isMerged(repo: string, ref: string, defaultBranch: string): Promise<boolean>;
|
|
15
|
+
export declare function isDirty(worktreePath: string): Promise<boolean>;
|
|
16
|
+
/** Never uses `--force`: git itself refuses dirty and locked worktrees. */
|
|
17
|
+
export declare function removeWorktree(repo: string, worktreePath: string): Promise<GitResult>;
|
|
18
|
+
export declare function pruneWorktrees(repo: string): Promise<GitResult>;
|
|
19
|
+
export declare function resolveRepoRoot(cwd: string): Promise<string | undefined>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { classifyWorktree, DEFAULT_EXCLUDE_PREFIXES, isExcludedPath } from "./classify";
|
|
2
|
+
export { formatClassification, formatResult, formatSummary } from "./format";
|
|
3
|
+
export { parseOlderThanDays } from "./options";
|
|
4
|
+
export { parseWorktreeList, linkedWorktrees } from "./parse-worktree-list";
|
|
5
|
+
export { sweepWorktrees } from "./sweep";
|
|
6
|
+
export { worktreeSweep } from "./worktree-sweep";
|
|
7
|
+
export type { ClassificationInput, WorktreeClassification, WorktreeDecision, WorktreeKeepReason, WorktreeRecord, WorktreeSweepOptions, WorktreeSweepRepoReport, WorktreeSweepResult, } from "./types";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WorktreeRecord } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Parses `git worktree list --porcelain` output. Records are separated by blank
|
|
4
|
+
* lines; the first record is always the main worktree.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseWorktreeList(porcelain: string): WorktreeRecord[];
|
|
7
|
+
/** Drops the main worktree (first record), which is never a sweep candidate. */
|
|
8
|
+
export declare function linkedWorktrees(records: readonly WorktreeRecord[]): WorktreeRecord[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export interface WorktreeRecord {
|
|
2
|
+
/** Absolute worktree path as reported by `git worktree list --porcelain`. */
|
|
3
|
+
readonly path: string;
|
|
4
|
+
/** Commit SHA of the worktree HEAD, when reported. */
|
|
5
|
+
readonly head?: string;
|
|
6
|
+
/** Short branch name (`refs/heads/` stripped), when the worktree is attached. */
|
|
7
|
+
readonly branch?: string;
|
|
8
|
+
readonly detached: boolean;
|
|
9
|
+
readonly locked: boolean;
|
|
10
|
+
/** Lock reason string recorded by `git worktree lock --reason`, when present. */
|
|
11
|
+
readonly lockReason?: string;
|
|
12
|
+
readonly prunable: boolean;
|
|
13
|
+
}
|
|
14
|
+
export type WorktreeDecision = "SWEEP" | "KEEP" | "PRUNE";
|
|
15
|
+
export type WorktreeKeepReason = "locked" | "external" | "unmerged" | "dirty";
|
|
16
|
+
export interface WorktreeClassification {
|
|
17
|
+
readonly path: string;
|
|
18
|
+
/** Branch name when attached, otherwise the HEAD sha; empty when neither is known. */
|
|
19
|
+
readonly ref: string;
|
|
20
|
+
readonly decision: WorktreeDecision;
|
|
21
|
+
readonly reason?: WorktreeKeepReason;
|
|
22
|
+
}
|
|
23
|
+
export interface ClassificationInput {
|
|
24
|
+
readonly record: WorktreeRecord;
|
|
25
|
+
/** False when the worktree path no longer exists on disk. */
|
|
26
|
+
readonly pathExists: boolean;
|
|
27
|
+
readonly merged: boolean;
|
|
28
|
+
/** True when `--older-than` is satisfied; always false when `--older-than=0`. */
|
|
29
|
+
readonly agedOut: boolean;
|
|
30
|
+
readonly dirty: boolean;
|
|
31
|
+
/** True when the path lives under one of the configured protected prefixes. */
|
|
32
|
+
readonly external: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface WorktreeSweepRepoReport {
|
|
35
|
+
readonly repo: string;
|
|
36
|
+
readonly defaultBranch: string;
|
|
37
|
+
readonly classifications: readonly WorktreeClassification[];
|
|
38
|
+
/** Paths actually removed; always empty in dry-run mode. */
|
|
39
|
+
readonly removed: readonly string[];
|
|
40
|
+
/** Paths that `git worktree remove` refused, with the git error text. */
|
|
41
|
+
readonly failed: readonly {
|
|
42
|
+
readonly path: string;
|
|
43
|
+
readonly error: string;
|
|
44
|
+
}[];
|
|
45
|
+
}
|
|
46
|
+
export interface WorktreeSweepResult {
|
|
47
|
+
readonly apply: boolean;
|
|
48
|
+
readonly olderThanDays: number;
|
|
49
|
+
readonly repos: readonly WorktreeSweepRepoReport[];
|
|
50
|
+
readonly sweepCount: number;
|
|
51
|
+
readonly keepCount: number;
|
|
52
|
+
readonly pruneCount: number;
|
|
53
|
+
}
|
|
54
|
+
export interface WorktreeSweepOptions {
|
|
55
|
+
/** Dry-run is the default; only an explicit `true` removes anything. */
|
|
56
|
+
readonly apply?: boolean;
|
|
57
|
+
/** 0 means merged-only: unmerged worktrees are never swept. */
|
|
58
|
+
readonly olderThanDays?: number;
|
|
59
|
+
/** Repositories to sweep; defaults to the current working directory's repo. */
|
|
60
|
+
readonly repos?: readonly string[];
|
|
61
|
+
readonly json?: boolean;
|
|
62
|
+
/** Overrides the built-in externally-owned path prefixes. */
|
|
63
|
+
readonly excludePrefixes?: readonly string[];
|
|
64
|
+
readonly cwd?: string;
|
|
65
|
+
}
|