lazycodex-ai 5.0.0-beta.8 → 5.0.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +34 -31
- package/dist/cli/run/on-complete-hook.d.ts +2 -0
- package/dist/cli-node/index.js +34 -31
- package/package.json +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +2 -2
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +2 -2
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/codex-hook.test.ts +3 -17
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/test/bundled-rules-priority.test.ts +11 -16
- package/packages/omo-codex/plugin/components/rules/test/bundled-rules.test.ts +16 -23
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-budget.test.ts +9 -7
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-context.test.ts +0 -6
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-dedup.test.ts +6 -4
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-directive.test.ts +12 -9
- package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +28 -37
- package/packages/omo-codex/plugin/components/rules/test/formatter.test.ts +37 -69
- package/packages/omo-codex/plugin/components/rules/test/hook-output.test.ts +2 -3
- package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +1 -15
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/test/cli.test.ts +0 -3
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +2 -16
- 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/test/thread-title-hook.test.ts +3 -9
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +0 -136
- package/packages/omo-codex/plugin/components/ultrawork/test/skill-pointer.test.ts +0 -2
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-continuation.test.ts +0 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-hook.test.ts +0 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +2 -35
- package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +4 -5
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +20 -20
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/ATTRIBUTION.md +35 -8
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +110 -0
- package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +19 -173
- package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +4 -24
- package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +175 -13
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +78 -2
- package/packages/omo-codex/plugin/test/auto-update-release-notes.test.mjs +19 -33
- package/packages/omo-codex/plugin/test/lcx-contribute-bug-fix-template.test.mjs +21 -27
- package/packages/omo-codex/plugin/test/scaffold-plan.test.mjs +0 -36
- package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +101 -0
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +1 -119
- package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +0 -40
- package/packages/omo-codex/plugin/test/teammode-communication.test.mjs +6 -62
- package/packages/omo-codex/plugin/test/teammode-thread-links.test.mjs +3 -36
- package/packages/omo-codex/plugin/test/teammode-transport.test.mjs +0 -44
- package/packages/omo-codex/plugin/test/teammode-worktree.test.mjs +2 -6
- package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +0 -3
- package/packages/omo-codex/plugin/test/ulw-plan-review-state-contract.test.mjs +0 -3
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
- package/packages/shared-skills/skills/ultimate-browsing/ATTRIBUTION.md +35 -8
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +110 -0
- package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +0 -92
- package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +0 -314
- package/packages/omo-codex/plugin/test/ulw-plan-scope-contract.test.mjs +0 -24
|
@@ -2,6 +2,116 @@
|
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-08-10 / 38d268995
|
|
4
4
|
|
|
5
|
+
## UPSTREAM BASELINE AND VERSION POLICY
|
|
6
|
+
|
|
7
|
+
**READ THIS BEFORE TOUCHING `engine/**` OR PROPOSING AN UPSTREAM SYNC.**
|
|
8
|
+
|
|
9
|
+
This engine is NOT project-original code. It is a vendored-and-modified snapshot of
|
|
10
|
+
[fivetaku/insane-search](https://github.com/fivetaku/insane-search), and the version
|
|
11
|
+
we run on is a deliberate choice, not an accident of neglect.
|
|
12
|
+
|
|
13
|
+
### The pin
|
|
14
|
+
|
|
15
|
+
| Fact | Value |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Upstream project | `https://github.com/fivetaku/insane-search` |
|
|
18
|
+
| Vendoring commit | `a4e4ed797` (2026-06-21) `feat(ultimate-browsing): vendor insane-search engine (junk-excluded)` |
|
|
19
|
+
| De-personalization | `4743199a5` (2026-06-21) |
|
|
20
|
+
| Pinned upstream baseline | upstream state as of 2026-06-21, **pre-0.7.0** (0.7.0 is dated 2026-06-22) |
|
|
21
|
+
| Re-vendors since | none — every later change here is ours |
|
|
22
|
+
|
|
23
|
+
We intentionally track a **pinned baseline plus local divergence**, not upstream HEAD.
|
|
24
|
+
There is no submodule and no automated drift check for this engine (unlike the
|
|
25
|
+
`frontend` skill's upstream submodules): the vendored files ARE the source of truth,
|
|
26
|
+
and upstream is a reference we port FROM, deliberately, file by file.
|
|
27
|
+
|
|
28
|
+
### Why we do not blind-rebase onto upstream HEAD
|
|
29
|
+
|
|
30
|
+
1. **Upstream reset its public history.** On 2026-08-06 upstream published a single
|
|
31
|
+
squashed commit, `019ee16 refactor: reset public history at 0.14.0`, discarding the
|
|
32
|
+
prior public history through 0.13.x. There is no upstream commit graph to rebase onto and no
|
|
33
|
+
way to cherry-pick an individual upstream change by sha — only whole-file diffing
|
|
34
|
+
against a moving HEAD.
|
|
35
|
+
2. **Upstream 0.14.0 REMOVED capability.** The endpoint-mining / internal-API
|
|
36
|
+
auto-derivation / site-recipe subsystems that upstream carried publicly between
|
|
37
|
+
0.12.0 and 0.14.0 are gone from upstream HEAD. Syncing to HEAD is therefore not
|
|
38
|
+
strictly an upgrade: parts of it are a downgrade relative to the intermediate
|
|
39
|
+
versions, and none of it is recoverable from the reset history.
|
|
40
|
+
3. **Our tree diverged on purpose.** The KEEP list below is functionality upstream
|
|
41
|
+
never had. A wholesale overwrite with upstream HEAD would silently delete it.
|
|
42
|
+
4. **Different threat model.** Our engine ships inside a published npm package and a
|
|
43
|
+
public marketplace mirror, under a CI no-site-name gate and a de-personalization
|
|
44
|
+
deny-list. Upstream carries neither constraint, so upstream code is not
|
|
45
|
+
drop-in-shippable here.
|
|
46
|
+
|
|
47
|
+
### KEEP — our divergences a re-vendor MUST NOT regress
|
|
48
|
+
|
|
49
|
+
These exist only in our tree. Any upstream sync that removes or bypasses one of them
|
|
50
|
+
is a regression, not an upgrade:
|
|
51
|
+
|
|
52
|
+
- **Phase 2.5 surrogate retrieval** (`surrogate.py`, `surrogates.yaml`) — archive /
|
|
53
|
+
reader / proxy routes tried before paying for a browser spin-up, with per-entry
|
|
54
|
+
`last_verified` staleness handling and `--allow-proxy` gating.
|
|
55
|
+
- **Provenance / trust contract** (`result_schema.py`) — `Provenance` and `Trust`
|
|
56
|
+
literals on every result, so a snapshot can never be reported as the live page.
|
|
57
|
+
- **Surrogate dead-end validation** (L1.5 in `validators.py`) — interstitial titles and
|
|
58
|
+
AMP-style redirect stubs rejected instead of returned as content.
|
|
59
|
+
- **The no-site-name rule and its CI gate** (`bias_check.py`) — zero hard-coded site
|
|
60
|
+
names, brands, or target domains in `engine/**`.
|
|
61
|
+
- **Module split of the fetch chain** — `curl_probe` / `referers` / `url_transforms` /
|
|
62
|
+
`waf_detector` / `validators` / `executor` / `summary` as separate modules rather than
|
|
63
|
+
one monolith.
|
|
64
|
+
- **The Python test suite** under `engine/tests/` with its HTML/JSON fixtures.
|
|
65
|
+
- **De-personalization** — no personal absolute paths, no personal auth token literals,
|
|
66
|
+
no personal browser choice; enforced by `depersonalization-gate.test.ts`.
|
|
67
|
+
- **Skill-level layering** — the engine is Tier 1 under a router that also owns Tier 1.5
|
|
68
|
+
(agent-reach) and Tier 2 (CloakBrowser + agent-browser). Upstream has no such tiering.
|
|
69
|
+
|
|
70
|
+
### WANT — upstream improvements worth porting forward
|
|
71
|
+
|
|
72
|
+
Our snapshot predates these; they are wanted, and each must be ported as a reviewed,
|
|
73
|
+
site-agnostic change that preserves every KEEP item above. Port individually; never as
|
|
74
|
+
a tree overwrite:
|
|
75
|
+
|
|
76
|
+
- **Content quality**: dedicated markdown conversion of fetched HTML, main-content
|
|
77
|
+
extraction, PDF text extraction, and JSON-LD rescue when the HTML body is thin.
|
|
78
|
+
- **Transient-failure retry** and **render-merge** of statically fetched HTML with the
|
|
79
|
+
browser-rendered DOM.
|
|
80
|
+
- **Differential block classification** — distinguishing a bot-detection block from an
|
|
81
|
+
infrastructure or authentication failure, instead of collapsing both into `challenge`.
|
|
82
|
+
- **Additional stealth fetch backends** beyond the current Playwright templates, and
|
|
83
|
+
additional WAF vendor profiles.
|
|
84
|
+
- **Per-host route learning** — remembering which route succeeded for a host, with a TTL
|
|
85
|
+
and a bounded store. Must stay runtime state, never committed site knowledge (R4).
|
|
86
|
+
- **Engine-level Phase 0 routing** — the official-public-API preference is currently only
|
|
87
|
+
a documented rule (R5) the agent can skip; upstream moved it into code so it cannot be
|
|
88
|
+
skipped. Worth adopting.
|
|
89
|
+
|
|
90
|
+
### OUT OF SCOPE
|
|
91
|
+
|
|
92
|
+
- **The removed upstream endpoint-mining / internal-API auto-derivation / site-recipe
|
|
93
|
+
subsystems.** They are absent from upstream HEAD and are not reconstructed here. They
|
|
94
|
+
also sit against R3/R4 and R7's anti-bias rule: discovered internal endpoints are
|
|
95
|
+
runtime findings, never committed engine knowledge.
|
|
96
|
+
- **Any upstream code carrying site-specific selectors, domains, or brand names.** It
|
|
97
|
+
fails `bias_check.py` at the door; re-derive it site-agnostically or leave it out.
|
|
98
|
+
- **Automated upstream tracking.** No submodule, no drift check, no auto-bump. Syncing is
|
|
99
|
+
a deliberate, reviewed, human-initiated act.
|
|
100
|
+
|
|
101
|
+
### THE SYNC RULE
|
|
102
|
+
|
|
103
|
+
Any future upstream sync preserves BOTH sides. Concretely:
|
|
104
|
+
|
|
105
|
+
1. Diff the specific upstream capability you want against our tree — do not overwrite
|
|
106
|
+
files wholesale, and never `git checkout` upstream over `engine/`.
|
|
107
|
+
2. Port it as its own reviewed change, keeping every KEEP item intact.
|
|
108
|
+
3. Re-run `python3 engine/bias_check.py` and the `engine/tests/` suite; a port that
|
|
109
|
+
introduces a site name or breaks a fixture does not ship.
|
|
110
|
+
4. Update the pin table above (baseline, date, what was ported) in the same change, plus
|
|
111
|
+
the provenance section of [`../ATTRIBUTION.md`](../ATTRIBUTION.md).
|
|
112
|
+
5. If a port must drop a KEEP item, say so explicitly in the PR and get it agreed first —
|
|
113
|
+
silent regressions of the KEEP list are the failure mode this policy exists to prevent.
|
|
114
|
+
|
|
5
115
|
## OVERVIEW
|
|
6
116
|
|
|
7
117
|
A 17-module Python package embedded in the `ultimate-browsing` skill: a site-agnostic fetch chain that escalates from a cheap curl probe to a real browser, with declarative WAF and surrogate registries. Not "optional scripts" — it has its own CLI entry (`python3 -m engine URL`), two YAML config schemas, a 4-file test suite, and a standalone CI guard. Package exports (`__init__.py`): `fetch`, `FetchResult`, `Attempt`, `Verdict`, `ValidationResult`, `validate`, `CHALLENGE_MARKERS`, `detect`, `TRANSFORMS`, `apply_transform`.
|
|
@@ -15,103 +15,25 @@ const agentSchemaKeys = new Set([
|
|
|
15
15
|
"developer_instructions",
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
|
+
// `includes` entries below are machine-consumed tokens only: EVIDENCE_RECORDED is the sentinel the
|
|
19
|
+
// lazycodex-executor-verify SubagentStop hook greps from worker messages; recommendation, blockers,
|
|
20
|
+
// codeQualityStatus, not_applicable, surfaceEvidence, and adversarialCases are fields the ulw-loop
|
|
21
|
+
// quality gate parses out of reviewer/QA reports; <attemptDir>/<goalId>-code-review.md and
|
|
22
|
+
// -manual-qa.md are the artifact names the ulw-loop spawn guard requires before a gate-reviewer
|
|
23
|
+
// spawn; currentAttemptDir is a field of `ulw-loop status --json`.
|
|
18
24
|
const lazycodexAgentInvariants = new Map([
|
|
19
|
-
[
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
],
|
|
27
|
-
[
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
effort: "low",
|
|
32
|
-
includes: [/Read-only/, /SHA-pinned GitHub permalink/, /external/],
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
[
|
|
36
|
-
"metis.toml",
|
|
37
|
-
{
|
|
38
|
-
model: "gpt-5.6-sol",
|
|
39
|
-
effort: "high",
|
|
40
|
-
includes: [/pre-planning analyst/i, /contradictions/, /Read-only/],
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
[
|
|
44
|
-
"momus.toml",
|
|
45
|
-
{
|
|
46
|
-
model: "gpt-5.6-terra",
|
|
47
|
-
effort: "high",
|
|
48
|
-
includes: [/plan reviewer/i, /OKAY, ITERATE, or REJECT/, /Read-only/],
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
[
|
|
52
|
-
"plan.toml",
|
|
53
|
-
{
|
|
54
|
-
model: "gpt-5.6-sol",
|
|
55
|
-
effort: "high",
|
|
56
|
-
includes: [/strategic planning consultant/i, /\.omo\/plans\/<slug>\.md/, /never implements/i],
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
[
|
|
60
|
-
"lazycodex-worker-low.toml",
|
|
61
|
-
{
|
|
62
|
-
model: "gpt-5.6-luna",
|
|
63
|
-
effort: "high",
|
|
64
|
-
includes: [/EVIDENCE_RECORDED: <path>/, /low-difficulty/i, /smallest correct change/i],
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
[
|
|
68
|
-
"lazycodex-worker-medium.toml",
|
|
69
|
-
{
|
|
70
|
-
model: "gpt-5.6-terra",
|
|
71
|
-
effort: "high",
|
|
72
|
-
includes: [/EVIDENCE_RECORDED: <path>/, /medium-difficulty/i, /smallest correct change/i],
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
[
|
|
76
|
-
"lazycodex-worker-high.toml",
|
|
77
|
-
{
|
|
78
|
-
model: "gpt-5.6-sol",
|
|
79
|
-
effort: "medium",
|
|
80
|
-
includes: [/EVIDENCE_RECORDED: <path>/, /high-difficulty/i, /smallest correct change/i],
|
|
81
|
-
},
|
|
82
|
-
],
|
|
83
|
-
[
|
|
84
|
-
"lazycodex-clone-fidelity-reviewer.toml",
|
|
85
|
-
{
|
|
86
|
-
model: "gpt-5.6-terra",
|
|
87
|
-
effort: "high",
|
|
88
|
-
includes: [/recommendation/, /blockers/, /\.omo\/evidence\/<goal>-clone-fidelity\.md/],
|
|
89
|
-
},
|
|
90
|
-
],
|
|
91
|
-
[
|
|
92
|
-
"lazycodex-code-reviewer.toml",
|
|
93
|
-
{
|
|
94
|
-
model: "gpt-5.6-terra",
|
|
95
|
-
effort: "medium",
|
|
96
|
-
includes: [/codeQualityStatus/, /recommendation/, /<attemptDir>\/<goalId>-code-review\.md/, /currentAttemptDir/],
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
[
|
|
100
|
-
"lazycodex-qa-executor.toml",
|
|
101
|
-
{
|
|
102
|
-
model: "gpt-5.6-luna",
|
|
103
|
-
effort: "high",
|
|
104
|
-
includes: [/not_applicable/, /surfaceEvidence/, /adversarialCases/, /<attemptDir>\/<goalId>-manual-qa\.md/],
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
[
|
|
108
|
-
"lazycodex-gate-reviewer.toml",
|
|
109
|
-
{
|
|
110
|
-
model: "gpt-5.6-sol",
|
|
111
|
-
effort: "low",
|
|
112
|
-
includes: [/APPROVE\/REJECT/, /blockers/, /<attemptDir>\/<goalId>-gate-review\.md/, /currentAttemptDir/],
|
|
113
|
-
},
|
|
114
|
-
],
|
|
25
|
+
["explorer.toml", { model: "gpt-5.6-luna", effort: "low" }],
|
|
26
|
+
["librarian.toml", { model: "gpt-5.6-luna", effort: "low" }],
|
|
27
|
+
["metis.toml", { model: "gpt-5.6-sol", effort: "high" }],
|
|
28
|
+
["momus.toml", { model: "gpt-5.6-terra", effort: "high" }],
|
|
29
|
+
["plan.toml", { model: "gpt-5.6-sol", effort: "high" }],
|
|
30
|
+
["lazycodex-worker-low.toml", { model: "gpt-5.6-luna", effort: "high", includes: [/EVIDENCE_RECORDED: <path>/] }],
|
|
31
|
+
["lazycodex-worker-medium.toml", { model: "gpt-5.6-terra", effort: "high", includes: [/EVIDENCE_RECORDED: <path>/] }],
|
|
32
|
+
["lazycodex-worker-high.toml", { model: "gpt-5.6-sol", effort: "medium", includes: [/EVIDENCE_RECORDED: <path>/] }],
|
|
33
|
+
["lazycodex-clone-fidelity-reviewer.toml", { model: "gpt-5.6-terra", effort: "high", includes: [/recommendation/, /blockers/] }],
|
|
34
|
+
["lazycodex-code-reviewer.toml", { model: "gpt-5.6-terra", effort: "medium", includes: [/codeQualityStatus/, /recommendation/, /<attemptDir>\/<goalId>-code-review\.md/, /currentAttemptDir/] }],
|
|
35
|
+
["lazycodex-qa-executor.toml", { model: "gpt-5.6-luna", effort: "high", includes: [/not_applicable/, /surfaceEvidence/, /adversarialCases/, /<attemptDir>\/<goalId>-manual-qa\.md/] }],
|
|
36
|
+
["lazycodex-gate-reviewer.toml", { model: "gpt-5.6-sol", effort: "low", includes: [/blockers/, /currentAttemptDir/] }],
|
|
115
37
|
]);
|
|
116
38
|
|
|
117
39
|
const externalSourceTokenPattern = new RegExp(
|
|
@@ -156,7 +78,6 @@ test("#given bundled Codex agents #when components/ultrawork/agents directory is
|
|
|
156
78
|
}
|
|
157
79
|
}
|
|
158
80
|
});
|
|
159
|
-
|
|
160
81
|
test("#given bundled agent TOMLs #when nickname_candidates are inspected #then they use only the codex-accepted charset", async () => {
|
|
161
82
|
// given: codex_app_server ignores a role whose nickname has characters outside
|
|
162
83
|
// ASCII letters, digits, spaces, hyphens, underscores (observed live in task-15 QA)
|
|
@@ -174,16 +95,6 @@ test("#given bundled agent TOMLs #when nickname_candidates are inspected #then t
|
|
|
174
95
|
}
|
|
175
96
|
});
|
|
176
97
|
|
|
177
|
-
test("#given planner agent prompt #when inspected #then generated artifacts stay under .omo", async () => {
|
|
178
|
-
const prompt = await readFile(join(root, "components", "ultrawork", "agents", "plan.toml"), "utf8");
|
|
179
|
-
|
|
180
|
-
assert.match(prompt, /\.omo\/plans\/<slug>\.md/);
|
|
181
|
-
assert.match(prompt, /<attemptDir>\/task-<N>-<slug>\.<ext>/);
|
|
182
|
-
assert.match(prompt, /\.omo\/evidence\/ulw\/<session>\/<goalId>\/a<attempt>/);
|
|
183
|
-
assert.doesNotMatch(prompt, /(?<!\.omo\/)plans\/<slug>\.md/);
|
|
184
|
-
assert.doesNotMatch(prompt, /(?<!\.omo\/)evidence\/task-/);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
98
|
test("#given lazycodex agent prompts #when inspected #then each role pins model effort and evidence discipline", async () => {
|
|
188
99
|
const agentsDir = join(root, "components", "ultrawork", "agents");
|
|
189
100
|
|
|
@@ -197,73 +108,8 @@ test("#given lazycodex agent prompts #when inspected #then each role pins model
|
|
|
197
108
|
assert.doesNotMatch(prompt, /^blocking\s*=/m);
|
|
198
109
|
assert.doesNotMatch(prompt, externalSourceTokenPattern);
|
|
199
110
|
|
|
200
|
-
for (const pattern of invariant.includes) {
|
|
111
|
+
for (const pattern of invariant.includes ?? []) {
|
|
201
112
|
assert.match(prompt, pattern, `${fileName} must include ${pattern}`);
|
|
202
113
|
}
|
|
203
114
|
}
|
|
204
115
|
});
|
|
205
|
-
|
|
206
|
-
test("#given LazyCodex reviewer prompts #when inspected #then anti-slop review coverage is required", async () => {
|
|
207
|
-
const agentsDir = join(root, "components", "ultrawork", "agents");
|
|
208
|
-
const codeReviewer = await readFile(join(agentsDir, "lazycodex-code-reviewer.toml"), "utf8");
|
|
209
|
-
const gateReviewer = await readFile(join(agentsDir, "lazycodex-gate-reviewer.toml"), "utf8");
|
|
210
|
-
|
|
211
|
-
assert.match(codeReviewer, /remove-ai-slops/);
|
|
212
|
-
assert.match(codeReviewer, /programming/);
|
|
213
|
-
assert.match(codeReviewer, /load or consult/);
|
|
214
|
-
assert.match(codeReviewer, /documented criteria/);
|
|
215
|
-
assert.match(codeReviewer, /violates either skill perspective/);
|
|
216
|
-
assert.match(codeReviewer, /overfit\/slop review pass/);
|
|
217
|
-
assert.match(codeReviewer, /deletion-only tests/);
|
|
218
|
-
assert.match(codeReviewer, /tests that merely verify a requested removal/);
|
|
219
|
-
assert.match(codeReviewer, /tautological tests/);
|
|
220
|
-
assert.match(codeReviewer, /mirror implementation constants/);
|
|
221
|
-
assert.match(codeReviewer, /unnecessary production data extraction, parsing, or normalization/);
|
|
222
|
-
assert.match(codeReviewer, /false confidence/);
|
|
223
|
-
|
|
224
|
-
assert.match(gateReviewer, /remove-ai-slops/);
|
|
225
|
-
assert.match(gateReviewer, /programming/);
|
|
226
|
-
assert.match(gateReviewer, /load or consult/);
|
|
227
|
-
assert.match(gateReviewer, /documented criteria/);
|
|
228
|
-
assert.match(gateReviewer, /Run the `remove-ai-slops`/);
|
|
229
|
-
assert.match(gateReviewer, /Apply the `programming`/);
|
|
230
|
-
assert.match(gateReviewer, /overfit\/slop pass yourself/);
|
|
231
|
-
assert.match(gateReviewer, /tests that merely verify a requested removal/);
|
|
232
|
-
assert.match(gateReviewer, /deletion-only/);
|
|
233
|
-
assert.match(gateReviewer, /tautological/);
|
|
234
|
-
assert.match(gateReviewer, /implementation-mirroring tests/);
|
|
235
|
-
assert.match(gateReviewer, /unnecessary production extraction, parsing, or normalization/);
|
|
236
|
-
|
|
237
|
-
const directPassIndex = gateReviewer.indexOf("overfit/slop pass yourself");
|
|
238
|
-
const reportCoverageIndex = gateReviewer.indexOf("Then confirm the code review report");
|
|
239
|
-
assert.notEqual(directPassIndex, -1);
|
|
240
|
-
assert.notEqual(reportCoverageIndex, -1);
|
|
241
|
-
assert.ok(
|
|
242
|
-
directPassIndex < reportCoverageIndex,
|
|
243
|
-
"gate reviewer must perform the overfit/slop pass directly before checking report coverage",
|
|
244
|
-
);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test("#given done-gate reviewer prompts #when inspected #then burden of proof is approve-unless-cited and reject priors are gone", async () => {
|
|
248
|
-
const agentsDir = join(root, "components", "ultrawork", "agents");
|
|
249
|
-
const gateReviewer = await readFile(join(agentsDir, "lazycodex-gate-reviewer.toml"), "utf8");
|
|
250
|
-
const qaExecutor = await readFile(join(agentsDir, "lazycodex-qa-executor.toml"), "utf8");
|
|
251
|
-
const codeReviewer = await readFile(join(agentsDir, "lazycodex-code-reviewer.toml"), "utf8");
|
|
252
|
-
|
|
253
|
-
assert.match(gateReviewer, /APPROVE unless you can cite/);
|
|
254
|
-
assert.match(gateReviewer, /violatedCriterion/);
|
|
255
|
-
assert.match(gateReviewer, /evidencePointer/);
|
|
256
|
-
assert.match(gateReviewer, /top blockers inline/);
|
|
257
|
-
assert.match(gateReviewer, /is a NOTE, not a blocker/);
|
|
258
|
-
assert.match(gateReviewer, /You do NOT check/);
|
|
259
|
-
assert.doesNotMatch(gateReviewer, /Assume the work has already failed/);
|
|
260
|
-
assert.doesNotMatch(gateReviewer, /Return exactly one recommendation: APPROVE\/REJECT\./);
|
|
261
|
-
|
|
262
|
-
assert.match(qaExecutor, /one-line reason/);
|
|
263
|
-
assert.match(qaExecutor, /rejecting a legitimately untriggered class is itself an error/);
|
|
264
|
-
assert.doesNotMatch(qaExecutor, /Trust nothing\./);
|
|
265
|
-
|
|
266
|
-
assert.match(codeReviewer, /MEDIUM by default/);
|
|
267
|
-
assert.doesNotMatch(codeReviewer, /Treat useless tests or needless production complexity as CRITICAL\/HIGH/);
|
|
268
|
-
});
|
|
269
|
-
|
|
@@ -84,7 +84,7 @@ test("#given aggregate SubagentStop hooks #when inspected #then start-work and L
|
|
|
84
84
|
assert.equal(verifierGroups.length, 1);
|
|
85
85
|
assert.equal(verifierGroups[0]?.groupIndex, 0);
|
|
86
86
|
assert.equal(verifierGroups[0]?.handler.timeout, 10);
|
|
87
|
-
assert.match(verifierGroups[0]?.handler.statusMessage ?? "",
|
|
87
|
+
assert.match(verifierGroups[0]?.handler.statusMessage ?? "", /\S/);
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
test("#given aggregate PostCompact hooks #when hooks are inspected #then LSP diagnostics cache reset is registered", async () => {
|
|
@@ -100,7 +100,7 @@ test("#given aggregate PostCompact hooks #when hooks are inspected #then LSP dia
|
|
|
100
100
|
|
|
101
101
|
// then
|
|
102
102
|
assert.equal(lspPostCompactHooks.length, 1);
|
|
103
|
-
assert.match(lspPostCompactHooks[0]?.handler.statusMessage ?? "",
|
|
103
|
+
assert.match(lspPostCompactHooks[0]?.handler.statusMessage ?? "", /\S/);
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
test("#given aggregate hook commands #when inspected #then every command exposes a Codex status message", async () => {
|
|
@@ -142,23 +142,6 @@ test("#given component hook commands #when inspected #then standalone packages e
|
|
|
142
142
|
assert.deepEqual(missingStatusMessages, []);
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
-
test("#given hook status messages #when inspected #then labels describe OMO responsibilities instead of the hook runner", async () => {
|
|
146
|
-
// given
|
|
147
|
-
const componentHooks = await readComponentHookManifests();
|
|
148
|
-
|
|
149
|
-
// when
|
|
150
|
-
const commandHooks = [
|
|
151
|
-
...(await readAggregateCommandHooks()),
|
|
152
|
-
...componentHooks.flatMap(({ source, hooks }) => collectCommandHooks(hooks, source)),
|
|
153
|
-
];
|
|
154
|
-
const genericStatusMessages = commandHooks
|
|
155
|
-
.filter(({ handler }) => typeof handler.statusMessage !== "string" || /\bhook\b/i.test(handler.statusMessage))
|
|
156
|
-
.map(hookLocation);
|
|
157
|
-
|
|
158
|
-
// then
|
|
159
|
-
assert.deepEqual(genericStatusMessages, []);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
145
|
test("#given aggregate OMO plugin is enabled #when hooks are inspected #then shell guidance and ulw-loop guard are registered", async () => {
|
|
163
146
|
// given
|
|
164
147
|
const manifests = await readAggregateHookManifests();
|
|
@@ -169,9 +152,7 @@ test("#given aggregate OMO plugin is enabled #when hooks are inspected #then she
|
|
|
169
152
|
|
|
170
153
|
// then
|
|
171
154
|
assert.match(text, /components\/git-bash\/dist\/cli\.js/);
|
|
172
|
-
assert.match(text, /Recommending Git Bash MCP/);
|
|
173
155
|
assert.match(text, /hook post-compact/);
|
|
174
|
-
assert.match(text, /Resetting Git Bash MCP Reminder/);
|
|
175
156
|
assert.match(text, /components\/ulw-loop\/dist\/cli\.js/);
|
|
176
157
|
assert.match(text, /hook pre-tool-use/);
|
|
177
158
|
assert.deepEqual(preToolUseGroups.map((group) => group.matcher), [
|
|
@@ -221,7 +202,6 @@ test("#given aggregate SessionStart hooks #when inspected #then LazyCodex auto-u
|
|
|
221
202
|
// then
|
|
222
203
|
assert.equal(autoUpdateGroup?.matcher, "^startup$");
|
|
223
204
|
assert.match(text, /scripts\/auto-update\.mjs/);
|
|
224
|
-
assert.match(text, /Checking Auto Update/);
|
|
225
205
|
assert(sessionStartCommands.some((command) => command.includes("scripts/auto-update.mjs")));
|
|
226
206
|
});
|
|
227
207
|
|
|
@@ -265,7 +245,7 @@ test("#given aggregate PostToolUse hooks #when inspected #then CodeGraph init gu
|
|
|
265
245
|
// then
|
|
266
246
|
assert.equal(codegraphPostToolUseHooks.length, 1);
|
|
267
247
|
assert.equal(codegraphPostToolUseHooks[0]?.matcher, "^(codegraph[._].*|mcp__codegraph__.*)$");
|
|
268
|
-
assert.match(codegraphPostToolUseHooks[0]?.handler.statusMessage ?? "",
|
|
248
|
+
assert.match(codegraphPostToolUseHooks[0]?.handler.statusMessage ?? "", /\S/);
|
|
269
249
|
});
|
|
270
250
|
|
|
271
251
|
test("#given aggregate PostToolUse hooks #when inspected #then thread title hygiene is registered for created Codex threads", async () => {
|
|
@@ -282,7 +262,7 @@ test("#given aggregate PostToolUse hooks #when inspected #then thread title hygi
|
|
|
282
262
|
// then
|
|
283
263
|
assert.equal(threadTitleHooks.length, 1);
|
|
284
264
|
assert.equal(threadTitleHooks[0]?.matcher, "^(create_thread|codex_app\\.create_thread)$");
|
|
285
|
-
assert.match(threadTitleHooks[0]?.handler.statusMessage ?? "",
|
|
265
|
+
assert.match(threadTitleHooks[0]?.handler.statusMessage ?? "", /\S/);
|
|
286
266
|
});
|
|
287
267
|
|
|
288
268
|
test("#given aggregate plugin packaging #when inspected #then hooks and compatibility sentinels stay Python-free", async () => {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
2
|
+
import { basename, dirname, join, sep } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
4
|
export const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
6
5
|
export const repoRoot = join(root, "..", "..", "..");
|
|
7
|
-
|
|
8
6
|
export async function readJson(relativePath) {
|
|
9
7
|
return JSON.parse(await readFile(join(root, relativePath), "utf8"));
|
|
10
8
|
}
|
|
@@ -79,18 +77,182 @@ export function hookLocation({ source, eventName, groupIndex, handlerIndex, hand
|
|
|
79
77
|
return `${source}:${eventName}:${groupIndex}:${handlerIndex}:${handler.command}`;
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
const SPAWN_AGENT_START = /(?:(?<receiver>\b[A-Za-z_]\w*)\s*\.\s*)?(?<callee>\bspawn_agent)\s*\(/g;
|
|
81
|
+
|
|
82
|
+
async function collectFiles(directory, predicate) {
|
|
83
|
+
let entries;
|
|
84
|
+
try {
|
|
85
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return [];
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const files = [];
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
const path = join(directory, entry.name);
|
|
94
|
+
if (entry.isDirectory()) files.push(...(await collectFiles(path, predicate)));
|
|
95
|
+
else if (entry.isFile() && predicate(path)) files.push(path);
|
|
96
|
+
}
|
|
97
|
+
return files;
|
|
84
98
|
}
|
|
85
99
|
|
|
86
|
-
export function
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
export async function listShippedSpawnPromptFiles() {
|
|
101
|
+
const skillFiles = [
|
|
102
|
+
...(await collectFiles(join(root, "skills"), (path) => basename(path) === "SKILL.md")),
|
|
103
|
+
...(await collectFiles(
|
|
104
|
+
join(root, "components"),
|
|
105
|
+
(path) => path.includes(`${sep}skills${sep}`) && basename(path) === "SKILL.md",
|
|
106
|
+
)),
|
|
107
|
+
];
|
|
108
|
+
const hephaestusRules = await collectFiles(
|
|
109
|
+
join(root, "components", "rules", "bundled-rules", "hephaestus"),
|
|
110
|
+
(path) => path.endsWith(".md"),
|
|
111
|
+
);
|
|
112
|
+
return [...skillFiles, ...hephaestusRules].sort();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function findSpawnAgentCalls(content) {
|
|
116
|
+
const calls = [];
|
|
117
|
+
for (const match of content.matchAll(SPAWN_AGENT_START)) {
|
|
118
|
+
const openingParenthesis = match.index + match[0].lastIndexOf("(");
|
|
119
|
+
let depth = 1;
|
|
120
|
+
let quote = null;
|
|
121
|
+
let escaped = false;
|
|
122
|
+
|
|
123
|
+
for (let index = openingParenthesis + 1; index < content.length; index += 1) {
|
|
124
|
+
const character = content[index];
|
|
125
|
+
if (quote !== null) {
|
|
126
|
+
if (escaped) escaped = false;
|
|
127
|
+
else if (character === "\\") escaped = true;
|
|
128
|
+
else if (character === quote) quote = null;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (character === '"' || character === "'" || character === "`") {
|
|
132
|
+
quote = character;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (character === "(") depth += 1;
|
|
136
|
+
else if (character === ")") depth -= 1;
|
|
137
|
+
if (depth !== 0) continue;
|
|
138
|
+
|
|
139
|
+
const { receiver = null, callee } = match.groups;
|
|
140
|
+
calls.push({ call: content.slice(match.index, index + 1), index: match.index, receiver, callee });
|
|
141
|
+
break;
|
|
93
142
|
}
|
|
94
143
|
}
|
|
95
|
-
return
|
|
144
|
+
return calls;
|
|
96
145
|
}
|
|
146
|
+
|
|
147
|
+
function tokensForSpawnCall(call) {
|
|
148
|
+
const tokens = [];
|
|
149
|
+
let braceDepth = 0;
|
|
150
|
+
let bracketDepth = 0;
|
|
151
|
+
let parenthesisDepth = 0;
|
|
152
|
+
|
|
153
|
+
for (let index = call.indexOf("(") + 1; index < call.length - 1; ) {
|
|
154
|
+
const character = call[index];
|
|
155
|
+
if (/\s/.test(character)) {
|
|
156
|
+
index += 1;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (character === '"' || character === "'" || character === "`") {
|
|
160
|
+
const start = index;
|
|
161
|
+
const quote = character;
|
|
162
|
+
let value = "";
|
|
163
|
+
index += 1;
|
|
164
|
+
while (index < call.length - 1) {
|
|
165
|
+
const current = call[index];
|
|
166
|
+
if (current === "\\" && index + 1 < call.length - 1) {
|
|
167
|
+
value += call[index + 1];
|
|
168
|
+
index += 2;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
index += 1;
|
|
172
|
+
if (current === quote) break;
|
|
173
|
+
value += current;
|
|
174
|
+
}
|
|
175
|
+
tokens.push({ type: "string", value, start, end: index, braceDepth, bracketDepth, parenthesisDepth });
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (/[A-Za-z_]/.test(character)) {
|
|
179
|
+
const start = index;
|
|
180
|
+
while (/[A-Za-z0-9_]/.test(call[index] ?? "")) index += 1;
|
|
181
|
+
tokens.push({ type: "identifier", value: call.slice(start, index), start, end: index,
|
|
182
|
+
braceDepth, bracketDepth, parenthesisDepth });
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (character === "{") braceDepth += 1;
|
|
187
|
+
else if (character === "}") braceDepth -= 1;
|
|
188
|
+
else if (character === "[") bracketDepth += 1;
|
|
189
|
+
else if (character === "]") bracketDepth -= 1;
|
|
190
|
+
else if (character === "(") parenthesisDepth += 1;
|
|
191
|
+
else if (character === ")") parenthesisDepth -= 1;
|
|
192
|
+
else if (character === ":" || character === "=") {
|
|
193
|
+
tokens.push({ type: "separator", value: character, start: index, end: index + 1,
|
|
194
|
+
braceDepth, bracketDepth, parenthesisDepth });
|
|
195
|
+
}
|
|
196
|
+
index += 1;
|
|
197
|
+
}
|
|
198
|
+
return tokens;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function spawnParameters(call) {
|
|
202
|
+
const tokens = tokensForSpawnCall(call);
|
|
203
|
+
const parameters = [];
|
|
204
|
+
for (let index = 0; index < tokens.length - 2; index += 1) {
|
|
205
|
+
const key = tokens[index];
|
|
206
|
+
const separator = tokens[index + 1];
|
|
207
|
+
const value = tokens[index + 2];
|
|
208
|
+
const isArgumentScope = key.parenthesisDepth === 0 && key.bracketDepth === 0 && key.braceDepth <= 1;
|
|
209
|
+
const isSameScope =
|
|
210
|
+
separator.braceDepth === key.braceDepth && separator.bracketDepth === key.bracketDepth &&
|
|
211
|
+
separator.parenthesisDepth === key.parenthesisDepth && value.braceDepth === key.braceDepth &&
|
|
212
|
+
value.bracketDepth === key.bracketDepth &&
|
|
213
|
+
value.parenthesisDepth === key.parenthesisDepth;
|
|
214
|
+
const isAdjacent = /^\s*$/.test(call.slice(key.end, separator.start)) &&
|
|
215
|
+
/^\s*$/.test(call.slice(separator.end, value.start));
|
|
216
|
+
if (!isArgumentScope || !isSameScope || !isAdjacent || separator.type !== "separator") continue;
|
|
217
|
+
parameters.push({ name: key.value, value, direct: key.braceDepth === 0 });
|
|
218
|
+
}
|
|
219
|
+
return parameters;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function hasSpawnIsolationArgument(call) {
|
|
223
|
+
return spawnParameters(call).some(
|
|
224
|
+
({ name, value }) => (name === "fork_context" && value.type === "identifier" && value.value === "false") ||
|
|
225
|
+
(name === "fork_turns" && value.type === "string" && value.value === "none"),
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function findSpawnAgentCallsWithoutIsolation(content) {
|
|
230
|
+
return findSpawnAgentCalls(content).filter(({ call }) => !hasSpawnIsolationArgument(call));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function findSpawnAgentCallsWithUnsupportedParameters(content) {
|
|
234
|
+
return findSpawnAgentCalls(content).flatMap((entry) => {
|
|
235
|
+
const allowsObjectAgentType = entry.receiver === "multi_agent_v1";
|
|
236
|
+
const parameters = spawnParameters(entry.call)
|
|
237
|
+
.filter(({ name, direct }) => name === "model" || name === "reasoning_effort" ||
|
|
238
|
+
(name === "agent_type" && (direct || !allowsObjectAgentType)))
|
|
239
|
+
.map(({ name }) => name);
|
|
240
|
+
return parameters.length === 0 ? [] : [{ ...entry, parameters }];
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function findShippedSpawnViolations(findViolations) {
|
|
245
|
+
const violations = [];
|
|
246
|
+
for (const path of await listShippedSpawnPromptFiles()) {
|
|
247
|
+
const content = await readFile(path, "utf8");
|
|
248
|
+
for (const violation of findViolations(content)) {
|
|
249
|
+
const line = content.slice(0, violation.index).split("\n").length;
|
|
250
|
+
violations.push({ path, line, ...violation });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return violations;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export const findShippedSpawnIsolationViolations = () => findShippedSpawnViolations(findSpawnAgentCallsWithoutIsolation);
|
|
257
|
+
export const findShippedUnsupportedSpawnParameterViolations = () =>
|
|
258
|
+
findShippedSpawnViolations(findSpawnAgentCallsWithUnsupportedParameters);
|