lazycodex-ai 5.1.29 → 5.1.32
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/README.ja.md +2 -0
- package/README.ko.md +2 -0
- package/README.md +2 -0
- package/README.ru.md +2 -0
- package/README.zh-cn.md +2 -0
- package/dist/cli/cli-program.d.ts +1 -20
- package/dist/cli/index.js +950 -51
- package/dist/cli/install-command-options.d.ts +20 -0
- package/dist/cli/service-auth/browser.d.ts +3 -0
- package/dist/cli/service-auth/cli-keystore.fixture.d.ts +7 -0
- package/dist/cli/service-auth/commands.d.ts +19 -0
- package/dist/cli/service-auth/device-flow.d.ts +15 -0
- package/dist/cli/service-auth/device-keys.d.ts +10 -0
- package/dist/cli/service-auth/device-name.d.ts +3 -0
- package/dist/cli/service-auth/keystore.d.ts +20 -0
- package/dist/cli/service-auth/loopback.d.ts +11 -0
- package/dist/cli/service-auth/protocol.d.ts +86 -0
- package/dist/cli/service-auth/refresh-child.fixture.d.ts +1 -0
- package/dist/cli/service-auth/session.d.ts +17 -0
- package/dist/cli/service-auth/terminal-text.d.ts +1 -0
- package/dist/cli/service-auth/test-support.d.ts +43 -0
- package/dist/cli/service-auth/tls-test-certificate.d.ts +4 -0
- package/dist/cli-node/index.js +950 -51
- package/package.json +1 -1
- package/packages/git-bash-mcp/dist/cli.js +1 -1
- package/packages/lsp-daemon/dist/cli.js +1 -1
- package/packages/lsp-daemon/dist/client.js +1 -1
- package/packages/lsp-daemon/dist/index.js +1 -1
- package/packages/lsp-tools-mcp/dist/cli.js +1 -1
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +1 -1
- package/packages/lsp-tools-mcp/dist/mcp.js +1 -1
- package/packages/lsp-tools-mcp/dist/tools.js +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +7 -4
- 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/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/lcx/skills/lcx-contribute-bug-fix/SKILL.md +7 -4
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +1 -1
- 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/teammode/hooks/hooks.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 +3 -3
- 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/dist/cli.js +18 -15
- 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/ultrawork/src/directive-content.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +18 -15
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +2 -2
- 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-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/post-tool-use-recording-spawn-admission.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-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-ulw-execute-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-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 +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/auto-update-plan.mjs +2 -0
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +2 -0
- package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +2 -0
- package/packages/omo-codex/plugin/scripts/build-components.mjs +1 -0
- package/packages/omo-codex/plugin/scripts/materialize-shared-upstreams.mjs +42 -10
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +2 -2
- package/packages/omo-codex/plugin/skills/browser/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +7 -4
- package/packages/omo-codex/plugin/skills/teammode/SKILL.md +3 -3
- package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +14 -16
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +18 -15
- package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +17 -18
- package/packages/omo-codex/plugin/test/materialize-shared-upstreams.test.mjs +179 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +2 -2
- package/packages/omo-codex/plugin/test/windows-console-hide.test.mjs +150 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +11 -6
- package/packages/prompts-core/prompts/ultrawork/codex.md +18 -15
- package/packages/shared-skills/skills/browser/SKILL.md +1 -1
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +14 -16
- package/packages/shared-skills/skills/ulw-execute/SKILL.md +2 -2
- package/packages/shared-skills/skills/ulw-research/SKILL.md +17 -18
- package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +5 -3
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.test.ts +10 -4
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +3 -2
- package/packages/shared-skills/skills/visual-qa/scripts/cli.test.ts +5 -3
|
@@ -88,7 +88,7 @@ Saturation research defaults to teammode, not isolated fire-and-forget workers:
|
|
|
88
88
|
|
|
89
89
|
## Worker ground rules
|
|
90
90
|
|
|
91
|
-
Research workers (explore,
|
|
91
|
+
Research workers (explore, web, repo-dive) differ by harness, but assume:
|
|
92
92
|
|
|
93
93
|
- **Read-only.** Most research workers cannot write files. Never ask a worker to write the journal or any session file — every journal write is yours.
|
|
94
94
|
- **No recursion — workers AND members.** Workers cannot spawn their own subagents, and team members must not re-orchestrate: a member researches its axis and reports; it never stands up its own team, loads this skill, or fans out a research swarm of its own. Depth comes from YOUR expansion waves. Say so in every spawn message — a member that starts its own research protocol burns the run's budget on duplicated orchestration and returns nothing you can cite.
|
|
@@ -121,11 +121,13 @@ Before spawning anything, decompose the query. Start from "what must be true if
|
|
|
121
121
|
<analysis>
|
|
122
122
|
Core question: <the actual information need>
|
|
123
123
|
Axes (3+ orthogonal): <axis — what to search, where, why> ...
|
|
124
|
-
Codebase relevant: <yes/no> · External: <yes/no> ·
|
|
124
|
+
Codebase relevant: <yes/no> · External: <yes/no> · Collection: <search-only | browser> · Verification likely: <yes/no>
|
|
125
125
|
Scale: <axis count, source territories, target document length> · Precision demand: <what a wrong claim costs here> → lifecycle: <single team | research team then refinement team>
|
|
126
126
|
</analysis>
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
`Collection` is `search-only` only when a result snippet settles every axis (a date, a version, a spec value); anything that needs reading a source is `browser`.
|
|
130
|
+
|
|
129
131
|
Then create the session directory:
|
|
130
132
|
|
|
131
133
|
```bash
|
|
@@ -171,26 +173,23 @@ Otherwise launch the entire first wave in one turn — every axis at once, as te
|
|
|
171
173
|
|
|
172
174
|
Scaling floor — more angles always justify more workers:
|
|
173
175
|
|
|
174
|
-
| Query scope | explore |
|
|
175
|
-
|
|
176
|
-
| Single topic, codebase only | 3 | 0 | 0 |
|
|
177
|
-
| Single topic, web only | 0 |
|
|
178
|
-
| Single topic, both | 2 |
|
|
179
|
-
| Multi-faceted | 4 |
|
|
180
|
-
| Full due diligence | 4 |
|
|
181
|
-
|
|
182
|
-
The browsing column is BINDING, not advisory: when the brief says `Browsing: yes`, the roster names a browsing-worker angle, armed with the `ultimate-browsing` skill, before the first wave launches, and that worker is spawned in the same turn as the rest of the wave. A run that reaches wave 2 with zero browsing workers on a `Browsing: yes` brief has silently downgraded every source to what plain fetch happened to return.
|
|
176
|
+
| Query scope | explore | web | repo-dive | floor |
|
|
177
|
+
|---|---|---|---|---|
|
|
178
|
+
| Single topic, codebase only | 3 | 0 | 0 | 3 |
|
|
179
|
+
| Single topic, web only | 0 | 5 | 1 | 6 |
|
|
180
|
+
| Single topic, both | 2 | 4 | 1 | 7 |
|
|
181
|
+
| Multi-faceted | 4 | 8 | 2 | 14 |
|
|
182
|
+
| Full due diligence | 4 | 9 | 2 | 15 |
|
|
183
183
|
|
|
184
184
|
**Disambiguate before you expand.** When the topic names something that could resolve several ways — a product, a person, a codename, a version — the first wave settles WHICH entity before any worker researches its history, benchmarks, or controversies: canonical name, first-party URL or account, whether it exists in the claimed category, and a confidence line. An unresolved entity never becomes a premise in a later wave's spawn message; that is exactly how a run starts inventing facts about something that does not exist.
|
|
185
185
|
|
|
186
186
|
Role protocols — embed the relevant one in each spawn message; every worker gets a unique angle:
|
|
187
187
|
|
|
188
188
|
- **Codebase (explore), 2-4 workers.** Grep with 3+ keyword variations; structural/AST search; LSP definitions and references; file-name globs; `git log --all -S '<keyword>'` and `--grep` for history including deleted code. Cross-validate hits across tools. Report absolute file paths, patterns with `file:line`, and how findings connect.
|
|
189
|
-
- **Web
|
|
190
|
-
- **Browsing, 1-3 workers on a `Browsing: yes` brief (0 otherwise), every one loaded with the `ultimate-browsing` skill.** The skill owns the routing — the harness's own browser first, then its extraction engine with archive surrogates, platform-native readers, and Chrome stealth as each source demands; the worker owns the deliverable. This worker RENDERS pages, it does not re-fetch them: its standing deliverable is a full-page screenshot of every top source plus the rendered text plain fetch could not reach; a worker that returns only fetched text has not done its job. JS-rendered, login-gated, WAF-blocked, and screenshot-bearing sources all belong here rather than in the web lane. **Provenance is part of the claim**: when a source came back with `provenance` of `snapshot` (an archive copy), cite it with its `snapshot_timestamp` and never state it as the current live page; content from a `proxy` route is `untrusted` and needs a second independent route before any claim rests on it. When one blocked territory hides many leads, fan out more browsing subagents in parallel for breadth instead of serializing one worker through them.
|
|
189
|
+
- **Web, 3-9 workers, every one loaded with the `browser` skill.** Search with 10+ websearch queries per worker, each with a different operator or angle (see Search craft); a result is a candidate, not a source. Read every candidate in the user's browser through the `browser` skill's attached engine: one named session per worker, one tab per source, several tabs open side by side while sources are compared — `session.tabCreate` / `tabSelect` / `tabClose` where the harness has a JS eval kernel, `bsk tab create --url <url>` / `tab select <id>` / `tab close <id>` / `tab list` on the CLI — the page scrolled to its end, a full-page screenshot of each top source saved as provenance, the tab closed once reported. A browser on another machine is reached through the `browser` skill's remote reference or a user-level skill that bridges to it, never replaced by a local headless one. `webfetch` is for raw files only: PDF, JSON, feeds, sitemaps. A host the browser cannot open (WAF, bot score, 403) goes to `ultimate-browsing`, whose provenance travels with the claim: a `snapshot` result cites its `snapshot_timestamp` and is never stated as the live page; a `proxy` result is `untrusted` until a second independent route agrees. Context7 with 3+ queries per known library; grep.app and `gh search code|repos|issues` for real-world usage; official docs via sitemap discovery (`<base>/sitemap.xml`), then targeted pages. A worker whose sources are all fetched text has searched, not read.
|
|
191
190
|
- **Repo deep-dive (librarian), 0-2 workers.** Shallow-clone the most relevant repos to `${TMPDIR:-/tmp}`, pin the HEAD SHA, read core modules, follow call chains, return SHA-pinned permalinks.
|
|
192
191
|
|
|
193
|
-
Example spawn (codebase axis;
|
|
192
|
+
Example spawn (codebase axis; web and repo-dive follow the same contract with their own protocol):
|
|
194
193
|
|
|
195
194
|
```
|
|
196
195
|
task(subagent_type="explore", run_in_background=true, prompt="TASK: act as a codebase researcher. AXIS: <specific angle>.
|
|
@@ -209,9 +208,9 @@ This loop is what makes the mode research rather than search. Collect returns as
|
|
|
209
208
|
3. Spawn an expansion worker immediately for each new unchecked lead:
|
|
210
209
|
|
|
211
210
|
```
|
|
212
|
-
task(
|
|
211
|
+
task(category="deep-low", load_skills=["browser"], run_in_background=true, prompt="TASK: expansion wave <N> — investigate: <lead>.
|
|
213
212
|
PARENT: <which return surfaced it>. This is an explicit exhaustive-research assignment; budgets do not apply.
|
|
214
|
-
<role protocol for the lead's territory —
|
|
213
|
+
<role protocol for the lead's territory — web protocol for external leads, explore protocol for codebase leads>
|
|
215
214
|
End your reply with the ## EXPAND tail.")
|
|
216
215
|
```
|
|
217
216
|
|
|
@@ -333,7 +332,7 @@ Asset workers (background, parallel, each fed `design-spec.md`) — visuals are
|
|
|
333
332
|
- **Charts for every quantitative finding, computed from real data.** Pull the numbers into an actual table first (CSV/JSON under `$SESSION_DIR`), then plot from that table, never from prose. Follow the data-scientist tool doctrine — numpy always, Polars for filtering/sorting/transforms, DuckDB for joins/aggregations/window functions, never pandas — and load the `data-scientist` skill when this session has it: `uv run --with numpy --with polars --with duckdb --with pyarrow --with matplotlib python`. Keep `pyarrow` in that set — the DuckDB-to-Polars handoff (`.pl()`) fails without it, and `.df()` fails without pandas, so hand data across through `.pl()`, never `.df()`. Save to `$SESSION_DIR/assets/`.
|
|
334
333
|
- **Mermaid graphs** for process, architecture, argument, timeline, and evidence-flow structure, themed to the spec's fonts and palette. Render each to SVG and confirm the file exists before the document references it.
|
|
335
334
|
- **Generated visuals through the imagegen skill whenever the session has it:** a cover plus a concept illustration per major theme, prompted from the spec's style, palette, and mood — document-styled illustration, never generic stock art dropped into a designed page.
|
|
336
|
-
- **Full-page screenshots** of the top 5-10 sources
|
|
335
|
+
- **Full-page screenshots** of the top 5-10 sources, saved by the web workers while reading, as provenance you can show.
|
|
337
336
|
|
|
338
337
|
**Verify the asset manifest before rendering.** List every asset the document references, assert each file exists and is non-empty on disk, and re-render whatever is missing. A document that renders with three broken diagrams is a document you will publish twice.
|
|
339
338
|
|
|
@@ -391,7 +390,7 @@ High-yield combinations: official docs (`site:<docs domain>`), GitHub implementa
|
|
|
391
390
|
| Obeying a surrounding "stop exploring" rule mid-research | Authority section — those rules do not bind this mode |
|
|
392
391
|
| Asking a worker to write journal or session files | Workers are read-only; you journal every return |
|
|
393
392
|
| Two workers given the same angle | One unique angle per worker, always |
|
|
394
|
-
| A
|
|
393
|
+
| A web worker that returned only fetched text, or a `Collection: browser` run whose workers never opened a tab | Sources are read in the user's browser through the `browser` skill; `webfetch` is for raw files; every web worker carries the skill from the first wave |
|
|
395
394
|
| Contested claim settled by judgment | Phase 3 — run code, capture output, verdict |
|
|
396
395
|
| Deliverable claims without citations | Every claim cites a source or a verification artifact |
|
|
397
396
|
| Guessing the deliverable format | Derive it from the request and destination; ask the empty-info interview only for what is still missing, and record `answered_by` for every field |
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
+
|
|
9
|
+
const scripts = join(dirname(fileURLToPath(import.meta.url)), "..", "scripts");
|
|
10
|
+
|
|
11
|
+
// Drive the shipped CLI in a disposable layout. Only external Git results and
|
|
12
|
+
// backoff time are controlled; no test-only export or option is added to it.
|
|
13
|
+
function runCli(t, { failures = 0, strict = true, strictEnv = false, skip = false, pins = "valid" } = {}) {
|
|
14
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "omo-materialize-retry-")));
|
|
15
|
+
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
16
|
+
const entry = join(root, "packages", "omo-codex", "plugin", "scripts");
|
|
17
|
+
const shared = join(root, "packages", "shared-skills");
|
|
18
|
+
mkdirSync(entry, { recursive: true });
|
|
19
|
+
mkdirSync(join(shared, "scripts"), { recursive: true });
|
|
20
|
+
for (const name of ["materialize-shared-upstreams.mjs", "entry-guard.mjs"]) {
|
|
21
|
+
copyFileSync(join(scripts, name), join(entry, name));
|
|
22
|
+
}
|
|
23
|
+
writeFileSync(join(shared, "scripts", "materialize-frontend-refs.mjs"),
|
|
24
|
+
'export async function materializeFrontendRefs({ strict }) { globalThis.record({ op: "materialize", strict }); return { skipped: false }; }\n');
|
|
25
|
+
writeFileSync(join(shared, "stage-omowright-runtime.mjs"),
|
|
26
|
+
'export async function stageOmowrightRuntime() { globalThis.record({ op: "stage" }); return { version: "fixture" }; }\n');
|
|
27
|
+
const trace = join(root, "trace.json");
|
|
28
|
+
writeFileSync(trace, "[]");
|
|
29
|
+
const preload = join(root, "git-fixture.mjs");
|
|
30
|
+
writeFileSync(preload, `
|
|
31
|
+
import childProcess from "node:child_process";
|
|
32
|
+
import timers from "node:timers/promises";
|
|
33
|
+
import { syncBuiltinESMExports } from "node:module";
|
|
34
|
+
import { writeFileSync } from "node:fs";
|
|
35
|
+
const events = [];
|
|
36
|
+
const realSleep = timers.setTimeout;
|
|
37
|
+
let attempts = 0;
|
|
38
|
+
let paths = [];
|
|
39
|
+
globalThis.record = (event) => {
|
|
40
|
+
events.push(event);
|
|
41
|
+
writeFileSync(${JSON.stringify(trace)}, JSON.stringify(events));
|
|
42
|
+
};
|
|
43
|
+
childProcess.execFileSync = (command, args, options) => {
|
|
44
|
+
if (command !== "git") throw new Error("unexpected command: " + command);
|
|
45
|
+
record({ op: args[0], args, cwd: options.cwd, windowsHide: options.windowsHide });
|
|
46
|
+
if (args[0] === "submodule") {
|
|
47
|
+
paths = args.slice(4);
|
|
48
|
+
if (++attempts <= ${failures}) throw new Error("fixture fetch unavailable");
|
|
49
|
+
return Buffer.alloc(0);
|
|
50
|
+
}
|
|
51
|
+
if (args[0] !== "ls-files") throw new Error("unexpected Git operation");
|
|
52
|
+
if (${JSON.stringify(pins)} === "error") throw new Error("fixture index unavailable");
|
|
53
|
+
return paths.map((path, i) => {
|
|
54
|
+
const mode = ${JSON.stringify(pins)} === "invalid" ? "100644" : "160000";
|
|
55
|
+
const stage = ${JSON.stringify(pins)} === "unmerged" ? "2" : "0";
|
|
56
|
+
const sha = String(i + 1).repeat(${JSON.stringify(pins)} === "sha256" ? 64 : 40);
|
|
57
|
+
return mode + " " + sha + " " + stage + "\\t" + path + "\\0";
|
|
58
|
+
}).join("");
|
|
59
|
+
};
|
|
60
|
+
timers.setTimeout = async (ms) => {
|
|
61
|
+
record({ op: "sleep", ms });
|
|
62
|
+
await realSleep(15);
|
|
63
|
+
record({ op: "wake", ms });
|
|
64
|
+
};
|
|
65
|
+
syncBuiltinESMExports();
|
|
66
|
+
`);
|
|
67
|
+
const env = { ...process.env };
|
|
68
|
+
delete env.NODE_OPTIONS;
|
|
69
|
+
delete env.OMO_SKIP_MATERIALIZE;
|
|
70
|
+
delete env.OMO_MATERIALIZE_STRICT;
|
|
71
|
+
if (strictEnv) env.OMO_MATERIALIZE_STRICT = "1";
|
|
72
|
+
if (skip) env.OMO_SKIP_MATERIALIZE = "1";
|
|
73
|
+
const result = spawnSync(process.execPath, ["--import", pathToFileURL(preload).href, join(entry, "materialize-shared-upstreams.mjs"), ...(strict ? ["--strict"] : [])], {
|
|
74
|
+
cwd: root, env, encoding: "utf8", windowsHide: true, timeout: 10000,
|
|
75
|
+
});
|
|
76
|
+
assert.ifError(result.error);
|
|
77
|
+
assert.equal(result.signal, null, result.stderr);
|
|
78
|
+
return { ...result, events: JSON.parse(readFileSync(trace, "utf8")), root };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const operations = (result) => result.events.map(({ op }) => op);
|
|
82
|
+
const attempts = (result) => result.events.filter(({ op }) => op === "submodule");
|
|
83
|
+
const delays = (result) => result.events.filter(({ op }) => op === "sleep").map(({ ms }) => ms);
|
|
84
|
+
|
|
85
|
+
function assertFetchContract(result, count) {
|
|
86
|
+
assert.equal(attempts(result).length, count);
|
|
87
|
+
for (const call of attempts(result)) {
|
|
88
|
+
assert.deepEqual(call.args.slice(0, 4), ["submodule", "update", "--init", "--recursive"]);
|
|
89
|
+
assert.equal(call.args.includes("--remote"), false);
|
|
90
|
+
assert.equal(call.cwd, result.root);
|
|
91
|
+
assert.equal(call.windowsHide, true);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
test("#given reachable pins #when strict materialization runs #then fetch once without backoff", (t) => {
|
|
96
|
+
const r = runCli(t);
|
|
97
|
+
assert.equal(r.status, 0, r.stderr);
|
|
98
|
+
assertFetchContract(r, 1);
|
|
99
|
+
assert.deepEqual(operations(r), ["submodule", "materialize", "stage"]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("#given one failed fetch #when strict materialization retries #then await recovery before staging", (t) => {
|
|
103
|
+
const r = runCli(t, { failures: 1 });
|
|
104
|
+
assert.equal(r.status, 0, r.stderr);
|
|
105
|
+
assertFetchContract(r, 2);
|
|
106
|
+
assert.deepEqual(delays(r), [1000]);
|
|
107
|
+
assert.deepEqual(operations(r), ["submodule", "sleep", "wake", "submodule", "materialize", "stage"]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("#given two failed fetches #when the final attempt recovers #then stop retrying with bounded backoff", (t) => {
|
|
111
|
+
const r = runCli(t, { failures: 2 });
|
|
112
|
+
assert.equal(r.status, 0, r.stderr);
|
|
113
|
+
assertFetchContract(r, 3);
|
|
114
|
+
assert.deepEqual(delays(r), [1000, 2000]);
|
|
115
|
+
assert.equal(r.events.filter(({ op }) => op === "materialize").length, 1);
|
|
116
|
+
assert.equal(r.events.some(({ op }) => op === "ls-files"), false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("#given persistent fetch failure #when strict attempts exhaust #then fail with every path and pinned SHA", (t) => {
|
|
120
|
+
const r = runCli(t, { failures: 9 });
|
|
121
|
+
assert.equal(r.status, 1, r.stderr);
|
|
122
|
+
assertFetchContract(r, 3);
|
|
123
|
+
assert.deepEqual(delays(r), [1000, 2000]);
|
|
124
|
+
assert.equal(r.events.some(({ op }) => op === "materialize" || op === "stage"), false);
|
|
125
|
+
assert.match(r.stderr, /failed after 3 attempts/);
|
|
126
|
+
assert.match(r.stderr, /fixture fetch unavailable/);
|
|
127
|
+
const paths = attempts(r)[0].args.slice(4);
|
|
128
|
+
paths.forEach((path, i) => assert.ok(r.stderr.includes(`${path}@${String(i + 1).repeat(40)}`), r.stderr));
|
|
129
|
+
const lookup = r.events.find(({ op }) => op === "ls-files");
|
|
130
|
+
assert.deepEqual(lookup.args, ["ls-files", "--stage", "-z", "--", ...paths]);
|
|
131
|
+
assert.equal(lookup.cwd, r.root);
|
|
132
|
+
assert.equal(lookup.windowsHide, true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("#given persistent fetch failure #when best-effort attempts exhaust #then warn and materialize once", (t) => {
|
|
136
|
+
const r = runCli(t, { failures: 9, strict: false });
|
|
137
|
+
assert.equal(r.status, 0, r.stderr);
|
|
138
|
+
assertFetchContract(r, 3);
|
|
139
|
+
assert.deepEqual(delays(r), [1000, 2000]);
|
|
140
|
+
assert.deepEqual(operations(r).slice(-3), ["ls-files", "materialize", "stage"]);
|
|
141
|
+
assert.match(r.stderr, /continuing without submodule refresh/);
|
|
142
|
+
assert.equal(r.events.find(({ op }) => op === "materialize").strict, false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("#given an unreadable index #when retries exhaust #then preserve the original fetch failure and name paths", (t) => {
|
|
146
|
+
const r = runCli(t, { failures: 9, pins: "error" });
|
|
147
|
+
assert.equal(r.status, 1, r.stderr);
|
|
148
|
+
assertFetchContract(r, 3);
|
|
149
|
+
assert.match(r.stderr, /fixture fetch unavailable/);
|
|
150
|
+
for (const path of attempts(r)[0].args.slice(4)) assert.ok(r.stderr.includes(`${path}@unknown`), r.stderr);
|
|
151
|
+
assert.match(r.stderr, /pinned SHA unavailable/);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
for (const pins of ["invalid", "unmerged"]) {
|
|
155
|
+
test(`#given ${pins} index entries #when reporting failure #then do not invent resolved gitlink pins`, (t) => {
|
|
156
|
+
const r = runCli(t, { failures: 9, pins });
|
|
157
|
+
assert.equal(r.status, 1, r.stderr);
|
|
158
|
+
for (const path of attempts(r)[0].args.slice(4)) assert.ok(r.stderr.includes(`${path}@unknown`), r.stderr);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
test("#given SHA-256 gitlinks #when retries exhaust #then retain complete pinned object IDs", (t) => {
|
|
163
|
+
const r = runCli(t, { failures: 9, pins: "sha256" });
|
|
164
|
+
assert.equal(r.status, 1, r.stderr);
|
|
165
|
+
attempts(r)[0].args.slice(4).forEach((path, i) => assert.ok(r.stderr.includes(`${path}@${String(i + 1).repeat(64)}`), r.stderr));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("#given strict mode from the environment #when retries exhaust #then fail before downstream materialization", (t) => {
|
|
169
|
+
const r = runCli(t, { failures: 9, strict: false, strictEnv: true });
|
|
170
|
+
assert.equal(r.status, 1, r.stderr);
|
|
171
|
+
assertFetchContract(r, 3);
|
|
172
|
+
assert.equal(r.events.some(({ op }) => op === "materialize"), false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("#given materialization was already performed #when skip is set #then perform no Git or materializer calls", (t) => {
|
|
176
|
+
const r = runCli(t, { failures: 9, skip: true });
|
|
177
|
+
assert.equal(r.status, 0, r.stderr);
|
|
178
|
+
assert.deepEqual(r.events, []);
|
|
179
|
+
});
|
|
@@ -85,7 +85,7 @@ export function removeCodexCompatibilityGuidance(content) {
|
|
|
85
85
|
const ulwExecuteOriginalCompletion = `When all top-level checkboxes in \`## TODOs\` and \`## Final Verification Wave\` are complete:
|
|
86
86
|
|
|
87
87
|
1. Run the plan's final verification commands.
|
|
88
|
-
2. For PR/branch work, finish the lifecycle from the last phase's worktree: sync \`.omo/\` state back to the main repo, create or update the PR, wait for review/verification gates, merge by default unless explicitly opted out, and remove the worktree only after successful merge or explicit handoff.
|
|
88
|
+
2. For PR/branch work, finish the lifecycle from the last phase's worktree: sync \`.omo/\` state back to the main repo, create or update the PR, wait for review/verification gates, merge by default unless explicitly opted out, and remove the worktree only after successful merge or explicit handoff. Finish directory teardown per Task resources and record its receipts.
|
|
89
89
|
3. Remove or mark the Boulder work as completed.
|
|
90
90
|
4. Print an \`ORCHESTRATION COMPLETE\` block with the plan path, verification commands, artifacts, and cleanup receipts.`;
|
|
91
91
|
|
|
@@ -94,7 +94,7 @@ const ulwExecuteCodexCompletion = `When all top-level checkboxes in \`## TODOs\`
|
|
|
94
94
|
1. Run the plan's final verification commands.
|
|
95
95
|
2. Record a self-review in the notepad: re-read the diff, run diagnostics, and capture evidence for every acceptance criterion. Run your own manual QA on the real surface.
|
|
96
96
|
3. Only when the user demanded strict, rigorous, or high-accuracy review, spawn ONE \`lazycodex-gate-reviewer\`; otherwise your self-review is the final verification.
|
|
97
|
-
4. Finish the PR/branch lifecycle from its task-owned worktree: sync \`.omo/\` state back to the main repo, create or update the PR, wait for review/verification gates, merge by default unless explicitly opted out, and remove the worktree only after successful merge or explicit handoff.
|
|
97
|
+
4. Finish the PR/branch lifecycle from its task-owned worktree: sync \`.omo/\` state back to the main repo, create or update the PR, wait for review/verification gates, merge by default unless explicitly opted out, and remove the worktree only after successful merge or explicit handoff. Finish directory teardown per Task resources and record its receipts.
|
|
98
98
|
5. Remove or mark the Boulder work as completed.
|
|
99
99
|
6. Print an \`ORCHESTRATION COMPLETE\` block with the plan path, verification commands, artifacts, and cleanup receipts.`;
|
|
100
100
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
// Platform contract (#9838, same class as #7144 and #8501): plugin scripts run from Codex hooks, which
|
|
8
|
+
// have no console of their own. On Windows a console-subsystem child spawned there without windowsHide
|
|
9
|
+
// gets a FRESH console window; the detached `cmd.exe /c npm.cmd` auto-update got one on every update.
|
|
10
|
+
// This gate walks every plugin script and resolves the node:child_process entry points each file
|
|
11
|
+
// imports, so a new call site, or an exec*/fork call a spawn-only regex would miss, cannot ship
|
|
12
|
+
// unflagged. The flag is inert on posix, so the call text is the only place this is provable off Windows.
|
|
13
|
+
const ENTRY_POINTS = ["spawnSync", "spawn", "execFileSync", "execFile", "execSync", "exec", "fork"];
|
|
14
|
+
|
|
15
|
+
// Intentionally visible processes, or calls whose text cannot carry the literal flag, go here with a
|
|
16
|
+
// reason. None today.
|
|
17
|
+
const ALLOWLIST = [];
|
|
18
|
+
|
|
19
|
+
const scriptsDir = join(dirname(fileURLToPath(import.meta.url)), "..", "scripts");
|
|
20
|
+
|
|
21
|
+
// readdirSync returns backslash-separated entries on win32; matching on raw entries would silently
|
|
22
|
+
// find nothing there while the gate still looked green.
|
|
23
|
+
function toPosix(entry) {
|
|
24
|
+
return entry.replaceAll("\\", "/");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function scriptSources() {
|
|
28
|
+
return readdirSync(scriptsDir, { recursive: true, encoding: "utf8" })
|
|
29
|
+
.map(toPosix)
|
|
30
|
+
.filter((entry) => /\.(mjs|mts|js|cjs|ts)$/.test(entry))
|
|
31
|
+
.sort();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function importedEntryPoints(source) {
|
|
35
|
+
const locals = new Set();
|
|
36
|
+
for (const [, clause] of source.matchAll(/import\s*\{([^}]*)\}\s*from\s*["'](?:node:)?child_process["']/g)) {
|
|
37
|
+
for (const specifier of (clause ?? "").split(",")) {
|
|
38
|
+
const [importedPart, aliasPart] = specifier.split(/\s+as\s+/);
|
|
39
|
+
const imported = (importedPart ?? "").trim().replace(/^type\s+/, "");
|
|
40
|
+
if (!ENTRY_POINTS.includes(imported)) continue;
|
|
41
|
+
locals.add((aliasPart ?? imported).trim());
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return [...locals];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// A namespace, default or require import would hide calls from the named-import resolution above, so
|
|
48
|
+
// the gate refuses those forms instead of silently auditing nothing.
|
|
49
|
+
function unsupportedImportForms(source) {
|
|
50
|
+
return [
|
|
51
|
+
/import\s+\*\s+as\s+\w+\s+from\s*["'](?:node:)?child_process["']/,
|
|
52
|
+
/import\s+\w+\s*(?:,\s*\{[^}]*\})?\s*from\s*["'](?:node:)?child_process["']/,
|
|
53
|
+
/require\(\s*["'](?:node:)?child_process["']\s*\)/,
|
|
54
|
+
].filter((pattern) => pattern.test(source)).length;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function callText(source, openingParen) {
|
|
58
|
+
let depth = 0;
|
|
59
|
+
for (let index = openingParen; index < source.length; index += 1) {
|
|
60
|
+
if (source[index] === "(") depth += 1;
|
|
61
|
+
else if (source[index] === ")") {
|
|
62
|
+
depth -= 1;
|
|
63
|
+
if (depth === 0) return source.slice(openingParen, index + 1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return source.slice(openingParen);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Comment prose names these functions too, so only the code spelling (identifier then `(`) on a
|
|
70
|
+
// non-comment line counts as a call.
|
|
71
|
+
function isCommentLine(source, offset) {
|
|
72
|
+
const lineStart = source.lastIndexOf("\n", offset - 1) + 1;
|
|
73
|
+
const lead = source.slice(lineStart, offset).trimStart();
|
|
74
|
+
return lead.startsWith("//") || lead.startsWith("*") || lead.startsWith("/*");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function collectCalls(file, source) {
|
|
78
|
+
const calls = [];
|
|
79
|
+
for (const local of importedEntryPoints(source)) {
|
|
80
|
+
for (const match of source.matchAll(new RegExp(String.raw`(?<![\w$.])${local}\(`, "g"))) {
|
|
81
|
+
if (isCommentLine(source, match.index)) continue;
|
|
82
|
+
calls.push({
|
|
83
|
+
file,
|
|
84
|
+
line: source.slice(0, match.index).split("\n").length,
|
|
85
|
+
callee: local,
|
|
86
|
+
text: `${local}${callText(source, match.index + match[0].length - 1)}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return calls;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function auditedCalls() {
|
|
94
|
+
return scriptSources().flatMap((file) => collectCalls(file, readFileSync(join(scriptsDir, file), "utf8")));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isAllowlisted(call) {
|
|
98
|
+
return ALLOWLIST.some((entry) => entry.file === call.file && entry.callee === call.callee);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
test("#given every plugin script child_process call #when inspected #then each passes windowsHide: true or is allowlisted with a reason", () => {
|
|
102
|
+
const offenders = auditedCalls()
|
|
103
|
+
.filter((call) => !/windowsHide:\s*true/.test(call.text) && !isAllowlisted(call))
|
|
104
|
+
.map((call) => `${call.file}:${call.line} ${call.callee}`);
|
|
105
|
+
|
|
106
|
+
assert.deepEqual(offenders, []);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("#given every plugin script #when its child_process import is read #then it uses a form the gate can audit", () => {
|
|
110
|
+
const unauditable = scriptSources().filter((file) => unsupportedImportForms(readFileSync(join(scriptsDir, file), "utf8")) > 0);
|
|
111
|
+
|
|
112
|
+
assert.deepEqual(unauditable, []);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("#given the audit #when it runs #then it still reaches the detached auto-update spawn it exists for", () => {
|
|
116
|
+
const autoUpdate = auditedCalls().filter((call) => call.file === "auto-update.mjs" && call.callee === "spawn");
|
|
117
|
+
|
|
118
|
+
assert.ok(autoUpdate.length > 0, "the gate no longer sees auto-update.mjs spawn calls");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("#given the allowlist #when checked #then every entry has a reason and still matches a real call", () => {
|
|
122
|
+
const calls = auditedCalls();
|
|
123
|
+
const stale = ALLOWLIST.filter(
|
|
124
|
+
(entry) => !entry.reason || !calls.some((call) => call.file === entry.file && call.callee === entry.callee),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
assert.deepEqual(stale, []);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("#given an aliased execFile import without the flag #when audited #then the call is reported, not skipped", () => {
|
|
131
|
+
const source = ['import { execFile as run } from "node:child_process";', 'run("powershell.exe", args, { encoding: "utf8" }, callback);'].join("\n");
|
|
132
|
+
|
|
133
|
+
const calls = collectCalls("fixture.mjs", source);
|
|
134
|
+
|
|
135
|
+
assert.deepEqual(
|
|
136
|
+
calls.map((call) => call.callee),
|
|
137
|
+
["run"],
|
|
138
|
+
);
|
|
139
|
+
assert.equal(/windowsHide:\s*true/.test(calls[0].text), false);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("#given a namespace child_process import #when checked #then it is refused as unauditable", () => {
|
|
143
|
+
assert.equal(unsupportedImportForms('import * as cp from "node:child_process";'), 1);
|
|
144
|
+
assert.equal(unsupportedImportForms('const cp = require("child_process");'), 1);
|
|
145
|
+
assert.equal(unsupportedImportForms('import { spawn } from "node:child_process";'), 0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("#given a win32 directory entry #when normalized #then it matches under POSIX separators", () => {
|
|
149
|
+
assert.equal(toPosix("lib\\auto-update.mjs"), "lib/auto-update.mjs");
|
|
150
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// omo-codex-install:
|
|
2
|
+
// omo-codex-install:643bf27c152edbd2cb2348ec12962f6fa9c214781d3435eac8f5f2bba2e2a8e5:d4eeade8402959d812046ab47a22010046503bc95f9fa850a38fd68254b0b542
|
|
3
3
|
var __esm = (fn, res, err) => () => {
|
|
4
4
|
if (fn)
|
|
5
5
|
try {
|
|
@@ -9984,7 +9984,7 @@ var package_default;
|
|
|
9984
9984
|
var init_package = __esm(() => {
|
|
9985
9985
|
package_default = {
|
|
9986
9986
|
name: "@oh-my-opencode/omo-codex",
|
|
9987
|
-
version: "5.1.
|
|
9987
|
+
version: "5.1.32",
|
|
9988
9988
|
type: "module",
|
|
9989
9989
|
private: true,
|
|
9990
9990
|
description: "Codex harness adapter for oh-my-openagent. Vendored Codex plugin namespace (omo) + TypeScript installer + telemetry.",
|
|
@@ -10239,6 +10239,9 @@ function resolveStdio(options) {
|
|
|
10239
10239
|
}
|
|
10240
10240
|
return [options.stdin ?? "ignore", options.stdout ?? "pipe", options.stderr ?? "inherit"];
|
|
10241
10241
|
}
|
|
10242
|
+
function createBunSpawnOptions(options) {
|
|
10243
|
+
return { ...options, windowsHide: true };
|
|
10244
|
+
}
|
|
10242
10245
|
function createNodeSpawnOptions(options, platform = process.platform) {
|
|
10243
10246
|
const nodeOptions = {
|
|
10244
10247
|
stdio: resolveStdio(options),
|
|
@@ -10327,7 +10330,7 @@ function spawn(cmdOrOpts, opts) {
|
|
|
10327
10330
|
const { cmd, opts: options } = resolveCommand(cmdOrOpts, opts);
|
|
10328
10331
|
const bun = getBunRuntime();
|
|
10329
10332
|
if (bun)
|
|
10330
|
-
return wrapBunProcess(bun.spawn(cmd, options));
|
|
10333
|
+
return wrapBunProcess(bun.spawn(cmd, createBunSpawnOptions(options)));
|
|
10331
10334
|
const [bin, ...args] = cmd;
|
|
10332
10335
|
if (!bin)
|
|
10333
10336
|
throw new Error("spawn requires a command");
|
|
@@ -10409,7 +10412,7 @@ function isKnownNonGitBashLauncher(path) {
|
|
|
10409
10412
|
}
|
|
10410
10413
|
function whereCommand(command) {
|
|
10411
10414
|
try {
|
|
10412
|
-
return execFileSync("where", [command], { encoding: "utf8" }).split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
10415
|
+
return execFileSync("where", [command], { encoding: "utf8", windowsHide: true }).split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
10413
10416
|
} catch (error) {
|
|
10414
10417
|
if (error instanceof Error)
|
|
10415
10418
|
return [];
|
|
@@ -19466,7 +19469,8 @@ var OmoMemoryNudgeSchema = object({
|
|
|
19466
19469
|
}).strict();
|
|
19467
19470
|
var OmoMemoryFactsSchema = object({
|
|
19468
19471
|
enabled: boolean2().default(true),
|
|
19469
|
-
debounce_settles: number2().int().min(1).default(4)
|
|
19472
|
+
debounce_settles: number2().int().min(1).default(4),
|
|
19473
|
+
model: string2().min(1).optional()
|
|
19470
19474
|
}).strict();
|
|
19471
19475
|
var OmoMemoryDreamSchema = object({
|
|
19472
19476
|
enabled: boolean2().default(true),
|
|
@@ -19528,7 +19532,8 @@ var OmoMemoryNudgeLayerSchema = object({
|
|
|
19528
19532
|
}).strict();
|
|
19529
19533
|
var OmoMemoryFactsLayerSchema = object({
|
|
19530
19534
|
enabled: boolean2().optional(),
|
|
19531
|
-
debounce_settles: number2().int().min(1).optional()
|
|
19535
|
+
debounce_settles: number2().int().min(1).optional(),
|
|
19536
|
+
model: string2().min(1).optional()
|
|
19532
19537
|
}).strict();
|
|
19533
19538
|
var OmoMemoryDreamLayerSchema = object({
|
|
19534
19539
|
enabled: boolean2().optional(),
|
|
@@ -105,7 +105,11 @@ degrades color and wide-glyph width. In this repo:
|
|
|
105
105
|
stream). Outside this repo, capture equivalent browser-rendered terminal
|
|
106
106
|
evidence: screenshot + plain transcript + cleanup receipt.
|
|
107
107
|
|
|
108
|
-
# Bootstrap (
|
|
108
|
+
# Bootstrap (COMPLETE EVERY SECTION BEFORE ANY OTHER WORK — NO SKIPPING)
|
|
109
|
+
|
|
110
|
+
## Task resources
|
|
111
|
+
Before the first task command, create one `RUN_TMP=$(mktemp -d)` per task, tracking its paths under section 3 immediately. Export `TMPDIR="$RUN_TMP" NODE_COMPILE_CACHE="$RUN_TMP/node-compile-cache" BUN_RUNTIME_TRANSPILER_CACHE_PATH="$RUN_TMP/bun-transpiler"` for every command; pass the same environment explicitly to children that do not inherit it. Give independent child tasks their own run roots.
|
|
112
|
+
At task end, after owned processes exit, remove only owned directories, including runtime caches. Never wipe shared caches; retain worktrees still needed by open PRs and any parent directory containing them.
|
|
109
113
|
|
|
110
114
|
## 0. Survey the skills, gather context, then size the work
|
|
111
115
|
First, survey the loaded skill list and read the description of each
|
|
@@ -199,7 +203,7 @@ steps.
|
|
|
199
203
|
|
|
200
204
|
## 3. Register obsessive todos via `update_plan`
|
|
201
205
|
The todo tool is Codex `update_plan` — your live, user-visible
|
|
202
|
-
checklist. Translate every action from the plan into one `update_plan`
|
|
206
|
+
checklist. Keep resource-cleanup todos registered earlier. Translate every action from the plan into one `update_plan`
|
|
203
207
|
step — one step per atomic work unit: an edit plus its verification, a
|
|
204
208
|
QA scenario run, a teardown. Keep each step small enough to finish
|
|
205
209
|
within a few tool calls.
|
|
@@ -211,6 +215,9 @@ behind reality. Add newly discovered steps the moment they surface
|
|
|
211
215
|
instead of waiting for the next pass. Step text encodes WHERE / WHY
|
|
212
216
|
(which criterion it advances) / HOW / VERIFY:
|
|
213
217
|
`path: <action> for <criterion> — verify by <check>`.
|
|
218
|
+
ALWAYS add a cleanup todo AT CREATION for each worktree, cache directory (including
|
|
219
|
+
the run root), and QA resource (PID, tmux session, browser context, port, container,
|
|
220
|
+
script or temp path); complete it only after a read-back teardown receipt.
|
|
214
221
|
|
|
215
222
|
GOOD pair (ordered):
|
|
216
223
|
`test/foo.test.ts: read the validateEmail cases for criterion 2 — verify by noting intent / coverage / pass in the notepad`
|
|
@@ -273,19 +280,15 @@ Until every success criterion PASSES with its evidence captured:
|
|
|
273
280
|
above; auxiliary surface for CLI- or data-shaped criteria), end to
|
|
274
281
|
end, yourself, plus the step-2 tests; a reproduction now passes.
|
|
275
282
|
Paste the artifact path into the notepad.
|
|
276
|
-
5. CLEANUP (PAIRED — NEVER SKIP):
|
|
277
|
-
|
|
278
|
-
`
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
sockets / files / dirs (`rm -rf` the `mktemp` paths), QA-only env
|
|
286
|
-
vars. Append a one-line cleanup receipt to the notepad next to the
|
|
287
|
-
artifact, e.g. `cleanup: killed 12345; tmux kill-session ulw-qa-foo;
|
|
288
|
-
rm -rf /tmp/ulw.aB12cD`. No receipt → criterion stays in_progress.
|
|
283
|
+
5. CLEANUP (PAIRED — NEVER SKIP):
|
|
284
|
+
Before recording QA, tear down owned server PIDs (`kill <pid>`; verify
|
|
285
|
+
`kill -0` fails), tmux sessions (`tmux kill-session -t ulw-qa-<criterion>`; confirm `tmux ls`),
|
|
286
|
+
browsers / sessions (`browser.close()` / `session.stop()`), containers (`docker rm -f`), ports
|
|
287
|
+
(`lsof -i :<port>` empty), QA scripts, temp paths and QA-only env vars.
|
|
288
|
+
Remove QA temp paths inside `$RUN_TMP` per criterion; remove the run root
|
|
289
|
+
and eligible worktrees at task end under Task resources.
|
|
290
|
+
Append the read-back receipt beside the artifact in the notepad.
|
|
291
|
+
No receipt → criterion stays in_progress.
|
|
289
292
|
6. Verify: LSP diagnostics clean on changed files; no test skipped or
|
|
290
293
|
xfail-ed this turn.
|
|
291
294
|
7. Mark completed. Append non-obvious findings / learnings.
|
|
@@ -11,7 +11,7 @@ One library, two engines. omowright ships inside this skill; choose the engine b
|
|
|
11
11
|
|---|---|---|
|
|
12
12
|
| A site the user is signed into, their open tabs, a form, a click-through, a screenshot, web QA, an extension popup | **attached** — the user's own browser through BrowserSkill | `connectBrowserSkill()` |
|
|
13
13
|
| A throwaway profile, bot-scoring evasion, a CAPTCHA, network interception, a QA flight trace, coordinate control, headless runs | **owned** — a browser your code launches | `connectPipe()` / `connectCloakProfile()` — [references/owned-engine/README.md](references/owned-engine/README.md) |
|
|
14
|
-
|
|
|
14
|
+
| A 403/WAF host the attached engine cannot open, a platform with a native reader, an archive snapshot | neither | the `ultimate-browsing` skill |
|
|
15
15
|
|
|
16
16
|
**Attached is the default,** because it is the only engine carrying the user's logins and the only
|
|
17
17
|
one where a human is a single call away. Never substitute one engine for the other silently: if
|