oh-my-opencode 4.14.0 → 4.14.2
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/.agents/skills/codex-qa/references/isolation.md +1 -1
- package/README.md +1 -1
- package/dist/cli/doctor/framework/types.d.ts +2 -0
- package/dist/cli/index.js +223 -1447
- package/dist/cli-node/index.js +223 -1447
- package/dist/index.js +4255 -714
- package/dist/plugin/native-skills.d.ts +9 -0
- package/dist/plugin/system-transform.d.ts +1 -1
- package/dist/skills/frontend/ATTRIBUTION.md +7 -1
- package/dist/skills/frontend/SKILL.md +4 -3
- package/dist/skills/frontend/references/design/README.md +4 -3
- package/dist/skills/frontend/references/design/_INDEX.md +5 -3
- package/dist/skills/frontend/references/design/aside.md +209 -0
- package/dist/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
- package/dist/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/dist/skills/lcx-doctor/SKILL.md +48 -13
- package/dist/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/dist/skills/lcx-report-bug/SKILL.md +44 -13
- package/dist/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/dist/skills/visual-qa/SKILL.md +5 -5
- package/dist/skills/visual-qa/references/agent-browser-setup.md +5 -4
- package/dist/skills/visual-qa/scripts/cli.test.ts +7 -2
- package/dist/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
- package/dist/skills/visual-qa/scripts/visual-qa.mjs +530 -0
- package/dist/tools/delegate-task/skill-resolver.d.ts +1 -0
- package/dist/tools/delegate-task/types.d.ts +2 -0
- package/dist/tools/skill/types.d.ts +1 -0
- package/package.json +26 -13
- package/packages/git-bash-mcp/dist/cli.js +2 -0
- package/packages/lsp-daemon/dist/cli.js +15 -4
- package/packages/lsp-daemon/dist/ensure-daemon.d.ts +1 -0
- package/packages/lsp-daemon/dist/ensure-daemon.js +9 -3
- package/packages/lsp-daemon/dist/index.js +15 -4
- package/packages/lsp-daemon/dist/paths.d.ts +1 -0
- package/packages/lsp-daemon/dist/paths.js +6 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +4 -7
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +123 -10
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +123 -10
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +126 -9
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +141 -0
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +90 -25
- package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +2 -0
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.d.ts +2 -0
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +56 -0
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +2 -5
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +2 -0
- package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +65 -0
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +6 -1
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +8 -106
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +4 -17
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +17 -5
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +63 -1
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +80 -50
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +4 -12
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +7 -33
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +4 -12
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/sample-quality-gate.json +7 -7
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +3 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-doc.test.ts +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +7 -6
- package/packages/omo-codex/plugin/package-lock.json +16 -56
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +7 -1
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +4 -3
- package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +4 -3
- package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +5 -3
- package/packages/omo-codex/plugin/skills/frontend/references/design/aside.md +209 -0
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +48 -13
- package/packages/omo-codex/plugin/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +44 -13
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/teammode/SKILL.md +17 -5
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +63 -1
- package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +80 -50
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +4 -4
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +5 -5
- package/packages/omo-codex/plugin/skills/visual-qa/references/agent-browser-setup.md +5 -4
- package/packages/omo-codex/plugin/skills/visual-qa/scripts/visual-qa.mjs +530 -0
- package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +23 -0
- package/packages/omo-codex/plugin/test/bootstrap-binlinks.test.mjs +38 -2
- package/packages/omo-codex/plugin/test/component-bin-names.test.mjs +12 -11
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +99 -1
- package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +62 -8
- package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +1 -0
- package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +102 -0
- package/packages/omo-codex/plugin/test/teammode-safety-fixture.mjs +41 -1
- package/packages/omo-codex/plugin/test/teammode-safety.test.mjs +93 -0
- package/packages/omo-codex/scripts/install-bin-links.test.mjs +6 -0
- package/packages/omo-codex/scripts/install-cache-copy.test.mjs +28 -0
- package/packages/omo-codex/scripts/install-config-autonomous-features.test.mjs +43 -3
- package/packages/omo-codex/scripts/install-delegated-command.test.mjs +93 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +169 -41
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +21 -2
- package/packages/omo-codex/scripts/install-local.test.mjs +0 -1
- package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +7 -1
- package/packages/shared-skills/skills/frontend/SKILL.md +4 -3
- package/packages/shared-skills/skills/frontend/references/design/README.md +4 -3
- package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +5 -3
- package/packages/shared-skills/skills/frontend/references/design/aside.md +209 -0
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/shared-skills/skills/lcx-doctor/SKILL.md +48 -13
- package/packages/shared-skills/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +44 -13
- package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/shared-skills/skills/visual-qa/SKILL.md +5 -5
- package/packages/shared-skills/skills/visual-qa/references/agent-browser-setup.md +5 -4
- package/packages/shared-skills/skills/visual-qa/scripts/cli.test.ts +7 -2
- package/packages/shared-skills/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
- package/packages/shared-skills/skills/visual-qa/scripts/visual-qa.mjs +530 -0
- package/dist/cli/sparkshell-appserver-websocket.d.ts +0 -4
- package/dist/cli/sparkshell-appserver.d.ts +0 -16
- package/dist/cli/sparkshell-condense.d.ts +0 -10
- package/dist/cli/sparkshell-parse.d.ts +0 -24
- package/dist/cli/sparkshell-session-context.d.ts +0 -20
- package/dist/cli/sparkshell-spark.d.ts +0 -23
- package/dist/cli/sparkshell.d.ts +0 -33
- package/dist/shared/sparkshell-awareness.d.ts +0 -5
- package/packages/omo-codex/plugin/components/rules/src/sparkshell-awareness.ts +0 -106
- package/packages/omo-codex/plugin/components/rules/test/sparkshell-awareness.test.ts +0 -355
|
@@ -18,7 +18,7 @@ Route ownership the same way as `$lcx-report-bug`, but the deliverable differs b
|
|
|
18
18
|
|
|
19
19
|
For `openai/codex`, create a fork PR that includes:
|
|
20
20
|
|
|
21
|
-
- a focused branch from a fresh
|
|
21
|
+
- a focused branch from a fresh `${TMPDIR:-/tmp}` clone/worktree
|
|
22
22
|
- reproduction logs from before the fix
|
|
23
23
|
- the smallest implementation that fixes the defect
|
|
24
24
|
- verification logs from after the fix
|
|
@@ -30,7 +30,7 @@ For `code-yeongyu/lazycodex`, create an issue (never a PR) that includes:
|
|
|
30
30
|
|
|
31
31
|
- reproduction logs from before the fix
|
|
32
32
|
- the root cause with source evidence
|
|
33
|
-
- the verified patch as a unified diff, produced and tested in a fresh
|
|
33
|
+
- the verified patch as a unified diff, produced and tested in a fresh `${TMPDIR:-/tmp}` clone/worktree
|
|
34
34
|
- verification logs from after the fix
|
|
35
35
|
- the `lazycodex-generated` label and the footer tag `Tag: lazycodex-generated`
|
|
36
36
|
- cleanup of temporary worktrees and clones
|
|
@@ -39,27 +39,58 @@ For `code-yeongyu/lazycodex`, create an issue (never a PR) that includes:
|
|
|
39
39
|
|
|
40
40
|
1. Read the user's bug report and identify the affected surface.
|
|
41
41
|
2. Invoke `$omo:debugging` for the investigation. If only unqualified skill names are exposed, invoke `$debugging` and state that it is the OMO debugging skill.
|
|
42
|
-
3. Materialize the latest sources
|
|
42
|
+
3. Materialize the latest sources under `LAZYCODEX_SOURCE_ROOT="${LAZYCODEX_SOURCE_ROOT:-${TMPDIR:-/tmp}/lazycodex-sources}"`, then decide the target repository. Sync both checkouts on every run and compare them before choosing. Validate cached checkouts before reuse so an incomplete `.git` directory cannot route the fix to the wrong repo:
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
+
LAZYCODEX_SOURCE_ROOT="${LAZYCODEX_SOURCE_ROOT:-${TMPDIR:-/tmp}/lazycodex-sources}"
|
|
46
|
+
mkdir -p "$LAZYCODEX_SOURCE_ROOT"
|
|
47
|
+
|
|
48
|
+
valid_source_checkout() {
|
|
49
|
+
DEST="$1"
|
|
50
|
+
git -C "$DEST" rev-parse --is-inside-work-tree >/dev/null 2>&1 &&
|
|
51
|
+
git -C "$DEST" config --get remote.origin.url >/dev/null 2>&1
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
recover_corrupt_source_checkout() {
|
|
55
|
+
DEST="$1"
|
|
56
|
+
if [ -e "$DEST" ] && ! valid_source_checkout "$DEST"; then
|
|
57
|
+
QUARANTINED="$DEST.corrupt.$(date +%Y%m%d%H%M%S)"
|
|
58
|
+
mv "$DEST" "$QUARANTINED"
|
|
59
|
+
echo "Moved corrupt source cache $DEST to $QUARANTINED" >&2
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
45
63
|
sync_latest_source() {
|
|
46
64
|
REPO="$1"; DEST="$2"
|
|
47
|
-
|
|
65
|
+
recover_corrupt_source_checkout "$DEST"
|
|
66
|
+
if [ ! -d "$DEST" ]; then
|
|
48
67
|
gh repo clone "$REPO" "$DEST" -- --depth=1 \
|
|
49
68
|
|| git clone --depth=1 "https://github.com/$REPO" "$DEST"
|
|
50
69
|
fi
|
|
70
|
+
if ! valid_source_checkout "$DEST"; then
|
|
71
|
+
echo "Source cache $DEST is not a usable git checkout after clone" >&2
|
|
72
|
+
return 1
|
|
73
|
+
fi
|
|
74
|
+
git -C "$DEST" remote set-url origin "https://github.com/$REPO.git" >/dev/null 2>&1 || true
|
|
51
75
|
DEFAULT_BRANCH="$(git -C "$DEST" remote show origin | sed -n '/HEAD branch/s/.*: //p')"
|
|
76
|
+
if [ -z "$DEFAULT_BRANCH" ]; then
|
|
77
|
+
DEFAULT_BRANCH="$(git -C "$DEST" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##')"
|
|
78
|
+
fi
|
|
79
|
+
if [ -z "$DEFAULT_BRANCH" ]; then
|
|
80
|
+
echo "Could not determine default branch for $REPO in $DEST" >&2
|
|
81
|
+
return 1
|
|
82
|
+
fi
|
|
52
83
|
git -C "$DEST" fetch --depth=1 origin "$DEFAULT_BRANCH"
|
|
53
84
|
git -C "$DEST" checkout -B "$DEFAULT_BRANCH" FETCH_HEAD
|
|
54
85
|
}
|
|
55
|
-
sync_latest_source code-yeongyu/lazycodex /
|
|
56
|
-
sync_latest_source openai/codex /
|
|
86
|
+
sync_latest_source code-yeongyu/lazycodex "$LAZYCODEX_SOURCE_ROOT/lazycodex-source"
|
|
87
|
+
sync_latest_source openai/codex "$LAZYCODEX_SOURCE_ROOT/openai-codex-source"
|
|
57
88
|
```
|
|
58
|
-
4. Create a fresh temporary clone and branch
|
|
89
|
+
4. Create a fresh temporary clone and branch under `${TMPDIR:-/tmp}`. Do not modify the user's current repository for the target fix unless the current repository is itself the requested target and the user explicitly asked for local edits.
|
|
59
90
|
|
|
60
91
|
```bash
|
|
61
92
|
TARGET_REPO="code-yeongyu/lazycodex" # or openai/codex
|
|
62
|
-
WORK_ROOT="$(mktemp -d
|
|
93
|
+
WORK_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/lazycodex-fix-XXXXXX")"
|
|
63
94
|
gh repo clone "$TARGET_REPO" "$WORK_ROOT/repo" -- --depth=1
|
|
64
95
|
cd "$WORK_ROOT/repo"
|
|
65
96
|
BASE_BRANCH="$(git remote show origin | sed -n '/HEAD branch/s/.*: //p')"
|
|
@@ -71,7 +102,7 @@ cd "$WORK_ROOT/worktree"
|
|
|
71
102
|
|
|
72
103
|
If `gh` cannot clone, use `git clone --depth=1 "https://github.com/$TARGET_REPO" "$WORK_ROOT/repo"` and continue with the same worktree flow.
|
|
73
104
|
|
|
74
|
-
5. Reproduce the bug in the worktree through the real surface. Save exact command output to
|
|
105
|
+
5. Reproduce the bug in the worktree through the real surface. Save exact command output to `${TMPDIR:-/tmp}/lazycodex-fix-<short-slug>-repro.log`.
|
|
75
106
|
6. Write or update a failing regression test before production changes. Confirm it fails for the bug, not for a missing fixture or typo.
|
|
76
107
|
7. Implement the smallest correct fix. Avoid refactors unless the fix cannot be made safely without one.
|
|
77
108
|
8. Run the regression test, adjacent tests, and the smallest real-surface QA command that proves the user-visible behavior changed.
|
|
@@ -89,7 +120,7 @@ git log --oneline "origin/$BASE_BRANCH..HEAD"
|
|
|
89
120
|
- `code-yeongyu/lazycodex`: export the verified patch and write the issue body from the Verified-Fix Issue Template below:
|
|
90
121
|
|
|
91
122
|
```bash
|
|
92
|
-
PATCH_FILE="
|
|
123
|
+
PATCH_FILE="${TMPDIR:-/tmp}/lazycodex-fix-<short-slug>.patch"
|
|
93
124
|
git diff "origin/$BASE_BRANCH"..HEAD > "$PATCH_FILE"
|
|
94
125
|
```
|
|
95
126
|
|
|
@@ -108,7 +139,7 @@ fi
|
|
|
108
139
|
- `code-yeongyu/lazycodex`: create the verified-fix issue. Never push a branch to this repo and never run `gh pr create` against it:
|
|
109
140
|
|
|
110
141
|
```bash
|
|
111
|
-
ISSUE_BODY="
|
|
142
|
+
ISSUE_BODY="${TMPDIR:-/tmp}/lazycodex-fix-<short-slug>-issue.md"
|
|
112
143
|
gh issue create --repo code-yeongyu/lazycodex --title "<short fix title>" "${LABEL_ARGS[@]}" --body-file "$ISSUE_BODY"
|
|
113
144
|
```
|
|
114
145
|
|
|
@@ -184,8 +215,8 @@ Use the bundled script to generate the PR body. Create a JSON file with this sha
|
|
|
184
215
|
Run:
|
|
185
216
|
|
|
186
217
|
```bash
|
|
187
|
-
PR_INPUT="
|
|
188
|
-
PR_BODY="
|
|
218
|
+
PR_INPUT="${TMPDIR:-/tmp}/lazycodex-fix-<short-slug>-pr.json"
|
|
219
|
+
PR_BODY="${TMPDIR:-/tmp}/lazycodex-fix-<short-slug>-pr.md"
|
|
189
220
|
node "<skill-root>/scripts/create-pr-body.mjs" "$PR_INPUT" "$PR_BODY"
|
|
190
221
|
```
|
|
191
222
|
|
|
@@ -9,4 +9,4 @@ interface:
|
|
|
9
9
|
- "lazycodex fix"
|
|
10
10
|
- "omo-codex bug fix"
|
|
11
11
|
- "codex bug fix pr"
|
|
12
|
-
default_prompt: "Use $lcx-contribute-bug-fix to debug this LazyCodex or Codex bug, work from a fresh
|
|
12
|
+
default_prompt: "Use $lcx-contribute-bug-fix to debug this LazyCodex or Codex bug, work from a fresh ${TMPDIR:-/tmp} clone and worktree, implement the smallest verified fix, apply the lazycodex-generated label, deliver it (verified-fix issue on code-yeongyu/lazycodex — never a PR there; fork PR only for openai/codex), and clean up."
|
|
@@ -7,40 +7,73 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# lcx-doctor
|
|
9
9
|
|
|
10
|
-
You are a LazyCodex install doctor. Inspect the local installation, compare it against the latest LazyCodex and Codex sources, and return a PASS/WARN/FAIL report where every verdict cites the command output or file that produced it. Diagnose only: the only writes you make are under
|
|
10
|
+
You are a LazyCodex install doctor. Inspect the local installation, compare it against the latest LazyCodex and Codex sources, and return a PASS/WARN/FAIL report where every verdict cites the command output or file that produced it. Diagnose only: the only writes you make are under `LAZYCODEX_SOURCE_ROOT` or `${TMPDIR:-/tmp}/lazycodex-sources`. Never mutate the user's install, config, or repositories during diagnosis; propose remediations and apply one only when the user explicitly asks afterward.
|
|
11
11
|
|
|
12
12
|
Use GPT-5.5 style: outcome first, concise, evidence-bound.
|
|
13
13
|
|
|
14
14
|
## Required Workflow
|
|
15
15
|
|
|
16
|
-
1. Materialize the latest sources under
|
|
16
|
+
1. Materialize the latest sources under `LAZYCODEX_SOURCE_ROOT="${LAZYCODEX_SOURCE_ROOT:-${TMPDIR:-/tmp}/lazycodex-sources}"` first. Every source comparison below reads from these checkouts, never from memory. Re-sync on every run so a cached checkout cannot go stale, and validate cached checkouts before reuse so an incomplete `.git` directory cannot poison diagnosis:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
+
LAZYCODEX_SOURCE_ROOT="${LAZYCODEX_SOURCE_ROOT:-${TMPDIR:-/tmp}/lazycodex-sources}"
|
|
20
|
+
mkdir -p "$LAZYCODEX_SOURCE_ROOT"
|
|
21
|
+
|
|
22
|
+
valid_source_checkout() {
|
|
23
|
+
DEST="$1"
|
|
24
|
+
git -C "$DEST" rev-parse --is-inside-work-tree >/dev/null 2>&1 &&
|
|
25
|
+
git -C "$DEST" config --get remote.origin.url >/dev/null 2>&1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
recover_corrupt_source_checkout() {
|
|
29
|
+
DEST="$1"
|
|
30
|
+
if [ -e "$DEST" ] && ! valid_source_checkout "$DEST"; then
|
|
31
|
+
QUARANTINED="$DEST.corrupt.$(date +%Y%m%d%H%M%S)"
|
|
32
|
+
mv "$DEST" "$QUARANTINED"
|
|
33
|
+
echo "Moved corrupt source cache $DEST to $QUARANTINED" >&2
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
|
|
19
37
|
sync_latest_source() {
|
|
20
38
|
REPO="$1"; DEST="$2"
|
|
21
|
-
|
|
39
|
+
recover_corrupt_source_checkout "$DEST"
|
|
40
|
+
if [ ! -d "$DEST" ]; then
|
|
22
41
|
gh repo clone "$REPO" "$DEST" -- --depth=1 \
|
|
23
42
|
|| git clone --depth=1 "https://github.com/$REPO" "$DEST"
|
|
24
43
|
fi
|
|
44
|
+
if ! valid_source_checkout "$DEST"; then
|
|
45
|
+
echo "Source cache $DEST is not a usable git checkout after clone" >&2
|
|
46
|
+
return 1
|
|
47
|
+
fi
|
|
48
|
+
git -C "$DEST" remote set-url origin "https://github.com/$REPO.git" >/dev/null 2>&1 || true
|
|
25
49
|
DEFAULT_BRANCH="$(git -C "$DEST" remote show origin | sed -n '/HEAD branch/s/.*: //p')"
|
|
50
|
+
if [ -z "$DEFAULT_BRANCH" ]; then
|
|
51
|
+
DEFAULT_BRANCH="$(git -C "$DEST" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##')"
|
|
52
|
+
fi
|
|
53
|
+
if [ -z "$DEFAULT_BRANCH" ]; then
|
|
54
|
+
echo "Could not determine default branch for $REPO in $DEST" >&2
|
|
55
|
+
return 1
|
|
56
|
+
fi
|
|
26
57
|
git -C "$DEST" fetch --depth=1 origin "$DEFAULT_BRANCH"
|
|
27
58
|
git -C "$DEST" checkout -B "$DEFAULT_BRANCH" FETCH_HEAD
|
|
28
59
|
}
|
|
29
|
-
sync_latest_source code-yeongyu/lazycodex /
|
|
30
|
-
sync_latest_source openai/codex /
|
|
60
|
+
sync_latest_source code-yeongyu/lazycodex "$LAZYCODEX_SOURCE_ROOT/lazycodex-source"
|
|
61
|
+
sync_latest_source openai/codex "$LAZYCODEX_SOURCE_ROOT/openai-codex-source"
|
|
31
62
|
```
|
|
32
63
|
|
|
33
64
|
2. Inventory the installed surface. Resolve `CODEX_HOME` (default `~/.codex`), then collect:
|
|
34
65
|
- `codex --version` and how `codex` resolves (`command -v codex`).
|
|
35
66
|
- Installed LazyCodex version: the `version` in the installed plugin manifest, discoverable with `find "${CODEX_HOME:-$HOME/.codex}/plugins" -path '*/.codex-plugin/plugin.json'`. Installed plugins live under `$CODEX_HOME/plugins/cache/<marketplace>/<name>/<version>/`.
|
|
36
|
-
- Latest LazyCodex version from
|
|
67
|
+
- Latest LazyCodex version from `$LAZYCODEX_SOURCE_ROOT/lazycodex-source` (release tags or the version stamped in the repo) and latest Codex release (`gh release view --repo openai/codex`).
|
|
37
68
|
- OS, install method, and `lazycodex` / `lazycodex-ai` bin links resolving (`command -v`).
|
|
38
|
-
3. Check config and wiring against the latest installer, not against assumptions. Read what the current installer under
|
|
69
|
+
3. Check config and wiring against the latest installer, not against assumptions. Read what the current installer under `$LAZYCODEX_SOURCE_ROOT/lazycodex-source` writes (installer sources live in the omo-codex package, e.g. `scripts/install/`), then verify the local equivalents:
|
|
39
70
|
- `$CODEX_HOME/config.toml` exists and parses; LazyCodex-managed entries match what the latest installer would write.
|
|
40
|
-
- Plugin payload present and non-empty: `hooks/hooks.json
|
|
71
|
+
- Plugin payload present and non-empty: read `.codex-plugin/plugin.json`; when that manifest declares a `hooks` array, validate every direct hook path declared by the manifest; require `hooks/hooks.json` only when the manifest declares it; do not require retired paths such as `components/workflow-selector` or `hooks/user-prompt-submit-selecting-lazycodex-workflow.json` unless the current manifest declares them.
|
|
72
|
+
- Verify the manifest-declared runtime payload, not a remembered source tree. Current payload includes `skills/`, `.mcp.json`, root CLI runtimes such as `dist/cli/index.js` and `dist/cli-node/index.js`, and every hook/MCP `components/*/dist/*.js` target referenced by installed manifests.
|
|
73
|
+
- Treat install-time materialization rewrites as expected when the rewritten target exists and is non-empty. For example, `.mcp.json` may use plugin-local or absolute installed paths for CodeGraph/MCP runtimes; that is PASS/WARN context, not payload drift. Missing or zero-byte rewritten targets are FAIL.
|
|
41
74
|
- Stale project-local leftovers the installer now removes (e.g. `.codex/hooks.json`, `.codex/skills` in the project) are flagged, not deleted.
|
|
42
|
-
4. Probe the real surface. Do not invoke `lazycodex doctor`; this skill is already running inside that doctor workflow, so calling it would recurse. Instead run non-recursive probes directly: `codex --version`, `command -v codex`, the bin-link checks above, config/plugin payload inspections, and a trivial non-interactive Codex invocation that loads the plugin. Capture stderr verbatim; a clean exit with warnings is WARN, not PASS.
|
|
43
|
-
5. Compare for drift. Where installed bundled files differ from the same files at the installed version, or the latest source
|
|
75
|
+
4. Probe the real surface. Do not invoke `lazycodex doctor`; this skill is already running inside that doctor workflow, so calling it would recurse. Instead run non-recursive probes directly: `codex --version`, `command -v codex`, the bin-link checks above, config/plugin payload inspections, and a trivial non-interactive Codex invocation that loads the plugin. Use the configured Codex default model for the runtime probe unless the user explicitly passed a model override to the doctor surface; never force a guessed/rejected model such as `gpt-5.5-codex-mini`. Capture stderr verbatim; a clean exit with warnings is WARN, not PASS.
|
|
76
|
+
5. Compare for drift. Where installed manifest-declared bundled files differ from the same files at the installed version, or the latest source removed or renamed something the local config still references, record it with both paths. Do not report expected materialization differences, such as absolute `.mcp.json` runtime paths, as drift when their targets exist and are non-empty.
|
|
44
77
|
6. Check whether each FAIL is already known: `gh issue list --repo code-yeongyu/lazycodex --search "<short symptom>" --state open` (and `openai/codex` when the failure points upstream). Link matches in the report instead of re-diagnosing from scratch.
|
|
45
78
|
7. If a probe fails and the cause is not explained by config or source comparison, invoke `$omo:debugging` for the investigation. If Codex exposes only unqualified skill names in the current session, invoke `$debugging` and state that it is the OMO debugging skill.
|
|
46
79
|
8. Emit the report.
|
|
@@ -67,7 +100,7 @@ sync_latest_source openai/codex /tmp/openai-codex-source
|
|
|
67
100
|
| Plugin payload wiring | PASS/WARN/FAIL | [evidence] |
|
|
68
101
|
| Bin links / aliases | PASS/WARN/FAIL | [evidence] |
|
|
69
102
|
| Runtime probe | PASS/WARN/FAIL | [evidence] |
|
|
70
|
-
| Drift vs latest source | PASS/WARN/FAIL | [evidence, citing /
|
|
103
|
+
| Drift vs latest source | PASS/WARN/FAIL | [evidence, citing `$LAZYCODEX_SOURCE_ROOT/lazycodex-source` or `$LAZYCODEX_SOURCE_ROOT/openai-codex-source` paths] |
|
|
71
104
|
|
|
72
105
|
### Remediations
|
|
73
106
|
1. [Most important fix first: exact command or config edit, and what it resolves.]
|
|
@@ -79,7 +112,7 @@ sync_latest_source openai/codex /tmp/openai-codex-source
|
|
|
79
112
|
## Follow-up Routing
|
|
80
113
|
|
|
81
114
|
- Local misconfiguration or stale install: give the remediation; reinstalling via the standard LazyCodex install command is the default fix for payload drift.
|
|
82
|
-
- Defect in LazyCodex or Codex product code: recommend `$lcx-report-bug` to file it, or `$lcx-contribute-bug-fix` when the user wants a fix PR. Both reuse the
|
|
115
|
+
- Defect in LazyCodex or Codex product code: recommend `$lcx-report-bug` to file it, or `$lcx-contribute-bug-fix` when the user wants a fix PR. Both reuse the source-root checkouts you already synced.
|
|
83
116
|
|
|
84
117
|
## Stop Conditions
|
|
85
118
|
|
|
@@ -89,5 +122,7 @@ Do not:
|
|
|
89
122
|
|
|
90
123
|
- mutate config, installs, or repositories during diagnosis
|
|
91
124
|
- report a verdict without captured evidence
|
|
92
|
-
- compare against remembered source layout instead of
|
|
125
|
+
- compare against remembered source layout instead of `$LAZYCODEX_SOURCE_ROOT/lazycodex-source` and `$LAZYCODEX_SOURCE_ROOT/openai-codex-source`
|
|
126
|
+
- require retired payload paths that the current `.codex-plugin/plugin.json` does not declare
|
|
127
|
+
- force a runtime-probe model unless the user explicitly passed one
|
|
93
128
|
- declare healthy while any probe output was never captured
|
|
@@ -8,4 +8,4 @@ interface:
|
|
|
8
8
|
- "codex doctor"
|
|
9
9
|
- "lazycodex install broken"
|
|
10
10
|
- "omo-codex diagnose"
|
|
11
|
-
default_prompt: "Use $lcx-doctor to sync the latest LazyCodex and Codex sources
|
|
11
|
+
default_prompt: "Use $lcx-doctor to sync the latest LazyCodex and Codex sources under LAZYCODEX_SOURCE_ROOT or ${TMPDIR:-/tmp}/lazycodex-sources, inventory the local installation, compare versions, config, and wiring against the latest sources, probe the real surface, and report PASS/WARN/FAIL findings with evidence and remediations."
|
|
@@ -33,28 +33,59 @@ Create or prepare a GitHub issue or PR that includes:
|
|
|
33
33
|
|
|
34
34
|
1. Read the user's bug report and identify the affected surface: LazyCodex installer, Codex plugin, skill, hook, MCP, CLI alias, GitHub marketplace sync, or web/docs.
|
|
35
35
|
2. Invoke `$omo:debugging` for the investigation. If Codex exposes only unqualified skill names in the current session, invoke `$debugging` and state that it is the OMO debugging skill.
|
|
36
|
-
3. Materialize the latest LazyCodex and upstream Codex sources under
|
|
36
|
+
3. Materialize the latest LazyCodex and upstream Codex sources under `LAZYCODEX_SOURCE_ROOT="${LAZYCODEX_SOURCE_ROOT:-${TMPDIR:-/tmp}/lazycodex-sources}"` before deciding ownership. Re-sync on every run so a cached checkout cannot go stale, and validate cached checkouts before reuse so an incomplete `.git` directory cannot produce wrong routing and dead line references:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
+
LAZYCODEX_SOURCE_ROOT="${LAZYCODEX_SOURCE_ROOT:-${TMPDIR:-/tmp}/lazycodex-sources}"
|
|
40
|
+
mkdir -p "$LAZYCODEX_SOURCE_ROOT"
|
|
41
|
+
|
|
42
|
+
valid_source_checkout() {
|
|
43
|
+
DEST="$1"
|
|
44
|
+
git -C "$DEST" rev-parse --is-inside-work-tree >/dev/null 2>&1 &&
|
|
45
|
+
git -C "$DEST" config --get remote.origin.url >/dev/null 2>&1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
recover_corrupt_source_checkout() {
|
|
49
|
+
DEST="$1"
|
|
50
|
+
if [ -e "$DEST" ] && ! valid_source_checkout "$DEST"; then
|
|
51
|
+
QUARANTINED="$DEST.corrupt.$(date +%Y%m%d%H%M%S)"
|
|
52
|
+
mv "$DEST" "$QUARANTINED"
|
|
53
|
+
echo "Moved corrupt source cache $DEST to $QUARANTINED" >&2
|
|
54
|
+
fi
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
sync_latest_source() {
|
|
40
58
|
REPO="$1"; DEST="$2"
|
|
41
|
-
|
|
59
|
+
recover_corrupt_source_checkout "$DEST"
|
|
60
|
+
if [ ! -d "$DEST" ]; then
|
|
42
61
|
gh repo clone "$REPO" "$DEST" -- --depth=1 \
|
|
43
62
|
|| git clone --depth=1 "https://github.com/$REPO" "$DEST"
|
|
44
63
|
fi
|
|
64
|
+
if ! valid_source_checkout "$DEST"; then
|
|
65
|
+
echo "Source cache $DEST is not a usable git checkout after clone" >&2
|
|
66
|
+
return 1
|
|
67
|
+
fi
|
|
68
|
+
git -C "$DEST" remote set-url origin "https://github.com/$REPO.git" >/dev/null 2>&1 || true
|
|
45
69
|
DEFAULT_BRANCH="$(git -C "$DEST" remote show origin | sed -n '/HEAD branch/s/.*: //p')"
|
|
70
|
+
if [ -z "$DEFAULT_BRANCH" ]; then
|
|
71
|
+
DEFAULT_BRANCH="$(git -C "$DEST" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##')"
|
|
72
|
+
fi
|
|
73
|
+
if [ -z "$DEFAULT_BRANCH" ]; then
|
|
74
|
+
echo "Could not determine default branch for $REPO in $DEST" >&2
|
|
75
|
+
return 1
|
|
76
|
+
fi
|
|
46
77
|
git -C "$DEST" fetch --depth=1 origin "$DEFAULT_BRANCH"
|
|
47
78
|
git -C "$DEST" checkout -B "$DEFAULT_BRANCH" FETCH_HEAD
|
|
48
79
|
}
|
|
49
|
-
sync_latest_source code-yeongyu/lazycodex /
|
|
50
|
-
sync_latest_source openai/codex /
|
|
80
|
+
sync_latest_source code-yeongyu/lazycodex "$LAZYCODEX_SOURCE_ROOT/lazycodex-source"
|
|
81
|
+
sync_latest_source openai/codex "$LAZYCODEX_SOURCE_ROOT/openai-codex-source"
|
|
51
82
|
```
|
|
52
83
|
4. Follow the debugging skill far enough to gather runtime evidence:
|
|
53
84
|
- form at least three plausible hypotheses
|
|
54
85
|
- run the smallest reproduction that exercises the real surface
|
|
55
86
|
- confirm the root cause by observing the failing state
|
|
56
87
|
- identify the minimal fix path or maintainer action
|
|
57
|
-
5. Compare runtime evidence with both
|
|
88
|
+
5. Compare runtime evidence with both `$LAZYCODEX_SOURCE_ROOT/lazycodex-source` and `$LAZYCODEX_SOURCE_ROOT/openai-codex-source` before choosing the target repo. Cite exact files, commands, logs, or source paths that support the routing decision.
|
|
58
89
|
6. Choose the target repo:
|
|
59
90
|
- Use `code-yeongyu/lazycodex` when the bug is in LazyCodex integration, distribution, bundled plugin code, skills, hooks, MCP wiring, installer behavior, aliases, marketplace sync, docs, or any behavior that disappears in clean upstream Codex.
|
|
60
91
|
- Use `openai/codex` when the bug reproduces in clean upstream Codex without LazyCodex, or the failing behavior comes from Codex CLI core, plugin API contracts, sandboxing, approvals, config loading, or built-in tool behavior.
|
|
@@ -110,8 +141,8 @@ Write the issue body in English and keep it direct:
|
|
|
110
141
|
## Repository Decision
|
|
111
142
|
- Target repository:
|
|
112
143
|
- Why this belongs there:
|
|
113
|
-
- LazyCodex evidence (runtime +
|
|
114
|
-
- Upstream Codex source evidence from
|
|
144
|
+
- LazyCodex evidence (runtime + `$LAZYCODEX_SOURCE_ROOT/lazycodex-source`):
|
|
145
|
+
- Upstream Codex source evidence from `$LAZYCODEX_SOURCE_ROOT/openai-codex-source`:
|
|
115
146
|
|
|
116
147
|
## Reproduction
|
|
117
148
|
1. [Exact command or UI action]
|
|
@@ -154,8 +185,8 @@ Use this only when a PR is the right artifact, which is only ever for `openai/co
|
|
|
154
185
|
## Repository Decision
|
|
155
186
|
- Target repository:
|
|
156
187
|
- Why this belongs there:
|
|
157
|
-
- LazyCodex evidence (runtime +
|
|
158
|
-
- Upstream Codex source evidence from
|
|
188
|
+
- LazyCodex evidence (runtime + `$LAZYCODEX_SOURCE_ROOT/lazycodex-source`):
|
|
189
|
+
- Upstream Codex source evidence from `$LAZYCODEX_SOURCE_ROOT/openai-codex-source`:
|
|
159
190
|
|
|
160
191
|
## Root Cause
|
|
161
192
|
[Confirmed cause. Cite runtime evidence and source paths.]
|
|
@@ -178,7 +209,7 @@ Tag: lazycodex-generated
|
|
|
178
209
|
Prefer `gh`:
|
|
179
210
|
|
|
180
211
|
```bash
|
|
181
|
-
ISSUE_BODY="
|
|
212
|
+
ISSUE_BODY="${TMPDIR:-/tmp}/lcx-report-bug-$(date +%Y%m%d-%H%M%S).md"
|
|
182
213
|
$EDITOR "$ISSUE_BODY"
|
|
183
214
|
gh issue create --repo "$TARGET_REPO" --title "<clear title>" "${LABEL_ARGS[@]}" --body-file "$ISSUE_BODY"
|
|
184
215
|
```
|
|
@@ -188,7 +219,7 @@ If `$EDITOR` is not usable, write the file with the available file-editing tool,
|
|
|
188
219
|
For an existing issue:
|
|
189
220
|
|
|
190
221
|
```bash
|
|
191
|
-
COMMENT_BODY="
|
|
222
|
+
COMMENT_BODY="${TMPDIR:-/tmp}/lcx-report-bug-comment-$(date +%Y%m%d-%H%M%S).md"
|
|
192
223
|
gh issue comment "<issue-number>" --repo "$TARGET_REPO" --body-file "$COMMENT_BODY"
|
|
193
224
|
if [ "${#LABEL_ARGS[@]}" -gt 0 ]; then
|
|
194
225
|
gh issue edit "<issue-number>" --repo "$TARGET_REPO" --add-label lazycodex-generated
|
|
@@ -198,7 +229,7 @@ fi
|
|
|
198
229
|
For a PR from a branch pushed to a fork — `openai/codex` only, never `code-yeongyu/lazycodex`:
|
|
199
230
|
|
|
200
231
|
```bash
|
|
201
|
-
PR_BODY="
|
|
232
|
+
PR_BODY="${TMPDIR:-/tmp}/lcx-report-bug-pr-$(date +%Y%m%d-%H%M%S).md"
|
|
202
233
|
gh pr create --repo openai/codex --title "<clear title>" "${LABEL_ARGS[@]}" --body-file "$PR_BODY"
|
|
203
234
|
```
|
|
204
235
|
|
|
@@ -232,6 +263,6 @@ Do not file:
|
|
|
232
263
|
- a vague issue without reproduction steps
|
|
233
264
|
- an issue that claims a root cause not supported by runtime evidence
|
|
234
265
|
- a duplicate when commenting on an existing issue is enough
|
|
235
|
-
- an issue without checking the latest
|
|
266
|
+
- an issue without checking the latest `$LAZYCODEX_SOURCE_ROOT/lazycodex-source` and `$LAZYCODEX_SOURCE_ROOT/openai-codex-source` checkouts
|
|
236
267
|
- a LazyCodex issue when the bug is proven to reproduce in clean upstream Codex
|
|
237
268
|
- a fix PR without a concrete branch, implemented fix, and verification result
|
|
@@ -8,4 +8,4 @@ interface:
|
|
|
8
8
|
- "omo-codex bug"
|
|
9
9
|
- "openai codex bug"
|
|
10
10
|
- "codex upstream issue"
|
|
11
|
-
default_prompt: "Use $lcx-report-bug to investigate this LazyCodex or Codex bug, compare LazyCodex evidence with
|
|
11
|
+
default_prompt: "Use $lcx-report-bug to investigate this LazyCodex or Codex bug, compare LazyCodex evidence with sources under LAZYCODEX_SOURCE_ROOT or ${TMPDIR:-/tmp}/lazycodex-sources, choose the correct GitHub repo, and file an issue (or, for openai/codex only, a PR — never a PR on code-yeongyu/lazycodex) with reproduction, root cause, fix guidance, and the lazycodex-generated label and footer."
|
|
@@ -68,7 +68,7 @@ node "<skill-root>/scripts/team.mjs" set-status --team <session_id> --id A --s
|
|
|
68
68
|
node "<skill-root>/scripts/team.mjs" worktree-add --team <session_id> --id A [--base-branch <branch>]
|
|
69
69
|
node "<skill-root>/scripts/team.mjs" worktree-remove --team <session_id> --id A [--force]
|
|
70
70
|
node "<skill-root>/scripts/team.mjs" integrate --team <session_id> [--id A]
|
|
71
|
-
node "<skill-root>/scripts/team.mjs" archive --team <session_id> [--id A]
|
|
71
|
+
node "<skill-root>/scripts/team.mjs" archive --team <session_id> [--id A] [--note "<...>"]
|
|
72
72
|
node "<skill-root>/scripts/team.mjs" delete --team <session_id> [--force]
|
|
73
73
|
node "<skill-root>/scripts/team.mjs" status --team <session_id>
|
|
74
74
|
```
|
|
@@ -80,6 +80,13 @@ space). `{session_id}` is the leader's Codex session id when you can pass it via
|
|
|
80
80
|
otherwise the script generates a stable handle. Re-running `init` is a safe no-op. Every mutating
|
|
81
81
|
subcommand rewrites `guide.md`, so the manual always matches the current team.
|
|
82
82
|
|
|
83
|
+
Mutating subcommands take a per-team state lock before reading and rewriting `team.json`. It is
|
|
84
|
+
safe to run independent `add-member`, `bind-thread`, `set-status`, `archive`, `delete`, `guide`,
|
|
85
|
+
and worktree mutation commands concurrently against the same team: they serialize and each command
|
|
86
|
+
reads the latest committed state before writing. If a command reports that team state is locked,
|
|
87
|
+
do not treat the intended mutation as complete; retry after the named command finishes, or inspect
|
|
88
|
+
`.omo/teams/{session_id}/.team.lock/owner.json` if the previous command crashed.
|
|
89
|
+
|
|
83
90
|
## Create the team and its threads
|
|
84
91
|
|
|
85
92
|
1. `init` the team, then `add-member` once per member.
|
|
@@ -183,12 +190,17 @@ result against that todo's acceptance criteria before you integrate.
|
|
|
183
190
|
|
|
184
191
|
DISBAND the team the moment it is no longer needed. A team exists only to do its work; once that
|
|
185
192
|
work is done, or the user no longer wants it, do not leave it lying around - archive every member,
|
|
186
|
-
then delete the team state
|
|
193
|
+
then delete the team state only after archival evidence is clean or preserved. A finished team that
|
|
194
|
+
is never disbanded is a leak.
|
|
187
195
|
|
|
188
196
|
- `archive` closes the team: notify each active member, copy anything useful into `artifacts/`,
|
|
189
|
-
archive each member thread with `codex_app.set_thread_archived
|
|
190
|
-
|
|
191
|
-
|
|
197
|
+
then try to archive each member thread with `codex_app.set_thread_archived`. Treat Codex App
|
|
198
|
+
failures such as "Ambiguous Codex thread id" or a thread id that is ambiguous across hosts as an
|
|
199
|
+
app-thread archival blocker, not as a team-state blocker: record the failure in the team log,
|
|
200
|
+
tell the user which member thread was not proven archived, and continue the team-state archive
|
|
201
|
+
with `archive --note "<blocker>"`. Never pretend a member thread was archived. Do not delete the
|
|
202
|
+
team state after an app-thread archival blocker unless the evidence has been copied elsewhere or
|
|
203
|
+
the user explicitly accepts that evidence loss.
|
|
192
204
|
- `delete` removes `.omo/teams/{session_id}` and refuses while the team is unarchived or any member
|
|
193
205
|
is still active unless `--force`.
|
|
194
206
|
- When the work wraps up, land it the way the user asked: `integrate --team <id>` for a direct merge
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
// this file stays the state concern only.
|
|
10
10
|
|
|
11
11
|
import { randomUUID } from "node:crypto";
|
|
12
|
-
import { lstat, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
12
|
+
import { lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
13
13
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
14
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
14
15
|
|
|
15
16
|
export const LENSES = ["area", "ownership", "perspective"];
|
|
16
17
|
export const MEMBER_STATUSES = ["pending", "active", "reported", "blocked", "archived"];
|
|
@@ -24,6 +25,8 @@ export function isUnderstaffed(team) {
|
|
|
24
25
|
// A team dir is a single child of .omo/teams. This pattern alone blocks "/", "\", and a
|
|
25
26
|
// leading "." so ".." and "a/b" can never name a team dir (the escape guard).
|
|
26
27
|
const SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
28
|
+
const LOCK_TIMEOUT_MS = Number.parseInt(process.env.OMO_TEAMMODE_LOCK_TIMEOUT_MS ?? "10000", 10);
|
|
29
|
+
const LOCK_RETRY_MS = Number.parseInt(process.env.OMO_TEAMMODE_LOCK_RETRY_MS ?? "25", 10);
|
|
27
30
|
|
|
28
31
|
function isoNow(now) {
|
|
29
32
|
return now ?? new Date().toISOString();
|
|
@@ -233,6 +236,65 @@ async function lstatOrNull(p) {
|
|
|
233
236
|
});
|
|
234
237
|
}
|
|
235
238
|
|
|
239
|
+
function positiveIntegerOrFallback(value, fallback) {
|
|
240
|
+
return Number.isInteger(value) && value > 0 ? value : fallback;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function readLockOwner(lockDir) {
|
|
244
|
+
return readFile(join(lockDir, "owner.json"), "utf8")
|
|
245
|
+
.then((content) => JSON.parse(content))
|
|
246
|
+
.catch(() => null);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function describeLockOwner(lockDir) {
|
|
250
|
+
const owner = await readLockOwner(lockDir);
|
|
251
|
+
if (!owner || typeof owner !== "object") return "another team.mjs command";
|
|
252
|
+
const parts = [];
|
|
253
|
+
if (owner.command) parts.push(String(owner.command));
|
|
254
|
+
if (owner.pid) parts.push(`pid ${owner.pid}`);
|
|
255
|
+
if (owner.createdAt) parts.push(`since ${owner.createdAt}`);
|
|
256
|
+
return parts.length > 0 ? parts.join(", ") : "another team.mjs command";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export async function withTeamLock(dir, command, fn, options = {}) {
|
|
260
|
+
const timeoutMs = positiveIntegerOrFallback(options.timeoutMs ?? LOCK_TIMEOUT_MS, 10000);
|
|
261
|
+
const retryMs = positiveIntegerOrFallback(options.retryMs ?? LOCK_RETRY_MS, 25);
|
|
262
|
+
const lockDir = join(dir, ".team.lock");
|
|
263
|
+
const deadline = Date.now() + timeoutMs;
|
|
264
|
+
let acquired = false;
|
|
265
|
+
for (;;) {
|
|
266
|
+
try {
|
|
267
|
+
await mkdir(lockDir, { mode: 0o700 });
|
|
268
|
+
acquired = true;
|
|
269
|
+
await writeFile(
|
|
270
|
+
join(lockDir, "owner.json"),
|
|
271
|
+
`${JSON.stringify({ pid: process.pid, command, createdAt: new Date().toISOString() }, null, 2)}\n`,
|
|
272
|
+
{ encoding: "utf8", flag: "wx" },
|
|
273
|
+
);
|
|
274
|
+
break;
|
|
275
|
+
} catch (error) {
|
|
276
|
+
if (acquired) {
|
|
277
|
+
await rm(lockDir, { recursive: true, force: true });
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
if (!error || error.code !== "EEXIST") throw error;
|
|
281
|
+
const st = await lstatOrNull(lockDir);
|
|
282
|
+
if (st?.isSymbolicLink()) throw new Error(`refused: team lock path is a symlink: ${lockDir}`);
|
|
283
|
+
if (st && !st.isDirectory()) throw new Error(`refused: team lock path is not a directory: ${lockDir}`);
|
|
284
|
+
if (Date.now() >= deadline) {
|
|
285
|
+
const owner = await describeLockOwner(lockDir);
|
|
286
|
+
throw new Error(`team state is locked by ${owner}; retry after that command completes`);
|
|
287
|
+
}
|
|
288
|
+
await delay(Math.min(retryMs, Math.max(1, deadline - Date.now())));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
return await fn();
|
|
293
|
+
} finally {
|
|
294
|
+
await rm(lockDir, { recursive: true, force: true });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
236
298
|
// Create a directory chain refusing any symlinked component, so team state can never be
|
|
237
299
|
// written through a symlink that escapes the workspace.
|
|
238
300
|
async function mkdirNoSymlink(dir, stopAt) {
|