oh-my-opencode 5.0.0-beta.16 → 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 +379 -25
- 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 +379 -25
- package/dist/index.js +26 -23
- package/dist/oh-my-opencode.schema.json +1 -1
- package/dist/skills/review-work/SKILL.md +3 -3
- package/dist/tui.js +12 -9
- 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/dist/cli.js +11 -8
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +11 -8
- 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",
|
|
@@ -20396,13 +20396,14 @@ function resolveOmoTaskSettings(input, resolveParallelism = availableParallelism
|
|
|
20396
20396
|
const record2 = record(string2(), unknown()).parse(input);
|
|
20397
20397
|
return OmoTaskSettingsSchema.parse({
|
|
20398
20398
|
...record2,
|
|
20399
|
-
residency_max_children: record2["residency_max_children"] ?? Math.max(8, resolveParallelism() * 3)
|
|
20399
|
+
residency_max_children: record2["residency_max_children"] ?? Math.max(8, resolveParallelism() * 3),
|
|
20400
|
+
global_concurrency: record2["global_concurrency"] ?? Math.max(8, resolveParallelism() * 2)
|
|
20400
20401
|
});
|
|
20401
20402
|
}
|
|
20402
20403
|
var ResidencyMaxChildrenInputSchema, OmoTaskWaitSchema, OmoTaskTeamSettingsSchema, OmoTaskWarningsSchema, OmoTaskDagSettingsSchema, OmoTaskSettingsSchema, OmoTaskDagSettingsLayerSchema, OmoTaskWaitLayerSchema, OmoTaskTeamSettingsLayerSchema, OmoTaskWarningsLayerSchema, OmoTaskSettingsLayerSchema;
|
|
20403
20404
|
var init_task = __esm(() => {
|
|
20404
20405
|
init_zod();
|
|
20405
|
-
ResidencyMaxChildrenInputSchema = union([number2().int().
|
|
20406
|
+
ResidencyMaxChildrenInputSchema = union([number2().int().nonnegative(), literal("unlimited")]);
|
|
20406
20407
|
OmoTaskWaitSchema = object({
|
|
20407
20408
|
min_ms: number2().int().positive().default(5000),
|
|
20408
20409
|
default_ms: number2().int().positive().default(60000),
|
|
@@ -20428,9 +20429,10 @@ var init_task = __esm(() => {
|
|
|
20428
20429
|
}).strict();
|
|
20429
20430
|
OmoTaskSettingsSchema = object({
|
|
20430
20431
|
default_execution_mode: _enum2(["in-process", "process"]).default("in-process"),
|
|
20431
|
-
default_concurrency: number2().int().
|
|
20432
|
-
|
|
20433
|
-
|
|
20432
|
+
default_concurrency: number2().int().nonnegative().default(5),
|
|
20433
|
+
global_concurrency: number2().int().nonnegative().default(8),
|
|
20434
|
+
provider_concurrency: record(string2(), number2().int().nonnegative()).optional(),
|
|
20435
|
+
model_concurrency: record(string2(), number2().int().nonnegative()).optional(),
|
|
20434
20436
|
max_depth: number2().int().nonnegative().default(1),
|
|
20435
20437
|
residency_max_children: ResidencyMaxChildrenInputSchema.default(8),
|
|
20436
20438
|
ttl_ms: number2().int().positive().default(86400000),
|
|
@@ -20471,9 +20473,10 @@ var init_task = __esm(() => {
|
|
|
20471
20473
|
}).strict();
|
|
20472
20474
|
OmoTaskSettingsLayerSchema = object({
|
|
20473
20475
|
default_execution_mode: _enum2(["in-process", "process"]).optional(),
|
|
20474
|
-
default_concurrency: number2().int().
|
|
20475
|
-
|
|
20476
|
-
|
|
20476
|
+
default_concurrency: number2().int().nonnegative().optional(),
|
|
20477
|
+
global_concurrency: number2().int().nonnegative().optional(),
|
|
20478
|
+
provider_concurrency: record(string2(), number2().int().nonnegative()).optional(),
|
|
20479
|
+
model_concurrency: record(string2(), number2().int().nonnegative()).optional(),
|
|
20477
20480
|
max_depth: number2().int().nonnegative().optional(),
|
|
20478
20481
|
residency_max_children: ResidencyMaxChildrenInputSchema.optional(),
|
|
20479
20482
|
ttl_ms: number2().int().positive().optional(),
|
|
@@ -94327,7 +94330,7 @@ var package_default2;
|
|
|
94327
94330
|
var init_package2 = __esm(() => {
|
|
94328
94331
|
package_default2 = {
|
|
94329
94332
|
name: "@oh-my-opencode/omo-codex",
|
|
94330
|
-
version: "5.0.0-beta.
|
|
94333
|
+
version: "5.0.0-beta.18",
|
|
94331
94334
|
type: "module",
|
|
94332
94335
|
private: true,
|
|
94333
94336
|
description: "Codex harness adapter for oh-my-openagent. Vendored Codex plugin namespace (omo) + TypeScript installer + telemetry.",
|
|
@@ -103299,7 +103302,7 @@ function localLauncherCmdPath(homeDir = homedir10()) {
|
|
|
103299
103302
|
}
|
|
103300
103303
|
function renderLocalLauncher(options) {
|
|
103301
103304
|
const brand = {
|
|
103302
|
-
name: "
|
|
103305
|
+
name: "OmO",
|
|
103303
103306
|
displayVersion: options.version ?? "local",
|
|
103304
103307
|
configDir: ".omo",
|
|
103305
103308
|
flatLayout: false,
|
|
@@ -106983,7 +106986,7 @@ var CircuitBreakerConfigSchema = exports_external.object({
|
|
|
106983
106986
|
consecutiveThreshold: exports_external.number().int().min(5).optional()
|
|
106984
106987
|
});
|
|
106985
106988
|
var BackgroundTaskConfigSchema = exports_external.object({
|
|
106986
|
-
defaultConcurrency: exports_external.number().min(
|
|
106989
|
+
defaultConcurrency: exports_external.number().min(0).optional(),
|
|
106987
106990
|
providerConcurrency: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional(),
|
|
106988
106991
|
modelConcurrency: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional(),
|
|
106989
106992
|
maxDepth: exports_external.number().int().min(1).optional(),
|
|
@@ -113919,6 +113922,333 @@ async function refreshModelCapabilities(options, deps = {}) {
|
|
|
113919
113922
|
}
|
|
113920
113923
|
}
|
|
113921
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
|
+
}
|
|
113922
114252
|
// packages/omo-opencode/src/cli/runtime-commands.ts
|
|
113923
114253
|
init_shared();
|
|
113924
114254
|
init_package();
|
|
@@ -113943,6 +114273,30 @@ function configureRuntimeCommands(program2) {
|
|
|
113943
114273
|
});
|
|
113944
114274
|
process.exit(exitCode);
|
|
113945
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
|
+
});
|
|
113946
114300
|
program2.command("ulw-loop [args...]").allowUnknownOption().passThroughOptions().description("Run the Codex LazyCodex ulw-loop CLI").action(async (args = []) => {
|
|
113947
114301
|
const exitCode = await codexUlwLoop(args);
|
|
113948
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
|
+
}
|