@khanhspring/forge-module 1.0.1 → 1.1.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
|
@@ -15,11 +15,15 @@ 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`.
|
|
19
19
|
If missing, say "Run forge-init to set up this module repo first."
|
|
20
|
+
- 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.
|
|
20
23
|
|
|
21
|
-
## If $ARGUMENTS is empty — Output the test command
|
|
24
|
+
## If $ARGUMENTS is empty — Output the test command(s)
|
|
22
25
|
|
|
26
|
+
**Single-module repo:**
|
|
23
27
|
```
|
|
24
28
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
25
29
|
Contract Tests: {module}
|
|
@@ -36,6 +40,24 @@ Contracts under test:
|
|
|
36
40
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
37
41
|
```
|
|
38
42
|
|
|
43
|
+
**Module with submodules (one block per submodule):**
|
|
44
|
+
```
|
|
45
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
46
|
+
Contract Tests: {submodule.name}
|
|
47
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
48
|
+
|
|
49
|
+
Make sure {submodule.name} is running at {submodule.test_base_url}, then run:
|
|
50
|
+
|
|
51
|
+
specmatic test \
|
|
52
|
+
--contract "{submodule.contract_glob}" \
|
|
53
|
+
--testBaseURL {submodule.test_base_url}
|
|
54
|
+
|
|
55
|
+
Contracts under test:
|
|
56
|
+
{list files matching submodule.contract_glob}
|
|
57
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
58
|
+
```
|
|
59
|
+
_(Repeat for each entry in `submodules[]`.)_
|
|
60
|
+
|
|
39
61
|
## If $ARGUMENTS contains test output — Analyze failures
|
|
40
62
|
|
|
41
63
|
For each failure, provide:
|
|
@@ -31,7 +31,10 @@ is handled separately by `/forge-close` in the spec repo.
|
|
|
31
31
|
## Step 1 — Read task list (read-only from submodule)
|
|
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
38
|
|
|
36
39
|
If `tasks.md` is missing:
|
|
37
40
|
> "No tasks file found at `specs/features/{slug}/tasks.md`.
|
|
@@ -65,6 +68,7 @@ Wait for confirmation before continuing.
|
|
|
65
68
|
|
|
66
69
|
## Step 3 — Generate commit message
|
|
67
70
|
|
|
71
|
+
**Module with no submodules:**
|
|
68
72
|
```
|
|
69
73
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
70
74
|
Commit message
|
|
@@ -73,15 +77,33 @@ feat({module}): implement {feature-slug}
|
|
|
73
77
|
|
|
74
78
|
- TASK-1: {title}
|
|
75
79
|
- TASK-2: {title}
|
|
76
|
-
- TASK-3: {title}
|
|
77
80
|
|
|
78
81
|
Spec: specs/features/{slug}/spec.md
|
|
79
|
-
Tasks: specs/features/{slug}/tasks.md
|
|
80
82
|
Contract: specs/contracts/{module}/{slug}.yaml
|
|
81
83
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
82
84
|
```
|
|
83
85
|
|
|
84
86
|
Then remind:
|
|
85
87
|
> "Once committed and pushed, go to the spec repo and run:
|
|
86
|
-
> `/forge-close {slug} {module}`
|
|
87
|
-
|
|
88
|
+
> `/forge-close {slug} {module}`"
|
|
89
|
+
|
|
90
|
+
**Module with submodules (tasks grouped by submodule):**
|
|
91
|
+
```
|
|
92
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
93
|
+
Commit message
|
|
94
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
95
|
+
feat(webapps): implement {feature-slug}
|
|
96
|
+
|
|
97
|
+
[admin]
|
|
98
|
+
- TASK-3: {title}
|
|
99
|
+
[portal]
|
|
100
|
+
- TASK-4: {title}
|
|
101
|
+
|
|
102
|
+
Spec: specs/features/{slug}/spec.md
|
|
103
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Then remind — one `/forge-close` per submodule, using the **submodule name directly**
|
|
107
|
+
(no parent prefix — same command you'd use if it were a standalone module):
|
|
108
|
+
> "/forge-close {slug} admin
|
|
109
|
+
> /forge-close {slug} portal"
|
|
@@ -25,10 +25,15 @@ 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 `
|
|
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`.
|
|
29
33
|
- Feature slug from $ARGUMENTS.
|
|
30
|
-
- If empty, scan `{spec_submodule_path}/features/*/tasks.md` for `### {
|
|
31
|
-
|
|
34
|
+
- If empty, scan `{spec_submodule_path}/features/*/tasks.md` for `### {scope-name}` headings
|
|
35
|
+
(module name for simple modules, submodule name for submodules), list features with pending
|
|
36
|
+
tasks, ask which to implement.
|
|
32
37
|
- Check `git submodule status` — if specs/ is out of date, say:
|
|
33
38
|
> "Your specs submodule may be out of date. Run `git submodule update --remote specs` first, or continue with the current version?"
|
|
34
39
|
|
|
@@ -42,14 +47,15 @@ Load the feature documents silently:
|
|
|
42
47
|
|
|
43
48
|
If `tasks.md` is missing → stop: "No tasks found. Run `/forge-tasks {slug}` in the spec repo first."
|
|
44
49
|
|
|
45
|
-
**Determine which contract(s) apply to this
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
- **If
|
|
50
|
+
**Determine which contract(s) apply to this scope:**
|
|
51
|
+
- The contract path is always `{spec_submodule_path}/contracts/{scope-name}/{slug}.yaml` where
|
|
52
|
+
`{scope-name}` is the module name (for simple modules) or the submodule name (for submodules).
|
|
53
|
+
Submodules use their own name — no parent prefix — so the path is identical to a standalone module.
|
|
54
|
+
- **If the contract exists** → this scope *provides* the API. Use it as the source of truth.
|
|
55
|
+
- **If it does NOT exist** (typical for frontend/consumer scopes) → scan
|
|
50
56
|
`{spec_submodule_path}/contracts/*/{slug}.yaml` for sibling contracts. These are the APIs
|
|
51
|
-
this
|
|
52
|
-
> "No contract for `{
|
|
57
|
+
this scope *consumes*. Report:
|
|
58
|
+
> "No contract for `{scope-name}` — consuming APIs from: {list providers}.
|
|
53
59
|
> I'll use their contracts as the integration source of truth."
|
|
54
60
|
- If no contract exists anywhere for this feature → note it and continue with spec + tasks only.
|
|
55
61
|
|
|
@@ -178,7 +184,7 @@ Wait for the answer before continuing.
|
|
|
178
184
|
- When **consuming** an API (frontend/client): send requests and parse responses exactly as the
|
|
179
185
|
consumed contract defines — treat its shapes as fixed, code defensively against its error responses
|
|
180
186
|
- If the contract looks wrong: "This looks like a contract issue — raise a PR in the spec repo"
|
|
181
|
-
- Only implement tasks under `### {
|
|
187
|
+
- Only implement tasks under `### {scope-name}` in tasks.md (module name or submodule name) — flag any cross-scope work
|
|
182
188
|
- Follow existing codebase patterns found in Step 1 — consistency over personal preference
|
|
183
189
|
- No code before the user confirms the plan in Step 4
|
|
184
190
|
|
|
@@ -19,9 +19,9 @@ disable-model-invocation: false
|
|
|
19
19
|
|
|
20
20
|
# Forge Init — Module Repo
|
|
21
21
|
|
|
22
|
-
Initialize this repo as a Forge module repo
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
Initialize this repo as a Forge module repo. It may contain one module (a dedicated service or
|
|
23
|
+
app) or multiple modules sharing the same repo to reduce overhead (a monorepo). Either way,
|
|
24
|
+
research first, then ask one question at a time before writing anything.
|
|
25
25
|
|
|
26
26
|
## Pre-check
|
|
27
27
|
If `.forge/module.json` already exists → say "Module repo already initialized." and stop.
|
|
@@ -34,9 +34,13 @@ 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. **
|
|
37
|
+
3. **Specs submodule** — does `specs/` already exist as a directory or submodule?
|
|
38
38
|
4. **CI** — does `.github/workflows/` already exist? Any contract test workflow present?
|
|
39
39
|
5. **Existing CLAUDE.md** — already has project context written?
|
|
40
|
+
6. **Monorepo signals** — does `nx.json`, `turbo.json`, `lerna.json`, `pnpm-workspace.yaml`, or
|
|
41
|
+
a `workspaces` key in `package.json` exist? Is there an `apps/` or `packages/` directory with
|
|
42
|
+
multiple sub-directories each containing their own `package.json`, `pom.xml`, etc.?
|
|
43
|
+
If yes, list the detected sub-apps (directory names).
|
|
40
44
|
|
|
41
45
|
Report findings before asking anything:
|
|
42
46
|
|
|
@@ -45,9 +49,38 @@ Report findings before asking anything:
|
|
|
45
49
|
> - Port: {detected port — or 'not found in config'}
|
|
46
50
|
> - specs/ submodule: {exists / not present}
|
|
47
51
|
> - GitHub Actions: {exists / not present}
|
|
52
|
+
> {if monorepo signals}: - Looks like a monorepo — detected sub-apps: {list of dir names}
|
|
48
53
|
>
|
|
49
54
|
> I'll use these as defaults — just confirm or correct as we go."
|
|
50
55
|
|
|
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)"
|
|
59
|
+
|
|
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).
|
|
83
|
+
|
|
51
84
|
---
|
|
52
85
|
|
|
53
86
|
## Step 2 — Questions (one at a time)
|
|
@@ -56,7 +89,7 @@ Ask one question per message. Wait for the answer before asking the next.
|
|
|
56
89
|
Where research already gives a confident answer, present it as a default to confirm
|
|
57
90
|
rather than asking from scratch.
|
|
58
91
|
|
|
59
|
-
**Q1 — Module name**
|
|
92
|
+
**Q1 — Module name** _(skip if Step 2-B was run)_
|
|
60
93
|
> "What's the module name for this repo?
|
|
61
94
|
> _(Must exactly match a `name` entry in the spec repo's `.forge/project.json`)_"
|
|
62
95
|
|
|
@@ -75,7 +108,7 @@ If `specs/` already exists:
|
|
|
75
108
|
If not:
|
|
76
109
|
> "What's the spec repo URL? (it will be added as a git submodule at `specs/`)"
|
|
77
110
|
|
|
78
|
-
**Q4 — Port**
|
|
111
|
+
**Q4 — Port** _(skip entirely if Step 2-B was run — port belongs to each submodule, not the module)_
|
|
79
112
|
|
|
80
113
|
If port was detected in Step 1:
|
|
81
114
|
> "I found port `{port}` in your config — is that the right local dev port? (yes / enter different port)"
|
|
@@ -129,9 +162,14 @@ Ready to initialize. Here's what I'll do:
|
|
|
129
162
|
git submodule update --init --recursive
|
|
130
163
|
|
|
131
164
|
.forge/module.json
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
+
...
|
|
135
173
|
|
|
136
174
|
CLAUDE.md
|
|
137
175
|
Module: {module-name}
|
|
@@ -161,6 +199,8 @@ git submodule update --init --recursive
|
|
|
161
199
|
```
|
|
162
200
|
|
|
163
201
|
Write `.forge/module.json`:
|
|
202
|
+
|
|
203
|
+
**For a single-app module** (no sub-apps):
|
|
164
204
|
```json
|
|
165
205
|
{
|
|
166
206
|
"module": "{module-name}",
|
|
@@ -171,17 +211,42 @@ Write `.forge/module.json`:
|
|
|
171
211
|
}
|
|
172
212
|
```
|
|
173
213
|
|
|
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:
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"module": "{module-name}",
|
|
219
|
+
"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
|
+
]
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
174
232
|
Write `CLAUDE.md`:
|
|
175
233
|
```markdown
|
|
176
234
|
# {module-name}
|
|
177
235
|
|
|
178
236
|
{description}
|
|
179
237
|
|
|
180
|
-
|
|
181
|
-
|
|
238
|
+
_(Single app: show Stack + Port inline. Monorepo: replace with a table.)_
|
|
239
|
+
|
|
240
|
+
**Stack:** {stack} **Port:** {port}
|
|
241
|
+
|
|
242
|
+
— or for a monorepo —
|
|
243
|
+
|
|
244
|
+
| Module | Stack | Port | Path |
|
|
245
|
+
|--------|-------|------|------|
|
|
246
|
+
| {name} | {stack} | {port} | {path} |
|
|
182
247
|
|
|
183
248
|
## Forge Workflow
|
|
184
|
-
1. `/forge-tasks` — see all pending tasks for this module
|
|
249
|
+
1. `/forge-tasks` — see all pending tasks for this repo's module(s)
|
|
185
250
|
2. `/forge-implement` — implement a feature task by task
|
|
186
251
|
3. `/forge-contract-test` — run Specmatic contract tests
|
|
187
252
|
4. `/forge-done` — confirm tasks done + generate commit message
|
|
@@ -237,4 +302,8 @@ Run `git status` and confirm:
|
|
|
237
302
|
- Never overwrite existing files
|
|
238
303
|
- All written files must be complete — no unfilled placeholders
|
|
239
304
|
- `module` in module.json must exactly match the name in the spec repo's project.json
|
|
305
|
+
- **Extract, don't re-ask**: if the user's answer contains information for upcoming fields
|
|
306
|
+
(e.g. "it's a NestJS app on port 3000 at apps/auth-ui"), extract and fill those fields
|
|
307
|
+
silently — only ask about what is genuinely missing. Never ask a question the user has
|
|
308
|
+
already answered, even indirectly.
|
|
240
309
|
- If the user provides multiple answers in one message, accept them gracefully and move forward
|
|
@@ -22,7 +22,13 @@ Show all tasks assigned to this module across all features.
|
|
|
22
22
|
|
|
23
23
|
## Steps
|
|
24
24
|
|
|
25
|
-
|
|
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.
|
|
28
|
+
Submodule task headings use the submodule's own name — no parent prefix — so they are
|
|
29
|
+
identical to standalone module headings and can be promoted without touching tasks.md.
|
|
30
|
+
|
|
31
|
+
Scan `{spec_submodule_path}/features/*/tasks.md` → find all matching headings → extract tasks
|
|
26
32
|
with their checkbox state and the parent feature's `Status`.
|
|
27
33
|
|
|
28
34
|
```
|
|
@@ -42,6 +48,22 @@ Feature: user-profile [Open]
|
|
|
42
48
|
2 features · 4 remaining · 1 done
|
|
43
49
|
```
|
|
44
50
|
|
|
51
|
+
For a module with submodules, group tasks by submodule under each feature:
|
|
52
|
+
```
|
|
53
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
54
|
+
Tasks for: webapps (submodules: admin · landing · portal)
|
|
55
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
56
|
+
|
|
57
|
+
Feature: user-registration [Open]
|
|
58
|
+
[admin]
|
|
59
|
+
- [ ] TASK-3 [ui] Registration form
|
|
60
|
+
[portal]
|
|
61
|
+
- [ ] TASK-4 [ui] Registration confirmation page
|
|
62
|
+
|
|
63
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
64
|
+
1 feature · 2 remaining · 0 done
|
|
65
|
+
```
|
|
66
|
+
|
|
45
67
|
If no tasks reference this module across any feature:
|
|
46
68
|
> "No tasks found for `{module}`. Either no feature targets this module yet, or the specs
|
|
47
69
|
> submodule is out of date — try `git submodule update --remote specs`."
|