project-guide 2.6.0__tar.gz → 2.7.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_guide-2.6.0 → project_guide-2.7.0}/CHANGELOG.md +29 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/PKG-INFO +41 -2
- {project_guide-2.6.0 → project_guide-2.7.0}/README.md +40 -1
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/features.md +30 -3
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/project-essentials.md +20 -3
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/stories.md +71 -1
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/tech-spec.md +20 -4
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/cli.py +173 -8
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/stories.py +51 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/version.py +1 -1
- {project_guide-2.6.0 → project_guide-2.7.0}/pyproject.toml +1 -1
- {project_guide-2.6.0 → project_guide-2.7.0}/tests/test_cli.py +291 -8
- {project_guide-2.6.0 → project_guide-2.7.0}/.github/FUNDING.yml +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/.github/dependabot.yml +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/.github/workflows/ci.yml +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/.github/workflows/deploy-docs.yml +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/.github/workflows/publish.yml +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/.github/workflows/test.yml +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/.gitignore +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/.project-guide.yml +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/.pyve/config +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/.tool-versions +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/CONTRIBUTING.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/LICENSE +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/SECURITY.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/.gitignore +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/about/changelog.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/about/license.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/developer-guide/contributing.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/developer-guide/development.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/developer-guide/testing.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/getting-started.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/images/project-guide-banner-landing.png +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/images/project-guide-header-readme.png +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/index.html +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/user-guide/commands.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/user-guide/configuration.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/user-guide/install-options.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/user-guide/modes.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/user-guide/overrides.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/site/user-guide/workflow.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/phase-j-modes-plan.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/phase-k-release-lifecycle-plan.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/phase-l-no-input-init-plan.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/phase-m-project-essentials-plan.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/phase-n-mode-naming-cli-memory-plan.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/phase-o-pyve-quiet-embedding-subplan.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/phase-o-quiet-non-interactive-embedding-feature.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/project-guide-no-input-spec.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/stories-v1.3.1.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/stories-v2.0.20.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/stories-v2.3.9.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/stories-v2.4.19.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/stories-v2.5.15.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/.archive/ux-problems-v2.0.10.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/brand-descriptions.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/concept.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/docs/specs/phase-p-auto-heal-plan.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/mkdocs.yml +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project-guide-old-template.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/__init__.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/__main__.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/actions.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/config.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/exceptions.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/metadata.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/render.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/runtime.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/sync.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/.project-guide.yml.template +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/.metadata.yml +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/README.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/developer/best-practices-guide.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/developer/brand-descriptions-guide.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/developer/codecov-setup-guide.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/developer/debug-guide.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/developer/landing-page-guide.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/developer/production-github-guide.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/developer/project-guide.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/developer/python-editable-install.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/artifacts/brand-descriptions.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/artifacts/concept.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/artifacts/features.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/artifacts/project-essentials.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/artifacts/pyve-essentials.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/artifacts/stories.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/artifacts/tech-spec.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/llm_entry_point.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/_header-common.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/_header-cycle.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/_header-sequence.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/_phase-letters.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/archive-stories-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/code-direct-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/code-test-first-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/debug-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/default-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/document-brand-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/document-landing-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/plan-concept-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/plan-features-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/plan-phase-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/plan-production-phase-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/plan-stories-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/plan-tech-spec-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/refactor-document-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/refactor-plan-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/project_guide/templates/project-guide/templates/modes/scaffold-project-mode.md +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/requirements-dev.txt +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/tests/__init__.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/tests/conftest.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/tests/test_actions.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/tests/test_archive_stories_mode.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/tests/test_config.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/tests/test_integration.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/tests/test_metadata.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/tests/test_purge.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/tests/test_render.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/tests/test_runtime.py +0 -0
- {project_guide-2.6.0 → project_guide-2.7.0}/tests/test_sync.py +0 -0
|
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.7.0] - 2026-05-11
|
|
11
|
+
|
|
12
|
+
New top-level `project-guide git-push` command — a thin wrapper over [gitbetter](https://github.com/pointmatic/gitbetter)'s `git-push` that auto-derives the commit subject from the most-recently-completed-and-not-yet-committed story in `docs/specs/stories.md`. Developer-lane convenience only; the LLM still does not initiate commits.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **`project-guide git-push [BRANCH_NAME]` command** (`project_guide/cli.py`) — parses `docs/specs/stories.md` for the last `[Done]` story heading, derives a normalized commit subject (strip `### Story ` prefix and ` [Done]` suffix, convert backticks and double quotes to single quotes, preserve the colon after the story ID), and shells out to gitbetter's `git-push` via `shutil.which` discovery + `subprocess.run(check=False)` with no captured output (gitbetter inherits stdin/stdout/stderr and stays fully interactive). Child exit code propagates unchanged.
|
|
16
|
+
- **Hard-error semantics** for ambiguous states: no `[Done]` story → exit 1; last `[Done]` already committed (per `git log --pretty=%s` subject-prefix match) → exit 1 with `Use 'git-push' directly for any follow-up commit.`; multiple uncommitted `[Done]` stories → exit 1 listing the IDs; `git-push` not on PATH → exit 1 with `brew install pointmatic/tap/gitbetter` hint. The wrapper deliberately refuses to second-guess the developer in ambiguous cases — raw `git-push` remains the escape hatch.
|
|
17
|
+
- **`project_guide/stories.py`** — `StoryHeading` dataclass, `_read_done_stories()`, and `derive_commit_message()` as reusable helpers (the message transformer is exposed as a public name because the heading rules are part of the wrapper's documented contract).
|
|
18
|
+
- **External CLI dependency pattern** documented in `docs/specs/tech-spec.md` under Cross-Cutting Concerns — `git-push` is the first project-guide subcommand depending on an external binary; future workflow-integration commands should follow the same shape (`shutil.which` discover, `subprocess.run(check=False)` invoke, propagate exit code).
|
|
19
|
+
|
|
20
|
+
### Docs
|
|
21
|
+
- `docs/specs/features.md` — new FR-15 (Story-Aware `git-push` Wrapper), added Inputs / Command Line entry, added Acceptance Criteria item 17.
|
|
22
|
+
- `docs/specs/tech-spec.md` — added `git-push` to the Commands table, Key Functions documents the new helpers, new Cross-Cutting Concerns section "External CLI Dependencies (Story P.k pattern)".
|
|
23
|
+
- `docs/specs/project-essentials.md` — new sub-section reinforcing the LLM-vs-developer-lane rule for this command and documenting the heading-derivation rules.
|
|
24
|
+
- `README.md` — new `### git-push` section in Command Reference between `heal` and `override`, with the optional-dependency callout for gitbetter.
|
|
25
|
+
|
|
26
|
+
## [2.6.1] - 2026-05-11
|
|
27
|
+
|
|
28
|
+
Post-v2.6.0 follow-up: tighten the canonical `# project-guide` gitignore block from four lines to three. Behavior unchanged; the dropped line was functionally redundant.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
- **`project_guide/cli.py:_build_project_guide_block()`** — dropped the trailing `<target>/**/*.bak.*` line. The broader `<target>/**` rule introduced in v2.6.0 already ignores backups under that subtree, so the explicit line was a no-op carried over from the pre-P.d block during the policy inversion. New canonical form is three lines: header, `<target>/**`, `!<target>/go.md`.
|
|
32
|
+
- **`project_guide/cli.py:_recognized_block_lines()`** — kept the `<target>/**/*.bak.*` entry in the recognized-lines set so v2.6.0-shipped installs heal cleanly to the v2.6.1 3-line form on the next `init --force`. Foreign hand-customized blocks remain warned-about (no behavior change there).
|
|
33
|
+
- **Tests** — `_EXPECTED_GITIGNORE_BLOCK` updated to the 3-line form; added a test for the v2.6.0-→-v2.6.1 cleanup path on `init --force`.
|
|
34
|
+
- **Docs** — updated the gitignore prose in `docs/specs/features.md`, `docs/specs/tech-spec.md`, `docs/specs/project-essentials.md`, and `README.md` to reflect the 3-line canonical form. Historical references to the 4-line v2.6.0 form retained where they explain the migration path.
|
|
35
|
+
|
|
36
|
+
### Migration
|
|
37
|
+
None required. v2.6.0 installs see no behavioral change — the dropped line was a no-op under the `<target>/**` rule. Running `init --force` on a v2.6.0 install simply produces a tidier 3-line block. Consumers who never run `init --force` keep the 4-line block forever without issue.
|
|
38
|
+
|
|
10
39
|
## [2.6.0] - 2026-05-09
|
|
11
40
|
|
|
12
41
|
Phase P: Auto-heal and production hardening. Collapses the consumer-repo install footprint to a single tracked file (`go.md`) plus the tracked config; everything else is gitignored bundled data, repopulated on demand by a new self-contained `heal` command that runs invisibly before every other invocation. Bundled with a subset of post-1.0 production-readiness items: `SECURITY.md`, `CONTRIBUTING.md`, tightened `dependabot.yml`, and a CI-workflow PR-readiness audit.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: project-guide
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.0
|
|
4
4
|
Summary: Stay organized and in control with adaptive LLM workflow prompts.
|
|
5
5
|
Project-URL: Homepage, https://github.com/pointmatic/project-guide
|
|
6
6
|
Project-URL: Documentation, https://pointmatic.github.io/project-guide/
|
|
@@ -125,7 +125,7 @@ This creates:
|
|
|
125
125
|
- `docs/project-guide/go.md` - Rendered LLM instructions (tracked — must be visible to IDE-integrated LLMs)
|
|
126
126
|
- `docs/project-guide/` - Mode templates, artifact templates, and metadata (gitignored bundled data)
|
|
127
127
|
|
|
128
|
-
Everything under `docs/project-guide/` is gitignored **except** `go.md` (which the LLM reads)
|
|
128
|
+
Everything under `docs/project-guide/` is gitignored **except** `go.md` (which the LLM reads). The gitignored template tree is bundled static data — `project-guide heal` repopulates it on first invocation in a fresh clone, and the auto-hook makes that healing run silently before any other command.
|
|
129
129
|
|
|
130
130
|
### 2. Tell your LLM to read the guide
|
|
131
131
|
|
|
@@ -374,6 +374,45 @@ project-guide heal
|
|
|
374
374
|
project-guide heal --no-input
|
|
375
375
|
```
|
|
376
376
|
|
|
377
|
+
### `git-push`
|
|
378
|
+
|
|
379
|
+
Wrap [gitbetter](https://github.com/pointmatic/gitbetter)'s `git-push` with a commit message auto-derived from the most-recently-completed-and-not-yet-committed story in `docs/specs/stories.md`. Optional: requires gitbetter on PATH.
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
project-guide git-push [BRANCH_NAME]
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Arguments:**
|
|
386
|
+
- `BRANCH_NAME` (optional) - Passed through to gitbetter for branch-aware push flows (e.g. switching to a feature branch and offering cleanup after merge)
|
|
387
|
+
|
|
388
|
+
**Heading-to-message transformation:**
|
|
389
|
+
```
|
|
390
|
+
### Story G.a: v1.2.3 New command `foo` with "Hello" [Done]
|
|
391
|
+
↓
|
|
392
|
+
G.a: v1.2.3 New command 'foo' with 'Hello'
|
|
393
|
+
```
|
|
394
|
+
Backticks and double quotes become single quotes; single quotes pass through; the colon after the story ID is preserved (it's the anchor for the already-committed check).
|
|
395
|
+
|
|
396
|
+
**Hard errors (exit 1):**
|
|
397
|
+
- No `[Done]` story in `stories.md`
|
|
398
|
+
- The last `[Done]` story is already committed — the wrapper does not second-guess; resolve manually with raw `git-push`
|
|
399
|
+
- Multiple `[Done]` stories are uncommitted — commit them one at a time with explicit messages via raw `git-push`
|
|
400
|
+
- `git-push` not on PATH — install gitbetter: `brew install pointmatic/tap/gitbetter`
|
|
401
|
+
|
|
402
|
+
**Examples:**
|
|
403
|
+
```bash
|
|
404
|
+
# Most common: ready to commit the just-completed story to the current branch
|
|
405
|
+
project-guide git-push
|
|
406
|
+
|
|
407
|
+
# Feature-branch push (gitbetter switches to the branch first, offers cleanup after merge)
|
|
408
|
+
project-guide git-push feature/heal-command
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**Optional dependency.** gitbetter is not required for any other `project-guide` command. Install it only if you want this wrapper:
|
|
412
|
+
```bash
|
|
413
|
+
brew install pointmatic/tap/gitbetter
|
|
414
|
+
```
|
|
415
|
+
|
|
377
416
|
### `override`
|
|
378
417
|
|
|
379
418
|
Mark a file as customized to prevent automatic updates.
|
|
@@ -89,7 +89,7 @@ This creates:
|
|
|
89
89
|
- `docs/project-guide/go.md` - Rendered LLM instructions (tracked — must be visible to IDE-integrated LLMs)
|
|
90
90
|
- `docs/project-guide/` - Mode templates, artifact templates, and metadata (gitignored bundled data)
|
|
91
91
|
|
|
92
|
-
Everything under `docs/project-guide/` is gitignored **except** `go.md` (which the LLM reads)
|
|
92
|
+
Everything under `docs/project-guide/` is gitignored **except** `go.md` (which the LLM reads). The gitignored template tree is bundled static data — `project-guide heal` repopulates it on first invocation in a fresh clone, and the auto-hook makes that healing run silently before any other command.
|
|
93
93
|
|
|
94
94
|
### 2. Tell your LLM to read the guide
|
|
95
95
|
|
|
@@ -338,6 +338,45 @@ project-guide heal
|
|
|
338
338
|
project-guide heal --no-input
|
|
339
339
|
```
|
|
340
340
|
|
|
341
|
+
### `git-push`
|
|
342
|
+
|
|
343
|
+
Wrap [gitbetter](https://github.com/pointmatic/gitbetter)'s `git-push` with a commit message auto-derived from the most-recently-completed-and-not-yet-committed story in `docs/specs/stories.md`. Optional: requires gitbetter on PATH.
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
project-guide git-push [BRANCH_NAME]
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Arguments:**
|
|
350
|
+
- `BRANCH_NAME` (optional) - Passed through to gitbetter for branch-aware push flows (e.g. switching to a feature branch and offering cleanup after merge)
|
|
351
|
+
|
|
352
|
+
**Heading-to-message transformation:**
|
|
353
|
+
```
|
|
354
|
+
### Story G.a: v1.2.3 New command `foo` with "Hello" [Done]
|
|
355
|
+
↓
|
|
356
|
+
G.a: v1.2.3 New command 'foo' with 'Hello'
|
|
357
|
+
```
|
|
358
|
+
Backticks and double quotes become single quotes; single quotes pass through; the colon after the story ID is preserved (it's the anchor for the already-committed check).
|
|
359
|
+
|
|
360
|
+
**Hard errors (exit 1):**
|
|
361
|
+
- No `[Done]` story in `stories.md`
|
|
362
|
+
- The last `[Done]` story is already committed — the wrapper does not second-guess; resolve manually with raw `git-push`
|
|
363
|
+
- Multiple `[Done]` stories are uncommitted — commit them one at a time with explicit messages via raw `git-push`
|
|
364
|
+
- `git-push` not on PATH — install gitbetter: `brew install pointmatic/tap/gitbetter`
|
|
365
|
+
|
|
366
|
+
**Examples:**
|
|
367
|
+
```bash
|
|
368
|
+
# Most common: ready to commit the just-completed story to the current branch
|
|
369
|
+
project-guide git-push
|
|
370
|
+
|
|
371
|
+
# Feature-branch push (gitbetter switches to the branch first, offers cleanup after merge)
|
|
372
|
+
project-guide git-push feature/heal-command
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Optional dependency.** gitbetter is not required for any other `project-guide` command. Install it only if you want this wrapper:
|
|
376
|
+
```bash
|
|
377
|
+
brew install pointmatic/tap/gitbetter
|
|
378
|
+
```
|
|
379
|
+
|
|
341
380
|
### `override`
|
|
342
381
|
|
|
343
382
|
Mark a file as customized to prevent automatic updates.
|
|
@@ -81,6 +81,10 @@ For a high-level concept (why), see [`concept.md`](concept.md). For implementati
|
|
|
81
81
|
**`project-guide heal`**
|
|
82
82
|
- Optional: `--no-input` (auto-yes the `[Y/n]` prompt; emit a one-line stderr notice when writes occur — auto-enabled by `CI=1`, `PROJECT_GUIDE_NO_INPUT=1`, or non-TTY stdin)
|
|
83
83
|
|
|
84
|
+
**`project-guide git-push [BRANCH_NAME]`**
|
|
85
|
+
- Optional positional `BRANCH_NAME` — passed through to gitbetter's `git-push` for branch-aware push flows
|
|
86
|
+
- No other flags — the wrapped command is fully interactive (preview, confirm, branch cleanup, reject/recovery menu), so `--no-input` / `--quiet` would be no-ops; for those, route through raw `git-push` instead
|
|
87
|
+
|
|
84
88
|
**`project-guide override FILE_NAME REASON`**
|
|
85
89
|
- Required: file name (template-relative path)
|
|
86
90
|
- Required: reason for override
|
|
@@ -127,7 +131,7 @@ Only `.project-guide.yml` (config) and `docs/project-guide/go.md` (rendered LLM
|
|
|
127
131
|
```
|
|
128
132
|
project-root/
|
|
129
133
|
├── .project-guide.yml # Configuration (tracked)
|
|
130
|
-
├── .gitignore # `# project-guide` block: ignore everything under target_dir except go.md
|
|
134
|
+
├── .gitignore # `# project-guide` block: ignore everything under target_dir except go.md
|
|
131
135
|
└── docs/
|
|
132
136
|
└── project-guide/
|
|
133
137
|
├── go.md # Rendered entry point (tracked in git — required for IDE LLM visibility)
|
|
@@ -242,7 +246,7 @@ The system renders a single entry-point document (`go.md`) from Jinja2 templates
|
|
|
242
246
|
1. Copy template tree from package to target directory (default: `docs/project-guide`)
|
|
243
247
|
2. Render `go.md` in `default` mode
|
|
244
248
|
3. Create `.project-guide.yml` with current version, target directory, metadata file path, and `default` mode
|
|
245
|
-
4.
|
|
249
|
+
4. Write the canonical `# project-guide` block to `.gitignore` (3 lines: ignore everything under `target_dir` except `go.md` — the LLM reads it and IDE-integrated LLMs hide gitignored files from the LLM's view; see FR-14)
|
|
246
250
|
5. Report number of files installed
|
|
247
251
|
|
|
248
252
|
**Edge Cases:**
|
|
@@ -372,7 +376,29 @@ The bundled `templates/artifacts/pyve-essentials.md` artifact covers: two-enviro
|
|
|
372
376
|
- `.project-guide.yml` is absent (let `init` bootstrap; the hook does not error).
|
|
373
377
|
- The config fails to load (schema mismatch, parse error) — the subcommand surfaces the error with its own guidance.
|
|
374
378
|
|
|
375
|
-
**Inverted gitignore policy.** `init`'s gitignore writer
|
|
379
|
+
**Inverted gitignore policy.** `init`'s gitignore writer produces a canonical 3-line block under a `# project-guide` header: ignore everything under `target_dir` *except* `go.md` (tightened from the original 4-line v2.6.0 form in v2.6.1 — the explicit `.bak.*` line was redundant with the broader `**` rule). The remaining template tree is bundled static data that `heal` repopulates on first invocation. This eliminates the ~35-file install footprint from consumer-repo `git status` and PR reviews. Consumers migrating from a pre-Phase-P install run `project-guide init --force` to refresh the gitignore block; `git rm --cached` is the manual cleanup for previously tracked files. v2.6.0 installs heal to the v2.6.1 3-line form on the next `init --force`.
|
|
380
|
+
|
|
381
|
+
### FR-15: Story-Aware `git-push` Wrapper (gitbetter integration)
|
|
382
|
+
|
|
383
|
+
`project-guide git-push [BRANCH_NAME]` wraps [gitbetter](https://github.com/pointmatic/gitbetter)'s `git-push` with story metadata: it derives the commit message from the most-recently-completed-and-not-yet-committed story in `docs/specs/stories.md` and shells out to gitbetter to perform the actual push. The wrapper collapses the developer's per-story commit step from "find the story ID, format the message, type the command" to a single command, while delegating every real git operation (preview, confirm, branch cleanup, reject/recovery menu) to gitbetter.
|
|
384
|
+
|
|
385
|
+
**Heading-to-message transformation:**
|
|
386
|
+
- Input: `### Story G.a: v1.2.3 New command \`foo\` with "Hello" [Done]`
|
|
387
|
+
- Output: `G.a: v1.2.3 New command 'foo' with 'Hello'`
|
|
388
|
+
- Rules: strip `### Story ` prefix and ` [Done]` suffix; replace backticks and double quotes with single quotes; preserve single quotes and the colon after the story ID. The colon is the anchor the already-committed check searches for in `git log --pretty=%s`.
|
|
389
|
+
|
|
390
|
+
**Hard errors (exit 1):**
|
|
391
|
+
- `docs/specs/stories.md` is absent.
|
|
392
|
+
- No `[Done]` story in `stories.md`.
|
|
393
|
+
- The last `[Done]` story is already committed (per `git log` subject prefix match) — the wrapper does not second-guess; the developer resolves manually with raw `git-push`.
|
|
394
|
+
- Multiple `[Done]` stories are uncommitted — same reasoning. The wrapper is for the common "one story done, ready to push" case; multi-story batches are explicit-is-better-than-implicit.
|
|
395
|
+
- `git-push` is not on PATH — the wrapper prints the install hint (`brew install pointmatic/tap/gitbetter`) and exits.
|
|
396
|
+
|
|
397
|
+
**Child-process semantics.** The wrapper invokes `git-push` via `subprocess.run(argv, check=False)` with no captured output, so gitbetter inherits the parent's stdin/stdout/stderr and stays fully interactive. The child's exit code is propagated to `sys.exit` unchanged so gitbetter's reject/recovery menu surfaces with real semantics.
|
|
398
|
+
|
|
399
|
+
**LLM-vs-developer-lane.** This is a developer-lane convenience command. The LLM **does not** initiate it — the approval-gate discipline (do not propose commits, pushes, or follow-ups at story-end) remains in force. The wrapper is invoked by the developer after the LLM presents a completed story.
|
|
400
|
+
|
|
401
|
+
**`spec_artifacts_path` resolution.** The wrapper reads `spec_artifacts_path` from project-guide metadata when available; otherwise falls back to `docs/specs`. This lets the wrapper work in projects that haven't yet run `project-guide init`, including this project itself before metadata renders.
|
|
376
402
|
|
|
377
403
|
### FR-7: Shell Completion
|
|
378
404
|
|
|
@@ -500,3 +526,4 @@ modes:
|
|
|
500
526
|
14. Package is published to PyPI as `project-guide`
|
|
501
527
|
15. `project-guide heal` (FR-14) is **silent on no drift** and applies fixes after the `[Y/n]` prompt on drift; under `--no-input` / `CI=1` / non-TTY the prompt is replaced with auto-yes plus the `Auto-healing N templates under --no-input.` stderr notice
|
|
502
528
|
16. The auto-hook fires for every CLI invocation including `--help` and `--version`, is silent in the steady state, recursion-guarded by `PROJECT_GUIDE_HEALING=1`, and never blocks the original subcommand on prompt decline
|
|
529
|
+
17. `project-guide git-push [BRANCH_NAME]` (FR-15) derives the commit message from the last `[Done]` story, hard-errors on already-committed or multi-uncommitted-Done states or missing gitbetter, and propagates gitbetter's child exit code unchanged
|
|
@@ -63,11 +63,11 @@ The hook is silent in the steady state (no drift → no output). It is recursion
|
|
|
63
63
|
|
|
64
64
|
**Skip conditions:** the hook returns silently when `PROJECT_GUIDE_HEALING=1` is set, when `.project-guide.yml` is absent (let `init` bootstrap; `heal` would error otherwise), or when config load / drift detection fails. Missing `.project-guide.yml` is a hard error from the **`heal` subcommand itself** but a silent skip from the **hook** — the original subcommand surfaces the missing-config error with its own guidance.
|
|
65
65
|
|
|
66
|
-
### Inverted gitignore policy (added v2.6.0)
|
|
66
|
+
### Inverted gitignore policy (added v2.6.0, tightened v2.6.1)
|
|
67
67
|
|
|
68
|
-
`init`'s gitignore writer
|
|
68
|
+
`init`'s gitignore writer produces a canonical 3-line block: ignore everything under `target_dir` *except* `go.md` (Story P.d wrote the inversion; Story P.j / v2.6.1 dropped a redundant `.bak.*` line that the original block carried over from the pre-P.d shape). The remaining template tree is bundled static data that `heal` repopulates on first invocation in a fresh clone, so tracking it in the consumer repo would just add ~35 files of noise to `git status` and PR reviews.
|
|
69
69
|
|
|
70
|
-
**Idempotent rewrite:** `_ensure_gitignore_entry()` recognizes the
|
|
70
|
+
**Idempotent rewrite:** `_ensure_gitignore_entry()` recognizes the v2.6.1 canonical block plus prior forms (the v2.6.0 4-line block, the pre-P.d `.bak.*`-only block, and the legacy `<target>/go.md` line) and rewrites all of them cleanly to the v2.6.1 3-line form. A foreign hand-customized block under a `# project-guide` header is left untouched with a stderr warning; migrate manually or run `init --force`.
|
|
71
71
|
|
|
72
72
|
### IDE-LLM visibility constraint (added v2.6.0)
|
|
73
73
|
|
|
@@ -94,3 +94,20 @@ Auto-healing N templates under --no-input.
|
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
The notice is **non-suppressible** (always emitted even with `--quiet`) so CI logs and embedding callers (pyve scaffolding, etc.) have a visible signal that file writes occurred. This is the heal-specific application of the `--no-input` contract documented earlier in this file.
|
|
97
|
+
|
|
98
|
+
### `project-guide git-push` is developer-lane (added v2.7.0)
|
|
99
|
+
|
|
100
|
+
`project-guide git-push` is a thin wrapper over [gitbetter](https://github.com/pointmatic/gitbetter)'s `git-push` that auto-derives the commit message from the most-recently-completed-and-not-yet-committed `[Done]` story heading. It is a **developer-lane convenience command** — the LLM **must not** initiate it. The approval-gate discipline rule earlier in this file ("do not propose commits, pushes, or bundling options ... do not offer 'want me to also …?' follow-ups") remains in force, and applies to this wrapper just as it does to raw `git`. The wrapper exists to shorten the developer's typing at commit time, not to give the LLM a new excuse to volunteer commits.
|
|
101
|
+
|
|
102
|
+
**Heading-to-message rules:**
|
|
103
|
+
- Output is `"<id>: <title>"`. The colon after the story ID is preserved — it is the anchor the already-committed check searches for in `git log --pretty=%s` via the regex `^([A-Z]\.[a-z]+):\s`.
|
|
104
|
+
- Backticks (`` ` ``) in the title become single quotes.
|
|
105
|
+
- Double quotes (`"`) in the title become single quotes.
|
|
106
|
+
- Single quotes pass through unchanged. The wrapper invokes gitbetter via `subprocess.run([...], shell=False)`, so there is no shell-quoting concern.
|
|
107
|
+
|
|
108
|
+
**Branch logic:**
|
|
109
|
+
- 0 uncommitted `[Done]` stories → exit 1 "Story <last id> is already committed."
|
|
110
|
+
- 1 uncommitted `[Done]` story → derive message, invoke `git-push`.
|
|
111
|
+
- 2+ uncommitted `[Done]` stories → exit 1 listing the IDs; developer commits them one at a time with raw `git-push`.
|
|
112
|
+
|
|
113
|
+
**External CLI dependency pattern.** `git-push` is the first `project-guide` subcommand that depends on an external binary being on PATH. See `tech-spec.md` § "External CLI Dependencies" for the canonical pattern (discover via `shutil.which`, invoke via `subprocess.run(..., check=False)` with no captured output, propagate exit code). Future workflow-integration commands should follow the same shape.
|
|
@@ -34,7 +34,7 @@ Collapse the `docs/project-guide/` install footprint in consumer repos to a sing
|
|
|
34
34
|
|
|
35
35
|
**Implementation order:** `heal` core (P.a) → auto-hook (P.b) → `--no-input` semantics (P.c) → gitignore template flip (P.d). Production hardening items (P.e–P.h) are independent and can ship in any order. P.i is the doc-only story that bundles the v2.6.0 release.
|
|
36
36
|
|
|
37
|
-
**Version cadence:** phase-bundled — stories
|
|
37
|
+
**Version cadence:** phase-bundled — stories P.a–P.i ran unversioned and shipped together as **v2.6.0** (P.i owned the single bundled bump). **Stories P.j and P.k were added post-bundle** and follow standard per-story cadence: **P.j → v2.6.1** (patch — gitignore-block tightening, a fix-up to P.d), **P.k → v2.7.0** (minor — new `git-push` wrapper command). Phases can be extended after their bundled release, but new stories then follow the standard per-story cadence rather than rejoining the (already-shipped) bundle.
|
|
38
38
|
|
|
39
39
|
### Story P.a: Heal command with drift detection and create-missing semantics [Done]
|
|
40
40
|
|
|
@@ -192,6 +192,76 @@ Doc-only release bundling story for Phase P. Updates the spec artifacts, README,
|
|
|
192
192
|
- [x] Bump `pyproject.toml`: `version = "2.6.0"`
|
|
193
193
|
- [x] Add `## [2.6.0] - <date>` entry to `CHANGELOG.md` summarizing all of Phase P's user-visible changes (heal command, auto-hook, gitignore inversion, SECURITY.md, CONTRIBUTING.md, dependabot, CI tightening)
|
|
194
194
|
|
|
195
|
+
### Story P.j: v2.6.1 Drop redundant `.bak.*` line from canonical gitignore block [Done]
|
|
196
|
+
|
|
197
|
+
Tighten the canonical `# project-guide` gitignore block from four lines to three by removing the `<target>/**/*.bak.*` line — it is already covered by the broader `<target>/**` rule introduced in P.d. The fourth line was carried over from the pre-P.d block during the policy inversion and is functionally redundant under the new shape. **Existing installs are not at risk:** `_recognized_block_lines()` continues to recognize the old line, so `init --force` cleanly rewrites a v2.6.0-style 4-line block to the v2.6.1 3-line form.
|
|
198
|
+
|
|
199
|
+
- [x] In `project_guide/cli.py:_build_project_guide_block()`: remove the trailing `f"{target_dir}/**/*.bak.*\n"` line. New canonical form:
|
|
200
|
+
```
|
|
201
|
+
# project-guide
|
|
202
|
+
<target>/**
|
|
203
|
+
!<target>/go.md
|
|
204
|
+
```
|
|
205
|
+
- [x] In `project_guide/cli.py:_recognized_block_lines()`: **keep** the `f"{target_dir}/**/*.bak.*"` entry in the recognized set so a v2.6.0-shipped 4-line block is treated as "ours" and rewritten on `init --force` rather than warned about as foreign.
|
|
206
|
+
- [x] In `tests/test_cli.py`:
|
|
207
|
+
- [x] Update `_EXPECTED_GITIGNORE_BLOCK` to the 3-line form
|
|
208
|
+
- [x] Update `test_init_force_rewrites_old_recognized_block_cleanly` to verify the test case where the prior block is the v2.6.0 4-line form (recognized → rewritten to 3-line) in addition to the existing legacy-`.bak.*`-only case *(split into two tests: `test_init_force_rewrites_legacy_bak_only_block_cleanly` for the pre-P.d shape and `test_init_force_rewrites_v260_four_line_block_to_three_lines` for the v2.6.0→v2.6.1 cleanup)*
|
|
209
|
+
- [x] Update `test_init_with_existing_canonical_block_is_idempotent` so the seed block is the new 3-line form *(picked up via the `_EXPECTED_GITIGNORE_BLOCK` rebind)*
|
|
210
|
+
- [x] All assertions that the `.bak.*` line is present should be removed
|
|
211
|
+
- [x] Doc updates (bundled in this story):
|
|
212
|
+
- [x] `docs/specs/features.md`: update the Outputs / File Structure prose that currently lists `.bak.*` as a separate exception — the only tracked file under `target_dir` is `go.md`; `.bak.*` is just one of the many things subsumed by the broad `**` rule
|
|
213
|
+
- [x] `docs/specs/tech-spec.md`: update the `## .gitignore Management` code block from 4 lines to 3, and refresh the surrounding prose
|
|
214
|
+
- [x] `docs/specs/project-essentials.md`: amend the "Inverted gitignore policy" sub-section added in P.i to note the v2.6.1 simplification (the `.bak.*` line was redundant and was removed; existing v2.6.0 blocks heal on `init --force`)
|
|
215
|
+
- [x] `README.md`: update the Quick Start step-1 footnote to drop the "and `.bak.*` backup files" callout — `.bak.*` is no longer a separate exception, just one of the things ignored by `<target>/**`
|
|
216
|
+
- [x] Bump `project_guide/version.py`: `__version__ = "2.6.1"`
|
|
217
|
+
- [x] Bump `pyproject.toml`: `version = "2.6.1"`
|
|
218
|
+
- [x] Add `## [2.6.1] - <date>` entry to `CHANGELOG.md` (this is a follow-up tightening of P.d; the v2.6.0 entry stays as historical record of what actually shipped, including the redundant line)
|
|
219
|
+
|
|
220
|
+
**Migration:** none required. Consumer repos running v2.6.0 see identical *behavior* under v2.6.1 (the gitignore semantics don't change — the redundant line was a no-op). Running `init --force` on a v2.6.0 install simply produces a tidier 3-line block. Consumers who never run `init --force` keep the 4-line block forever without issue.
|
|
221
|
+
|
|
222
|
+
### Story P.k: v2.7.0 `project-guide git-push` wrapper for gitbetter [Done]
|
|
223
|
+
|
|
224
|
+
Add a new `project-guide git-push [BRANCH_NAME]` command that auto-derives the commit message from the most-recently-completed-and-not-yet-committed story heading in `docs/specs/stories.md` and shells out to [gitbetter](https://github.com/pointmatic/gitbetter)'s `git-push` to perform the actual push. Collapses the developer's per-story commit step from "find the story ID, format the message correctly, type the command" to a single command, while delegating every actual git operation (preview, confirm, branch cleanup, reject/recovery menu) to gitbetter.
|
|
225
|
+
|
|
226
|
+
This is a developer-lane convenience command. **The LLM still does not initiate it** — the approval-gate discipline rule in `project-essentials.md` remains in force, and this story's doc updates reinforce that explicitly so future LLMs don't start offering `project-guide git-push` as a follow-up.
|
|
227
|
+
|
|
228
|
+
- [x] Story detection (`project_guide/stories.py` or a small new helper):
|
|
229
|
+
- [x] Parse `docs/specs/stories.md` for the **last** `### Story <ID>: ... [Done]` heading in file order *(implemented as `_read_done_stories()` returning **all** `[Done]` headings; the "last" rule emerges naturally from the "0 / 1 / 2+ uncommitted" branch logic)*
|
|
230
|
+
- [x] When no `[Done]` story exists, exit 1 with stderr `No completed story found in <stories.md>.`
|
|
231
|
+
- [x] When `stories.md` is absent, exit 1 with stderr matching the existing `_read_stories_summary` no-stories behavior *(emits `Error: docs/specs/stories.md not found.`)*
|
|
232
|
+
- [x] Commit-message derivation:
|
|
233
|
+
- [x] Input form: `### Story G.a: v1.2.3 New command \`foo\` with "Hello" [Done]`
|
|
234
|
+
- [x] Output form: `G.a: v1.2.3 New command 'foo' with 'Hello'`
|
|
235
|
+
- [x] Transformations: strip `### Story ` prefix, strip ` [Done]` suffix, replace each backtick with a single quote, replace each double quote with a single quote. Single quotes in the original heading pass through unchanged (no shell quoting concerns because the wrapper invokes gitbetter via `subprocess.run([...], shell=False)`)
|
|
236
|
+
- [x] Preserve the colon after the story ID (matches the project's commit-message convention and is the anchor the already-committed check searches for)
|
|
237
|
+
- [x] Already-committed detection (Q5 → hard error):
|
|
238
|
+
- [x] Run `git log --pretty=%s` and scan for a subject line whose prefix matches `<story_id>: ` (e.g., `G.a: `)
|
|
239
|
+
- [x] If found, exit 1 with stderr `Story <ID> is already committed. Use 'git-push' directly for any follow-up commit.` — the developer resolves manually rather than the wrapper second-guessing
|
|
240
|
+
- [x] Multi-uncommitted-story detection (Q3 → hard error):
|
|
241
|
+
- [x] If more than one `[Done]` story in the file has no matching `git log` subject, exit 1 with stderr listing the IDs and pointing the developer at raw `git-push` to commit them one at a time with explicit messages
|
|
242
|
+
- [x] gitbetter invocation:
|
|
243
|
+
- [x] Detect `git-push` on PATH via `shutil.which("git-push")`; on miss, exit 1 with stderr `git-push not found on PATH. Install gitbetter: brew install pointmatic/tap/gitbetter`
|
|
244
|
+
- [x] Build argv: `["git-push", message]` plus `[branch_name]` when provided
|
|
245
|
+
- [x] `subprocess.run(argv, check=False)`; propagate the child's exit code unchanged so gitbetter's reject/recovery menu surfaces to the developer with its real exit semantics
|
|
246
|
+
- [x] CLI surface (`project_guide/cli.py`):
|
|
247
|
+
- [x] New `@main.command(name="git-push")` with optional `BRANCH_NAME` positional
|
|
248
|
+
- [x] No `--quiet` / `--no-input` plumbing — the wrapped command is fully interactive, so the flags would be no-ops
|
|
249
|
+
- [x] Help text cites gitbetter, the brew install command, and the auto-message-derivation rules
|
|
250
|
+
- [x] Tests in `tests/test_cli.py`: 11 new tests under `--- Story P.k ---`. Covers happy path, branch-name passthrough, all heading transforms (plain, backticks-only, double-quotes-only, both, single-quote passthrough, no-version doc-only form), already-committed error, multi-uncommitted error, no-Done error, stories.md missing, `git-push` missing on PATH, and child-exit-code propagation.
|
|
251
|
+
- [x] Doc updates (bundled in this story per the version-bumping rule — code story owns its own doc churn):
|
|
252
|
+
- [x] `docs/specs/features.md`: new functional requirement (FR-15 or next) for the wrapper; add to Inputs / Command Line; add an Acceptance Criteria item
|
|
253
|
+
- [x] `docs/specs/tech-spec.md`: add `git-push` to the CLI Design / Commands table; document the `shutil.which` discovery + `subprocess.run` exit-code-propagation pattern under Cross-Cutting Concerns *(added as new "External CLI Dependencies (Story P.k pattern)" section so future wrappers follow the same shape)*
|
|
254
|
+
- [x] `README.md`: new `### git-push` section in Command Reference between `update` (or `heal`) and `override`; make the gitbetter dependency clearly optional and link the install command
|
|
255
|
+
- [x] `docs/specs/project-essentials.md`: append a sub-section covering (a) the message-derivation rules, (b) the **LLM-vs-developer-lane reminder** — `project-guide git-push` is invoked by the developer after the LLM presents a completed story; the LLM still does not initiate commits per the approval-gate discipline
|
|
256
|
+
- [x] Bump `project_guide/version.py`: `__version__ = "2.7.0"`
|
|
257
|
+
- [x] Bump `pyproject.toml`: `version = "2.7.0"`
|
|
258
|
+
- [x] Add `## [2.7.0] - <date>` CHANGELOG entry summarizing the new command and gitbetter integration
|
|
259
|
+
|
|
260
|
+
**Out of scope (revisit if demand):**
|
|
261
|
+
- Passthrough of gitbetter's other flags (`--amend`, `--keep`, `--force-with-lease`). The wrapper accepts only `BRANCH_NAME`; everything else routes through raw `git-push`.
|
|
262
|
+
- A parallel `project-guide git-tag` wrapper. gitbetter has one but the project's release process already uses `bump-version` + raw `git tag`, so the value is lower.
|
|
263
|
+
- Pre-flight `pyve test` / `ruff check` gates before invoking gitbetter. The developer runs those during the story's normal cycle before marking `[Done]`, so re-running them at push time is redundant.
|
|
264
|
+
|
|
195
265
|
---
|
|
196
266
|
|
|
197
267
|
## Future
|
|
@@ -121,18 +121,22 @@ project-guide/
|
|
|
121
121
|
| `status` | Grouped status: Mode, Guide, Files (with `--verbose`) |
|
|
122
122
|
| `update` | Hash-based sync with prompt/force/dry-run |
|
|
123
123
|
| `heal` | Silent-when-clean drift repair with create-missing semantics; fires automatically before every other command via the group-level auto-hook |
|
|
124
|
+
| `git-push [BRANCH_NAME]` | Wrap gitbetter's `git-push` with a commit message derived from the last `[Done]` story heading; shells out via `shutil.which` + `subprocess.run`, propagates child exit code |
|
|
124
125
|
| `override` | Lock a file from updates |
|
|
125
126
|
| `unoverride` | Remove a file lock |
|
|
126
127
|
| `overrides` | List all locked files |
|
|
127
128
|
| `purge` | Remove all project-guide files with confirmation |
|
|
128
129
|
|
|
129
130
|
**Key functions:**
|
|
130
|
-
- `_ensure_gitignore_entry(target_dir)` — writes the canonical `# project-guide` block: ignore everything under `target_dir` except `go.md`
|
|
131
|
+
- `_ensure_gitignore_entry(target_dir)` — writes the canonical `# project-guide` block: ignore everything under `target_dir` except `go.md` (3-line form as of P.j / v2.6.1). Idempotent. Recognized prior blocks (pre-P.d `.bak.*`-only form, v2.6.0 4-line form with the redundant `.bak.*` line, legacy `<target>/go.md` line) are rewritten cleanly to the v2.6.1 3-line form; foreign hand-customized content under a `# project-guide` header is left alone with a stderr warning.
|
|
131
132
|
- `_copy_template_tree(src, dest, force)` — recursive copy preserving structure
|
|
132
133
|
- `_migrate_config_if_needed()` — renames legacy `.project-guides.yml`
|
|
133
134
|
- `_apply_heal(config, config_path)` — apply pending template syncs and re-render `go.md`. Sets `PROJECT_GUIDE_HEALING=1` in `os.environ` before doing any writes so nested subprocess invocations don't re-enter the auto-hook.
|
|
134
135
|
- `_run_pre_invoke_hook()` — group-level auto-heal hook (Story P.b/c). Calls `should_skip_input()` to honor the `--no-input` contract via env / TTY signals; silent when no drift; prompts on drift in interactive mode; auto-yes + `Auto-healing N templates under --no-input.` stderr notice in skip-input mode.
|
|
135
136
|
- `HealGroup(click.Group)` — custom Click group whose overridden `main()` runs `_run_pre_invoke_hook()` before `super().main()`, so `--help` and `--version` (eager flags that would otherwise short-circuit during arg parsing) still trigger the hook.
|
|
137
|
+
- `_get_committed_story_ids()` — parses `git log --pretty=%s` and extracts story IDs from any subject line whose prefix matches `<id>: ` (regex `^([A-Z]\.[a-z]+):\s`). Returns an empty set on `git`-not-found, non-git cwd, or empty history. Used by the `git-push` wrapper to decide which `[Done]` stories are uncommitted.
|
|
138
|
+
- `_resolve_spec_artifacts_path()` — best-effort resolver for the `spec_artifacts_path` metadata value used by `git-push` to locate `stories.md`. Falls back to `docs/specs` when config / metadata are unavailable so the wrapper works in projects that haven't yet run `init`.
|
|
139
|
+
- `project_guide/stories.py:_read_done_stories()` / `derive_commit_message()` — pure helpers used by `git-push`. `_read_done_stories` returns all `[Done]` headings as `StoryHeading(story_id, title)` tuples in file order; `derive_commit_message` produces the gitbetter-ready subject `"<id>: <transformed title>"` (backticks → single quotes, double quotes → single quotes, single quotes pass through, colon preserved).
|
|
136
140
|
|
|
137
141
|
### Module: `config.py` (138 lines)
|
|
138
142
|
|
|
@@ -266,17 +270,18 @@ class ModeDefinition:
|
|
|
266
270
|
|
|
267
271
|
### `.gitignore` Management
|
|
268
272
|
|
|
269
|
-
`init` writes a canonical
|
|
273
|
+
`init` writes a canonical 3-line block under a `# project-guide` comment header (Story P.d, tightened in P.j / v2.6.1):
|
|
270
274
|
```
|
|
271
275
|
# project-guide
|
|
272
276
|
docs/project-guide/**
|
|
273
277
|
!docs/project-guide/go.md
|
|
274
|
-
docs/project-guide/**/*.bak.*
|
|
275
278
|
```
|
|
276
279
|
|
|
277
280
|
**Why this shape:** every file under `target_dir` except `go.md` is bundled static data that `heal` (FR-14) repopulates on first invocation, so tracking the full template tree in the consumer repo would just add ~35 files of noise to `git status` and PR reviews. `go.md` itself **must remain tracked** because IDE-integrated LLMs (Cursor, Claude Code, etc.) typically hide gitignored files from the LLM's view, and the LLM's instruction to `Read docs/project-guide/go.md` requires the file to be visible. The repo-history value of `go.md` is incidental — the file churns on every mode switch — and that churn is the acceptable cost for LLM visibility.
|
|
278
281
|
|
|
279
|
-
**
|
|
282
|
+
**Why not the explicit `.bak.*` line that v2.6.0 shipped?** It was carried over from the pre-P.d block during the policy inversion but is functionally redundant: the `<target>/**` rule already ignores backups produced by sync/heal under that subtree. P.j dropped the line; existing v2.6.0 installs heal cleanly to the 3-line form on `init --force` because `_recognized_block_lines()` still lists the v2.6.0 entry.
|
|
283
|
+
|
|
284
|
+
**Existing-block detection:** `_ensure_gitignore_entry()` is idempotent. A recognized prior block (the v2.6.1 canonical form, the v2.6.0 4-line form, or the pre-P.d `.bak.*`-only form) is rewritten cleanly to the v2.6.1 3-line form. A foreign hand-customized block under a `# project-guide` header is left untouched with a stderr warning. A `.gitignore` with no `# project-guide` header gets the canonical block appended (separated by a blank line). Migration for pre-Phase-P consumer repos: `project-guide init --force` rewrites the block; `git rm --cached` is the manual cleanup for already-tracked files.
|
|
280
285
|
|
|
281
286
|
---
|
|
282
287
|
|
|
@@ -337,6 +342,17 @@ Fail fast with actionable messages:
|
|
|
337
342
|
- `.project-guides.yml` → `.project-guide.yml` (automatic rename on any CLI command)
|
|
338
343
|
- v1.x config detection → migration notice in `status` output
|
|
339
344
|
|
|
345
|
+
### External CLI Dependencies (Story P.k pattern)
|
|
346
|
+
|
|
347
|
+
`git-push` is the first `project-guide` subcommand that **depends on an external CLI being on PATH** (gitbetter's `git-push` binary). Future workflow-integration commands (potential `git-tag`, `git-rebase`, etc.) should follow the same pattern:
|
|
348
|
+
|
|
349
|
+
1. **Discover** via `shutil.which(name)`. If `None`, exit 1 with stderr that names the missing tool and the canonical install command. Never silently fall back to a degraded behavior.
|
|
350
|
+
2. **Invoke** via `subprocess.run(argv, check=False)` with **no captured output** so the external tool inherits the parent's stdin/stdout/stderr (interactive flows like prompts and progress reporting must reach the developer unaltered).
|
|
351
|
+
3. **Propagate** the child's exit code with `sys.exit(result.returncode)`. The wrapper's own exit semantics are a passthrough — the external tool's reject/recovery semantics are the source of truth, not the wrapper's.
|
|
352
|
+
4. **Tests** mock both `shutil.which` (to control discovery) and `subprocess.run` (to control the child's behavior and capture argv). See `tests/test_cli.py::test_git_push_*` for the reference test shape.
|
|
353
|
+
|
|
354
|
+
This deliberately keeps each wrapper a thin convenience layer rather than a parallel implementation. The tested invariants are: discovery error message, argv shape (including positional passthrough), and exit-code propagation. Nothing else.
|
|
355
|
+
|
|
340
356
|
### Auto-Heal Group Hook (Phase P)
|
|
341
357
|
|
|
342
358
|
Every `project-guide` invocation runs the heal drift-detection + prompt path before dispatching the requested subcommand. This is implemented as a custom `HealGroup(click.Group)` whose overridden `main()` calls `_run_pre_invoke_hook()` before delegating to `super().main()`. Running before `super().main()` is deliberate: it places the hook **ahead of `make_context` / arg parsing**, which is what makes the hook fire even for `--help` and `--version` (eager flags that would otherwise short-circuit before any subcommand or group body runs).
|