Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
322 commits
Select commit Hold shift + click to select a range
bc71080
fix: address review feedback in init and boundary tests
djm81 Feb 6, 2026
e5d7002
Fix test setup for tmpfiles
djm81 Feb 6, 2026
c97080b
Merge branch 'main' into dev
djm81 Feb 6, 2026
46b1357
docs: add openspec change arch-05 bridge registry (#210)
djm81 Feb 8, 2026
20410a6
docs: add openspec change arch-06 manifest security (#211)
djm81 Feb 8, 2026
04aba4b
feat: Core Contracts and Module Interface Formalization (#209)
djm81 Feb 8, 2026
0f492f6
Update specs and archive arch-04 change
djm81 Feb 8, 2026
7656d7c
Fix changelog format
djm81 Feb 8, 2026
b826fb6
docs: align arch-05 scope with protocol migration cleanup (#212)
djm81 Feb 8, 2026
c8ec9bb
docs: add OpenSpec changes for module marketplace phases
djm81 Feb 9, 2026
b6e1fe1
feat: apply arch-05 bridge registry implementation (#216)
djm81 Feb 10, 2026
f10e14e
Archive arch-05 change after implementation
djm81 Feb 10, 2026
285a9ff
Merge branch 'main' into dev
djm81 Feb 10, 2026
5075d0c
fix: address post-merge review findings and restore lazy registration
djm81 Feb 10, 2026
31e6924
fix: refresh project console per invocation in tests
djm81 Feb 10, 2026
d7ca6af
fix: detect runtime interface protocol ops in source scan
djm81 Feb 10, 2026
0e5463f
fix: harden project console refresh and protocol source scan
djm81 Feb 10, 2026
57c729b
fix: refresh import command console for each invocation
djm81 Feb 10, 2026
e270e09
fix: resolve type-check errors and harden protocol scanning
djm81 Feb 10, 2026
7cc1c48
fix: stabilize module protocol scan and project console lifecycle
djm81 Feb 10, 2026
5996a1d
Merge branch 'main' into dev
djm81 Feb 10, 2026
81527a5
Refine pending changes for new modular ecosystem and marketplace inte…
djm81 Feb 10, 2026
6fd151a
fix: update stale spec-delta paths in tasks.md after change renames
djm81 Feb 10, 2026
22640fa
docs: rewrite CHANGE_ORDER.md for module-scoped changes and full depe…
djm81 Feb 10, 2026
e7d69d6
Merge branch 'main' into dev
djm81 Feb 10, 2026
9a7f9f6
feat(backlog): daily/refine comment context, interactive standup post…
djm81 Feb 11, 2026
e61d56b
fix(backlog): satisfy interactive daily adapter typing
djm81 Feb 11, 2026
fff65e9
fix(backlog): bypass default daily limit for issue-window flags
djm81 Feb 11, 2026
b2a8ab5
Merge branch 'main' into dev
djm81 Feb 11, 2026
88ea93c
fix: parse backlog refine writeback fields and refactor refine comman…
djm81 Feb 12, 2026
f3c5759
fix: avoid raw label fallback when description block is missing
djm81 Feb 12, 2026
f9f2fcc
Merge branch 'main' into dev
djm81 Feb 12, 2026
1870cfe
fix: harden backlog refine prompt scaffold and mixed-format parsing (…
djm81 Feb 12, 2026
16db526
Finish change
djm81 Feb 12, 2026
a3e8ff2
chore: bump version to 0.30.4 and update changelog
djm81 Feb 12, 2026
1bdcbc9
Merge branch 'main' into dev
djm81 Feb 12, 2026
998ab59
Add MEMORY.md for claude code
djm81 Feb 12, 2026
bedf75c
Archive backlog writeback field split change
djm81 Feb 12, 2026
6e66806
Archived flask support sidecar change
djm81 Feb 12, 2026
7d6c550
feat: add backlog-core module β€” dependency analysis and command suite…
djm81 Feb 13, 2026
4d1f950
Merge branch 'main' into dev
djm81 Feb 13, 2026
47ea3f4
fix(backlog-core): remove unused module io contract global
djm81 Feb 13, 2026
2509412
Fixed conflict
djm81 Feb 14, 2026
d8d17a5
fix: rename LICENSE.md to LICENSE for GitHub license detection (#233)
djm81 Feb 14, 2026
100c94f
fix: restore standard Apache 2.0 license text for GitHub detection (#…
djm81 Feb 14, 2026
002cf19
Merge branch 'main' into dev
djm81 Feb 14, 2026
ee0b766
Add openspec changes for architecture level enhancement
djm81 Feb 15, 2026
540c96a
Merge branch 'dev' of https://github.com/nold-ai/specfact-cli into dev
djm81 Feb 15, 2026
ea430ab
Merge branch 'main' into dev
djm81 Feb 15, 2026
6dc73ca
Merge branch 'main' into dev
djm81 Feb 16, 2026
d621e91
Merge branch 'dev' of https://github.com/nold-ai/specfact-cli into dev
djm81 Feb 16, 2026
1b06427
feat(ci): attach test and repro log artifacts to PR orchestrator runs…
djm81 Feb 16, 2026
0bc52a5
feat: enhanced module manifest security and integrity (arch-06) (#263)
djm81 Feb 16, 2026
3f09ee7
feat: Schema Extension System for Modular ProjectBundle Extensions (a…
djm81 Feb 16, 2026
e959e61
Merge branch 'main' into dev
djm81 Feb 16, 2026
ba08744
Fix codeql findings
djm81 Feb 16, 2026
a3a3b6a
Merge branch 'dev' of https://github.com/nold-ai/specfact-cli into dev
djm81 Feb 16, 2026
87a499f
Merge branch 'main' into dev
djm81 Feb 17, 2026
faf3b2e
feat(workflow): standardize worktree-first development flow (#268)
djm81 Feb 17, 2026
5c72c9f
Fix review finding
djm81 Feb 17, 2026
c51e96e
Merge branch 'main' into dev
djm81 Feb 17, 2026
deb176a
feat: implement policy-engine-01 unified policy framework (#270)
djm81 Feb 18, 2026
22a19a9
fix: harden policy module imports and snapshot path resolution
djm81 Feb 18, 2026
2724dbd
Merge branch 'main' into dev
djm81 Feb 18, 2026
1970266
Update backlog core change to cover workspace level modules
djm81 Feb 18, 2026
806f765
feat(init): align init module discovery with registry (backlog-core-0…
djm81 Feb 18, 2026
87dafba
feat: add thorough codebase validation (validation-01, #163) (#272)
djm81 Feb 18, 2026
1aac44f
feat: add patch apply (local + --write with confirmation) [#177] (#273)
djm81 Feb 18, 2026
d835ffb
feat: add bundle-mapper module (bundle-mapper-01, #121) (#274)
djm81 Feb 18, 2026
85576de
Archive finished changes
djm81 Feb 18, 2026
94a8ed1
fix: implement verification-01 wave1 delta closure (#277)
djm81 Feb 18, 2026
0f4a77c
Archive delta validation change and update specs
djm81 Feb 18, 2026
e2c93d1
Update patch version
djm81 Feb 18, 2026
c1c41f5
Potential fix for pull request finding 'Empty except'
djm81 Feb 19, 2026
27063ba
apply review fixes
djm81 Feb 19, 2026
6350aa8
Merge branch 'main' into dev
djm81 Feb 19, 2026
767d5b8
Add cli validation changes
djm81 Feb 19, 2026
3f4e1a3
Merge branch 'main' into dev
djm81 Feb 19, 2026
4f4b2ff
feat: launch central module marketplace lifecycle (#287)
djm81 Feb 21, 2026
78ddf06
Merge branch 'main' into dev
djm81 Feb 21, 2026
6772f0a
fix: resolve bundle-mapper review defects with TDD evidence (#290)
djm81 Feb 22, 2026
4529df1
feat:Add architecture review docs and findings to mitigate
djm81 Feb 22, 2026
cc50388
Merge branch 'dev' of https://github.com/nold-ai/specfact-cli into dev
djm81 Feb 22, 2026
53a0fce
feat(backlog): add backlog add for interactive issue creation (#289)
djm81 Feb 22, 2026
c651c42
chore(openspec): archive completed changes and align architecture doc…
djm81 Feb 22, 2026
d7e8199
docs(change): Archive architecture discrepancy remediation change
djm81 Feb 22, 2026
aa4a9fb
fix(codeql): preserve module contract marker and document fallback ex…
djm81 Feb 22, 2026
176cd09
Merge branch 'main' into dev
djm81 Feb 22, 2026
30e2bbe
fix(backlog): restore installed-runtime discovery parity and add back…
djm81 Feb 23, 2026
9f479e4
fix(version): sync manifests to 0.36.1 and archive backlog-core-04 (#…
djm81 Feb 23, 2026
37a4a30
Merge branch 'main' into dev
djm81 Feb 23, 2026
991d568
Merge branch 'dev' of https://github.com/nold-ai/specfact-cli into dev
djm81 Feb 23, 2026
15194b8
fix: harden module lifecycle bootstrap and signing workflows (#299)
djm81 Feb 24, 2026
a819852
test: simplify monkeypatch callables in module_security tests
djm81 Feb 24, 2026
67cd7e9
Merge branch 'main' into dev
djm81 Feb 24, 2026
078012f
Fix duplicate with statement
djm81 Feb 24, 2026
fa0604a
Merge branch 'main' into dev
djm81 Feb 24, 2026
c3f5bb6
chore(release): bump to v0.37.1 and harden signature gates
djm81 Feb 24, 2026
c0692e9
test: fix init command regression assertions
djm81 Feb 24, 2026
7787ba7
Merge branch 'main' into dev
djm81 Feb 24, 2026
db8d153
fix: release v0.37.2 with runtime crypto deps
djm81 Feb 24, 2026
cce4cf7
fix: address signature-backend warning and module version drift
djm81 Feb 24, 2026
297fb6c
fix: use hatch build in PyPI publish workflow script (#304)
djm81 Feb 24, 2026
e63669c
Merge branch 'main' into dev
djm81 Feb 24, 2026
dad2381
fix: resolve startup module freshness home path dynamically (#306)
djm81 Feb 24, 2026
0e1ac55
Merge branch 'main' into dev
djm81 Feb 24, 2026
764e5c9
Merge branch 'main' into dev
djm81 Feb 24, 2026
fa8708c
Merge branch 'dev' of https://github.com/nold-ai/specfact-cli into dev
djm81 Feb 24, 2026
5d90ced
fix: harden module signing workflow and reduce startup log noise
djm81 Feb 24, 2026
276830e
test: align module migration compatibility with decoupled module vers…
djm81 Feb 24, 2026
effd96f
fix: fail fast on invalid base ref in changed-only module signing
djm81 Feb 24, 2026
798fc1a
fix: stabilize module precedence and backlog github mapping flow
djm81 Feb 25, 2026
f72d6e8
merge: resolve main into dev and retain module/backlog updates
djm81 Feb 25, 2026
c6a05f5
fix(module-registry): persist disables and correct bundled availability
djm81 Feb 25, 2026
c9db837
Re-sign module registry and fix / ignore local temp artifacts
djm81 Feb 25, 2026
db4da83
bump module registry version to 0.1.3
djm81 Feb 25, 2026
48ae89b
fix(registry): restore protocol reporting logs in debug mode
djm81 Feb 25, 2026
c87c2d0
Merge branch 'main' into dev
djm81 Feb 25, 2026
619f2a3
fix(backlog): harden refine writeback, prompts, and any-filter semant…
djm81 Feb 25, 2026
818dd32
fix(hooks,ado): correct format gate and enforce iteration on direct
djm81 Feb 25, 2026
954ff76
Apply review findings and fix tests
djm81 Feb 26, 2026
9825acd
Merge branch 'main' into dev
djm81 Feb 26, 2026
27b4078
Pin virtualenv < 21 to avoid incaopatibility failure
djm81 Feb 26, 2026
f9e9d74
Merge branch 'main' into dev
djm81 Feb 26, 2026
567a303
fix: finalize backlog-core-06 ado comment API versioning (#314)
djm81 Feb 26, 2026
a95e89f
feat: Advanced marketplace features (marketplace-02) - dependency res…
djm81 Feb 27, 2026
4a0620a
Merge branch 'main' into dev
djm81 Feb 27, 2026
c5635c9
fix: complete marketplace publish registry PR flow and bump (#320)
djm81 Feb 27, 2026
4ec9bdc
Merge branch 'main' into dev
djm81 Feb 27, 2026
f48631d
fix: update init ide hint and repair publish workflow
djm81 Feb 27, 2026
37d8475
Merge branch 'main' into dev
djm81 Feb 27, 2026
8be301e
feat(backlog): normalize daily summarize Markdown output (#323)
djm81 Feb 27, 2026
a6dcab0
Update version
djm81 Feb 27, 2026
0b99c6c
Merge branch 'dev' of https://github.com/nold-ai/specfact-cli into dev
djm81 Feb 27, 2026
073ae36
Merge branch 'main' into dev
djm81 Feb 27, 2026
2f13173
Add github skills
djm81 Feb 27, 2026
1e743b5
Add new marketplace changes
djm81 Feb 27, 2026
da80932
feat(cli): category groups and flat shims using real module Typer (#331)
djm81 Feb 28, 2026
c480e19
Update docs regarding module migration change
djm81 Feb 28, 2026
039da8b
feat: module-migration-02 bundle extraction (#332)
djm81 Mar 2, 2026
f4ae6b0
Implement blockers to prepare for module-migration-03 change. (#336)
djm81 Mar 2, 2026
efadc08
feat: module-migration-03 core slimming closeout and registry fixes (…
djm81 Mar 3, 2026
aeeaab0
Finalize module-migration-02 change
djm81 Mar 3, 2026
e1a90be
docs(backlog-auth): update auth docs and OpenSpec task status (#342)
djm81 Mar 3, 2026
d7d7c68
chore(openspec): archive completed changes and sync main specs
djm81 Mar 3, 2026
27ef25a
docs(openspec): prefix module migration proposal titles with IDs
djm81 Mar 3, 2026
594b0fa
Add bug change for ado required fields setting and update change order
djm81 Mar 4, 2026
10c1d6c
Update change order
djm81 Mar 4, 2026
f86b516
feat(core): finalize migration-03 auth removal and 3-core slim packag…
djm81 Mar 4, 2026
8b0e0d8
Archive module-migration-03 change
djm81 Mar 4, 2026
b1e9245
feat: remove flat command shims (category-only CLI) (#344)
djm81 Mar 4, 2026
5f9b537
Archived module-migration-04 and updated specs
djm81 Mar 4, 2026
ebb6e61
docs(openspec): finalize module-migration-05 tracking after modules P…
djm81 Mar 4, 2026
29df29c
Archive module-migration-05 change and update specs
djm81 Mar 4, 2026
1811f68
test(migration-06): move legacy sync tests out of core (#346)
djm81 Mar 5, 2026
61352f7
Archived module-migration-06 change and updated specs
djm81 Mar 5, 2026
9483fea
test: module-migration-07 core test ownership cleanup (#347)
djm81 Mar 5, 2026
c48fadb
Archived backlog-core-07 change and updated specs
djm81 Mar 5, 2026
2dd42f8
Update some docs and archive latest finished changes and specs
djm81 Mar 5, 2026
1e11e0a
Add docs update change
djm81 Mar 5, 2026
1578c6c
feat: add agile-01-feature-hierarchy change and update CHANGE_ORDER.m…
djm81 Mar 5, 2026
32c6fc1
docs: align core docs and sync pending changes (#377)
djm81 Mar 5, 2026
6b518b6
fix: stabilize release test suite after module migration
djm81 Mar 6, 2026
39409c5
Update module
djm81 Mar 6, 2026
675277f
Merge origin/main into dev for v0.40.0 release
djm81 Mar 6, 2026
acb3f88
Fix module install
djm81 Mar 6, 2026
04e96c5
Fix module install
djm81 Mar 6, 2026
4616064
Fix failed tests
djm81 Mar 6, 2026
53ed3a0
Merge branch 'main' into dev
djm81 Mar 6, 2026
e1a239e
Fix marketplace client regression
djm81 Mar 6, 2026
23ef66c
Merge branch 'main' into dev
djm81 Mar 6, 2026
d578657
Fix install regression for specfact-cli (#380)
djm81 Mar 6, 2026
5c8b661
Merge branch 'main' into dev
djm81 Mar 6, 2026
3de7b1c
Merge branch 'dev' of https://github.com/nold-ai/specfact-cli into dev
djm81 Mar 6, 2026
40e9695
Add changes to improve runtime validation and backlog module remainin…
djm81 Mar 6, 2026
978cc82
refactor: remove backlog ownership from core cli (#384)
djm81 Mar 6, 2026
00658a3
Add new command alignment change
djm81 Mar 6, 2026
f8e5af1
Merge branch 'dev' of https://github.com/nold-ai/specfact-cli into dev
djm81 Mar 6, 2026
08f028a
Merge branch 'main' into dev
djm81 Mar 6, 2026
332949e
fix: finalize cli runtime validation regressions (#387)
djm81 Mar 9, 2026
2a4a0ab
docs: archive cli-val-07 change
djm81 Mar 9, 2026
d031281
Archive changes and update specs
djm81 Mar 9, 2026
d21e157
Merge branch 'main' into dev
djm81 Mar 9, 2026
216f03f
Add code-review change proposals
djm81 Mar 10, 2026
e979938
test: align command surface regression coverage
djm81 Mar 10, 2026
9612cff
docs: add OpenSpec change for backlog-core commands migration (#390)
djm81 Mar 10, 2026
bd5badd
fix: use POST instead of PATCH for ADO work item creation (#391)
djm81 Mar 11, 2026
0448022
docs: archive backlog-02-migrate-core-commands change
djm81 Mar 11, 2026
e91e672
feat: document code-review module scaffold (#410)
djm81 Mar 11, 2026
aa494ff
Add change for project codebase ownership
djm81 Mar 11, 2026
b62292c
Merge branch 'main' into dev
djm81 Mar 11, 2026
67aaf31
Realign code import ownership surface (#412)
djm81 Mar 12, 2026
1f4d236
Update code review changes
djm81 Mar 16, 2026
516b4c7
docs: update reward ledger OpenSpec tracking (#413)
djm81 Mar 16, 2026
8579fe5
Track house-rules skill OpenSpec changes (#414)
djm81 Mar 16, 2026
ff8d041
docs: Update change-proposal for code-review-07 (#415)
djm81 Mar 16, 2026
b524f3a
Finalize code-review-07 status
djm81 Mar 16, 2026
097d8ef
Finalize code-review-08 status
djm81 Mar 16, 2026
37efea0
feat: apply code-review-09 pre-commit integration
djm81 Mar 17, 2026
fa503da
fix: fall back when cached hatch test env is broken
djm81 Mar 17, 2026
6e828e7
fix: avoid hatch env for coverage xml export
djm81 Mar 17, 2026
0aa178f
fix: install type-check and lint tools directly in CI
djm81 Mar 17, 2026
9f6a749
fix: install pytest fallback deps in test job
djm81 Mar 17, 2026
c451134
fix: install pytest-cov for test fallback path
djm81 Mar 17, 2026
f328223
Merge branch 'main' into dev
djm81 Mar 17, 2026
9146118
Merge branch 'main' into dev
djm81 Mar 17, 2026
0aa017d
Finalize code-review-09 status
djm81 Mar 17, 2026
f067288
[Change] Align core docs with modules site ownership (#419)
djm81 Mar 17, 2026
e456d46
fix: harden docs parity URL assertions
djm81 Mar 17, 2026
41052e6
Archive finished changes and update specs
djm81 Mar 17, 2026
05e81bb
Merge branch 'main' into dev
djm81 Mar 17, 2026
0648162
docs: fix command syntax parity after lean-core/modules split (v0.42.…
djm81 Mar 17, 2026
a6978a2
Archive finished changes and update specs
djm81 Mar 17, 2026
0273f1a
Update evidence
djm81 Mar 17, 2026
8ec2c11
Potential fix for pull request finding 'Unused global variable'
djm81 Mar 17, 2026
7d63dd8
Merge branch 'main' into dev
djm81 Mar 18, 2026
a77e5fd
docs: align core docs ownership and parity (#424)
djm81 Mar 20, 2026
664d4e4
docs: fix quickstart install guidance
djm81 Mar 20, 2026
09688de
docs: remove generated project plan docs
djm81 Mar 20, 2026
f7cca1e
Merge branch 'main' into dev
djm81 Mar 20, 2026
f4e91be
Add code-review change
djm81 Mar 20, 2026
37dfecb
fix: preserve native backlog import payloads (#429)
djm81 Mar 20, 2026
b54aaa6
fix: add docs review workflow and repair docs links (#428)
djm81 Mar 20, 2026
ae7f05c
fix: keep imported change ids stable across title changes (#431)
djm81 Mar 20, 2026
7449714
Merge branch 'main' into dev
djm81 Mar 20, 2026
65726fb
fix: remove conflicting pages file copies
djm81 Mar 20, 2026
fbb3b83
Merge branch 'main' into dev
djm81 Mar 20, 2026
c6c47fc
Add docs sync changs
djm81 Mar 20, 2026
eaa87ac
docs: update openspec clean-code planning
djm81 Mar 22, 2026
f602dba
Update change status
djm81 Mar 22, 2026
58314e5
fix: code-review-zero-findings dogfood remediation (v0.42.3) (#435)
djm81 Mar 23, 2026
fb3c5fd
Merge branch 'main' into dev
djm81 Mar 23, 2026
2809390
Add docs refactoring changes
djm81 Mar 23, 2026
215df59
Add bug change tracking for encoding and resources
djm81 Mar 24, 2026
2a60f15
docs: restructure core site IA to 6-section progressive nav (#442)
djm81 Mar 24, 2026
81bca26
fix: harden cross-platform runtime and IDE resource discovery (#443)
djm81 Mar 24, 2026
852f446
fix: resolve review type-safety findings
djm81 Mar 24, 2026
2bbde33
Merge branch 'main' into dev
djm81 Mar 24, 2026
8272233
Improve clarity and scope of ide prompt change
djm81 Mar 25, 2026
2f0675c
feat(init): IDE prompt source catalog, --prompts, namespaced exports …
djm81 Mar 25, 2026
278142e
fix tests
djm81 Mar 25, 2026
38a9d21
release: bump version to 0.42.5 and update CHANGELOG
djm81 Mar 25, 2026
f552107
Fix review findings
djm81 Mar 25, 2026
dab2ffe
feat(init): selective IDE prompt export cleanup and VS Code recommend…
djm81 Mar 25, 2026
fbb2307
Fix review findings
djm81 Mar 25, 2026
71e760b
Merge branch 'main' into dev
djm81 Mar 25, 2026
41dc0db
Add missing import
djm81 Mar 25, 2026
2b26098
Bump patch version and changelog
djm81 Mar 25, 2026
90da7da
Fix failed tests
djm81 Mar 25, 2026
1eca7a9
Fix review findings
djm81 Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ All notable changes to this project will be documented in this file.

---

## [0.42.6] - 2026-03-26

### Fixed

- `specfact init ide` multi-source export writes prompts to a **flat** layout under the IDE export root (for example `.github/prompts/` or `.cursor/commands/`) so editors and agents can discover `specfact*.prompt.md` (or equivalent) without per-source subfolders.
- Prompt catalog: **core** omits template basenames already provided by an installed module, avoiding duplicate exports when both ship the same filename.
- Re-export removes legacy per-source segment directories and prunes stale flat `specfact*` exports when the selected sources change.
- Tests: import `pytest` for `MonkeyPatch` annotations in init IDE prompt selection tests (Ruff F821).

---

## [0.42.5] - 2026-03-25

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@

- Command: `hatch run pytest tests/unit/modules/init/test_init_ide_prompt_selection.py tests/unit/utils/test_ide_setup.py tests/e2e/test_init_command.py -q`
- Status: green after `ide_setup` catalog + namespaced copy, `init ide` wiring, startup_checks rglob, and e2e path updates.

## Follow-up: flat export + core/module dedupe (2026-03-26)

- **Change:** Multi-source export uses a flat IDE folder; core omits template basenames covered by any module; legacy per-source subfolders are removed on export.
- **Tests:** `hatch test tests/unit/utils/test_ide_setup.py tests/unit/modules/init/test_init_ide_prompt_selection.py -v` β€” all passed.
- **Contract:** `hatch run contract-test` β€” PASS.
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,16 @@ Non-interactive `specfact init ide` SHALL accept a comma-separated prompt source
- **WHEN** a user passes a prompt source token that is not `all`, not `core`, and not an installed module id with prompt resources
- **THEN** the command fails with actionable guidance describing the invalid token and the available prompt sources.

### Requirement: Exported Prompt Files Must Preserve Source Provenance
### Requirement: Exported Prompt Files Must Be IDE-Discoverable With Deterministic Single Sourcing

Exported prompt files SHALL preserve module/core provenance so collisions are deterministic and later command-surface migrations do not silently overwrite unrelated prompts.
Exported prompts for VS Code / Copilot (under ``.github/prompts/``) and other multi-source IDE targets SHALL use a **flat** layout (no per-source subfolders) so editors and agents can discover ``specfact*.prompt.md`` (or equivalent) at the export root.

#### Scenario: Multiple sources expose similarly named prompts
- **WHEN** `core` and one or more installed modules expose prompt files with overlapping basenames or command affinity
- **THEN** the exported IDE-facing output preserves which source owns each prompt
- **AND** the collision outcome is deterministic and visible to the user.
#### Scenario: Core defers to modules on overlapping template basenames
- **WHEN** `core` and one or more installed modules expose the same source filename (e.g. ``specfact.01-import.md``)
- **THEN** the prompt catalog SHALL list that basename only under the owning module source
- **AND** `core` SHALL NOT duplicate that basename so exports are single-sourced.

#### Scenario: Multiple module sources expose the same basename
- **WHEN** two installed modules expose the same template basename
- **THEN** the merged export uses a deterministic last-wins rule by sorted source id (later id overwrites earlier)
- **AND** the flat export contains exactly one file per output basename.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "specfact-cli"
version = "0.42.5"
version = "0.42.6"
description = "The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with validation and contract enforcement for new projects and long-lived codebases."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
if __name__ == "__main__":
_setup = setup(
name="specfact-cli",
version="0.42.5",
version="0.42.6",
description=(
"The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with "
"validation and contract enforcement for new projects and long-lived codebases."
Expand Down
2 changes: 1 addition & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
"""

# Package version: keep in sync with pyproject.toml, setup.py, src/specfact_cli/__init__.py
__version__ = "0.42.5"
__version__ = "0.42.6"
2 changes: 1 addition & 1 deletion src/specfact_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ def _bootstrap_bundle_paths() -> None:

_bootstrap_bundle_paths()

__version__ = "0.42.5"
__version__ = "0.42.6"

__all__ = ["__version__"]
6 changes: 3 additions & 3 deletions src/specfact_cli/modules/init/module-package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: init
version: 0.1.17
version: 0.1.18
commands:
- init
category: core
Expand All @@ -17,5 +17,5 @@ publisher:
description: Initialize SpecFact workspace and bootstrap local configuration.
license: Apache-2.0
integrity:
checksum: sha256:589ec7f24e7d0129cf61ad972e9f505c744ae14bd84db32cc241bc5013834163
signature: 9hiFgBYZZwCTMBRDBgly07v5JejhDHWXyX3/tTkTYOjjtkFT7xjZs2Pyea7l1mt2dhUZ/Tet4bdDRXyeSNcYCg==
checksum: sha256:218801ddd11b02e90e386a3019685add0d14a9a09d246ef958c05f53c9b46a72
signature: o9QdwF5+ASt8dJy5D38PgMy7pysqZUJqOaDHHjcRPXoI1dGpeSyOKjJOOtboqoH9qN2a+4nhZX6S5NGp8hlPCA==
2 changes: 1 addition & 1 deletion src/specfact_cli/modules/init/src/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def copy_templates_to_ide(
*,
prompts_by_source: dict[str, list[Path]] | None = None,
) -> tuple[list[Path], Path | None]:
"""Discover prompt templates and copy them; use ``prompts_by_source`` for namespaced multi-source export."""
"""Discover prompt templates and copy them; use ``prompts_by_source`` for multi-source flat export."""
if prompts_by_source is not None:
return copy_prompts_by_source_to_ide(repo_path, ide, prompts_by_source, force)
return _copy_template_files_to_ide(repo_path, ide, discover_prompt_template_files(repo_path), force)
Expand Down
194 changes: 96 additions & 98 deletions src/specfact_cli/utils/ide_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,18 @@ def _core_prompt_template_paths(repo_path: Path, include_package_fallback: bool)
return []


def _core_prompts_excluding_module_basenames(
core_files: list[Path],
module_catalog: dict[str, list[Path]],
) -> list[Path]:
"""Drop core templates whose basename is already provided by an installed module (single source of truth)."""
module_basenames: set[str] = set()
for paths in module_catalog.values():
for p in paths:
module_basenames.add(p.name)
return [p for p in core_files if p.name not in module_basenames]


def _module_prompt_sources_catalog(repo_path: Path) -> dict[str, list[Path]]:
from specfact_cli.registry.module_packages import CORE_MODULE_ORDER, discover_package_metadata

Expand Down Expand Up @@ -249,12 +261,17 @@ def discover_prompt_sources_catalog(

Core templates come from the repo checkout or the installed ``specfact_cli`` package. Module templates
are discovered from effective module roots (builtin, project, user, marketplace, custom).

When a module ships a template with the same source filename as core (e.g. ``specfact.01-import.md``),
the module copy wins: core does not list that basename so exports stay single-sourced.
"""
catalog: dict[str, list[Path]] = {}
module_catalog = _module_prompt_sources_catalog(repo_path)
core_files = _core_prompt_template_paths(repo_path, include_package_fallback)
if core_files:
catalog[PROMPT_SOURCE_CORE] = list(core_files)
catalog.update(_module_prompt_sources_catalog(repo_path))
core_filtered = _core_prompts_excluding_module_basenames(core_files, module_catalog)
catalog: dict[str, list[Path]] = {}
if core_filtered:
catalog[PROMPT_SOURCE_CORE] = list(core_filtered)
catalog.update(module_catalog)
return catalog


Expand Down Expand Up @@ -378,71 +395,75 @@ def _output_filename_for_template(template_path: Path, format_type: str) -> str:
return template_path.name


def _safe_resolved_segment_dir(repo_path: Path, ide: str, segment: str) -> Path | None:
"""Return ``repo_path / ide_folder / segment`` resolved, or ``None`` if it escapes the IDE export root."""
config = IDE_CONFIG[ide]
base = (repo_path / str(config["folder"])).resolve()
segment_dir = (base / segment).resolve()
try:
segment_dir.relative_to(base)
except ValueError:
return None
return segment_dir
def _merge_prompt_export_outputs_by_basename(
prompts_by_source: dict[str, list[Path]],
format_type: str,
) -> dict[str, Path]:
"""Map IDE output filename to source template; later sources win over core on basename collisions."""
ordered = sorted(
prompts_by_source.items(),
key=lambda item: (item[0] != PROMPT_SOURCE_CORE, item[0]),
)
out: dict[str, Path] = {}
for _source_id, paths in ordered:
for p in paths:
out[_output_filename_for_template(p, format_type)] = p
return out


def _prune_segment_exports_not_in_expected(
repo_path: Path,
ide: str,
segment: str,
template_paths: list[Path],
) -> None:
"""Remove files under ``ide_folder/segment`` that are not part of this export (same filenames as copy)."""
if not template_paths:
return
def _cleanup_legacy_multisource_segment_dirs(repo_path: Path, ide: str) -> None:
"""Remove per-source subfolders from older multi-source exports (layout is now flat under the IDE root)."""
config = IDE_CONFIG[ide]
format_type = str(config["format"])
segment_dir = _safe_resolved_segment_dir(repo_path, ide, segment)
if segment_dir is None or not segment_dir.is_dir():
base = (repo_path / str(config["folder"])).resolve()
if not base.is_dir():
return
expected_resolved: set[Path] = {
(segment_dir / _output_filename_for_template(tp, format_type)).resolve() for tp in template_paths
}
for p in list(segment_dir.iterdir()):
if not p.is_file():
for child in list(base.iterdir()):
if not child.is_dir():
continue
if p.resolve() not in expected_resolved:
try:
p.unlink()
console.print(f"[dim]Removed stale prompt export:[/dim] {p}")
except OSError as exc:
console.print(f"[yellow]Could not remove stale export {p}:[/yellow] {exc}")
if child.name != PROMPT_SOURCE_CORE and "__" not in child.name:
continue
try:
child.resolve().relative_to(base)
except ValueError:
continue
try:
shutil.rmtree(child)
console.print(f"[dim]Removed legacy prompt export segment:[/dim] {child}")
except OSError as exc:
console.print(f"[yellow]Could not remove legacy segment {child}:[/yellow] {exc}")


def _flat_export_glob_pattern_for_prune(format_type: str) -> str:
"""Glob for SpecFact-managed flat exports; must stay aligned with ``_output_filename_for_template``."""
if format_type == "prompt.md":
return "specfact*.prompt.md"
if format_type == "toml":
return "specfact*.toml"
return "specfact*.md"
Comment thread
djm81 marked this conversation as resolved.


def _remove_unselected_prompt_export_segments(
def _prune_flat_specfact_exports_not_in_expected(
repo_path: Path,
ide: str,
prompts_by_source: dict[str, list[Path]],
expected_output_names: set[str],
) -> None:
"""Remove on-disk segment directories under the IDE export root that are not in this selective export."""
"""Remove prior flat ``specfact*`` exports that are not part of this merged export."""
config = IDE_CONFIG[ide]
format_type = str(config["format"])
base = (repo_path / str(config["folder"])).resolve()
selected_segments = {source_id_to_path_segment(sid) for sid in prompts_by_source}
if not base.is_dir():
return
for child in list(base.iterdir()):
if not child.is_dir():
continue
try:
child.resolve().relative_to(base)
except ValueError:
pattern = _flat_export_glob_pattern_for_prune(format_type)
for p in base.glob(pattern):
if not p.is_file():
continue
if child.name in selected_segments:
if p.name in expected_output_names:
continue
try:
shutil.rmtree(child)
console.print(f"[dim]Removed unselected export segment:[/dim] {child}")
p.unlink()
console.print(f"[dim]Removed stale prompt export:[/dim] {p}")
except OSError as exc:
console.print(f"[yellow]Could not remove segment {child}:[/yellow] {exc}")
console.print(f"[yellow]Could not remove stale export {p}:[/yellow] {exc}")


def _copy_template_files_to_ide(
Expand Down Expand Up @@ -507,7 +528,7 @@ def expected_ide_prompt_export_paths(
*,
prompt_source_ids: frozenset[str] | None = None,
) -> list[Path]:
"""Return expected on-disk paths for exported IDE prompts (source-namespaced layout).
"""Return expected on-disk paths for exported IDE prompts (flat layout under the IDE export folder).

If ``prompt_source_ids`` is set (from ``.specfact/ide-prompt-export.yaml``), only those sources are
expectedβ€”matching a selective ``init ide --prompts`` run. Otherwise the full discovered catalog is used.
Expand All @@ -518,12 +539,8 @@ def expected_ide_prompt_export_paths(
catalog = discover_prompt_sources_catalog(repo_path)
if prompt_source_ids is not None:
catalog = {k: v for k, v in catalog.items() if k in prompt_source_ids}
paths: list[Path] = []
for sid, templates in sorted(catalog.items(), key=lambda item: (item[0] != PROMPT_SOURCE_CORE, item[0])):
segment = source_id_to_path_segment(sid)
for template_path in templates:
paths.append(base / segment / _output_filename_for_template(template_path, format_type))
return paths
merged = _merge_prompt_export_outputs_by_basename(catalog, format_type)
return [base / name for name in sorted(merged.keys())]


@beartype
Expand Down Expand Up @@ -551,13 +568,12 @@ def count_outdated_ide_prompt_exports(
catalog = discover_prompt_sources_catalog(repo_path)
if prompt_source_ids is not None:
catalog = {k: v for k, v in catalog.items() if k in prompt_source_ids}
merged = _merge_prompt_export_outputs_by_basename(catalog, format_type)
outdated = 0
for sid, paths in catalog.items():
segment = source_id_to_path_segment(sid)
for src in paths:
dest = base / segment / _output_filename_for_template(src, format_type)
if src.exists() and dest.exists() and dest.stat().st_mtime < src.stat().st_mtime:
outdated += 1
for dest_name, src in merged.items():
dest = base / dest_name
if src.exists() and dest.exists() and dest.stat().st_mtime < src.stat().st_mtime:
outdated += 1
return outdated


Expand All @@ -581,25 +597,18 @@ def copy_prompts_by_source_to_ide(
prompts_by_source: dict[str, list[Path]],
force: bool = False,
) -> tuple[list[Path], Path | None]:
"""Copy prompts grouped by source id into source-namespaced subfolders under the IDE export directory."""
all_copied: list[Path] = []
_remove_unselected_prompt_export_segments(repo_path, ide, prompts_by_source)
ordered = sorted(
prompts_by_source.items(),
key=lambda item: (item[0] != PROMPT_SOURCE_CORE, item[0]),
"""Copy prompts from multiple sources into a single flat folder under the IDE export directory."""
config = IDE_CONFIG[ide]
format_type = str(config["format"])
_cleanup_legacy_multisource_segment_dirs(repo_path, ide)
merged = _merge_prompt_export_outputs_by_basename(prompts_by_source, format_type)
_prune_flat_specfact_exports_not_in_expected(repo_path, ide, set(merged.keys()))
template_list = list(merged.values())
all_copied, _settings = _copy_template_files_to_ide(
repo_path, ide, template_list, force, source_segment=None, write_settings=False
)
for source_id, paths in ordered:
if not paths:
continue
segment = source_id_to_path_segment(source_id)
_prune_segment_exports_not_in_expected(repo_path, ide, segment, paths)
copied, _settings = _copy_template_files_to_ide(
repo_path, ide, paths, force, source_segment=segment, write_settings=False
)
all_copied.extend(copied)

settings_path: Path | None = None
config = IDE_CONFIG[ide]
settings_file = config.get("settings_file")
if settings_file and isinstance(settings_file, str):
settings_path = create_vscode_settings(repo_path, settings_file, prompts_by_source=prompts_by_source)
Expand Down Expand Up @@ -764,27 +773,16 @@ def copy_templates_to_ide(


def _vscode_prompt_recommendation_paths_from_sources(prompts_by_source: dict[str, list[Path]]) -> list[str]:
"""Build `.github/prompts/...` recommendation strings matching namespaced IDE export layout."""
prompt_files: list[str] = []
for source_id, paths in sorted(
prompts_by_source.items(),
key=lambda item: (item[0] != PROMPT_SOURCE_CORE, item[0]),
):
segment = source_id_to_path_segment(source_id)
for template_path in paths:
prompt_files.append(f".github/prompts/{segment}/{template_path.stem}.prompt.md")
return prompt_files
"""Build `.github/prompts/...` recommendation strings matching flat IDE export layout."""
merged = _merge_prompt_export_outputs_by_basename(prompts_by_source, "prompt.md")
return [f".github/prompts/{name}" for name in sorted(merged.keys())]


def _vscode_prompt_paths_from_full_catalog(repo_path: Path) -> list[str]:
"""Recommendation paths for the full discovered prompt catalog (namespaced segments)."""
"""Recommendation paths for the full discovered prompt catalog (flat under ``.github/prompts/``)."""
catalog = discover_prompt_sources_catalog(repo_path)
out: list[str] = []
for source_id, paths in sorted(catalog.items(), key=lambda item: (item[0] != PROMPT_SOURCE_CORE, item[0])):
segment = source_id_to_path_segment(source_id)
for template_path in paths:
out.append(f".github/prompts/{segment}/{template_path.stem}.prompt.md")
return out
merged = _merge_prompt_export_outputs_by_basename(catalog, "prompt.md")
return [f".github/prompts/{name}" for name in sorted(merged.keys())]


def _finalize_vscode_prompt_recommendation_paths(repo_path: Path, prompt_files: list[str]) -> list[str]:
Expand Down Expand Up @@ -883,7 +881,7 @@ def create_vscode_settings(
prompts_by_source: When set (e.g. from ``copy_prompts_by_source_to_ide``), recommendations list only
templates from that export; prior **SpecFact-managed** ``.github/prompts/`` entries (paths whose
filename looks like ``specfact*.prompt.md``) are removed so selective ``--prompts`` runs do not
leave stale exports; other ``.github/prompts/`` entries and paths outside that folder are preserved.
leave stale recommendations; other ``.github/prompts/`` entries and paths outside that folder are preserved.
When ``None``,
recommendations follow the full discovered catalog (or legacy flat fallbacks).

Expand Down
Loading
Loading