@nathapp/nax 0.27.0 → 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/CLAUDE.md +38 -8
- package/docs/ROADMAP.md +42 -17
- 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/runner.ts +2 -1
- package/src/pipeline/stages/autofix.ts +5 -0
- package/src/pipeline/stages/execution.ts +5 -0
- package/src/pipeline/stages/prompt.ts +13 -4
- package/src/pipeline/stages/rectify.ts +5 -0
- package/src/pipeline/stages/regression.ts +6 -1
- package/src/pipeline/stages/verify.ts +2 -1
- package/src/pipeline/types.ts +9 -0
- 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/orchestrator.ts +11 -1
- package/src/tdd/rectification-gate.ts +18 -13
- package/src/tdd/session-runner.ts +12 -12
- package/src/tdd/types.ts +2 -0
- 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/CLAUDE.md
CHANGED
|
@@ -92,16 +92,46 @@ Runner.run() [src/execution/runner.ts — thin orchestrator only]
|
|
|
92
92
|
2. **Plan complex tasks**: for multi-file changes, write a short plan before implementing.
|
|
93
93
|
3. **Implement in small chunks**: one logical concern per commit.
|
|
94
94
|
|
|
95
|
-
## Code Intelligence (Solograph MCP)
|
|
95
|
+
## Code Intelligence (Solograph MCP) — MANDATORY
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
**Always use solograph MCP tools before writing code or analyzing architecture.** Do NOT use `web_search` or `kb_search` as substitutes.
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
|
102
|
-
|
|
103
|
-
| `codegraph_query` |
|
|
104
|
-
| `
|
|
99
|
+
### Tool Selection Guide
|
|
100
|
+
|
|
101
|
+
| Tool | Capability | When to Use | Availability |
|
|
102
|
+
|:-----|:-----------|:-----------|:-------------|
|
|
103
|
+
| `codegraph_query` | Structural queries (Cypher) — find calls, dependencies, imports | **Preferred for dependency analysis, call tracing, symbol lookup** | ✅ Always works (in-memory graph) |
|
|
104
|
+
| `project_code_search` | Semantic search (Redis vector DB) — pattern matching by meaning | Natural language queries like "find auth patterns" | ⚠️ Requires explicit `project_code_reindex` + Redis daemon |
|
|
105
|
+
| `codegraph_explain` | Architecture overview for unfamiliar subsystems | Understand module relationships before major changes | ✅ Always works |
|
|
106
|
+
| `project_code_reindex` | Index project for semantic search | After creating/deleting source files | ✅ Always works |
|
|
107
|
+
|
|
108
|
+
### Recommended Workflow
|
|
109
|
+
|
|
110
|
+
For nax, **prefer `codegraph_query`** for routine tasks:
|
|
111
|
+
- Finding where functions are called (`calculateAggregateMetrics` called by `status-cost.ts`)
|
|
112
|
+
- Analyzing dependencies before refactoring
|
|
113
|
+
- Tracing import/export chains
|
|
114
|
+
- Querying symbol definitions and relationships
|
|
115
|
+
|
|
116
|
+
**Use `project_code_search` only if:**
|
|
117
|
+
- You need semantic similarity ("find authentication patterns")
|
|
118
|
+
- Redis is indexed and running (not guaranteed in all sessions)
|
|
119
|
+
|
|
120
|
+
### Example Queries
|
|
121
|
+
|
|
122
|
+
```cypher
|
|
123
|
+
-- Find files calling calculateAggregateMetrics
|
|
124
|
+
MATCH (f:File)-[:CALLS]->(s:Symbol {name: "calculateAggregateMetrics"})
|
|
125
|
+
RETURN f.path
|
|
126
|
+
|
|
127
|
+
-- Find all imports of aggregator.ts
|
|
128
|
+
MATCH (f:File)-[:IMPORTS]->(target:File {path: "src/metrics/aggregator.ts"})
|
|
129
|
+
RETURN f.path
|
|
130
|
+
|
|
131
|
+
-- Find symbols defined in a file
|
|
132
|
+
MATCH (f:File {path: "src/metrics/aggregator.ts"})-[:DEFINES]->(s:Symbol)
|
|
133
|
+
RETURN s.name, s.type
|
|
134
|
+
```
|
|
105
135
|
|
|
106
136
|
## Coding Standards & Forbidden Patterns
|
|
107
137
|
|
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 |
|
|
@@ -321,22 +347,21 @@
|
|
|
321
347
|
- [x] ~~**BUG-022:** Story interleaving — `getNextStory()` round-robins instead of exhausting retries on current story → fixed in v0.18.0~~
|
|
322
348
|
- [x] ~~**BUG-023:** Agent failure silent — no exitCode/stderr in JSONL → fixed in v0.18.0~~
|
|
323
349
|
- [x] ~~**BUG-025:** `needsHumanReview` not triggering interactive plugin → fixed in v0.18.0~~
|
|
324
|
-
|
|
325
|
-
- [x]
|
|
326
|
-
- [x]
|
|
327
|
-
- [x]
|
|
328
|
-
- [x]
|
|
329
|
-
- [x]
|
|
330
|
-
|
|
331
|
-
- [x] ~~**BUG-
|
|
332
|
-
- [x] ~~**BUG-
|
|
333
|
-
- [x] ~~**BUG-
|
|
334
|
-
- [x] ~~**BUG-
|
|
335
|
-
- [
|
|
336
|
-
- [
|
|
337
|
-
- [
|
|
338
|
-
- [
|
|
339
|
-
- [ ] **BUG-051:** `quality.commands.typecheck` and `quality.commands.lint` are dead config — silently ignored. `QualityConfig.commands.{typecheck,lint}` exist in the type definition and are documented in `nax config --explain`, but are never read by any runtime code. The review runner reads only `review.commands.typecheck/lint`. Users who set `quality.commands.typecheck` get no effect. **Location:** `src/config/types.ts` (QualityConfig), `src/review/runner.ts:resolveCommand()`. **Fix:** Either (A) remove the dead fields from `QualityConfig` and update docs, or (B) consolidate — make review runner read from `quality.commands` and deprecate `review.commands`.
|
|
350
|
+
- [x] ~~**BUG-029:** Escalation resets story to `pending`. Fixed.~~
|
|
351
|
+
- [x] ~~**BUG-030:** Review lint failure resets. Fixed.~~
|
|
352
|
+
- [x] ~~**BUG-031:** Keyword fallback classifier inconsistency. Fixed.~~
|
|
353
|
+
- [x] ~~**BUG-032:** Routing stage overrides escalated modelTier. Fixed.~~
|
|
354
|
+
- [x] ~~**BUG-033:** LLM routing timeout/retry. Fixed.~~
|
|
355
|
+
- [x] ~~**BUG-037:** Test output summary (verify stage) tail. Fixed.~~
|
|
356
|
+
- [x] ~~**BUG-038:** smart-runner over-matching. Fixed.~~
|
|
357
|
+
- [x] ~~**BUG-043:** Scoped test command construction. Fixed.~~
|
|
358
|
+
- [x] ~~**BUG-044:** Scoped/full-suite test command logging. Fixed.~~
|
|
359
|
+
- [x] ~~**BUG-049:** Review typecheck runs on dirty working tree. Fixed in v0.27.0.~~
|
|
360
|
+
- [x] ~~**BUG-050:** `checkOptionalCommands` precheck uses legacy config fields. Fixed in v0.27.0.~~
|
|
361
|
+
- [x] ~~**BUG-051:** `quality.commands.typecheck/lint` are dead config. Fixed in v0.27.0.~~
|
|
362
|
+
- [x] ~~**BUG-052:** `console.warn` in runtime pipeline code bypasses JSONL logger. Fixed in v0.26.0.~~
|
|
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.
|
|
340
365
|
|
|
341
366
|
### Features
|
|
342
367
|
- [x] ~~`nax unlock` command~~
|
|
@@ -362,4 +387,4 @@ Sequential canary → stable: `v0.12.0-canary.0` → `canary.N` → `v0.12.0`
|
|
|
362
387
|
Canary: `npm publish --tag canary`
|
|
363
388
|
Stable: `npm publish` (latest)
|
|
364
389
|
|
|
365
|
-
*Last updated: 2026-03-
|
|
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 */
|
package/src/pipeline/runner.ts
CHANGED
|
@@ -51,7 +51,8 @@ export async function runPipeline(
|
|
|
51
51
|
|
|
52
52
|
// Skip disabled stages
|
|
53
53
|
if (!stage.enabled(context)) {
|
|
54
|
-
|
|
54
|
+
const reason = stage.skipReason?.(context) ?? "disabled";
|
|
55
|
+
logger.debug("pipeline", `Stage "${stage.name}" skipped (${reason})`);
|
|
55
56
|
i++;
|
|
56
57
|
continue;
|
|
57
58
|
}
|
|
@@ -29,6 +29,11 @@ export const autofixStage: PipelineStage = {
|
|
|
29
29
|
return autofixEnabled;
|
|
30
30
|
},
|
|
31
31
|
|
|
32
|
+
skipReason(ctx: PipelineContext): string {
|
|
33
|
+
if (!ctx.reviewResult || ctx.reviewResult.success) return "not needed (review passed)";
|
|
34
|
+
return "disabled (autofix not enabled in config)";
|
|
35
|
+
},
|
|
36
|
+
|
|
32
37
|
async execute(ctx: PipelineContext): Promise<StageResult> {
|
|
33
38
|
const logger = getLogger();
|
|
34
39
|
const { reviewResult } = ctx;
|
|
@@ -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
|
|
|
@@ -27,6 +27,11 @@ export const rectifyStage: PipelineStage = {
|
|
|
27
27
|
return ctx.config.execution.rectification?.enabled ?? false;
|
|
28
28
|
},
|
|
29
29
|
|
|
30
|
+
skipReason(ctx: PipelineContext): string {
|
|
31
|
+
if (!ctx.verifyResult || ctx.verifyResult.success) return "not needed (verify passed)";
|
|
32
|
+
return "disabled (rectification not enabled in config)";
|
|
33
|
+
},
|
|
34
|
+
|
|
30
35
|
async execute(ctx: PipelineContext): Promise<StageResult> {
|
|
31
36
|
const logger = getLogger();
|
|
32
37
|
const { verifyResult } = ctx;
|