Skip to content

Commit 80a5596

Browse files
Reorganize tests by runtime surface and move test-only artifacts out of production paths (#155)
2 parents db041b4 + d9f979a commit 80a5596

81 files changed

Lines changed: 5603 additions & 4910 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/architecture/overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ To add a new command:
215215
- register the slice in `src/commands/index.ts`
216216
- use or extend `<group>/shared/` only for helper logic genuinely shared by multiple commands in that group, and name modules by responsibility
217217

218-
Command-specific tests live beside the slice by default. Cross-cutting CLI, core, OpenCode tool/plugin, and package-level tests remain in `test/`.
218+
Command-specific tests live beside the slice by default when they exercise only that slice's local contract surface, such as `definition.test.ts` and focused runtime-surface tests like `core.test.ts`, `tool.test.ts`, or `cli.test.ts`. Cross-cutting CLI, core, OpenCode tool/plugin, template-runtime, shared mock, and package-level tests remain in `test/`, and test-only support artifacts such as GitHub mocks must not live under `src/`.
219219

220220
## Architectural rules to keep in mind
221221

docs/orfe/spec.md

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ See also:
1515
`orfe` is a stand-alone GitHub operations tool with two entrypoints:
1616

1717
- an installable CLI named `orfe`
18-
- an OpenCode custom tool wrapper also named `orfe`
18+
- an OpenCode plugin that registers the `orfe` tool
1919

2020
V1 exists to provide a deterministic, reusable contract for:
2121

@@ -32,8 +32,8 @@ Accepted architecture direction now also includes installable extensions layered
3232
## 2. Resolved design decisions
3333

3434
1. `orfe` is a stand-alone tool, not a repo-specific workflow engine.
35-
2. The OpenCode wrapper is the **only** layer allowed to read `context.agent`.
36-
3. The wrapper resolves a plain `callerName` string and passes that into the `orfe` core.
35+
2. The OpenCode tool entrypoint is the **only** layer allowed to read `context.agent`.
36+
3. The tool entrypoint resolves a plain `callerName` string and passes that into the `orfe` core.
3737
4. The core never reads `context.agent` directly.
3838
5. Repo-local config maps `callerName` to the GitHub bot used for auth.
3939
6. `orfe` uses Octokit as the GitHub API client layer.
@@ -70,8 +70,8 @@ For terminology in this spec:
7070

7171
- package/runtime architecture
7272
- CLI contract
73-
- OpenCode wrapper contract
74-
- wrapper/core boundary
73+
- OpenCode tool contract
74+
- OpenCode tool/core boundary
7575
- caller resolution rules
7676
- repo-local config model
7777
- success/error/help contract
@@ -99,7 +99,7 @@ For terminology in this spec:
9999

100100
`orfe` v1 is split into four layers.
101101

102-
### 4.1 OpenCode wrapper
102+
### 4.1 OpenCode tool entrypoint
103103

104104
Responsibilities:
105105

@@ -109,7 +109,7 @@ Responsibilities:
109109
- reject missing or invalid caller context
110110
- pass plain structured input plus `callerName` into the core
111111

112-
The wrapper must not:
112+
The tool entrypoint must not:
113113

114114
- call GitHub directly
115115
- load repo config itself except as needed to locate repo context
@@ -130,15 +130,15 @@ Responsibilities:
130130
- dispatch command handlers
131131
- return structured success objects or typed errors
132132

133-
The core is runtime-agnostic. It must be callable from both CLI and OpenCode wrapper code.
133+
The core is runtime-agnostic. It must be callable from both CLI and OpenCode tool entrypoint code.
134134

135135
### 4.3 Auth adapter
136136

137137
V1 auth is bot-aware and internal to `orfe`.
138138

139139
Auth flow:
140140

141-
1. the wrapper or CLI provides `callerName`
141+
1. the OpenCode tool entrypoint or CLI provides `callerName`
142142
2. the core resolves `callerName -> github bot`
143143
3. the auth adapter loads machine-local auth config for that GitHub bot
144144
4. the auth adapter reads the GitHub App credentials and private key path for that bot
@@ -169,21 +169,21 @@ For `issue set-state` specifically:
169169
- non-duplicate open/close transitions use Octokit REST issue update operations
170170
- duplicate closure uses Octokit GraphQL because REST `state_reason=duplicate` alone does not establish GitHub's canonical duplicate relationship
171171

172-
## 5. Wrapper/core boundary
172+
## 5. OpenCode tool/core boundary
173173

174174
The boundary is strict.
175175

176-
### 5.1 Wrapper input source
176+
### 5.1 Tool entrypoint input source
177177

178-
At the OpenCode boundary, the wrapper resolves the caller from `context.agent` only.
178+
At the OpenCode boundary, the tool entrypoint resolves the caller from `context.agent` only.
179179

180180
Resolution rules:
181181

182182
1. If `context.agent` is a non-empty string, use that string.
183183
2. Else if `context.agent.name` is a non-empty string, use that string.
184184
3. Else fail with `caller_context_missing`.
185185

186-
After resolution, the wrapper passes only a plain string:
186+
After resolution, the tool entrypoint passes only a plain string:
187187

188188
```ts
189189
type CallerName = string;
@@ -576,7 +576,7 @@ The tool name is `orfe`.
576576

577577
### 9.1 Tool input shape
578578

579-
The wrapper accepts structured JSON input:
579+
The tool entrypoint accepts structured JSON input:
580580

581581
```json
582582
{
@@ -591,14 +591,14 @@ Rules:
591591
- `command` is required.
592592
- `command` uses the canonical space-separated vocabulary, matching the CLI subcommands exactly.
593593
- command-specific fields use `snake_case`.
594-
- `config` and `auth_config` are supported wrapper-level path overrides, matching CLI `--config` and `--auth-config`.
594+
- `config` and `auth_config` are supported tool-level path overrides, matching CLI `--config` and `--auth-config`.
595595
- template selection uses `template` in tool input when applicable.
596596
- `caller_name` is **not** accepted from tool input.
597-
- the wrapper injects `callerName` from `context.agent`.
597+
- the tool entrypoint injects `callerName` from `context.agent`.
598598

599599
### 9.2 Tool output
600600

601-
The wrapper returns the same structured success/error object shape produced by the core. The wrapper may translate thrown errors into the shared error envelope, but it must not redefine field names or introduce repo-specific semantics.
601+
The tool entrypoint returns the same structured success/error object shape produced by the core. The tool entrypoint may translate thrown errors into the shared error envelope, but it must not redefine field names or introduce repo-specific semantics.
602602

603603
## 10. CLI command hierarchy
604604

@@ -661,7 +661,7 @@ orfe auth token --repo <owner/name> [--config <path>] [--auth-config <path>]
661661

662662
Rules:
663663

664-
- the caller identity is resolved normally through CLI or wrapper caller resolution
664+
- the caller identity is resolved normally through CLI or tool entrypoint caller resolution
665665
- the command mints only for that resolved caller bot; it is not a cross-bot impersonation feature
666666
- `repo` is required and must be `owner/name`
667667
- `app_slug` is config-derived from the resolved bot auth metadata, not looked up live from GitHub
@@ -1622,7 +1622,7 @@ Representative targeted help shape:
16221622

16231623
Rules:
16241624

1625-
- help is a deliberate public runtime command, not a wrapper-only special case
1625+
- help is a deliberate public runtime command, not a tool-entrypoint-only special case
16261626
- root help must expose enough structured information for an agent to discover available commands and choose the correct one
16271627
- root help is the canonical agent discovery entrypoint for OpenCode tool usage
16281628
- root help must make the recommended discovery flow explicit: start with root help, then request targeted help for a canonical command name
@@ -1644,7 +1644,7 @@ Issues #14 and #15 should assume these implementation constraints:
16441644
- core handlers return structured result objects
16451645
- handlers throw typed errors with stable codes
16461646
- CLI formatting is a thin adapter over core success/error objects
1647-
- custom tool formatting is also a thin adapter over the same core contract
1647+
- OpenCode tool formatting is also a thin adapter over the same core contract
16481648
- unimplemented leaf commands in early scaffolding must fail consistently with `not_implemented`
16491649

16501650
Placeholder behavior for stubs:
@@ -1666,7 +1666,9 @@ Placeholder behavior for stubs:
16661666
### 13.1 Required automated coverage direction
16671667

16681668
- contract tests for CLI structure and validation
1669-
- contract tests for wrapper/core separation
1669+
- contract tests for OpenCode tool/core separation
1670+
- slice-local `definition.test.ts` files cover command metadata, validation wiring, and help-data construction where applicable
1671+
- runtime-surface contract coverage should stay split by entrypoint as `core.test.ts`, `tool.test.ts`, and `cli.test.ts` rather than mixing those surfaces in one file
16701672
- config-loading tests
16711673
- caller-resolution tests
16721674
- command-handler tests for GitHub interactions
Lines changed: 3 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,11 @@
11
import assert from 'node:assert/strict';
2+
23
import { test } from 'vitest';
34

45
import { OrfeError } from '../../../../src/runtime/errors.js';
5-
import { runCoreCommand, runToolCommand } from '../../../../test/support/command-runtime.js';
6-
import { mockAuthTokenMintRequest } from './mocks/github.js';
7-
import { withNock } from '../../../../test/support/http-test.js';
86
import { createGitHubClientFactory, createRuntimeDependencies, invokeCli, repoConfigPath } from '../../../../test/support/cli-test.js';
9-
10-
test('runOrfeCore mints an auth token for the resolved caller bot', async () => {
11-
await withNock(async () => {
12-
const api = mockAuthTokenMintRequest({ repo: { owner: 'throw-if-null', name: 'orfe' } });
13-
14-
const result = await runCoreCommand({
15-
command: 'auth token',
16-
input: { repo: 'throw-if-null/orfe' },
17-
});
18-
19-
assert.deepEqual(result, {
20-
ok: true,
21-
command: 'auth token',
22-
repo: 'throw-if-null/orfe',
23-
data: {
24-
bot: 'greg',
25-
app_slug: 'GR3G-BOT',
26-
repo: 'throw-if-null/orfe',
27-
token: 'ghs_123',
28-
expires_at: '2026-04-06T12:00:00Z',
29-
auth_mode: 'github-app',
30-
},
31-
});
32-
assert.equal(api.isDone(), true);
33-
});
34-
});
35-
36-
test('executeOrfeTool resolves auth token from context.agent and returns shared success envelope', async () => {
37-
await withNock(async () => {
38-
const api = mockAuthTokenMintRequest();
39-
40-
const result = await runToolCommand({
41-
input: {
42-
command: 'auth token',
43-
repo: 'throw-if-null/orfe',
44-
},
45-
});
46-
47-
assert.deepEqual(result, {
48-
ok: true,
49-
command: 'auth token',
50-
repo: 'throw-if-null/orfe',
51-
data: {
52-
bot: 'greg',
53-
app_slug: 'GR3G-BOT',
54-
repo: 'throw-if-null/orfe',
55-
token: 'ghs_123',
56-
expires_at: '2026-04-06T12:00:00Z',
57-
auth_mode: 'github-app',
58-
},
59-
});
60-
assert.equal(api.isDone(), true);
61-
});
62-
});
63-
64-
test('runOrfeCore rejects bot override input for auth token', async () => {
65-
await assert.rejects(
66-
runCoreCommand({
67-
command: 'auth token',
68-
input: { bot: 'unknown', repo: 'throw-if-null/orfe' },
69-
}),
70-
(error: unknown) => {
71-
assert(error instanceof OrfeError);
72-
assert.equal(error.code, 'invalid_usage');
73-
assert.equal(error.message, 'Command "auth token" does not accept input field "bot".');
74-
return true;
75-
},
76-
);
77-
});
78-
79-
test('executeOrfeTool rejects bot override input for auth token', async () => {
80-
const result = await runToolCommand({
81-
input: {
82-
command: 'auth token',
83-
bot: 'greg',
84-
repo: 'throw-if-null/orfe',
85-
},
86-
});
87-
88-
assert.deepEqual(result, {
89-
ok: false,
90-
command: 'auth token',
91-
error: {
92-
code: 'invalid_usage',
93-
message: 'Command "auth token" does not accept input field "bot".',
94-
retryable: false,
95-
},
96-
});
97-
});
7+
import { mockAuthTokenMintRequest } from '../../../../test/support/github/auth.js';
8+
import { withNock } from '../../../../test/support/http-test.js';
989

9910
test('runCli requires caller identity and mints auth token for that caller bot', async () => {
10011
await withNock(async () => {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import assert from 'node:assert/strict';
2+
3+
import { test } from 'vitest';
4+
5+
import { OrfeError } from '../../../../src/runtime/errors.js';
6+
import { runCoreCommand } from '../../../../test/support/command-runtime.js';
7+
import { mockAuthTokenMintRequest } from '../../../../test/support/github/auth.js';
8+
import { withNock } from '../../../../test/support/http-test.js';
9+
10+
test('runOrfeCore mints an auth token for the resolved caller bot', async () => {
11+
await withNock(async () => {
12+
const api = mockAuthTokenMintRequest({ repo: { owner: 'throw-if-null', name: 'orfe' } });
13+
14+
const result = await runCoreCommand({
15+
command: 'auth token',
16+
input: { repo: 'throw-if-null/orfe' },
17+
});
18+
19+
assert.deepEqual(result, {
20+
ok: true,
21+
command: 'auth token',
22+
repo: 'throw-if-null/orfe',
23+
data: {
24+
bot: 'greg',
25+
app_slug: 'GR3G-BOT',
26+
repo: 'throw-if-null/orfe',
27+
token: 'ghs_123',
28+
expires_at: '2026-04-06T12:00:00Z',
29+
auth_mode: 'github-app',
30+
},
31+
});
32+
assert.equal(api.isDone(), true);
33+
});
34+
});
35+
36+
test('runOrfeCore rejects bot override input for auth token', async () => {
37+
await assert.rejects(
38+
runCoreCommand({
39+
command: 'auth token',
40+
input: { bot: 'unknown', repo: 'throw-if-null/orfe' },
41+
}),
42+
(error: unknown) => {
43+
assert(error instanceof OrfeError);
44+
assert.equal(error.code, 'invalid_usage');
45+
assert.equal(error.message, 'Command "auth token" does not accept input field "bot".');
46+
return true;
47+
},
48+
);
49+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import assert from 'node:assert/strict';
2+
3+
import { test } from 'vitest';
4+
5+
import { assertDefinitionIdentity, assertOption, assertValidInputExample } from '../../../../test/support/definition-test.js';
6+
import { authTokenCommand } from './definition.js';
7+
8+
test('auth token definition requires repo input and exposes token metadata examples', () => {
9+
assertDefinitionIdentity(authTokenCommand, { name: 'auth token', group: 'auth', leaf: 'token', execution: 'github' });
10+
assertOption(authTokenCommand, 'repo', { flag: '--repo', type: 'string', required: true });
11+
assertValidInputExample(authTokenCommand);
12+
assert.equal(authTokenCommand.successDataExample.app_slug, 'GR3G-BOT');
13+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import assert from 'node:assert/strict';
2+
3+
import { test } from 'vitest';
4+
5+
import { runToolCommand } from '../../../../test/support/command-runtime.js';
6+
import { mockAuthTokenMintRequest } from '../../../../test/support/github/auth.js';
7+
import { withNock } from '../../../../test/support/http-test.js';
8+
9+
test('executeOrfeTool resolves auth token from context.agent and returns shared success envelope', async () => {
10+
await withNock(async () => {
11+
const api = mockAuthTokenMintRequest();
12+
13+
const result = await runToolCommand({
14+
input: {
15+
command: 'auth token',
16+
repo: 'throw-if-null/orfe',
17+
},
18+
});
19+
20+
assert.deepEqual(result, {
21+
ok: true,
22+
command: 'auth token',
23+
repo: 'throw-if-null/orfe',
24+
data: {
25+
bot: 'greg',
26+
app_slug: 'GR3G-BOT',
27+
repo: 'throw-if-null/orfe',
28+
token: 'ghs_123',
29+
expires_at: '2026-04-06T12:00:00Z',
30+
auth_mode: 'github-app',
31+
},
32+
});
33+
assert.equal(api.isDone(), true);
34+
});
35+
});
36+
37+
test('executeOrfeTool rejects bot override input for auth token', async () => {
38+
const result = await runToolCommand({
39+
input: {
40+
command: 'auth token',
41+
bot: 'greg',
42+
repo: 'throw-if-null/orfe',
43+
},
44+
});
45+
46+
assert.deepEqual(result, {
47+
ok: false,
48+
command: 'auth token',
49+
error: {
50+
code: 'invalid_usage',
51+
message: 'Command "auth token" does not accept input field "bot".',
52+
retryable: false,
53+
},
54+
});
55+
});

0 commit comments

Comments
 (0)