Skip to content

Commit 8dd4d0b

Browse files
export field configured by exposing a stable public API surface for mex-agent (#44)
* export field configured * cleanups done, inclding rewriting package-lock.json
1 parent 08b2bbf commit 8dd4d0b

9 files changed

Lines changed: 431 additions & 33 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ result
1212

1313
# OS
1414
Thumbs.db
15+
16+
# Per-user Claude Code config
17+
.claude/

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
"version": "0.3.5",
44
"description": "CLI engine for mex scaffold — drift detection, pre-analysis, and targeted sync",
55
"type": "module",
6+
"main": "./dist/index.js",
7+
"types": "./dist/index.d.ts",
8+
"exports": {
9+
".": {
10+
"types": "./dist/index.d.ts",
11+
"import": "./dist/index.js"
12+
},
13+
"./package.json": "./package.json"
14+
},
615
"bin": {
716
"mex": "./dist/cli.js"
817
},

src/config.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,51 @@
11
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
2-
import { resolve, dirname } from "node:path";
2+
import { resolve, dirname, isAbsolute } from "node:path";
33
import type { MexConfig, AiTool, StalenessThresholds, WatchConfig, HeartbeatConfig } from "./types.js";
44
import { DEFAULT_STALENESS_THRESHOLDS } from "./drift/checkers/staleness.js";
55

6+
/**
7+
* Inputs accepted by {@link createConfig}. Only the two roots are required —
8+
* everything else mirrors the optional fields on {@link MexConfig} so callers
9+
* can opt in field by field.
10+
*/
11+
export interface CreateConfigInput {
12+
/** Absolute path to the project root (e.g. where .git lives). */
13+
projectRoot: string;
14+
/** Absolute path to the scaffold root (the directory holding ROUTER.md, etc.). */
15+
scaffoldRoot: string;
16+
aiTools?: AiTool[];
17+
stalenessThresholds?: StalenessThresholds;
18+
watch?: WatchConfig;
19+
heartbeat?: HeartbeatConfig;
20+
}
21+
22+
/**
23+
* Build a {@link MexConfig} from explicit inputs, bypassing the on-disk
24+
* discovery that {@link findConfig} performs. Intended for embedders that
25+
* already know where the project and scaffold live — for example, tools that
26+
* use a non-default scaffold directory name and therefore can't rely on
27+
* findConfig's `.mex/` lookup.
28+
*
29+
* Defaults `aiTools` to an empty array. Other optional fields are passed
30+
* through untouched.
31+
*/
32+
export function createConfig(input: CreateConfigInput): MexConfig {
33+
if (!isAbsolute(input.projectRoot)) {
34+
throw new Error(`createConfig: projectRoot must be an absolute path, got "${input.projectRoot}"`);
35+
}
36+
if (!isAbsolute(input.scaffoldRoot)) {
37+
throw new Error(`createConfig: scaffoldRoot must be an absolute path, got "${input.scaffoldRoot}"`);
38+
}
39+
return {
40+
projectRoot: input.projectRoot,
41+
scaffoldRoot: input.scaffoldRoot,
42+
aiTools: input.aiTools ?? [],
43+
stalenessThresholds: input.stalenessThresholds,
44+
watch: input.watch,
45+
heartbeat: input.heartbeat,
46+
};
47+
}
48+
649
/**
750
* Walk up from startDir looking for .git to find project root,
851
* then look for scaffold root (.mex/ or context/ directory).

src/drift/index.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,45 @@ import { checkCrossFile } from "./checkers/cross-file.js";
1515
import { checkScriptCoverage } from "./checkers/script-coverage.js";
1616
import { checkToolConfigSync } from "./checkers/tool-config-sync.js";
1717

18+
/**
19+
* Default glob patterns used to locate scaffold markdown files, relative to
20+
* `MexConfig.scaffoldRoot`. Exported so consumers can extend rather than
21+
* replace the list, e.g.
22+
*
23+
* ```ts
24+
* runDriftCheck(config, {
25+
* scaffoldPatterns: [...DEFAULT_SCAFFOLD_PATTERNS, "traces/**\/*.md"],
26+
* });
27+
* ```
28+
*
29+
* NOT a stable contract — mex may add to this list between minor versions.
30+
* If exact behavior matters, pass `scaffoldPatterns` explicitly.
31+
*/
32+
export const DEFAULT_SCAFFOLD_PATTERNS = [
33+
"context/*.md",
34+
"patterns/*.md",
35+
"ROUTER.md",
36+
"AGENTS.md",
37+
"SETUP.md",
38+
"SYNC.md",
39+
] as const;
40+
41+
export interface RunDriftCheckOpts {
42+
verbose?: boolean;
43+
/** Override the glob patterns used to discover scaffold files (relative to
44+
* `config.scaffoldRoot`). Defaults to {@link DEFAULT_SCAFFOLD_PATTERNS}. */
45+
scaffoldPatterns?: readonly string[];
46+
}
47+
1848
/** Run full drift detection across all scaffold files */
1949
export async function runDriftCheck(
2050
config: MexConfig,
21-
opts: { verbose?: boolean } = {}
51+
opts: RunDriftCheckOpts = {}
2252
): Promise<DriftReport> {
2353
const { projectRoot, scaffoldRoot } = config;
2454

2555
// Find all markdown files in scaffold
26-
const scaffoldFiles = findScaffoldFiles(projectRoot, scaffoldRoot);
56+
const scaffoldFiles = findScaffoldFiles(projectRoot, scaffoldRoot, opts.scaffoldPatterns);
2757
const allClaims: Claim[] = [];
2858
const allIssues: DriftIssue[] = [];
2959
const checkerIssueCounts: Array<[string, number]> = [];
@@ -106,21 +136,13 @@ export async function runDriftCheck(
106136
/** Find all markdown files that are part of the scaffold */
107137
function findScaffoldFiles(
108138
projectRoot: string,
109-
scaffoldRoot: string
139+
scaffoldRoot: string,
140+
patterns: readonly string[] = DEFAULT_SCAFFOLD_PATTERNS
110141
): string[] {
111-
const scaffoldPatterns = [
112-
"context/*.md",
113-
"patterns/*.md",
114-
"ROUTER.md",
115-
"AGENTS.md",
116-
"SETUP.md",
117-
"SYNC.md",
118-
];
119-
120142
const files: string[] = [];
121143

122144
// Search inside scaffold root (handles both .mex/ and root layouts)
123-
for (const pattern of scaffoldPatterns) {
145+
for (const pattern of patterns) {
124146
const matches = globSync(pattern, {
125147
cwd: scaffoldRoot,
126148
absolute: true,

src/events.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { dirname, resolve, relative } from "node:path";
33
import chalk from "chalk";
44
import type { MexConfig } from "./types.js";
55

6-
export type EventKind = "decision" | "note" | "risk" | "todo";
6+
/** Runtime list of valid event kinds. Re-exported as part of the public API so
7+
* consumers can validate user-supplied kinds against the same source of truth. */
8+
export const EVENT_KINDS = ["decision", "note", "risk", "todo"] as const;
9+
10+
export type EventKind = (typeof EVENT_KINDS)[number];
711

812
export interface EventEntry {
913
timestamp: string;
@@ -25,7 +29,7 @@ export interface TimelineOpts {
2529
limit?: number;
2630
}
2731

28-
const VALID_KINDS = new Set<EventKind>(["decision", "note", "risk", "todo"]);
32+
const VALID_KINDS = new Set<EventKind>(EVENT_KINDS);
2933
const EVENT_FILE = "events/decisions.jsonl";
3034

3135
export function eventLogPath(config: MexConfig): string {

src/heartbeat.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,30 @@ export interface HeartbeatResult {
1515

1616
export interface HeartbeatOpts {
1717
json?: boolean;
18+
/** Override the glob patterns used to discover heartbeat files (relative to
19+
* `config.scaffoldRoot`). Defaults to {@link DEFAULT_HEARTBEAT_PATTERNS}. */
20+
scaffoldPatterns?: readonly string[];
1821
}
1922

23+
/**
24+
* Default glob patterns used by the heartbeat checker, relative to
25+
* `MexConfig.scaffoldRoot`. NOT a stable contract — mex may add to this list
26+
* between minor versions. Pass `scaffoldPatterns` explicitly when exact
27+
* behavior matters.
28+
*/
29+
export const DEFAULT_HEARTBEAT_PATTERNS = [
30+
"ROUTER.md",
31+
"AGENTS.md",
32+
"context/*.md",
33+
"patterns/*.md",
34+
] as const;
35+
2036
const DEFAULT_STALE_DAYS = 7;
2137
const DEFAULT_MEMORY_CLEANUP_DAYS = 7;
2238
const DEFAULT_DAILY_MEMORY_RETENTION_DAYS = 14;
2339

2440
export async function runHeartbeat(config: MexConfig, opts: HeartbeatOpts = {}): Promise<HeartbeatResult> {
25-
const result = checkHeartbeat(config);
41+
const result = checkHeartbeat(config, new Date(), { scaffoldPatterns: opts.scaffoldPatterns });
2642
if (opts.json) {
2743
console.log(JSON.stringify(result, null, 2));
2844
return result;
@@ -31,12 +47,22 @@ export async function runHeartbeat(config: MexConfig, opts: HeartbeatOpts = {}):
3147
return result;
3248
}
3349

34-
export function checkHeartbeat(config: MexConfig, now = new Date()): HeartbeatResult {
50+
export interface CheckHeartbeatOpts {
51+
/** Override the glob patterns used to discover heartbeat files (relative to
52+
* `config.scaffoldRoot`). Defaults to {@link DEFAULT_HEARTBEAT_PATTERNS}. */
53+
scaffoldPatterns?: readonly string[];
54+
}
55+
56+
export function checkHeartbeat(
57+
config: MexConfig,
58+
now = new Date(),
59+
opts: CheckHeartbeatOpts = {}
60+
): HeartbeatResult {
3561
const staleDays = config.heartbeat?.staleDays ?? DEFAULT_STALE_DAYS;
3662
const memoryCleanupDays = config.heartbeat?.memoryCleanupDays ?? DEFAULT_MEMORY_CLEANUP_DAYS;
3763
const dailyRetentionDays = config.heartbeat?.dailyMemoryRetentionDays ?? DEFAULT_DAILY_MEMORY_RETENTION_DAYS;
3864

39-
const staleFiles = scaffoldHeartbeatFiles(config.scaffoldRoot)
65+
const staleFiles = scaffoldHeartbeatFiles(config.scaffoldRoot, opts.scaffoldPatterns)
4066
.map((file) => {
4167
const fm = parseFrontmatter(file);
4268
const days = daysSinceFrontmatterDate(
@@ -60,8 +86,10 @@ export function checkHeartbeat(config: MexConfig, now = new Date()): HeartbeatRe
6086
};
6187
}
6288

63-
function scaffoldHeartbeatFiles(scaffoldRoot: string): string[] {
64-
const patterns = ["ROUTER.md", "AGENTS.md", "context/*.md", "patterns/*.md"];
89+
function scaffoldHeartbeatFiles(
90+
scaffoldRoot: string,
91+
patterns: readonly string[] = DEFAULT_HEARTBEAT_PATTERNS,
92+
): string[] {
6593
return patterns.flatMap((pattern) =>
6694
globSync(pattern, {
6795
cwd: scaffoldRoot,

src/index.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Public API for `mex-agent`.
3+
*
4+
* Everything re-exported from this file is part of the package's compatibility
5+
* contract — removing or reshaping any of these is a breaking change.
6+
*
7+
* Internal modules (`src/cli.ts`, `src/sync/`, `src/scanner/`, `src/setup/`,
8+
* `src/tui.ts`, `src/watch.ts`, `src/doctor.ts`, etc.) are NOT part of the
9+
* contract and may change without notice. Import only from `"mex-agent"`.
10+
*/
11+
12+
// ── Config ───────────────────────────────────────────────────────────────────
13+
export { findConfig, createConfig } from "./config.js";
14+
export type { CreateConfigInput } from "./config.js";
15+
16+
// ── Events (append-only JSONL log) ───────────────────────────────────────────
17+
export {
18+
appendEvent,
19+
readEvents,
20+
eventLogPath,
21+
EVENT_KINDS,
22+
} from "./events.js";
23+
export type { EventEntry, EventKind } from "./events.js";
24+
25+
// ── Drift detection ──────────────────────────────────────────────────────────
26+
export {
27+
runDriftCheck,
28+
DEFAULT_SCAFFOLD_PATTERNS,
29+
} from "./drift/index.js";
30+
export type { RunDriftCheckOpts } from "./drift/index.js";
31+
export { parseFrontmatter } from "./drift/frontmatter.js";
32+
export { DEFAULT_STALENESS_THRESHOLDS } from "./drift/checkers/staleness.js";
33+
34+
// ── Heartbeat (scaffold staleness + memory cleanup) ──────────────────────────
35+
export {
36+
checkHeartbeat,
37+
runHeartbeat,
38+
DEFAULT_HEARTBEAT_PATTERNS,
39+
} from "./heartbeat.js";
40+
export type {
41+
HeartbeatResult,
42+
HeartbeatOpts,
43+
CheckHeartbeatOpts,
44+
} from "./heartbeat.js";
45+
46+
// ── Shared types ─────────────────────────────────────────────────────────────
47+
export type {
48+
AiTool,
49+
MexConfig,
50+
StalenessThresholds,
51+
WatchConfig,
52+
HeartbeatConfig,
53+
ScaffoldFrontmatter,
54+
FrontmatterEdge,
55+
DriftReport,
56+
DriftIssue,
57+
IssueCode,
58+
Severity,
59+
Claim,
60+
ClaimKind,
61+
} from "./types.js";

0 commit comments

Comments
 (0)