Skip to content

Commit 497773b

Browse files
committed
Reorganize runtime into explicit layer directories
1 parent 98dfcbc commit 497773b

100 files changed

Lines changed: 1217 additions & 1064 deletions

File tree

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: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The current command architecture uses explicit vertical command slices under `sr
1717
## Major runtime parts
1818

1919
### 1. OpenCode plugin entrypoint
20-
Represented by `src/plugin.ts`, exported from the package at `./plugin`, and consumed by repositories through the `opencode.json` `plugin` array.
20+
Represented by `src/opencode/plugin.ts`, with OpenCode-specific caller resolution in `src/opencode/context.ts` and tool execution in `src/opencode/tool.ts`, exported from the package at `./plugin`, and consumed by repositories through the `opencode.json` `plugin` array.
2121

2222
Responsibilities:
2323
- expose the `orfe` tool through `OrfePlugin`
@@ -30,7 +30,7 @@ It must not:
3030
- pass raw OpenCode runtime objects into the core
3131

3232
### 2. CLI entrypoint
33-
Represented by `src/cli.ts` and `src/command.ts`.
33+
Represented by `src/cli/entrypoint.ts`, `src/cli/run.ts`, `src/cli/parse.ts`, `src/cli/help.ts`, and `src/cli/types.ts`.
3434

3535
Responsibilities:
3636
- support commandless invocation as a help/noop path
@@ -42,7 +42,7 @@ Responsibilities:
4242
The CLI is intentionally a thin adapter. It renders root, group, and leaf help from the registered command catalog rather than maintaining a separate hardcoded command map.
4343

4444
### 3. Core runtime
45-
Represented by `src/core.ts` plus the command catalog under `src/commands/`.
45+
Represented by `src/core/run.ts`, `src/core/context.ts`, `src/core/types.ts`, plus the command catalog under `src/commands/`.
4646

4747
Responsibilities:
4848
- load repo-local config
@@ -58,7 +58,7 @@ The core is runtime-agnostic and must remain callable from both CLI and plugin e
5858
It is also the future extension host, but it must preserve the same wrapper/core and plain-data boundaries while doing so.
5959

6060
### 4. Config layer
61-
Current examples include `src/config.ts` and `.orfe/config.json`.
61+
Current examples include `src/config/repo-config.ts`, `src/config/auth-config.ts`, `src/config/project-defaults.ts`, `src/config/repository-ref.ts`, `src/config/shared.ts`, and `.orfe/config.json`.
6262

6363
Responsibilities:
6464
- hold repo-local non-secret configuration
@@ -81,15 +81,15 @@ Responsibilities:
8181
- stay below repository workflow policy rather than interpreting ownership or orchestration semantics
8282

8383
### 6. Auth layer
84-
Current examples include `src/github.ts` and machine-local auth config.
84+
Current examples include `src/github/installation-auth.ts`, `src/github/jwt.ts`, and machine-local auth config.
8585

8686
Responsibilities:
8787
- load machine-local per-bot GitHub App credentials
8888
- mint installation tokens
8989
- keep secret-bearing auth details outside repo-local config
9090

9191
### 7. GitHub adapter layer
92-
Current examples include the slice handlers under `src/commands/**/handler.ts` and the shared GitHub client factory in `src/github.ts`.
92+
Current examples include the slice handlers under `src/commands/**/handler.ts` and the shared GitHub client factory in `src/github/client-factory.ts`.
9393

9494
Responsibilities:
9595
- use Octokit REST where appropriate for issue and PR behavior
@@ -113,13 +113,13 @@ Enabled does not mean validly configured.
113113

114114
```mermaid
115115
graph TD
116-
Plugin[OpenCode plugin<br/>src/plugin.ts] --> Core[Core runtime<br/>src/core.ts]
117-
CLI[CLI entrypoint<br/>src/cli.ts + src/command.ts] --> Core
116+
Plugin[OpenCode plugin<br/>src/opencode/plugin.ts + tool.ts + context.ts] --> Core[Core runtime<br/>src/core/run.ts]
117+
CLI[CLI entrypoint<br/>src/cli/entrypoint.ts + run.ts + parse.ts + help.ts] --> Core
118118
119-
Core --> Config[Repo config<br/>src/config.ts]
119+
Core --> Config[Repo config<br/>src/config/*.ts]
120120
Core --> Templates[Templates modules<br/>src/templates/*.ts]
121-
Core --> Auth[Caller bot + auth config]
122-
Core --> GitHub[GitHub client factory<br/>src/github.ts]
121+
Core --> Auth[Caller bot + auth config<br/>src/config/auth-config.ts + src/github/installation-auth.ts]
122+
Core --> GitHub[GitHub client factory<br/>src/github/client-factory.ts]
123123
Core --> Registry[Generic command registry<br/>src/commands/registry/index.ts]
124124
Core --> Extensions[Installed extensions<br/>optional + namespaced]
125125
@@ -213,7 +213,7 @@ To add a new command:
213213
- register the slice in `src/commands/index.ts`
214214
- use or extend `<group>/shared.ts` only for helper logic genuinely shared by multiple commands in that group
215215

216-
Command-specific tests live beside the slice by default. Cross-cutting CLI, core, wrapper, plugin, and package-level tests remain in `test/`.
216+
Command-specific tests live beside the slice by default. Cross-cutting CLI, core, OpenCode tool/plugin, and package-level tests remain in `test/`.
217217

218218
## Architectural rules to keep in mind
219219

docs/orfe/spec.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ For terminology in this spec:
6060

6161
- npm package name: `@mirzamerdovic/orfe`
6262
- installed executable name: `orfe`
63-
- package CLI entrypoint: package `bin.orfe -> dist/cli.js`
63+
- package CLI entrypoint: package `bin.orfe -> dist/cli/entrypoint.js`
6464
- primary public distribution is npmjs.org (`https://registry.npmjs.org`)
6565
- supported invocation path is `npx @mirzamerdovic/orfe`
6666
- package-artifact installs produced by `npm pack` remain valid for local development

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
"description": "Generic GitHub operations runtime for humans and agents",
55
"license": "MIT",
66
"type": "module",
7-
"main": "./dist/wrapper.js",
7+
"main": "./dist/opencode/tool.js",
88
"exports": {
9-
".": "./dist/wrapper.js",
10-
"./plugin": "./dist/plugin.js",
11-
"./server": "./dist/plugin.js"
9+
".": "./dist/opencode/tool.js",
10+
"./plugin": "./dist/opencode/plugin.js",
11+
"./server": "./dist/opencode/plugin.js"
1212
},
1313
"bin": {
14-
"orfe": "./dist/cli.js"
14+
"orfe": "./dist/cli/entrypoint.js"
1515
},
1616
"files": [
1717
"dist",
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import assert from 'node:assert/strict';
22

33
import { test } from 'vitest';
4-
import { runCliEntrypoint } from './cli.js';
4+
import { runCliEntrypoint } from './entrypoint.js';
5+
6+
import type { runCli } from './run.js';
57

68
test('runCliEntrypoint forwards explicit argv to runCli and sets process.exitCode', async () => {
79
const observedCalls: string[][] = [];
810
const processStub: { exitCode: number | undefined } = { exitCode: undefined };
11+
const runCliImpl: typeof runCli = async (args) => {
12+
observedCalls.push(args);
13+
return 23;
14+
};
915

1016
const exitCode = await runCliEntrypoint({
1117
argv: ['issue', 'get', '--issue-number', '14'],
12-
runCliImpl: async (args) => {
13-
observedCalls.push(args);
14-
return 23;
15-
},
18+
runCliImpl,
1619
processImpl: processStub,
1720
});
1821

@@ -24,15 +27,16 @@ test('runCliEntrypoint forwards explicit argv to runCli and sets process.exitCod
2427
test('runCliEntrypoint falls back to process argv tail when argv is omitted', async () => {
2528
const processStub: { exitCode: number | undefined } = { exitCode: undefined };
2629
const originalArgv = process.argv;
30+
const runCliImpl: typeof runCli = async (args) => {
31+
assert.deepEqual(args, ['issue', 'get', '--issue-number', '14']);
32+
return 0;
33+
};
2734

2835
process.argv = ['/usr/bin/node', '/tmp/orfe', 'issue', 'get', '--issue-number', '14'];
2936

3037
try {
3138
const exitCode = await runCliEntrypoint({
32-
runCliImpl: async (args) => {
33-
assert.deepEqual(args, ['issue', 'get', '--issue-number', '14']);
34-
return 0;
35-
},
39+
runCliImpl,
3640
processImpl: processStub,
3741
});
3842

src/cli.ts renamed to src/cli/entrypoint.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { fileURLToPath } from 'node:url';
44

5-
import { runCli } from './command.js';
5+
import { runCli } from './run.js';
66

77
export async function runCliEntrypoint(options: {
88
argv?: string[];

src/cli/help.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { getCliCommonOptions, getGroupDefinitions, listCommandDefinitions, listCommandGroups, type CommandDefinition, type CommandOptionDefinition } from '../commands/registry/index.js';
2+
3+
import type { OrfeCommandGroup } from './types.js';
4+
5+
export function renderRootHelp(): string {
6+
const commandGroups = listCommandGroups();
7+
const rootExamples = listCommandDefinitions()
8+
.flatMap((definition) => definition.examples)
9+
.filter((example, index, examples) => example.trim().length > 0 && examples.indexOf(example) === index)
10+
.slice(0, 2);
11+
12+
return [
13+
'orfe - generic GitHub operations runtime',
14+
'',
15+
'Usage:',
16+
` orfe <${commandGroups.join('|')}> <command> [options]`,
17+
' orfe --help',
18+
' orfe --version',
19+
'',
20+
'Command groups:',
21+
...commandGroups.map((group) => ` ${group}`),
22+
'',
23+
'Examples:',
24+
...(rootExamples.length > 0 ? rootExamples.map((example) => ` ${example}`) : [' orfe --help']),
25+
'',
26+
'Run `orfe <group> --help` for group-specific help.',
27+
].join('\n');
28+
}
29+
30+
export function renderGroupHelp(group: OrfeCommandGroup): string {
31+
const groupDefinitions = getGroupDefinitions(group);
32+
33+
return [
34+
`orfe ${group}`,
35+
'',
36+
'Usage:',
37+
` orfe ${group} <command> [options]`,
38+
'',
39+
'Commands:',
40+
...groupDefinitions.map((definition) => ` ${definition.leaf} - ${definition.purpose}`),
41+
'',
42+
'Example:',
43+
` ${groupDefinitions[0]?.examples[0] ?? `orfe ${group} --help`}`,
44+
].join('\n');
45+
}
46+
47+
export function renderLeafHelp(commandDefinition: CommandDefinition): string {
48+
const requiredOptions = commandDefinition.options.filter((option) => option.required);
49+
const optionalOptions = dedupeOptionDefinitions([
50+
...commandDefinition.options.filter((option) => !option.required),
51+
...getCliCommonOptions().filter((option) => !requiredOptions.some((requiredOption) => requiredOption.flag === option.flag)),
52+
]);
53+
54+
return [
55+
`${commandDefinition.name}`,
56+
'',
57+
`Purpose: ${commandDefinition.purpose}`,
58+
`Usage: ${commandDefinition.usage}`,
59+
'',
60+
'Required options:',
61+
...(requiredOptions.length > 0 ? requiredOptions.map(formatOptionLine) : [' (none)']),
62+
'',
63+
'Optional options:',
64+
...optionalOptions.map(formatOptionLine),
65+
'',
66+
`Success: ${commandDefinition.successSummary}`,
67+
'',
68+
'Examples:',
69+
...commandDefinition.examples.map((example) => ` ${example}`),
70+
'',
71+
'JSON success shape example:',
72+
` ${JSON.stringify(commandDefinition.successDataExample)}`,
73+
].join('\n');
74+
}
75+
76+
function dedupeOptionDefinitions(optionDefinitions: CommandOptionDefinition[]): CommandOptionDefinition[] {
77+
const seenFlags = new Set<string>();
78+
79+
return optionDefinitions.filter((optionDefinition) => {
80+
if (seenFlags.has(optionDefinition.flag)) {
81+
return false;
82+
}
83+
84+
seenFlags.add(optionDefinition.flag);
85+
return true;
86+
});
87+
}
88+
89+
function formatOptionLine(optionDefinition: CommandOptionDefinition): string {
90+
return ` ${optionDefinition.flag} - ${optionDefinition.description}`;
91+
}

0 commit comments

Comments
 (0)