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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ Capability/scope summary only. See the [claims-and-evidence map](https://github.

## Quickstart

Start with the generated graph. `madar generate` creates the local graph artifact; `madar summary`, `madar pack`, `madar prompt`, and `madar handoff` can use that graph without any agent install. Run `madar <agent> install` only when you want Madar wired into an agent through MCP or local instruction files.
Start with a one-command local proof. `madar try` builds or reuses `out/graph.json`, prints one human-readable explain-pack result, and recommends the next install command without requiring MCP first. When you want more control, drop down to `madar generate`, `madar summary`, `madar pack`, `madar prompt`, and `madar handoff` directly.

```bash
npm install -g @lubab/madar

cd your-project
madar try "how does auth work?" # one-command local proof before agent install
madar generate . # builds out/graph.json, no API key, no cloud
madar summary # bounded repo overview before deeper retrieval
madar claude install # wires Claude Code to use Madar via MCP
Expand Down
23 changes: 16 additions & 7 deletions docs/tutorials/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@ npm install -g @lubab/madar

If you are working from this repository instead of a published npm install, run `npm run build` from the repository root first so the local CLI is up to date.

## 2. Generate a graph for the sample workspace
## 2. Start with the one-command trial flow

```bash
madar try "how does password reset request enqueue the reset email" examples/sample-workspace
```

This builds or reuses `examples/sample-workspace/out/graph.json`, prints one human-readable local explanation, and ends with the next recommended install command without requiring Claude/Cursor/Codex/Copilot setup first.

## 3. Generate a graph for the sample workspace manually

```bash
madar generate examples/sample-workspace --no-html
Expand All @@ -24,7 +32,7 @@ If your real repo is framework-heavy TypeScript/JavaScript and you care about ri
madar generate examples/sample-workspace --spi --no-html
```

## 3. Install one agent profile
## 4. Install one agent profile

Move into the sample workspace before installing so the generated graph, agent config, and verification commands all point at the same project:

Expand All @@ -40,7 +48,7 @@ madar claude install

If you want a different runtime, use the same step with `madar codex install`, `madar cursor install`, `madar copilot install`, `madar gemini install`, `madar aider install`, or `madar opencode install`.

## 4. Verify the install before asking bigger questions
## 5. Verify the install before asking bigger questions

```bash
madar doctor out/graph.json
Expand All @@ -49,15 +57,15 @@ madar status out/graph.json

For Claude, Cursor, Gemini, and Copilot, `doctor` checks graph freshness plus the install wiring, and `status` gives you the compact readiness summary plus the next recommended commands. `doctor`/`status` also report Codex, Aider, and OpenCode when their AGENTS/hook/plugin/MCP signals are present; if any of those drift, the agent is marked `partial` with a reinstall suggestion.

## 5. Start with a bounded summary
## 6. Start with a bounded summary

```bash
madar summary out/graph.json
```

This prints the deterministic high-signal overview first: graph counts, source domains, top modules, frameworks, entrypoints, and runtime paths. It is the fastest way to decide whether you need a deeper `pack`, `prompt`, or MCP retrieval call.

## 6. Build a compact pack
## 7. Build a compact pack

```bash
madar pack "how does password reset request enqueue the reset email" \
Expand All @@ -67,7 +75,7 @@ madar pack "how does password reset request enqueue the reset email" \

This is the fastest way to confirm the route → service → job flow is represented in the graph. On runtime-generation questions like this one, newer reports can also preserve an `execution_slice` so you can inspect ordered steps without reading the whole raw slice. Treat it as a static runtime-path hypothesis from the graph, not a live trace. The nested `phase_coverage` is also static and prompt-scoped, so broader report-generation questions may show planner/research/report-builder/scoring/renderer/persistence phases when the graph supports them.

## 7. Compile a provider-ready prompt
## 8. Compile a provider-ready prompt

```bash
madar prompt "where is reset token persisted before the email job runs" \
Expand All @@ -77,7 +85,7 @@ madar prompt "where is reset token persisted before the email job runs" \

`prompt` only compiles the prompt payload. It does **not** call Claude or spend paid model tokens by itself.

## 8. Run a safe compare smoke check
## 9. Run a safe compare smoke check

If you want to exercise `compare` without calling a paid model, use a local echo-style runner:

Expand All @@ -93,6 +101,7 @@ This does **not** measure model quality. It is a safe local smoke check that pro

## Expected output

- `try` should print one human-readable local result plus a recommended install command
- `generate` should write `examples/sample-workspace/out/graph.json`
- `claude install` should register the local Madar integration for the sample workspace
- `doctor` should confirm the graph path plus install wiring for Claude, Cursor, Gemini, or Copilot
Expand Down
22 changes: 22 additions & 0 deletions src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { runContextPromptCommand } from '../infrastructure/context-prompt-comman
import { runDoctorCommand, runStatusCommand } from '../infrastructure/doctor.js'
import { runProofReportCommand, type ProofReportResult } from '../infrastructure/proof-report.js'
import { runReviewCompareCommand } from '../infrastructure/review-compare.js'
import { runTryCommand } from '../infrastructure/try-command.js'
import { saveQueryResult } from '../infrastructure/save-query-result.js'
import { compareRefs } from '../infrastructure/time-travel.js'
import { federate } from '../pipeline/federate.js'
Expand Down Expand Up @@ -77,6 +78,8 @@ import {
parseServeArgs,
parseTelemetryArgs,
parseTimeTravelArgs,
parseTryArgs,
type TryCliOptions,
type HandoffCliOptions,
type PackCliOptions,
type ProofReportCliOptions,
Expand Down Expand Up @@ -130,6 +133,11 @@ export interface ContextPackCommandContext {
io: CliIO
}

export interface TryCommandContext {
options: TryCliOptions
io: CliIO
}

export interface HandoffCommandContext {
options: HandoffCliOptions
io: CliIO
Expand All @@ -155,6 +163,7 @@ export interface CliDependencies {
runReviewCompare: (context: ReviewCompareCommandContext) => Promise<string | void> | string | void
runTimeTravel: (context: TimeTravelCommandContext) => Promise<string | void> | string | void
runContextPack: (context: ContextPackCommandContext) => Promise<string | void> | string | void
runTry: (context: TryCommandContext) => Promise<string | void> | string | void
runHandoff: (context: HandoffCommandContext) => Promise<string | void> | string | void
runContextPrompt: (context: ContextPromptCommandContext) => Promise<string | void> | string | void
runProofReport: (options: ProofReportCliOptions) => ProofReportResult
Expand Down Expand Up @@ -269,6 +278,9 @@ const DEFAULT_DEPENDENCIES: CliDependencies = {
runContextPack: async ({ options }) => {
return await runContextPackCommand(options)
},
runTry: async ({ options, io }) => {
return await runTryCommand(options, io)
},
runHandoff: async ({ options }) => {
return await runHandoffCommand(options)
},
Expand Down Expand Up @@ -427,6 +439,7 @@ export function formatHelp(binaryName = 'madar'): string {
' --stdio serve graph query methods over stdio (JSON lines)',
' --mcp alias for --stdio for installer/runtime parity',
' summary [graph.json] print a compact deterministic graph summary as JSON',
' try "<question>" [path] one-command local first proof before agent install',
' query "<question>" traverse graph.json for a question',
' --dfs use depth-first instead of breadth-first',
' --budget N cap output at N tokens (default 2000)',
Expand Down Expand Up @@ -790,6 +803,15 @@ export async function executeCli(argv: string[], io: CliIO = console, dependenci
return 0
}

if (command === 'try') {
const options = parseTryArgs(args)
const output = await dependencies.runTry({ options, io })
if (output !== undefined) {
io.log(output)
}
return 0
}

if (command === 'pack') {
const options = parsePackArgs(args)
const output = await dependencies.runContextPack({ options, io })
Expand Down
40 changes: 40 additions & 0 deletions src/cli/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export interface PackCliOptions {
retrievalStrategy?: ContextPackRetrievalStrategy
}

export interface TryCliOptions {
prompt: string
path: string
}

export interface HandoffCliOptions {
prompt: string
budget: number
Expand Down Expand Up @@ -618,6 +623,41 @@ export function parsePackArgs(args: string[]): PackCliOptions {
}
}

export function parseTryArgs(args: string[]): TryCliOptions {
const usage = 'Usage: madar try "<question>" [path]'
const prompt = args[0]?.trim()
if (!prompt) {
throw new UsageError(usage)
}

let path = '.'
for (let index = 1; index < args.length; index += 1) {
const argument = args[index]
if (!argument) {
continue
}

if (argument.startsWith('--')) {
throw new UsageError(`error: unknown option for try: ${argument}`)
}

if (path !== '.') {
throw new UsageError(usage)
}

path = argument
}

if (path.length > MAX_CLI_PATH_LENGTH) {
throw new UsageError(`error: path exceeds maximum length of ${MAX_CLI_PATH_LENGTH} characters`)
}

return {
prompt: validateCliQuestionText('question', prompt),
path,
}
}

export function parseHandoffArgs(args: string[]): HandoffCliOptions {
const usage = 'Usage: madar handoff "<prompt>" [--budget N] [--task KIND] [--graph path] [--consumer generic|codex|cursor|copilot] [--allow-snippets] [--require-fresh-graph] [--require-fresh-context]'
const prompt = args[0]?.trim()
Expand Down
23 changes: 21 additions & 2 deletions src/infrastructure/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ export interface GenerateGraphCacheSummary {
fileCount: number
}

export type GenerateUnsupportedCorpusCode = 'NO_SUPPORTED_FILES' | 'NO_GRAPH_NODES'

export class GenerateUnsupportedCorpusError extends Error {
readonly code: GenerateUnsupportedCorpusCode

constructor(code: GenerateUnsupportedCorpusCode, message: string) {
super(message)
this.name = 'GenerateUnsupportedCorpusError'
this.code = code
}
}

type IncrementalDetectResult = ReturnType<typeof detectIncremental>

function detectOptions(options: GenerateGraphOptions): { followSymlinks?: boolean } {
Expand Down Expand Up @@ -218,6 +230,13 @@ function missingCodeExtractionMessage(totalFiles: number): string {
return 'No graph nodes could be generated from the detected corpus. The current TypeScript extractor supports Python, JavaScript/TypeScript, documents, text-like papers, and image assets, but some detected formats still have shallow coverage.'
}

function missingCodeExtractionError(totalFiles: number): GenerateUnsupportedCorpusError {
return new GenerateUnsupportedCorpusError(
totalFiles === 0 ? 'NO_SUPPORTED_FILES' : 'NO_GRAPH_NODES',
missingCodeExtractionMessage(totalFiles),
)
}

export function loadGraphExtractorVersion(graphPath: string): number | null {
try {
const parsed = JSON.parse(readFileSync(graphPath, 'utf8')) as unknown
Expand Down Expand Up @@ -404,11 +423,11 @@ export function generateGraph(rootPath = '.', options: GenerateGraphOptions = {}
: null

if (!graph) {
throw new Error(missingCodeExtractionMessage(detected.total_files))
throw missingCodeExtractionError(detected.total_files)
}

if (!options.clusterOnly && graph.numberOfNodes() === 0) {
throw new Error(missingCodeExtractionMessage(detected.total_files))
throw missingCodeExtractionError(detected.total_files)
}

progress?.({ step: 'build', message: `Built graph: ${graph.numberOfNodes()} nodes, ${graph.numberOfEdges()} edges` })
Expand Down
Loading
Loading