Compare commits
9 Commits
v0.1.0
...
e766210920
| Author | SHA1 | Date | |
|---|---|---|---|
| e766210920 | |||
| 6849631cc1 | |||
| 7c87d6f1f0 | |||
| 60723c33bc | |||
| 6195f7b8a3 | |||
| 801544951f | |||
| 65508d263f | |||
| 2bdfbe25d5 | |||
| 12d2577a31 |
260
AGENT.md
Normal file
260
AGENT.md
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
# AGENT.md
|
||||||
|
|
||||||
|
AI Agent Development Guide
|
||||||
|
|
||||||
|
This file defines the operational rules that AI agents must follow when working in this repository.
|
||||||
|
|
||||||
|
The goal is to ensure:
|
||||||
|
- reproducible AI-assisted development
|
||||||
|
- traceable decisions
|
||||||
|
- consistent repository history
|
||||||
|
- safe automated contributions
|
||||||
|
|
||||||
|
This document complements:
|
||||||
|
- `instructions-agent.md`
|
||||||
|
- `docs/ai-worklog.md`
|
||||||
|
- `docs/ai-prompts.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Core Principles
|
||||||
|
|
||||||
|
Agents must prioritize:
|
||||||
|
|
||||||
|
1. Deterministic changes
|
||||||
|
2. Minimal scope modifications
|
||||||
|
3. Explicit logging of work
|
||||||
|
4. Repository stability
|
||||||
|
|
||||||
|
Agents must never:
|
||||||
|
|
||||||
|
- fabricate repository state
|
||||||
|
- invent files that do not exist
|
||||||
|
- modify unrelated parts of the project
|
||||||
|
- skip validation steps
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Development Workflow
|
||||||
|
|
||||||
|
Agents operate using two primary task types:
|
||||||
|
|
||||||
|
## PLAN
|
||||||
|
|
||||||
|
Used to:
|
||||||
|
- investigate problems
|
||||||
|
- inspect repository state
|
||||||
|
- propose architecture changes
|
||||||
|
|
||||||
|
PLAN tasks must:
|
||||||
|
- analyze the current implementation
|
||||||
|
- identify the root cause
|
||||||
|
- propose minimal changes
|
||||||
|
|
||||||
|
PLAN tasks **must not modify code**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BUILD
|
||||||
|
|
||||||
|
Used to:
|
||||||
|
- implement changes
|
||||||
|
- update code
|
||||||
|
- add tests
|
||||||
|
- update documentation
|
||||||
|
|
||||||
|
BUILD tasks may:
|
||||||
|
- modify source files
|
||||||
|
- update tests
|
||||||
|
- update documentation
|
||||||
|
|
||||||
|
BUILD tasks must:
|
||||||
|
- remain minimal in scope
|
||||||
|
- preserve existing behavior unless explicitly changed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Validation Requirements
|
||||||
|
|
||||||
|
All BUILD tasks must run:
|
||||||
|
|
||||||
|
```
|
||||||
|
make fix
|
||||||
|
make quality
|
||||||
|
```
|
||||||
|
|
||||||
|
These commands must pass before changes are considered valid.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Logging System
|
||||||
|
|
||||||
|
All significant AI actions must be recorded.
|
||||||
|
|
||||||
|
Two log files are used:
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/ai-worklog.md
|
||||||
|
docs/ai-prompts.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Worklog Entries
|
||||||
|
|
||||||
|
Executed work must be recorded in:
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/ai-worklog.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Each entry must follow the standard format defined in `instructions-agent.md`.
|
||||||
|
|
||||||
|
Mandatory fields:
|
||||||
|
|
||||||
|
- Date (YYYY-MM-DD HH:MM)
|
||||||
|
- Task ID
|
||||||
|
- Agent
|
||||||
|
- Task
|
||||||
|
- Objective
|
||||||
|
- Scope
|
||||||
|
- Files Modified / Files Inspected
|
||||||
|
- Key Decisions / Proposed Changes
|
||||||
|
- Validation
|
||||||
|
- Result
|
||||||
|
- Open Issues
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Prompt Log
|
||||||
|
|
||||||
|
Prompts that trigger significant work must be recorded in:
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/ai-prompts.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Each prompt entry must include:
|
||||||
|
|
||||||
|
- Date
|
||||||
|
- Task ID
|
||||||
|
- Agent
|
||||||
|
- Task
|
||||||
|
- Scope
|
||||||
|
- Prompt Summary
|
||||||
|
- Result Summary
|
||||||
|
|
||||||
|
Chain-of-thought reasoning must **never be stored**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task IDs
|
||||||
|
|
||||||
|
All tasks must include a unique identifier.
|
||||||
|
|
||||||
|
Format:
|
||||||
|
|
||||||
|
```
|
||||||
|
BUILD-YYYYMMDD-XXX
|
||||||
|
PLAN-YYYYMMDD-XXX
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
BUILD-20260315-001
|
||||||
|
PLAN-20260316-002
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows prompts and worklog entries to be correlated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Git Rules
|
||||||
|
|
||||||
|
Agents must follow these repository rules.
|
||||||
|
|
||||||
|
Agents must **not commit or push** unless explicitly requested.
|
||||||
|
|
||||||
|
When committing:
|
||||||
|
|
||||||
|
Use structured commit messages.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: improve terminal navigation behavior
|
||||||
|
|
||||||
|
- refine tree navigation
|
||||||
|
- improve scrolling continuity
|
||||||
|
- update AI logs
|
||||||
|
```
|
||||||
|
|
||||||
|
Documentation-only updates should use:
|
||||||
|
|
||||||
|
```
|
||||||
|
docs:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Code Modification Rules
|
||||||
|
|
||||||
|
Agents should prefer:
|
||||||
|
|
||||||
|
- small targeted patches
|
||||||
|
- isolated logic changes
|
||||||
|
- preserving existing abstractions
|
||||||
|
|
||||||
|
Agents should avoid:
|
||||||
|
|
||||||
|
- rewriting entire modules
|
||||||
|
- introducing unnecessary dependencies
|
||||||
|
- altering unrelated code paths
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# UI Changes
|
||||||
|
|
||||||
|
When modifying UI behavior:
|
||||||
|
|
||||||
|
- keep navigation predictable
|
||||||
|
- preserve keyboard ergonomics
|
||||||
|
- ensure selections remain visible
|
||||||
|
- maintain compatibility with history mode
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Testing Rules
|
||||||
|
|
||||||
|
Whenever logic changes:
|
||||||
|
|
||||||
|
- update existing tests
|
||||||
|
- add focused tests when appropriate
|
||||||
|
|
||||||
|
Tests should validate behavior, not implementation details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Scope Discipline
|
||||||
|
|
||||||
|
Agents must strictly respect scope defined in prompts.
|
||||||
|
|
||||||
|
If additional changes appear necessary:
|
||||||
|
|
||||||
|
Agents must propose them in a PLAN task before implementing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Repository Safety
|
||||||
|
|
||||||
|
Agents must not:
|
||||||
|
|
||||||
|
- delete important files
|
||||||
|
- alter project configuration without justification
|
||||||
|
- introduce breaking changes without explicit instruction
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# End of Document
|
||||||
|
---
|
||||||
249
AGENT_WORKFLOW.md
Normal file
249
AGENT_WORKFLOW.md
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# AGENT_WORKFLOW.md
|
||||||
|
|
||||||
|
AI Agent Operational Workflow
|
||||||
|
|
||||||
|
This document defines the deterministic workflow that AI agents must follow when performing work in this repository.
|
||||||
|
|
||||||
|
The workflow ensures:
|
||||||
|
|
||||||
|
- predictable development cycles
|
||||||
|
- traceable decisions
|
||||||
|
- minimal risk of unintended changes
|
||||||
|
- consistent logging of AI actions
|
||||||
|
|
||||||
|
This file complements:
|
||||||
|
|
||||||
|
- `AGENT.md`
|
||||||
|
- `instructions-agent.md`
|
||||||
|
- `docs/ai-worklog.md`
|
||||||
|
- `docs/ai-prompts.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Standard Workflow
|
||||||
|
|
||||||
|
Every AI task must follow this sequence:
|
||||||
|
|
||||||
|
PLAN → REVIEW → BUILD → VALIDATE → LOG
|
||||||
|
|
||||||
|
This sequence must not be skipped.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1. PLAN
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
Understand the problem and determine the minimal solution.
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
|
||||||
|
- inspect repository state
|
||||||
|
- read relevant source files
|
||||||
|
- analyze existing architecture
|
||||||
|
- identify root cause of the problem
|
||||||
|
- propose a minimal change
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- PLAN must **not modify code**
|
||||||
|
- PLAN must **not change files**
|
||||||
|
- PLAN must only produce analysis and a proposal
|
||||||
|
|
||||||
|
Output should include:
|
||||||
|
|
||||||
|
- diagnosis summary
|
||||||
|
- proposed changes
|
||||||
|
- expected file scope
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2. REVIEW
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
Validate the plan before implementing it.
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
|
||||||
|
- confirm the proposed scope is minimal
|
||||||
|
- ensure no unrelated components are affected
|
||||||
|
- verify the change aligns with project architecture
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- If the scope expands unexpectedly, return to PLAN.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3. BUILD
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
Implement the approved change.
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
|
||||||
|
- modify only the files defined in scope
|
||||||
|
- preserve existing architecture
|
||||||
|
- avoid unnecessary refactors
|
||||||
|
- maintain compatibility with existing behavior
|
||||||
|
|
||||||
|
BUILD tasks may modify:
|
||||||
|
|
||||||
|
- source files
|
||||||
|
- tests
|
||||||
|
- documentation
|
||||||
|
|
||||||
|
BUILD tasks must remain minimal and targeted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4. VALIDATE
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
Ensure the repository remains stable.
|
||||||
|
|
||||||
|
Agents must run:
|
||||||
|
|
||||||
|
```
|
||||||
|
make fix
|
||||||
|
make quality
|
||||||
|
```
|
||||||
|
|
||||||
|
Validation must pass before changes are considered complete.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5. LOG
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
Record the AI activity for traceability.
|
||||||
|
|
||||||
|
Two log files must be updated.
|
||||||
|
|
||||||
|
### Work Log
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/ai-worklog.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Record:
|
||||||
|
|
||||||
|
- AI BUILD ENTRY
|
||||||
|
- or AI PLAN ENTRY
|
||||||
|
|
||||||
|
Including:
|
||||||
|
|
||||||
|
- Date (YYYY-MM-DD HH:MM)
|
||||||
|
- Task ID
|
||||||
|
- Agent
|
||||||
|
- Task
|
||||||
|
- Objective
|
||||||
|
- Scope
|
||||||
|
- Files Modified / Files Inspected
|
||||||
|
- Key Decisions / Proposed Changes
|
||||||
|
- Validation
|
||||||
|
- Result
|
||||||
|
- Open Issues
|
||||||
|
|
||||||
|
### Prompt Log
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/ai-prompts.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Record:
|
||||||
|
|
||||||
|
- Date (YYYY-MM-DD HH:MM)
|
||||||
|
- Task ID
|
||||||
|
- Agent
|
||||||
|
- Task
|
||||||
|
- prompt summary
|
||||||
|
- scope
|
||||||
|
- result summary
|
||||||
|
|
||||||
|
Do not store chain-of-thought reasoning.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task ID Coordination
|
||||||
|
|
||||||
|
Every PLAN or BUILD must generate a unique Task ID.
|
||||||
|
|
||||||
|
Format:
|
||||||
|
|
||||||
|
```
|
||||||
|
PLAN-YYYYMMDD-XXX
|
||||||
|
BUILD-YYYYMMDD-XXX
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
PLAN-20260316-001
|
||||||
|
BUILD-20260316-002
|
||||||
|
```
|
||||||
|
|
||||||
|
The same Task ID must appear in:
|
||||||
|
|
||||||
|
- ai-prompts.md
|
||||||
|
- ai-worklog.md
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Commit Workflow
|
||||||
|
|
||||||
|
Agents must **not commit or push automatically** unless explicitly instructed.
|
||||||
|
|
||||||
|
When commits are requested:
|
||||||
|
|
||||||
|
1. Validate repository state
|
||||||
|
2. Stage modified files
|
||||||
|
3. Use structured commit messages
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: improve terminal navigation
|
||||||
|
|
||||||
|
- refine tree navigation
|
||||||
|
- improve scrolling
|
||||||
|
- update AI logs
|
||||||
|
```
|
||||||
|
|
||||||
|
Documentation-only updates should use:
|
||||||
|
|
||||||
|
```
|
||||||
|
docs:
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Failure Handling
|
||||||
|
|
||||||
|
If validation fails:
|
||||||
|
|
||||||
|
Agents must:
|
||||||
|
|
||||||
|
1. stop the BUILD process
|
||||||
|
2. report the error
|
||||||
|
3. propose a fix
|
||||||
|
|
||||||
|
Agents must **not silently bypass failing checks**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Scope Control
|
||||||
|
|
||||||
|
Agents must strictly respect the defined scope.
|
||||||
|
|
||||||
|
If additional changes appear necessary:
|
||||||
|
|
||||||
|
- return to PLAN
|
||||||
|
- propose a new change
|
||||||
|
|
||||||
|
Do not expand scope during BUILD.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# End of Document
|
||||||
145
AI_TASK_TEMPLATE.md
Normal file
145
AI_TASK_TEMPLATE.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# AI Task Template
|
||||||
|
|
||||||
|
Reusable prompt template for launching tasks with AI agents.
|
||||||
|
|
||||||
|
This template enforces the repository workflow defined in:
|
||||||
|
|
||||||
|
- AGENT.md
|
||||||
|
- AGENT_WORKFLOW.md
|
||||||
|
- instructions-agent.md
|
||||||
|
|
||||||
|
It ensures every task follows the deterministic sequence:
|
||||||
|
|
||||||
|
PLAN → REVIEW → BUILD → VALIDATE → LOG
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Basic Task Structure
|
||||||
|
|
||||||
|
Use the following structure when requesting work from an AI agent.
|
||||||
|
|
||||||
|
```
|
||||||
|
Task Type:
|
||||||
|
PLAN | BUILD
|
||||||
|
|
||||||
|
Task ID:
|
||||||
|
PLAN-YYYYMMDD-XXX | BUILD-YYYYMMDD-XXX
|
||||||
|
|
||||||
|
Date:
|
||||||
|
YYYY-MM-DD HH:MM
|
||||||
|
|
||||||
|
Objective:
|
||||||
|
Short description of the goal.
|
||||||
|
|
||||||
|
Context:
|
||||||
|
Relevant background about the current repository state.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
List of files or directories the agent may modify or inspect.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
Detailed description of the behavior that must be implemented or analyzed.
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
Things the agent must NOT change or must preserve.
|
||||||
|
|
||||||
|
Testing:
|
||||||
|
Describe expected tests or validations.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
The agent must run:
|
||||||
|
|
||||||
|
make fix
|
||||||
|
make quality
|
||||||
|
|
||||||
|
Logging requirements:
|
||||||
|
- Append an entry to docs/ai-worklog.md
|
||||||
|
- Append an entry to docs/ai-prompts.md
|
||||||
|
- Include Date, Task ID, Agent, Scope, Result
|
||||||
|
|
||||||
|
Git requirements:
|
||||||
|
- Do NOT commit or push unless explicitly requested.
|
||||||
|
|
||||||
|
Expected Report:
|
||||||
|
The agent must summarize:
|
||||||
|
- files changed
|
||||||
|
- behavior changes
|
||||||
|
- validation results
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Example BUILD Task
|
||||||
|
|
||||||
|
```
|
||||||
|
Task Type:
|
||||||
|
BUILD
|
||||||
|
|
||||||
|
Task ID:
|
||||||
|
BUILD-YYYYMMDD-XXX
|
||||||
|
|
||||||
|
Date:
|
||||||
|
YYYY-MM-DD HH:MM
|
||||||
|
|
||||||
|
Objective:
|
||||||
|
Improve vertical scrolling behavior in the terminal UI.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
Ensure the selected entry remains visible when navigating through long directory lists.
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
Do not modify navigator logic.
|
||||||
|
|
||||||
|
Testing:
|
||||||
|
Add helper tests if necessary.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
make fix
|
||||||
|
make quality
|
||||||
|
|
||||||
|
Logging requirements:
|
||||||
|
Update ai-worklog.md and ai-prompts.md using the standard log format.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Example PLAN Task
|
||||||
|
|
||||||
|
```
|
||||||
|
Task Type:
|
||||||
|
PLAN
|
||||||
|
|
||||||
|
Task ID:
|
||||||
|
PLAN-YYYYMMDD-XXX
|
||||||
|
|
||||||
|
Date:
|
||||||
|
YYYY-MM-DD HH:MM
|
||||||
|
|
||||||
|
Objective:
|
||||||
|
Investigate inconsistent navigation behavior.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- src/app/ui.py
|
||||||
|
- src/app/navigator.py
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
Analyze the current navigation flow and propose a minimal fix.
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
Do not modify code.
|
||||||
|
|
||||||
|
Expected Output:
|
||||||
|
- diagnosis summary
|
||||||
|
- proposed changes
|
||||||
|
- expected file scope
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
|
||||||
|
This template keeps AI interactions consistent across tasks and helps maintain a clean development history.
|
||||||
155
docs/ai-prompts.md
Normal file
155
docs/ai-prompts.md
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# AI Prompts Log
|
||||||
|
|
||||||
|
This file stores the prompts that triggered significant AI-driven development tasks.
|
||||||
|
|
||||||
|
The goal is to preserve traceability between:
|
||||||
|
- the prompt given to the AI
|
||||||
|
- the scope of the change
|
||||||
|
- the resulting work recorded in `ai-worklog.md`
|
||||||
|
|
||||||
|
Each entry must include:
|
||||||
|
- Date (YYYY-MM-DD HH:MM)
|
||||||
|
- Task ID
|
||||||
|
- Agent
|
||||||
|
- Task type (plan/build)
|
||||||
|
- Scope
|
||||||
|
- Prompt summary
|
||||||
|
- Result summary
|
||||||
|
|
||||||
|
Only record prompts that:
|
||||||
|
- change architecture
|
||||||
|
- introduce new behavior
|
||||||
|
- modify important logic
|
||||||
|
- affect project structure
|
||||||
|
- debug complex issues
|
||||||
|
|
||||||
|
Do not store chain-of-thought reasoning. Only store the prompt intent and a concise result summary.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### BUILD PROMPT ENTRY
|
||||||
|
Date: 2026-03-15 18:05
|
||||||
|
Task ID: BUILD-20260315-001
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
|
||||||
|
Prompt Summary:
|
||||||
|
Implement a dedicated history mode in `src/app/ui.py` so pressing `h` hides the tree, shows only history entries, supports Up/Down navigation, and uses Enter to select a history entry and return to the normal tree view.
|
||||||
|
|
||||||
|
Result Summary:
|
||||||
|
Added a dedicated history mode with its own selection state, removed the temporary overlay behavior, preserved existing `b`/`f` and normal tree navigation behavior, and added focused UI tests.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### BUILD PROMPT ENTRY
|
||||||
|
Date: 2026-03-15 19:25
|
||||||
|
Task ID: BUILD-20260315-002
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- instructions-agent.md
|
||||||
|
- docs/ai-worklog.md
|
||||||
|
- docs/ai-prompts.md
|
||||||
|
|
||||||
|
Prompt Summary:
|
||||||
|
Introduce a mandatory AI logging system by updating `instructions-agent.md` and adding repository logs for AI work entries and prompt summaries.
|
||||||
|
|
||||||
|
Result Summary:
|
||||||
|
Added enforceable AI logging rules to `instructions-agent.md`, created `docs/ai-worklog.md` and `docs/ai-prompts.md`, and established a traceable workflow for recording significant AI-assisted tasks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### BUILD PROMPT ENTRY
|
||||||
|
Date: 2026-03-15 20:00
|
||||||
|
Task ID: BUILD-20260315-003
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- instructions-agent.md
|
||||||
|
- docs/ai-worklog.md
|
||||||
|
- docs/ai-prompts.md
|
||||||
|
|
||||||
|
Prompt Summary:
|
||||||
|
Introduce a standard AI log entry format in `instructions-agent.md` and record the change in the repository AI logs.
|
||||||
|
|
||||||
|
Result Summary:
|
||||||
|
Added standardized `AI BUILD ENTRY` and `AI PLAN ENTRY` formats, improving consistency and long-term traceability of AI-assisted development.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### BUILD PROMPT ENTRY
|
||||||
|
Date: 2026-03-15 21:35
|
||||||
|
Task ID: BUILD-20260315-004
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
|
||||||
|
Prompt Summary:
|
||||||
|
Improve keyboard navigation in `src/app/ui.py` so Right expands or enters, Left collapses or goes to the parent, Enter confirms the selected destination, and ESC cancels back to the starting directory.
|
||||||
|
|
||||||
|
Result Summary:
|
||||||
|
Updated the tree-mode key handling in the terminal UI, preserved history mode behavior, and added focused UI tests for the new navigation rules.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### BUILD PROMPT ENTRY
|
||||||
|
Date: 2026-03-15 22:20
|
||||||
|
Task ID: BUILD-20260315-005
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
|
||||||
|
Prompt Summary:
|
||||||
|
Improve vertical scrolling in `src/app/ui.py` so the selected entry always remains visible in tree mode and history mode when the list is taller than the screen.
|
||||||
|
|
||||||
|
Result Summary:
|
||||||
|
Added UI scroll offset handling for tree and history views, kept existing navigation behavior unchanged, and added focused tests for the scroll helper logic.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### BUILD PROMPT ENTRY
|
||||||
|
Date: 2026-03-16 00:27
|
||||||
|
Task ID: BUILD-20260316-001
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
|
||||||
|
Prompt Summary:
|
||||||
|
Refine terminal UI navigation, maintain dedicated history mode, and ensure scrolling continuity so selections remain visible.
|
||||||
|
|
||||||
|
Result Summary:
|
||||||
|
Updated terminal UI behavior, consolidated navigation improvements, and ensured consistent scrolling and selection visibility across tree and history modes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### BUILD PROMPT ENTRY
|
||||||
|
Date: 2026-03-16 01:26
|
||||||
|
Task ID: BUILD-20260316-002
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- instructions-agent.md
|
||||||
|
- docs/ai-worklog.md
|
||||||
|
- docs/ai-prompts.md
|
||||||
|
|
||||||
|
Prompt Summary:
|
||||||
|
Finalize the AI logging format system by requiring timestamped structured log entries and aligning the repository worklog and prompt log with the current logging framework.
|
||||||
|
|
||||||
|
Result Summary:
|
||||||
|
Updated the logging instructions, normalized the prompt log to the structured entry format, and added matching repository log entries for the finalized AI logging system.
|
||||||
254
docs/ai-worklog.md
Normal file
254
docs/ai-worklog.md
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# AI Worklog
|
||||||
|
|
||||||
|
This file records important AI‑assisted development actions.
|
||||||
|
|
||||||
|
The log is designed for long‑term traceability of AI‑driven development.
|
||||||
|
|
||||||
|
Each entry must include:
|
||||||
|
- Date (YYYY-MM-DD HH:MM)
|
||||||
|
- Task ID
|
||||||
|
- Agent
|
||||||
|
- Task type (plan/build)
|
||||||
|
- Objective
|
||||||
|
- Scope
|
||||||
|
- Files Modified / Files Inspected
|
||||||
|
- Key Decisions or Proposed Changes
|
||||||
|
- Validation
|
||||||
|
- Result
|
||||||
|
- Open Issues
|
||||||
|
|
||||||
|
Entries must remain chronological.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### AI BUILD ENTRY
|
||||||
|
Date: 2026-03-15 18:10
|
||||||
|
Task ID: BUILD-20260315-001
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
Objective: Replace the temporary history debug overlay with a dedicated history mode in the terminal UI.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
|
||||||
|
Files Modified:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
|
||||||
|
Key Decisions:
|
||||||
|
- Keep `b` and `f` unchanged.
|
||||||
|
- Add separate history-mode selection state inside the UI.
|
||||||
|
- Hide the tree while history mode is active.
|
||||||
|
- Use Enter to select a history entry and return to the tree view.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- make fix
|
||||||
|
- make quality
|
||||||
|
|
||||||
|
Result:
|
||||||
|
- History mode now shows only the history list and allows navigation and selection of past directories.
|
||||||
|
|
||||||
|
Open Issues:
|
||||||
|
- None
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### AI BUILD ENTRY
|
||||||
|
Date: 2026-03-15 19:30
|
||||||
|
Task ID: BUILD-20260315-002
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
Objective: Introduce mandatory AI logging rules and repository logging workflow.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- instructions-agent.md
|
||||||
|
- docs/ai-worklog.md
|
||||||
|
- docs/ai-prompts.md
|
||||||
|
|
||||||
|
Files Modified:
|
||||||
|
- instructions-agent.md
|
||||||
|
- docs/ai-worklog.md
|
||||||
|
- docs/ai-prompts.md
|
||||||
|
|
||||||
|
Key Decisions:
|
||||||
|
- Require AI agents to log plan and build tasks.
|
||||||
|
- Separate logs for work results and prompts.
|
||||||
|
- Keep entries concise and user‑focused.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- not run
|
||||||
|
|
||||||
|
Result:
|
||||||
|
- Repository now enforces AI logging via instructions-agent.md.
|
||||||
|
|
||||||
|
Open Issues:
|
||||||
|
- None
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### AI BUILD ENTRY
|
||||||
|
Date: 2026-03-15 20:05
|
||||||
|
Task ID: BUILD-20260315-003
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
Objective: Introduce the standard AI log entry format and improve logging consistency.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- instructions-agent.md
|
||||||
|
- docs/ai-worklog.md
|
||||||
|
- docs/ai-prompts.md
|
||||||
|
|
||||||
|
Files Modified:
|
||||||
|
- instructions-agent.md
|
||||||
|
- docs/ai-worklog.md
|
||||||
|
- docs/ai-prompts.md
|
||||||
|
|
||||||
|
Key Decisions:
|
||||||
|
- Define AI BUILD ENTRY and AI PLAN ENTRY blocks.
|
||||||
|
- Standardize structure for long‑term repository traceability.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- make fix (passed)
|
||||||
|
- make quality (passed)
|
||||||
|
|
||||||
|
Result:
|
||||||
|
- Repository now uses a standardized AI logging format.
|
||||||
|
|
||||||
|
Open Issues:
|
||||||
|
- None
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### AI BUILD ENTRY
|
||||||
|
Date: 2026-03-15 21:40
|
||||||
|
Task ID: BUILD-20260315-004
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
Objective: Improve keyboard navigation behavior in the terminal UI for tree mode.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
|
||||||
|
Files Modified:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
|
||||||
|
Key Decisions:
|
||||||
|
- Right expands collapsed directories and enters expanded directories.
|
||||||
|
- Left collapses expanded directories or navigates to the parent directory.
|
||||||
|
- Enter confirms the selected path.
|
||||||
|
- ESC cancels and returns to the original directory.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- make fix (passed)
|
||||||
|
- make quality (passed)
|
||||||
|
|
||||||
|
Result:
|
||||||
|
- Tree navigation semantics improved with clearer expand, enter, and cancel behavior.
|
||||||
|
|
||||||
|
Open Issues:
|
||||||
|
- None
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### AI BUILD ENTRY
|
||||||
|
Date: 2026-03-15 22:25
|
||||||
|
Task ID: BUILD-20260315-005
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
Objective: Implement vertical scrolling so the selected entry always remains visible.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
|
||||||
|
Files Modified:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
|
||||||
|
Key Decisions:
|
||||||
|
- Introduce scroll offsets in the UI state.
|
||||||
|
- Clamp offsets to keep the selected row visible.
|
||||||
|
- Apply the same mechanism to history mode.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- make fix (passed)
|
||||||
|
- make quality (passed)
|
||||||
|
|
||||||
|
Result:
|
||||||
|
- Tree and history views now scroll when navigating beyond the visible screen area.
|
||||||
|
|
||||||
|
Open Issues:
|
||||||
|
- None
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### AI BUILD ENTRY
|
||||||
|
Date: 2026-03-16 00:27
|
||||||
|
Task ID: BUILD-20260316-001
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
Objective: Consolidate terminal UI navigation improvements and document updated behavior.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
- docs/ai-worklog.md
|
||||||
|
- docs/ai-prompts.md
|
||||||
|
|
||||||
|
Files Modified:
|
||||||
|
- src/app/ui.py
|
||||||
|
- tests/test_ui.py
|
||||||
|
- docs/ai-worklog.md
|
||||||
|
- docs/ai-prompts.md
|
||||||
|
|
||||||
|
Key Decisions:
|
||||||
|
- Maintain dedicated history mode.
|
||||||
|
- Refine tree-mode keyboard behavior.
|
||||||
|
- Keep UI logic isolated from navigator logic.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- make fix (passed)
|
||||||
|
- make quality (passed)
|
||||||
|
|
||||||
|
Result:
|
||||||
|
- Terminal UI now includes history mode, improved navigation semantics, and reliable vertical scrolling.
|
||||||
|
|
||||||
|
Open Issues:
|
||||||
|
- None
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### AI BUILD ENTRY
|
||||||
|
Date: 2026-03-16 01:26
|
||||||
|
Task ID: BUILD-20260316-002
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
Objective: Finalize the AI logging format system and align repository log files with the structured logging framework.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- instructions-agent.md
|
||||||
|
- docs/ai-worklog.md
|
||||||
|
- docs/ai-prompts.md
|
||||||
|
|
||||||
|
Files Modified:
|
||||||
|
- instructions-agent.md
|
||||||
|
- docs/ai-worklog.md
|
||||||
|
- docs/ai-prompts.md
|
||||||
|
|
||||||
|
Key Decisions:
|
||||||
|
- Require timestamped log entries with date and time, Task ID, Agent, and Scope fields.
|
||||||
|
- Keep the worklog and prompt log aligned with the current structured logging framework.
|
||||||
|
- Normalize prompt logging into a single consistent `BUILD PROMPT ENTRY` format.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- make fix (passed)
|
||||||
|
- make quality (passed)
|
||||||
|
|
||||||
|
Result:
|
||||||
|
- The AI logging system now uses consistent structured entries across repository instructions, worklog history, and prompt history.
|
||||||
|
|
||||||
|
Open Issues:
|
||||||
|
- None
|
||||||
@@ -59,3 +59,149 @@ Agent rules:
|
|||||||
3. If the specification conflicts with a previous assumption, the specification takes precedence.
|
3. If the specification conflicts with a previous assumption, the specification takes precedence.
|
||||||
4. Keep implementation aligned with the MVP scope unless explicitly asked to extend it.
|
4. Keep implementation aligned with the MVP scope unless explicitly asked to extend it.
|
||||||
5. After code changes, run the appropriate validation workflow defined by this repository.
|
5. After code changes, run the appropriate validation workflow defined by this repository.
|
||||||
|
|
||||||
|
## AI Worklog Policy
|
||||||
|
|
||||||
|
This repository maintains an AI-assisted development log.
|
||||||
|
|
||||||
|
Files:
|
||||||
|
- `docs/ai-worklog.md`
|
||||||
|
- `docs/ai-prompts.md`
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. After every significant `plan` or `build` task, append a concise worklog entry to `docs/ai-worklog.md`.
|
||||||
|
2. Each worklog entry must include:
|
||||||
|
- date
|
||||||
|
- task type (`plan` or `build`)
|
||||||
|
- short objective
|
||||||
|
- files inspected or modified
|
||||||
|
- key decisions
|
||||||
|
- validation commands run
|
||||||
|
- result
|
||||||
|
- unresolved issues if any
|
||||||
|
3. When a prompt meaningfully changes architecture, behavior, workflow, debugging direction, or project structure, append the prompt (or a concise cleaned version of it) to `docs/ai-prompts.md`.
|
||||||
|
4. Do not store private chain-of-thought or internal reasoning.
|
||||||
|
5. Store only concise, user-facing summaries of what was done.
|
||||||
|
6. Keep entries chronological and easy to scan.
|
||||||
|
|
||||||
|
## Mandatory AI Logging
|
||||||
|
|
||||||
|
Every significant AI interaction in this repository must be logged.
|
||||||
|
|
||||||
|
This applies to:
|
||||||
|
- `plan` tasks
|
||||||
|
- `build` tasks
|
||||||
|
- debugging tasks
|
||||||
|
- documentation tasks
|
||||||
|
- architectural discussions that affect the project
|
||||||
|
|
||||||
|
Required files:
|
||||||
|
- `docs/ai-worklog.md`
|
||||||
|
- `docs/ai-prompts.md`
|
||||||
|
|
||||||
|
Mandatory rules:
|
||||||
|
1. After every significant `plan` task, append a concise entry to `docs/ai-worklog.md`.
|
||||||
|
2. After every significant `build` task, append a concise entry to `docs/ai-worklog.md`.
|
||||||
|
3. If the task was driven by a meaningful prompt, also append an entry to `docs/ai-prompts.md`.
|
||||||
|
4. Logging is part of the task itself and must not be skipped.
|
||||||
|
5. The task is not complete until the logs are updated.
|
||||||
|
|
||||||
|
Each worklog entry must include:
|
||||||
|
- date
|
||||||
|
- task type (`plan` or `build`)
|
||||||
|
- short objective
|
||||||
|
- files inspected or modified
|
||||||
|
- key decisions
|
||||||
|
- validation commands run, if any
|
||||||
|
- result
|
||||||
|
- unresolved issues, if any
|
||||||
|
|
||||||
|
Validation logging rule:
|
||||||
|
- If validation commands such as `make fix`, `make quality`, `pytest`, or other checks are executed during the task, the worklog entry must record them explicitly.
|
||||||
|
- The value `validation: not run` may only be used if no validation commands were executed.
|
||||||
|
- The worklog entry must reflect the actual commands run during the task.
|
||||||
|
|
||||||
|
Each prompt log entry must include:
|
||||||
|
- date
|
||||||
|
- task type (`plan` or `build`)
|
||||||
|
- short prompt summary
|
||||||
|
- scope
|
||||||
|
- result summary
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Do not store chain-of-thought or private reasoning.
|
||||||
|
- Store only concise user-facing summaries.
|
||||||
|
- Keep entries chronological.
|
||||||
|
- Even small but meaningful tasks must be logged.
|
||||||
|
|
||||||
|
## Standard AI Log Entry Format
|
||||||
|
|
||||||
|
To ensure consistency and readability of long AI-assisted development histories, all log entries must follow a standardized block format.
|
||||||
|
|
||||||
|
Agents must write log entries using the following structure.
|
||||||
|
|
||||||
|
Example for build tasks:
|
||||||
|
|
||||||
|
### AI BUILD ENTRY
|
||||||
|
Date: YYYY-MM-DD HH:MM
|
||||||
|
Task ID: BUILD-YYYYMMDD-XXX
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: build
|
||||||
|
Objective: short description of the task
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- src/app/module.py
|
||||||
|
- tests/test_module.py
|
||||||
|
|
||||||
|
Files Modified:
|
||||||
|
- file/path/example.py
|
||||||
|
- another/file.md
|
||||||
|
|
||||||
|
Key Decisions:
|
||||||
|
- short bullet explaining important choices
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- make fix (passed)
|
||||||
|
- make quality (passed)
|
||||||
|
|
||||||
|
Result:
|
||||||
|
- short description of what changed or was achieved
|
||||||
|
|
||||||
|
Open Issues:
|
||||||
|
- optional list of unresolved problems
|
||||||
|
|
||||||
|
Example for plan tasks:
|
||||||
|
|
||||||
|
### AI PLAN ENTRY
|
||||||
|
Date: YYYY-MM-DD HH:MM
|
||||||
|
Task ID: PLAN-YYYYMMDD-XXX
|
||||||
|
Agent: OpenCode
|
||||||
|
Task: plan
|
||||||
|
Objective: short description of planning objective
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- src/app/module.py
|
||||||
|
- specs/module_spec.md
|
||||||
|
|
||||||
|
Files Inspected:
|
||||||
|
- src/app/example.py
|
||||||
|
- specs/example_spec.md
|
||||||
|
|
||||||
|
Proposed Changes:
|
||||||
|
- summary of planned changes
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- optional relevant observations
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Always use these headers exactly (`AI BUILD ENTRY` or `AI PLAN ENTRY`).
|
||||||
|
- Keep entries concise and structured.
|
||||||
|
- Do not include chain-of-thought reasoning.
|
||||||
|
- Use bullet points when possible.
|
||||||
|
- This format must be used when updating `docs/ai-worklog.md`.
|
||||||
|
- The Date field must include both date and time using the format YYYY-MM-DD HH:MM.
|
||||||
|
- Every log entry must include a `Task ID`.
|
||||||
|
- `Task ID` format must be `BUILD-YYYYMMDD-XXX` or `PLAN-YYYYMMDD-XXX`.
|
||||||
|
- `Agent` must identify the tool or AI system performing the task (e.g., OpenCode).
|
||||||
|
- `Scope` must describe the main files or project areas affected.
|
||||||
|
- These fields make the AI worklog suitable for long-term traceability and large histories.
|
||||||
|
|||||||
112
scripts/ai-task
Executable file
112
scripts/ai-task
Executable file
@@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ai-task
|
||||||
|
# Helper CLI to generate AI task prompts based on AI_TASK_TEMPLATE.md
|
||||||
|
# Usage:
|
||||||
|
# scripts/ai-task build "Objective text" [scope...]
|
||||||
|
# scripts/ai-task plan "Objective text" [scope...]
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TYPE="$1"
|
||||||
|
OBJECTIVE="$2"
|
||||||
|
shift 2 || true
|
||||||
|
SCOPE=("$@")
|
||||||
|
|
||||||
|
if [[ -z "$TYPE" || -z "$OBJECTIVE" ]]; then
|
||||||
|
echo "Usage: ai-task <plan|build> \"Objective\" [scope files...]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TYPE_LOWER=$(echo "$TYPE" | tr '[:upper:]' '[:lower:]')
|
||||||
|
TYPE_UPPER=$(echo "$TYPE_LOWER" | tr '[:lower:]' '[:upper:]')
|
||||||
|
|
||||||
|
DATE=$(date "+%Y-%m-%d %H:%M")
|
||||||
|
DAY=$(date "+%Y%m%d")
|
||||||
|
|
||||||
|
case "$TYPE_LOWER" in
|
||||||
|
build)
|
||||||
|
PREFIX="BUILD"
|
||||||
|
;;
|
||||||
|
plan)
|
||||||
|
PREFIX="PLAN"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Error: task type must be 'plan' or 'build'"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
WORKLOG_FILE="docs/ai-worklog.md"
|
||||||
|
LAST_SEQ=0
|
||||||
|
|
||||||
|
if [[ -f "$WORKLOG_FILE" ]]; then
|
||||||
|
LAST_MATCH=$(grep -Eo "${PREFIX}-${DAY}-[0-9]{3}" "$WORKLOG_FILE" | tail -n 1 || true)
|
||||||
|
if [[ -n "$LAST_MATCH" ]]; then
|
||||||
|
LAST_SEQ=${LAST_MATCH##*-}
|
||||||
|
LAST_SEQ=$((10#$LAST_SEQ))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
NEXT_SEQ=$(printf "%03d" $((LAST_SEQ + 1)))
|
||||||
|
TASK_ID="${PREFIX}-${DAY}-${NEXT_SEQ}"
|
||||||
|
|
||||||
|
# Build scope block
|
||||||
|
cat <<EOF
|
||||||
|
Task Type:
|
||||||
|
${TYPE_UPPER}
|
||||||
|
|
||||||
|
Task ID:
|
||||||
|
${TASK_ID}
|
||||||
|
|
||||||
|
Date:
|
||||||
|
${DATE}
|
||||||
|
|
||||||
|
Objective:
|
||||||
|
${OBJECTIVE}
|
||||||
|
|
||||||
|
Context:
|
||||||
|
<describe repository state if needed>
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ ${#SCOPE[@]} -gt 0 ]; then
|
||||||
|
for item in "${SCOPE[@]}"; do
|
||||||
|
printf '%s\n' "- ${item}"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
printf '%s\n' "- <fill scope>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
<describe expected behavior>
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
- follow AGENT_WORKFLOW.md
|
||||||
|
- keep scope minimal
|
||||||
|
- preserve existing behavior unless explicitly changed
|
||||||
|
|
||||||
|
Testing:
|
||||||
|
<describe expected tests>
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
make fix
|
||||||
|
make quality
|
||||||
|
|
||||||
|
Logging requirements:
|
||||||
|
- append entry to docs/ai-worklog.md
|
||||||
|
- append entry to docs/ai-prompts.md
|
||||||
|
- include Date, Task ID, Agent, Scope
|
||||||
|
- use Task ID: ${TASK_ID}
|
||||||
|
|
||||||
|
Git requirements:
|
||||||
|
- do NOT commit or push unless explicitly requested
|
||||||
|
|
||||||
|
Expected Report:
|
||||||
|
- files changed
|
||||||
|
- behavior changes
|
||||||
|
- validation results
|
||||||
|
EOF
|
||||||
227
src/app/ui.py
227
src/app/ui.py
@@ -22,6 +22,14 @@ class RenderLine:
|
|||||||
is_selected: bool
|
is_selected: bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class HistoryLine:
|
||||||
|
"""A rendered history row ready for terminal output."""
|
||||||
|
|
||||||
|
text: str
|
||||||
|
is_selected: bool
|
||||||
|
|
||||||
|
|
||||||
def move_selection(current_index: int, step: int, entry_count: int) -> int:
|
def move_selection(current_index: int, step: int, entry_count: int) -> int:
|
||||||
"""Move the selected index within the available entry range."""
|
"""Move the selected index within the available entry range."""
|
||||||
|
|
||||||
@@ -57,9 +65,46 @@ def build_render_lines(navigator: Navigator) -> list[RenderLine]:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def build_history_lines(
|
||||||
|
entries: tuple[Path, ...], selected_index: int
|
||||||
|
) -> list[HistoryLine]:
|
||||||
|
"""Build all visible lines for history mode."""
|
||||||
|
|
||||||
|
return [
|
||||||
|
HistoryLine(text=str(path), is_selected=index == selected_index)
|
||||||
|
for index, path in enumerate(entries)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def clamp_scroll_offset(
|
||||||
|
selected_index: int, scroll_offset: int, visible_count: int, total_count: int
|
||||||
|
) -> int:
|
||||||
|
"""Keep the selected row within the visible scrolling window."""
|
||||||
|
|
||||||
|
if visible_count <= 0 or total_count <= visible_count:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
max_offset = total_count - visible_count
|
||||||
|
offset = max(0, min(scroll_offset, max_offset))
|
||||||
|
|
||||||
|
if selected_index < offset:
|
||||||
|
return selected_index
|
||||||
|
|
||||||
|
if selected_index >= offset + visible_count:
|
||||||
|
return selected_index - visible_count + 1
|
||||||
|
|
||||||
|
return offset
|
||||||
|
|
||||||
|
|
||||||
class TerminalUI:
|
class TerminalUI:
|
||||||
"""Minimal curses UI for the directory navigator MVP."""
|
"""Minimal curses UI for the directory navigator MVP."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._history_mode = False
|
||||||
|
self._history_selected_index = 0
|
||||||
|
self._tree_scroll_offset = 0
|
||||||
|
self._history_scroll_offset = 0
|
||||||
|
|
||||||
def run(self, navigator: Navigator) -> Path:
|
def run(self, navigator: Navigator) -> Path:
|
||||||
"""Start the interactive session and return the final directory path."""
|
"""Start the interactive session and return the final directory path."""
|
||||||
|
|
||||||
@@ -70,53 +115,52 @@ class TerminalUI:
|
|||||||
def _run_session(self, stdscr: curses.window, navigator: Navigator) -> Path:
|
def _run_session(self, stdscr: curses.window, navigator: Navigator) -> Path:
|
||||||
curses.curs_set(0)
|
curses.curs_set(0)
|
||||||
stdscr.keypad(True)
|
stdscr.keypad(True)
|
||||||
|
original_path = navigator.current_path
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
self._render(stdscr, navigator)
|
self._render(stdscr, navigator)
|
||||||
key = stdscr.getch()
|
key = stdscr.getch()
|
||||||
|
|
||||||
if key == ESCAPE_KEY:
|
if key == ESCAPE_KEY:
|
||||||
return navigator.current_path
|
return original_path
|
||||||
|
|
||||||
if key == curses.KEY_UP:
|
if key == ord("h"):
|
||||||
navigator.set_selected_index(
|
self._toggle_history_mode(navigator)
|
||||||
move_selection(
|
|
||||||
navigator.selected_index,
|
|
||||||
step=-1,
|
|
||||||
entry_count=len(navigator.visible_entries),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if key == curses.KEY_DOWN:
|
if self._history_mode:
|
||||||
navigator.set_selected_index(
|
self._handle_history_key(key, navigator)
|
||||||
move_selection(
|
|
||||||
navigator.selected_index,
|
|
||||||
step=1,
|
|
||||||
entry_count=len(navigator.visible_entries),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if key == curses.KEY_RIGHT:
|
tree_result = self._handle_tree_key(key, navigator)
|
||||||
navigator.expand_selected_directory()
|
if tree_result is not None:
|
||||||
continue
|
return tree_result
|
||||||
|
|
||||||
if key == curses.KEY_LEFT:
|
|
||||||
navigator.collapse_selected_directory()
|
|
||||||
continue
|
|
||||||
|
|
||||||
if key in (curses.KEY_ENTER, 10, 13):
|
|
||||||
navigator.enter_selected_directory()
|
|
||||||
|
|
||||||
def _render(self, stdscr: curses.window, navigator: Navigator) -> None:
|
def _render(self, stdscr: curses.window, navigator: Navigator) -> None:
|
||||||
stdscr.erase()
|
stdscr.erase()
|
||||||
height, width = stdscr.getmaxyx()
|
height, width = stdscr.getmaxyx()
|
||||||
|
|
||||||
|
if self._history_mode:
|
||||||
|
self._render_history_mode(stdscr, navigator, width=width, height=height)
|
||||||
|
stdscr.refresh()
|
||||||
|
return
|
||||||
|
|
||||||
path_text = str(navigator.current_path)
|
path_text = str(navigator.current_path)
|
||||||
stdscr.addnstr(0, 0, path_text, width - 1)
|
stdscr.addnstr(0, 0, path_text, width - 1)
|
||||||
|
|
||||||
for row, line in enumerate(build_render_lines(navigator), start=2):
|
lines = build_render_lines(navigator)
|
||||||
|
visible_count = max(0, height - 2)
|
||||||
|
self._tree_scroll_offset = clamp_scroll_offset(
|
||||||
|
navigator.selected_index,
|
||||||
|
self._tree_scroll_offset,
|
||||||
|
visible_count,
|
||||||
|
len(lines),
|
||||||
|
)
|
||||||
|
|
||||||
|
for row, line in enumerate(
|
||||||
|
lines[self._tree_scroll_offset : self._tree_scroll_offset + visible_count],
|
||||||
|
start=2,
|
||||||
|
):
|
||||||
if row >= height:
|
if row >= height:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -125,6 +169,133 @@ class TerminalUI:
|
|||||||
|
|
||||||
stdscr.refresh()
|
stdscr.refresh()
|
||||||
|
|
||||||
|
def _render_history_mode(
|
||||||
|
self, stdscr: curses.window, navigator: Navigator, width: int, height: int
|
||||||
|
) -> None:
|
||||||
|
history_lines = build_history_lines(
|
||||||
|
navigator.history.entries(), self._history_selected_index
|
||||||
|
)
|
||||||
|
start_row = 1
|
||||||
|
visible_count = max(0, height - start_row - 1)
|
||||||
|
self._history_scroll_offset = clamp_scroll_offset(
|
||||||
|
self._history_selected_index,
|
||||||
|
self._history_scroll_offset,
|
||||||
|
visible_count,
|
||||||
|
len(history_lines),
|
||||||
|
)
|
||||||
|
|
||||||
|
stdscr.addnstr(start_row, 0, "History:", width - 1, curses.A_BOLD)
|
||||||
|
|
||||||
|
for index, line in enumerate(
|
||||||
|
history_lines[
|
||||||
|
self._history_scroll_offset : self._history_scroll_offset
|
||||||
|
+ visible_count
|
||||||
|
],
|
||||||
|
start=1,
|
||||||
|
):
|
||||||
|
row = start_row + index
|
||||||
|
if row >= height:
|
||||||
|
break
|
||||||
|
|
||||||
|
attributes = curses.A_REVERSE if line.is_selected else curses.A_NORMAL
|
||||||
|
stdscr.addnstr(row, 0, line.text, width - 1, attributes)
|
||||||
|
|
||||||
|
def _toggle_history_mode(self, navigator: Navigator) -> None:
|
||||||
|
self._history_mode = not self._history_mode
|
||||||
|
if self._history_mode:
|
||||||
|
self._history_selected_index = self._get_current_history_index(navigator)
|
||||||
|
self._history_scroll_offset = 0
|
||||||
|
|
||||||
|
def _handle_history_key(self, key: int, navigator: Navigator) -> None:
|
||||||
|
history_entries = navigator.history.entries()
|
||||||
|
|
||||||
|
if key == curses.KEY_UP:
|
||||||
|
self._history_selected_index = move_selection(
|
||||||
|
self._history_selected_index,
|
||||||
|
step=-1,
|
||||||
|
entry_count=len(history_entries),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if key == curses.KEY_DOWN:
|
||||||
|
self._history_selected_index = move_selection(
|
||||||
|
self._history_selected_index,
|
||||||
|
step=1,
|
||||||
|
entry_count=len(history_entries),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if key in (curses.KEY_ENTER, 10, 13) and history_entries:
|
||||||
|
navigator.select_history_entry(self._history_selected_index)
|
||||||
|
self._history_mode = False
|
||||||
|
self._tree_scroll_offset = 0
|
||||||
|
|
||||||
|
def _handle_tree_key(self, key: int, navigator: Navigator) -> Path | None:
|
||||||
|
if key == curses.KEY_UP:
|
||||||
|
navigator.set_selected_index(
|
||||||
|
move_selection(
|
||||||
|
navigator.selected_index,
|
||||||
|
step=-1,
|
||||||
|
entry_count=len(navigator.visible_entries),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if key == curses.KEY_DOWN:
|
||||||
|
navigator.set_selected_index(
|
||||||
|
move_selection(
|
||||||
|
navigator.selected_index,
|
||||||
|
step=1,
|
||||||
|
entry_count=len(navigator.visible_entries),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if key == curses.KEY_RIGHT:
|
||||||
|
was_expanded = navigator.selected_entry.is_expanded
|
||||||
|
|
||||||
|
if not navigator.expand_selected_directory():
|
||||||
|
navigator.enter_selected_directory()
|
||||||
|
self._tree_scroll_offset = 0
|
||||||
|
return None
|
||||||
|
|
||||||
|
if was_expanded:
|
||||||
|
navigator.enter_selected_directory()
|
||||||
|
self._tree_scroll_offset = 0
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
if key == curses.KEY_LEFT:
|
||||||
|
if not navigator.collapse_selected_directory():
|
||||||
|
navigator.go_to_parent_directory()
|
||||||
|
self._tree_scroll_offset = 0
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
if key == ord("b"):
|
||||||
|
navigator.go_back()
|
||||||
|
self._tree_scroll_offset = 0
|
||||||
|
return None
|
||||||
|
|
||||||
|
if key == ord("f"):
|
||||||
|
navigator.go_forward()
|
||||||
|
self._tree_scroll_offset = 0
|
||||||
|
return None
|
||||||
|
|
||||||
|
if key in (curses.KEY_ENTER, 10, 13):
|
||||||
|
return navigator.selected_entry.path
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_current_history_index(self, navigator: Navigator) -> int:
|
||||||
|
history_entries = navigator.history.entries()
|
||||||
|
|
||||||
|
for index in range(len(history_entries) - 1, -1, -1):
|
||||||
|
if history_entries[index] == navigator.current_path:
|
||||||
|
return index
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def open_terminal_streams() -> Iterator[tuple[TextIO, TextIO]]:
|
def open_terminal_streams() -> Iterator[tuple[TextIO, TextIO]]:
|
||||||
|
|||||||
156
tests/test_ui.py
156
tests/test_ui.py
@@ -1,11 +1,16 @@
|
|||||||
|
import curses
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from app.navigator import Navigator, VisibleEntry
|
from app.navigator import Navigator, VisibleEntry
|
||||||
from app.ui import (
|
from app.ui import (
|
||||||
|
HistoryLine,
|
||||||
RenderLine,
|
RenderLine,
|
||||||
|
TerminalUI,
|
||||||
|
build_history_lines,
|
||||||
build_render_lines,
|
build_render_lines,
|
||||||
|
clamp_scroll_offset,
|
||||||
format_entry,
|
format_entry,
|
||||||
move_selection,
|
move_selection,
|
||||||
open_terminal_streams,
|
open_terminal_streams,
|
||||||
@@ -65,6 +70,157 @@ def test_build_render_lines_marks_selected_entry(tmp_path: Path) -> None:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_history_lines_marks_selected_history_entry(tmp_path: Path) -> None:
|
||||||
|
first = tmp_path / "first"
|
||||||
|
second = tmp_path / "second"
|
||||||
|
|
||||||
|
assert build_history_lines((first, second), selected_index=1) == [
|
||||||
|
HistoryLine(text=str(first), is_selected=False),
|
||||||
|
HistoryLine(text=str(second), is_selected=True),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_clamp_scroll_offset_keeps_selection_visible_when_moving_down() -> None:
|
||||||
|
assert clamp_scroll_offset(5, scroll_offset=0, visible_count=3, total_count=10) == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_clamp_scroll_offset_keeps_selection_visible_when_moving_up() -> None:
|
||||||
|
assert clamp_scroll_offset(1, scroll_offset=4, visible_count=3, total_count=10) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_clamp_scroll_offset_returns_zero_when_all_items_fit() -> None:
|
||||||
|
assert clamp_scroll_offset(2, scroll_offset=1, visible_count=5, total_count=3) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_toggle_history_mode_selects_current_history_entry(tmp_path: Path) -> None:
|
||||||
|
root = tmp_path / "root"
|
||||||
|
child = root / "child"
|
||||||
|
root.mkdir()
|
||||||
|
child.mkdir()
|
||||||
|
|
||||||
|
navigator = Navigator(root)
|
||||||
|
navigator.set_selected_index(1)
|
||||||
|
navigator.enter_selected_directory()
|
||||||
|
ui = TerminalUI()
|
||||||
|
|
||||||
|
ui._toggle_history_mode(navigator)
|
||||||
|
|
||||||
|
assert ui._history_mode is True
|
||||||
|
assert ui._history_selected_index == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_history_mode_enter_selects_entry_and_exits(tmp_path: Path) -> None:
|
||||||
|
root = tmp_path / "root"
|
||||||
|
first = root / "first"
|
||||||
|
second = root / "second"
|
||||||
|
root.mkdir()
|
||||||
|
first.mkdir()
|
||||||
|
second.mkdir()
|
||||||
|
|
||||||
|
navigator = Navigator(root)
|
||||||
|
navigator.set_selected_index(1)
|
||||||
|
navigator.enter_selected_directory()
|
||||||
|
navigator.go_to_parent_directory()
|
||||||
|
navigator.set_selected_index(2)
|
||||||
|
navigator.enter_selected_directory()
|
||||||
|
|
||||||
|
ui = TerminalUI()
|
||||||
|
ui._toggle_history_mode(navigator)
|
||||||
|
ui._handle_history_key(curses.KEY_UP, navigator)
|
||||||
|
ui._handle_history_key(10, navigator)
|
||||||
|
|
||||||
|
assert ui._history_mode is False
|
||||||
|
assert navigator.current_path == root.resolve()
|
||||||
|
assert navigator.selected_index == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_history_mode_toggle_off_keeps_current_directory(tmp_path: Path) -> None:
|
||||||
|
root = tmp_path / "root"
|
||||||
|
root.mkdir()
|
||||||
|
navigator = Navigator(root)
|
||||||
|
ui = TerminalUI()
|
||||||
|
|
||||||
|
ui._toggle_history_mode(navigator)
|
||||||
|
ui._toggle_history_mode(navigator)
|
||||||
|
|
||||||
|
assert ui._history_mode is False
|
||||||
|
assert navigator.current_path == root.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def test_right_arrow_expands_collapsed_directory(tmp_path: Path) -> None:
|
||||||
|
workspace = tmp_path / "workspace"
|
||||||
|
scripts = workspace / "scripts"
|
||||||
|
build = scripts / "build"
|
||||||
|
workspace.mkdir()
|
||||||
|
scripts.mkdir()
|
||||||
|
build.mkdir()
|
||||||
|
|
||||||
|
navigator = Navigator(workspace)
|
||||||
|
navigator.set_selected_index(1)
|
||||||
|
ui = TerminalUI()
|
||||||
|
|
||||||
|
assert ui._handle_tree_key(curses.KEY_RIGHT, navigator) is None
|
||||||
|
assert navigator.current_path == workspace.resolve()
|
||||||
|
assert [entry.name for entry in navigator.visible_entries] == [
|
||||||
|
"..",
|
||||||
|
"scripts",
|
||||||
|
"build",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_right_arrow_enters_already_expanded_directory(tmp_path: Path) -> None:
|
||||||
|
workspace = tmp_path / "workspace"
|
||||||
|
scripts = workspace / "scripts"
|
||||||
|
build = scripts / "build"
|
||||||
|
workspace.mkdir()
|
||||||
|
scripts.mkdir()
|
||||||
|
build.mkdir()
|
||||||
|
|
||||||
|
navigator = Navigator(workspace)
|
||||||
|
navigator.set_selected_index(1)
|
||||||
|
navigator.expand_selected_directory()
|
||||||
|
ui = TerminalUI()
|
||||||
|
|
||||||
|
assert ui._handle_tree_key(curses.KEY_RIGHT, navigator) is None
|
||||||
|
assert navigator.current_path == scripts.resolve()
|
||||||
|
assert navigator.selected_index == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_left_arrow_goes_to_parent_when_selected_entry_is_collapsed(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
parent = tmp_path / "parent"
|
||||||
|
current = parent / "current"
|
||||||
|
child = current / "child"
|
||||||
|
parent.mkdir()
|
||||||
|
current.mkdir()
|
||||||
|
child.mkdir()
|
||||||
|
|
||||||
|
navigator = Navigator(current)
|
||||||
|
navigator.set_selected_index(1)
|
||||||
|
ui = TerminalUI()
|
||||||
|
|
||||||
|
assert ui._handle_tree_key(curses.KEY_LEFT, navigator) is None
|
||||||
|
assert navigator.current_path == parent.resolve()
|
||||||
|
assert navigator.selected_index == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_enter_returns_selected_path_without_navigating(tmp_path: Path) -> None:
|
||||||
|
workspace = tmp_path / "workspace"
|
||||||
|
child = workspace / "child"
|
||||||
|
workspace.mkdir()
|
||||||
|
child.mkdir()
|
||||||
|
|
||||||
|
navigator = Navigator(workspace)
|
||||||
|
navigator.set_selected_index(1)
|
||||||
|
ui = TerminalUI()
|
||||||
|
|
||||||
|
result = ui._handle_tree_key(10, navigator)
|
||||||
|
|
||||||
|
assert result == child.resolve()
|
||||||
|
assert navigator.current_path == workspace.resolve()
|
||||||
|
|
||||||
|
|
||||||
def test_open_terminal_streams_raises_without_terminal(
|
def test_open_terminal_streams_raises_without_terminal(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user