Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions .firecrawl/devin-2-pricing.md

Large diffs are not rendered by default.

40 changes: 20 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: '1.26.4'

Expand Down Expand Up @@ -107,7 +107,7 @@ jobs:
exit $FAIL

- name: Upload coverage
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: coverage
path: coverage.out
Expand All @@ -117,15 +117,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: '1.26.4'

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: v2.12.2

Expand All @@ -137,18 +137,18 @@ jobs:
os: [ubuntu-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: '1.26.4'

- name: Build binary
run: go build -o nxd ./cmd/nxd

- name: Upload binary
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: nxd-${{ matrix.os }}
path: nxd
Expand All @@ -161,7 +161,7 @@ jobs:
# scripts/check-leaks.sh when adding new forbidden tokens.
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Run leak check
run: bash scripts/check-leaks.sh
Expand All @@ -173,7 +173,7 @@ jobs:
continue-on-error: true
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Build container image
run: docker build -t nxd:ci .
Expand All @@ -190,10 +190,10 @@ jobs:
continue-on-error: true
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: '1.26.4'

Expand All @@ -215,10 +215,10 @@ jobs:
# alone can't because they stub the bridge.
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.11'

Expand All @@ -241,10 +241,10 @@ jobs:
continue-on-error: true
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: '1.26.4'

Expand All @@ -261,17 +261,17 @@ jobs:
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0

- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: '1.26.4'

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
version: '~> v2'
args: release --clean
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/nxd-automate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: '1.23'

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/traffic-stats.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: main

Expand Down
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ make mempalace-check # smoke the MemPalace bridge end-to-end
- Native Windows: all read-only commands work (`status`, `dashboard`, `doctor`, `config`, `events`, `metrics`, `report`, `projects`). Full agent pipeline (`req`/`resume`) needs tmux → run inside WSL2.
- Platform-specific code lives in `_unix.go` / `_windows.go` build-tagged pairs: `internal/cli/req_*.go` (daemon detach), `internal/engine/lockfile_*.go` (advisory lock + process liveness), `internal/devdb/docker/host_*.go` (docker default host). Shell command exec goes through `internal/shellexec` (`sh -c` on Unix, `cmd.exe /C` on Windows, override with `NXD_SHELL`).

## Current State (2026-07-02) — factory completeness: docs subsystem, completion gate, frontend skill

- **Requirement-completion gate** (`engine/completion_gate.go` + `engine/verification_loop.go`): REQ_COMPLETED is only emitted after the composed mainline verifies green (deps install, build, tests, hallucination/conflict-marker scan). A red mainline gets up to `qa.completion_fix_cycles` (default 2) auto-fix agent cycles; still red → **REQ_BLOCKED** (requirement status `blocked`), gaps written to `.nxd-fix-gaps.md`, resume with `--godmode` after addressing. Nil LLM client ⇒ hard gate (verify once, block on red — completing on a red build is impossible regardless of wiring). Config: `qa.disable_completion_gate` (default false = ON), `qa.completion_fix_cycles` (0→2, negative→hard gate). Wired in `resume.go`; the local checkout is pulled to the composed mainline (`pullBaseAfterMerge`, `engine/monitor_pull.go`) before the gate verifies.
- **Docs subsystem** (`engine/doc_generator.go` + `svg_docs.go` + `factory_docs.go` + `factory_docs_adr.go`): after all stories merge, generates/updates README.md and backstops the full documentation set — `docs/architecture.svg` + `docs/sequence.svg` as REAL rendered SVG (validated, Mermaid rejected, up to 3 retry attempts feeding the validation error back), `docs/training.md` (only if the agent didn't supply one), `docs/adr/` Architecture Decision Records (JSON-validated, one file per decision + index), and a fully deterministic `docs/README.md` index. Best-effort — a model failure logs and skips, never blocking completion. Wired via `Monitor.SetDocGenerator` in resume.go.
- **Planner factory stories** (default ON, `planning.emit_integration_story` / `planning.emit_scribe_story`): every persisted plan appends (a) an integration story that wires all components into the real entry point, bridges interface mismatches with adapters, and adds a boot-the-app smoke test — closing the compose gap where unit tests pass against mocks but the whole never runs; (b) a scribe story owning README.md + docs/ that authors the documentation set up front (greenfield-aware: existing READMEs only edited inside `<!-- nxd:scribe:start/end -->` markers). Ephemeral estimates skip both. Tests asserting exact story counts must set both flags false.
- **Frontend design skill** (`agent/frontend.go` + `engine/detect.go`): UI-facing stories (owned-file extensions + whole-word keyword regex) get `agent.FrontendDesignBrief` injected into their goal prompt — token-first two-pass design planning, one signature element, named banned AI-default looks, WCAG accessibility floor, copy-as-design-material. Threaded through the CLI-runtime, native-runtime, AND retry prompt paths (`TestExecutor_WiresFrontendDetection`). The planner requires the first UI story to establish a design-token foundation.
- **Security agent deltas**: `RunScanners` now returns a fourth `failed` list — a scanner that ran but errored (crash/timeout/parse) is logged and reported as coverage LOST, never counted as a clean run; `Report.Failed` renders in the markdown summary. `KnownScanners()` + `InstallHint()` expose the registry. Coverage: internal/security at 98.3% via a fake-scanner harness (shell scripts on a controlled PATH emitting canned tool output) driving RunScanners end-to-end.
- **CI supply-chain**: all GitHub Actions pinned to full commit SHAs with version comments.

## Current State (2026-06-26) — security agent (ported from VXD)

Self-upskilling security agent, mirrored from vortex-dispatch (offline-friendly: scanners are local binaries, LLM layer uses the configured Ollama/cloud client).
Expand Down Expand Up @@ -251,6 +260,9 @@ Architectural ceilings (cannot reach 95% without major refactor):

## Event Types

Completion-gate event (added 2026-07-02):
- `REQ_BLOCKED` — the completion gate could not get the composed mainline green after its auto-fix budget; requirement status → `blocked` instead of `completed` (resume with `--godmode` after addressing `.nxd-fix-gaps.md`)

Security agent events (added 2026-06-26):
- `STORY_SECURITY_PASSED` / `STORY_SECURITY_FAILED` — per-story security gate result; a FAILED gate pauses the requirement (human decision) rather than escalating
- `SECURITY_SCAN_COMPLETED` — a standalone `nxd security scan` finished (findings count, max severity)
Expand Down
67 changes: 67 additions & 0 deletions internal/agent/frontend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package agent

// FrontendDesignBrief is nxd's frontend design skill: the standards block
// injected into the goal prompt of every UI-facing story (PromptContext.
// IsFrontend, detected in the engine from owned files + story text).
//
// Synthesized from Anthropic's frontend-design skill and current guidance on
// avoiding generic AI-generated design ("AI slop"): token-first planning, one
// signature element, named anti-pattern looks, real copy, and a non-negotiable
// accessibility floor. Content lives in one const so the planner, the goal
// prompt, and tests share a single source of truth. Keep it under the size
// budget pinned by TestFrontendDesignBrief_SizeBudget — it rides on every
// UI-story dispatch.
const FrontendDesignBrief = `
## FRONTEND DESIGN — MANDATORY STANDARDS

You are also the design lead for this UI. The client rejects anything that
looks templated. Make deliberate, opinionated choices specific to THIS
product, its audience, and this page's single job.

### Two-pass process (plan tokens BEFORE code)
1. Write a compact design-token plan first, as a comment block or DESIGN.md:
- Palette: 4-6 named hex values — one dominant color creating atmosphere,
one sharp accent. Not evenly-distributed timid pastels.
- Type: 2+ roles — a characterful display face used with restraint, a
complementary body face (never the same family you'd pick for any other
project), optional utility face for data.
- Layout: one-sentence concept. Structure must encode something true about
the content (numbered markers only if the content really is a sequence).
- Signature: the ONE element this page will be remembered by. Spend your
boldness there; keep everything around it quiet and disciplined.
2. Critique the plan before coding: if any part is what you would produce for
ANY similar brief, it is a default, not a choice — revise it. Then write
the code deriving every color and type decision from the plan. Encode the
tokens once (CSS custom properties or the Tailwind theme), never ad-hoc
per component.

### Banned defaults (these read as AI-generated)
- Fonts: Inter, Roboto, Open Sans, Lato, Arial, bare system-ui as the design.
- Purple/blue-purple gradients on white; emerald or acid-green single accent
on near-black; warm cream #F4F1EA + serif display + terracotta accent;
broadsheet hairlines with zero border-radius EVERYWHERE. All four are
legitimate only if the brief explicitly asks for them.
- The template page: gradient hero → vague centered headline → three feature
cards with icons → testimonials → footer. Uniform 16px-radius cards.
- Scattered animations. One orchestrated moment (a page-load sequence or a
scroll reveal) beats effects everywhere; extra motion reads as generated.

### Quality floor (non-negotiable, never announced in the UI)
- Responsive down to 360px wide; no horizontal scroll, no overlapping text.
- Visible keyboard focus on every interactive element (focus-visible ring).
- prefers-reduced-motion respected: gate every animation on it.
- WCAG AA contrast: 4.5:1 body text, 3:1 large text and UI components.
- Touch targets at least 44x44px. Semantic HTML (nav/main/button, alt text,
labels tied to inputs) — a div with onClick is not a button.
- Empty, loading, and error states designed, not defaulted.
- Watch CSS specificity: section-level and element-level spacing rules that
cancel each other are the classic generated-CSS failure.

### Copy is design material
Write real copy for THIS product — never lorem ipsum or vague marketing
lines. Buttons say exactly what happens ("Save changes", never "Submit");
the same action keeps the same name through the whole flow. Errors state
what went wrong and how to fix it, without apologizing. Name things by what
the user controls ("notifications"), not how the system is built ("webhook
config"). Active voice, sentence case, no filler.
`
83 changes: 83 additions & 0 deletions internal/agent/frontend_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package agent

import (
"strings"
"testing"
)

// The frontend design brief is injected into the goal prompt only for
// UI-facing stories (ctx.IsFrontend). It is the vxd factory's design skill:
// token-first planning, one signature element, the named anti-pattern looks,
// and a non-negotiable accessibility floor.
func TestGoalPrompt_FrontendBriefInjectedWhenFlagSet(t *testing.T) {
ctx := PromptContext{
StoryID: "s-1",
StoryTitle: "Build the landing page",
StoryDescription: "Marketing page for the product",
AcceptanceCriteria: "- page renders",
IsFrontend: true,
}
got := GoalPrompt(RoleSenior, ctx)

for _, want := range []string{
"FRONTEND DESIGN", // section header
"Signature", // one memorable element
"token", // token-first plan before code
"prefers-reduced-motion", // quality floor
"focus", // visible keyboard focus
"Inter", // named anti-pattern font
"purple", // named anti-pattern palette
"#F4F1EA", // named second-generation cliché
"WCAG", // contrast floor
"Submit", // copy rule: never label a button Submit
} {
if !strings.Contains(got, want) {
t.Errorf("frontend brief missing %q", want)
}
}
}

func TestGoalPrompt_FrontendBriefAbsentForBackendStories(t *testing.T) {
ctx := PromptContext{
StoryID: "s-2",
StoryTitle: "Create REST API endpoints",
StoryDescription: "Express routes",
AcceptanceCriteria: "- routes tested",
IsFrontend: false,
}
got := GoalPrompt(RoleSenior, ctx)
if strings.Contains(got, "FRONTEND DESIGN") {
t.Error("backend story must not carry the frontend design brief")
}
}

// Retry dispatches go through RenderGoalWithAttempts — the brief must survive
// the retry path too, or the second attempt regresses to default design.
func TestRenderGoalWithAttempts_CarriesFrontendBrief(t *testing.T) {
ctx := TemplateContext{
StoryID: "s-1",
StoryTitle: "Build the landing page",
StoryDescription: "Marketing page",
AcceptanceCriteria: "- page renders",
IsFrontend: true,
IsRetry: true,
RetryNumber: 2,
ReviewFeedback: "colors are generic",
PriorAttempts: []AttemptSummary{{Number: 1, Role: "senior", Outcome: "review_failed"}},
}
got := RenderGoalWithAttempts(ctx)
if !strings.Contains(got, "FRONTEND DESIGN") {
t.Error("retry path must carry the frontend design brief")
}
}

// The brief itself must stay within a sane token budget — it rides on every
// UI story dispatch. ~6k chars ≈ 1.5k tokens is the ceiling.
func TestFrontendDesignBrief_SizeBudget(t *testing.T) {
if n := len(FrontendDesignBrief); n > 6000 {
t.Errorf("FrontendDesignBrief is %d chars — trim it below 6000 (prompt budget)", n)
}
if n := len(FrontendDesignBrief); n < 1500 {
t.Errorf("FrontendDesignBrief is %d chars — suspiciously small, did the content get lost?", n)
}
}
4 changes: 4 additions & 0 deletions internal/agent/prompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type PromptContext struct {
IsBugFix bool
IsRefactor bool
IsInfrastructure bool
IsFrontend bool // true when the story builds/changes a user-facing web UI
InvestigationReport string // formatted markdown, injected by planner
PriorWorkContext string // MemPalace search results
WaveBrief string // parallel stories in this wave
Expand Down Expand Up @@ -173,6 +174,9 @@ The previous implementation was rejected. Fix these issues:
if ctx.IsInfrastructure {
goal += "\n\nMANDATORY INFRASTRUCTURE WORKFLOW:\n1. Check services: docker ps -a, lsof for LISTEN\n2. Check logs: docker logs --tail 50, journalctl\n3. Check config: env vars, .env, docker-compose.yml\n4. Check resources: df -h, memory\n5. Fix and verify with health checks"
}
if ctx.IsFrontend {
goal += "\n" + FrontendDesignBrief
}

if ctx.PriorWorkContext != "" {
goal += "\n\n" + SanitizePromptField(ctx.PriorWorkContext)
Expand Down
2 changes: 2 additions & 0 deletions internal/agent/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type TemplateContext struct {
IsExistingCodebase bool
IsBugFix bool
IsInfrastructure bool
IsFrontend bool // true when the story builds/changes a user-facing web UI
IsRetry bool // true if this is not the first attempt
RetryNumber int // which attempt this is (1-indexed)
}
Expand Down Expand Up @@ -78,6 +79,7 @@ func RenderGoalWithAttempts(ctx TemplateContext) string {
IsExistingCodebase: ctx.IsExistingCodebase,
IsBugFix: ctx.IsBugFix,
IsInfrastructure: ctx.IsInfrastructure,
IsFrontend: ctx.IsFrontend,
}

// Route to the appropriate role based on complexity.
Expand Down
Loading
Loading