You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* refactor: restructure main function and improve error handling
* chore: update project description and enhance validation script
* chore: update bun version to 1.3.9
* chore: update validation steps to include build check
* chore: remove duplicate version entry from CHANGELOG
* fix: formatting
Copy file name to clipboardExpand all lines: .archgate/adrs/ARCH-001-command-structure.md
+73-22Lines changed: 73 additions & 22 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -20,31 +20,34 @@ The explicit register pattern strikes the right balance: each command owns its r
20
20
21
21
## Decision
22
22
23
-
Commands live in `src/commands/` and export a `register*Command(program)` function. The main entry point (`src/cli.ts`) explicitly imports and calls each register function. Subcommands (e.g., `adr create`, `adr list`) use nested directories with an `index.ts` that composes the subcommand group.
23
+
Commands live in src/commands/ and export a register\*Command(program) function. The main entry point (src/cli.ts) explicitly imports and calls each register function. Subcommands (e.g., adr create, adr list) use nested directories with an index.ts that composes the subcommand group.
24
24
25
25
**Key constraints:**
26
26
27
-
1.**One command per file** — Each `.ts` file in `src/commands/` defines exactly one command (or one command group via its `index.ts`)
28
-
2.**Explicit registration** — Every command must be manually imported and registered in `src/cli.ts`. No auto-discovery.
27
+
1.**One command per file** — Each .ts file in src/commands/ defines exactly one command (or one command group via its index.ts)
28
+
2.**Explicit registration** — Every command must be manually imported and registered in src/cli.ts. No auto-discovery.
29
29
3.**Thin commands** — Command files handle I/O only: parse arguments, call engine/helpers, format output. No business logic.
30
30
4.**In-process execution** — Commands run in the same Bun process as the CLI entry point. No child process spawning.
31
+
5.**main() wrapper in entry point** — All async bootstrap logic in src/cli.ts MUST be wrapped in an async function main() called via .catch(). Top-level await is forbidden in the entry point.
31
32
32
33
## Do's and Don'ts
33
34
34
35
### Do
35
36
36
-
- Export a `register*Command` function from each command module
37
+
- Export a register\*Command function from each command module
37
38
- Keep commands thin: parse args, call helpers/engine, format output
38
-
- Use `src/commands/<name>.ts` for top-level commands
39
-
- Use `src/commands/<name>/index.ts` for command groups with subcommands
40
-
- Import the register function explicitly in `src/cli.ts`
39
+
- Use src/commands/<name>.ts for top-level commands
40
+
- Use src/commands/<name>/index.ts for command groups with subcommands
41
+
- Import the register function explicitly in src/cli.ts
42
+
- Wrap all async logic in src/cli.ts in an async function main() and call it as main().catch((err) => { logError(String(err)); process.exit(2); }) — this is required for bun build --compile --bytecode compatibility
41
43
42
44
### Don't
43
45
44
-
- Don't put business logic in command files — move it to `src/engine/`, `src/helpers/`, or `src/formats/`
45
-
- Don't use `executableDir()` for command discovery
46
-
- Don't call `.parse()` in command files — the entry point handles parsing
46
+
- Don't put business logic in command files — move it to src/engine/, src/helpers/, or src/formats/
47
+
- Don't use executableDir() for command discovery
48
+
- Don't call .parse() in command files — the entry point handles parsing
47
49
- Don't create commands that spawn child processes for subcommand execution
50
+
- Don't use top-level await in src/cli.ts — bun build --compile --bytecode (the binary compiler) rejects it even though bun run and tsc accept it. The symptom is a build-time parse error: "await" can only be used inside an "async" function
48
51
49
52
## Implementation Pattern
50
53
@@ -105,6 +108,49 @@ export function registerCheckCommand(program: Command) {
105
108
}
106
109
```
107
110
111
+
### Entry Point main() Pattern
112
+
113
+
bun build --compile --bytecode — the command used to produce standalone binaries — rejects top-level await at parse time, even though bun run and tsc both accept it. All async bootstrap logic in src/cli.ts MUST be wrapped in an async function main().
114
+
115
+
```typescript
116
+
// src/cli.ts — GOOD: all async logic wrapped in main()
117
+
import { logError } from"./helpers/log";
118
+
119
+
// Synchronous bootstrap checks can remain at top level
120
+
if (!semver.satisfies(Bun.version, ">=1.2.21"))
121
+
thrownewError("You need to update Bun to version 1.2.21 or higher");
awaitinstallGit(); // ERROR: "await" can only be used inside an "async" function
149
+
150
+
const program =newCommand().name("archgate").version(packageJson.version);
151
+
awaitprogram.parseAsync(process.argv); // also breaks
152
+
```
153
+
108
154
### Subcommand Group Pattern
109
155
110
156
```typescript
@@ -131,37 +177,42 @@ export function registerAdrCommand(program: Command) {
131
177
132
178
### Positive
133
179
134
-
-**In-process execution enables testing** — Commands can be tested by calling `register*Command()` directly, without spawning subprocesses or mocking executables
135
-
-**Explicit imports make dependencies clear** — Opening `src/cli.ts` shows every command the CLI supports. No hidden commands loaded at runtime.
136
-
-**Subcommand nesting is straightforward** — Command groups use the same pattern as top-level commands, with an `index.ts` that composes children
137
-
-**Type-safe registration** — Commander.js `@commander-js/extra-typings` provides full type inference for options and arguments within each register function
180
+
-**In-process execution enables testing** — Commands can be tested by calling register\*Command() directly, without spawning subprocesses or mocking executables
181
+
-**Explicit imports make dependencies clear** — Opening src/cli.ts shows every command the CLI supports. No hidden commands loaded at runtime.
182
+
-**Subcommand nesting is straightforward** — Command groups use the same pattern as top-level commands, with an index.ts that composes children
183
+
-**Type-safe registration** — Commander.js @commander-js/extra-typings provides full type inference for options and arguments within each register function
184
+
-**Binary-compatible entry point** — The main() wrapper pattern ensures src/cli.ts compiles cleanly with bun build --compile --bytecode for standalone binary distribution
138
185
139
186
### Negative
140
187
141
-
-**Manual import bookkeeping** — Each new command requires adding an import and registration call in `src/cli.ts`. This is a minor overhead for a CLI with fewer than 15 commands.
188
+
-**Manual import bookkeeping** — Each new command requires adding an import and registration call in src/cli.ts. This is a minor overhead for a CLI with fewer than 15 commands.
142
189
-**No hot-reload of commands** — Adding a new command requires restarting the CLI process. Acceptable for a development tool.
143
190
144
191
### Risks
145
192
146
-
-**Stale imports when commands are removed** — If a command file is deleted but its import in `src/cli.ts` is not removed, TypeScript will catch the error at compile time. The `bun run typecheck` step in the validation pipeline prevents this from reaching production.
147
-
-**Command group index.ts confused with barrels** — The `index.ts` files in command group directories (e.g., `src/commands/adr/index.ts`) contain real composition logic, not re-exports. [ARCH-004 — No Barrel Files](./ARCH-004-no-barrel-files.md) explicitly permits `index.ts` files with logic.
193
+
-**Stale imports when commands are removed** — If a command file is deleted but its import in src/cli.ts is not removed, TypeScript will catch the error at compile time. The bun run typecheck step in the validation pipeline prevents this from reaching production.
194
+
-**Command group index.ts confused with barrels** — The index.ts files in command group directories (e.g., src/commands/adr/index.ts) contain real composition logic, not re-exports. ARCH-004 No Barrel Files explicitly permits index.ts files with logic.
195
+
-**Top-level await regression** — A developer unfamiliar with the --bytecode constraint may introduce top-level await back into src/cli.ts. Mitigation: The bun run build:check step in the validate pipeline catches this immediately — bun run validate will fail locally before the code reaches CI.
148
196
149
197
## Compliance and Enforcement
150
198
151
199
### Automated Enforcement
152
200
153
-
-**Archgate rule**`ARCH-001/register-function-export`: Scans all command files under `src/commands/` (excluding `index.ts` group files) and verifies each exports a `register*Command` function. Severity: `error`.
154
-
-**Archgate rule**`ARCH-001/no-business-logic`: Detects complex data transformation patterns in command files that should be in helpers. Severity: `error`.
201
+
-**Archgate rule** ARCH-001/register-function-export: Scans all command files under src/commands/ (excluding index.ts group files) and verifies each exports a register\*Command function. Severity: error.
202
+
-**Archgate rule** ARCH-001/no-business-logic: Detects complex data transformation patterns in command files that should be in helpers. Severity: error.
203
+
-**Build check** bun run build:check: Compiles src/cli.ts with bun build --compile --bytecode as part of bun run validate. A top-level await regression causes an immediate, descriptive parse error.
155
204
156
205
### Manual Enforcement
157
206
158
207
Code reviewers MUST verify:
159
208
160
-
1. New commands are imported and registered in `src/cli.ts`
209
+
1. New commands are imported and registered in src/cli.ts
161
210
2. Command files delegate to engine/helpers for business logic
162
-
3. Command group `index.ts` files contain composition logic, not just re-exports
211
+
3. Command group index.ts files contain composition logic, not just re-exports
212
+
4. No top-level await has been introduced in src/cli.ts — all async logic must be inside main()
Copy file name to clipboardExpand all lines: .claude/agent-memory/archgate-developer/MEMORY.md
+7-1Lines changed: 7 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -34,13 +34,19 @@ Skipping steps 2 or 3 is a workflow violation. The user should NEVER have to inv
34
34
-**oxlint `no-negated-condition`** — Always write ternaries with the positive condition first: `x === null ? A : B` not `x !== null ? B : A`. Applies to both `if/else` blocks and ternary expressions.
35
35
-**oxlint `no-unused-vars` on catch parameters** — Use bare `catch { }` (no parameter) when the caught error is not used. `catch (err) { }` with unused `err` triggers the rule.
36
36
-**oxlint `no-await-in-loop`** — Sequential `await` inside a `for` loop is flagged (warning). When the sequential order is intentional (e.g., build steps with per-step output), suppress with `// oxlint-disable-next-line no-await-in-loop -- <reason>`.
37
+
-**`bun build --compile --bytecode` rejects top-level `await`** — Even though `bun run` and `tsc` handle top-level `await` fine, the Bun bytecode compiler (`--bytecode`) does not. In `src/cli.ts`, all async bootstrap logic MUST be wrapped in `async function main() { ... }` and called as `main().catch((err) => { logError(String(err)); process.exit(2); })`. Never use top-level `await` in the CLI entry point. See ARCH-001 Do's for the documented pattern.
38
+
-**npm `main` field always gets included in publish** — `"main"` in `package.json` is always included in `npm publish` regardless of the `files` array. If the package doesn't need a default entry point (only sub-path exports like `./rules`), remove `main` entirely to avoid bundling the CLI entry point into the npm package.
37
39
38
40
## Validation Pipeline
39
41
40
-
-`bun run validate` is the mandatory gate: lint → typecheck → format:check → test → ADR check
42
+
-`bun run validate` is the mandatory gate: lint → typecheck → format:check → test → ADR check → build:check
41
43
- All ADR rule severities are `error` (not `warning`) — violations are hard blockers
42
44
- The pipeline is fail-fast — fix failures in order
43
45
46
+
## CLI Repo Quirk
47
+
48
+
-**`archgate` command = `bun run cli`** — This is the CLI repo itself, so the `archgate` binary is not installed in PATH. Use `bun run cli <command>` (e.g., `bun run cli check`, `bun run cli adr list`) instead of `archgate <command>`. The `bun run cli` script maps to `bun run src/cli.ts`.
49
+
44
50
## MCP Tools Structure
45
51
46
52
- MCP tools live in `src/mcp/tools/` — one file per tool (check, list-adrs, review-context, session-context)
Copy file name to clipboardExpand all lines: CLAUDE.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,14 +18,14 @@ bun run typecheck # tsc --build
18
18
bun run format # prettier --write
19
19
bun run format:check # prettier --check
20
20
bun test# all tests
21
-
bun run validate # MANDATORY: lint + typecheck + format + test + ADR check
21
+
bun run validate # MANDATORY: lint + typecheck + format + test + ADR check + build check
22
22
bun run build # binaries → dist/ (darwin-arm64, linux-x64)
23
23
bun run commit # conventional commit wizard
24
24
```
25
25
26
26
## Validation Gate
27
27
28
-
**`bun run validate` must pass before any task is considered complete.** Fail-fast pipeline: lint → typecheck → format → test → ADR check. Mirrors CI in `.github/workflows/code-pull-request.yml`.
28
+
**`bun run validate` must pass before any task is considered complete.** Fail-fast pipeline: lint → typecheck → format → test → ADR check → build check. Mirrors CI in `.github/workflows/code-pull-request.yml`.
0 commit comments