Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
- [ ] Runtime source set is `FPF-spec.md` only — no additional corpora added
- [ ] No vector database or remote indexing introduced
- [ ] No Python code added
- [ ] MCP tool contracts stay in `src/mcp/tool-contracts.ts`
- [ ] MCP tool contracts stay in `src/mcp/public-contracts.ts` and `src/mcp/expert-contracts.ts`

## Agent metadata

Expand Down
7 changes: 4 additions & 3 deletions README.md
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 README documents removed lm-check CLI command that no longer exists

The lm-check subcommand was removed from src/cli.ts in this PR and replaced by a standalone script at src/runtime/synthesizer/lm-check.ts. However, the README still references bun run cli -- lm-check in two separate code blocks (lines 100-101 and 164-165). Users following these instructions will hit the default case in the CLI switch (src/cli.ts:34-36), which prints help and exits with code 1. The CLI help text at src/cli.ts:130 was correctly updated to point to bun run src/runtime/synthesizer/lm-check.ts, but the README was not.

(Refers to lines 100-101)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in 27dc0ba — updated both README occurrences (lines 100-101 and 164-165) to point to bun run src/runtime/synthesizer/lm-check.ts.

Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ Smoke-test the local full-surface runtime before using expert tools or deploying

```bash
bun run cli -- status
bun run cli -- lm-check --timeout-ms 60000
bun run cli -- lm-check --base-url http://localhost:1234 --api-style chat --api-key "$FPF_LOCAL_LLM_API_KEY" --timeout-ms 60000
bun run src/runtime/synthesizer/lm-check.ts --timeout-ms 60000
bun run src/runtime/synthesizer/lm-check.ts --base-url http://localhost:1234 --api-style chat --api-key "$FPF_LOCAL_LLM_API_KEY" --timeout-ms 60000
bun run cli -- refresh
bun run cli -- query --question "What is U.BoundedContext?" --mode verbose
bun run cli -- trace --question "How do U.RoleAssignment and U.BoundedContext connect?" --mode proof
Expand Down Expand Up @@ -221,7 +221,8 @@ Call trace_fpf_path with:

## Runtime surfaces

- `src/mcp/tool-contracts.ts`: Zod-authored MCP input and output contracts
- `src/mcp/public-contracts.ts`: Zod-authored MCP input/output contracts for the 3 public tools
- `src/mcp/expert-contracts.ts`: Zod-authored MCP input/output contracts for the 6 expert tools
- `src/mcp/tools.ts`: canonical snake_case MCP tools and `ask_fpf`
- `src/mastra/mcp/server.ts`: MCPServer definitions (public and full surfaces)
- `src/mastra/index.ts`: Mastra instance registration
Expand Down
27 changes: 1 addition & 26 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import { getRuntimeLogger } from './logging/runtime-logger.js';
import {
normalizeLmStudioApiStyle,
runLmStudioHealthCheck,
} from './runtime/lm-studio-synthesizer.js';
import { FpfRuntime } from './runtime/runtime.js';
import type { AnswerMode } from './runtime/types.js';

Expand Down Expand Up @@ -34,9 +30,6 @@ try {
case 'trace':
await runTrace(args.slice(1));
break;
case 'lm-check':
await runLmCheck(args.slice(1));
break;
default:
printHelp();
process.exitCode = command === 'help' ? 0 : 1;
Comment on lines 33 to 35
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 Preserve lm-check alias in the main CLI

Removing the lm-check switch case makes bun run cli -- lm-check ... fall into the default branch and exit with an error, which is a breaking regression for existing documented usage (README still invokes that command). If the health check has moved to a standalone script, keep a compatibility alias here (or migrate all documented call sites in the same change) so current automation and operator runbooks do not fail unexpectedly.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is intentional — the whole point of this PR (issue #37) is to extract lm-check out of the CLI into a standalone executable at src/runtime/synthesizer/lm-check.ts. The README and help text have both been updated in this PR to reference the new path. There's no backward-compatibility obligation here since lm-check was an internal dev tool, not a public API, and the docs are migrated in the same change.

Expand Down Expand Up @@ -109,24 +102,6 @@ async function runTrace(commandArgs: string[]): Promise<void> {
await print(runtime.trace(question, mode, forceRefresh, sessionId));
}

async function runLmCheck(commandArgs: string[]): Promise<void> {
const timeoutMsRaw = value(commandArgs, '--timeout-ms');
const timeoutMs = timeoutMsRaw ? Number(timeoutMsRaw) : undefined;
const apiStyle = normalizeLmStudioApiStyle(value(commandArgs, '--api-style'));

await print(
runLmStudioHealthCheck({
baseUrl: value(commandArgs, '--base-url'),
model: value(commandArgs, '--model'),
apiStyle,
apiKey: value(commandArgs, '--api-key') ?? process.env.FPF_LOCAL_LLM_API_KEY,
timeoutMs: Number.isFinite(timeoutMs) ? timeoutMs : undefined,
systemPrompt: value(commandArgs, '--system-prompt'),
input: value(commandArgs, '--input'),
}),
);
}

function flag(argsList: string[], flagName: string): boolean {
return argsList.includes(flagName);
}
Expand All @@ -152,6 +127,6 @@ function printHelp(): void {
bun run cli -- inspect --selector "A.1.1" [--kind auto|id|route|lexeme] [--force]
bun run cli -- read-doc --selector "A.1.1" [--kind auto|id|route|lexeme] [--force]
bun run cli -- trace --question "How do routes work?" [--mode compact|verbose|proof] [--session s1] [--force]
bun run cli -- lm-check [--base-url http://localhost:1234/v1] [--model google/gemma-4-31b] [--api-style responses|chat|lmstudio_chat] [--api-key <token>] [--timeout-ms 60000]
bun run src/runtime/synthesizer/lm-check.ts [--base-url http://localhost:1234/v1] [--model google/gemma-4-31b] [--api-style responses|chat|chat_completions|lmstudio_chat] [--api-key <token>] [--timeout-ms 60000]
`);
}
144 changes: 8 additions & 136 deletions src/mcp/tool-contracts.ts → src/mcp/expert-contracts.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { z } from 'zod';

export const answerModeSchema = z.enum(['compact', 'verbose', 'proof']);
import {
answerModeSchema,
answerStatusSchema,
baseQueryInputSchema,
snapshotWithRebuildSchema,
} from './public-contracts.js';
Comment on lines +3 to +8
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Import the base query input schema to deduplicate the traceFpfPathInputSchema definition.

Suggested change
import {
answerModeSchema,
answerStatusSchema,
snapshotWithRebuildSchema,
} from './public-contracts.js';
import {
answerModeSchema,
answerStatusSchema,
baseQueryInputSchema,
snapshotWithRebuildSchema,
} from './public-contracts.js';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Done in 27dc0batraceFpfPathInputSchema now reuses baseQueryInputSchema from public-contracts.ts.


export const nodeKindSchema = z.enum(['pattern', 'route', 'lexeme']);
export const selectorKindSchema = z.enum(['auto', 'id', 'route', 'lexeme']);
export const answerStatusSchema = z.enum([
'ok',
'not_found',
'ambiguous',
'unsupported',
'stale_snapshot_prevented',
]);
export const anchorRoleSchema = z.enum([
'definition',
'solution',
Expand All @@ -26,14 +25,6 @@ export const buildReasonSchema = z.enum([
'source_hash_changed',
'snapshot_current',
]);
export const observabilityFormatSchema = z.enum(['flat', 'tree', 'normalized']);
export const observabilityLogLevelSchema = z.enum([
'debug',
'info',
'warn',
'error',
'fatal',
]);
export const resolvedAsSchema = z.enum(['id', 'route', 'lexeme', 'not_found']);
export const inspectStatusSchema = z.enum(['ok', 'not_found']);
export const frontierOriginSchema = z.enum([
Expand All @@ -45,15 +36,6 @@ export const frontierOriginSchema = z.enum([
'session_context',
]);
export const expandedCitationStatusSchema = z.enum(['ok', 'not_found']);
export const lmStudioApiStyleSchema = z.enum(['responses', 'lmstudio_chat', 'chat_completions']);

export const relationEdgeSchema = z
.object({
from: z.string(),
relation: z.string(),
to: z.string(),
})
.strict();

export const inspectNeighborSchema = z
.object({
Expand Down Expand Up @@ -110,14 +92,6 @@ export const compiledNodeSchema = z
})
.strict();

export const snapshotWithRebuildSchema = z
.object({
sourceHash: z.string(),
builtAt: z.string(),
rebuilt: z.boolean(),
})
.strict();

export const buildAuditSchema = z
.object({
sourcePath: z.string(),
Expand Down Expand Up @@ -154,78 +128,6 @@ export const buildAuditSchema = z
})
.strict();

export const queryResultSchema = z
.object({
mode: answerModeSchema,
question: z.string(),
answer: z.string(),
ids: z.array(z.string()),
relations: z.array(relationEdgeSchema),
constraints: z.array(z.string()),
citations: z.array(z.string()),
confidence: z.number(),
gaps: z.array(z.string()),
snapshot: snapshotWithRebuildSchema,
status: answerStatusSchema,
groundingChain: z.array(z.string()).optional(),
})
.strict();

export const askFpfResultSchema = z
.object({
question: z.string(),
mode: answerModeSchema,
markdown: z.string(),
ids: z.array(z.string()),
citations: z.array(z.string()),
constraints: z.array(z.string()),
gaps: z.array(z.string()),
confidence: z.number(),
status: answerStatusSchema,
snapshot: snapshotWithRebuildSchema,
groundingChain: z.array(z.string()).optional(),
})
.strict();

export const runtimeStatusSchema = z
.object({
sourcePath: z.string(),
sourceHash: z.string().optional(),
builtAt: z.string().optional(),
snapshotExists: z.boolean(),
currentSourceHash: z.string(),
fresh: z.boolean(),
compilerMode: z.literal('local_vectorless'),
artifacts: z.record(z.string(), z.boolean()),
synthesizer: z
.object({
configured: z.boolean(),
provider: z.string().optional(),
model: z.string().optional(),
baseUrl: z.string().optional(),
apiStyle: lmStudioApiStyleSchema.optional(),
})
.strict(),
observability: z
.object({
configured: z.boolean(),
filePath: z.string(),
format: observabilityFormatSchema,
includeInternalSpans: z.boolean(),
logLevel: observabilityLogLevelSchema,
excludeModelChunks: z.boolean(),
})
.strict(),
sessionCache: z
.object({
enabled: z.boolean(),
maxSessions: z.number(),
activeSessions: z.number(),
})
.strict(),
})
.strict();

export const traceDetectedSchema = z
.object({
ids: z.array(z.string()),
Expand Down Expand Up @@ -387,25 +289,7 @@ export const refreshFpfIndexInputSchema = z
})
.strict();

export const queryFpfSpecInputSchema = z
.object({
question: z.string().min(1),
mode: answerModeSchema.optional(),
forceRefresh: z.boolean().optional(),
sessionId: z.string().min(1).optional(),
})
.strict();

export const askFpfInputSchema = z
.object({
question: z.string().min(1),
mode: answerModeSchema.optional(),
forceRefresh: z.boolean().optional(),
sessionId: z.string().min(1).optional(),
})
.strict();

export const getFpfIndexStatusInputSchema = z.object({}).strict();
export const traceFpfPathInputSchema = baseQueryInputSchema;

export const inspectFpfNodeInputSchema = z
.object({
Expand Down Expand Up @@ -437,19 +321,7 @@ export const expandFpfCitationsInputSchema = z
})
.strict();

export const traceFpfPathInputSchema = z
.object({
question: z.string().min(1),
mode: answerModeSchema.optional(),
forceRefresh: z.boolean().optional(),
sessionId: z.string().min(1).optional(),
})
.strict();

export type RefreshFpfIndexInput = z.infer<typeof refreshFpfIndexInputSchema>;
export type QueryFpfSpecInput = z.infer<typeof queryFpfSpecInputSchema>;
export type AskFpfInput = z.infer<typeof askFpfInputSchema>;
export type GetFpfIndexStatusInput = z.infer<typeof getFpfIndexStatusInputSchema>;
export type InspectFpfNodeInput = z.infer<typeof inspectFpfNodeInputSchema>;
export type ReadFpfDocInput = z.infer<typeof readFpfDocInputSchema>;
export type InspectFpfAnchorInput = z.infer<typeof inspectFpfAnchorInputSchema>;
Expand Down
Loading
Loading