project-guide 2.7.1__tar.gz → 2.7.2__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.7.1 → project_guide-2.7.2}/.gitignore +5 -3
- {project_guide-2.7.1 → project_guide-2.7.2}/.project-guide.yml +2 -2
- {project_guide-2.7.1 → project_guide-2.7.2}/CHANGELOG.md +17 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/PKG-INFO +1 -1
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/stories.md +34 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/actions.py +4 -2
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/cli.py +3 -2
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/stories.py +4 -1
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/_phase-letters.md +9 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/version.py +1 -1
- {project_guide-2.7.1 → project_guide-2.7.2}/pyproject.toml +1 -1
- {project_guide-2.7.1 → project_guide-2.7.2}/tests/test_actions.py +13 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/tests/test_cli.py +83 -0
- project_guide-2.7.2/tests/test_stories.py +141 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/.github/FUNDING.yml +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/.github/dependabot.yml +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/.github/workflows/ci.yml +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/.github/workflows/deploy-docs.yml +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/.github/workflows/publish.yml +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/.github/workflows/test.yml +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/.pyve/config +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/.tool-versions +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/CONTRIBUTING.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/LICENSE +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/README.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/SECURITY.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/.gitignore +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/about/changelog.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/about/license.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/developer-guide/contributing.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/developer-guide/development.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/developer-guide/testing.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/getting-started.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/images/project-guide-banner-landing.png +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/images/project-guide-header-readme.png +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/index.html +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/user-guide/commands.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/user-guide/configuration.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/user-guide/install-options.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/user-guide/modes.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/user-guide/overrides.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/site/user-guide/workflow.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/phase-j-modes-plan.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/phase-k-release-lifecycle-plan.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/phase-l-no-input-init-plan.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/phase-m-project-essentials-plan.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/phase-n-mode-naming-cli-memory-plan.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/phase-o-pyve-quiet-embedding-subplan.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/phase-o-quiet-non-interactive-embedding-feature.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/project-guide-no-input-spec.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/stories-v1.3.1.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/stories-v2.0.20.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/stories-v2.3.9.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/stories-v2.4.19.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/stories-v2.5.15.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/ux-problems-v2.0.10.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/brand-descriptions.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/concept.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/features.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/phase-p-auto-heal-plan.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/project-essentials.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/tech-spec.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/mkdocs.yml +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project-guide-old-template.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/__init__.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/__main__.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/config.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/exceptions.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/metadata.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/render.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/runtime.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/sync.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/.project-guide.yml.template +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/.metadata.yml +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/README.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/developer/best-practices-guide.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/developer/brand-descriptions-guide.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/developer/codecov-setup-guide.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/developer/debug-guide.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/developer/landing-page-guide.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/developer/production-github-guide.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/developer/project-guide.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/developer/python-editable-install.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/artifacts/brand-descriptions.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/artifacts/concept.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/artifacts/features.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/artifacts/project-essentials.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/artifacts/pyve-essentials.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/artifacts/stories.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/artifacts/tech-spec.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/llm_entry_point.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/_header-common.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/_header-cycle.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/_header-sequence.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/archive-stories-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/code-direct-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/code-test-first-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/debug-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/default-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/document-brand-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/document-landing-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/plan-concept-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/plan-features-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/plan-phase-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/plan-production-phase-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/plan-stories-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/plan-tech-spec-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/refactor-document-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/refactor-plan-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/templates/modes/scaffold-project-mode.md +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/requirements-dev.txt +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/tests/__init__.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/tests/conftest.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/tests/test_archive_stories_mode.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/tests/test_config.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/tests/test_integration.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/tests/test_metadata.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/tests/test_purge.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/tests/test_render.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/tests/test_runtime.py +0 -0
- {project_guide-2.7.1 → project_guide-2.7.2}/tests/test_sync.py +0 -0
|
@@ -32,6 +32,8 @@ build/
|
|
|
32
32
|
/site/
|
|
33
33
|
|
|
34
34
|
# project-guide
|
|
35
|
-
docs/project-guide
|
|
36
|
-
|
|
37
|
-
docs/project-guide
|
|
35
|
+
/docs/project-guide/.metadata.yml
|
|
36
|
+
/docs/project-guide/README.md
|
|
37
|
+
/docs/project-guide/developer/
|
|
38
|
+
/docs/project-guide/templates/
|
|
39
|
+
/docs/project-guide/**/*.bak.*
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
version: '2.0'
|
|
2
|
-
installed_version: 2.
|
|
2
|
+
installed_version: 2.7.2
|
|
3
3
|
target_dir: docs/project-guide
|
|
4
4
|
metadata_file: .metadata.yml
|
|
5
|
-
current_mode:
|
|
5
|
+
current_mode: debug
|
|
6
6
|
test_first: false
|
|
7
7
|
pyve_version: pyve version 2.6.2
|
|
8
8
|
project_name: project-guide
|
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.7.2] - 2026-05-19
|
|
11
|
+
|
|
12
|
+
Bug fix: `project-guide git-push` (and version/phase detection) silently dropped sub-numbered story IDs (`J.m.1`, `J.m.2`, …). When the latest `[Done]` story used the sub-numbered form, the wrapper fell back to the previous bare-letter heading and reported it as "already committed," blocking the push of the new story.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **`project_guide/stories.py:_STORY_RE`** — extended the story-ID character class from `[A-Z]\.[a-z]+` to `[A-Z]\.[a-z]+(?:\.\d+)?` so headings of the form `### Story J.m.1: ...` parse correctly. Without this, `_read_done_stories()` silently filtered such headings out and the wrapper picked the wrong "last [Done] story."
|
|
16
|
+
- **`project_guide/cli.py:_COMMIT_SUBJECT_STORY_ID_RE`** — extended in the same shape so the already-committed check recognizes commit subjects like `J.m.1: ...`. Without this fix the duplicate-detection path would have a second-layer hole even after the stories.md side was fixed.
|
|
17
|
+
- **`project_guide/actions.py:_VERSION_RE`** — extended in the same shape so `detect_latest_version()` no longer under-reports the highest version when the latest story uses the sub-numbered form.
|
|
18
|
+
|
|
19
|
+
### Documented
|
|
20
|
+
- **`_phase-letters.md`** — added a "Sub-numbered stories" subsection covering both use cases: pre-implementation split (`J.m` was planned but split into `J.m.1`, `J.m.2` before any work; bare `J.m` heading dropped) and post-implementation follow-up (`J.m` shipped, then a bug or follow-on feature lands as `J.m.1` before proceeding to `J.n`). Flat single-level only — no cascading like `J.m.1.1`.
|
|
21
|
+
|
|
22
|
+
### Tests
|
|
23
|
+
- New `tests/test_stories.py` covering `_STORY_RE`, `_read_done_stories()`, and `derive_commit_message()` — `stories.py` had no direct unit tests prior to this story (the test-coverage gap that let the bug ship in P.k).
|
|
24
|
+
- New `tests/test_cli.py` cases for both `git-push` scenarios (post-impl follow-up with bare `J.m` present; pre-impl split with no bare `J.m`) and the round-trip "sub-numbered story is already committed" path.
|
|
25
|
+
- New `tests/test_actions.py` case asserting `detect_latest_version()` picks up the version on a sub-numbered heading.
|
|
26
|
+
|
|
10
27
|
## [2.7.1] - 2026-05-11
|
|
11
28
|
|
|
12
29
|
Compatibility fix for IDE-integrated LLM @-mention / fuzzy-search. The v2.6.0–v2.7.0 gitignore block used a clean `<target>/**` + `!<target>/go.md` negation pair — correct per the `.gitignore` spec, but **several IDE tools** (Cursor, parts of the VS Code fork ecosystem, certain LSP-based search backends) implement a subset of gitignore semantics that does not honor re-include negation. Those tools applied the broad `**` rule, hid `go.md`, and defeated the IDE-LLM-visibility constraint that's the entire reason `go.md` is tracked. P.l switches to a negation-free explicit-list form so simplistic parsers handle it reliably.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: project-guide
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.2
|
|
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/
|
|
@@ -308,6 +308,40 @@ The new canonical form lists every gitignored top-level entry explicitly so no n
|
|
|
308
308
|
- An opt-back-in flag (`--gitignore-style=negation`). YAGNI until someone asks; the new form is strictly better for the documented IDE-LLM-visibility constraint.
|
|
309
309
|
- A `project-guide check` integrity rule that detects "consumer has tracked-but-should-be-ignored files under `target_dir`". Defer until there's a second integrity rule worth shipping (see Future > Integrity & Validation).
|
|
310
310
|
|
|
311
|
+
### Story P.m: v2.7.2 Recognize sub-numbered story IDs (`J.m.1`) in regex sites [Done]
|
|
312
|
+
|
|
313
|
+
Reported bug: in a consumer project with the heading sequence `… J.l [Done], J.m.1 [Done]`, `project-guide git-push` printed `"Story J.l is already committed. Use 'git-push' directly for any follow-up commit."` and refused to push, even though `J.m.1` was the actual just-completed story.
|
|
314
|
+
|
|
315
|
+
**Root cause:** three regexes encoded the story-ID shape as `[A-Z]\.[a-z]+`, which silently fails to match the sub-numbered form. `stories.py:_STORY_RE` was the proximate cause — `_read_done_stories()` filtered the `J.m.1` heading out entirely, leaving `J.l` as the "last `[Done]`," and the commit-subject check then correctly observed that `J.l` had been committed. The other two sites (`cli.py:_COMMIT_SUBJECT_STORY_ID_RE`, `actions.py:_VERSION_RE`) had the same hole and were fixed in the same story to avoid a half-fix where a future code path re-introduced the bug from a different angle.
|
|
316
|
+
|
|
317
|
+
**Sub-numbered form** — `<phase>.<letter>.<digit>+`, flat single-level only (no cascading like `J.m.1.1`). Two use cases per the developer's intent:
|
|
318
|
+
|
|
319
|
+
- **Pre-implementation split:** `J.m` is planned but its scope is judged too large before any work begins; the heading is split into `J.m.1`, `J.m.2` and the bare `J.m` heading is dropped. Sequence: `… J.l, J.m.1, J.m.2, J.n, …`.
|
|
320
|
+
- **Post-implementation follow-up:** `J.m` ships, then a bug or follow-on feature must land before proceeding to `J.n`; the follow-up is added as `J.m.1` (and may cascade to `J.m.2`, `J.m.3`, …). Sequence: `… J.l, J.m, J.m.1, J.m.2, …, J.n, …`.
|
|
321
|
+
|
|
322
|
+
Both scenarios are exercised by the new test suite.
|
|
323
|
+
|
|
324
|
+
- [x] `project_guide/stories.py:_STORY_RE` — extend capture group to `[A-Z]\.[a-z]+(?:\.\d+)?` with comment cross-referencing `_phase-letters.md`
|
|
325
|
+
- [x] `project_guide/cli.py:_COMMIT_SUBJECT_STORY_ID_RE` — matching extension; updated comment to reflect the new shape so future readers see why the two regexes must move together
|
|
326
|
+
- [x] `project_guide/actions.py:_VERSION_RE` — matching extension; `detect_latest_version()` now picks up versions on sub-numbered headings
|
|
327
|
+
- [x] `project_guide/templates/project-guide/templates/modes/_phase-letters.md` — new "Sub-numbered stories" subsection documenting both scenarios and the flat-only constraint
|
|
328
|
+
- [x] Ran `project-guide update` to propagate the template change into `docs/project-guide/templates/modes/_phase-letters.md` (installed copy)
|
|
329
|
+
- [x] New `tests/test_stories.py` — first direct unit-test coverage for `stories.py` (the gap that let the bug ship in P.k). Covers `_STORY_RE` matches (plain, sub-numbered, multi-digit sub-number), `_read_done_stories()` for both scenarios, `derive_commit_message()` with sub-numbered ID, and the round-trip through `cli._COMMIT_SUBJECT_STORY_ID_RE`
|
|
330
|
+
- [x] `tests/test_cli.py` — new cases for both `git-push` scenarios (post-impl follow-up; pre-impl split with no bare `J.m`) and the "sub-numbered story is already committed" path that surfaces the second-layer regex hole
|
|
331
|
+
- [x] `tests/test_actions.py` — new case asserting `detect_latest_version()` picks up the version on a sub-numbered heading
|
|
332
|
+
- [x] Bump `project_guide/version.py`: `__version__ = "2.7.2"`
|
|
333
|
+
- [x] Bump `pyproject.toml`: `version = "2.7.2"`
|
|
334
|
+
- [x] Add `## [2.7.2] - 2026-05-19` entry to `CHANGELOG.md` framed as a bug fix with test-coverage backfill
|
|
335
|
+
|
|
336
|
+
**Migration:** none. The change is purely permissive — existing plain-letter IDs (`J.l`, `J.m`) continue to match. Consumers who never used sub-numbered IDs see no behavior change; consumers who did will see `git-push` and `detect_latest_version()` start handling those headings correctly.
|
|
337
|
+
|
|
338
|
+
**Why the docs change rides this story:** the sub-numbered form was already in use by consumers but undocumented in `_phase-letters.md`. Extending the regex without documenting the form would leave the next maintainer staring at an unexplained `(?:\.\d+)?` tail. Doc + code travel together.
|
|
339
|
+
|
|
340
|
+
**Out of scope:**
|
|
341
|
+
- Multi-level cascading (`J.m.1.1`). The developer's intent is flat; YAGNI until a consumer asks. Adding it later is a strict superset (`(?:\.\d+)+` instead of `(?:\.\d+)?`) and would be backwards compatible.
|
|
342
|
+
- A `project-guide check` rule that warns when a sub-numbered ID appears without a preceding plain-letter heading in non-pre-split contexts. The two valid scenarios cover everything seen in the wild; defer until there's evidence of misuse.
|
|
343
|
+
- Sub-numbered phase letters (`AA.1`). Phases don't carry the same pre-impl-split / post-impl-followup workflow that motivates the story-level sub-numbering, and no consumer has asked.
|
|
344
|
+
|
|
311
345
|
---
|
|
312
346
|
|
|
313
347
|
## Future
|
|
@@ -127,9 +127,11 @@ class Artifact:
|
|
|
127
127
|
action=action,
|
|
128
128
|
)
|
|
129
129
|
|
|
130
|
-
# Matches `### Story <Phase>.<sub>: vMAJOR.MINOR.PATCH ...`
|
|
130
|
+
# Matches `### Story <Phase>.<sub>: vMAJOR.MINOR.PATCH ...` — the optional
|
|
131
|
+
# `(?:\.\d+)?` tail covers sub-numbered IDs (`J.m.1`, `J.m.2`, …) per the
|
|
132
|
+
# `_phase-letters.md` "Sub-numbered stories" rule.
|
|
131
133
|
_VERSION_RE = re.compile(
|
|
132
|
-
r"^###\s+Story\s+[A-Za-z]+\.[a-z]
|
|
134
|
+
r"^###\s+Story\s+[A-Za-z]+\.[a-z]+(?:\.\d+)?:\s+v(\d+)\.(\d+)\.(\d+)",
|
|
133
135
|
re.M,
|
|
134
136
|
)
|
|
135
137
|
|
|
@@ -1626,8 +1626,9 @@ def heal(no_input: bool):
|
|
|
1626
1626
|
|
|
1627
1627
|
# Regex matching the story-ID prefix of a commit-subject line — used to detect
|
|
1628
1628
|
# which stories have already been committed (Story P.k). Mirrors the
|
|
1629
|
-
# `[A-Z]\.[a-z]
|
|
1630
|
-
|
|
1629
|
+
# `[A-Z]\.[a-z]+(?:\.\d+)?` shape of `_STORY_RE` in `stories.py` (the optional
|
|
1630
|
+
# `.\d+` tail covers sub-numbered IDs like `J.m.1`).
|
|
1631
|
+
_COMMIT_SUBJECT_STORY_ID_RE = __import__("re").compile(r"^([A-Z]\.[a-z]+(?:\.\d+)?):\s")
|
|
1631
1632
|
|
|
1632
1633
|
|
|
1633
1634
|
def _get_committed_story_ids() -> set[str]:
|
|
@@ -17,8 +17,11 @@ from dataclasses import dataclass, field
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
|
|
19
19
|
# Matches: ### Story N.a: v2.4.0 Some Title [Done]
|
|
20
|
+
# The optional `(?:\.\d+)?` tail captures sub-numbered IDs (`J.m.1`, `J.m.2`, …)
|
|
21
|
+
# used for pre-implementation splits or post-implementation follow-ups. See
|
|
22
|
+
# `_phase-letters.md` (Sub-numbered stories).
|
|
20
23
|
_STORY_RE = re.compile(
|
|
21
|
-
r"^### Story ([A-Z]\.[a-z]+): (.+) \[(Done|In Progress|Planned)\]\s*$",
|
|
24
|
+
r"^### Story ([A-Z]\.[a-z]+(?:\.\d+)?): (.+) \[(Done|In Progress|Planned)\]\s*$",
|
|
22
25
|
re.MULTILINE,
|
|
23
26
|
)
|
|
24
27
|
|
|
@@ -14,6 +14,15 @@ Within a phase, stories use lowercase letters following the same scheme: `A.a`,
|
|
|
14
14
|
|
|
15
15
|
Examples: `A.a`, `A.b`, …, `A.z`, `A.aa`, `A.ab`, ….
|
|
16
16
|
|
|
17
|
+
### Sub-numbered stories
|
|
18
|
+
|
|
19
|
+
A story may carry an optional numeric suffix — `J.m.1`, `J.m.2`, … — appended after the sub-letter. Sub-numbers are flat (no cascading like `J.m.1.1`) and start at `1`. Two situations use them:
|
|
20
|
+
|
|
21
|
+
- **Pre-implementation split.** When `J.m` is planned but its scope is judged too large before any work begins, the heading is split into `J.m.1`, `J.m.2`, … and the bare `J.m` heading is dropped. Sequence becomes `…, J.l, J.m.1, J.m.2, J.n, …`.
|
|
22
|
+
- **Post-implementation follow-up.** When `J.m` ships but a bug or follow-on feature must land before proceeding to `J.n`, the follow-up is added as `J.m.1` (and may cascade to `J.m.2`, `J.m.3`, …). Sequence becomes `…, J.l, J.m, J.m.1, J.m.2, …, J.n, …`.
|
|
23
|
+
|
|
24
|
+
Sub-numbered stories follow normal Version Cadence: each one that ships code takes its own bump.
|
|
25
|
+
|
|
17
26
|
### Continuing across archive boundaries
|
|
18
27
|
|
|
19
28
|
When `stories.md` is archived (via `archive_stories` mode), the fresh `stories.md` starts empty — but phase letters do **not** reset. To determine the next phase letter:
|
|
@@ -150,6 +150,19 @@ def test_detect_latest_version_raises_when_none_found():
|
|
|
150
150
|
detect_latest_version(text)
|
|
151
151
|
|
|
152
152
|
|
|
153
|
+
def test_detect_latest_version_recognizes_subnumbered_story_id():
|
|
154
|
+
"""Sub-numbered story IDs (J.m.1, used for pre-impl splits or post-impl
|
|
155
|
+
follow-ups) must contribute to the latest-version computation. The
|
|
156
|
+
plain-letter regex silently dropped them, causing `detect_latest_version`
|
|
157
|
+
to under-report the highest version when the latest story used the
|
|
158
|
+
`.NN` form."""
|
|
159
|
+
text = (
|
|
160
|
+
"### Story J.l: v0.68.0 prior story [Done]\n\n"
|
|
161
|
+
"### Story J.m.1: v0.70.0 follow-up after J.m [Done]\n"
|
|
162
|
+
)
|
|
163
|
+
assert detect_latest_version(text) == (0, 70, 0)
|
|
164
|
+
|
|
165
|
+
|
|
153
166
|
# ---------------------------------------------------------------------------
|
|
154
167
|
# detect_latest_phase_letter
|
|
155
168
|
# ---------------------------------------------------------------------------
|
|
@@ -2811,4 +2811,87 @@ def test_git_push_propagates_child_exit_code(runner, tmp_path, monkeypatch):
|
|
|
2811
2811
|
assert result.exit_code == 7, result.output
|
|
2812
2812
|
|
|
2813
2813
|
|
|
2814
|
+
def test_git_push_picks_up_subnumbered_last_done_story_scenario_a(runner, tmp_path, monkeypatch):
|
|
2815
|
+
"""Scenario A — post-implementation follow-up: ..., J.l, J.m, J.m.1.
|
|
2816
|
+
|
|
2817
|
+
Reported bug: when the last [Done] story is `J.m.1`, the wrapper
|
|
2818
|
+
silently fell back to `J.l` and reported it as already committed.
|
|
2819
|
+
"""
|
|
2820
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
2821
|
+
_write_stories_md(
|
|
2822
|
+
"### Story J.l: update remaining refs [Done]",
|
|
2823
|
+
"### Story J.m: v0.69.0 Integrate component [Done]",
|
|
2824
|
+
"### Story J.m.1: v0.70.0 Follow-up after J.m [Done]",
|
|
2825
|
+
)
|
|
2826
|
+
_mock_git_push_on_path(monkeypatch)
|
|
2827
|
+
captured: list = []
|
|
2828
|
+
_mock_git_log_subjects(
|
|
2829
|
+
monkeypatch,
|
|
2830
|
+
subjects=[
|
|
2831
|
+
"J.l: update remaining refs",
|
|
2832
|
+
"J.m: v0.69.0 Integrate component",
|
|
2833
|
+
],
|
|
2834
|
+
git_push_argv_capture=captured,
|
|
2835
|
+
)
|
|
2836
|
+
|
|
2837
|
+
result = runner.invoke(main, ['git-push'])
|
|
2838
|
+
|
|
2839
|
+
assert result.exit_code == 0, result.output
|
|
2840
|
+
assert len(captured) == 1, captured
|
|
2841
|
+
assert captured[0][1] == "J.m.1: v0.70.0 Follow-up after J.m"
|
|
2842
|
+
|
|
2843
|
+
|
|
2844
|
+
def test_git_push_picks_up_subnumbered_last_done_story_scenario_b(runner, tmp_path, monkeypatch):
|
|
2845
|
+
"""Scenario B — pre-implementation split: ..., J.l, J.m.1, J.m.2 (no bare J.m).
|
|
2846
|
+
|
|
2847
|
+
When J.m's scope is split before implementation, the bare J.m heading
|
|
2848
|
+
never exists. The wrapper's "last [Done]" must be J.m.2.
|
|
2849
|
+
"""
|
|
2850
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
2851
|
+
_write_stories_md(
|
|
2852
|
+
"### Story J.l: update remaining refs [Done]",
|
|
2853
|
+
"### Story J.m.1: v0.69.0 First half of the split [Done]",
|
|
2854
|
+
"### Story J.m.2: v0.70.0 Second half of the split [Done]",
|
|
2855
|
+
)
|
|
2856
|
+
_mock_git_push_on_path(monkeypatch)
|
|
2857
|
+
captured: list = []
|
|
2858
|
+
_mock_git_log_subjects(
|
|
2859
|
+
monkeypatch,
|
|
2860
|
+
subjects=[
|
|
2861
|
+
"J.l: update remaining refs",
|
|
2862
|
+
"J.m.1: v0.69.0 First half of the split",
|
|
2863
|
+
],
|
|
2864
|
+
git_push_argv_capture=captured,
|
|
2865
|
+
)
|
|
2866
|
+
|
|
2867
|
+
result = runner.invoke(main, ['git-push'])
|
|
2868
|
+
|
|
2869
|
+
assert result.exit_code == 0, result.output
|
|
2870
|
+
assert len(captured) == 1, captured
|
|
2871
|
+
assert captured[0][1] == "J.m.2: v0.70.0 Second half of the split"
|
|
2872
|
+
|
|
2873
|
+
|
|
2874
|
+
def test_git_push_errors_when_subnumbered_story_is_already_committed(runner, tmp_path, monkeypatch):
|
|
2875
|
+
"""Already-committed detection must recognize sub-numbered IDs in
|
|
2876
|
+
commit subjects (e.g. `J.m.1: ...`), not just `J.m: ...`. Without
|
|
2877
|
+
this, a sub-numbered story would be re-pushed silently."""
|
|
2878
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
2879
|
+
_write_stories_md(
|
|
2880
|
+
"### Story J.m.1: v0.70.0 Follow-up after J.m [Done]",
|
|
2881
|
+
)
|
|
2882
|
+
_mock_git_push_on_path(monkeypatch)
|
|
2883
|
+
captured: list = []
|
|
2884
|
+
_mock_git_log_subjects(
|
|
2885
|
+
monkeypatch,
|
|
2886
|
+
subjects=["J.m.1: v0.70.0 Follow-up after J.m"],
|
|
2887
|
+
git_push_argv_capture=captured,
|
|
2888
|
+
)
|
|
2889
|
+
|
|
2890
|
+
result = runner.invoke(main, ['git-push'])
|
|
2891
|
+
|
|
2892
|
+
assert result.exit_code == 1
|
|
2893
|
+
assert "Story J.m.1 is already committed" in result.output
|
|
2894
|
+
assert captured == [], "git-push must not be invoked when the story is already committed"
|
|
2895
|
+
|
|
2896
|
+
|
|
2814
2897
|
# --- End Story P.k ----------------------------------------------------------
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Copyright (c) 2026 Pointmatic
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from project_guide.stories import (
|
|
18
|
+
_STORY_RE,
|
|
19
|
+
StoryHeading,
|
|
20
|
+
_read_done_stories,
|
|
21
|
+
derive_commit_message,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
_HEADER = """\
|
|
25
|
+
# stories.md -- testproject (python)
|
|
26
|
+
|
|
27
|
+
## Phase J: Demo
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _write(tmp_path: Path, body: str) -> Path:
|
|
33
|
+
specs = tmp_path / "docs" / "specs"
|
|
34
|
+
specs.mkdir(parents=True)
|
|
35
|
+
(specs / "stories.md").write_text(_HEADER + body, encoding="utf-8")
|
|
36
|
+
return specs
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# _STORY_RE — direct regex coverage for sub-numbered story IDs
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_story_re_matches_plain_letter_id():
|
|
45
|
+
"""Baseline: ordinary `J.l` form still matches."""
|
|
46
|
+
line = "### Story J.l: update remaining quizazz refs [Done]\n"
|
|
47
|
+
matches = _STORY_RE.findall(line)
|
|
48
|
+
assert matches == [("J.l", "update remaining quizazz refs", "Done")]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_story_re_matches_sub_numbered_id():
|
|
52
|
+
"""Sub-numbered `J.m.1` form (post-impl follow-up or pre-impl split)
|
|
53
|
+
must be parsed the same way as the plain form."""
|
|
54
|
+
line = "### Story J.m.1: v0.70.0 Follow-up after J.m [Done]\n"
|
|
55
|
+
matches = _STORY_RE.findall(line)
|
|
56
|
+
assert matches == [("J.m.1", "v0.70.0 Follow-up after J.m", "Done")]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_story_re_matches_multi_digit_sub_number():
|
|
60
|
+
"""Sub-numbers can exceed a single digit (e.g., `.10`, `.11`)."""
|
|
61
|
+
line = "### Story J.m.10: tenth follow-up [Done]\n"
|
|
62
|
+
matches = _STORY_RE.findall(line)
|
|
63
|
+
assert matches == [("J.m.10", "tenth follow-up", "Done")]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# _read_done_stories — "last [Done]" selection with sub-numbered IDs
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_read_done_stories_scenario_a_subnumber_follows_bare_letter(tmp_path):
|
|
72
|
+
"""Scenario A — post-implementation follow-up.
|
|
73
|
+
|
|
74
|
+
Sequence: J.l, J.m, J.m.1 (all Done). J.m was implemented, then a
|
|
75
|
+
follow-up bug/feature surfaced as J.m.1 before proceeding to J.n.
|
|
76
|
+
The wrapper's "last [Done]" must be J.m.1, not J.m or J.l.
|
|
77
|
+
"""
|
|
78
|
+
specs = _write(
|
|
79
|
+
tmp_path,
|
|
80
|
+
(
|
|
81
|
+
"### Story J.l: update remaining refs [Done]\n\n"
|
|
82
|
+
"### Story J.m: v0.69.0 Integrate component [Done]\n\n"
|
|
83
|
+
"### Story J.m.1: v0.70.0 Follow-up after J.m [Done]\n"
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
headings = _read_done_stories(str(specs))
|
|
87
|
+
assert headings is not None
|
|
88
|
+
assert [h.story_id for h in headings] == ["J.l", "J.m", "J.m.1"]
|
|
89
|
+
assert headings[-1] == StoryHeading(
|
|
90
|
+
story_id="J.m.1", title="v0.70.0 Follow-up after J.m"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_read_done_stories_scenario_b_subnumber_without_bare_letter(tmp_path):
|
|
95
|
+
"""Scenario B — pre-implementation split.
|
|
96
|
+
|
|
97
|
+
Sequence: J.l, J.m.1, J.m.2 (all Done). J.m's scope was deemed too
|
|
98
|
+
large before any implementation began, so it was split into J.m.1
|
|
99
|
+
and J.m.2 and the bare J.m heading never existed. The wrapper's
|
|
100
|
+
"last [Done]" must be J.m.2.
|
|
101
|
+
"""
|
|
102
|
+
specs = _write(
|
|
103
|
+
tmp_path,
|
|
104
|
+
(
|
|
105
|
+
"### Story J.l: update remaining refs [Done]\n\n"
|
|
106
|
+
"### Story J.m.1: v0.69.0 First half of the split [Done]\n\n"
|
|
107
|
+
"### Story J.m.2: v0.70.0 Second half of the split [Done]\n"
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
headings = _read_done_stories(str(specs))
|
|
111
|
+
assert headings is not None
|
|
112
|
+
assert [h.story_id for h in headings] == ["J.l", "J.m.1", "J.m.2"]
|
|
113
|
+
assert headings[-1].story_id == "J.m.2"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
# derive_commit_message — round-trip with the cli's commit-subject regex
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_derive_commit_message_with_sub_numbered_id():
|
|
122
|
+
"""Output preserves the full sub-numbered ID, including the colon."""
|
|
123
|
+
heading = StoryHeading(story_id="J.m.1", title="v0.70.0 Follow-up after J.m")
|
|
124
|
+
assert derive_commit_message(heading) == "J.m.1: v0.70.0 Follow-up after J.m"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_derive_commit_message_round_trip_with_cli_subject_regex():
|
|
128
|
+
"""The commit-subject already-committed check (cli._COMMIT_SUBJECT_STORY_ID_RE)
|
|
129
|
+
must recognize the sub-numbered ID it would produce from our own
|
|
130
|
+
`derive_commit_message`. Without this, a J.m.1 commit subject would
|
|
131
|
+
not register in the "committed" set, breaking the wrapper's
|
|
132
|
+
duplicate-detection path even after the stories.md side is fixed.
|
|
133
|
+
"""
|
|
134
|
+
from project_guide.cli import _COMMIT_SUBJECT_STORY_ID_RE
|
|
135
|
+
|
|
136
|
+
subject = derive_commit_message(
|
|
137
|
+
StoryHeading(story_id="J.m.1", title="v0.70.0 Follow-up after J.m")
|
|
138
|
+
)
|
|
139
|
+
m = _COMMIT_SUBJECT_STORY_ID_RE.match(subject)
|
|
140
|
+
assert m is not None, f"regex failed to match subject: {subject!r}"
|
|
141
|
+
assert m.group(1) == "J.m.1"
|
|
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
|
{project_guide-2.7.1 → project_guide-2.7.2}/docs/site/images/project-guide-banner-landing.png
RENAMED
|
File without changes
|
{project_guide-2.7.1 → project_guide-2.7.2}/docs/site/images/project-guide-header-readme.png
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
|
{project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/phase-k-release-lifecycle-plan.md
RENAMED
|
File without changes
|
{project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/phase-l-no-input-init-plan.md
RENAMED
|
File without changes
|
{project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/phase-m-project-essentials-plan.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{project_guide-2.7.1 → project_guide-2.7.2}/docs/specs/.archive/project-guide-no-input-spec.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
|
{project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/.project-guide.yml.template
RENAMED
|
File without changes
|
{project_guide-2.7.1 → project_guide-2.7.2}/project_guide/templates/project-guide/.metadata.yml
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
|
|
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
|
|
File without changes
|
|
File without changes
|