Imported from bensyverson/TextUI (
AGENTS.md). Install upstream withnpx skills add bensyverson/TextUI. Copyright stays with the author.
IMPORTANT: As you're implementing features, be sure to keep the DocC documentation up to date, updating README only if a new documentation file has been added, or if new users MUST know about your changes.
Overview
You are working on TextUI, a Swift library which is heavily modeled on SwiftUI, and makes it easy for SwiftUI developers to create expressive apps for the terminal.
Documentation
When you need additional context, consult the docs:
- README.md - Project overview and quick start
- DocC documentation - In-depth documentation about Operator's architecture, models and concepts
- project/ - Internal design documents and past project plans
API documentation is generated with DocC: swift package generate-documentation --target TextUI. Ensure any new or updated code keeps our DocC annotations up to date with 100% coverage.
General
- If a requirement is ambiguous or could be solved in several ways, choose the most idiomatic way to solve the problem in Swift if that would resolve the ambiguity.
- If the ambiguity is more about the requirement itself, or you face an architectural question, don't make a decision—stop and ask the user instead.
- Avoid dependencies unless the required functionality would be unreasonable to re-implement. If you MUST bring in a dependency, get the user's permission first.
- If a routine is more fragile or more complex than a simple CRUD operation, always write a unit test to verify a correct implementation.
- DO NOT begin a new chat by doing an extensive exploration of the entire codebase. That is wasteful, as this is a large codebase. Instead, read the README and use an Explore agent to read the DocC documentation if you want to get the lay of the land. Of course, once you have a specific need, you can explore as much of the code as you require.
Understand the "why"
Before you answer a question or respond to a request, you must understand why the user has made this request. Beware of the "XY Problem." If the user's motivation or goal is not 100% clear, first ask clarifying questions until you fully understand what they're trying to achieve.
Diverge, then converge
Once you understand the prompt, rather than jumping to a solution, you will use divergent thinking. Brainstorm other options. Weigh these options against the user's preferences and overall objectives. Then, converge on a recommendation, and ask for confirmation from the user. Only then can you fully converge and begin executing on a direction.
Analysis
It will sometimes be valuable to create a script or tool in order to aid your analysis. Before you do, check the scripts directory to make sure it doesn't already exist. If it doesn't, rather than creating a disposable or inline script, add it to the scripts directory, so the user can re-run the tool in the future.
Pre-completion Critique
Before you declare a task done, or a question answered, pause and critique your own work. Return to the context of the original user request—have you truly addressed their need? If you're producing code, do all linting and unit tests pass? If you're synthesizing or analyzing information, what are the gaps or weaknesses in your answer? What would a relevant and intelligent expert say about your work? If you identify serious flaws, keep working until you resolve them.
Tidiness
Don't create temporary files in the root of the directory and leave them there. If you truly need a transient file, that's fine, but delete it when you're done. But if the artifact is something valuable (such as an agent's report, or a script), please save it in the correct directory.
Git workflow
At moments when significant work has been completed and accepted by the user, offer to commit the changes for them. You may need to pull changes and resolve conflicts, which you should do for the user using git rebase whenever possible. If there is a real conflict, ask the user how they want to resolve it, explaining the situation clearly. Always commit all uncommitted files together, as your changes may depend on prior changes, and we don't want to commit the code in a half-working state.
Development workflow
- All Git commit messages should complete the sentence "This commit…", e.g. "Adds an email verification flow". You can then go on to detail the change with bullets or headers in the body of the commit.
- IMPORTANT: In this project we ALWAYS follow strict "red/green" TDD; write tests for all example cases we need to handle and any new methods we're implementing, verify that they fail, and then proceed to implement your code changes. If you must alter a previous test to get it to pass, explain exactly WHY to the user and get their consent.
- Before fixing a bug, try to create a regression test to catch it in the future.
- DO NOT begin a new chat by doing an extensive exploration of the entire codebase. That is wasteful, as this is a large codebase. Instead, read the README and use an Explore agent to read the DocC documentation if you want to get the lay of the land. Of course, once you have a specific need, you can explore as much of the code as you require.
Development Stage
- We are in BUILD mode. This library is pre-launch, with zero users or existing files. When refactoring or implementing new features, we NEVER need to consider backward compatibility. We can assume that every use of the project is green-field. We're trying to architect a clean, clear API and codebase without any baggage. With that said, make sure the user knows when you make breaking changes, and be sure to update any relevant unit tests.
- In build mode, we are ambitious. Even if you think a feature will take weeks or months, if it's important, let's take it on now. We will build what we know is needed to achieve the overall project goals, without taking shortcuts or implementing the MVP. But we will balance this with avoiding "future-proofing" or over-engineering.
Swift-specific
When writing Swift, adhere to these guidelines:
- Organize Source and Test files in folders rather than placing all source in
Sources. However, try not to nest folders more than one level deep. - Try to always make new types conform to
Friendly(it's defined astypealias Friendly = Codable & Hashable & Equatable & Sendable), even if you have no current plans to serialize or compare them. - In general, prefer a
structover afinal class, especially for data. When modeling durable objects that need to be referenced by many callers,final classmay be better-suited. Use anactorwhen we need to control access to shared mutable state or a shared access point such as a database connection or message queue. - Strongly prefer modern async / await APIs
- Ensure your work supports Swift 6's strict concurrency requirements. Resolve compiler warnings as you encounter them. Do not use workarounds like
nonisolated(unsafe)without permission. - Try to keep your code as cross-platform as possible; if you must use macOS or iOS-specific APIs, surround them with @available checks, and provide coverage for at least macOS and iOS. Whenever possible, stay within Foundation, so your code will compile on Linux/FreeBSD as well as Apple platforms.
- When using regex, always use the modern Swift Regex API over the old methods
- When initializing new objects, don't use the
.init()shorthand; it makes the Swift type checker work harder. - When initializing a variable with the result of an expression which uses generics, is chained, complex, or where the function signature could vary based on input (e.g. duplicate methods with the same name but different input & output types), give the type checker a hint by declaring the type of the return:
let output: String = {complex} - When creating a new combined library/executable, if the entire package is FooBar, name the library target & files FooBarCore, and the CLI target & files FooBarCommand.
- Nest subtype declarations within the type declaration when they're small (enums and small structs), rather than polluting the global namespace with new types.
- When creating a new group of tests (new struct), place them in a new Swift file in the correct folder, rather than adding them to an existing file.
- In general, stick to "one type per file;" don't define many types in a single source file. If you find yourself with a very long source file (over 200-300 lines), it's time to think about how to pull related methods into a separate file or extension.
- When extending a type, place the extension in its own file, named
BaseType+ExtensionName.swift—for example, if you need a custom Codable implementation forTimeline, it would beTimeline+Codable.swift. This applies to extending third-party types as well. - Prior to commiting changes, you must run the linter (
swiftformat . --lint) and re-run to format if necessary (swiftformat .), or fix any issues manually if necessary. You must also run the full test suite (swift test --quiet). Linting and testing are both pre-commit hooks, so this will allow you to catch problems before attempting a commit.