issue-flow 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jan Petter Maehlen
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,147 @@
1
+ Metadata-Version: 2.3
2
+ Name: issue-flow
3
+ Version: 0.1.0
4
+ Summary: Agents should behave. Let them follow the issue flow.
5
+ Keywords: cursor,ai,agents,issue-tracking,workflow
6
+ Author: jepegit
7
+ Author-email: jepegit <jepe@ife.no>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Jan Petter Maehlen
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ Classifier: Development Status :: 3 - Alpha
30
+ Classifier: Intended Audience :: Developers
31
+ Classifier: Topic :: Software Development :: Build Tools
32
+ Classifier: Programming Language :: Python :: 3.13
33
+ Requires-Dist: jinja2>=3.1.6
34
+ Requires-Dist: python-dotenv>=1.2.2
35
+ Requires-Dist: rich>=14.3.3
36
+ Requires-Dist: typer>=0.24.1
37
+ Requires-Python: >=3.13
38
+ Project-URL: Homepage, https://github.com/jepegit/issue-flow
39
+ Project-URL: Repository, https://github.com/jepegit/issue-flow
40
+ Project-URL: Issues, https://github.com/jepegit/issue-flow/issues
41
+ Description-Content-Type: text/markdown
42
+
43
+ # issue-flow
44
+
45
+ Agents should behave. Let them follow the issue flow.
46
+
47
+ **issue-flow** scaffolds a lightweight issue-tracking workflow into your project so that Cursor AI agents can pick up GitHub issues, plan work, and land PRs in a consistent way.
48
+
49
+ ## What it does
50
+
51
+ Running `issue-flow init` in your project root creates:
52
+
53
+ ```text
54
+ your-project/
55
+ .issueflows/
56
+ 00-tools/ # Helper scripts for agents
57
+ 01-current-issues/ # Active issue markdown files
58
+ 02-partly-solved-issues/ # Parked / in-progress issues
59
+ 03-solved-issues/ # Completed issues archive
60
+ .cursor/
61
+ commands/
62
+ issue-init.md # /issue-init — fetch a GitHub issue locally
63
+ issue-start.md # /issue-start — plan and implement
64
+ issue-close.md # /issue-close — test, commit, push, PR
65
+ rules/
66
+ issueflow-rules.mdc # Always-on Cursor rule for the workflow
67
+ docs/
68
+ cursor-issue-workflow.md # Human-readable overview of the workflow
69
+ ```
70
+
71
+ The three Cursor slash commands give agents a repeatable flow:
72
+
73
+ 1. `/issue-init 42` — pulls GitHub issue #42 into `.issueflows/01-current-issues/` and archives older issues.
74
+ 2. `/issue-start` — reads the issue file, plans, and implements.
75
+ 3. `/issue-close` — runs tests, updates status files, commits, pushes, and opens a PR.
76
+
77
+ ## Installation
78
+
79
+ Requires Python 3.13+ and [uv](https://docs.astral.sh/uv/).
80
+
81
+ ```bash
82
+ uv tool install issue-flow
83
+ ```
84
+
85
+ Or add it as a dev dependency to your project:
86
+
87
+ ```bash
88
+ uv add --dev issue-flow
89
+ ```
90
+
91
+ ## Quick start
92
+
93
+ ```bash
94
+ cd your-project
95
+ issue-flow init
96
+ ```
97
+
98
+ That's it. Open the project in Cursor and use `/issue-init`, `/issue-start`, `/issue-close`.
99
+
100
+ ## Usage
101
+
102
+ ```
103
+ issue-flow init [PROJECT_DIR] [--force]
104
+ ```
105
+
106
+ | Argument / Option | Description |
107
+ |---|---|
108
+ | `PROJECT_DIR` | Project root directory. Defaults to `.` (current directory). |
109
+ | `--force`, `-f` | Overwrite existing files instead of skipping them. |
110
+
111
+ Running `init` a second time is safe — existing files are skipped unless `--force` is passed.
112
+
113
+ ## Configuration
114
+
115
+ issue-flow reads a `.env` file from the project root (via python-dotenv). The following environment variables are supported:
116
+
117
+ | Variable | Default | Description |
118
+ |---|---|---|
119
+ | `ISSUEFLOW_DIR` | `.issueflows` | Name of the issue-tracking directory. |
120
+ | `ISSUEFLOW_CURSOR_DIR` | `.cursor` | Name of the Cursor config directory. |
121
+ | `ISSUEFLOW_DOCS_DIR` | `docs` | Where to write the workflow documentation file. |
122
+
123
+ ## Development
124
+
125
+ ```bash
126
+ git clone https://github.com/jepegit/issue-flow.git
127
+ cd issue-flow
128
+ uv sync
129
+
130
+ # Run tests
131
+ uv run pytest
132
+
133
+ # Lint
134
+ uv run ruff check src/ tests/
135
+ ```
136
+
137
+ ## Future plans
138
+
139
+ - **Multi-tool support** — generate config for other AI coding tools (Claude Code, Windsurf, etc.) in addition to Cursor.
140
+ - **`issue-flow status`** — show a dashboard of current, partly-solved, and solved issues.
141
+ - **Custom templates** — let users supply their own Jinja2 templates to tailor slash commands and rules to their team's conventions.
142
+ - **Git hook integration** — optionally move issue files on commit based on status markers.
143
+ - **GitHub Actions workflow** — ship a reusable action that syncs issue state between `.issueflows/` and GitHub issue labels/milestones.
144
+
145
+ ## License
146
+
147
+ See [LICENSE](LICENSE).
@@ -0,0 +1,105 @@
1
+ # issue-flow
2
+
3
+ Agents should behave. Let them follow the issue flow.
4
+
5
+ **issue-flow** scaffolds a lightweight issue-tracking workflow into your project so that Cursor AI agents can pick up GitHub issues, plan work, and land PRs in a consistent way.
6
+
7
+ ## What it does
8
+
9
+ Running `issue-flow init` in your project root creates:
10
+
11
+ ```text
12
+ your-project/
13
+ .issueflows/
14
+ 00-tools/ # Helper scripts for agents
15
+ 01-current-issues/ # Active issue markdown files
16
+ 02-partly-solved-issues/ # Parked / in-progress issues
17
+ 03-solved-issues/ # Completed issues archive
18
+ .cursor/
19
+ commands/
20
+ issue-init.md # /issue-init — fetch a GitHub issue locally
21
+ issue-start.md # /issue-start — plan and implement
22
+ issue-close.md # /issue-close — test, commit, push, PR
23
+ rules/
24
+ issueflow-rules.mdc # Always-on Cursor rule for the workflow
25
+ docs/
26
+ cursor-issue-workflow.md # Human-readable overview of the workflow
27
+ ```
28
+
29
+ The three Cursor slash commands give agents a repeatable flow:
30
+
31
+ 1. `/issue-init 42` — pulls GitHub issue #42 into `.issueflows/01-current-issues/` and archives older issues.
32
+ 2. `/issue-start` — reads the issue file, plans, and implements.
33
+ 3. `/issue-close` — runs tests, updates status files, commits, pushes, and opens a PR.
34
+
35
+ ## Installation
36
+
37
+ Requires Python 3.13+ and [uv](https://docs.astral.sh/uv/).
38
+
39
+ ```bash
40
+ uv tool install issue-flow
41
+ ```
42
+
43
+ Or add it as a dev dependency to your project:
44
+
45
+ ```bash
46
+ uv add --dev issue-flow
47
+ ```
48
+
49
+ ## Quick start
50
+
51
+ ```bash
52
+ cd your-project
53
+ issue-flow init
54
+ ```
55
+
56
+ That's it. Open the project in Cursor and use `/issue-init`, `/issue-start`, `/issue-close`.
57
+
58
+ ## Usage
59
+
60
+ ```
61
+ issue-flow init [PROJECT_DIR] [--force]
62
+ ```
63
+
64
+ | Argument / Option | Description |
65
+ |---|---|
66
+ | `PROJECT_DIR` | Project root directory. Defaults to `.` (current directory). |
67
+ | `--force`, `-f` | Overwrite existing files instead of skipping them. |
68
+
69
+ Running `init` a second time is safe — existing files are skipped unless `--force` is passed.
70
+
71
+ ## Configuration
72
+
73
+ issue-flow reads a `.env` file from the project root (via python-dotenv). The following environment variables are supported:
74
+
75
+ | Variable | Default | Description |
76
+ |---|---|---|
77
+ | `ISSUEFLOW_DIR` | `.issueflows` | Name of the issue-tracking directory. |
78
+ | `ISSUEFLOW_CURSOR_DIR` | `.cursor` | Name of the Cursor config directory. |
79
+ | `ISSUEFLOW_DOCS_DIR` | `docs` | Where to write the workflow documentation file. |
80
+
81
+ ## Development
82
+
83
+ ```bash
84
+ git clone https://github.com/jepegit/issue-flow.git
85
+ cd issue-flow
86
+ uv sync
87
+
88
+ # Run tests
89
+ uv run pytest
90
+
91
+ # Lint
92
+ uv run ruff check src/ tests/
93
+ ```
94
+
95
+ ## Future plans
96
+
97
+ - **Multi-tool support** — generate config for other AI coding tools (Claude Code, Windsurf, etc.) in addition to Cursor.
98
+ - **`issue-flow status`** — show a dashboard of current, partly-solved, and solved issues.
99
+ - **Custom templates** — let users supply their own Jinja2 templates to tailor slash commands and rules to their team's conventions.
100
+ - **Git hook integration** — optionally move issue files on commit based on status markers.
101
+ - **GitHub Actions workflow** — ship a reusable action that syncs issue state between `.issueflows/` and GitHub issue labels/milestones.
102
+
103
+ ## License
104
+
105
+ See [LICENSE](LICENSE).
@@ -0,0 +1,41 @@
1
+ [project]
2
+ name = "issue-flow"
3
+ version = "0.1.0"
4
+ description = "Agents should behave. Let them follow the issue flow."
5
+ readme = "README.md"
6
+ license = { file = "LICENSE" }
7
+ authors = [
8
+ { name = "jepegit", email = "jepe@ife.no" }
9
+ ]
10
+ requires-python = ">=3.13"
11
+ keywords = ["cursor", "ai", "agents", "issue-tracking", "workflow"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Intended Audience :: Developers",
15
+ "Topic :: Software Development :: Build Tools",
16
+ "Programming Language :: Python :: 3.13",
17
+ ]
18
+ dependencies = [
19
+ "jinja2>=3.1.6",
20
+ "python-dotenv>=1.2.2",
21
+ "rich>=14.3.3",
22
+ "typer>=0.24.1",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/jepegit/issue-flow"
27
+ Repository = "https://github.com/jepegit/issue-flow"
28
+ Issues = "https://github.com/jepegit/issue-flow/issues"
29
+
30
+ [project.scripts]
31
+ issue-flow = "issue_flow.cli:main"
32
+
33
+ [build-system]
34
+ requires = ["uv_build>=0.10.2,<0.11.0"]
35
+ build-backend = "uv_build"
36
+
37
+ [dependency-groups]
38
+ dev = [
39
+ "pytest>=9.0.2",
40
+ "ruff>=0.15.9",
41
+ ]
@@ -0,0 +1,3 @@
1
+ """issue-flow: Agents should behave. Let them follow the issue flow."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,44 @@
1
+ """Command-line interface for issue-flow."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import typer
8
+
9
+ app = typer.Typer(
10
+ name="issue-flow",
11
+ add_completion=False,
12
+ )
13
+
14
+
15
+ @app.callback()
16
+ def _callback() -> None:
17
+ """Agents should behave. Let them follow the issue flow."""
18
+
19
+
20
+ @app.command()
21
+ def init(
22
+ project_dir: Path = typer.Argument(
23
+ default=Path("."),
24
+ help="Project root directory (defaults to current directory).",
25
+ exists=True,
26
+ file_okay=False,
27
+ resolve_path=True,
28
+ ),
29
+ force: bool = typer.Option(
30
+ False,
31
+ "--force",
32
+ "-f",
33
+ help="Overwrite existing files without asking.",
34
+ ),
35
+ ) -> None:
36
+ """Scaffold issue-flow directories and Cursor config files in a project."""
37
+ from issue_flow.init import run_init
38
+
39
+ run_init(project_root=project_dir, force=force)
40
+
41
+
42
+ def main() -> None:
43
+ """Entry point for the `issue-flow` console script."""
44
+ app()
@@ -0,0 +1,76 @@
1
+ """Configuration for issue-flow, backed by .env files and environment variables."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+
8
+ from dotenv import load_dotenv
9
+ import os
10
+
11
+
12
+ # Load .env from the current working directory (the user's project root).
13
+ # This runs at import time so that all downstream code sees the env vars.
14
+ load_dotenv(override=False)
15
+
16
+
17
+ @dataclass
18
+ class Settings:
19
+ """Runtime settings for issue-flow.
20
+
21
+ Values come from environment variables (prefixed with ISSUEFLOW_) with
22
+ sensible defaults. A .env file in the project root is loaded automatically.
23
+ """
24
+
25
+ issueflows_dir: str = field(
26
+ default_factory=lambda: os.getenv("ISSUEFLOW_DIR", ".issueflows")
27
+ )
28
+ cursor_dir: str = field(
29
+ default_factory=lambda: os.getenv("ISSUEFLOW_CURSOR_DIR", ".cursor")
30
+ )
31
+ docs_dir: str = field(
32
+ default_factory=lambda: os.getenv("ISSUEFLOW_DOCS_DIR", "docs")
33
+ )
34
+
35
+ # Subdirectory names inside .issueflows/
36
+ tools_folder: str = "00-tools"
37
+ current_issues_folder: str = "01-current-issues"
38
+ partly_solved_folder: str = "02-partly-solved-issues"
39
+ solved_folder: str = "03-solved-issues"
40
+
41
+ @property
42
+ def issueflows_subdirs(self) -> list[str]:
43
+ return [
44
+ self.tools_folder,
45
+ self.current_issues_folder,
46
+ self.partly_solved_folder,
47
+ self.solved_folder,
48
+ ]
49
+
50
+ def template_context(self, project_root: Path) -> dict[str, str]:
51
+ """Build the Jinja2 template context dictionary."""
52
+ project_name = _detect_project_name(project_root)
53
+ return {
54
+ "issueflows_dir": self.issueflows_dir,
55
+ "cursor_dir": self.cursor_dir,
56
+ "docs_dir": self.docs_dir,
57
+ "tools_folder": self.tools_folder,
58
+ "current_issues_folder": self.current_issues_folder,
59
+ "partly_solved_folder": self.partly_solved_folder,
60
+ "solved_folder": self.solved_folder,
61
+ "project_name": project_name,
62
+ }
63
+
64
+
65
+ def _detect_project_name(project_root: Path) -> str:
66
+ """Try to read the project name from pyproject.toml, fall back to dir name."""
67
+ pyproject = project_root / "pyproject.toml"
68
+ if pyproject.exists():
69
+ for line in pyproject.read_text(encoding="utf-8").splitlines():
70
+ stripped = line.strip()
71
+ if stripped.startswith("name") and "=" in stripped:
72
+ # e.g. name = "my-project"
73
+ value = stripped.split("=", 1)[1].strip().strip('"').strip("'")
74
+ if value:
75
+ return value
76
+ return project_root.resolve().name
@@ -0,0 +1,83 @@
1
+ """Implementation of the `issue-flow init` command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from rich.console import Console
8
+
9
+ from issue_flow.config import Settings
10
+ from issue_flow.templating import (
11
+ TEMPLATE_MANIFEST,
12
+ render_template,
13
+ resolve_output_path,
14
+ )
15
+
16
+ console = Console()
17
+
18
+
19
+ def run_init(project_root: Path, force: bool = False) -> None:
20
+ """Scaffold .issueflows/ directories and .cursor/ config files.
21
+
22
+ Args:
23
+ project_root: Absolute path to the user's project directory.
24
+ force: If True, overwrite existing files without asking.
25
+ """
26
+ settings = Settings()
27
+ context = settings.template_context(project_root)
28
+
29
+ console.print(
30
+ f"\n[bold]Initializing issue-flow in [cyan]{project_root}[/cyan][/bold]\n"
31
+ )
32
+
33
+ # ── 1. Create .issueflows/ subdirectories ────────────────────────
34
+ _create_issueflow_dirs(project_root, settings)
35
+
36
+ # ── 2. Render and write template files ───────────────────────────
37
+ written_files: list[Path] = []
38
+ skipped_files: list[Path] = []
39
+
40
+ for template_name, path_template in TEMPLATE_MANIFEST:
41
+ relative_path = resolve_output_path(path_template, context)
42
+ absolute_path = project_root / relative_path
43
+
44
+ if absolute_path.exists() and not force:
45
+ console.print(
46
+ f" [yellow]skip[/yellow] {relative_path} (already exists, use --force to overwrite)"
47
+ )
48
+ skipped_files.append(relative_path)
49
+ continue
50
+
51
+ rendered = render_template(template_name, context)
52
+ absolute_path.parent.mkdir(parents=True, exist_ok=True)
53
+ absolute_path.write_text(rendered, encoding="utf-8")
54
+ console.print(f" [green]write[/green] {relative_path}")
55
+ written_files.append(relative_path)
56
+
57
+ # ── 3. Summary ───────────────────────────────────────────────────
58
+ console.print()
59
+ if written_files:
60
+ console.print(
61
+ f"[bold green]Created {len(written_files)} file(s).[/bold green]"
62
+ )
63
+ if skipped_files:
64
+ console.print(
65
+ f"[bold yellow]Skipped {len(skipped_files)} existing file(s).[/bold yellow]"
66
+ )
67
+ if not written_files and not skipped_files:
68
+ console.print("[bold]Nothing to do.[/bold]")
69
+
70
+ console.print("\n[dim]Run [bold]/issue-init <number>[/bold] in Cursor to start tracking a GitHub issue.[/dim]\n")
71
+
72
+
73
+ def _create_issueflow_dirs(project_root: Path, settings: Settings) -> None:
74
+ """Create the .issueflows/ directory tree."""
75
+ base = project_root / settings.issueflows_dir
76
+
77
+ for subdir_name in settings.issueflows_subdirs:
78
+ dir_path = base / subdir_name
79
+ if dir_path.exists():
80
+ console.print(f" [dim]exists[/dim] {settings.issueflows_dir}/{subdir_name}/")
81
+ else:
82
+ dir_path.mkdir(parents=True, exist_ok=True)
83
+ console.print(f" [green]mkdir[/green] {settings.issueflows_dir}/{subdir_name}/")
File without changes
File without changes
@@ -0,0 +1,36 @@
1
+ # Close out the current issue
2
+
3
+ Run this when implementation is done and you are ready to land the work.
4
+
5
+ ## Input
6
+
7
+ Optional: branch name, PR title, or anything special (e.g. draft PR, skip issue doc update, commit all changes).
8
+
9
+ ## Typical steps
10
+
11
+ 1. **Sanity check**
12
+ - Run tests and any checks you rely on (e.g. `uv run pytest`).
13
+ - Skim the diff so the commit matches what you intend to ship.
14
+
15
+ 2. **Issue tracking in the repo** (see project rules under `{{ issueflows_dir }}/{{ current_issues_folder }}`)
16
+ - Update the status file for this issue: clear checklist, remaining work, and use `- [x] Done` only when fully resolved.
17
+ - If the issue is fully resolved, move its markdown files from `{{ issueflows_dir }}/{{ current_issues_folder }}` to `{{ issueflows_dir }}/{{ solved_folder }}`. If partially resolved, move to `{{ issueflows_dir }}/{{ partly_solved_folder }}`.
18
+
19
+ 3. **Commit and fix merge conflicts**
20
+ - Unless told to commit all, stage the right files (avoid unrelated changes).
21
+ - Write a commit message that states what changed and why in normal sentences.
22
+ - Make sure you have pulled the last changes from the default branch (e.g. `main`) and check for and fix merge conflicts.
23
+
24
+ 4. **Push**
25
+ - Push your branch to `origin` (or the remote you use).
26
+
27
+ 5. **Pull request**
28
+ - Open a PR against the default branch (e.g. `main`).
29
+ - Describe the change, how to test it, and link the GitHub issue (e.g. `Closes #123` or `Refs #123` in the PR body).
30
+
31
+ 6. **After review**
32
+ - Address feedback, push updates, and merge when approved and CI is green.
33
+
34
+ ## Output
35
+
36
+ Summarize what was committed, pushed, and the PR URL (or next step if blocked).
@@ -0,0 +1,81 @@
1
+ # Create original issue file from GitHub issue
2
+
3
+ Create an `*_original.md` file in `{{ issueflows_dir }}/{{ current_issues_folder }}` from a GitHub issue.
4
+
5
+ ## Input
6
+ The user will provide one of:
7
+ - an issue number (e.g. `123`)
8
+ - or a full GitHub issue URL
9
+
10
+ Use the text provided after this slash command as the issue reference.
11
+
12
+ ## Steps
13
+
14
+ 0. Check that the required folders exist (`{{ issueflows_dir }}/{{ tools_folder }}`, `{{ issueflows_dir }}/{{ current_issues_folder }}`, `{{ issueflows_dir }}/{{ partly_solved_folder }}`, `{{ issueflows_dir }}/{{ solved_folder }}`). If not, create them after asking for permission.
15
+
16
+ 1. Resolve the issue reference from the user input.
17
+ - If input is a full URL, extract `owner`, `repo`, and `issue number`.
18
+ - If input is only an issue number:
19
+ - derive `owner/repo` from `git remote` (prefer `origin`)
20
+ - support both SSH and HTTPS remote URL formats
21
+ - if parsing fails, ask the user for either full issue URL or `owner/repo`
22
+
23
+ 2. Fetch issue data using GitHub CLI (explicit repo if needed):
24
+ - title
25
+ - body
26
+ - url
27
+ - number
28
+ - and confirm resolved `owner/repo`
29
+
30
+ 3. Archive existing issue files already in `{{ issueflows_dir }}/{{ current_issues_folder }}` (except the current issue number).
31
+ - Inspect issue groups by issue number (for example `issue121_*` belongs to issue 121).
32
+ - Consider all files for that issue in `{{ issueflows_dir }}/{{ current_issues_folder }}` (original + status/supplementary files) as one group to move together.
33
+ - Decide destination:
34
+ - move to `{{ issueflows_dir }}/{{ solved_folder }}` if the issue is done
35
+ - move to `{{ issueflows_dir }}/{{ partly_solved_folder }}` if the issue is not done
36
+ - Determine "done" status from an explicit checkbox marker in a status file:
37
+ - done only if a status markdown file for that issue contains `- [x] Done` (case-insensitive match for `done`)
38
+ - if the checkbox is missing, unchecked (`- [ ] Done`), unclear, or no status file exists, treat as not done
39
+ - Never move files for the issue currently being created.
40
+
41
+ 4. Create this file:
42
+ - `{{ issueflows_dir }}/{{ current_issues_folder }}/issue<number>_original.md`
43
+ 5. File content format:
44
+ ```markdown
45
+ # Issue #<number>: <title>
46
+
47
+ Source: <url>
48
+
49
+ ## Original issue text
50
+
51
+ <body exactly as in GitHub issue>
52
+ ```
53
+ 6. If `gh` is not authenticated or issue fetch fails:
54
+ - stop and report the exact error
55
+ - suggest `gh auth login`
56
+ 7. If the target file already exists:
57
+ - do not overwrite silently
58
+ - ask whether to overwrite or keep both
59
+
60
+ ## Output to user
61
+ Report:
62
+ - issue number fetched
63
+ - repository used (`owner/repo`)
64
+ - file path created
65
+ - archive moves performed (source -> destination, grouped by issue number)
66
+ - whether the operation succeeded
67
+
68
+ ## Constraints
69
+ - Preserve the issue body exactly as returned by GitHub.
70
+ - Use UTF-8 markdown.
71
+ - Allowed file modifications for this command:
72
+ - create/update the target `issue<number>_original.md`
73
+ - move pre-existing issue files from `{{ issueflows_dir }}/{{ current_issues_folder }}` to `{{ issueflows_dir }}/{{ partly_solved_folder }}` or `{{ issueflows_dir }}/{{ solved_folder }}` according to the archive rule above
74
+ - If `{{ issueflows_dir }}/{{ current_issues_folder }}` does not exist, report an error and stop.
75
+ - If archive destination directories do not exist, report an error and stop.
76
+ - Prefer deterministic behavior: always state which repo was resolved before writing the file.
77
+
78
+ ## Example invocations
79
+ - `/issue-init 123`
80
+ - `/issue-init https://github.com/owner/repo/issues/123`
81
+ - `/issue-init owner/repo/#123`
@@ -0,0 +1,16 @@
1
+ # Start working with current issue
2
+
3
+ The issue should already be explained in a markdown file in `{{ issueflows_dir }}/{{ current_issues_folder }}`.
4
+
5
+ ## Input
6
+ If additional input is added, use that for further detailed guidance
7
+
8
+ ## Steps
9
+
10
+ 0. If the issue markdown file is not present, or it is ambiguous which one to select, ask.
11
+
12
+ 1. Plan. If not in plan mode, stop and ask for a confirmation.
13
+
14
+ 2. Check that the plan is not too broad. If too broad, ask if it should be split into several parts.
15
+
16
+ 3. Implement the steps of the plan
@@ -0,0 +1,83 @@
1
+ # Cursor issue workflow (slash commands)
2
+
3
+ This repo uses three Cursor **slash commands** under `{{ cursor_dir }}/commands/` that line up with how we track GitHub issues in `{{ issueflows_dir }}/{{ current_issues_folder }}/`. Use them in order when you pick up work from GitHub and want the assistant to follow the same steps.
4
+
5
+ | Command | File | Role |
6
+ |--------|------|------|
7
+ | `/issue-init` | `issue-init.md` | Pull an issue from GitHub into the repo as a local markdown file and tidy older current issues. |
8
+ | `/issue-start` | `issue-start.md` | Plan and implement the work described in `{{ issueflows_dir }}/{{ current_issues_folder }}/`. |
9
+ | `/issue-close` | `issue-close.md` | Finish: tests, issue-folder housekeeping, commit, push, PR. |
10
+
11
+ ---
12
+
13
+ ## 1. `/issue-init` — capture the issue locally
14
+
15
+ **When:** You have a GitHub issue you want to work on (or archive older "current" issues before starting a new one).
16
+
17
+ **What you pass:** Either an issue number (e.g. `42`) or a full GitHub issue URL. The assistant resolves `owner/repo` from `git remote origin` when you only pass a number.
18
+
19
+ **What happens:**
20
+
21
+ - The assistant uses **GitHub CLI** (`gh`) to fetch title, body, URL, and number. You need `gh` authenticated (`gh auth login` if needed).
22
+ - It creates **`{{ issueflows_dir }}/{{ current_issues_folder }}/issue<number>_original.md`** with the title, source URL, and the **exact** issue body from GitHub.
23
+ - **Archive:** Other files already in `{{ issueflows_dir }}/{{ current_issues_folder }}/` (grouped by issue number, e.g. `issue121_*`) may be **moved** to `{{ issueflows_dir }}/{{ partly_solved_folder }}/` or `{{ issueflows_dir }}/{{ solved_folder }}/`, based on whether a status file for that issue contains a checked **Done** line (`- [x] Done`). The new issue's files are never moved as part of this step.
24
+ - If the target `issue<number>_original.md` already exists, the assistant should not overwrite it without asking.
25
+
26
+ **Result:** One canonical "original issue" file under `{{ issueflows_dir }}/{{ current_issues_folder }}/` plus optional archive moves. You can add or edit a separate `issue<number>_status.md` (or similar) by hand or with the assistant as work progresses.
27
+
28
+ ---
29
+
30
+ ## 2. `/issue-start` — plan and implement
31
+
32
+ **When:** The issue is represented in `{{ issueflows_dir }}/{{ current_issues_folder }}/` (at minimum the `*_original.md` file) and you are ready to code.
33
+
34
+ **What you pass:** Optional extra instructions (scope, constraints, design preferences).
35
+
36
+ **What the assistant does:**
37
+
38
+ 1. Confirms **which** issue file applies if several exist or things are ambiguous.
39
+ 2. **Plans** the work. If you are not in plan mode, it should stop and ask you to confirm before large changes.
40
+ 3. Checks the plan is **not too broad**; may suggest splitting into smaller chunks.
41
+ 4. **Implements** the plan (code, tests, and updates to issue status docs as appropriate for the task).
42
+
43
+ **Result:** Implementation aligned with the markdown in `{{ issueflows_dir }}/{{ current_issues_folder }}/` and project rules (tests with `uv run`, dependency management with `uv`, etc.).
44
+
45
+ ---
46
+
47
+ ## 3. `/issue-close` — land the work
48
+
49
+ **When:** Implementation is done and you want to ship (commit, push, PR).
50
+
51
+ **What you pass:** Optional notes (branch name, PR title, draft PR, or "skip issue doc update").
52
+
53
+ **Typical steps the assistant follows:**
54
+
55
+ 1. **Sanity check** — e.g. `uv run pytest`, review the diff.
56
+ 2. **Issue folders** — update status markdown; use `- [x] Done` only when fully resolved. Move completed issue files from `{{ issueflows_dir }}/{{ current_issues_folder }}/` to `{{ issueflows_dir }}/{{ solved_folder }}/`, or partly done work to `{{ issueflows_dir }}/{{ partly_solved_folder }}/` (see project rules).
57
+ 3. **Commit** — focused staging and a clear message.
58
+ 4. **Push** — to your usual remote (e.g. `origin`).
59
+ 5. **Pull request** — open against the default branch; link the GitHub issue (`Closes #n` / `Refs #n`).
60
+ 6. **After review** — address comments, merge when ready.
61
+
62
+ **Result:** Short summary of commit, push, and PR link (or what is blocked).
63
+
64
+ ---
65
+
66
+ ## End-to-end flow
67
+
68
+ ```text
69
+ GitHub issue
70
+ │ /issue-init
71
+
72
+ {{ issueflows_dir }}/{{ current_issues_folder }}/issueN_original.md (+ optional status files)
73
+ │ /issue-start
74
+
75
+ Code + tests (+ status updates during work)
76
+ │ /issue-close
77
+
78
+ Commit → push → PR → merge
79
+
80
+ └── issue docs in {{ issueflows_dir }}/{{ solved_folder }}/ or {{ issueflows_dir }}/{{ partly_solved_folder }}/ when appropriate
81
+ ```
82
+
83
+ The command definitions are the source of truth: `{{ cursor_dir }}/commands/issue-init.md`, `issue-start.md`, and `issue-close.md`. This document is a readable overview only.
@@ -0,0 +1,86 @@
1
+ ---
2
+ description: Issue-flow workflow rules for LLMs
3
+ globs:
4
+ alwaysApply: true
5
+ ---
6
+
7
+
8
+ # Issue-flow best practices
9
+
10
+
11
+ ## Running python
12
+
13
+ This is a python project. It uses a python environment (.venv) managed by uv.
14
+
15
+ ❌ BAD:
16
+ ```bash
17
+ python run_script.py
18
+ ```
19
+
20
+ ✅ GOOD:
21
+ ```bash
22
+ uv run run_script.py
23
+ ```
24
+
25
+ ### Package Management with `uv`
26
+
27
+ **✅ Use `uv` exclusively**
28
+
29
+ - All Python dependencies **must be installed, synchronized, and locked** using `uv`.
30
+ - Never use `pip`, `pip-tools`, or `poetry` directly for dependency management.
31
+
32
+ **🔁 Managing Dependencies**
33
+
34
+ ```bash
35
+ # Add or upgrade dependencies
36
+ uv add <package>
37
+
38
+ # Remove dependencies
39
+ uv remove <package>
40
+
41
+ # Reinstall all dependencies from lock file
42
+ uv sync
43
+ ```
44
+
45
+ **🔁 Scripts**
46
+
47
+ ```bash
48
+ # Run script with proper dependencies
49
+ uv run script.py
50
+ ```
51
+
52
+
53
+ ## Issue tracking structure
54
+
55
+ ```bash
56
+ {{ project_name }}/
57
+ {{ issueflows_dir }}/
58
+ {{ tools_folder }}/
59
+ {{ current_issues_folder }}/
60
+ issueXX_original.md
61
+ issueXX_status.md
62
+ {{ partly_solved_folder }}/
63
+ {{ solved_folder }}/
64
+ pyproject.toml
65
+ readme.md
66
+ ...
67
+ ```
68
+
69
+
70
+ ## Development information
71
+
72
+
73
+ ### Working on issues
74
+
75
+ After each iteration, update the documents in `{{ issueflows_dir }}/{{ current_issues_folder }}` (should contain one file labelled `_original` that consists of the original issue description, and supplementary status files describing what has been done, current status, and remaining work).
76
+ Use an explicit status checkbox in the status file:
77
+ - `- [x] Done` when fully resolved
78
+ - `- [ ] Done` when not fully resolved
79
+
80
+ ### When finishing an issue
81
+
82
+ If the issue is fully resolved (no additional subtasks present), move the original and status markdown files to `{{ issueflows_dir }}/{{ solved_folder }}`. Else, move them to `{{ issueflows_dir }}/{{ partly_solved_folder }}`.
83
+
84
+ ### Scripts that can help us when working on issues
85
+
86
+ If you want, you can put small scripts etc. that you have made and think could be useful in the future in our llm tools folder: `{{ issueflows_dir }}/{{ tools_folder }}`. Also, feel free to use the tools in our llm tools folder if you find someone that could be useful.
@@ -0,0 +1,79 @@
1
+ """Jinja2 template loading and rendering for issue-flow."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib import resources
6
+ from pathlib import Path
7
+
8
+ from jinja2 import Environment, BaseLoader, TemplateNotFound
9
+
10
+
11
+ # ---------------------------------------------------------------------------
12
+ # Custom loader that reads from the package's templates/ directory using
13
+ # importlib.resources so it works whether the package is installed as a
14
+ # directory, zip, or editable install.
15
+ # ---------------------------------------------------------------------------
16
+
17
+ _TEMPLATES_PACKAGE = "issue_flow.templates"
18
+
19
+
20
+ class _PackageLoader(BaseLoader):
21
+ """Load Jinja2 templates shipped inside the issue_flow.templates package."""
22
+
23
+ def get_source(
24
+ self, environment: Environment, template: str
25
+ ) -> tuple[str, str, callable]:
26
+ # template is e.g. "commands/issue-init.md.j2"
27
+ parts = template.replace("\\", "/").split("/")
28
+ package = _TEMPLATES_PACKAGE + "." + ".".join(parts[:-1]) if len(parts) > 1 else _TEMPLATES_PACKAGE
29
+ filename = parts[-1]
30
+
31
+ try:
32
+ ref = resources.files(package).joinpath(filename)
33
+ source = ref.read_text(encoding="utf-8")
34
+ except (ModuleNotFoundError, FileNotFoundError, TypeError) as exc:
35
+ raise TemplateNotFound(template) from exc
36
+
37
+ # The third element is a callable that returns True if the template
38
+ # is still up-to-date (always True for packaged templates).
39
+ return source, template, lambda: True
40
+
41
+
42
+ def get_environment() -> Environment:
43
+ """Return a configured Jinja2 environment that loads from the package."""
44
+ env = Environment(
45
+ loader=_PackageLoader(),
46
+ keep_trailing_newline=True,
47
+ trim_blocks=False,
48
+ lstrip_blocks=False,
49
+ )
50
+ return env
51
+
52
+
53
+ def render_template(template_name: str, context: dict[str, str]) -> str:
54
+ """Render a single template by name and return the result string."""
55
+ env = get_environment()
56
+ template = env.get_template(template_name)
57
+ return template.render(context)
58
+
59
+
60
+ # ---------------------------------------------------------------------------
61
+ # Mapping of template name -> output path (relative to the project root).
62
+ # The output path may itself contain Jinja-style placeholders, so we render
63
+ # the path first.
64
+ # ---------------------------------------------------------------------------
65
+
66
+ # Each entry: (template_file, output_path_template)
67
+ # The output_path_template uses simple str.format with the context dict.
68
+ TEMPLATE_MANIFEST: list[tuple[str, str]] = [
69
+ ("commands/issue-init.md.j2", "{cursor_dir}/commands/issue-init.md"),
70
+ ("commands/issue-start.md.j2", "{cursor_dir}/commands/issue-start.md"),
71
+ ("commands/issue-close.md.j2", "{cursor_dir}/commands/issue-close.md"),
72
+ ("rules/issueflow-rules.mdc.j2", "{cursor_dir}/rules/issueflow-rules.mdc"),
73
+ ("docs/cursor-issue-workflow.md.j2", "{docs_dir}/cursor-issue-workflow.md"),
74
+ ]
75
+
76
+
77
+ def resolve_output_path(path_template: str, context: dict[str, str]) -> Path:
78
+ """Resolve a path template like '{cursor_dir}/commands/foo.md' into a Path."""
79
+ return Path(path_template.format(**context))