oh-my-opencode 5.0.0-beta.11 → 5.0.0-beta.13

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.
Files changed (98) hide show
  1. package/.agents/skills/senpi-qa/SKILL.md +80 -0
  2. package/.agents/skills/senpi-qa/scripts/resolve-evidence-dir.mjs +65 -0
  3. package/.agents/skills/senpi-qa/scripts/resolve-evidence-dir.test.mjs +171 -0
  4. package/dist/cli/index.js +6784 -4321
  5. package/dist/cli-node/index.js +6784 -4321
  6. package/dist/create-hooks.d.ts +1 -0
  7. package/dist/features/btw-side/btw-command-draft.d.ts +7 -0
  8. package/dist/features/btw-side/context-injector.d.ts +15 -0
  9. package/dist/features/btw-side/index.d.ts +8 -0
  10. package/dist/features/btw-side/metadata.d.ts +15 -0
  11. package/dist/features/btw-side/parent-context-budget.d.ts +9 -0
  12. package/dist/features/btw-side/server-session-registry.d.ts +8 -0
  13. package/dist/features/btw-side/tui-adoption-cache.d.ts +13 -0
  14. package/dist/features/btw-side/tui-adoption-guard.d.ts +5 -0
  15. package/dist/features/btw-side/tui-controller-types.d.ts +63 -0
  16. package/dist/features/btw-side/tui-controller.d.ts +14 -0
  17. package/dist/features/btw-side/tui-keymap.d.ts +10 -0
  18. package/dist/features/btw-side/tui-parent-validator.d.ts +6 -0
  19. package/dist/features/btw-side/tui-prompt-queue.d.ts +8 -0
  20. package/dist/features/btw-side/tui-session-bridge.d.ts +11 -0
  21. package/dist/features/btw-side/tui-side-removal.d.ts +11 -0
  22. package/dist/features/btw-side/tui-side-start.d.ts +9 -0
  23. package/dist/features/btw-side/tui-wiring.d.ts +8 -0
  24. package/dist/hooks/runtime-fallback/chat-message-handler.d.ts +2 -0
  25. package/dist/hooks/runtime-fallback/retry-model-payload.d.ts +1 -0
  26. package/dist/hooks/runtime-fallback/runtime-model-settings.d.ts +6 -0
  27. package/dist/hooks/runtime-fallback/session-status-handler.d.ts +1 -1
  28. package/dist/hooks/runtime-fallback/types.d.ts +3 -1
  29. package/dist/index.js +6512 -3851
  30. package/dist/plugin/event-session-lifecycle.d.ts +1 -0
  31. package/dist/plugin/hooks/create-core-hooks.d.ts +1 -0
  32. package/dist/plugin/hooks/create-transform-hooks.d.ts +2 -0
  33. package/dist/plugin/messages-transform.d.ts +1 -0
  34. package/dist/shared/index.d.ts +1 -0
  35. package/dist/shared/runtime-fallback-retry-marker.d.ts +16 -0
  36. package/dist/tui.js +8102 -6793
  37. package/package.json +24 -23
  38. package/packages/git-bash-mcp/dist/cli.js +5 -1
  39. package/packages/lsp-daemon/dist/cli.js +5 -1
  40. package/packages/lsp-daemon/dist/client.js +5 -1
  41. package/packages/lsp-daemon/dist/index.js +5 -1
  42. package/packages/lsp-tools-mcp/dist/cli.js +5 -1
  43. package/packages/lsp-tools-mcp/dist/mcp.js +5 -1
  44. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  45. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  46. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  47. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +14 -1
  48. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +14 -1
  49. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  50. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  51. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  52. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  53. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  54. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  55. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  56. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  57. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  58. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  59. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  60. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  61. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  62. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  63. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  64. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  65. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +2242 -381
  66. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +2247 -386
  67. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  68. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  69. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  70. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  71. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  72. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  73. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  74. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  75. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  76. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  77. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  78. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  79. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  80. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  81. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  82. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  83. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  84. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  85. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  86. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  87. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  88. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  89. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  90. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  91. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  92. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  93. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  94. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  95. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  96. package/packages/omo-codex/plugin/package-lock.json +13 -13
  97. package/packages/omo-codex/plugin/package.json +1 -1
  98. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2307 -401
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: senpi-qa
3
+ description: "QA the omo Senpi adapter (packages/omo-senpi, packages/senpi-task) against the REAL senpi binary in strict isolation, and write every artifact to the one canonical evidence path .omo/evidence/omo-senpi-adapter/<slug>/. The live drivers under packages/omo-senpi/scripts/qa/ create their own isolated SENPI_CODING_AGENT_DIR and ignore the caller's, so the real ~/.senpi/agent is never written. Ships scripts/resolve-evidence-dir.mjs, which is the ONLY sanctioned way to pick an evidence directory: it rejects traversal, separators, absolute paths, and stray roots such as local-ignore/qa-evidence. Use whenever someone changes anything under packages/omo-senpi or packages/senpi-task, or wants to QA, smoke-test, verify, or debug the Senpi adapter, the task/team engine, the DAG, task RPC, or skill delivery. Triggers: senpi qa, qa senpi, senpi-qa, test senpi adapter, verify senpi task, senpi task e2e, senpi team e2e, task dag qa, live senpi driver, senpi evidence path."
4
+ ---
5
+
6
+ # Senpi QA
7
+
8
+ QA the omo Senpi adapter (`packages/omo-senpi/`) and the task engine
9
+ (`packages/senpi-task/`) by driving the REAL `senpi` binary. Unit tests never
10
+ count as live QA here: `bun run test:senpi` is the package gate, the drivers in
11
+ `packages/omo-senpi/scripts/qa/` are the harness proof.
12
+
13
+ ## Golden rules
14
+
15
+ - **Evidence lives at exactly one path.** Every artifact goes under
16
+ `.omo/evidence/omo-senpi-adapter/<slug>/`. Pick it with
17
+ `scripts/resolve-evidence-dir.mjs` and nothing else — a hand-typed path is how
18
+ runs end up somewhere like `local-ignore/qa-evidence/`, which no reviewer reads
19
+ and the PR cannot cite.
20
+ - **The real agent dir stays untouched.** The live drivers build their own
21
+ isolated `SENPI_CODING_AGENT_DIR` and deliberately IGNORE a caller-provided
22
+ one, so `~/.senpi/agent` is never used as the sandbox. Report the driver's
23
+ `realSenpiUntouched` / changed-path fields and the isolated agent-dir path;
24
+ treat a whole-directory digest as supporting evidence, not proof by itself.
25
+ - **No binary means SKIP, not silence.** When `senpi` is absent the live drivers
26
+ report `SKIP` or `FAIL` in their final JSON rather than degrading to the real
27
+ home. A `SKIP` is not a pass — say so in the evidence README.
28
+ - **The captured JSON is the evidence.** No file on disk means the QA did not
29
+ happen, which means no commit and no push.
30
+
31
+ ## Resolve the evidence directory first
32
+
33
+ ```bash
34
+ ev="$(node .agents/skills/senpi-qa/scripts/resolve-evidence-dir.mjs \
35
+ --repo-root "$(git rev-parse --show-toplevel)" --slug <YYYYMMDD>-<short-slug>)"
36
+ mkdir -p "$ev"
37
+ ```
38
+
39
+ The resolver returns an absolute path and creates nothing, so the caller decides
40
+ when the directory appears. A slug is ONE relative segment of lowercase letters,
41
+ digits, and hyphens (`20260820-senpi-qa-contract`). Separators, `.`/`..`,
42
+ traversal, absolute paths, and a non-git root are rejected with a non-zero exit
43
+ and a message naming the offending slug.
44
+
45
+ ## Router: pick your case
46
+
47
+ | You changed… | Run | Proves |
48
+ |---|---|---|
49
+ | Any adapter code, as the fast precondition | `node packages/omo-senpi/scripts/qa/drive.mjs --self-test` | the driver + isolation harness itself works |
50
+ | Adapter wiring reaching a live session | `node packages/omo-senpi/scripts/qa/drive.mjs` | a real senpi run with the plugin loaded, isolated agent dir, and no attributed real-home changes |
51
+ | Task lifecycle (single + batch) | `SENPI_BIN="$(command -v senpi)" node packages/omo-senpi/scripts/qa/task-e2e.mjs` | live task start/stream/terminal states |
52
+ | Team delivery, shutdown, reclaim, restart recovery | `SENPI_BIN="$(command -v senpi)" node packages/omo-senpi/scripts/qa/team-e2e.mjs` | injection delivery and exactly-once recovery |
53
+ | Task RPC driver scripts | `node packages/omo-senpi/scripts/qa/task-rpc-e2e.mjs --self-test` | the RPC surface contract |
54
+ | Skill delivery into a task | `SENPI_BIN="$(command -v senpi)" node packages/omo-senpi/scripts/qa/task-load-skills-e2e.mjs` | skills reach the child |
55
+ | Continuation behavior | `node packages/omo-senpi/scripts/qa/probe-continuation.mjs` | turns continue as expected |
56
+ | DAG state machine / runners | `bun test packages/senpi-task` | unit + chaos invariants (NOT live proof) |
57
+
58
+ Point a driver's output at the resolved directory, e.g.:
59
+
60
+ ```bash
61
+ TASK_E2E_OUT_DIR="$ev/live-task-dag" SENPI_BIN="$(command -v senpi)" \
62
+ node packages/omo-senpi/scripts/qa/task-e2e.mjs
63
+ ```
64
+
65
+ ## Package gate
66
+
67
+ ```bash
68
+ tsgo --noEmit -p packages/omo-senpi/tsconfig.json
69
+ bun run test:senpi
70
+ ```
71
+
72
+ ## Write the evidence README
73
+
74
+ Every run leaves `$ev/README.md` a reviewer can read without rerunning anything.
75
+ The required sections are the repo-wide evidence rules in the root
76
+ [`AGENTS.md`](../../../AGENTS.md) (what was tested / observed / why it is enough /
77
+ what was omitted). For Senpi, record the driver's changed-path/isolation fields
78
+ and sandbox agent-dir path. Some drivers report sandbox paths without removing
79
+ them; the caller must delete every task-owned sandbox and verify child PIDs are
80
+ terminal before writing the cleanup receipt.
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ // resolve-evidence-dir.mjs - pick the canonical evidence directory for a live Senpi QA run.
3
+ //
4
+ // Live Senpi QA artifacts belong under .omo/evidence/omo-senpi-adapter/<slug>/ and nowhere else.
5
+ // That path is the repository-standard location every reviewer and AGENTS.md rule points at, so a
6
+ // hand-typed alternative (local-ignore/qa-evidence/..., a temp dir, a traversal) silently strands
7
+ // the evidence where the PR cannot cite it. Resolving through this script makes the path a checked
8
+ // contract instead of a convention.
9
+ //
10
+ // The resolver COMPUTES and VALIDATES only; creating the directory stays the caller's decision, so
11
+ // a rejected slug never leaves a stray directory behind.
12
+ //
13
+ // node resolve-evidence-dir.mjs --repo-root "$(git rev-parse --show-toplevel)" --slug 20260820-my-run
14
+ //
15
+ // Prints the absolute path to stdout. Exit 0 iff the root is a git worktree and the slug is safe.
16
+ import { existsSync, realpathSync } from "node:fs"
17
+ import { join, resolve } from "node:path"
18
+ import { fileURLToPath } from "node:url"
19
+
20
+ export const EVIDENCE_RELATIVE_ROOT = join(".omo", "evidence", "omo-senpi-adapter")
21
+
22
+ // One relative segment: lowercase alphanumerics joined by single hyphens. This is what makes the
23
+ // path predictable - it also rejects every separator, traversal, and absolute form outright, since
24
+ // none of them can match.
25
+ const SAFE_SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
26
+
27
+ /**
28
+ * @param {{ repoRoot: string, slug: string }} input
29
+ * @returns {string} absolute evidence directory; NOT created
30
+ */
31
+ export function resolveEvidenceDir({ repoRoot, slug }) {
32
+ if (typeof repoRoot !== "string" || repoRoot.length === 0) {
33
+ throw new Error("repoRoot must be a non-empty path to a git worktree")
34
+ }
35
+ if (!existsSync(join(repoRoot, ".git"))) {
36
+ throw new Error(`repoRoot is not a git worktree: ${repoRoot}`)
37
+ }
38
+ if (typeof slug !== "string" || !SAFE_SLUG.test(slug)) {
39
+ throw new Error(
40
+ `slug must be one lowercase alphanumeric-and-hyphen segment (got ${JSON.stringify(slug)}); ` +
41
+ `evidence lives under ${EVIDENCE_RELATIVE_ROOT}/<slug>`,
42
+ )
43
+ }
44
+ return resolve(repoRoot, EVIDENCE_RELATIVE_ROOT, slug)
45
+ }
46
+
47
+ const isDirectRun =
48
+ process.argv[1] !== undefined &&
49
+ existsSync(process.argv[1]) &&
50
+ realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))
51
+
52
+ if (isDirectRun) {
53
+ const args = process.argv.slice(2)
54
+ const read = (flag) => {
55
+ const at = args.indexOf(flag)
56
+ return at === -1 ? undefined : args[at + 1]
57
+ }
58
+ try {
59
+ const dir = resolveEvidenceDir({ repoRoot: read("--repo-root") ?? process.cwd(), slug: read("--slug") })
60
+ process.stdout.write(`${dir}\n`)
61
+ } catch (error) {
62
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
63
+ process.exit(1)
64
+ }
65
+ }
@@ -0,0 +1,171 @@
1
+ import { afterEach, describe, expect, test } from "bun:test"
2
+ import { spawnSync } from "node:child_process"
3
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync } from "node:fs"
4
+ import { tmpdir } from "node:os"
5
+ import { join, relative } from "node:path"
6
+ import { fileURLToPath, pathToFileURL } from "node:url"
7
+
8
+ import { EVIDENCE_RELATIVE_ROOT, resolveEvidenceDir } from "./resolve-evidence-dir.mjs"
9
+
10
+ const cleanupRoots = []
11
+ const cliPath = fileURLToPath(new URL("./resolve-evidence-dir.mjs", import.meta.url))
12
+
13
+ afterEach(() => {
14
+ for (const root of cleanupRoots.splice(0)) rmSync(root, { recursive: true, force: true })
15
+ })
16
+
17
+ function makeGitRoot() {
18
+ const root = mkdtempSync(join(tmpdir(), "senpi-qa-evidence-"))
19
+ cleanupRoots.push(root)
20
+ mkdirSync(join(root, ".git"))
21
+ return root
22
+ }
23
+
24
+ describe("resolveEvidenceDir", () => {
25
+ test("#given a git worktree and a safe slug #when resolved #then it returns the canonical adapter evidence directory", () => {
26
+ // given
27
+ const repoRoot = makeGitRoot()
28
+
29
+ // when
30
+ const resolved = resolveEvidenceDir({ repoRoot, slug: "20260820-senpi-qa-contract" })
31
+
32
+ // then
33
+ expect(resolved).toBe(join(repoRoot, ".omo", "evidence", "omo-senpi-adapter", "20260820-senpi-qa-contract"))
34
+ })
35
+
36
+ test("#given a relative git worktree path #when resolved #then it still returns an absolute evidence directory", () => {
37
+ // given
38
+ const repoRoot = makeGitRoot()
39
+ const relativeRoot = relative(process.cwd(), repoRoot)
40
+
41
+ // when
42
+ const resolved = resolveEvidenceDir({ repoRoot: relativeRoot, slug: "20260820-relative-root" })
43
+
44
+ // then
45
+ expect(resolved).toBe(join(repoRoot, ".omo", "evidence", "omo-senpi-adapter", "20260820-relative-root"))
46
+ })
47
+
48
+ test("#given a resolved evidence path #when the resolver returns #then it has created no directory", () => {
49
+ // given
50
+ const repoRoot = makeGitRoot()
51
+
52
+ // when
53
+ const resolved = resolveEvidenceDir({ repoRoot, slug: "20260820-senpi-qa-contract" })
54
+
55
+ // then
56
+ expect(existsSync(resolved)).toBe(false)
57
+ expect(existsSync(join(repoRoot, ".omo"))).toBe(false)
58
+ })
59
+
60
+ test("#given the adapter evidence root #when it is the declared relative root #then it stays under .omo/evidence", () => {
61
+ // given / when / then
62
+ expect(EVIDENCE_RELATIVE_ROOT).toBe(join(".omo", "evidence", "omo-senpi-adapter"))
63
+ })
64
+
65
+ test("#given an unsafe slug #when resolved #then it is rejected before any path is produced", () => {
66
+ // given
67
+ const repoRoot = makeGitRoot()
68
+ const unsafeSlugs = [
69
+ "",
70
+ " ",
71
+ ".",
72
+ "..",
73
+ "../escape",
74
+ "nested/slug",
75
+ "nested\\slug",
76
+ "/absolute",
77
+ join(repoRoot, "absolute"),
78
+ "local-ignore/qa-evidence/20260819-senpi",
79
+ "..%2Fescape",
80
+ "-leading-hyphen",
81
+ "trailing-hyphen-",
82
+ "Upper-Case",
83
+ "under_score",
84
+ ]
85
+
86
+ // when / then
87
+ for (const slug of unsafeSlugs) {
88
+ expect(() => resolveEvidenceDir({ repoRoot, slug }), `must reject ${JSON.stringify(slug)}`).toThrow()
89
+ }
90
+ })
91
+
92
+ test("#given a directory that is not a git worktree #when resolved #then it is rejected", () => {
93
+ // given
94
+ const notARepo = mkdtempSync(join(tmpdir(), "senpi-qa-not-git-"))
95
+ cleanupRoots.push(notARepo)
96
+
97
+ // when / then
98
+ expect(() => resolveEvidenceDir({ repoRoot: notARepo, slug: "20260820-senpi-qa-contract" })).toThrow(/git/i)
99
+ })
100
+
101
+ test("#given a rejected slug #when the error surfaces #then it names the offending slug for the operator", () => {
102
+ // given
103
+ const repoRoot = makeGitRoot()
104
+
105
+ // when / then
106
+ expect(() => resolveEvidenceDir({ repoRoot, slug: "local-ignore/qa-evidence/x" })).toThrow(
107
+ /local-ignore\/qa-evidence\/x/,
108
+ )
109
+ })
110
+
111
+ test("#given the documented Node CLI #when valid and invalid slugs run #then stdout and exit codes enforce the contract", () => {
112
+ // given
113
+ const repoRoot = makeGitRoot()
114
+
115
+ // when
116
+ const valid = spawnSync("node", [cliPath, "--repo-root", repoRoot, "--slug", "20260820-node-cli"], {
117
+ encoding: "utf8",
118
+ })
119
+ const invalid = spawnSync(
120
+ "node",
121
+ [cliPath, "--repo-root", repoRoot, "--slug", "local-ignore/qa-evidence/x"],
122
+ { encoding: "utf8" },
123
+ )
124
+
125
+ // then
126
+ expect(valid.status).toBe(0)
127
+ expect(valid.stdout.trim()).toBe(join(repoRoot, EVIDENCE_RELATIVE_ROOT, "20260820-node-cli"))
128
+ expect(invalid.status).toBe(1)
129
+ expect(invalid.stderr).toContain("local-ignore/qa-evidence/x")
130
+ })
131
+
132
+ test("#given a symlink to the documented Node CLI #when invoked #then the entrypoint still enforces the contract", () => {
133
+ // given
134
+ const repoRoot = makeGitRoot()
135
+ const symlinkPath = join(repoRoot, "resolve-evidence-dir.mjs")
136
+ symlinkSync(cliPath, symlinkPath)
137
+
138
+ // when
139
+ const result = spawnSync(
140
+ "node",
141
+ [symlinkPath, "--repo-root", repoRoot, "--slug", "20260820-symlink-cli"],
142
+ { encoding: "utf8" },
143
+ )
144
+
145
+ // then
146
+ expect(result.status).toBe(0)
147
+ expect(result.stdout.trim()).toBe(join(repoRoot, EVIDENCE_RELATIVE_ROOT, "20260820-symlink-cli"))
148
+ })
149
+
150
+ test("#given an embedding runner with a missing argv path #when the module is imported #then exports still load", () => {
151
+ // given
152
+ const missingEntrypoint = join(tmpdir(), `missing-senpi-qa-entrypoint-${process.pid}.mjs`)
153
+ const moduleUrl = pathToFileURL(cliPath).href
154
+
155
+ // when
156
+ const result = spawnSync(
157
+ "node",
158
+ [
159
+ "--input-type=module",
160
+ "--eval",
161
+ `process.argv[1] = ${JSON.stringify(missingEntrypoint)}; await import(${JSON.stringify(moduleUrl)})`,
162
+ ],
163
+ { encoding: "utf8" },
164
+ )
165
+
166
+ // then
167
+ expect(result.status).toBe(0)
168
+ expect(result.stdout).toBe("")
169
+ expect(result.stderr).toBe("")
170
+ })
171
+ })