march-cli 0.1.30 → 0.1.32

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "march-cli",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "description": "March CLI — terminal-native coding agent with context reconstruction",
5
5
  "type": "module",
6
6
  "main": "./src/main.mjs",
@@ -113,9 +113,9 @@ function renderTable(token, lines, width) {
113
113
  shrinkColumns(widths, Math.max(1, width - borderWidth - paddingWidth));
114
114
 
115
115
  lines.push(formatTableBorder(widths, "┌", "┬", "┐"));
116
- lines.push(formatTableRow(cells[0], widths, true));
116
+ lines.push(...formatTableRow(cells[0], widths, true));
117
117
  lines.push(formatTableBorder(widths, "├", "┼", "┤"));
118
- for (const row of cells.slice(1)) lines.push(formatTableRow(row, widths, false));
118
+ for (const row of cells.slice(1)) lines.push(...formatTableRow(row, widths, false));
119
119
  lines.push(formatTableBorder(widths, "└", "┴", "┘"));
120
120
  }
121
121
 
@@ -200,12 +200,18 @@ function plainInline(tokens) {
200
200
  }
201
201
 
202
202
  function formatTableRow(row, widths, header) {
203
- const cells = widths.map((width, i) => {
204
- const text = truncateCell(row[i] ?? "", width);
205
- const padded = text + " ".repeat(Math.max(0, width - visibleWidth(text)));
206
- return ` ${header ? bold(padded) : padded} `;
207
- });
208
- return `${brightBlack("")}${cells.join(brightBlack("│"))}${brightBlack("│")}`;
203
+ const wrappedCells = widths.map((width, i) => wrapTableCell(row[i] ?? "", width));
204
+ const height = Math.max(...wrappedCells.map((cell) => cell.length));
205
+ const visualRows = [];
206
+ for (let lineIndex = 0; lineIndex < height; lineIndex += 1) {
207
+ const cells = widths.map((width, i) => {
208
+ const text = wrappedCells[i][lineIndex] ?? "";
209
+ const padded = text + " ".repeat(Math.max(0, width - visibleWidth(text)));
210
+ return ` ${header ? bold(padded) : padded} `;
211
+ });
212
+ visualRows.push(`${brightBlack("│")}${cells.join(brightBlack("│"))}${brightBlack("│")}`);
213
+ }
214
+ return visualRows;
209
215
  }
210
216
 
211
217
  function formatTableBorder(widths, left, join, right) {
@@ -218,22 +224,30 @@ function shrinkColumns(widths, maxTotal) {
218
224
  widths[index] -= 1;
219
225
  }
220
226
  }
221
-
222
- function truncateCell(text, width) {
223
- const chars = [];
224
- const sourceChars = Array.from(text);
225
- let used = 0;
226
- for (const ch of sourceChars) {
227
- const w = visibleWidth(ch);
228
- if (used + w > width) break;
229
- chars.push(ch);
230
- used += w;
231
- }
232
- if (chars.length < sourceChars.length && width > 1) {
233
- while (chars.length && used + 1 > width) used -= visibleWidth(chars.pop());
234
- return `${chars.join("")}…`;
227
+ function wrapTableCell(text, width) {
228
+ const lines = [];
229
+ let current = "";
230
+ let currentWidth = 0;
231
+ for (const ch of String(text ?? "")) {
232
+ if (ch === "\n") {
233
+ lines.push(current);
234
+ current = "";
235
+ currentWidth = 0;
236
+ continue;
237
+ }
238
+ const charWidth = visibleWidth(ch);
239
+ if (currentWidth + charWidth > width && currentWidth > 0) {
240
+ lines.push(current.trimEnd());
241
+ current = "";
242
+ currentWidth = 0;
243
+ if (/\s/.test(ch)) continue;
244
+ }
245
+ if (!current && /\s/.test(ch)) continue;
246
+ current += ch;
247
+ currentWidth += charWidth;
235
248
  }
236
- return chars.join("");
249
+ lines.push(current);
250
+ return lines;
237
251
  }
238
252
 
239
253
  function renderPlainMarkdownFallback(markdown, width) {
@@ -5,6 +5,7 @@ The user primarily asks for software engineering work: fixing bugs, adding behav
5
5
 
6
6
  <communication_contract>
7
7
  - Be concise and direct. Match the response shape to the task; simple questions get simple answers.
8
+ - Keep responses honest, restrained, and professional. Do not flatter, praise performatively, exaggerate approval, or default to agreeing with the user.
8
9
  - Assume users may not see tool calls. Before the first tool call, say in one sentence what you are about to do. While working, give brief updates when you find something important, change direction, or hit a blocker.
9
10
  - For multi-step work, checkpoint after meaningful milestones: what changed, what was verified, and what remains.
10
11
  - Don't narrate hidden reasoning. State decisions, results, and relevant next steps.
@@ -18,6 +19,7 @@ The user primarily asks for software engineering work: fixing bugs, adding behav
18
19
  - Distinguish the proposed solution from the underlying problem; restate the problem before accepting the solution when the user brings a design or implementation idea.
19
20
  - Surface assumptions and ambiguity before acting. If intent, constraints, or code organization are unclear, ask or state the uncertainty instead of guessing.
20
21
  - Challenge weak, over-engineered, or mis-scoped proposals directly and offer 1-2 concrete alternatives.
22
+ - If the user is mistaken, the logic is unclear, or information is insufficient, say so plainly with a brief reason, then offer facts, analysis, or actionable next steps.
21
23
  - Ask one focused question at a time; when useful, provide 2-4 distinct options rather than open-ended questionnaires.
22
24
  - Keep context use bounded. If the task is sprawling or the conversation is losing state, summarize and restart the plan instead of pushing forward blindly.
23
25
  - Do not force discussion when the request is already clear; summarize the decision and move toward the appropriate next step.
@@ -95,8 +97,10 @@ The user primarily asks for software engineering work: fixing bugs, adding behav
95
97
  - A recall hint's description may record key operational constraints, including when the full memory must be opened; factor those constraints into relevance before acting.
96
98
  - If a recall hint may help the current task, use memory_open(id) to read the full memory before relying on it. Ignore hints that are clearly unrelated or too low-value for the task.
97
99
  - Use memory_search(query) for full-text search across all memories.
98
- - To edit an existing memory, use memory_open(id) to get its path, then edit_file with mode="patch" for targeted edits.
99
- - Use memory_save() to create memories or update whole fields. Before creating a new memory, first search/open related memories and merge updates into an existing memory when they share the same topic, project, or decision thread; prefer modifying the existing memory file over creating a scattered new one. Tags are the primary retrieval key for future recall. Prefer lowercase kebab-case tags like 'march-cli', 'tooling', 'permissions'.
100
+ - To edit an existing memory's body, use memory_open(id) to get its path, then edit_file with mode="patch" for targeted edits.
101
+ - Memory identity is the `id`; local Markdown filenames are id-based storage paths and do not carry title meaning. The user-visible recall/list title comes from frontmatter `name`, not from the filename.
102
+ - When renaming or re-describing an existing memory, update the memory metadata fields (`name`, `description`, `tags`) as metadata, not only the Markdown body heading. Already-injected recall hints in the current prompt may remain stale until the next recall.
103
+ - Use memory_save() to create memories or update whole metadata fields on an existing memory. Before creating a new memory, first search/open related memories and merge updates into an existing memory when they share the same topic, project, or decision thread; prefer modifying the existing memory file over creating a scattered new one. Tags are the primary retrieval key for future recall. Prefer lowercase-kebab-case tags like 'march-cli', 'tooling', 'permissions'.
100
104
  - When learning multiple related external workflows or skills, maintain memory as an evolving domain library: start with the specific source name when only one item exists, then rename and rewrite the memory title/description as the scope grows; merge new related learnings into the same memory, preserving each source's unique traits while distilling reusable principles.
101
105
  - Distinguish "migrating a Skill to memory" from "learning a Skill": migration preserves the complete Skill folder under memory_root/skills/ and creates a memory entry as its index; that memory should describe what the Skill is for and reference the copied Skill folder path so future recall knows how to use it. Learning only reads and internalizes the Skill's methods, scenarios, and principles into ordinary memory without copying source files. Infer the action from the user's wording, and ask when ambiguous.
102
106
  - Unlike recall blocks, this system-core center is always visible in every model call. Only update the center for instructions that must always be followed; use memory for contextual, project-specific, or recall-dependent knowledge.
@@ -55,15 +55,6 @@ export function generateMemoryId() {
55
55
  return `mem_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
56
56
  }
57
57
 
58
- export function slugify(value) {
59
- return String(value ?? "memory")
60
- .trim()
61
- .toLowerCase()
62
- .replace(/[^a-z0-9\u4e00-\u9fff]+/g, "-")
63
- .replace(/^-+|-+$/g, "")
64
- .slice(0, 80) || "memory";
65
- }
66
-
67
58
  export function walkMarkdownFiles(root) {
68
59
  const out = [];
69
60
  if (!existsSync(root)) return out;
@@ -8,7 +8,6 @@ import {
8
8
  normalizeText,
9
9
  parseMemoryMarkdown,
10
10
  quoteFtsTerm,
11
- slugify,
12
11
  walkMarkdownFiles,
13
12
  } from "./markdown/markdown-format.mjs";
14
13
  import { scoreEntry, toHint } from "./markdown/markdown-recall.mjs";
@@ -176,7 +175,7 @@ export class MarkdownMemoryStore {
176
175
  if (!nextDescription) throw new Error("description is required");
177
176
  if (!existing && body == null) throw new Error("body is required");
178
177
  const nextBody = body ?? (existing ? parseMemoryMarkdown(readFileSync(existing.path, "utf8")).body : "");
179
- const nextPath = existing?.path ?? this.#newMemoryPath(now, nextName);
178
+ const nextPath = existing?.path ?? this.#newMemoryPath(now, nextId);
180
179
  mkdirSync(dirname(nextPath), { recursive: true });
181
180
  const content = formatMemoryMarkdown({
182
181
  frontmatter: {
@@ -248,11 +247,11 @@ export class MarkdownMemoryStore {
248
247
  }
249
248
  }
250
249
 
251
- #newMemoryPath(isoDate, name) {
250
+ #newMemoryPath(isoDate, id) {
252
251
  const date = isoDate.slice(0, 10);
253
252
  const [year, month, day] = date.split("-");
254
253
  const week = `week${Math.ceil(Number(day) / 7)}`;
255
- return join(this.root, year, month, week, `${date}-${slugify(name)}.md`);
254
+ return join(this.root, year, month, week, `${date}-${id}.md`);
256
255
  }
257
256
 
258
257
  #resolveMemoryPath(raw) {
@@ -70,7 +70,7 @@ export function createMarkdownMemoryTools(store, { remoteSources = [] } = {}) {
70
70
  name: "memory_save",
71
71
  label: "Memory Save",
72
72
  description:
73
- "Create a Markdown memory or update whole fields on an existing memory. For targeted edits to an existing memory body or frontmatter, use memory_open to get the path, then edit_file. Before creating a new memory, merge related updates into an existing memory when they share the same topic or decision thread. New memories require name, description, body, and at least one tag because recall hints only use tags. When updating by id, omitted fields keep their existing values; passing tags replaces the full tag list.",
73
+ "Create a Markdown memory or update whole fields on an existing memory. Local memory filenames are id-based storage paths; frontmatter name is the user-visible title. For targeted edits to an existing memory body or frontmatter, use memory_open to get the path, then edit_file. Before creating a new memory, merge related updates into an existing memory when they share the same topic or decision thread. New memories require name, description, body, and at least one tag because recall hints only use tags. When updating by id, omitted fields keep their existing values; passing tags replaces the full tag list.",
74
74
  parameters: Type.Object({
75
75
  id: Type.Optional(Type.String({ description: "Existing memory id to update. Omit to create a new memory." })),
76
76
  name: Type.Optional(Type.String({ description: "Memory name. Required when creating." })),