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: 0 additions & 3 deletions plugins/adapters/claude-code/INSTALL.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# Claude Code — Post-install steps

Hooks installed: SessionStart (context injection), PreCompact (state preservation).
Restart Claude Code for hooks to take effect.

1. Enable the ai-memory MCP server in your tool's settings (it's disabled by default).
2. Start a new session and verify with: "What does .ai/IDENTITY.md say about this project?"

Expand Down
3 changes: 2 additions & 1 deletion src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { fileURLToPath } from "url";
import { DEFAULT_DOCS_SCHEMA_JSON } from "../docs-schema.js";
import { TOOL_ADAPTERS, getMCPJson, MCP_LAUNCHER, MCP_LAUNCHER_PATH, CANONICAL_SKILLS } from "./adapters.js";
import { detectEnvironments, injectCapabilityConfig, getCapabilityManualInstructions } from "./environment.js";
import { AI_PATHS } from "../schema-constants.js";

// ─── CLI helpers ─────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -644,7 +645,7 @@ program
}

// 6. Harness validity
const harnessPath = join(aiDir, "temp/harness.json");
const harnessPath = join(aiDir, AI_PATHS.HARNESS);
if (existsSync(harnessPath)) {
try {
const rules = JSON.parse(readFileSync(harnessPath, "utf-8")) as Array<{ id?: string; type?: string; pattern?: string; severity?: string }>;
Expand Down
11 changes: 6 additions & 5 deletions src/evals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { readFile, writeFile, readdir, mkdir } from "fs/promises";
import { join, resolve } from "path";
import { existsSync } from "fs";
import { safeRead } from "../utils/fs.js";
import { AI_PATHS } from "../schema-constants.js";
import type { EvalMetric } from "./types.js";

export type { EvalMetric } from "./types.js";
Expand Down Expand Up @@ -32,7 +33,7 @@ export async function runEvals(aiDir: string): Promise<EvalReport> {
metrics.push(await evalDeprecatedRatio(aiDir));

// 6. Recall test pass rate (Full tier only)
if (existsSync(join(aiDir, "temp/rule-tests/tests.json"))) {
if (existsSync(join(aiDir, AI_PATHS.RULE_TESTS))) {
metrics.push(await evalRuleTests(aiDir));
}

Expand Down Expand Up @@ -123,7 +124,7 @@ async function evalRuleCoverage(aiDir: string): Promise<EvalMetric> {
}

async function evalSessionCadence(aiDir: string): Promise<EvalMetric> {
const archive = await safeRead(join(aiDir, "sessions/archive/thread-archive.md"));
const archive = await safeRead(join(aiDir, AI_PATHS.THREAD_ARCHIVE));
const dateMatches = archive.match(/\[(\d{4}-\d{2}-\d{2})\]/g);
if (!dateMatches || dateMatches.length === 0) {
return { name: "Session cadence", value: "No sessions recorded", status: "warn" };
Expand Down Expand Up @@ -167,7 +168,7 @@ async function evalIndexCoverage(aiDir: string): Promise<EvalMetric> {
}

async function evalOpenItems(aiDir: string): Promise<EvalMetric> {
const content = await safeRead(join(aiDir, "sessions/open-items.md"));
const content = await safeRead(join(aiDir, AI_PATHS.OPEN_ITEMS));
const open = (content.match(/^- \[ \]/gm) ?? []).length;
const closed = (content.match(/^- \[x\]/gm) ?? []).length;
return {
Expand Down Expand Up @@ -206,8 +207,8 @@ async function evalDeprecatedRatio(aiDir: string): Promise<EvalMetric> {

async function evalRuleTests(aiDir: string): Promise<EvalMetric> {
// Check if all rule tests are referenced by a rule in harness.json
const testsPath = join(aiDir, "temp/rule-tests/tests.json");
const harnessPath = join(aiDir, "temp/harness.json");
const testsPath = join(aiDir, AI_PATHS.RULE_TESTS);
const harnessPath = join(aiDir, AI_PATHS.HARNESS);
try {
const tests = JSON.parse(await readFile(testsPath, "utf-8")) as Array<{ rule_id: string }>;
const rules = JSON.parse(await readFile(harnessPath, "utf-8")) as Array<{ id: string }>;
Expand Down
9 changes: 5 additions & 4 deletions src/evals/platform-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,13 @@ export async function evalCloudReadiness(projectDir: string): Promise<EvalMetric
checks.push("✗ .ai/ missing");
}

// Check .mcp.json exists (sync_memory available via MCP)
if (existsSync(join(projectDir, ".mcp.json"))) {
checks.push("✓ .mcp.json present (sync_memory available)");
// Check MCP config exists (sync_memory available via MCP)
const mcpLocations = [".mcp.json", ".cursor/mcp.json"].filter((p) => existsSync(join(projectDir, p)));
if (mcpLocations.length > 0) {
checks.push(`✓ MCP config present (${mcpLocations.join(", ")})`);
passed++;
} else {
checks.push("✗ .mcp.json missing");
checks.push("✗ No MCP config found (.mcp.json or .cursor/mcp.json)");
}

// Check if MCP server code supports HTTP
Expand Down
3 changes: 2 additions & 1 deletion src/governance/p0-parser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { readFile } from "fs/promises";
import { join } from "path";
import yaml from "js-yaml";
import { AI_PATHS } from "../schema-constants.js";

/** Regex rules only. Which lines to scan: additions (default), deletions, or both. */
export type RuleScope = "additions" | "deletions" | "all";
Expand Down Expand Up @@ -107,7 +108,7 @@ export function parseMemoryEntries(

// Read all [P0] entries from decisions.md and debugging.md
export async function readP0Entries(aiDir: string): Promise<P0Entry[]> {
const files = ["memory/decisions.md", "memory/debugging.md"];
const files = [AI_PATHS.DECISIONS, AI_PATHS.DEBUGGING];
const entries: P0Entry[] = [];

for (const file of files) {
Expand Down
17 changes: 9 additions & 8 deletions src/mcp-server/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { join } from "path";
import { existsSync } from "fs";
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { safeRead, assertPathWithinAiDir } from "../utils/fs.js";
import { AI_PATHS } from "../schema-constants.js";
import {
ListResourcesRequestSchema,
ReadResourceRequestSchema,
Expand Down Expand Up @@ -46,7 +47,7 @@ export function registerResources(server: Server, aiDir: string): void {
];

// Add harness/active if it exists (Full tier)
if (existsSync(join(aiDir, "temp/harness.json"))) {
if (existsSync(join(aiDir, AI_PATHS.HARNESS))) {
resources.push({
uri: "memory://harness/active",
name: "Active Harness Rules",
Expand All @@ -56,7 +57,7 @@ export function registerResources(server: Server, aiDir: string): void {
}

// Add evals if report exists
if (existsSync(join(aiDir, "temp/eval-report.json"))) {
if (existsSync(join(aiDir, AI_PATHS.EVAL_REPORT))) {
resources.push({
uri: "memory://evals",
name: "Eval Report",
Expand Down Expand Up @@ -87,17 +88,17 @@ export function registerResources(server: Server, aiDir: string): void {
}

if (uri === "memory://index") {
const index = await safeRead(join(aiDir, "memory/memory-index.md"));
const index = await safeRead(join(aiDir, AI_PATHS.MEMORY_INDEX));
return {
contents: [{ uri, mimeType: "text/markdown", text: index || "Memory index not yet generated. Run `/mem-compound` to create it." }],
};
}

if (uri === "memory://tails") {
const tailSpecs = [
{ file: "memory/decisions.md", lines: 40, heading: "## Recent Decisions" },
{ file: "memory/debugging.md", lines: 30, heading: "## Recent Debugging" },
{ file: "memory/patterns.md", lines: 20, heading: "## Recent Patterns" },
{ file: AI_PATHS.DECISIONS, lines: 40, heading: "## Recent Decisions" },
{ file: AI_PATHS.DEBUGGING, lines: 30, heading: "## Recent Debugging" },
{ file: AI_PATHS.PATTERNS, lines: 20, heading: "## Recent Patterns" },
{ file: "sessions/archive/thread-archive.md", lines: 200, heading: "## Session Archive (recent)" },
];
const tails = await Promise.all(tailSpecs.map((s) => tail(join(aiDir, s.file), s.lines)));
Expand All @@ -117,14 +118,14 @@ export function registerResources(server: Server, aiDir: string): void {
}

if (uri === "memory://harness/active") {
const harness = await safeRead(join(aiDir, "temp/harness.json"));
const harness = await safeRead(join(aiDir, AI_PATHS.HARNESS));
return {
contents: [{ uri, mimeType: "application/json", text: harness || "[]" }],
};
}

if (uri === "memory://evals") {
const report = await safeRead(join(aiDir, "temp/eval-report.json"));
const report = await safeRead(join(aiDir, AI_PATHS.EVAL_REPORT));
return {
contents: [{ uri, mimeType: "application/json", text: report || "{}" }],
};
Expand Down
2 changes: 1 addition & 1 deletion src/mcp-server/tools/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export async function handleGetOpenItems(aiDir: string): Promise<McpResponse> {
}

export async function handleGetEvals(aiDir: string): Promise<McpResponse> {
const evalPath = join(aiDir, "temp/eval-report.json");
const evalPath = join(aiDir, AI_PATHS.EVAL_REPORT);
try {
const content = await readFile(evalPath, "utf-8");
return textResponse(content);
Expand Down
15 changes: 1 addition & 14 deletions src/mcp-server/tools/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,13 @@ import { existsSync } from "fs";
import { execFileSync } from "child_process";
import matter from "gray-matter";
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
export { AI_PATHS } from "../../schema-constants.js";

/** Resolve project root from args or default to parent of aiDir */
export function getProjectRoot(args: Record<string, unknown>, aiDir: string): string {
return args.project_root ? String(args.project_root) : resolve(aiDir, "..");
}

// ─── Canonical paths within .ai/ ─────────────────────────────────────────────
export const AI_PATHS = {
OPEN_ITEMS: "sessions/open-items.md",
THREAD_ARCHIVE: "sessions/archive/thread-archive.md",
HARNESS: "temp/harness.json",
RULE_TESTS: "temp/rule-tests/tests.json",
EVAL_REPORT: "temp/eval-report.json",
EVAL_HISTORY: "temp/eval-history.jsonl",
MEMORY_INDEX: "memory/memory-index.md",
DECISIONS: "memory/decisions.md",
DEBUGGING: "memory/debugging.md",
PATTERNS: "memory/patterns.md",
} as const;

// Input limits (security)
export const MAX_COMMIT_CONTENT_BYTES = 1024 * 1024; // 1MB
export const MAX_GIT_DIFF_BYTES = 512 * 1024; // 500KB
Expand Down
15 changes: 15 additions & 0 deletions src/schema-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ export const SKILL_TOOL_NAMES_BLOCKLIST = [

export const VALID_OUTCOMES = ["success", "failure", "partial"] as const;

/** Canonical paths within .ai/ — single source of truth for all file references. */
export const AI_PATHS = {
OPEN_ITEMS: "sessions/open-items.md",
THREAD_ARCHIVE: "sessions/archive/thread-archive.md",
HARNESS: "temp/harness.json",
RULE_TESTS: "temp/rule-tests/tests.json",
EVAL_REPORT: "temp/eval-report.json",
EVAL_HISTORY: "temp/eval-history.jsonl",
MEMORY_INDEX: "memory/memory-index.md",
DECISIONS: "memory/decisions.md",
DEBUGGING: "memory/debugging.md",
PATTERNS: "memory/patterns.md",
IMPROVEMENTS: "memory/improvements.md",
} as const;

export type SchemaType = (typeof VALID_TYPES)[number];
export type SchemaStatus = (typeof VALID_STATUSES)[number];
export type Outcome = (typeof VALID_OUTCOMES)[number];
Expand Down
Loading