project-kit-cli 0.3.0__tar.gz → 0.4.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.
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/PKG-INFO +19 -1
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/README.md +18 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/pyproject.toml +2 -2
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/pyproject.toml.orig +2 -2
- project_kit_cli-0.4.0/src/project_kit/__init__.py +14 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-start/SKILL.md +8 -3
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/cli.py +38 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/doctor.py +15 -0
- project_kit_cli-0.4.0/src/project_kit/entry.py +36 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/help.py +1 -0
- project_kit_cli-0.4.0/src/project_kit/statusline.py +347 -0
- project_kit_cli-0.3.0/src/project_kit/__init__.py +0 -8
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/LICENSE +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/__main__.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/agents.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/ai_attribution.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/approval.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/hooks/commit-msg +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/project/skills-catalog.yml +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/project/workflow.yml +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-amend/SKILL.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-decide/SKILL.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-discover/SKILL.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-foundations/SKILL.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-grill/SKILL.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-handoff/SKILL.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-plan/SKILL.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-provision/SKILL.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-skills/SKILL.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-workflow/SKILL.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/adr.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/backlog.example.json +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/brief.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/constitution-input.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/development-workflow.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/github/ISSUE_TEMPLATE/adr.yml +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/github/ISSUE_TEMPLATE/bug.yml +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/github/ISSUE_TEMPLATE/config.yml +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/github/ISSUE_TEMPLATE/epic.yml +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/github/ISSUE_TEMPLATE/feature.yml +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/github/pull_request_template.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/gitlab/issue_templates/adr.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/gitlab/issue_templates/bug.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/gitlab/issue_templates/epic.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/gitlab/issue_templates/feature.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/gitlab/merge_request_templates/Default.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/interview.md +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/backlog.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/config.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/context_files.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/fsutil.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/gh.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/glab.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/handoff.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/installer.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/labels.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/manifest.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/project_choice.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/providers/__init__.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/providers/github.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/providers/gitlab.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/provision.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/provision_gitlab.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/provision_issues.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/skills.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/state.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/templating.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/upgrade.py +0 -0
- {project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/workflow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: project-kit-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Project definition and dev-workflow kit that hands off to Spec Kit
|
|
5
5
|
Keywords: project-management,spec-kit,claude-code,workflow,github
|
|
6
6
|
Author: Miguel Tomás
|
|
@@ -138,6 +138,7 @@ It never rewrites issues that already exist, and re-running it is always safe.
|
|
|
138
138
|
| `pjkit backlog validate` | Validate `.project-kit/backlog.json` |
|
|
139
139
|
| `pjkit provision [--dry-run] [--project X]` | Create milestones, branches, the Project and issues on GitHub |
|
|
140
140
|
| `pjkit handoff [--install-speckit]` | Generate the Spec Kit constitution input |
|
|
141
|
+
| `pjkit statusline [--install]` | One line for Claude Code's status bar: model, context, usage limits and the project phase |
|
|
141
142
|
| `pjkit context [--dry-run]` | Write or refresh the project block in `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` |
|
|
142
143
|
| `pjkit skills list\|show\|mark` | Recommended third-party skills, their install command, and what the project decided |
|
|
143
144
|
|
|
@@ -155,6 +156,23 @@ writes the context file each one reads (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`).
|
|
|
155
156
|
between its markers is managed; the rest of the file is yours. Only Claude Code is started by
|
|
156
157
|
`init`; for the others, open the agent and ask it to run the `pk-start` skill.
|
|
157
158
|
|
|
159
|
+
## Status bar
|
|
160
|
+
|
|
161
|
+
Claude Code can show a line of your choosing at the bottom of the screen. `pjkit statusline`
|
|
162
|
+
fills it with the model, the context in use, the 5-hour and 7-day usage limits (Pro and Max
|
|
163
|
+
plans) and where the project is in the flow:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
[Opus · high] · ctx 23% · 5h 41% (↻ 17:00) · 7d 12% · ~$1.23 · fase: decide → /pk-decide
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`init` asks whether you want it, and `pjkit statusline --install` sets it up in
|
|
170
|
+
`.claude/settings.json` (`--remove` undoes it). It is never turned on without a yes. If you
|
|
171
|
+
already have a status bar, yours is kept and runs first. It is one line: when the terminal is
|
|
172
|
+
narrow, the cost goes first, then the 7-day limit, the effort and the context. The cost is an
|
|
173
|
+
estimate at list price. More in
|
|
174
|
+
[docs/statusline.md](https://github.com/miguelmatg/project-kit/blob/main/docs/statusline.md).
|
|
175
|
+
|
|
158
176
|
## GitLab
|
|
159
177
|
|
|
160
178
|
`pjkit init --provider gitlab` makes `provision` use `glab`: phases become milestones, kind,
|
|
@@ -118,6 +118,7 @@ It never rewrites issues that already exist, and re-running it is always safe.
|
|
|
118
118
|
| `pjkit backlog validate` | Validate `.project-kit/backlog.json` |
|
|
119
119
|
| `pjkit provision [--dry-run] [--project X]` | Create milestones, branches, the Project and issues on GitHub |
|
|
120
120
|
| `pjkit handoff [--install-speckit]` | Generate the Spec Kit constitution input |
|
|
121
|
+
| `pjkit statusline [--install]` | One line for Claude Code's status bar: model, context, usage limits and the project phase |
|
|
121
122
|
| `pjkit context [--dry-run]` | Write or refresh the project block in `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` |
|
|
122
123
|
| `pjkit skills list\|show\|mark` | Recommended third-party skills, their install command, and what the project decided |
|
|
123
124
|
|
|
@@ -135,6 +136,23 @@ writes the context file each one reads (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`).
|
|
|
135
136
|
between its markers is managed; the rest of the file is yours. Only Claude Code is started by
|
|
136
137
|
`init`; for the others, open the agent and ask it to run the `pk-start` skill.
|
|
137
138
|
|
|
139
|
+
## Status bar
|
|
140
|
+
|
|
141
|
+
Claude Code can show a line of your choosing at the bottom of the screen. `pjkit statusline`
|
|
142
|
+
fills it with the model, the context in use, the 5-hour and 7-day usage limits (Pro and Max
|
|
143
|
+
plans) and where the project is in the flow:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
[Opus · high] · ctx 23% · 5h 41% (↻ 17:00) · 7d 12% · ~$1.23 · fase: decide → /pk-decide
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`init` asks whether you want it, and `pjkit statusline --install` sets it up in
|
|
150
|
+
`.claude/settings.json` (`--remove` undoes it). It is never turned on without a yes. If you
|
|
151
|
+
already have a status bar, yours is kept and runs first. It is one line: when the terminal is
|
|
152
|
+
narrow, the cost goes first, then the 7-day limit, the effort and the context. The cost is an
|
|
153
|
+
estimate at list price. More in
|
|
154
|
+
[docs/statusline.md](https://github.com/miguelmatg/project-kit/blob/main/docs/statusline.md).
|
|
155
|
+
|
|
138
156
|
## GitLab
|
|
139
157
|
|
|
140
158
|
`pjkit init --provider gitlab` makes `provision` use `glab`: phases become milestones, kind,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "project-kit-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Project definition and dev-workflow kit that hands off to Spec Kit"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -31,7 +31,7 @@ Repository = "https://github.com/miguelmatg/project-kit"
|
|
|
31
31
|
Issues = "https://github.com/miguelmatg/project-kit/issues"
|
|
32
32
|
|
|
33
33
|
[project.scripts]
|
|
34
|
-
pjkit = "project_kit.
|
|
34
|
+
pjkit = "project_kit.entry:main"
|
|
35
35
|
|
|
36
36
|
[tool.uv.build-backend]
|
|
37
37
|
module-name = "project_kit"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "project-kit-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Project definition and dev-workflow kit that hands off to Spec Kit"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -25,7 +25,7 @@ Repository = "https://github.com/miguelmatg/project-kit"
|
|
|
25
25
|
Issues = "https://github.com/miguelmatg/project-kit/issues"
|
|
26
26
|
|
|
27
27
|
[project.scripts]
|
|
28
|
-
pjkit = "project_kit.
|
|
28
|
+
pjkit = "project_kit.entry:main"
|
|
29
29
|
|
|
30
30
|
[tool.uv.build-backend]
|
|
31
31
|
# The distribution is project-kit-cli (the name free on PyPI); the package keeps its name.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""project-kit: define, plan and set up a project before handing off to Spec Kit."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def __getattr__(name: str) -> str:
|
|
5
|
+
"""``__version__`` is looked up on first use: reading package metadata costs more than the
|
|
6
|
+
whole status line command, which runs at every update of the agent's status bar."""
|
|
7
|
+
if name == "__version__":
|
|
8
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
return version("project-kit-cli")
|
|
12
|
+
except PackageNotFoundError: # running from a source tree that was never installed
|
|
13
|
+
return "0.0.0"
|
|
14
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
{project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-start/SKILL.md
RENAMED
|
@@ -28,7 +28,12 @@ now. Nothing is created in GitHub or GitLab here.
|
|
|
28
28
|
`commit-msg` hook that strips the co-author trailer of any other assistant;
|
|
29
29
|
- ask before running `git config core.hooksPath .githooks`, which switches the hook on for
|
|
30
30
|
this clone. Each teammate runs that command once.
|
|
31
|
-
4. **
|
|
31
|
+
4. **Status bar (Claude Code only).** If `config.yml` lists `claude` and `pjkit doctor` shows
|
|
32
|
+
the status bar as not set up, ask with a selection list (**Yes, show it** / **No, thanks**)
|
|
33
|
+
whether to show the model, the usage limits and the project phase in the status bar. Only on
|
|
34
|
+
a yes run `pjkit statusline --install`; it keeps any status bar the user already has and
|
|
35
|
+
runs it first. Never set it up without that yes. `pjkit statusline --remove` undoes it.
|
|
36
|
+
5. **Ask how far to go now**, with a selection list (`AskUserQuestion`, see
|
|
32
37
|
`.project-kit/templates/interview.md`) and exactly these two options. Put B first with
|
|
33
38
|
`(Recommended)` unless the user said they are in a hurry:
|
|
34
39
|
|
|
@@ -36,8 +41,8 @@ now. Nothing is created in GitHub or GitLab here.
|
|
|
36
41
|
|--------|--------------|
|
|
37
42
|
| **B. Start defining the project** | Continue now with the discovery interview |
|
|
38
43
|
| **A. Only initialise** | Stop here. The user comes back later with `/pk-discover` |
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
6. **If A:** run `pjkit status`, show it, and explain that `/pk-discover` is next. Done.
|
|
45
|
+
7. **If B:** ask for a short description of the project in the user's own words, a few
|
|
41
46
|
sentences, a pasted note or "nothing yet, ask me". Then follow the `pk-discover` skill
|
|
42
47
|
(`.claude/skills/pk-discover/SKILL.md`) using that description as the starting point.
|
|
43
48
|
|
|
@@ -17,6 +17,7 @@ from project_kit import (
|
|
|
17
17
|
provision,
|
|
18
18
|
provision_gitlab,
|
|
19
19
|
skills,
|
|
20
|
+
statusline,
|
|
20
21
|
upgrade,
|
|
21
22
|
)
|
|
22
23
|
from project_kit import (
|
|
@@ -84,6 +85,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
84
85
|
"is attached)",
|
|
85
86
|
)
|
|
86
87
|
|
|
88
|
+
init.add_argument(
|
|
89
|
+
"--statusline",
|
|
90
|
+
action=argparse.BooleanOptionalAction,
|
|
91
|
+
help="show model, usage limits and the project phase in Claude Code's status bar "
|
|
92
|
+
"(asked when omitted and a terminal is attached; never turned on without a yes)",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
st = sub.add_parser(
|
|
96
|
+
"statusline",
|
|
97
|
+
help="one line for the agent's status bar (reads the agent's JSON on stdin)",
|
|
98
|
+
)
|
|
99
|
+
statusline.add_arguments(st)
|
|
100
|
+
|
|
87
101
|
up = sub.add_parser(
|
|
88
102
|
"upgrade",
|
|
89
103
|
help="bring the skills and templates installed by init up to this version",
|
|
@@ -251,8 +265,29 @@ def _help() -> int:
|
|
|
251
265
|
return 0
|
|
252
266
|
|
|
253
267
|
|
|
268
|
+
def _offer_statusline(root: Path, requested: bool | None, *, fresh: bool) -> None:
|
|
269
|
+
"""Set up Claude Code's status bar if the user says yes. Never without a yes.
|
|
270
|
+
|
|
271
|
+
``requested`` is the flag; without it the question is asked only in a new project with a
|
|
272
|
+
terminal, so running ``init`` again does not ask what was already answered.
|
|
273
|
+
"""
|
|
274
|
+
if "claude" not in load_config(root).agents or statusline.is_configured(root):
|
|
275
|
+
return
|
|
276
|
+
if requested is None:
|
|
277
|
+
if not (fresh and sys.stdin.isatty()):
|
|
278
|
+
return
|
|
279
|
+
before = statusline.existing_command(root)
|
|
280
|
+
keep = f" Your current bar ({before}) is kept and runs first." if before else ""
|
|
281
|
+
requested = _ask(
|
|
282
|
+
"Show the model, usage limits and the project phase in Claude Code's status bar?" + keep
|
|
283
|
+
)
|
|
284
|
+
if requested:
|
|
285
|
+
print(statusline.install(root).message)
|
|
286
|
+
|
|
287
|
+
|
|
254
288
|
def _init(args: argparse.Namespace) -> int:
|
|
255
289
|
root = Path.cwd()
|
|
290
|
+
fresh = not config_path(root).exists()
|
|
256
291
|
choice = args.ai_coauthor
|
|
257
292
|
if choice is None and sys.stdin.isatty() and not config_path(root).exists():
|
|
258
293
|
remove = _ask("Remove AI assistants (Claude, Copilot...) as co-authors of commits and PRs?")
|
|
@@ -283,6 +318,7 @@ def _init(args: argparse.Namespace) -> int:
|
|
|
283
318
|
print(f"pjkit init: {exc}", file=sys.stderr)
|
|
284
319
|
return 1
|
|
285
320
|
_print_init_report(report)
|
|
321
|
+
_offer_statusline(root, args.statusline, fresh=fresh)
|
|
286
322
|
if not _offer_agent(root, args.agent, load_config(root).agents):
|
|
287
323
|
print(NEXT_AFTER_INIT)
|
|
288
324
|
return 0
|
|
@@ -476,6 +512,8 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
476
512
|
return _provision(args)
|
|
477
513
|
if args.command == "handoff":
|
|
478
514
|
return _handoff(args)
|
|
515
|
+
if args.command == "statusline":
|
|
516
|
+
return statusline.run(args)
|
|
479
517
|
if args.command == "context":
|
|
480
518
|
return _context(dry_run=args.dry_run)
|
|
481
519
|
if args.command == "skills":
|
|
@@ -200,6 +200,20 @@ def check_agents(which: Which, cwd: Path) -> list[Check]:
|
|
|
200
200
|
return checks
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
def check_statusline(cwd: Path) -> list[Check]:
|
|
204
|
+
"""Whether Claude Code's status bar shows project-kit; optional, so never a warning."""
|
|
205
|
+
from project_kit import statusline
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
if "claude" not in load_config(cwd).agents:
|
|
209
|
+
return []
|
|
210
|
+
except ConfigError:
|
|
211
|
+
return []
|
|
212
|
+
if statusline.is_configured(cwd):
|
|
213
|
+
return [Check("status bar", Status.OK, "configured in .claude/settings.json")]
|
|
214
|
+
return [Check("status bar", Status.OK, "not set up (optional: pjkit statusline --install)")]
|
|
215
|
+
|
|
216
|
+
|
|
203
217
|
def run_checks(
|
|
204
218
|
*,
|
|
205
219
|
gh: Gh | None = None,
|
|
@@ -224,6 +238,7 @@ def run_checks(
|
|
|
224
238
|
check_uv(which, runner),
|
|
225
239
|
check_speckit(which, runner, here),
|
|
226
240
|
*check_agents(which, here),
|
|
241
|
+
*check_statusline(here),
|
|
227
242
|
]
|
|
228
243
|
|
|
229
244
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""The ``pjkit`` command's entry point.
|
|
2
|
+
|
|
3
|
+
``statusline`` runs at every update of an agent's status bar, so it must start in a fraction of
|
|
4
|
+
a second: it is dispatched here, before ``cli`` imports every command of the program. Every
|
|
5
|
+
other command goes through ``cli.main`` unchanged.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import sys
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _utf8_stdout() -> None:
|
|
14
|
+
"""Print UTF-8 whatever the console code page is (the line has arrows and accents)."""
|
|
15
|
+
try:
|
|
16
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
17
|
+
except (AttributeError, ValueError):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
22
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
23
|
+
if args and args[0] == "statusline" and "--help" not in args and "-h" not in args:
|
|
24
|
+
from project_kit import statusline
|
|
25
|
+
|
|
26
|
+
parser = argparse.ArgumentParser(prog="pjkit statusline")
|
|
27
|
+
statusline.add_arguments(parser)
|
|
28
|
+
_utf8_stdout()
|
|
29
|
+
return statusline.run(parser.parse_args(args[1:]))
|
|
30
|
+
from project_kit.cli import main as cli_main
|
|
31
|
+
|
|
32
|
+
return cli_main(args)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
sys.exit(main())
|
|
@@ -31,6 +31,7 @@ TERMINAL_COMMANDS = (
|
|
|
31
31
|
"pjkit skills list|show|mark|detect",
|
|
32
32
|
"recommended skills for this project and what was decided",
|
|
33
33
|
),
|
|
34
|
+
("pjkit statusline [--install]", "one-line status bar: model, limits and project phase"),
|
|
34
35
|
("pjkit help", "show this guide"),
|
|
35
36
|
)
|
|
36
37
|
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""``pjkit statusline``: one line for the agent's status bar - model, context, usage limits and
|
|
2
|
+
where the project is in the flow.
|
|
3
|
+
|
|
4
|
+
The agent runs the command at every update and hands it a JSON document on stdin. What is in
|
|
5
|
+
that JSON belongs to the agent, so reading it is isolated in an *adapter* per agent
|
|
6
|
+
(``ADAPTERS``): it turns the agent's document into ``Metrics`` and nothing else in this module
|
|
7
|
+
knows the shape. Only Claude Code has a documented status bar today, so it has the only
|
|
8
|
+
adapter; another agent needs a new adapter and an entry in ``ADAPTERS``.
|
|
9
|
+
|
|
10
|
+
The project's part (phase and next skill) comes from ``.project-kit/`` and does not depend on
|
|
11
|
+
the agent. Everything is optional: a missing field, a document that is not JSON, or a directory
|
|
12
|
+
that is not a project still prints something sensible, and nothing here touches the network.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import time
|
|
22
|
+
from collections.abc import Callable, Mapping
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
DEFAULT_WIDTH = 120
|
|
28
|
+
COMMAND = "pjkit statusline"
|
|
29
|
+
SETTINGS = Path(".claude") / "settings.json"
|
|
30
|
+
_SEP = " · "
|
|
31
|
+
_ANSI = re.compile("\x1b\\[[0-9;]*m")
|
|
32
|
+
_GREEN, _YELLOW, _RED, _DIM = "32", "33", "31", "2"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class Metrics:
|
|
37
|
+
"""What the status bar shows, from whichever agent supplied it. ``None``: not available."""
|
|
38
|
+
|
|
39
|
+
model: str | None = None
|
|
40
|
+
effort: str | None = None
|
|
41
|
+
context_pct: float | None = None
|
|
42
|
+
cost_usd: float | None = None
|
|
43
|
+
five_hour_pct: float | None = None
|
|
44
|
+
five_hour_resets: float | None = None # unix seconds
|
|
45
|
+
seven_day_pct: float | None = None
|
|
46
|
+
cwd: str | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _get(data: Any, *path: str) -> Any:
|
|
50
|
+
for key in path:
|
|
51
|
+
if not isinstance(data, Mapping):
|
|
52
|
+
return None
|
|
53
|
+
data = data.get(key)
|
|
54
|
+
return data
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _number(value: Any) -> float | None:
|
|
58
|
+
return float(value) if isinstance(value, int | float) and not isinstance(value, bool) else None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _text(value: Any) -> str | None:
|
|
62
|
+
return value if isinstance(value, str) and value.strip() else None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def claude_adapter(data: Mapping[str, Any]) -> Metrics:
|
|
66
|
+
"""Claude Code's status line document (https://code.claude.com/docs/en/statusline)."""
|
|
67
|
+
return Metrics(
|
|
68
|
+
model=_text(_get(data, "model", "display_name")),
|
|
69
|
+
effort=_text(_get(data, "effort", "level")),
|
|
70
|
+
context_pct=_number(_get(data, "context_window", "used_percentage")),
|
|
71
|
+
cost_usd=_number(_get(data, "cost", "total_cost_usd")),
|
|
72
|
+
five_hour_pct=_number(_get(data, "rate_limits", "five_hour", "used_percentage")),
|
|
73
|
+
five_hour_resets=_number(_get(data, "rate_limits", "five_hour", "resets_at")),
|
|
74
|
+
seven_day_pct=_number(_get(data, "rate_limits", "seven_day", "used_percentage")),
|
|
75
|
+
cwd=_text(_get(data, "workspace", "current_dir")) or _text(data.get("cwd")),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
ADAPTERS: dict[str, Callable[[Mapping[str, Any]], Metrics]] = {"claude": claude_adapter}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def parse_input(raw: str, agent: str = "claude") -> Metrics:
|
|
83
|
+
"""Metrics from the agent's document; empty ones if it is missing or not what we expect."""
|
|
84
|
+
try:
|
|
85
|
+
data = json.loads(raw) if raw.strip() else {}
|
|
86
|
+
except json.JSONDecodeError:
|
|
87
|
+
return Metrics()
|
|
88
|
+
return ADAPTERS[agent](data) if isinstance(data, Mapping) else Metrics()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# --- the project's part -------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def project_segment(root: Path) -> str:
|
|
95
|
+
"""``fase: decide -> /pk-decide`` from the project's state; empty outside a project."""
|
|
96
|
+
if not (root / ".project-kit" / "config.yml").is_file():
|
|
97
|
+
return "" # not a project: do not even load the YAML reader
|
|
98
|
+
from project_kit.config import ConfigError, load_config
|
|
99
|
+
from project_kit.state import StateError, load_state
|
|
100
|
+
from project_kit.workflow import WorkflowError, load_workflow
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
load_config(root)
|
|
104
|
+
workflow, state = load_workflow(root), load_state(root)
|
|
105
|
+
except (ConfigError, StateError, WorkflowError):
|
|
106
|
+
return ""
|
|
107
|
+
step = workflow.next_step(state)
|
|
108
|
+
if step is None:
|
|
109
|
+
return "fase: completa"
|
|
110
|
+
return f"fase: {step.id} → /{step.skill}"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# --- rendering ----------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _paint(text: str, code: str, color: bool) -> str:
|
|
117
|
+
return f"\033[{code}m{text}\033[0m" if color else text
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _level(pct: float) -> str:
|
|
121
|
+
return _GREEN if pct < 60 else _YELLOW if pct < 85 else _RED
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _clock(epoch: float, now: float) -> str:
|
|
125
|
+
local = time.localtime(epoch)
|
|
126
|
+
if epoch - now > 20 * 3600:
|
|
127
|
+
return time.strftime("%d/%m %H:%M", local)
|
|
128
|
+
return time.strftime("%H:%M", local)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _pieces(metrics: Metrics, segment: str, dropped: set[str], color: bool, now: float) -> str:
|
|
132
|
+
"""The line with the parts in ``dropped`` left out."""
|
|
133
|
+
out: list[str] = []
|
|
134
|
+
label = metrics.model
|
|
135
|
+
if label and metrics.effort and "effort" not in dropped:
|
|
136
|
+
label = f"{label} · {metrics.effort}"
|
|
137
|
+
if label and "model" not in dropped:
|
|
138
|
+
out.append(_paint(f"[{label}]", _DIM, color))
|
|
139
|
+
if metrics.context_pct is not None and "context" not in dropped:
|
|
140
|
+
pct = f"{metrics.context_pct:.0f}%"
|
|
141
|
+
out.append(f"ctx {_paint(pct, _level(metrics.context_pct), color)}")
|
|
142
|
+
if metrics.five_hour_pct is not None:
|
|
143
|
+
pct = f"{metrics.five_hour_pct:.0f}%"
|
|
144
|
+
text = f"5h {_paint(pct, _level(metrics.five_hour_pct), color)}"
|
|
145
|
+
if metrics.five_hour_resets:
|
|
146
|
+
text += f" (↻ {_clock(metrics.five_hour_resets, now)})"
|
|
147
|
+
out.append(text)
|
|
148
|
+
if metrics.seven_day_pct is not None and "seven" not in dropped:
|
|
149
|
+
pct = f"{metrics.seven_day_pct:.0f}%"
|
|
150
|
+
out.append(f"7d {_paint(pct, _level(metrics.seven_day_pct), color)}")
|
|
151
|
+
if metrics.cost_usd is not None and "cost" not in dropped:
|
|
152
|
+
out.append(f"~${metrics.cost_usd:.2f}")
|
|
153
|
+
if segment:
|
|
154
|
+
out.append(segment)
|
|
155
|
+
return _SEP.join(out)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# What goes first when the line is too long: the least important to the most.
|
|
159
|
+
_DROP_ORDER = ("cost", "seven", "effort", "context", "model")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def render(
|
|
163
|
+
metrics: Metrics,
|
|
164
|
+
segment: str = "",
|
|
165
|
+
*,
|
|
166
|
+
width: int = DEFAULT_WIDTH,
|
|
167
|
+
color: bool = True,
|
|
168
|
+
now: float | None = None,
|
|
169
|
+
) -> str:
|
|
170
|
+
"""One line. The 5-hour limit and the project's phase are the last to go."""
|
|
171
|
+
now = time.time() if now is None else now
|
|
172
|
+
dropped: set[str] = set()
|
|
173
|
+
for step in (None, *_DROP_ORDER):
|
|
174
|
+
if step:
|
|
175
|
+
dropped.add(step)
|
|
176
|
+
line = _pieces(metrics, segment, dropped, color, now)
|
|
177
|
+
if len(_ANSI.sub("", line)) <= width:
|
|
178
|
+
return line
|
|
179
|
+
return line
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# --- wrapping the bar the user already has -------------------------------------------------
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def run_wrapped(command: str, raw: str, timeout: float = 2.0) -> str:
|
|
186
|
+
"""First line the user's own status command prints for this document; empty if it fails."""
|
|
187
|
+
try:
|
|
188
|
+
proc = subprocess.run(
|
|
189
|
+
command, shell=True, input=raw, capture_output=True, text=True,
|
|
190
|
+
encoding="utf-8", timeout=timeout,
|
|
191
|
+
) # fmt: skip
|
|
192
|
+
except (OSError, subprocess.SubprocessError):
|
|
193
|
+
return ""
|
|
194
|
+
lines = (proc.stdout or "").strip().splitlines() if proc.returncode == 0 else []
|
|
195
|
+
return lines[0].strip() if lines else ""
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def build_line(
|
|
199
|
+
raw: str,
|
|
200
|
+
root: Path,
|
|
201
|
+
*,
|
|
202
|
+
segment_only: bool = False,
|
|
203
|
+
wrap: str = "",
|
|
204
|
+
width: int = DEFAULT_WIDTH,
|
|
205
|
+
color: bool = True,
|
|
206
|
+
) -> str:
|
|
207
|
+
"""What the command prints for one update."""
|
|
208
|
+
segment = project_segment(root)
|
|
209
|
+
if segment_only:
|
|
210
|
+
return segment
|
|
211
|
+
before = run_wrapped(wrap, raw) if wrap else ""
|
|
212
|
+
metrics = parse_input(raw)
|
|
213
|
+
own = render(metrics, segment, width=max(width - len(before) - 3, 20), color=color)
|
|
214
|
+
return _SEP.join(p for p in (before, own) if p)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# --- configuring the agent's settings -----------------------------------------------------
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@dataclass(frozen=True)
|
|
221
|
+
class Outcome:
|
|
222
|
+
changed: bool
|
|
223
|
+
message: str
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _read_settings(path: Path) -> dict[str, Any] | None:
|
|
227
|
+
if not path.exists():
|
|
228
|
+
return {}
|
|
229
|
+
try:
|
|
230
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
231
|
+
except (OSError, json.JSONDecodeError):
|
|
232
|
+
return None
|
|
233
|
+
return data if isinstance(data, dict) else None
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _command_of(settings: Mapping[str, Any]) -> str:
|
|
237
|
+
bar = settings.get("statusLine")
|
|
238
|
+
if isinstance(bar, Mapping) and bar.get("type", "command") == "command":
|
|
239
|
+
command = bar.get("command")
|
|
240
|
+
return command if isinstance(command, str) else ""
|
|
241
|
+
return ""
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def existing_command(root: Path, home: Path | None = None) -> str:
|
|
245
|
+
"""The status command the user has: the project's, else the one in their own settings."""
|
|
246
|
+
home = home if home is not None else Path.home()
|
|
247
|
+
for path in (root / SETTINGS, home / SETTINGS):
|
|
248
|
+
data = _read_settings(path)
|
|
249
|
+
if data and (command := _command_of(data)):
|
|
250
|
+
return command
|
|
251
|
+
return ""
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def is_configured(root: Path) -> bool:
|
|
255
|
+
data = _read_settings(root / SETTINGS)
|
|
256
|
+
return bool(data) and COMMAND in _command_of(data)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def install(root: Path, home: Path | None = None, *, dry_run: bool = False) -> Outcome:
|
|
260
|
+
"""Point the project's Claude Code status line at ``pjkit statusline``.
|
|
261
|
+
|
|
262
|
+
An existing command (the project's or the user's own) is kept: it runs first and ours is
|
|
263
|
+
added after it. Settings that cannot be read are never overwritten.
|
|
264
|
+
"""
|
|
265
|
+
from project_kit.fsutil import atomic_write_text
|
|
266
|
+
|
|
267
|
+
path = root / SETTINGS
|
|
268
|
+
data = _read_settings(path)
|
|
269
|
+
if data is None:
|
|
270
|
+
return Outcome(False, f"{SETTINGS.as_posix()} is not a valid JSON object; left alone.")
|
|
271
|
+
if COMMAND in _command_of(data):
|
|
272
|
+
return Outcome(False, "The status bar is already configured.")
|
|
273
|
+
before = existing_command(root, home)
|
|
274
|
+
command = f"{COMMAND} --wrap {json.dumps(before)}" if before else COMMAND
|
|
275
|
+
data["statusLine"] = {"type": "command", "command": command}
|
|
276
|
+
if not dry_run:
|
|
277
|
+
atomic_write_text(path, json.dumps(data, indent=2, ensure_ascii=False) + "\n")
|
|
278
|
+
kept = f" Your existing bar ({before}) is kept and runs first." if before else ""
|
|
279
|
+
return Outcome(True, f"Status bar set in {SETTINGS.as_posix()}.{kept}")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def remove(root: Path, *, dry_run: bool = False) -> Outcome:
|
|
283
|
+
"""Undo ``install``: restore the wrapped command, or drop ``statusLine`` if there was none."""
|
|
284
|
+
from project_kit.fsutil import atomic_write_text
|
|
285
|
+
|
|
286
|
+
path = root / SETTINGS
|
|
287
|
+
data = _read_settings(path)
|
|
288
|
+
if data is None:
|
|
289
|
+
return Outcome(False, f"{SETTINGS.as_posix()} is not a valid JSON object; left alone.")
|
|
290
|
+
command = _command_of(data)
|
|
291
|
+
if COMMAND not in command:
|
|
292
|
+
return Outcome(False, "The status bar was not set by project-kit; nothing to remove.")
|
|
293
|
+
match = re.search(r"--wrap (\".*\")\s*$", command)
|
|
294
|
+
if match:
|
|
295
|
+
data["statusLine"] = {"type": "command", "command": json.loads(match.group(1))}
|
|
296
|
+
else:
|
|
297
|
+
del data["statusLine"]
|
|
298
|
+
if not dry_run:
|
|
299
|
+
atomic_write_text(path, json.dumps(data, indent=2, ensure_ascii=False) + "\n")
|
|
300
|
+
return Outcome(True, "Status bar removed.")
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# --- the command line -----------------------------------------------------------------------
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def add_arguments(parser: argparse.ArgumentParser) -> None:
|
|
307
|
+
parser.add_argument("--install", action="store_true", help="set it up in .claude/settings.json")
|
|
308
|
+
parser.add_argument("--remove", action="store_true", help="undo --install")
|
|
309
|
+
parser.add_argument(
|
|
310
|
+
"--dry-run", action="store_true", help="with --install/--remove: change nothing"
|
|
311
|
+
)
|
|
312
|
+
parser.add_argument("--segment", action="store_true", help="only the project's part, to embed")
|
|
313
|
+
parser.add_argument(
|
|
314
|
+
"--wrap", default="", help="a status command to run first (set by --install)"
|
|
315
|
+
)
|
|
316
|
+
parser.add_argument("--width", type=int, help="columns available (default: $COLUMNS or 120)")
|
|
317
|
+
parser.add_argument("--no-color", action="store_true", help="plain text (NO_COLOR also works)")
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def run(args: argparse.Namespace) -> int:
|
|
321
|
+
root = Path.cwd()
|
|
322
|
+
if args.install or args.remove:
|
|
323
|
+
from project_kit.config import ConfigError, load_config
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
load_config(root)
|
|
327
|
+
except ConfigError as exc:
|
|
328
|
+
print(f"pjkit statusline: {exc}", file=sys.stderr)
|
|
329
|
+
return 1
|
|
330
|
+
outcome = (install if args.install else remove)(root, dry_run=args.dry_run)
|
|
331
|
+
print(("Dry run: " if args.dry_run and outcome.changed else "") + outcome.message)
|
|
332
|
+
return 0 if outcome.changed or "already" in outcome.message else 1
|
|
333
|
+
raw = "" if sys.stdin.isatty() else sys.stdin.read()
|
|
334
|
+
cwd = parse_input(raw).cwd
|
|
335
|
+
project = Path(cwd) if cwd and Path(cwd).is_dir() else root
|
|
336
|
+
width = args.width or int(os.environ.get("COLUMNS", "") or DEFAULT_WIDTH)
|
|
337
|
+
print(
|
|
338
|
+
build_line(
|
|
339
|
+
raw,
|
|
340
|
+
project,
|
|
341
|
+
segment_only=args.segment,
|
|
342
|
+
wrap=args.wrap,
|
|
343
|
+
width=width,
|
|
344
|
+
color=not (args.no_color or os.environ.get("NO_COLOR")),
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
return 0
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"""project-kit: define, plan and set up a project before handing off to Spec Kit."""
|
|
2
|
-
|
|
3
|
-
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
-
|
|
5
|
-
try:
|
|
6
|
-
__version__ = version("project-kit-cli")
|
|
7
|
-
except PackageNotFoundError: # running from a source tree that was never installed
|
|
8
|
-
__version__ = "0.0.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/project/skills-catalog.yml
RENAMED
|
File without changes
|
|
File without changes
|
{project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-amend/SKILL.md
RENAMED
|
File without changes
|
{project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-decide/SKILL.md
RENAMED
|
File without changes
|
{project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-discover/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-grill/SKILL.md
RENAMED
|
File without changes
|
{project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-handoff/SKILL.md
RENAMED
|
File without changes
|
{project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-plan/SKILL.md
RENAMED
|
File without changes
|
{project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-provision/SKILL.md
RENAMED
|
File without changes
|
{project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-skills/SKILL.md
RENAMED
|
File without changes
|
{project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/skills/pk-workflow/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{project_kit_cli-0.3.0 → project_kit_cli-0.4.0}/src/project_kit/assets/templates/interview.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|