strands-issue-writer 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. strands_issue_writer-0.1.0/.github/workflows/ci.yml +21 -0
  2. strands_issue_writer-0.1.0/.github/workflows/publish.yml +61 -0
  3. strands_issue_writer-0.1.0/.gitignore +14 -0
  4. strands_issue_writer-0.1.0/LICENSE +21 -0
  5. strands_issue_writer-0.1.0/Modelfile.example +10 -0
  6. strands_issue_writer-0.1.0/PKG-INFO +210 -0
  7. strands_issue_writer-0.1.0/README.md +174 -0
  8. strands_issue_writer-0.1.0/docs/SERVING.md +201 -0
  9. strands_issue_writer-0.1.0/pyproject.toml +62 -0
  10. strands_issue_writer-0.1.0/strands-issue-writer-logo.svg +38 -0
  11. strands_issue_writer-0.1.0/strands_issue_writer/__init__.py +18 -0
  12. strands_issue_writer-0.1.0/strands_issue_writer/agent.py +48 -0
  13. strands_issue_writer-0.1.0/strands_issue_writer/cli.py +114 -0
  14. strands_issue_writer-0.1.0/strands_issue_writer/dashboard/__init__.py +1 -0
  15. strands_issue_writer-0.1.0/strands_issue_writer/dashboard/index.html +204 -0
  16. strands_issue_writer-0.1.0/strands_issue_writer/dashboard/server.py +89 -0
  17. strands_issue_writer-0.1.0/strands_issue_writer/models.py +73 -0
  18. strands_issue_writer-0.1.0/strands_issue_writer/prompts.py +41 -0
  19. strands_issue_writer-0.1.0/strands_issue_writer/provider.py +81 -0
  20. strands_issue_writer-0.1.0/strands_issue_writer/tools/__init__.py +6 -0
  21. strands_issue_writer-0.1.0/strands_issue_writer/tools/draft.py +78 -0
  22. strands_issue_writer-0.1.0/strands_issue_writer/tools/inbox.py +37 -0
  23. strands_issue_writer-0.1.0/strands_issue_writer/tools/review.py +74 -0
  24. strands_issue_writer-0.1.0/strands_issue_writer/tools/tracker.py +104 -0
  25. strands_issue_writer-0.1.0/tests/test_models.py +61 -0
  26. strands_issue_writer-0.1.0/tests/test_provider.py +24 -0
  27. strands_issue_writer-0.1.0/tests/test_review.py +65 -0
  28. strands_issue_writer-0.1.0/tests/test_tracker.py +40 -0
  29. strands_issue_writer-0.1.0/uv.lock +2206 -0
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ check:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v5
14
+ with:
15
+ enable-cache: true
16
+ - run: uv python install 3.12
17
+ - run: uv sync --group dev --extra ollama --extra vllm --extra dashboard
18
+ - run: uv run ruff check .
19
+ # No model is served in CI; these tests cover the contract, the rule
20
+ # review and the tracker payloads, all of which are deterministic.
21
+ - run: uv run pytest
@@ -0,0 +1,61 @@
1
+ name: Publish to PyPI
2
+
3
+ # Publishing is deliberate: tag a release, or run it by hand.
4
+ on:
5
+ workflow_dispatch:
6
+ inputs:
7
+ target:
8
+ description: "Where to publish"
9
+ type: choice
10
+ options: [testpypi, pypi]
11
+ default: testpypi
12
+ push:
13
+ tags: ["v*"]
14
+
15
+ jobs:
16
+ build:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: astral-sh/setup-uv@v5
21
+ - run: uv python install 3.12
22
+
23
+ # Never publish something that does not pass its own gate.
24
+ - run: uv sync --group dev --extra ollama --extra vllm --extra dashboard
25
+ - run: uv run ruff check .
26
+ - run: uv run pytest
27
+
28
+ - name: Build
29
+ run: uv build
30
+
31
+ - name: Check metadata
32
+ run: uvx twine check dist/*
33
+
34
+ - uses: actions/upload-artifact@v4
35
+ with:
36
+ name: dist
37
+ path: dist/
38
+
39
+ publish:
40
+ needs: build
41
+ runs-on: ubuntu-latest
42
+ environment: ${{ github.event.inputs.target || 'pypi' }}
43
+ # Trusted publishing: no API token to store or rotate. Configure the
44
+ # publisher once at pypi.org/manage/account/publishing/
45
+ permissions:
46
+ id-token: write
47
+ steps:
48
+ - uses: actions/download-artifact@v4
49
+ with:
50
+ name: dist
51
+ path: dist/
52
+
53
+ - name: Publish to TestPyPI
54
+ if: ${{ github.event.inputs.target == 'testpypi' }}
55
+ uses: pypa/gh-action-pypi-publish@release/v1
56
+ with:
57
+ repository-url: https://test.pypi.org/legacy/
58
+
59
+ - name: Publish to PyPI
60
+ if: ${{ github.event.inputs.target == 'pypi' || startsWith(github.ref, 'refs/tags/v') }}
61
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,14 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ build/
9
+ .DS_Store
10
+
11
+ # local model artefacts and inputs
12
+ model/
13
+ *.gguf
14
+ inbox.jsonl
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Furkan Portakal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ # Example Modelfile for Ollama. Adjust the FROM path to your downloaded GGUF.
2
+ FROM ./model/issue-writer-gemma4.q4_k_m.gguf
3
+
4
+ # The model fills a fixed schema; sampling only produces malformed JSON.
5
+ PARAMETER temperature 0
6
+ PARAMETER top_p 1
7
+ PARAMETER num_ctx 4096
8
+ PARAMETER num_predict 2048
9
+
10
+ SYSTEM """You are a senior agile delivery assistant. You turn raw product input into well-formed issues. Reply with a single valid JSON object and nothing else. Follow INVEST, write testable Given/When/Then acceptance criteria, and never invent facts: anything the input does not state goes into `assumptions` or `clarifying_questions`."""
@@ -0,0 +1,210 @@
1
+ Metadata-Version: 2.5
2
+ Name: strands-issue-writer
3
+ Version: 0.1.0
4
+ Summary: A Strands agent that turns raw product chatter into well-formed issue tracker entries, powered by a locally served fine-tuned model
5
+ Project-URL: Homepage, https://github.com/fport/strands-issue-writer
6
+ Project-URL: Dataset, https://huggingface.co/datasets/fport/issue-writer-tr-en
7
+ Project-URL: Training, https://github.com/fport/issue-writer
8
+ Project-URL: Issues, https://github.com/fport/strands-issue-writer/issues
9
+ Author: Furkan Portakal
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,issue-tracker,jira,llm,lora,ollama,strands,vllm
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Topic :: Software Development :: Bug Tracking
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: httpx>=0.27
21
+ Requires-Dist: pydantic>=2.9
22
+ Requires-Dist: strands-agents>=1.0
23
+ Provides-Extra: all
24
+ Requires-Dist: fastapi>=0.115; extra == 'all'
25
+ Requires-Dist: ollama>=0.4; extra == 'all'
26
+ Requires-Dist: openai>=1.50; extra == 'all'
27
+ Requires-Dist: uvicorn[standard]>=0.32; extra == 'all'
28
+ Provides-Extra: dashboard
29
+ Requires-Dist: fastapi>=0.115; extra == 'dashboard'
30
+ Requires-Dist: uvicorn[standard]>=0.32; extra == 'dashboard'
31
+ Provides-Extra: ollama
32
+ Requires-Dist: ollama>=0.4; extra == 'ollama'
33
+ Provides-Extra: vllm
34
+ Requires-Dist: openai>=1.50; extra == 'vllm'
35
+ Description-Content-Type: text/markdown
36
+
37
+ <div align="center">
38
+ <img src="strands-issue-writer-logo.svg" alt="strands-issue-writer" width="180">
39
+ <h1>strands-issue-writer</h1>
40
+ <p><strong>Turn product chatter into well-formed issues.</strong> Locally served, rule-checked, never invented.</p>
41
+ <p>
42
+ <a href="https://pypi.org/project/strands-issue-writer/"><img alt="PyPI" src="https://img.shields.io/pypi/v/strands-issue-writer?style=flat-square&color=F97316"></a>
43
+ <a href="https://pypi.org/project/strands-issue-writer/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/strands-issue-writer?style=flat-square"></a>
44
+ <a href="https://github.com/fport/strands-issue-writer/actions"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/fport/strands-issue-writer/ci.yml?style=flat-square&label=CI"></a>
45
+ <a href="https://huggingface.co/datasets/fport/issue-writer-tr-en"><img alt="Dataset" src="https://img.shields.io/badge/%F0%9F%A4%97%20dataset-issue--writer--tr--en-yellow?style=flat-square"></a>
46
+ <a href="https://github.com/fport/strands-issue-writer/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-green?style=flat-square"></a>
47
+ </p>
48
+ </div>
49
+
50
+ ---
51
+
52
+ A [Strands](https://strandsagents.com/) agent that turns raw product chatter —
53
+ Slack messages, support tickets, Sentry alerts, meeting notes — into well-formed
54
+ issue tracker entries, using a **fine-tuned model you serve yourself**.
55
+
56
+ Nothing leaves your machine. The model is the one trained in
57
+ [fport/issue-writer](https://github.com/fport/issue-writer) on
58
+ [fport/issue-writer-tr-en](https://huggingface.co/datasets/fport/issue-writer-tr-en),
59
+ served through Ollama or vLLM.
60
+
61
+ ```
62
+ $ strands-issue-writer draft "selam, müşteriler fatura geçmişini tek tek açmak
63
+ yerine toplu PDF olarak indirmek istiyor. muhasebeciler ayda 30-40 fatura
64
+ indiriyor, çok vakit alıyor. bu sprint yetişir mi?"
65
+
66
+ [Story] Fatura geçmişini tek PDF olarak dışa aktarma ekle
67
+ priority Medium · 5 points
68
+ components: Billing labels: export, self-service
69
+
70
+ h2. Kullanıcı Hikâyesi
71
+ Bir muhasebe işlerini yürüten müşteri olarak fatura geçmişimi tek bir PDF olarak
72
+ indirmek istiyorum; böylece her faturayı tek tek açmak zorunda kalmayayım.
73
+ ...
74
+
75
+ Assumptions:
76
+ - Dışa aktarma kapsamının seçilen tarih aralığı olduğu varsayıldı; girdide sınır
77
+ belirtilmedi.
78
+ Open questions:
79
+ - İndirilebilecek en geniş tarih aralığı ne olmalı?
80
+ ```
81
+
82
+ ## Two models, two jobs
83
+
84
+ The distinction matters and it is easy to get wrong:
85
+
86
+ **The writer** is the fine-tuned model behind `draft_issue`. It is good at exactly
87
+ one thing — turning messy input into a structured issue that obeys the writing
88
+ rules. It is not a general assistant and should not be asked to reason about your
89
+ backlog.
90
+
91
+ **The orchestrator** runs the Strands reasoning loop: which tool to call, whether a
92
+ draft is good enough, when to ask instead of guess. By default it is the same local
93
+ model, which keeps everything offline. Give it a stronger model when you want better
94
+ judgement about pushing back on a draft:
95
+
96
+ ```python
97
+ from strands.models.anthropic import AnthropicModel
98
+ from strands_issue_writer import build_agent
99
+
100
+ agent = build_agent(orchestrator_model=AnthropicModel(model_id="claude-opus-5"))
101
+ ```
102
+
103
+ The writer stays local either way — your product conversations never leave.
104
+
105
+ ## Install
106
+
107
+ ```bash
108
+ pip install "strands-issue-writer[ollama,dashboard]" # or [vllm,dashboard]
109
+ ```
110
+
111
+ From source, for development:
112
+
113
+ ```bash
114
+ git clone https://github.com/fport/strands-issue-writer && cd strands-issue-writer
115
+ uv sync --group dev --extra ollama --extra dashboard
116
+ ```
117
+
118
+ Then serve the model. [`docs/SERVING.md`](docs/SERVING.md) covers going from a
119
+ trained LoRA adapter to a running endpoint; the short version:
120
+
121
+ ```bash
122
+ huggingface-cli download fport/issue-writer-gemma4-gguf --include "*q4_k_m.gguf" --local-dir ./model
123
+ ollama create issue-writer -f Modelfile.example
124
+ export ISSUE_WRITER_PROVIDER=ollama ISSUE_WRITER_MODEL=issue-writer
125
+ strands-issue-writer doctor
126
+ ```
127
+
128
+ ## Use
129
+
130
+ ```bash
131
+ strands-issue-writer doctor # is the endpoint alive, right model?
132
+ strands-issue-writer draft "…" [--json] # one issue, rendered or as JSON
133
+ strands-issue-writer agent # interactive agent with all tools
134
+ strands-issue-writer dashboard # web UI on :8765
135
+ ```
136
+
137
+ As a library:
138
+
139
+ ```python
140
+ from strands_issue_writer import build_agent
141
+
142
+ agent = build_agent()
143
+ agent("Read inbox.jsonl and draft issues for anything that looks like a bug. "
144
+ "Show me each one, do not push anything.")
145
+ ```
146
+
147
+ ## Tools
148
+
149
+ | Tool | What it does |
150
+ |---|---|
151
+ | `draft_issue` | raw text → validated `Issue` via the local writer model |
152
+ | `review_issue` | rule checks: schema, sections, reproduction steps, invented facts, criteria observability |
153
+ | `render_issue` | human-readable rendering for review before anything is published |
154
+ | `push_to_tracker` | creates the issue — **dry run unless `confirm=True`** |
155
+ | `read_inbox` | reads pending raw inputs from a `.jsonl` or `---`-separated file |
156
+
157
+ `review_issue` is deliberately **not** model-based. These are checks a rule can
158
+ make, so a rule makes them: deterministically, on every draft, for free. It catches
159
+ the failure that matters most with a small local model — inventing a version number
160
+ or a metric that was never in the input.
161
+
162
+ `push_to_tracker` refuses to publish without explicit confirmation. Creating tickets
163
+ in someone's tracker is an outward action; it should not happen because a model
164
+ decided it was helpful.
165
+
166
+ ## Dashboard
167
+
168
+ ```bash
169
+ strands-issue-writer dashboard
170
+ ```
171
+
172
+ Three panels answering the three questions you actually have while a local model
173
+ drafts for you:
174
+
175
+ - **left** — the raw input, and a history of what you have drafted this session
176
+ - **centre** — the issue as it came out: type, summary, chips for priority and
177
+ components, the rendered body
178
+ - **right** — the rule review. Green when it passes, and when it does not, the
179
+ violations by name. Below that, the model's own assumptions and open questions,
180
+ which is where you find out whether it filled a gap honestly or silently.
181
+
182
+ The header shows which provider and model answered, and how long it took. A local
183
+ 4B model on a laptop runs about 3–8 seconds per issue.
184
+
185
+ ## Configuration
186
+
187
+ | Variable | Default | Meaning |
188
+ |---|---|---|
189
+ | `ISSUE_WRITER_PROVIDER` | `ollama` | `ollama`, `vllm`, or `openai` |
190
+ | `ISSUE_WRITER_MODEL` | `issue-writer` | served model name |
191
+ | `ISSUE_WRITER_HOST` | `http://localhost:11434` | Ollama host |
192
+ | `ISSUE_WRITER_BASE_URL` | `http://localhost:8000/v1` | OpenAI-compatible endpoint |
193
+ | `ISSUE_WRITER_TEMPERATURE` | `0` | leave it there; the output is a schema |
194
+ | `TRACKER_URL` / `TRACKER_EMAIL` / `TRACKER_TOKEN` | — | only needed to actually publish |
195
+
196
+ ## Development
197
+
198
+ ```bash
199
+ uv sync --group dev
200
+ uv run pytest
201
+ uv run ruff check .
202
+ ```
203
+
204
+ The test suite runs without a model: the contract, the rule review and the tracker
205
+ payloads are all deterministic. Only `draft_issue` needs a served endpoint, and
206
+ `doctor` tells you whether you have one.
207
+
208
+ ## License
209
+
210
+ MIT.
@@ -0,0 +1,174 @@
1
+ <div align="center">
2
+ <img src="strands-issue-writer-logo.svg" alt="strands-issue-writer" width="180">
3
+ <h1>strands-issue-writer</h1>
4
+ <p><strong>Turn product chatter into well-formed issues.</strong> Locally served, rule-checked, never invented.</p>
5
+ <p>
6
+ <a href="https://pypi.org/project/strands-issue-writer/"><img alt="PyPI" src="https://img.shields.io/pypi/v/strands-issue-writer?style=flat-square&color=F97316"></a>
7
+ <a href="https://pypi.org/project/strands-issue-writer/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/strands-issue-writer?style=flat-square"></a>
8
+ <a href="https://github.com/fport/strands-issue-writer/actions"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/fport/strands-issue-writer/ci.yml?style=flat-square&label=CI"></a>
9
+ <a href="https://huggingface.co/datasets/fport/issue-writer-tr-en"><img alt="Dataset" src="https://img.shields.io/badge/%F0%9F%A4%97%20dataset-issue--writer--tr--en-yellow?style=flat-square"></a>
10
+ <a href="https://github.com/fport/strands-issue-writer/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-green?style=flat-square"></a>
11
+ </p>
12
+ </div>
13
+
14
+ ---
15
+
16
+ A [Strands](https://strandsagents.com/) agent that turns raw product chatter —
17
+ Slack messages, support tickets, Sentry alerts, meeting notes — into well-formed
18
+ issue tracker entries, using a **fine-tuned model you serve yourself**.
19
+
20
+ Nothing leaves your machine. The model is the one trained in
21
+ [fport/issue-writer](https://github.com/fport/issue-writer) on
22
+ [fport/issue-writer-tr-en](https://huggingface.co/datasets/fport/issue-writer-tr-en),
23
+ served through Ollama or vLLM.
24
+
25
+ ```
26
+ $ strands-issue-writer draft "selam, müşteriler fatura geçmişini tek tek açmak
27
+ yerine toplu PDF olarak indirmek istiyor. muhasebeciler ayda 30-40 fatura
28
+ indiriyor, çok vakit alıyor. bu sprint yetişir mi?"
29
+
30
+ [Story] Fatura geçmişini tek PDF olarak dışa aktarma ekle
31
+ priority Medium · 5 points
32
+ components: Billing labels: export, self-service
33
+
34
+ h2. Kullanıcı Hikâyesi
35
+ Bir muhasebe işlerini yürüten müşteri olarak fatura geçmişimi tek bir PDF olarak
36
+ indirmek istiyorum; böylece her faturayı tek tek açmak zorunda kalmayayım.
37
+ ...
38
+
39
+ Assumptions:
40
+ - Dışa aktarma kapsamının seçilen tarih aralığı olduğu varsayıldı; girdide sınır
41
+ belirtilmedi.
42
+ Open questions:
43
+ - İndirilebilecek en geniş tarih aralığı ne olmalı?
44
+ ```
45
+
46
+ ## Two models, two jobs
47
+
48
+ The distinction matters and it is easy to get wrong:
49
+
50
+ **The writer** is the fine-tuned model behind `draft_issue`. It is good at exactly
51
+ one thing — turning messy input into a structured issue that obeys the writing
52
+ rules. It is not a general assistant and should not be asked to reason about your
53
+ backlog.
54
+
55
+ **The orchestrator** runs the Strands reasoning loop: which tool to call, whether a
56
+ draft is good enough, when to ask instead of guess. By default it is the same local
57
+ model, which keeps everything offline. Give it a stronger model when you want better
58
+ judgement about pushing back on a draft:
59
+
60
+ ```python
61
+ from strands.models.anthropic import AnthropicModel
62
+ from strands_issue_writer import build_agent
63
+
64
+ agent = build_agent(orchestrator_model=AnthropicModel(model_id="claude-opus-5"))
65
+ ```
66
+
67
+ The writer stays local either way — your product conversations never leave.
68
+
69
+ ## Install
70
+
71
+ ```bash
72
+ pip install "strands-issue-writer[ollama,dashboard]" # or [vllm,dashboard]
73
+ ```
74
+
75
+ From source, for development:
76
+
77
+ ```bash
78
+ git clone https://github.com/fport/strands-issue-writer && cd strands-issue-writer
79
+ uv sync --group dev --extra ollama --extra dashboard
80
+ ```
81
+
82
+ Then serve the model. [`docs/SERVING.md`](docs/SERVING.md) covers going from a
83
+ trained LoRA adapter to a running endpoint; the short version:
84
+
85
+ ```bash
86
+ huggingface-cli download fport/issue-writer-gemma4-gguf --include "*q4_k_m.gguf" --local-dir ./model
87
+ ollama create issue-writer -f Modelfile.example
88
+ export ISSUE_WRITER_PROVIDER=ollama ISSUE_WRITER_MODEL=issue-writer
89
+ strands-issue-writer doctor
90
+ ```
91
+
92
+ ## Use
93
+
94
+ ```bash
95
+ strands-issue-writer doctor # is the endpoint alive, right model?
96
+ strands-issue-writer draft "…" [--json] # one issue, rendered or as JSON
97
+ strands-issue-writer agent # interactive agent with all tools
98
+ strands-issue-writer dashboard # web UI on :8765
99
+ ```
100
+
101
+ As a library:
102
+
103
+ ```python
104
+ from strands_issue_writer import build_agent
105
+
106
+ agent = build_agent()
107
+ agent("Read inbox.jsonl and draft issues for anything that looks like a bug. "
108
+ "Show me each one, do not push anything.")
109
+ ```
110
+
111
+ ## Tools
112
+
113
+ | Tool | What it does |
114
+ |---|---|
115
+ | `draft_issue` | raw text → validated `Issue` via the local writer model |
116
+ | `review_issue` | rule checks: schema, sections, reproduction steps, invented facts, criteria observability |
117
+ | `render_issue` | human-readable rendering for review before anything is published |
118
+ | `push_to_tracker` | creates the issue — **dry run unless `confirm=True`** |
119
+ | `read_inbox` | reads pending raw inputs from a `.jsonl` or `---`-separated file |
120
+
121
+ `review_issue` is deliberately **not** model-based. These are checks a rule can
122
+ make, so a rule makes them: deterministically, on every draft, for free. It catches
123
+ the failure that matters most with a small local model — inventing a version number
124
+ or a metric that was never in the input.
125
+
126
+ `push_to_tracker` refuses to publish without explicit confirmation. Creating tickets
127
+ in someone's tracker is an outward action; it should not happen because a model
128
+ decided it was helpful.
129
+
130
+ ## Dashboard
131
+
132
+ ```bash
133
+ strands-issue-writer dashboard
134
+ ```
135
+
136
+ Three panels answering the three questions you actually have while a local model
137
+ drafts for you:
138
+
139
+ - **left** — the raw input, and a history of what you have drafted this session
140
+ - **centre** — the issue as it came out: type, summary, chips for priority and
141
+ components, the rendered body
142
+ - **right** — the rule review. Green when it passes, and when it does not, the
143
+ violations by name. Below that, the model's own assumptions and open questions,
144
+ which is where you find out whether it filled a gap honestly or silently.
145
+
146
+ The header shows which provider and model answered, and how long it took. A local
147
+ 4B model on a laptop runs about 3–8 seconds per issue.
148
+
149
+ ## Configuration
150
+
151
+ | Variable | Default | Meaning |
152
+ |---|---|---|
153
+ | `ISSUE_WRITER_PROVIDER` | `ollama` | `ollama`, `vllm`, or `openai` |
154
+ | `ISSUE_WRITER_MODEL` | `issue-writer` | served model name |
155
+ | `ISSUE_WRITER_HOST` | `http://localhost:11434` | Ollama host |
156
+ | `ISSUE_WRITER_BASE_URL` | `http://localhost:8000/v1` | OpenAI-compatible endpoint |
157
+ | `ISSUE_WRITER_TEMPERATURE` | `0` | leave it there; the output is a schema |
158
+ | `TRACKER_URL` / `TRACKER_EMAIL` / `TRACKER_TOKEN` | — | only needed to actually publish |
159
+
160
+ ## Development
161
+
162
+ ```bash
163
+ uv sync --group dev
164
+ uv run pytest
165
+ uv run ruff check .
166
+ ```
167
+
168
+ The test suite runs without a model: the contract, the rule review and the tracker
169
+ payloads are all deterministic. Only `draft_issue` needs a served endpoint, and
170
+ `doctor` tells you whether you have one.
171
+
172
+ ## License
173
+
174
+ MIT.