@@ -32,7 +32,6 @@ import {
3232 DIM ,
3333 RESET ,
3434 RED ,
35- YELLOW ,
3635 log ,
3736 hasFlag ,
3837 flagValue ,
@@ -47,7 +46,7 @@ import {
4746 rulesAnalyze ,
4847 rulesValidate ,
4948 rulesStats ,
50- rulesSplitNotice ,
49+ rulesSplit ,
5150 kernelResolve ,
5251 kernelExplain ,
5352 kernelUsage ,
@@ -60,6 +59,8 @@ import {
6059import { runDoctor , printDoctor , defaultDoctorPaths } from "./doctor.js" ;
6160import { buildReport , printReport } from "./report.js" ;
6261import { runHookTest , printHookTest , defaultHookPath } from "./hook.js" ;
62+ import { dispatchRefresh } from "./refresh.js" ;
63+ import { interceptHelp } from "./help.js" ;
6364
6465// ── Version ───────────────────────────────────────────────────
6566export function getVersion ( ) : string {
@@ -94,7 +95,7 @@ ${BOLD}Rituals:${RESET}
9495 loadout-os doctor [--json] Read-only health screen
9596 loadout-os report [--index <p>] [--jsonl <p>] Observability over usage.jsonl
9697 loadout-os hook test [--prompt "<text>"] Drive the runtime hook on a sample prompt
97- loadout-os refresh (not yet implemented — use the Index Freshness Ritual )
98+ loadout-os refresh [--store <d>] [--dest <p>] [--dry-run] Index Freshness Ritual (index → validate → write global )
9899
99100${ BOLD } Options:${ RESET }
100101 --json Machine-readable output (doctor/report/most verbs)
@@ -129,18 +130,23 @@ ${BOLD}loadout-os rules${RESET} — wraps @mcptoolshop/claude-rules
129130 loadout-os rules analyze <CLAUDE.md> [--rules-dir <dir>] [--json]
130131 loadout-os rules validate [--rules-dir <dir>] [--lazy] [--repo-root <dir>] [--json]
131132 loadout-os rules stats <CLAUDE.md> [--rules-dir <dir>] [--json]
132- loadout-os rules split (interactive — not wrapped; use the claude-rules bin )
133+ loadout-os rules split [CLAUDE.md] [--yes] [--dry-run] (interactive — passes through to claude-rules)
133134` ;
134135}
135136
136137// ── Namespace dispatchers ─────────────────────────────────────
137138function dispatchMemories ( args : string [ ] ) : void {
138- if ( hasFlag ( args , "help" ) || args . length === 0 ) {
139+ // Namespace-level help: `memories --help` or `memories` (no subcommand).
140+ // A help flag WITH a subcommand falls through to per-command help below.
141+ const firstIsSub = args . length > 0 && ! args [ 0 ] . startsWith ( "-" ) ;
142+ if ( args . length === 0 || ( hasFlag ( args , "help" ) && ! firstIsSub ) ) {
139143 log ( memoriesHelp ( ) ) ;
140144 return ;
141145 }
142146 const sub = args [ 0 ] ;
143147 const rest = args . slice ( 1 ) ;
148+ // Per-command help: `memories index --help`, etc.
149+ if ( interceptHelp ( `memories ${ sub } ` , rest ) ) return ;
144150 switch ( sub ) {
145151 case "index" :
146152 return memoriesIndex ( rest ) ;
@@ -160,12 +166,20 @@ function dispatchMemories(args: string[]): void {
160166}
161167
162168function dispatchRules ( args : string [ ] ) : void {
163- if ( hasFlag ( args , "help" ) || args . length === 0 ) {
169+ // Namespace-level help: `rules --help` or `rules` (no subcommand). A help flag
170+ // WITH a subcommand falls through to per-command help below — except `split`,
171+ // whose --help we own here (we never forward --help into the interactive bin).
172+ const firstIsSub = args . length > 0 && ! args [ 0 ] . startsWith ( "-" ) ;
173+ if ( args . length === 0 || ( hasFlag ( args , "help" ) && ! firstIsSub ) ) {
164174 log ( rulesHelp ( ) ) ;
165175 return ;
166176 }
167177 const sub = args [ 0 ] ;
168178 const rest = args . slice ( 1 ) ;
179+ // Per-command help: `rules analyze --help`, `rules split --help`, etc.
180+ // For split we intercept --help BEFORE the passthrough so the readline prompt
181+ // is never spawned just to ask for usage.
182+ if ( interceptHelp ( `rules ${ sub } ` , rest ) ) return ;
169183 switch ( sub ) {
170184 case "analyze" :
171185 return rulesAnalyze ( rest ) ;
@@ -174,7 +188,7 @@ function dispatchRules(args: string[]): void {
174188 case "stats" :
175189 return rulesStats ( rest ) ;
176190 case "split" :
177- return rulesSplitNotice ( ) ;
191+ return rulesSplit ( rest ) ;
178192 default :
179193 throw new CliError (
180194 "UNKNOWN_COMMAND" ,
@@ -231,6 +245,8 @@ function dispatchReport(args: string[]): void {
231245
232246function dispatchHook ( args : string [ ] ) : void {
233247 const sub = args [ 0 ] ;
248+ // `hook test --help` (or `hook --help`) → per-command help for "hook test".
249+ if ( interceptHelp ( "hook test" , args ) ) return ;
234250 if ( sub !== "test" ) {
235251 throw new CliError (
236252 "UNKNOWN_COMMAND" ,
@@ -255,14 +271,6 @@ function dispatchHook(args: string[]): void {
255271 }
256272}
257273
258- function refreshStub ( ) : void {
259- log ( ) ;
260- log ( ` ${ YELLOW } !${ RESET } ${ BOLD } loadout-os refresh${ RESET } is not yet implemented.` ) ;
261- log ( ` ${ DIM } It writes the live global index and needs a named compensator; deferred to a later wave.${ RESET } ` ) ;
262- log ( ` ${ DIM } For now, run the Index Freshness Ritual (claude-memories index → validate → copy to ~/.ai-loadout/index.json).${ RESET } ` ) ;
263- log ( ) ;
264- }
265-
266274// ── Main dispatch ─────────────────────────────────────────────
267275const FLAT_KERNEL = new Set ( [
268276 "resolve" ,
@@ -287,14 +295,29 @@ export function dispatch(args: string[]): void {
287295 return ;
288296 }
289297
290- if ( args . length === 0 || ( hasFlag ( args , "help" ) && ! [ "memories" , "rules" ] . includes ( args [ 0 ] ) ) ) {
298+ // Top-level help: no args, or a help flag with NO command in front of it
299+ // (the first token is itself a flag). A help flag AFTER a command routes to
300+ // that command's per-command help, handled inside each dispatcher / below.
301+ const firstIsCmd = args . length > 0 && ! args [ 0 ] . startsWith ( "-" ) ;
302+ if ( args . length === 0 || ( hasFlag ( args , "help" ) && ! firstIsCmd ) ) {
291303 log ( topLevelHelp ( ) ) ;
292304 return ;
293305 }
294306
295307 const cmd = args [ 0 ] ;
296308 const rest = args . slice ( 1 ) ;
297309
310+ // Per-command help for the flat verbs + rituals (namespaces handle their own
311+ // inside dispatchMemories/dispatchRules; hook handles "hook test" below).
312+ if (
313+ cmd !== "memories" &&
314+ cmd !== "rules" &&
315+ cmd !== "hook" &&
316+ interceptHelp ( cmd , rest )
317+ ) {
318+ return ;
319+ }
320+
298321 switch ( cmd ) {
299322 case "memories" :
300323 return dispatchMemories ( rest ) ;
@@ -307,7 +330,7 @@ export function dispatch(args: string[]): void {
307330 case "hook" :
308331 return dispatchHook ( rest ) ;
309332 case "refresh" :
310- return refreshStub ( ) ;
333+ return dispatchRefresh ( rest ) ;
311334 case "resolve" :
312335 return kernelResolve ( rest ) ;
313336 case "explain" :
0 commit comments