Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 1 addition & 76 deletions .auxiliary/notes/issues.md
Original file line number Diff line number Diff line change
@@ -1,78 +1,3 @@
# Known Issues

## CLI Parser Failure with tyro

**Discovered**: 2025-11-09 during Detextive 2.0 port verification

**Status**: Pre-existing issue (exists before Detextive 2.0 port)

**Severity**: Critical - CLI is completely non-functional

### Description

The mimeogram CLI fails to start with a tyro parser error:

```
AssertionError: UnsupportedStructTypeMessage(message="Empty hints for <slot wrapper '__init__' of '_io.TextIOWrapper' objects>!")
```

### Reproduction

```bash
hatch run mimeogram --help
# or any other command: version, create, apply, provide-prompt
```

### Analysis

The error originates from `tyro` attempting to parse the CLI structure and encountering a type that lacks proper type hints. The error occurs in:

```
File "/root/.local/share/hatch/env/.../tyro/_parsers.py", line 113, in from_callable_or_type
assert not isinstance(out, UnsupportedStructTypeMessage), out
```

The error mentions `_io.TextIOWrapper`, suggesting that somewhere in the command classes or their dependencies, there's a reference to stdin/stdout/stderr or file handles that tyro cannot introspect.

### Timeline

- **Commit 556db71** (Merge PR #9 - appcore cutover): Error present
- **Commit fac1d9f** (Integrate detextive package): Error present
- **Commit c1401a1** (Port to Detextive 2.0): Error present
- **Commit 32a777f** (Fix linter errors): Error present

This indicates the issue was introduced during the appcore refactor (PR #9), not by the Detextive 2.0 port.

### Investigation Points

1. **appcore type annotations**: The issue likely stems from how `appcore` types are exposed to tyro
2. **CLI command definitions**: Check `cli.py`, `create.py`, `apply.py`, `prompt.py` for problematic type hints
3. **TextIOWrapper references**: Search for uses of `sys.stdin`, `sys.stdout`, `sys.stderr` that may need explicit typing

Confirmed uses in codebase:
- `sources/mimeogram/apply.py:134`: `__.sys.stdin.isatty()`
- `sources/mimeogram/apply.py:144`: `__.sys.stdin.read()`
- `sources/mimeogram/interactions.py:76`: `__.sys.stdout.flush()`
- `sources/mimeogram/display.py:60`: `__.sys.stdin.isatty()`

### Suggested Fix

Based on the user's suggestion: Switch to `emcd-appcore[cli]` which likely includes additional dependencies or type stubs that help tyro properly parse the CLI structure.

### Impact

- **Tests**: All 173 tests pass (tests don't exercise CLI parsing, they import modules directly)
- **Linters**: Pass cleanly (ruff and pyright)
- **Detextive integration**: Working correctly
- **CLI functionality**: Completely broken - cannot run any commands

### Workaround

None currently available. The application can be used programmatically by importing modules directly, but the CLI is unusable.

### Next Steps

1. Try switching dependency from `emcd-appcore~=1.4` to `emcd-appcore[cli]~=1.4`
2. If that doesn't resolve it, investigate the specific type annotation that tyro cannot parse
3. Consider adding explicit type annotations to any stdin/stdout/stderr usage
4. May need to report issue to `tyro` if it's a limitation in their type introspection
No known issues at this time.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ dependencies = [
'aiofiles',
'detextive~=2.0',
'dynadoc~=1.4',
'emcd-appcore~=1.4',
'emcd-appcore[cli]~=1.6',
'exceptiongroup',
'frigid~=4.1',
'gitignorefile',
Expand Down
1 change: 1 addition & 0 deletions sources/mimeogram/__/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
# --- END: Injected by Copier ---

from absence import Absential, absent, is_absent
from appcore import cli as appcore_cli
from appcore import asyncf
from appcore import generics

Expand Down
17 changes: 11 additions & 6 deletions sources/mimeogram/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,25 @@ def provide_configuration_edits(
return ( )


_application_default = __.appcore.application.Information( name = 'mimeogram' )
_inscription_mode_default = (
__.appcore.inscription.Control(
mode = __.appcore.inscription.Presentations.Rich,
__.appcore_cli.InscriptionControl(
presentation = __.appcore.inscription.Presentations.Rich,
)
)


class Cli(
__.immut.DataclassObject,
decorators = ( __.simple_tyro_class, ),
):
''' Mimeogram: hierarchical data exchange between humans and LLMs. '''

application: __.appcore.application.Information
application: __.appcore.application.Information = (
__.dcls.field( default_factory = lambda: _application_default ) )
configfile: __.typx.Optional[ str ] = None
# display: ConsoleDisplay
inscription: __.appcore.inscription.Control = (
inscription: __.appcore_cli.InscriptionControl = (
__.dcls.field( default_factory = lambda: _inscription_mode_default ) )
command: __.typx.Union[
__.typx.Annotated[
Expand All @@ -93,14 +97,15 @@ class Cli(

async def __call__( self ):
''' Invokes command after library preparation. '''
nomargs = self.prepare_invocation_args( )
async with __.ctxl.AsyncExitStack( ) as exits:
nomargs = self.prepare_invocation_args( exits )
auxdata = await __.appcore.prepare( exits = exits, **nomargs )
await self.command( auxdata = auxdata )
# await self.command( auxdata = auxdata, display = self.display )

def prepare_invocation_args(
self,
exits: __.ctxl.AsyncExitStack,
) -> __.cabc.Mapping[ str, __.typx.Any ]:
''' Prepares arguments for initial configuration. '''
configedits: __.appcore.dictedits.Edits = (
Expand All @@ -109,7 +114,7 @@ def prepare_invocation_args(
application = self.application,
configedits = configedits,
environment = True,
inscription = self.inscription,
inscription = self.inscription.as_control( exits ),
)
if self.configfile: args[ 'configfile' ] = self.configfile
return args
Expand Down