@nathapp/nax 0.27.1 → 0.28.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/docs/ROADMAP.md +29 -3
- package/nax/features/prompt-builder/prd.json +152 -0
- package/nax/features/prompt-builder/progress.txt +3 -0
- package/nax/status.json +14 -14
- package/package.json +1 -1
- package/src/cli/config.ts +40 -1
- package/src/cli/prompts.ts +18 -6
- package/src/config/defaults.ts +1 -0
- package/src/config/schemas.ts +10 -0
- package/src/config/types.ts +7 -0
- package/src/pipeline/stages/execution.ts +5 -0
- package/src/pipeline/stages/prompt.ts +13 -4
- package/src/precheck/checks-warnings.ts +37 -0
- package/src/precheck/checks.ts +1 -0
- package/src/precheck/index.ts +14 -7
- package/src/prompts/builder.ts +178 -0
- package/src/prompts/index.ts +2 -0
- package/src/prompts/loader.ts +43 -0
- package/src/prompts/sections/conventions.ts +15 -0
- package/src/prompts/sections/index.ts +11 -0
- package/src/prompts/sections/isolation.ts +24 -0
- package/src/prompts/sections/role-task.ts +32 -0
- package/src/prompts/sections/story.ts +13 -0
- package/src/prompts/sections/verdict.ts +70 -0
- package/src/prompts/templates/implementer.ts +6 -0
- package/src/prompts/templates/single-session.ts +6 -0
- package/src/prompts/templates/test-writer.ts +6 -0
- package/src/prompts/templates/verifier.ts +6 -0
- package/src/prompts/types.ts +21 -0
- package/src/tdd/session-runner.ts +12 -12
- package/test/integration/cli/cli-config-prompts-explain.test.ts +74 -0
- package/test/integration/prompts/pb-004-migration.test.ts +523 -0
- package/test/unit/precheck/checks-warnings.test.ts +114 -0
- package/test/unit/prompts/builder.test.ts +258 -0
- package/test/unit/prompts/loader.test.ts +355 -0
- package/test/unit/prompts/sections/conventions.test.ts +30 -0
- package/test/unit/prompts/sections/isolation.test.ts +35 -0
- package/test/unit/prompts/sections/role-task.test.ts +40 -0
- package/test/unit/prompts/sections/sections.test.ts +238 -0
- package/test/unit/prompts/sections/story.test.ts +45 -0
- package/test/unit/prompts/sections/verdict.test.ts +58 -0
package/docs/ROADMAP.md
CHANGED
|
@@ -135,6 +135,31 @@
|
|
|
135
135
|
|
|
136
136
|
---
|
|
137
137
|
|
|
138
|
+
|
|
139
|
+
## v0.28.0 — Prompt Builder
|
|
140
|
+
|
|
141
|
+
**Theme:** Unified, user-overridable prompt architecture replacing 11 scattered functions
|
|
142
|
+
**Status:** 🔲 Planned
|
|
143
|
+
**Spec:** `nax/features/prompt-builder/prd.json`
|
|
144
|
+
|
|
145
|
+
### Stories
|
|
146
|
+
- [ ] **PB-001:** PromptBuilder class with layered section architecture + fluent API
|
|
147
|
+
- [ ] **PB-002:** Typed sections: isolation, role-task, story, verdict, conventions
|
|
148
|
+
- [ ] **PB-003:** Default templates + user override loader + config schema (`prompts.overrides`)
|
|
149
|
+
- [ ] **PB-004:** Migrate all 6 user-facing prompt call sites to PromptBuilder
|
|
150
|
+
- [ ] **PB-005:** Document `prompts` config in `nax config --explain` + precheck validation
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## v0.27.1 — Pipeline Observability ✅ Shipped (2026-03-08)
|
|
155
|
+
|
|
156
|
+
**Theme:** Fix redundant verify stage + improve pipeline skip log messages
|
|
157
|
+
**Status:** ✅ Shipped (2026-03-08)
|
|
158
|
+
|
|
159
|
+
### Bugfixes
|
|
160
|
+
- [x] **BUG-054:** Skip pipeline verify stage when TDD full-suite gate already passed — `runFullSuiteGate()` now returns `boolean`, propagated via `ThreeSessionTddResult` → `executionStage` → `ctx.fullSuiteGatePassed` → `verifyStage.enabled()` returns false with reason "not needed (full-suite gate already passed)"
|
|
161
|
+
- [x] **BUG-055:** Pipeline skip messages now differentiate "not needed" from "disabled". Added optional `skipReason(ctx)` to `PipelineStage` interface; `rectify`, `autofix`, `regression`, `verify` stages all provide context-aware reasons
|
|
162
|
+
|
|
138
163
|
## v0.27.0 — Review Quality ✅ Shipped (2026-03-08)
|
|
139
164
|
|
|
140
165
|
**Theme:** Fix review stage reliability — dirty working tree false-positive, stale precheck, dead config fields
|
|
@@ -262,6 +287,7 @@
|
|
|
262
287
|
|
|
263
288
|
| Version | Theme | Date | Details |
|
|
264
289
|
|:---|:---|:---|:---|
|
|
290
|
+
| v0.27.1 | Pipeline Observability | 2026-03-08 | BUG-054: skip redundant verify after full-suite gate; BUG-055: differentiate skip reasons |
|
|
265
291
|
| v0.26.0 | Routing Persistence | 2026-03-08 | RRP-001–004: persist initial routing, initialComplexity, contentHash staleness detection, unit tests; BUG-052: structured logger in review/optimizer |
|
|
266
292
|
| v0.25.0 | Trigger Completion | 2026-03-07 | TC-001–004: run.complete event, crash recovery, headless formatter, trigger completion |
|
|
267
293
|
| v0.24.0 | Central Run Registry | 2026-03-07 | CRR-000–003: events writer, registry, nax runs CLI, nax logs --run global resolution |
|
|
@@ -334,8 +360,8 @@
|
|
|
334
360
|
- [x] ~~**BUG-050:** `checkOptionalCommands` precheck uses legacy config fields. Fixed in v0.27.0.~~
|
|
335
361
|
- [x] ~~**BUG-051:** `quality.commands.typecheck/lint` are dead config. Fixed in v0.27.0.~~
|
|
336
362
|
- [x] ~~**BUG-052:** `console.warn` in runtime pipeline code bypasses JSONL logger. Fixed in v0.26.0.~~
|
|
337
|
-
- [
|
|
338
|
-
- [
|
|
363
|
+
- [x] ~~**BUG-054:** Redundant scoped verify after TDD full-suite gate passes. Fixed in v0.27.1.~~ When rectification gate runs full test suite and passes, the pipeline verify stage re-runs scoped tests (subset). **Fix:** Skip verify if full-suite gate already passed.
|
|
364
|
+
- [x] ~~**BUG-055:** Pipeline skip messages conflate "not needed" with "disabled". Fixed in v0.27.1.~~ `runner.ts:54` logs "skipped (disabled)" for all stages where `enabled()` returns false, even if just because tests passed. **Fix:** Differentiate log message.
|
|
339
365
|
|
|
340
366
|
### Features
|
|
341
367
|
- [x] ~~`nax unlock` command~~
|
|
@@ -361,4 +387,4 @@ Sequential canary → stable: `v0.12.0-canary.0` → `canary.N` → `v0.12.0`
|
|
|
361
387
|
Canary: `npm publish --tag canary`
|
|
362
388
|
Stable: `npm publish` (latest)
|
|
363
389
|
|
|
364
|
-
*Last updated: 2026-03-08 (v0.27.
|
|
390
|
+
*Last updated: 2026-03-08 (v0.27.1 shipped — Pipeline Observability; v0.28.0 PRD ready — Prompt Builder)*
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project": "nax-prompt-builder",
|
|
3
|
+
"branchName": "feat/prompt-builder",
|
|
4
|
+
"feature": "prompt-builder",
|
|
5
|
+
"updatedAt": "2026-03-08T07:39:46.206Z",
|
|
6
|
+
"userStories": [
|
|
7
|
+
{
|
|
8
|
+
"id": "PB-001",
|
|
9
|
+
"title": "Create PromptBuilder class with layered section architecture",
|
|
10
|
+
"description": "Currently nax has 11 scattered prompt-building functions across src/tdd/prompts.ts and src/execution/prompts.ts. There is no unified entry point, no user override support, and adding a new prompt variable requires touching multiple files. Introduce a PromptBuilder class in src/prompts/builder.ts with a fluent API. The builder composes a prompt from ordered sections: (1) Constitution, (2) Role task, (3) User override OR default template body, (4) Story context, (5) Isolation rules, (6) Context markdown, (7) Conventions footer. Each section is a typed unit independently testable. The builder is the single entry point — all existing prompt-building calls are migrated to use it.",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
"src/prompts/builder.ts exports PromptBuilder class with fluent API: PromptBuilder.for(role, options).story(story).context(md).constitution(c).override(path).build()",
|
|
13
|
+
"PromptBuilder.build() returns Promise<string> — async to support file loading for overrides",
|
|
14
|
+
"Section precedence enforced: constitution first, role task prepended, isolation rules appended, story context always included, conventions footer always last",
|
|
15
|
+
"Non-overridable sections (isolation rules, story context, conventions footer) cannot be removed by user override",
|
|
16
|
+
"src/prompts/types.ts exports: PromptRole ('test-writer' | 'implementer' | 'verifier' | 'single-session'), PromptSection interface, PromptOptions type",
|
|
17
|
+
"Unit tests: verify section order for each role; verify non-overridable sections always present; verify missing override falls through to default template"
|
|
18
|
+
],
|
|
19
|
+
"complexity": "medium",
|
|
20
|
+
"status": "passed",
|
|
21
|
+
"tags": [
|
|
22
|
+
"feature",
|
|
23
|
+
"prompts",
|
|
24
|
+
"architecture"
|
|
25
|
+
],
|
|
26
|
+
"attempts": 0,
|
|
27
|
+
"priorErrors": [],
|
|
28
|
+
"priorFailures": [],
|
|
29
|
+
"escalations": [],
|
|
30
|
+
"dependencies": [],
|
|
31
|
+
"storyPoints": 1,
|
|
32
|
+
"routing": {
|
|
33
|
+
"complexity": "medium",
|
|
34
|
+
"initialComplexity": "medium",
|
|
35
|
+
"testStrategy": "three-session-tdd-lite",
|
|
36
|
+
"reasoning": "Class design with fluent API, async file loading, section composition/ordering logic, and section non-overridability enforcement.",
|
|
37
|
+
"contentHash": "0a1c55a2430989f408b99f7b75a82491fc6606eb736f734386e36c61ccc143c9"
|
|
38
|
+
},
|
|
39
|
+
"passes": true
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "PB-002",
|
|
43
|
+
"title": "Implement typed sections: isolation, role-task, story, verdict, conventions",
|
|
44
|
+
"description": "The PromptBuilder needs typed section builders that produce the non-overridable parts of each prompt. Five sections: (1) isolation.ts — generates isolation rules based on strict (no src/ access) or lite (may read src/, create stubs) mode; (2) role-task.ts — generates role definition for standard implementer (make failing tests pass) or lite implementer (write tests and implement); (3) story.ts — formats title, description, acceptance criteria; (4) verdict.ts — verifier verdict JSON schema instructions (non-overridable); (5) conventions.ts — bun test scoping warning and commit conventions.",
|
|
45
|
+
"acceptanceCriteria": [
|
|
46
|
+
"src/prompts/sections/isolation.ts exports buildIsolationSection(mode: 'strict' | 'lite'): string — strict forbids src/ modification, lite allows reading src/ and creating stubs",
|
|
47
|
+
"src/prompts/sections/role-task.ts exports buildRoleTaskSection(variant: 'standard' | 'lite'): string — standard says 'make failing tests pass, do not modify test files', lite says 'write tests first then implement'",
|
|
48
|
+
"src/prompts/sections/story.ts exports buildStorySection(story: UserStory): string — formats title, description, numbered acceptance criteria",
|
|
49
|
+
"src/prompts/sections/verdict.ts exports buildVerdictSection(story: UserStory): string — identical content to current buildVerifierPrompt verdict instructions",
|
|
50
|
+
"src/prompts/sections/conventions.ts exports buildConventionsSection(): string — includes bun test scoping warning and commit message instruction",
|
|
51
|
+
"Each section function is pure (no side effects, no file I/O) and independently unit-testable",
|
|
52
|
+
"Unit tests: isolation strict does not contain 'MAY read'; isolation lite contains 'MAY read src/'; role-task standard contains 'Do NOT modify test files'; role-task lite contains 'Write tests first'"
|
|
53
|
+
],
|
|
54
|
+
"complexity": "simple",
|
|
55
|
+
"status": "passed",
|
|
56
|
+
"tags": [
|
|
57
|
+
"feature",
|
|
58
|
+
"prompts",
|
|
59
|
+
"sections"
|
|
60
|
+
],
|
|
61
|
+
"attempts": 0,
|
|
62
|
+
"priorErrors": [],
|
|
63
|
+
"priorFailures": [],
|
|
64
|
+
"escalations": [],
|
|
65
|
+
"dependencies": [],
|
|
66
|
+
"storyPoints": 1,
|
|
67
|
+
"passes": true
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "PB-003",
|
|
71
|
+
"title": "Implement default templates and user override loader",
|
|
72
|
+
"description": "Each PromptRole needs a default template forming the overridable body of the prompt. Implement src/prompts/loader.ts which resolves and reads the user override file relative to workdir. If the path is missing or the file does not exist, the loader returns null and the default template is used. The NaxConfig schema gains a new optional prompts block: { overrides: { 'test-writer'?: string, 'implementer'?: string, 'verifier'?: string, 'single-session'?: string } }.",
|
|
73
|
+
"acceptanceCriteria": [
|
|
74
|
+
"src/prompts/templates/ contains test-writer.ts, implementer.ts, verifier.ts, single-session.ts — each exports a default template string (body section only, no story/isolation/conventions)",
|
|
75
|
+
"src/prompts/loader.ts exports loadOverride(role: PromptRole, workdir: string, config: NaxConfig): Promise<string | null>",
|
|
76
|
+
"Loader resolves path relative to workdir — e.g. '.nax/prompts/test-writer.md' -> '<workdir>/.nax/prompts/test-writer.md'",
|
|
77
|
+
"Loader returns null (not error) when config.prompts is absent, role key is absent, or file does not exist",
|
|
78
|
+
"Loader throws with clear message when file path is set but file is unreadable (permissions error)",
|
|
79
|
+
"src/config/types.ts NaxConfig gains optional prompts?: { overrides?: Partial<Record<PromptRole, string>> }",
|
|
80
|
+
"Unit tests: loader returns null when config.prompts undefined; returns null when file missing; returns content when file exists; throws on permission error"
|
|
81
|
+
],
|
|
82
|
+
"complexity": "simple",
|
|
83
|
+
"status": "passed",
|
|
84
|
+
"tags": [
|
|
85
|
+
"feature",
|
|
86
|
+
"prompts",
|
|
87
|
+
"config",
|
|
88
|
+
"loader"
|
|
89
|
+
],
|
|
90
|
+
"attempts": 0,
|
|
91
|
+
"priorErrors": [],
|
|
92
|
+
"priorFailures": [],
|
|
93
|
+
"escalations": [],
|
|
94
|
+
"dependencies": [],
|
|
95
|
+
"storyPoints": 1,
|
|
96
|
+
"passes": true
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "PB-004",
|
|
100
|
+
"title": "Migrate all existing prompt-building call sites to PromptBuilder",
|
|
101
|
+
"description": "Replace the 6 user-facing scattered prompt functions with PromptBuilder calls. Mapping: buildTestWriterPrompt -> PromptBuilder.for('test-writer', { isolation: 'strict' }); buildTestWriterLitePrompt -> PromptBuilder.for('test-writer', { isolation: 'lite' }); buildImplementerPrompt -> PromptBuilder.for('implementer', { variant: 'standard' }); buildImplementerLitePrompt -> PromptBuilder.for('implementer', { variant: 'lite' }); buildVerifierPrompt -> PromptBuilder.for('verifier'); buildSingleSessionPrompt -> PromptBuilder.for('single-session'). Internal prompts (rectification, routing, batch) remain unchanged.",
|
|
102
|
+
"acceptanceCriteria": [
|
|
103
|
+
"All 6 user-facing prompt functions replaced with PromptBuilder calls in their respective call sites",
|
|
104
|
+
"buildImplementerRectificationPrompt, buildRectificationPrompt, buildBatchPrompt (execution), buildRoutingPrompt, buildBatchPrompt (routing) remain unchanged",
|
|
105
|
+
"Generated prompt text for each role is semantically equivalent to previous output when no user override is set (no regression)",
|
|
106
|
+
"All call sites pass workdir and config so the loader can check for overrides",
|
|
107
|
+
"src/tdd/prompts.ts and src/execution/prompts.ts deleted or reduced to re-exports only if referenced by tests",
|
|
108
|
+
"Integration test: build each of the 6 roles with no override, verify output contains story title, acceptance criteria, and role-specific instructions"
|
|
109
|
+
],
|
|
110
|
+
"complexity": "medium",
|
|
111
|
+
"status": "pending",
|
|
112
|
+
"tags": [
|
|
113
|
+
"feature",
|
|
114
|
+
"prompts",
|
|
115
|
+
"migration",
|
|
116
|
+
"refactor"
|
|
117
|
+
],
|
|
118
|
+
"attempts": 0,
|
|
119
|
+
"priorErrors": [],
|
|
120
|
+
"priorFailures": [],
|
|
121
|
+
"escalations": [],
|
|
122
|
+
"dependencies": [],
|
|
123
|
+
"storyPoints": 1
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"id": "PB-005",
|
|
127
|
+
"title": "Document prompts config in nax config --explain and add precheck validation",
|
|
128
|
+
"description": "Users need to discover the prompts.overrides config block. Update nax config --explain to document it with example paths. Add a precheck warning (non-blocking) when a configured override file path does not exist at run start — surfaces the issue early rather than silently falling back to the default at runtime.",
|
|
129
|
+
"acceptanceCriteria": [
|
|
130
|
+
"nax config --explain output includes a 'prompts' section describing overrides for each role with example: '.nax/prompts/test-writer.md'",
|
|
131
|
+
"src/precheck/checks-warnings.ts adds check: for each key in config.prompts?.overrides, if resolved file does not exist, emit warning 'Prompt override file not found for role <role>: <resolved-path>'",
|
|
132
|
+
"Precheck warning is non-blocking — run continues with default template",
|
|
133
|
+
"Precheck skipped when config.prompts absent or overrides empty",
|
|
134
|
+
"Unit tests: override path exists -> no warning; override path set but file missing -> warning emitted; config.prompts absent -> no warning"
|
|
135
|
+
],
|
|
136
|
+
"complexity": "simple",
|
|
137
|
+
"status": "pending",
|
|
138
|
+
"tags": [
|
|
139
|
+
"feature",
|
|
140
|
+
"prompts",
|
|
141
|
+
"docs",
|
|
142
|
+
"precheck"
|
|
143
|
+
],
|
|
144
|
+
"attempts": 0,
|
|
145
|
+
"priorErrors": [],
|
|
146
|
+
"priorFailures": [],
|
|
147
|
+
"escalations": [],
|
|
148
|
+
"dependencies": [],
|
|
149
|
+
"storyPoints": 1
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
[2026-03-08T07:17:53.244Z] PB-001 — PASSED — Create PromptBuilder class with layered section architecture — Cost: $0.3280
|
|
2
|
+
[2026-03-08T07:29:04.611Z] PB-002 — PASSED — Implement typed sections: isolation, role-task, story, verdict, conventions — Cost: $0.1821
|
|
3
|
+
[2026-03-08T07:39:44.184Z] PB-003 — PASSED — Implement default templates and user override loader — Cost: $0.2823
|
package/nax/status.json
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
3
|
"run": {
|
|
4
|
-
"id": "run-2026-03-
|
|
5
|
-
"feature": "
|
|
6
|
-
"startedAt": "2026-03-
|
|
4
|
+
"id": "run-2026-03-08T07-06-40-645Z",
|
|
5
|
+
"feature": "prompt-builder",
|
|
6
|
+
"startedAt": "2026-03-08T07:06:40.645Z",
|
|
7
7
|
"status": "running",
|
|
8
8
|
"dryRun": false,
|
|
9
|
-
"pid":
|
|
9
|
+
"pid": 42182
|
|
10
10
|
},
|
|
11
11
|
"progress": {
|
|
12
|
-
"total":
|
|
13
|
-
"passed":
|
|
12
|
+
"total": 5,
|
|
13
|
+
"passed": 3,
|
|
14
14
|
"failed": 0,
|
|
15
15
|
"paused": 0,
|
|
16
16
|
"blocked": 0,
|
|
17
|
-
"pending":
|
|
17
|
+
"pending": 2
|
|
18
18
|
},
|
|
19
19
|
"cost": {
|
|
20
|
-
"spent": 0.
|
|
20
|
+
"spent": 0.7924205000000001,
|
|
21
21
|
"limit": 8
|
|
22
22
|
},
|
|
23
23
|
"current": {
|
|
24
|
-
"storyId": "
|
|
25
|
-
"title": "
|
|
24
|
+
"storyId": "PB-004",
|
|
25
|
+
"title": "Migrate all existing prompt-building call sites to PromptBuilder",
|
|
26
26
|
"complexity": "medium",
|
|
27
27
|
"tddStrategy": "test-after",
|
|
28
28
|
"model": "balanced",
|
|
29
29
|
"attempt": 1,
|
|
30
30
|
"phase": "routing"
|
|
31
31
|
},
|
|
32
|
-
"iterations":
|
|
33
|
-
"updatedAt": "2026-03-
|
|
34
|
-
"durationMs":
|
|
35
|
-
"lastHeartbeat": "2026-03-
|
|
32
|
+
"iterations": 4,
|
|
33
|
+
"updatedAt": "2026-03-08T07:59:01.999Z",
|
|
34
|
+
"durationMs": 3141354,
|
|
35
|
+
"lastHeartbeat": "2026-03-08T07:59:01.999Z"
|
|
36
36
|
}
|
package/package.json
CHANGED
package/src/cli/config.ts
CHANGED
|
@@ -183,6 +183,14 @@ const FIELD_DESCRIPTIONS: Record<string, string> = {
|
|
|
183
183
|
"precheck.storySizeGate.maxAcCount": "Max acceptance criteria count before flagging",
|
|
184
184
|
"precheck.storySizeGate.maxDescriptionLength": "Max description character length before flagging",
|
|
185
185
|
"precheck.storySizeGate.maxBulletPoints": "Max bullet point count before flagging",
|
|
186
|
+
|
|
187
|
+
// Prompts
|
|
188
|
+
prompts: "Prompt template overrides (PB-003: PromptBuilder)",
|
|
189
|
+
"prompts.overrides": "Custom prompt template files for specific roles",
|
|
190
|
+
"prompts.overrides.test-writer": 'Path to custom test-writer prompt (e.g., ".nax/prompts/test-writer.md")',
|
|
191
|
+
"prompts.overrides.implementer": 'Path to custom implementer prompt (e.g., ".nax/prompts/implementer.md")',
|
|
192
|
+
"prompts.overrides.verifier": 'Path to custom verifier prompt (e.g., ".nax/prompts/verifier.md")',
|
|
193
|
+
"prompts.overrides.single-session": 'Path to custom single-session prompt (e.g., ".nax/prompts/single-session.md")',
|
|
186
194
|
};
|
|
187
195
|
|
|
188
196
|
/** Options for config command */
|
|
@@ -473,6 +481,34 @@ function displayConfigWithDescriptions(
|
|
|
473
481
|
// Handle objects
|
|
474
482
|
const entries = Object.entries(obj as Record<string, unknown>);
|
|
475
483
|
|
|
484
|
+
// Special handling for prompts section: always show overrides documentation
|
|
485
|
+
const objAsRecord = obj as Record<string, unknown>;
|
|
486
|
+
const isPromptsSection = path.join(".") === "prompts";
|
|
487
|
+
if (isPromptsSection && !objAsRecord.overrides) {
|
|
488
|
+
// Add prompts.overrides documentation even if not in config
|
|
489
|
+
const description = FIELD_DESCRIPTIONS["prompts.overrides"];
|
|
490
|
+
if (description) {
|
|
491
|
+
console.log(`${indentStr}# prompts.overrides: ${description}`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Show role examples
|
|
495
|
+
const roles = ["test-writer", "implementer", "verifier", "single-session"];
|
|
496
|
+
console.log(`${indentStr}overrides:`);
|
|
497
|
+
for (const role of roles) {
|
|
498
|
+
const roleDesc = FIELD_DESCRIPTIONS[`prompts.overrides.${role}`];
|
|
499
|
+
if (roleDesc) {
|
|
500
|
+
console.log(`${indentStr} # ${roleDesc}`);
|
|
501
|
+
// Extract the example path from description
|
|
502
|
+
const match = roleDesc.match(/e\.g\., "([^"]+)"/);
|
|
503
|
+
if (match) {
|
|
504
|
+
console.log(`${indentStr} # ${role}: "${match[1]}"`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
console.log();
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
476
512
|
for (let i = 0; i < entries.length; i++) {
|
|
477
513
|
const [key, value] = entries[i];
|
|
478
514
|
const currentPath = [...path, key];
|
|
@@ -481,7 +517,10 @@ function displayConfigWithDescriptions(
|
|
|
481
517
|
|
|
482
518
|
// Display description comment if available
|
|
483
519
|
if (description) {
|
|
484
|
-
|
|
520
|
+
// Include path only for prompts section (where tests expect "prompts.overrides" to appear)
|
|
521
|
+
const isPromptsSubSection = currentPathStr.startsWith("prompts.");
|
|
522
|
+
const comment = isPromptsSubSection ? `${currentPathStr}: ${description}` : description;
|
|
523
|
+
console.log(`${indentStr}# ${comment}`);
|
|
485
524
|
}
|
|
486
525
|
|
|
487
526
|
// Handle nested objects
|
package/src/cli/prompts.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { PipelineContext } from "../pipeline";
|
|
|
17
17
|
import { constitutionStage, contextStage, promptStage, routingStage } from "../pipeline/stages";
|
|
18
18
|
import type { UserStory } from "../prd";
|
|
19
19
|
import { loadPRD } from "../prd";
|
|
20
|
+
import { PromptBuilder } from "../prompts";
|
|
20
21
|
|
|
21
22
|
export interface PromptsCommandOptions {
|
|
22
23
|
/** Feature name */
|
|
@@ -253,14 +254,25 @@ async function handleThreeSessionTddPrompts(
|
|
|
253
254
|
outputDir: string | undefined,
|
|
254
255
|
logger: ReturnType<typeof getLogger>,
|
|
255
256
|
): Promise<void> {
|
|
256
|
-
//
|
|
257
|
-
const
|
|
257
|
+
// Build prompts for each session using PromptBuilder
|
|
258
|
+
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
259
|
+
PromptBuilder.for("test-writer", { isolation: "strict" })
|
|
260
|
+
.withLoader(ctx.workdir, ctx.config)
|
|
261
|
+
.story(story)
|
|
262
|
+
.context(ctx.contextMarkdown)
|
|
263
|
+
.build(),
|
|
264
|
+
PromptBuilder.for("implementer", { variant: "standard" })
|
|
265
|
+
.withLoader(ctx.workdir, ctx.config)
|
|
266
|
+
.story(story)
|
|
267
|
+
.context(ctx.contextMarkdown)
|
|
268
|
+
.build(),
|
|
269
|
+
PromptBuilder.for("verifier").withLoader(ctx.workdir, ctx.config).story(story).build(),
|
|
270
|
+
]);
|
|
258
271
|
|
|
259
|
-
// Build prompts for each session
|
|
260
272
|
const sessions = [
|
|
261
|
-
{ role: "test-writer", prompt:
|
|
262
|
-
{ role: "implementer", prompt:
|
|
263
|
-
{ role: "verifier", prompt:
|
|
273
|
+
{ role: "test-writer", prompt: testWriterPrompt },
|
|
274
|
+
{ role: "implementer", prompt: implementerPrompt },
|
|
275
|
+
{ role: "verifier", prompt: verifierPrompt },
|
|
264
276
|
];
|
|
265
277
|
|
|
266
278
|
for (const session of sessions) {
|
package/src/config/defaults.ts
CHANGED
package/src/config/schemas.ts
CHANGED
|
@@ -290,6 +290,15 @@ const PrecheckConfigSchema = z.object({
|
|
|
290
290
|
storySizeGate: StorySizeGateConfigSchema,
|
|
291
291
|
});
|
|
292
292
|
|
|
293
|
+
const PromptsConfigSchema = z.object({
|
|
294
|
+
overrides: z
|
|
295
|
+
.record(
|
|
296
|
+
z.enum(["test-writer", "implementer", "verifier", "single-session"]),
|
|
297
|
+
z.string().min(1, "Override path must be non-empty"),
|
|
298
|
+
)
|
|
299
|
+
.optional(),
|
|
300
|
+
});
|
|
301
|
+
|
|
293
302
|
export const NaxConfigSchema = z
|
|
294
303
|
.object({
|
|
295
304
|
version: z.number(),
|
|
@@ -310,6 +319,7 @@ export const NaxConfigSchema = z
|
|
|
310
319
|
hooks: HooksConfigSchema.optional(),
|
|
311
320
|
interaction: InteractionConfigSchema.optional(),
|
|
312
321
|
precheck: PrecheckConfigSchema.optional(),
|
|
322
|
+
prompts: PromptsConfigSchema.optional(),
|
|
313
323
|
})
|
|
314
324
|
.refine((data) => data.version === 1, {
|
|
315
325
|
message: "Invalid version: expected 1",
|
package/src/config/types.ts
CHANGED
|
@@ -406,6 +406,11 @@ export interface RoutingConfig {
|
|
|
406
406
|
llm?: LlmRoutingConfig;
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
+
/** Prompt overrides config (PB-003) */
|
|
410
|
+
export interface PromptsConfig {
|
|
411
|
+
overrides?: Partial<Record<"test-writer" | "implementer" | "verifier" | "single-session", string>>;
|
|
412
|
+
}
|
|
413
|
+
|
|
409
414
|
/** Full nax configuration */
|
|
410
415
|
export interface NaxConfig {
|
|
411
416
|
/** Schema version */
|
|
@@ -444,6 +449,8 @@ export interface NaxConfig {
|
|
|
444
449
|
interaction?: InteractionConfig;
|
|
445
450
|
/** Precheck settings (v0.16.0) */
|
|
446
451
|
precheck?: PrecheckConfig;
|
|
452
|
+
/** Prompt override settings (PB-003) */
|
|
453
|
+
prompts?: PromptsConfig;
|
|
447
454
|
}
|
|
448
455
|
|
|
449
456
|
/** Resolve a ModelEntry (string shorthand or full object) into a ModelDef */
|
|
@@ -147,6 +147,11 @@ export const executionStage: PipelineStage = {
|
|
|
147
147
|
durationMs: 0, // TDD result doesn't track total duration
|
|
148
148
|
};
|
|
149
149
|
|
|
150
|
+
// Propagate full-suite gate result so verify stage can skip redundant run (BUG-054)
|
|
151
|
+
if (tddResult.fullSuiteGatePassed) {
|
|
152
|
+
ctx.fullSuiteGatePassed = true;
|
|
153
|
+
}
|
|
154
|
+
|
|
150
155
|
if (!tddResult.success) {
|
|
151
156
|
// Store failure category in context for runner to use at max-attempts decision
|
|
152
157
|
ctx.tddFailureCategory = tddResult.failureCategory;
|
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import { buildBatchPrompt
|
|
24
|
+
import { buildBatchPrompt } from "../../execution/prompts";
|
|
25
25
|
import { getLogger } from "../../logger";
|
|
26
|
+
import { PromptBuilder } from "../../prompts";
|
|
26
27
|
import type { PipelineContext, PipelineStage, StageResult } from "../types";
|
|
27
28
|
|
|
28
29
|
export const promptStage: PipelineStage = {
|
|
@@ -34,9 +35,17 @@ export const promptStage: PipelineStage = {
|
|
|
34
35
|
const logger = getLogger();
|
|
35
36
|
const isBatch = ctx.stories.length > 1;
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
let prompt: string;
|
|
39
|
+
if (isBatch) {
|
|
40
|
+
prompt = buildBatchPrompt(ctx.stories, ctx.contextMarkdown, ctx.constitution);
|
|
41
|
+
} else {
|
|
42
|
+
const builder = PromptBuilder.for("single-session")
|
|
43
|
+
.withLoader(ctx.workdir, ctx.config)
|
|
44
|
+
.story(ctx.story)
|
|
45
|
+
.context(ctx.contextMarkdown)
|
|
46
|
+
.constitution(ctx.constitution?.content);
|
|
47
|
+
prompt = await builder.build();
|
|
48
|
+
}
|
|
40
49
|
|
|
41
50
|
ctx.prompt = prompt;
|
|
42
51
|
|
|
@@ -140,3 +140,40 @@ export async function checkGitignoreCoversNax(workdir: string): Promise<Check> {
|
|
|
140
140
|
message: passed ? ".gitignore covers nax runtime files" : `.gitignore missing patterns: ${missing.join(", ")}`,
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if configured prompt override files exist.
|
|
146
|
+
*
|
|
147
|
+
* For each role in config.prompts.overrides, verify the file exists.
|
|
148
|
+
* Emits one warning per missing file (non-blocking).
|
|
149
|
+
* Returns empty array if config.prompts is absent or overrides is empty.
|
|
150
|
+
*
|
|
151
|
+
* @param config - nax configuration
|
|
152
|
+
* @param workdir - working directory for resolving relative paths
|
|
153
|
+
* @returns Array of warning checks (one per missing file)
|
|
154
|
+
*/
|
|
155
|
+
export async function checkPromptOverrideFiles(config: NaxConfig, workdir: string): Promise<Check[]> {
|
|
156
|
+
// Skip if prompts config is absent or overrides is empty
|
|
157
|
+
if (!config.prompts?.overrides || Object.keys(config.prompts.overrides).length === 0) {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const checks: Check[] = [];
|
|
162
|
+
|
|
163
|
+
// Check each override file
|
|
164
|
+
for (const [role, relativePath] of Object.entries(config.prompts.overrides)) {
|
|
165
|
+
const resolvedPath = `${workdir}/${relativePath}`;
|
|
166
|
+
const exists = existsSync(resolvedPath);
|
|
167
|
+
|
|
168
|
+
if (!exists) {
|
|
169
|
+
checks.push({
|
|
170
|
+
name: `prompt-override-${role}`,
|
|
171
|
+
tier: "warning",
|
|
172
|
+
passed: false,
|
|
173
|
+
message: `Prompt override file not found for role ${role}: ${resolvedPath}`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return checks;
|
|
179
|
+
}
|
package/src/precheck/checks.ts
CHANGED
package/src/precheck/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
checkOptionalCommands,
|
|
21
21
|
checkPRDValid,
|
|
22
22
|
checkPendingStories,
|
|
23
|
+
checkPromptOverrideFiles,
|
|
23
24
|
checkStaleLock,
|
|
24
25
|
checkTestCommand,
|
|
25
26
|
checkTypecheckCommand,
|
|
@@ -142,19 +143,25 @@ export async function runPrecheck(
|
|
|
142
143
|
() => checkPendingStories(prd),
|
|
143
144
|
() => checkOptionalCommands(config),
|
|
144
145
|
() => checkGitignoreCoversNax(workdir),
|
|
146
|
+
() => checkPromptOverrideFiles(config, workdir),
|
|
145
147
|
];
|
|
146
148
|
|
|
147
149
|
for (const checkFn of tier2Checks) {
|
|
148
150
|
const result = await checkFn();
|
|
149
151
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
// Handle both single checks and arrays of checks
|
|
153
|
+
const checksToProcess = Array.isArray(result) ? result : [result];
|
|
154
|
+
|
|
155
|
+
for (const check of checksToProcess) {
|
|
156
|
+
if (format === "human") {
|
|
157
|
+
printCheckResult(check);
|
|
158
|
+
}
|
|
153
159
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
if (check.passed) {
|
|
161
|
+
passed.push(check);
|
|
162
|
+
} else {
|
|
163
|
+
warnings.push(check);
|
|
164
|
+
}
|
|
158
165
|
}
|
|
159
166
|
}
|
|
160
167
|
|