march-cli 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agent/command-exec-tool.mjs +42 -8
- package/src/agent/runner/runner-utils.mjs +6 -0
- package/src/agent/runner.mjs +16 -16
- package/src/agent/runtime/ipc/ipc-peer.mjs +99 -0
- package/src/agent/runtime/ipc/process-ipc-transport.mjs +16 -0
- package/src/agent/runtime/remote-runner-client.mjs +73 -0
- package/src/agent/runtime/remote-ui-client.mjs +19 -0
- package/src/agent/runtime/runner-ipc-target.mjs +126 -0
- package/src/agent/runtime/runner-process-client.mjs +47 -0
- package/src/agent/runtime/runner-process-entry.mjs +93 -0
- package/src/agent/runtime/ui-event-bridge.mjs +85 -0
- package/src/agent/tool-names.mjs +1 -1
- package/src/agent/tool-summary.mjs +112 -0
- package/src/agent/tools.mjs +0 -3
- package/src/agent/turn/turn-events.mjs +46 -0
- package/src/agent/turn/turn-runner.mjs +2 -1
- package/src/cli/commands/copy-command.mjs +16 -2
- package/src/cli/commands/status-command.mjs +7 -4
- package/src/cli/commands/thinking-command.mjs +10 -3
- package/src/cli/repl-loop.mjs +3 -1
- package/src/cli/startup/create-runtime-runner.mjs +61 -0
- package/src/cli/startup/startup-banner.mjs +64 -10
- package/src/cli/tui/layout/main-pane-layout.mjs +16 -7
- package/src/cli/tui/selection-screen.mjs +83 -34
- package/src/cli/tui/status/status-bar.mjs +154 -18
- package/src/cli/tui/syntax/highlighting.mjs +7 -24
- package/src/cli/tui/syntax/languages.mjs +1 -1
- package/src/cli/tui/tool-rendering.mjs +3 -113
- package/src/cli/tui/tui-handlers.mjs +1 -1
- package/src/cli/tui/ui-theme.mjs +14 -5
- package/src/cli/ui.mjs +1 -1
- package/src/context/engine.mjs +10 -9
- package/src/context/profiles.mjs +39 -0
- package/src/main.mjs +35 -29
- package/src/agent/find-tool.mjs +0 -112
- package/src/context/center-memory.mjs +0 -14
package/src/cli/tui/ui-theme.mjs
CHANGED
|
@@ -17,6 +17,7 @@ const brightRed = (s) => `\x1b[91m${s}${R}`;
|
|
|
17
17
|
const brightGreen = (s) => `\x1b[92m${s}${R}`;
|
|
18
18
|
const orange = (s) => `\x1b[38;2;245;167;66m${s}${R}`;
|
|
19
19
|
const softGreen = (s) => `\x1b[38;2;127;216;143m${s}${R}`;
|
|
20
|
+
const violet = (s) => `\x1b[38;2;232;91;226m${s}${R}`;
|
|
20
21
|
|
|
21
22
|
// ── Formatters ───────────────────────────────────────────────────────
|
|
22
23
|
const bold = (s) => `${B}${s}${R}`;
|
|
@@ -79,9 +80,16 @@ const message = {
|
|
|
79
80
|
};
|
|
80
81
|
|
|
81
82
|
const statusBar = {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
muted: brightBlack,
|
|
84
|
+
cwd: (s) => `${D}\x1b[38;5;244m${s}${R}`,
|
|
85
|
+
prompt: fg256(250),
|
|
86
|
+
accent: violet,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const modeLabel = {
|
|
90
|
+
do: orange,
|
|
91
|
+
discuss: green,
|
|
92
|
+
fallback: orange,
|
|
85
93
|
};
|
|
86
94
|
|
|
87
95
|
const shell = {
|
|
@@ -105,7 +113,7 @@ const selectList = {
|
|
|
105
113
|
|
|
106
114
|
// ── Editor theme (consumed by pi-tui Editor component) ──────────────
|
|
107
115
|
const EDITOR_THEME = {
|
|
108
|
-
borderColor:
|
|
116
|
+
borderColor: fg256(238),
|
|
109
117
|
selectList,
|
|
110
118
|
};
|
|
111
119
|
|
|
@@ -127,7 +135,7 @@ export {
|
|
|
127
135
|
R, B, D,
|
|
128
136
|
black, red, green, yellow, blue, magenta, cyan, white,
|
|
129
137
|
brightBlack, brightRed, brightGreen,
|
|
130
|
-
orange, softGreen,
|
|
138
|
+
orange, softGreen, violet,
|
|
131
139
|
bold, dim, inverse,
|
|
132
140
|
fg256, bg256,
|
|
133
141
|
// Semantic
|
|
@@ -140,6 +148,7 @@ export {
|
|
|
140
148
|
tool,
|
|
141
149
|
message,
|
|
142
150
|
statusBar,
|
|
151
|
+
modeLabel,
|
|
143
152
|
shell,
|
|
144
153
|
spinner,
|
|
145
154
|
selectList,
|
package/src/cli/ui.mjs
CHANGED
|
@@ -41,7 +41,7 @@ export function createTuiUI({
|
|
|
41
41
|
const tui = new TUI(terminal);
|
|
42
42
|
const output = new OutputBuffer();
|
|
43
43
|
const shellDrawer = new ShellDrawer({ shellRuntime });
|
|
44
|
-
const statusBar = new StatusBar();
|
|
44
|
+
const statusBar = new StatusBar(undefined, { cwd });
|
|
45
45
|
const editor = new Editor(tui, EDITOR_THEME, { paddingX: 1 });
|
|
46
46
|
const selection = new ScreenSelection();
|
|
47
47
|
const mainPane = new MainPaneLayout({ output, statusBar, editor, terminal, selection });
|
package/src/context/engine.mjs
CHANGED
|
@@ -3,14 +3,14 @@ import { buildSessionIdentity } from "./session-status.mjs";
|
|
|
3
3
|
import { buildSystemCore, resolveSystemCorePromptKey } from "./system-core.mjs";
|
|
4
4
|
import { buildInjectionsLayer } from "./injections.mjs";
|
|
5
5
|
import { buildProjectContext } from "./project-context.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { buildProfileLayers } from "./profiles.mjs";
|
|
7
7
|
import { formatRecallHints } from "../memory/markdown-store.mjs";
|
|
8
8
|
|
|
9
9
|
export class ContextEngine {
|
|
10
|
-
constructor({ cwd, modelId, provider = "deepseek", thinkingLevel = "medium", namespace = "", memoryRoot = null,
|
|
10
|
+
constructor({ cwd, modelId, provider = "deepseek", thinkingLevel = "medium", namespace = "", memoryRoot = null, profilePaths = null, shellRuntime = null, lspService = null, injections = [], maxTurns, trimBatch }) {
|
|
11
11
|
this.cwd = cwd;
|
|
12
12
|
this.memoryRoot = memoryRoot;
|
|
13
|
-
this.
|
|
13
|
+
this.profilePaths = profilePaths;
|
|
14
14
|
this.modelId = modelId;
|
|
15
15
|
this.provider = provider;
|
|
16
16
|
this.thinkingLevel = thinkingLevel;
|
|
@@ -62,19 +62,19 @@ export class ContextEngine {
|
|
|
62
62
|
const projectCtx = buildProjectContext(this.cwd);
|
|
63
63
|
if (projectCtx) layers.push({ name: "project_context", text: projectCtx });
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
if (centerMemory) layers.push({ name: "center_memory", text: centerMemory });
|
|
65
|
+
layers.push(...buildProfileLayers(this.profilePaths));
|
|
67
66
|
|
|
68
67
|
layers.push({ name: "recent_chat", text: this.#buildRecentChat() });
|
|
69
68
|
|
|
70
69
|
return layers;
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
recordTurn({ userMessage, assistantMessage, userRecallHints = [], assistantRecallHints = [] }) {
|
|
72
|
+
recordTurn({ userMessage, assistantMessage, assistantContext = "", userRecallHints = [], assistantRecallHints = [] }) {
|
|
74
73
|
this.turns.push({
|
|
75
74
|
index: this.turns.length + 1,
|
|
76
75
|
userMessage,
|
|
77
76
|
assistantMessage: assistantMessage ?? "",
|
|
77
|
+
assistantContext: assistantContext ?? "",
|
|
78
78
|
userRecallHints,
|
|
79
79
|
assistantRecallHints,
|
|
80
80
|
});
|
|
@@ -167,9 +167,10 @@ export class ContextEngine {
|
|
|
167
167
|
`[user]\n${String(turn.userMessage ?? "")}\n`;
|
|
168
168
|
const userRecall = formatRecallHints("user", turn.userRecallHints ?? []);
|
|
169
169
|
if (userRecall) block += `\n${userRecall}\n`;
|
|
170
|
-
block += `\n[
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
block += `\n[assistant]\n`;
|
|
171
|
+
const assistantText = turn.assistantContext || turn.assistantMessage;
|
|
172
|
+
if (assistantText) {
|
|
173
|
+
block += `\n${String(assistantText ?? "")}\n`;
|
|
173
174
|
}
|
|
174
175
|
const assistantRecall = formatRecallHints("assistant", turn.assistantRecallHints ?? []);
|
|
175
176
|
if (assistantRecall) block += `\n${assistantRecall}\n`;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export function defaultProfilePaths() {
|
|
6
|
+
const root = join(homedir(), ".march", "memory", "profiles");
|
|
7
|
+
return {
|
|
8
|
+
agent: join(root, "agent.md"),
|
|
9
|
+
user: join(root, "user.md"),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ensureProfileFiles(paths = defaultProfilePaths()) {
|
|
14
|
+
for (const [kind, path] of Object.entries(paths)) {
|
|
15
|
+
if (!path || existsSync(path)) continue;
|
|
16
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
17
|
+
writeFileSync(path, defaultProfileContent(kind), "utf8");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildProfileLayers(paths) {
|
|
22
|
+
if (!paths) return [];
|
|
23
|
+
return [
|
|
24
|
+
buildProfileLayer("agent_profile", paths.agent),
|
|
25
|
+
buildProfileLayer("user_profile", paths.user),
|
|
26
|
+
].filter(Boolean);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildProfileLayer(name, path) {
|
|
30
|
+
if (!path || !existsSync(path)) return null;
|
|
31
|
+
const content = readFileSync(path, "utf8").trimEnd();
|
|
32
|
+
if (!content.trim()) return null;
|
|
33
|
+
return { name, text: `[${name}]\n--- ${path} ---\n${content}` };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function defaultProfileContent(kind) {
|
|
37
|
+
const title = kind === "agent" ? "Agent Profile" : "User Profile";
|
|
38
|
+
return `# ${title}\n\n`;
|
|
39
|
+
}
|
package/src/main.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { createStatusLineUpdater } from "./cli/status-line-updater.mjs";
|
|
|
15
15
|
import { wireTuiHandlers } from "./cli/tui/tui-handlers.mjs";
|
|
16
16
|
import { createMarchAuthStorage } from "./auth/storage.mjs";
|
|
17
17
|
import { runLoginCommand } from "./auth/login-command.mjs";
|
|
18
|
-
import {
|
|
18
|
+
import { createRuntimeRunner } from "./cli/startup/create-runtime-runner.mjs";
|
|
19
19
|
import { createCliShellRuntime } from "./shell/cli-runtime.mjs";
|
|
20
20
|
import { MarkdownMemoryStore } from "./memory/markdown-store.mjs";
|
|
21
21
|
import { createMarkdownMemoryTools } from "./memory/markdown-tools.mjs";
|
|
@@ -23,14 +23,13 @@ import { loadDotEnv } from "./config/dotenv.mjs";
|
|
|
23
23
|
import { loadConfig } from "./config/loader.mjs";
|
|
24
24
|
import { discoverProjectExtensionPaths } from "./extensions/discovery.mjs";
|
|
25
25
|
import { loadProjectLifecycleHookManifests } from "./extensions/lifecycle-manifest.mjs";
|
|
26
|
-
import { resolvePiSessionManager } from "./session/pi-manager.mjs";
|
|
27
26
|
import { loadOrCreateProjectId, resumeStartupSession } from "./cli/startup/startup-session.mjs";
|
|
28
27
|
import { formatStartupBanner } from "./cli/startup/startup-banner.mjs";
|
|
29
28
|
import { initializeMcp } from "./mcp/index.mjs";
|
|
30
29
|
import { createWebToolsFromConfig } from "./web/tools.mjs";
|
|
31
30
|
import { createModelContextDumper } from "./debug/model-context-dumper.mjs";
|
|
32
31
|
import { createLogger, installProcessLogHandlers } from "./debug/logger.mjs";
|
|
33
|
-
import {
|
|
32
|
+
import { defaultProfilePaths, ensureProfileFiles } from "./context/profiles.mjs";
|
|
34
33
|
import { runProviderConfigCommand } from "./provider/config-command.mjs";
|
|
35
34
|
import { runWebSearchConfigCommand } from "./web/config-command.mjs";
|
|
36
35
|
import { createDesktopTurnNotifier } from "./notification/desktop-notifier.mjs";
|
|
@@ -49,6 +48,7 @@ export async function run(argv) {
|
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
const config = loadConfig(cwd);
|
|
51
|
+
const useRuntimeProcess = process.env.MARCH_RUNTIME_PROCESS !== "0";
|
|
52
52
|
installNetworkEnvironment(config.network);
|
|
53
53
|
if (args.command?.name === "login") {
|
|
54
54
|
try {
|
|
@@ -103,14 +103,16 @@ export async function run(argv) {
|
|
|
103
103
|
const modeState = createModeState();
|
|
104
104
|
const namespace = loadOrCreateProjectId(projectMarchDir);
|
|
105
105
|
const memoryRoot = resolveMemoryRoot(config.memoryRoot, stateRoot);
|
|
106
|
-
const
|
|
106
|
+
const profilePaths = defaultProfilePaths();
|
|
107
|
+
ensureProfileFiles(profilePaths);
|
|
107
108
|
const memoryStore = new MarkdownMemoryStore({ root: memoryRoot });
|
|
108
109
|
const memoryTools = createMarkdownMemoryTools(memoryStore);
|
|
109
110
|
const currentProject = basename(cwd);
|
|
110
111
|
const shellRuntime = args.shellRuntime ? createCliShellRuntime({ cwd }) : null;
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
const mcpInit = useRuntimeProcess
|
|
114
|
+
? { clientManager: null, mcpTools: [], mcpInjections: [], errors: [] }
|
|
115
|
+
: await initializeMcp({ projectDir: cwd });
|
|
114
116
|
const { clientManager: mcpClientManager, mcpTools, mcpInjections } = mcpInit;
|
|
115
117
|
for (const { server, error } of mcpInit.errors) {
|
|
116
118
|
if (args.json) {
|
|
@@ -159,17 +161,33 @@ export async function run(argv) {
|
|
|
159
161
|
let turnRunning = false;
|
|
160
162
|
let refreshStatusBar = null;
|
|
161
163
|
|
|
162
|
-
const
|
|
164
|
+
const runnerOptions = {
|
|
163
165
|
cwd,
|
|
164
166
|
modelId: model,
|
|
165
167
|
provider,
|
|
166
168
|
serviceTier,
|
|
167
|
-
|
|
168
169
|
providers: config.providers,
|
|
170
|
+
config,
|
|
169
171
|
stateRoot,
|
|
170
|
-
ui,
|
|
171
172
|
memoryRoot,
|
|
172
|
-
|
|
173
|
+
profilePaths,
|
|
174
|
+
namespace,
|
|
175
|
+
projectMarchDir,
|
|
176
|
+
extensionPaths,
|
|
177
|
+
permissionMode,
|
|
178
|
+
shellRuntime: Boolean(shellRuntime),
|
|
179
|
+
lifecycleHooks: lifecycleManifests.hooks,
|
|
180
|
+
lifecycleDiagnostics: lifecycleManifests.diagnostics,
|
|
181
|
+
modelContextDumper: {
|
|
182
|
+
enabled: args.dumpContext,
|
|
183
|
+
rootDir: contextDumpRoot,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const runner = await createRuntimeRunner({
|
|
188
|
+
useRuntimeProcess,
|
|
189
|
+
runnerOptions,
|
|
190
|
+
ui,
|
|
173
191
|
memoryStore,
|
|
174
192
|
memoryTools,
|
|
175
193
|
shellRuntime,
|
|
@@ -177,29 +195,14 @@ export async function run(argv) {
|
|
|
177
195
|
mcpInjections,
|
|
178
196
|
mcpClientManager,
|
|
179
197
|
webTools,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
extensionPaths,
|
|
183
|
-
sessionManager: resolvePiSessionManager({
|
|
184
|
-
cwd,
|
|
185
|
-
projectMarchDir,
|
|
186
|
-
enabled: usePiSessions,
|
|
187
|
-
}),
|
|
188
|
-
useRuntimeHost: usePiRuntimeHost,
|
|
189
|
-
syncPiSidecar: usePiSessions || usePiRuntimeHost,
|
|
190
|
-
lifecycleHooks: lifecycleManifests.hooks,
|
|
191
|
-
lifecycleDiagnostics: lifecycleManifests.diagnostics,
|
|
198
|
+
usePiSessions,
|
|
199
|
+
usePiRuntimeHost,
|
|
192
200
|
authStorage: authConfig.authStorage,
|
|
193
|
-
maxTurns: config.maxTurns ?? undefined,
|
|
194
|
-
trimBatch: config.trimBatch ?? undefined,
|
|
195
|
-
hostedTools: config.hostedTools,
|
|
196
201
|
permissionController,
|
|
197
202
|
modelContextDumper,
|
|
198
203
|
turnNotifier,
|
|
199
204
|
logger,
|
|
200
|
-
|
|
201
|
-
refreshStatusBar?.({ contextTokens: estimatedTokens });
|
|
202
|
-
},
|
|
205
|
+
refreshStatusBar: (...args) => refreshStatusBar?.(...args),
|
|
203
206
|
});
|
|
204
207
|
|
|
205
208
|
refreshStatusBar = createStatusLineUpdater({
|
|
@@ -209,7 +212,10 @@ export async function run(argv) {
|
|
|
209
212
|
sessionSource,
|
|
210
213
|
getMode: () => modeState.get(),
|
|
211
214
|
});
|
|
212
|
-
|
|
215
|
+
const initialContextTokens = typeof runner.estimateContextTokens === "function"
|
|
216
|
+
? await runner.estimateContextTokens("")
|
|
217
|
+
: null;
|
|
218
|
+
refreshStatusBar(initialContextTokens ? { contextTokens: initialContextTokens } : undefined);
|
|
213
219
|
|
|
214
220
|
wireTuiHandlers({
|
|
215
221
|
ui,
|
package/src/agent/find-tool.mjs
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { readdirSync, statSync } from "node:fs";
|
|
2
|
-
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
-
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { Type } from "typebox";
|
|
5
|
-
import { toolText } from "./tool-result.mjs";
|
|
6
|
-
|
|
7
|
-
const DEFAULT_LIMIT = 1000;
|
|
8
|
-
const DEFAULT_IGNORES = new Set([".git", "node_modules"]);
|
|
9
|
-
|
|
10
|
-
export function createFindTool({ cwd }) {
|
|
11
|
-
return defineTool({
|
|
12
|
-
name: "find",
|
|
13
|
-
label: "Find Files",
|
|
14
|
-
description: "Find files by glob pattern. Pattern is matched relative to the search directory. Basename-only patterns like '*.mjs' search recursively, so find('*.mjs', path:'src') and find('src/**/*.mjs') both work.",
|
|
15
|
-
parameters: Type.Object({
|
|
16
|
-
pattern: Type.String({ description: "Glob pattern to match files, e.g. '*.mjs', '**/*.json', or 'src/**/*.test.mjs'" }),
|
|
17
|
-
path: Type.Optional(Type.String({ description: "Directory to search in (default: current directory)" })),
|
|
18
|
-
limit: Type.Optional(Type.Number({ description: "Maximum number of results (default 1000)" })),
|
|
19
|
-
}),
|
|
20
|
-
execute: async (_toolCallId, params) => executeFind({ cwd, ...params }),
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function executeFind({ cwd, pattern, path = ".", limit = DEFAULT_LIMIT }) {
|
|
25
|
-
const searchRoot = resolveSearchRoot(cwd, path);
|
|
26
|
-
const trimmedPattern = String(pattern ?? "").trim().replaceAll("\\", "/");
|
|
27
|
-
if (!trimmedPattern) return toolText("Error: pattern is required", { error: true });
|
|
28
|
-
const effectivePattern = normalizePattern(trimmedPattern);
|
|
29
|
-
|
|
30
|
-
const max = Math.max(1, Number(limit) || DEFAULT_LIMIT);
|
|
31
|
-
let files;
|
|
32
|
-
try {
|
|
33
|
-
files = listFiles(searchRoot);
|
|
34
|
-
} catch (err) {
|
|
35
|
-
return toolText(`Error: ${err.message}`, { error: true });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const matches = [];
|
|
39
|
-
for (const file of files) {
|
|
40
|
-
const rel = toPosix(relative(searchRoot, file));
|
|
41
|
-
if (!matchesGlob(effectivePattern, rel)) continue;
|
|
42
|
-
matches.push(rel);
|
|
43
|
-
if (matches.length >= max) break;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (matches.length === 0) return toolText("No files found matching pattern", { pattern: trimmedPattern, effectivePattern, path: searchRoot, count: 0 });
|
|
47
|
-
const limitHint = matches.length >= max ? `\n\n[Results truncated to ${max}. Increase limit or refine pattern.]` : "";
|
|
48
|
-
return toolText(`${matches.join("\n")}${limitHint}`, {
|
|
49
|
-
pattern: trimmedPattern,
|
|
50
|
-
effectivePattern: effectivePattern === trimmedPattern ? undefined : effectivePattern,
|
|
51
|
-
path: searchRoot,
|
|
52
|
-
count: matches.length,
|
|
53
|
-
resultLimitReached: matches.length >= max ? max : undefined,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function normalizePattern(pattern) {
|
|
58
|
-
if (pattern.includes("/") || pattern.includes("**")) return pattern;
|
|
59
|
-
return `**/${pattern}`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function resolveSearchRoot(cwd, path) {
|
|
63
|
-
const raw = String(path || ".");
|
|
64
|
-
return isAbsolute(raw) ? raw : resolve(cwd, raw);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function listFiles(root) {
|
|
68
|
-
const out = [];
|
|
69
|
-
walk(root, out);
|
|
70
|
-
return out.sort((a, b) => toPosix(a).localeCompare(toPosix(b)));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function walk(dir, out) {
|
|
74
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
75
|
-
if (entry.isDirectory() && DEFAULT_IGNORES.has(entry.name)) continue;
|
|
76
|
-
const path = resolve(dir, entry.name);
|
|
77
|
-
if (entry.isDirectory()) walk(path, out);
|
|
78
|
-
else if (entry.isFile()) out.push(path);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function matchesGlob(pattern, candidate) {
|
|
83
|
-
return matchSegments(splitGlob(pattern), splitGlob(candidate));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function matchSegments(patternSegments, candidateSegments) {
|
|
87
|
-
if (patternSegments.length === 0) return candidateSegments.length === 0;
|
|
88
|
-
const [head, ...tail] = patternSegments;
|
|
89
|
-
if (head === "**") {
|
|
90
|
-
if (matchSegments(tail, candidateSegments)) return true;
|
|
91
|
-
return candidateSegments.length > 0 && matchSegments(patternSegments, candidateSegments.slice(1));
|
|
92
|
-
}
|
|
93
|
-
if (candidateSegments.length === 0) return false;
|
|
94
|
-
return matchSegment(head, candidateSegments[0]) && matchSegments(tail, candidateSegments.slice(1));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function matchSegment(pattern, candidate) {
|
|
98
|
-
const regex = new RegExp(`^${escapeRegex(pattern).replaceAll("\\*", "[^/]*").replaceAll("\\?", "[^/]")}$`);
|
|
99
|
-
return regex.test(candidate);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function splitGlob(value) {
|
|
103
|
-
return String(value).split("/").filter(Boolean);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function toPosix(value) {
|
|
107
|
-
return String(value).replaceAll("\\", "/");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function escapeRegex(value) {
|
|
111
|
-
return String(value).replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
|
|
112
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
|
|
5
|
-
export function defaultCenterMemoryPath() {
|
|
6
|
-
return join(homedir(), ".march", "memory", "center.md");
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function buildCenterMemory(path = defaultCenterMemoryPath()) {
|
|
10
|
-
if (!path || !existsSync(path)) return null;
|
|
11
|
-
const content = readFileSync(path, "utf8").trimEnd();
|
|
12
|
-
if (!content.trim()) return null;
|
|
13
|
-
return `[center_memory]\n--- ${path} ---\n${content}`;
|
|
14
|
-
}
|