@khanhspring/forge-module 1.2.0 → 1.4.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanhspring/forge-module",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Forge module repo skills for Claude Code — implement, contract-test, done, and more",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,11 +15,17 @@ disable-model-invocation: true
15
15
  Run or analyze Specmatic contract tests for this module.
16
16
 
17
17
  ## Pre-check
18
- - Read `.forge/module.json`.
18
+ - Read `.forge/module.json` for `module` and `spec_submodule_path`.
19
19
  If missing, say "Run forge-init to set up this module repo first."
20
+ - Read `{spec_submodule_path}/.forge/project.json` and find the `modules[]` entry where
21
+ `name == module` — port and submodule structure live there, not in module.json.
20
22
  - Determine scope:
21
- - Has no `submodules` key → single module; use top-level `contract_glob` and `test_base_url`.
22
- - Has `submodules[]` → module with submodules; show one command block per submodule.
23
+ - Entry has no `submodules[]` → single module; derive `contract_glob =
24
+ {spec_submodule_path}/contracts/{module}/*.yaml` and `test_base_url =
25
+ http://localhost:{entry.port}`.
26
+ - Entry has `submodules[]` → module with submodules; show one command block per submodule,
27
+ deriving each one's `contract_glob = {spec_submodule_path}/contracts/{submodule.name}/*.yaml`
28
+ and `test_base_url = http://localhost:{submodule.port}`.
23
29
 
24
30
  ## If $ARGUMENTS is empty — Output the test command(s)
25
31
 
@@ -2,7 +2,7 @@
2
2
  name: "forge-done"
3
3
  description: "Confirms all tasks for this module are complete and generates the module repo commit message. Module repo only — run /forge-close in the spec repo afterward to mark tasks done there."
4
4
  argument-hint: "Feature slug (e.g. 'user-registration')"
5
- compatibility: "Requires module repo with .forge/module.json and an initialized specs/ git submodule"
5
+ compatibility: "Requires module repo with .forge/module.json and an initialized specs/ link (git submodule or junction)"
6
6
  metadata:
7
7
  author: "forge-workflow"
8
8
  source: "module-skills/forge-done/SKILL.md"
@@ -28,20 +28,22 @@ is handled separately by `/forge-close` in the spec repo.
28
28
 
29
29
  ---
30
30
 
31
- ## Step 1 — Read task list (read-only from submodule)
31
+ ## Step 1 — Read task list (read-only from specs/)
32
32
 
33
33
  Read `{spec_submodule_path}/features/{slug}/tasks.md`.
34
34
 
35
- - If `module.json` has no `submodules` key: find the `### {module}` section.
36
- - If `module.json` has `submodules[]`: find the `### {submodule.name}` section for each entry;
37
- show them grouped by submodule name in Step 2.
35
+ Read `{spec_submodule_path}/.forge/project.json` and find the `modules[]` entry where
36
+ `name == module` submodule structure lives there, not in module.json:
37
+ - Entry has no `submodules[]`: find the `### {module}` section.
38
+ - Entry has `submodules[]`: find the `### {submodule.name}` section for each entry; show them
39
+ grouped by submodule name in Step 2.
38
40
 
39
41
  If `tasks.md` is missing:
40
42
  > "No tasks file found at `specs/features/{slug}/tasks.md`.
41
43
  > Run `/forge-tasks {slug}` in the spec repo first."
42
44
 
43
- Note: the submodule is read-only — task checkboxes cannot be updated from here.
44
- That is handled by `/forge-close` in the spec repo.
45
+ Note: `specs/` is read-only from here regardless of link type — task checkboxes cannot be
46
+ updated from this repo. That is handled by `/forge-close` in the spec repo.
45
47
 
46
48
  ---
47
49
 
@@ -2,7 +2,7 @@
2
2
  name: "forge-implement"
3
3
  description: "Researches existing codebase patterns, clears ambiguities one at a time, challenges implementation concerns, then guides task-by-task implementation scoped to this module. No code written until plan is confirmed."
4
4
  argument-hint: "Feature slug (e.g. 'user-registration')"
5
- compatibility: "Requires module repo with .forge/module.json and an initialized specs/ git submodule"
5
+ compatibility: "Requires module repo with .forge/module.json and an initialized specs/ link (git submodule or junction)"
6
6
  metadata:
7
7
  author: "forge-workflow"
8
8
  source: "module-skills/forge-implement/SKILL.md"
@@ -25,17 +25,24 @@ presented the Implementation Plan and the user has explicitly confirmed it.
25
25
  ## Pre-check
26
26
 
27
27
  - Read `.forge/module.json` — if missing, say "Run `/forge-init` to set up this module repo first."
28
- - Get `spec_submodule_path` from module.json.
29
- - **Determine working scope:**
30
- - `module.json` has no `submodules` → scope = the module itself; use top-level `test_base_url` and `contract_glob`.
31
- - `module.json` has `submodules[]` ask "Which submodule are you implementing? ({list submodule names})"
32
- then use that submodule's `path`, `test_base_url`, and `contract_glob`.
28
+ - Get `module`, `spec_submodule_path`, and `spec_link_type` from module.json.
29
+ - **Determine working scope** from the spec repo, not module.json:
30
+ - Read `{spec_submodule_path}/.forge/project.json` and find the `modules[]` entry where
31
+ `name == module`. If not found, warn: "`{module}` isn't registered in the spec repo's
32
+ `project.json` check the name matches, or add it there via `/forge-config`."
33
+ - Entry has no `submodules[]` → scope = the module itself; `test_base_url =
34
+ http://localhost:{entry.port}`, `contract_glob = {spec_submodule_path}/contracts/{module}/*.yaml`.
35
+ - Entry has `submodules[]` → ask "Which submodule are you implementing? ({list submodule names
36
+ from the entry})", then use that submodule's `path`, and derive `test_base_url =
37
+ http://localhost:{submodule.port}`, `contract_glob =
38
+ {spec_submodule_path}/contracts/{submodule.name}/*.yaml`.
33
39
  - Feature slug from $ARGUMENTS.
34
40
  - If empty, scan `{spec_submodule_path}/features/*/tasks.md` for `### {scope-name}` headings
35
41
  (module name for simple modules, submodule name for submodules), list features with pending
36
42
  tasks, ask which to implement.
37
- - Check `git submodule status` — if specs/ is out of date, say:
43
+ - If `spec_link_type` is `"submodule"` (or unset), check `git submodule status` — if specs/ is out of date, say:
38
44
  > "Your specs submodule may be out of date. Run `git submodule update --remote specs` first, or continue with the current version?"
45
+ - If `spec_link_type` is `"junction"`, skip this check — a junction/symlink always reflects the live spec folder.
39
46
 
40
47
  ---
41
48
 
@@ -34,7 +34,9 @@ Before asking anything, silently scan the repo and note:
34
34
 
35
35
  1. **Stack detection** — does `pom.xml`, `build.gradle`, `package.json`, `go.mod`, `requirements.txt`, or similar exist? What versions are declared?
36
36
  2. **Port detection** — is a port configured in `application.yml`, `application.properties`, `.env`, `.env.example`, or `docker-compose.yml`?
37
- 3. **Specs submodule** — does `specs/` already exist as a directory or submodule?
37
+ 3. **Specs link** — does `specs/` already exist? If so, determine how: a git submodule
38
+ (check `.gitmodules` / `git submodule status`) or a junction/symlink (check if it's a
39
+ reparse point / symlink, e.g. `git submodule status` reports nothing but the dir exists).
38
40
  4. **CI** — does `.github/workflows/` already exist? Any contract test workflow present?
39
41
  5. **Existing CLAUDE.md** — already has project context written?
40
42
  6. **Monorepo signals** — does `nx.json`, `turbo.json`, `lerna.json`, `pnpm-workspace.yaml`, or
@@ -47,39 +49,20 @@ Report findings before asking anything:
47
49
  > "Here's what I found in this repo:
48
50
  > - Stack: {detected stack — or 'not detected'}
49
51
  > - Port: {detected port — or 'not found in config'}
50
- > - specs/ submodule: {exists / not present}
52
+ > - specs/: {not present / exists — git submodule / exists junction or symlink}
51
53
  > - GitHub Actions: {exists / not present}
52
54
  > {if monorepo signals}: - Looks like a monorepo — detected sub-apps: {list of dir names}
53
55
  >
54
56
  > I'll use these as defaults — just confirm or correct as we go."
55
57
 
56
- **If monorepo/multi-module signals were found**, ask immediately after the report:
57
- > "This looks like a module with submodules ({names}).
58
- > Should I configure them as submodules so each gets its own tasks and contracts? (yes / no)"
58
+ **If monorepo/multi-module signals were found**, note it in passing — no question needed:
59
+ > "This looks like a module with submodules ({names}). Submodule structure (ports, stack,
60
+ > paths) lives in the spec repo's `project.json`, not here I'll cross-check against that
61
+ > once `specs/` is linked."
59
62
 
60
- If yes collect submodule details in Step 2-B before the main questions.
61
- If no proceed as a single module.
62
-
63
- ---
64
-
65
- ## Step 2-B — Submodule collection (only when confirmed)
66
-
67
- Submodules share the parent module's repo — they are not separate repos.
68
- Collect them one at a time. Pre-fill from what was detected and ask the user to confirm or correct:
69
-
70
- - "Submodule name? _(Must match a `name` under this module's `submodules` in the spec repo's `project.json`; default: `{detected-dir-name}`)_"
71
- - "Type? (backend / frontend / worker)"
72
- - "Stack? (detected: {stack-if-found} — or enter manually)"
73
- - "Port? _(each submodule has its own port — detected: {port-if-found})_"
74
- - "Path within this repo? (default: `{detected-path}`)"
75
-
76
- Confirm: "`{sub}` — {type} — {stack} — :{port} — {path}. Another submodule? (yes / no)"
77
-
78
- **Rules enforced here:**
79
- - Submodules have no `repo` field — they are in the same repo as the parent module
80
- - Port is collected per submodule; the parent module has no `port`
81
-
82
- After collecting all submodules, continue to Step 2 (Q4 port will be skipped automatically).
63
+ Submodule structure is never collected or stored in this repo's `.forge/module.json`. It's
64
+ configured once in the spec repo via `/forge-config` there, and every module skill reads it
65
+ from `specs/.forge/project.json` at runtime. This repo only needs to know its own module name.
83
66
 
84
67
  ---
85
68
 
@@ -89,57 +72,70 @@ Ask one question per message. Wait for the answer before asking the next.
89
72
  Where research already gives a confident answer, present it as a default to confirm
90
73
  rather than asking from scratch.
91
74
 
92
- **Q1 — Module name** _(skip if Step 2-B was run)_
75
+ **Q1 — Module name**
93
76
  > "What's the module name for this repo?
94
77
  > _(Must exactly match a `name` entry in the spec repo's `.forge/project.json`)_"
95
78
 
96
79
  Do not suggest a default — module names must be exact matches. Warn clearly:
97
80
  > "This name must match exactly. A mismatch will break `/forge-implement` and `/forge-done`."
98
81
 
82
+ If `specs/` was already detected in Step 1 (already linked), read
83
+ `{specs-path}/.forge/project.json` now and find the `modules[]` entry where `name` matches
84
+ this answer. If found, silently keep its `port`, `stack`, `type`, and `submodules[]` (if any)
85
+ for use in the Step 3 preview and the CLAUDE.md content written in Step 4 — do not re-ask for
86
+ any of it. If not found, note it and continue: "Heads up — `{module}` isn't in the spec repo's
87
+ `project.json` yet. Add it there with `/forge-config`, or continue and I'll leave stack/port
88
+ as TBD in CLAUDE.md for now." If `specs/` isn't linked yet, this lookup happens later, in
89
+ Step 4, right after the link is created.
90
+
99
91
  **Q2 — Module description**
100
92
  > "What does this module do? (one sentence)"
101
93
 
102
- **Q3 — Spec repo URL**
103
-
104
- If `specs/` already exists:
105
- > "I see a `specs/` directory already — is that the spec repo submodule? (yes/no)
106
- > If yes, what's the remote URL? (run `git remote -v` in `specs/` if unsure)"
107
-
108
- If not:
109
- > "What's the spec repo URL? (it will be added as a git submodule at `specs/`)"
110
-
111
- **Q4 Port** _(skip entirely if Step 2-B was run — port belongs to each submodule, not the module)_
112
-
113
- If port was detected in Step 1:
114
- > "I found port `{port}` in your config is that the right local dev port? (yes / enter different port)"
115
-
116
- If not detected:
117
- > "What port does this module run on locally?"
118
-
119
- **Q5 Tech stack**
120
-
121
- If stack was detected in Step 1:
122
- > "Looks like this is a `{detected stack}` module — is that right? Anything to add?"
123
-
124
- If not detected:
125
- > "What's the tech stack? (e.g. 'Spring Boot 3, Java 21' or 'React, TypeScript')"
126
-
127
- **Q6Principles**
94
+ **Q3 — Spec repo link**
95
+
96
+ If `specs/` already exists, confirm what was detected in Step 1 instead of asking from scratch:
97
+ - Detected as a git submodule → "I see `specs/` is already a git submodule — is that the spec
98
+ repo? (yes/no). If yes, what's the remote URL? (run `git remote -v` in `specs/` if unsure)"
99
+ - Detected as a junction/symlink → "I see `specs/` is already a local junction/symlink — what
100
+ local path does it point at? (confirm or correct)"
101
+ - Set `spec_link_type` accordingly and skip straight to Q4.
102
+
103
+ If `specs/` does not exist, ask how to link it:
104
+ > "How should this repo link to the spec repo?
105
+ > 1. **Git submodule** (default) versioned and works across machines/CI, but needs
106
+ > `git submodule update --remote specs` to sync after spec repo changes.
107
+ > 2. **Junction / local link** — for when the spec repo and this module repo live on the same
108
+ > machine (solo/local dev). `specs/` always reflects the live folder, no sync command needed —
109
+ > but it won't survive being cloned elsewhere or used in CI, and skips version pinning."
110
+
111
+ - **If git submodule (or no answer / "default")**:
112
+ > "What's the spec repo URL? (it will be added as a git submodule at `specs/`)"
113
+ Set `spec_link_type: "submodule"`.
114
+ - **If junction/local link**:
115
+ > "What's the local path to the spec repo folder? (absolute path, e.g. `D:\Workspace\my-specs`
116
+ > or `/home/me/my-specs`)"
117
+ Set `spec_link_type: "junction"` and `spec_source_path` to the given absolute path.
118
+
119
+ Note: port, stack, and submodule structure are never asked here they come from the spec
120
+ repo's `project.json` (looked up in Q1 if `specs/` already existed, or in Step 4 right after
121
+ linking otherwise). `.forge/module.json` doesn't store any of it either way.
122
+
123
+ **Q4 — Principles**
128
124
  > "What are the key architectural principles for this module?
129
125
  > _(e.g. 'stateless', 'no business logic in controllers', 'repository pattern for DB access')
130
126
  > Say 'none yet' to skip._"
131
127
 
132
- **Q7 — Conventions**
128
+ **Q5 — Conventions**
133
129
  > "Any coding conventions the team follows in this repo?
134
130
  > _(e.g. 'constructor injection only', 'all public methods must have unit tests', 'no magic strings')
135
131
  > Say 'none yet' to skip._"
136
132
 
137
- **Q8 — Never**
133
+ **Q6 — Never**
138
134
  > "Anything developers should never do in this codebase?
139
135
  > _(e.g. 'no direct DB calls from the API layer', 'never change a contract to fix a failing test')
140
136
  > Say 'none yet' to skip._"
141
137
 
142
- **Q9 — GitHub Actions CI**
138
+ **Q7 — GitHub Actions CI**
143
139
 
144
140
  If `.github/workflows/` already exists with a contract test:
145
141
  > "I see a CI workflow already exists — skip adding another? (yes to skip)"
@@ -157,24 +153,25 @@ Show a full preview of everything that will be created or run:
157
153
  ```
158
154
  Ready to initialize. Here's what I'll do:
159
155
 
160
- {if specs/ not present}
156
+ {if specs/ not present, spec_link_type = submodule}
161
157
  git submodule add {spec-repo-url} specs
162
158
  git submodule update --init --recursive
163
159
 
160
+ {if specs/ not present, spec_link_type = junction}
161
+ {Windows} mklink /J specs "{spec-source-path}"
162
+ {macOS/Linux} ln -s "{spec-source-path}" specs
163
+ append `specs/` to .gitignore (junction contents aren't tracked by this repo)
164
+
164
165
  .forge/module.json
165
- {if single app}
166
- module: {module-name}
167
- test_base_url: http://localhost:{port}
168
- contract_glob: specs/contracts/{module-name}/*.yaml
169
- {if submodules}
170
- submodules:
171
- {sub-name} path:{path} :{port} contract: specs/contracts/{sub-name}/*.yaml
172
- ...
166
+ module: {module-name}
167
+ spec_submodule_path: specs
168
+ spec_link_type: {submodule | junction}
169
+ {if junction} spec_source_path: {spec-source-path}
173
170
 
174
171
  CLAUDE.md
175
172
  Module: {module-name}
176
173
  Description: {description}
177
- Stack: {stack} Port: {port}
174
+ Stack/Port: {from spec repo's project.json, or "TBD — set via /forge-config in the spec repo"}
178
175
  Principles: {list or "none yet"}
179
176
  Conventions: {list or "none yet"}
180
177
  Never: {list or "none yet"}
@@ -192,50 +189,51 @@ Wait for confirmation. Do not write files or run git commands before the user sa
192
189
 
193
190
  ## Step 4 — Write files
194
191
 
195
- Run (if specs/ not already present):
192
+ Run (if specs/ not already present), based on the chosen `spec_link_type`:
193
+
194
+ **`submodule`:**
196
195
  ```bash
197
196
  git submodule add {spec-repo-url} specs
198
197
  git submodule update --init --recursive
199
198
  ```
200
199
 
201
- Write `.forge/module.json`:
202
-
203
- **For a single-app module** (no sub-apps):
204
- ```json
205
- {
206
- "module": "{module-name}",
207
- "spec_submodule_path": "specs",
208
- "specmatic_version": "2.x",
209
- "test_base_url": "http://localhost:{port}",
210
- "contract_glob": "specs/contracts/{module-name}/*.yaml"
211
- }
200
+ **`junction`:**
201
+ ```bash
202
+ # Windows
203
+ mklink /J specs "{spec-source-path}"
204
+ # macOS/Linux
205
+ ln -s "{spec-source-path}" specs
212
206
  ```
207
+ Then append `specs/` to `.gitignore` — a junction/symlink's contents belong to the spec repo,
208
+ not this one, and must not be tracked or committed here.
213
209
 
214
- **Module with submodules** no top-level `port` or `test_base_url`; each submodule owns those.
215
- Submodules have no `repo` field they are in the same repo as the parent module:
210
+ If the Q1 lookup didn't already happen (i.e. `specs/` was just created above, not already
211
+ present in Step 1), read `{spec_submodule_path}/.forge/project.json` now and find the
212
+ `modules[]` entry where `name` matches the module name from Q1. Use its `port`, `stack`, and
213
+ `submodules[]` (if any) to fill the CLAUDE.md content below. If the module still isn't found
214
+ there, write "TBD — set via `/forge-config` in the spec repo" for the stack/port line instead.
215
+
216
+ Write `.forge/module.json` — same shape whether or not the module has submodules in the spec
217
+ repo, since submodule structure is never duplicated here:
216
218
  ```json
217
219
  {
218
220
  "module": "{module-name}",
219
221
  "spec_submodule_path": "specs",
220
- "specmatic_version": "2.x",
221
- "submodules": [
222
- {
223
- "name": "{sub-name}",
224
- "path": "{relative-path}",
225
- "test_base_url": "http://localhost:{port}",
226
- "contract_glob": "specs/contracts/{sub-name}/*.yaml"
227
- }
228
- ]
222
+ "spec_link_type": "submodule"
229
223
  }
230
224
  ```
225
+ `spec_link_type` is `"submodule"` (default) or `"junction"`. When `"junction"`, also write
226
+ `"spec_source_path": "{absolute-local-path}"` so the link can be recreated if it's ever lost.
231
227
 
232
- Write `CLAUDE.md`:
228
+ Write `CLAUDE.md`. `{stack}`/`{port}`/`{path}` below come from the spec repo's `project.json`
229
+ lookup above (or "TBD" if not found there yet) — never from a question asked in this skill:
233
230
  ```markdown
234
231
  # {module-name}
235
232
 
236
233
  {description}
237
234
 
238
- _(Single app: show Stack + Port inline. Monorepo: replace with a table.)_
235
+ _(Single app: show Stack + Port inline. Monorepo: replace with a table, one row per submodule
236
+ found in the spec repo's project.json.)_
239
237
 
240
238
  **Stack:** {stack} **Port:** {port}
241
239
 
@@ -253,9 +251,15 @@ _(Single app: show Stack + Port inline. Monorepo: replace with a table.)_
253
251
  5. (then in the spec repo) `/forge-close {slug} {module}` — mark tasks done there
254
252
 
255
253
  ## Spec repo
256
- Linked via git submodule at `specs/`.
254
+ _(submodule)_ Linked via git submodule at `specs/`.
257
255
  Run `git submodule update --remote specs` before starting a new feature,
258
256
  and again after `/forge-close` is run in the spec repo to sync task status.
257
+
258
+ _(junction)_ Linked via local junction/symlink at `specs/` → `{spec-source-path}`.
259
+ Always reflects the live spec repo folder — no sync command needed. This only works while
260
+ both repos are on the same machine; switch to a git submodule (`/forge-init` again, or
261
+ re-run link setup) before cloning this repo elsewhere or using it in CI.
262
+
259
263
  Project-wide domain context (actors, glossary, principles) lives at `specs/CONTEXT.md`
260
264
  — read it before implementing.
261
265
 
@@ -304,6 +308,9 @@ Run `git status` and confirm:
304
308
  - Never overwrite existing files
305
309
  - All written files must be complete — no unfilled placeholders
306
310
  - `module` in module.json must exactly match the name in the spec repo's project.json
311
+ - `module.json` never stores `port`, `stack`, or submodule structure — those live only in the
312
+ spec repo's `project.json`; every module skill resolves them at runtime by reading
313
+ `specs/.forge/project.json`
307
314
  - **Extract, don't re-ask**: if the user's answer contains information for upcoming fields
308
315
  (e.g. "it's a NestJS app on port 3000 at apps/auth-ui"), extract and fill those fields
309
316
  silently — only ask about what is genuinely missing. Never ask a question the user has
@@ -2,7 +2,7 @@
2
2
  name: "forge-tasks"
3
3
  description: "Lists all tasks assigned to this module across every feature, with completion status, by scanning the spec submodule. Module repo task dashboard."
4
4
  argument-hint: ""
5
- compatibility: "Requires module repo with .forge/module.json and an initialized specs/ git submodule"
5
+ compatibility: "Requires module repo with .forge/module.json and an initialized specs/ link (git submodule or junction)"
6
6
  metadata:
7
7
  author: "forge-workflow"
8
8
  source: "module-skills/forge-tasks/SKILL.md"
@@ -15,16 +15,23 @@ disable-model-invocation: true
15
15
  Show all tasks assigned to this module across all features.
16
16
 
17
17
  ## Pre-check
18
- - Read `.forge/module.json` for `module` and `spec_submodule_path`.
18
+ - Read `.forge/module.json` for `module`, `spec_submodule_path`, and `spec_link_type`.
19
19
  If missing: "Run `/forge-init` to set up this module repo first."
20
- - If `specs/` is not initialized: suggest `git submodule update --init --recursive`
21
- - Optionally suggest `git submodule update --remote specs` to get the latest task status.
20
+ - If `spec_link_type` is `"submodule"` (or unset, for repos initialized before this field existed):
21
+ - If `specs/` is not initialized: suggest `git submodule update --init --recursive`
22
+ - Optionally suggest `git submodule update --remote specs` to get the latest task status.
23
+ - If `spec_link_type` is `"junction"`:
24
+ - If `specs/` is missing or broken, suggest recreating the link at `spec_source_path`
25
+ (Windows: `mklink /J specs "{spec_source_path}"`; macOS/Linux: `ln -s "{spec_source_path}" specs`).
26
+ - No sync command needed — a junction/symlink always reflects the live folder.
22
27
 
23
28
  ## Steps
24
29
 
25
- Determine the set of task headings to look for:
26
- - `module.json` has no `submodules` look for `### {module}` (one heading).
27
- - `module.json` has `submodules[]` → look for `### {submodule.name}` for each entry.
30
+ Determine the set of task headings to look for. Read `{spec_submodule_path}/.forge/project.json`
31
+ and find the `modules[]` entry where `name == module` (from module.json) — submodule structure
32
+ is never stored in module.json itself:
33
+ - Entry has no `submodules[]` → look for `### {module}` (one heading).
34
+ - Entry has `submodules[]` → look for `### {submodule.name}` for each entry.
28
35
  Submodule task headings use the submodule's own name — no parent prefix — so they are
29
36
  identical to standalone module headings and can be promoted without touching tasks.md.
30
37
 
@@ -65,7 +72,8 @@ Feature: user-registration [Open]
65
72
  ```
66
73
 
67
74
  If no tasks reference this module across any feature:
68
- > "No tasks found for `{module}`. Either no feature targets this module yet, or the specs
69
- > submodule is out of date — try `git submodule update --remote specs`."
75
+ > "No tasks found for `{module}`. Either no feature targets this module yet, {if submodule}
76
+ > or the specs submodule is out of date — try `git submodule update --remote specs`.
77
+ > {if junction} or the linked spec folder doesn't have it yet."
70
78
 
71
79
  After showing: "Tell me which feature to implement, or run `/forge-implement {slug}`."