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
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ jobs:
name: Test & Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: true

- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version: '1.24'
go-version: '1.25'
cache-dependency-path: src/mcp/go.sum

- name: Check go mod tidy
Expand Down Expand Up @@ -51,7 +51,7 @@ jobs:
fi

- name: Upload coverage
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
file: src/mcp/coverage.out
fail_ci_if_error: false
Expand All @@ -61,17 +61,17 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: true

- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version: '1.24'
go-version: '1.25'
cache-dependency-path: src/mcp/go.sum

- uses: golangci/golangci-lint-action@v7
with:
version: v2.1.6
version: v2.11.4
working-directory: src/mcp
args: --timeout=5m
8 changes: 4 additions & 4 deletions .github/workflows/install-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
name: Install (Linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Test install.sh (source build fallback)
run: |
Expand All @@ -29,11 +29,11 @@ jobs:
name: Install (macOS)
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version: '1.24'
go-version: '1.25'

- name: Test install.sh (source build fallback)
run: bash install.sh
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ jobs:
name: Pre-release tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: true
fetch-depth: 0

- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version: '1.24'
go-version: '1.25'
cache-dependency-path: src/mcp/go.sum

- name: Run tests
Expand All @@ -32,14 +32,14 @@ jobs:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: true
fetch-depth: 0

- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version: '1.24'
go-version: '1.25'
cache-dependency-path: src/mcp/go.sum

- uses: goreleaser/goreleaser-action@v6
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/update-fpf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ jobs:
update-fpf:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: true
fetch-depth: 0

- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version: '1.24'
go-version: '1.25'

- name: Get current submodule commit
id: current
Expand Down
8 changes: 2 additions & 6 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,12 @@ release:
header: |
## Quint Code {{ .Tag }}

**Complete product redesign.** v5 is a new product — not backward-compatible with v4.

New: FPF E.9 decision records, computed R_eff with evidence decay, problem lifecycle (Backlog → In Progress → Addressed), diversity check, archive recall, parity enforcement, indicator roles, Goldilocks signals, lemniscate feedback loop, universal artifact refresh, and 4243 indexed FPF spec sections.

6 MCP tools. Works with Claude Code, Cursor, Gemini CLI, Codex CLI.
FPF-native engineering decision tool. 6 MCP tools. Works with Claude Code, Cursor, Gemini CLI, Codex CLI.

### Install

```bash
curl -fsSL https://raw.githubusercontent.com/m0n0x41d/quint-code/main/install.sh | bash
```

See [CHANGELOG](https://github.com/m0n0x41d/quint-code/blob/main/src/mcp/CHANGELOG.md) for full details.
See [CHANGELOG](https://github.com/m0n0x41d/quint-code/blob/main/CHANGELOG.md) for full details.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

## [5.3.0] — 2026-03-24

### Added

- **Interactive terminal dashboard (`quint-code board`)** — Bubbletea v2 TUI with four tabs: Overview (health, activity, depth distribution, coverage, contexts, evidence), Problems (backlog with drill-in), Decisions (list with R_eff/drift, drill-in with glamour markdown), Modules (coverage tree). Live refresh every 3s. Connected tab borders, alternating row colors, adaptive dark/light theme, dynamic help bar. Exit code 1 with `--check` flag for CI/hooks.
- **Decision mode computed from artifact chain** — `inferModeFromChain` derives mode from linked problems (characterization) and portfolios (comparison). Agent-declared mode can only escalate, not downgrade. Fixes misclassification where full-cycle decisions were recorded as tactical.
- **FTS5 search keyword enrichment** — `search_keywords` column on artifacts, indexed by FTS5. Agent generates synonyms and related terms at write time. Accepted on `quint_note` and `quint_decision`. Migration 15 rebuilds FTS5 index.
- **C/C++ header-only module detection** — `-I` include directories from `compile_commands.json` are registered as modules (FileCount=0), so dependency edges to `include/` directories are no longer dropped by `ScanDependencies`.

### Fixed

- **`/q-refresh scan` now rescans modules** — module structure updates alongside drift and stale checks, keeping dependency graph fresh without requiring a separate `coverage` action.
- **C/C++ symlink-safe include resolution** — `resolveInclude` canonicalizes both `projectRoot` and `-I` paths with `EvalSymlinks` before computing relative paths. Fixes silent edge loss on macOS symlinked checkouts.
- **Notes excluded from drift detection** — notes are observations, not implementations. ScanStale no longer flags notes with affected_files as "no baseline."
- **Module scanner excludes `.claude` and `.context` directories** — Claude Code worktrees and reference repos no longer inflate module count.
- **q-reason skill context-aware entry** — skill no longer always falls through into full FPF cycle. Three paths: think-and-respond (no artifacts), prepare-and-wait (human drives), full autonomous cycle (agent drives). Default is prepare-and-wait.

## [5.2.0] — 2026-03-23

### Added
Expand Down
5 changes: 5 additions & 0 deletions src/mcp/.golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,18 @@ linters:
excludes:
- G301 # directory permissions 0755 — fine for user-facing dirs
- G302 # file permissions 0644 — fine for user-facing files
- G204 # subprocess with variable — we call quint/git as subprocess intentionally
- G304 # file inclusion via variable — we read user-specified paths intentionally
- G306 # WriteFile permissions 0644 — fine for markdown/config files
- G703 # path traversal — we construct paths from project root intentionally
exclusions:
rules:
- linters:
- staticcheck
text: "QF1003"
- linters:
- staticcheck
text: "QF1012"
- linters:
- errcheck
source: "defer .*(Close|Rollback)\\(\\)"
Expand Down
141 changes: 141 additions & 0 deletions src/mcp/cmd/board.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package cmd

import (
"database/sql"
"fmt"
"os"
"path/filepath"

tea "charm.land/bubbletea/v2"
"github.com/spf13/cobra"

"github.com/m0n0x41d/quint-code/internal/artifact"
"github.com/m0n0x41d/quint-code/internal/project"
"github.com/m0n0x41d/quint-code/internal/ui"
)

var checkMode bool

var boardCmd = &cobra.Command{
Use: "board",
Short: "Interactive dashboard — decision health, coverage, drift, problems",
Long: `Launch the Quint Code dashboard.

Shows decision health, module coverage, drift alerts, problem pipeline,
and evidence quality in an interactive terminal UI.

Navigation: tab/1-4 switch views, j/k navigate, enter drill in, esc back, q quit.

Use --check for CI/hooks: exits with code 1 if critical issues exist
(R_eff < 0.3, decisions expired > 30 days).`,
RunE: runBoard,
}

func init() {
boardCmd.Flags().BoolVar(&checkMode, "check", false, "Health check mode: print summary and exit with code 1 if critical issues")
rootCmd.AddCommand(boardCmd)
}

func runBoard(cmd *cobra.Command, _ []string) error {
// Find project root
projectRoot, err := findProjectRoot()
if err != nil {
return fmt.Errorf("not a quint-code project (no .quint/ directory found): %w", err)
}

quintDir := filepath.Join(projectRoot, ".quint")

// Load project config
projCfg, err := project.Load(quintDir)
if err != nil {
return fmt.Errorf("load project config: %w", err)
}
if projCfg == nil {
return fmt.Errorf("project not initialized — run 'quint-code init' first")
}

// Open DB
dbPath, err := projCfg.DBPath()
if err != nil {
return fmt.Errorf("get DB path: %w", err)
}

// Open DB with WAL mode and busy timeout.
// MCP server may hold a write connection to the same DB —
// WAL allows concurrent readers, busy timeout prevents instant SQLITE_BUSY.
dsn := dbPath + "?_pragma=journal_mode(WAL)&_pragma=busy_timeout(3000)"
sqlDB, err := sql.Open("sqlite", dsn)
if err != nil {
return fmt.Errorf("open DB: %w", err)
}
defer sqlDB.Close()

store := artifact.NewStore(sqlDB)
projectName := projCfg.Name

// Load all data
data, err := ui.LoadBoardData(store, sqlDB, projectName, projectRoot)
if err != nil {
return fmt.Errorf("load board data: %w", err)
}

// Check mode: print summary and exit
if checkMode {
return runCheck(data)
}

// Interactive mode
model := ui.New(data, store, sqlDB, projectName, projectRoot)
p := tea.NewProgram(model)
finalModel, err := p.Run()
if err != nil {
return fmt.Errorf("board: %w", err)
}

// Interactive mode always exits 0 — user saw the dashboard.
// Use --check for non-zero exit on critical issues.
_ = finalModel
return nil
}

func runCheck(data *ui.BoardData) error {
fmt.Printf("Quint Code Health: %s\n", data.ProjectName)
fmt.Printf(" Decisions: %d shipped, %d pending\n", data.ShippedCount, data.PendingCount)
fmt.Printf(" Problems: %d backlog, %d addressed\n", len(data.BacklogProblems), data.AddressedCount)
fmt.Printf(" Stale: %d items\n", len(data.StaleItems))

if data.CoverageReport != nil {
cr := data.CoverageReport
pct := 0
if cr.TotalModules > 0 {
pct = (cr.CoveredCount + cr.PartialCount) * 100 / cr.TotalModules
}
fmt.Printf(" Coverage: %d%% (%d/%d modules)\n", pct, cr.CoveredCount+cr.PartialCount, cr.TotalModules)
}

if data.CriticalCount > 0 {
fmt.Printf("\n CRITICAL: %d issue(s) require attention\n", data.CriticalCount)
os.Exit(1)
}

fmt.Println("\n OK: no critical issues")
return nil
}

func findProjectRoot() (string, error) {
dir, err := os.Getwd()
if err != nil {
return "", err
}

for {
if _, err := os.Stat(filepath.Join(dir, ".quint")); err == nil {
return dir, nil
}
parent := filepath.Dir(dir)
if parent == dir {
return "", fmt.Errorf("no .quint/ found")
}
dir = parent
}
}
16 changes: 16 additions & 0 deletions src/mcp/cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ func handleQuintNote(ctx context.Context, store *artifact.Store, quintDir string
input.Context = v
}
input.AffectedFiles = parseStringArrayFromArgs(args, "affected_files")
if v, ok := args["search_keywords"].(string); ok {
input.SearchKeywords = v
}

validation := artifact.ValidateNote(ctx, store, input)
navStrip := artifact.BuildNavStrip(ctx, store, input.Context)
Expand Down Expand Up @@ -535,6 +538,9 @@ func handleQuintDecision(ctx context.Context, store *artifact.Store, quintDir st
input.EvidenceReqs = parseStringArrayFromArgs(args, "evidence_requirements")
input.RefreshTriggers = parseStringArrayFromArgs(args, "refresh_triggers")
input.AffectedFiles = parseStringArrayFromArgs(args, "affected_files")
if v, ok := args["search_keywords"].(string); ok {
input.SearchKeywords = v
}
Comment on lines +541 to +543
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add search_keywords to quint_decision tool schema

handleQuintDecision now reads args["search_keywords"], but the quint_decision MCP input schema still does not declare that field (only quint_note was updated). Schema-driven MCP clients typically omit undeclared arguments, so decision keyword enrichment is effectively unavailable in normal tool-calling flows.

Useful? React with 👍 / 👎.


if rb, ok := args["rollback"].(map[string]interface{}); ok {
rollback := &artifact.RollbackSpec{}
Expand Down Expand Up @@ -761,6 +767,16 @@ func handleQuintRefresh(ctx context.Context, store *artifact.Store, quintDir str
switch artifact.RefreshAction(action) {
case artifact.RefreshScan:
projectRoot := filepath.Dir(quintDir)

// Rescan modules before drift detection — keeps dependency graph fresh
scanner := codebase.NewScanner(store.DB())
if _, err := scanner.ScanModules(ctx, projectRoot); err != nil {
logger.Warn().Err(err).Msg("refresh: module rescan failed (non-fatal)")
}
if _, err := scanner.ScanDependencies(ctx, projectRoot); err != nil {
_ = err // non-fatal
}

items, err := artifact.ScanStale(ctx, store, projectRoot)
if err != nil {
return "", err
Expand Down
23 changes: 23 additions & 0 deletions src/mcp/cmd/skill/q-reason/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,29 @@ This skill activates structured engineering reasoning powered by FPF (First Prin

---

## Context-aware entry — read what the user actually wants

**Before doing anything, assess the user's intent from context and arguments.** Do NOT always fall through into the full FPF cycle. There are three distinct paths:

### Path 1: Think and respond (no artifacts)
**Trigger:** "think about X", "what do you think about X", "analyze X", "is this the right approach?", "what are our options?"

The user wants structured thinking, not tool calls. Reason through the problem using FPF principles (weakest link, parity, distinguish object/description/carrier, etc.). Give a well-structured answer. **Do not call quint tools** unless the user explicitly asks to persist something.

### Path 2: Prepare for human-driven cycle (research + wait)
**Trigger:** "/q-reason [topic], prepare for framing", "let's think about X before deciding", "I want to reason through X"

The user wants to drive the cycle themselves. Gather context (read relevant code, search existing decisions, research). Present findings. **Stop and wait** for the user to decide the next step — they will call `/q-frame`, `/q-char`, etc. when ready.

### Path 3: Full autonomous cycle (agent drives)
**Trigger:** "/q-reason [topic] and implement", "figure out the best approach and do it", "fix everything", explicit delegation to agent

The user wants the agent to run the full cycle: frame → explore → decide → implement. Only in this mode does the agent drive without pausing.

**If unclear which path:** default to Path 2 (prepare and wait). Never default to Path 3. Ask: "Want me to think this through and present options, or drive the full cycle and implement?"

---

## What you have

### Quint tools (MCP) — persist reasoning as artifacts
Expand Down
Loading
Loading