march-cli 0.1.26 → 0.1.28
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/runtime/remote-ui-client.mjs +1 -1
- package/src/agent/runtime/ui-event-bridge.mjs +2 -2
- package/src/agent/turn/turn-runner.mjs +2 -2
- package/src/cli/fallback-ui.mjs +4 -4
- package/src/cli/repl-loop.mjs +5 -5
- package/src/cli/tui/layout/main-pane-layout.mjs +2 -1
- package/src/cli/tui/output/tool-card-renderer.mjs +8 -2
- package/src/cli/tui/recall-rendering.mjs +6 -7
- package/src/cli/tui/status/status-bar.mjs +11 -3
- package/src/cli/tui/tui-input-controller.mjs +2 -1
- package/src/cli/ui.mjs +9 -10
- package/src/context/system-core/base.md +37 -7
- package/src/memory/markdown/markdown-recall.mjs +1 -1
- package/src/memory/markdown-store.mjs +1 -1
- package/src/memory/markdown-tools.mjs +5 -5
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@ export function createRemoteRuntimeUiClient(peer) {
|
|
|
13
13
|
retryEnd: (event) => peer.notify("uiEvent", { type: "retry_end", ...event }),
|
|
14
14
|
status: (text) => peer.notify("uiEvent", { type: "status", text }),
|
|
15
15
|
debugLines: (lines) => peer.notify("uiEvent", { type: "debug_lines", lines }),
|
|
16
|
-
|
|
16
|
+
recall: ({ source, hints }) => peer.notify("uiEvent", { type: "recall", source, hints }),
|
|
17
17
|
editDiff: (path, diffLines) => peer.notify("uiEvent", { type: "edit_diff", path, diffLines }),
|
|
18
18
|
requestPermission: (request) => peer.call("uiRequest", { type: "permission_request", ...request }),
|
|
19
19
|
};
|
|
@@ -50,7 +50,7 @@ export function createRuntimeUiClient(eventBus) {
|
|
|
50
50
|
retryEnd: (event) => eventBus.emit({ type: "retry_end", ...event }),
|
|
51
51
|
status: (text) => eventBus.emit({ type: "status", text }),
|
|
52
52
|
debugLines: (lines) => eventBus.emit({ type: "debug_lines", lines }),
|
|
53
|
-
|
|
53
|
+
recall: ({ source, hints }) => eventBus.emit({ type: "recall", source, hints }),
|
|
54
54
|
editDiff: (path, diffLines) => eventBus.emit({ type: "edit_diff", path, diffLines }),
|
|
55
55
|
requestPermission: (request) => eventBus.request({ type: "permission_request", ...request }),
|
|
56
56
|
};
|
|
@@ -71,7 +71,7 @@ export function dispatchRuntimeUiEvent(ui, event) {
|
|
|
71
71
|
case "retry_end": return ui.retryEnd?.(pickRetryEnd(event));
|
|
72
72
|
case "status": return ui.status?.(event.text);
|
|
73
73
|
case "debug_lines": return writeDebugLines(ui, event.lines);
|
|
74
|
-
case "
|
|
74
|
+
case "recall": return ui.recall?.({ source: event.source, hints: event.hints });
|
|
75
75
|
case "edit_diff": return ui.editDiff?.(event.path, event.diffLines);
|
|
76
76
|
case "permission_request": return ui.requestPermission?.({ toolName: event.toolName, params: event.params, category: event.category });
|
|
77
77
|
default: return undefined;
|
|
@@ -42,7 +42,7 @@ export async function runRunnerTurn({
|
|
|
42
42
|
if (hints.length > 0) {
|
|
43
43
|
midTurnRecallHints.push(...hints);
|
|
44
44
|
queueMidTurnRecallHints(activeSession, hints, logger);
|
|
45
|
-
ui.
|
|
45
|
+
ui.recall?.({ source: "assistant", hints });
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
});
|
|
@@ -93,7 +93,7 @@ function queueMidTurnRecallHints(session, hints, logger) {
|
|
|
93
93
|
const content = formatRecallHints("assistant", hints);
|
|
94
94
|
if (!content) return;
|
|
95
95
|
const injected = session.sendCustomMessage?.({
|
|
96
|
-
customType: "march.
|
|
96
|
+
customType: "march.recall",
|
|
97
97
|
content,
|
|
98
98
|
display: false,
|
|
99
99
|
details: { source: "assistant" },
|
package/src/cli/fallback-ui.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { stdout } from "node:process";
|
|
2
2
|
import { extractToolOutput } from "./tool-output.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { formatRecallLines } from "./tui/recall-rendering.mjs";
|
|
4
4
|
import { formatToolStartLine } from "./tui/tool-rendering.mjs";
|
|
5
5
|
import { brightBlack, dim, red, green, yellow } from "./tui/ui-theme.mjs";
|
|
6
6
|
|
|
@@ -32,7 +32,7 @@ export function createJsonUI() {
|
|
|
32
32
|
stdout.write(delta);
|
|
33
33
|
},
|
|
34
34
|
status: () => {},
|
|
35
|
-
|
|
35
|
+
recall: () => {},
|
|
36
36
|
clearOutput: () => {},
|
|
37
37
|
restoreTranscript: () => {},
|
|
38
38
|
setStatusBar: () => {},
|
|
@@ -111,9 +111,9 @@ export function createPlainUI() {
|
|
|
111
111
|
},
|
|
112
112
|
textDelta: writeText,
|
|
113
113
|
status: (text) => { ensureNewline(); stdout.write(`${brightBlack(`● ${text}`)}\n`); },
|
|
114
|
-
|
|
114
|
+
recall: ({ hints }) => {
|
|
115
115
|
ensureNewline();
|
|
116
|
-
for (const line of
|
|
116
|
+
for (const line of formatRecallLines(hints)) stdout.write(`${brightBlack(line)}\n`);
|
|
117
117
|
},
|
|
118
118
|
clearOutput: () => {},
|
|
119
119
|
restoreTranscript: () => {},
|
package/src/cli/repl-loop.mjs
CHANGED
|
@@ -27,8 +27,8 @@ export async function runSingleShotPrompt({
|
|
|
27
27
|
const modePrompt = appendModeReminder(prompt, modeState?.get?.());
|
|
28
28
|
const fullPrompt = appendPromptBlocks(modePrompt, recallBlock, carryoverRecallBlock, shellHints);
|
|
29
29
|
ui.writeln(formatUserDisplayMessage(prompt));
|
|
30
|
-
ui.
|
|
31
|
-
if (carryoverRecallHints.length > 0 && !carryoverAlreadyRendered) ui.
|
|
30
|
+
ui.recall?.({ source: "user", hints: userRecallHints });
|
|
31
|
+
if (carryoverRecallHints.length > 0 && !carryoverAlreadyRendered) ui.recall?.({ source: "assistant", hints: carryoverRecallHints });
|
|
32
32
|
refreshStatusBar.startWorking?.();
|
|
33
33
|
try {
|
|
34
34
|
await runner.runTurn(fullPrompt, prompt, { userRecallHints, currentProject });
|
|
@@ -149,8 +149,8 @@ async function runReplTurn({ prompt, args, runner, memoryStore, currentProject,
|
|
|
149
149
|
const fullPrompt = appendPromptBlocks(modePrompt, recallBlock, carryoverRecallBlock, shellHints);
|
|
150
150
|
try {
|
|
151
151
|
ui.writeln(formatUserDisplayMessage(prompt));
|
|
152
|
-
ui.
|
|
153
|
-
if (carryoverRecallHints.length > 0 && !carryoverAlreadyRendered) ui.
|
|
152
|
+
ui.recall?.({ source: "user", hints: userRecallHints });
|
|
153
|
+
if (carryoverRecallHints.length > 0 && !carryoverAlreadyRendered) ui.recall?.({ source: "assistant", hints: carryoverRecallHints });
|
|
154
154
|
setTurnRunning(true);
|
|
155
155
|
refreshStatusBar.startWorking?.();
|
|
156
156
|
await runner.runTurn(fullPrompt, prompt, { userRecallHints, currentProject });
|
|
@@ -178,6 +178,6 @@ function renderPendingAssistantRecallPreview({ runner, ui }) {
|
|
|
178
178
|
if (runner.engine.hasRenderedPendingAssistantRecallHints?.()) return;
|
|
179
179
|
const hints = runner.engine.peekPendingAssistantRecallHints?.() ?? [];
|
|
180
180
|
if (hints.length === 0) return;
|
|
181
|
-
ui.
|
|
181
|
+
ui.recall?.({ source: "assistant", hints });
|
|
182
182
|
runner.engine.markPendingAssistantRecallHintsRendered?.();
|
|
183
183
|
}
|
|
@@ -10,7 +10,8 @@ export class MainPaneLayout {
|
|
|
10
10
|
render(width) {
|
|
11
11
|
const safeWidth = Math.max(1, Math.trunc(width));
|
|
12
12
|
const statusTopLines = this.statusBar.renderTop?.(safeWidth) ?? this.statusBar.render(safeWidth);
|
|
13
|
-
const
|
|
13
|
+
const editorWidth = this.statusBar.inputContentWidth?.(safeWidth) ?? safeWidth;
|
|
14
|
+
const rawEditorLines = this.editor.render(Math.max(1, editorWidth));
|
|
14
15
|
const editorLines = this.statusBar.renderInputLines?.(rawEditorLines, safeWidth)
|
|
15
16
|
?? rawEditorLines.map((line) => this.statusBar.renderInputLine?.(line, safeWidth) ?? line);
|
|
16
17
|
const statusBottomLines = this.statusBar.renderBottom?.(safeWidth) ?? [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
-
import { R, brightBlack, dim, red } from "../ui-theme.mjs";
|
|
2
|
+
import { R, brightBlack, dim, red, warmAmber } from "../ui-theme.mjs";
|
|
3
3
|
|
|
4
4
|
export function renderToolCardBlock(block, width) {
|
|
5
5
|
const lines = [];
|
|
@@ -7,7 +7,7 @@ export function renderToolCardBlock(block, width) {
|
|
|
7
7
|
const marker = block.state === "running" ? "▶" : block.expanded ? "▾" : "▸";
|
|
8
8
|
const summary = block.summary ? ` · ${block.summary}` : "";
|
|
9
9
|
const head = `${marker} ${block.title}${summary}`;
|
|
10
|
-
appendCardWrapped(lines, border, (block
|
|
10
|
+
appendCardWrapped(lines, border, colorToolHead(block)(head), width);
|
|
11
11
|
|
|
12
12
|
if (block.expanded && block.bodyLines?.length) {
|
|
13
13
|
lines.push(border);
|
|
@@ -16,6 +16,12 @@ export function renderToolCardBlock(block, width) {
|
|
|
16
16
|
return lines;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function colorToolHead(block) {
|
|
20
|
+
if (block.isError) return red;
|
|
21
|
+
if (block.name === "memory_open") return warmAmber;
|
|
22
|
+
return dim;
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
function appendCardWrapped(lines, border, text, width, indent = "") {
|
|
20
26
|
const prefix = `${border} ${indent}`;
|
|
21
27
|
const contentWidth = Math.max(8, width - visibleWidth(prefix));
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { brightBlack
|
|
1
|
+
import { brightBlack } from "./ui-theme.mjs";
|
|
2
2
|
|
|
3
3
|
const RECALL_ICON = "✦";
|
|
4
4
|
|
|
5
|
-
export function
|
|
5
|
+
export function formatRecallLines(hints = []) {
|
|
6
6
|
if (!hints.length) return [];
|
|
7
7
|
const noun = hints.length === 1 ? "note" : "notes";
|
|
8
8
|
return [
|
|
@@ -11,11 +11,10 @@ export function formatMemoryHintLines(hints = []) {
|
|
|
11
11
|
];
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export function
|
|
15
|
-
const lines =
|
|
16
|
-
lines.forEach((line
|
|
17
|
-
if (
|
|
18
|
-
else if (line.startsWith(" ")) output.writeln(brightBlack(line));
|
|
14
|
+
export function writeRecall({ output, hints = [] }) {
|
|
15
|
+
const lines = formatRecallLines(hints);
|
|
16
|
+
lines.forEach((line) => {
|
|
17
|
+
if (line.startsWith(" ")) output.writeln(brightBlack(line));
|
|
19
18
|
else output.writeln(line);
|
|
20
19
|
});
|
|
21
20
|
}
|
|
@@ -45,6 +45,10 @@ export class StatusBar {
|
|
|
45
45
|
return [`${left}${line}${right}`, ""];
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
inputContentWidth(width) {
|
|
49
|
+
return maxInputContentWidth(width);
|
|
50
|
+
}
|
|
51
|
+
|
|
48
52
|
renderInputLines(lines, width) {
|
|
49
53
|
if (width <= 0) return [""];
|
|
50
54
|
const { left, innerWidth, right } = insetForWidth(width);
|
|
@@ -62,9 +66,7 @@ export class StatusBar {
|
|
|
62
66
|
if (width <= 0) return "";
|
|
63
67
|
const paintWidth = inputPaintWidth(width);
|
|
64
68
|
const prompt = isFirst ? statusBar.prompt(INPUT_PROMPT) : " ";
|
|
65
|
-
const
|
|
66
|
-
const maxContentWidth = Math.max(0, paintWidth - promptWidth - 2);
|
|
67
|
-
const content = clipToWidth(line, maxContentWidth);
|
|
69
|
+
const content = clipToWidth(line, maxInputContentWidth(width));
|
|
68
70
|
return applyInputBackground(padToWidth(`${prompt}${content}`, paintWidth));
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -108,6 +110,12 @@ function inputPaintWidth(width) {
|
|
|
108
110
|
return safeWidth > 1 ? safeWidth - 1 : safeWidth;
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
function maxInputContentWidth(width) {
|
|
114
|
+
const paintWidth = inputPaintWidth(width);
|
|
115
|
+
const promptWidth = visibleWidth(stripAnsi(INPUT_PROMPT));
|
|
116
|
+
return Math.max(0, paintWidth - promptWidth - 2);
|
|
117
|
+
}
|
|
118
|
+
|
|
111
119
|
function applyInputBackground(line) {
|
|
112
120
|
return `${INPUT_BG}${String(line).replaceAll(R, `${R}${INPUT_BG}`)}${R}`;
|
|
113
121
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveAttachmentTokens, uniqueAttachmentToken, withLeadingSpace } from "../input/attachment-tokens.mjs";
|
|
2
2
|
|
|
3
|
-
export function createTuiInputController({ editor, requestRender, historyStore = null }) {
|
|
3
|
+
export function createTuiInputController({ editor, requestRender, historyStore = null, onSubmit = null }) {
|
|
4
4
|
let onSubmitResolve = null;
|
|
5
5
|
const attachmentTokens = new Map();
|
|
6
6
|
|
|
@@ -17,6 +17,7 @@ export function createTuiInputController({ editor, requestRender, historyStore =
|
|
|
17
17
|
}
|
|
18
18
|
clearSubmitState();
|
|
19
19
|
attachmentTokens.clear();
|
|
20
|
+
onSubmit?.();
|
|
20
21
|
resolve(resolvedText);
|
|
21
22
|
};
|
|
22
23
|
});
|
package/src/cli/ui.mjs
CHANGED
|
@@ -20,7 +20,7 @@ import { createMouseSelectionController } from "./tui/input/mouse-selection-cont
|
|
|
20
20
|
import { ScreenSelection } from "./tui/selection-screen.mjs";
|
|
21
21
|
import { writeEditDiff } from "./tui/tui-diff-rendering.mjs";
|
|
22
22
|
import { createTuiInputController } from "./tui/tui-input-controller.mjs";
|
|
23
|
-
import {
|
|
23
|
+
import { writeRecall } from "./tui/recall-rendering.mjs";
|
|
24
24
|
import { writeToolEnd, writeToolStart } from "./tui/tool-rendering.mjs";
|
|
25
25
|
import { EDITOR_THEME, brightBlack } from "./tui/ui-theme.mjs";
|
|
26
26
|
import { createRenderScheduler } from "./tui/render/render-scheduler.mjs";
|
|
@@ -104,12 +104,6 @@ export function createTuiUI({
|
|
|
104
104
|
if (copyKeyResult) return copyKeyResult;
|
|
105
105
|
const dispatched = keybindingDispatcher.dispatch(data);
|
|
106
106
|
if (dispatched) return dispatched;
|
|
107
|
-
// When output is scrolled up, the next render has fewer lines.
|
|
108
|
-
// On new input, reset scroll to tail so the editor stays at bottom.
|
|
109
|
-
if (output.scrollOffset > 0) {
|
|
110
|
-
output.resetScroll();
|
|
111
|
-
requestRender();
|
|
112
|
-
}
|
|
113
107
|
if (shellDrawer.isInputActive()) {
|
|
114
108
|
shellDrawer.sendInput(data);
|
|
115
109
|
requestRender();
|
|
@@ -150,7 +144,12 @@ export function createTuiUI({
|
|
|
150
144
|
retryStatus.end({ success, attempt, finalError });
|
|
151
145
|
}
|
|
152
146
|
|
|
153
|
-
const
|
|
147
|
+
const resetOutputScrollOnSubmit = () => {
|
|
148
|
+
if (output.scrollOffset <= 0) return;
|
|
149
|
+
output.resetScroll();
|
|
150
|
+
requestRender();
|
|
151
|
+
};
|
|
152
|
+
const inputController = createTuiInputController({ editor, requestRender, historyStore, onSubmit: resetOutputScrollOnSubmit });
|
|
154
153
|
|
|
155
154
|
return {
|
|
156
155
|
readline: (_prompt) => {
|
|
@@ -205,8 +204,8 @@ export function createTuiUI({
|
|
|
205
204
|
status: (text) => {
|
|
206
205
|
ensureStarted(); flushStreamDeltas(); retryStatus.stop(); spinnerStatus.stop(); output.setOverlayStatus([brightBlack(`● ${text}`)]); requestRender();
|
|
207
206
|
},
|
|
208
|
-
|
|
209
|
-
ensureStarted(); flushStreamDeltas(); retryStatus.stop(); spinnerStatus.stop(); output.ensureNewline();
|
|
207
|
+
recall: ({ hints }) => {
|
|
208
|
+
ensureStarted(); flushStreamDeltas(); retryStatus.stop(); spinnerStatus.stop(); output.ensureNewline(); writeRecall({ output, hints }); requestRender();
|
|
210
209
|
},
|
|
211
210
|
|
|
212
211
|
clearOutput: () => {
|
|
@@ -5,22 +5,31 @@ 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
|
-
- Surface assumptions and ambiguity before acting. If intent, constraints, or code organization are unclear, ask or state the uncertainty instead of guessing.
|
|
9
8
|
- 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.
|
|
10
9
|
- For multi-step work, checkpoint after meaningful milestones: what changed, what was verified, and what remains.
|
|
11
|
-
- 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.
|
|
12
10
|
- Don't narrate hidden reasoning. State decisions, results, and relevant next steps.
|
|
13
11
|
- End with a brief summary of what you did during the task, including what changed, verification status, and what's next if anything; keep it concise, but don't omit the execution overview.
|
|
14
12
|
- Report outcomes truthfully. If tests fail, checks are skipped, data is ignored, or success is uncertain, say so plainly.
|
|
15
13
|
</communication_contract>
|
|
16
14
|
|
|
15
|
+
<discussion_contract>
|
|
16
|
+
- For design, brainstorm, mechanism, planning, or ambiguous requests, act as a thinking partner before acting as an implementer.
|
|
17
|
+
- First classify whether the user needs clarification, option exploration, scope splitting, or implementation; do not treat every unclear request as a coding task.
|
|
18
|
+
- 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
|
+
- Surface assumptions and ambiguity before acting. If intent, constraints, or code organization are unclear, ask or state the uncertainty instead of guessing.
|
|
20
|
+
- Challenge weak, over-engineered, or mis-scoped proposals directly and offer 1-2 concrete alternatives.
|
|
21
|
+
- Ask one focused question at a time; when useful, provide 2-4 distinct options rather than open-ended questionnaires.
|
|
22
|
+
- 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
|
+
- Do not force discussion when the request is already clear; summarize the decision and move toward the appropriate next step.
|
|
24
|
+
</discussion_contract>
|
|
25
|
+
|
|
17
26
|
<operating_contract>
|
|
18
27
|
- Default to doing the requested work in the repository, not giving abstract advice.
|
|
19
28
|
- Define the success condition for non-trivial tasks, then iterate until it is actually met or a blocker is clear.
|
|
20
29
|
- Build context from current project facts before editing. Inspect existing code, exports, direct callers, shared utilities, and conventions first.
|
|
21
30
|
- Tool call history may be compacted when context is rebuilt. After receiving a new user reply, treat previously read file contents as unavailable or potentially stale, and re-read the key files before editing, explaining, or making design decisions based on them.
|
|
22
|
-
- Keep the
|
|
23
|
-
- Prefer the
|
|
31
|
+
- Keep the requested outcome scoped. Do not expand product behavior, refactors, files, or docs beyond the task, but allow structural changes when they are needed to keep responsibility boundaries correct.
|
|
32
|
+
- Prefer the clearest correct solution. Small duplication is acceptable when it avoids premature abstraction, but do not use local simplicity as an excuse to add scattered conditionals or bypass the proper abstraction boundary.
|
|
24
33
|
- When existing patterns conflict, do not blend them. Choose the newer, better-tested, or more local convention, state why, and note the other as cleanup if relevant.
|
|
25
34
|
- Follow repository conventions even when another style seems preferable. Raise harmful conventions explicitly; don't silently introduce a second pattern.
|
|
26
35
|
- Use model judgment only where judgment is needed, such as classification, drafting, summarization, or extracting from unstructured text. Deterministic routing, retry, status-code handling, and data transforms belong in code.
|
|
@@ -29,6 +38,26 @@ The user primarily asks for software engineering work: fixing bugs, adding behav
|
|
|
29
38
|
- Default to add one short comment when the WHY is helpful.
|
|
30
39
|
</operating_contract>
|
|
31
40
|
|
|
41
|
+
<implementation_principles>
|
|
42
|
+
- Prefer minimal coherent changes, not local minimal patches. The goal is correct responsibility boundaries, self-consistent behavior, and contained future complexity, not the fewest edited lines.
|
|
43
|
+
- Before adding a branch for a new scenario, identify the variation dimension: external input shape, provider/model/tool behavior, business rule, platform boundary, or a new responsibility in the main flow.
|
|
44
|
+
- Keep the main flow as stable orchestration. It should express steps and data movement, not accumulate provider, platform, mode, or format details.
|
|
45
|
+
- Put compatibility logic only at real boundaries such as external APIs, historical data, platform differences, and user input. Do not use compatibility branches to hide missing internal abstractions.
|
|
46
|
+
- When a condition represents a growing variation dimension, move it to the proper boundary with a registry, strategy, adapter, configuration mapping, or focused module instead of adding another inline if.
|
|
47
|
+
- Do not keep parallel internal paths for half-compatible behavior. If the old path is no longer the right model, migrate to one unified model unless an external compatibility window truly requires both.
|
|
48
|
+
- Implementation priority is: correct responsibility boundary > self-consistent system behavior > simple main flow > fewer local code changes.
|
|
49
|
+
</implementation_principles>
|
|
50
|
+
|
|
51
|
+
<coherence_contract>
|
|
52
|
+
- After non-trivial changes, perform a post-change coherence check before finalizing or committing.
|
|
53
|
+
- Re-state what the system is now, not only what changed locally.
|
|
54
|
+
- Check whether responsibility boundaries became clearer or more confused; fix or surface boundary drift.
|
|
55
|
+
- Look for duplicated rules, conflicting instructions, hidden priority changes, and parallel paths that now represent the same responsibility.
|
|
56
|
+
- Notice module, prompt, or flow expansion; if one area starts absorbing too many responsibilities, either refactor within scope or call it out as follow-up.
|
|
57
|
+
- Do not treat tests as a substitute for coherence. Tests verify behavior; coherence checks verify the system model.
|
|
58
|
+
- In the final summary for architecture, prompt, context, memory, tool, or provider changes, briefly report the coherence result.
|
|
59
|
+
</coherence_contract>
|
|
60
|
+
|
|
32
61
|
<safety_contract>
|
|
33
62
|
- Local, reversible actions such as reading files, editing files, and running tests are normally okay.
|
|
34
63
|
- Confirm before actions that are hard to reverse, destructive, outward-facing, or affect shared state: deleting user work, force operations, dependency downgrades, CI/CD changes, pushing code, creating PRs/issues, sending messages, or publishing content to external services.
|
|
@@ -62,13 +91,14 @@ The user primarily asks for software engineering work: fixing bugs, adding behav
|
|
|
62
91
|
</git_contract>
|
|
63
92
|
|
|
64
93
|
<memory_system>
|
|
65
|
-
- [
|
|
66
|
-
-
|
|
94
|
+
- [recall source="..."] blocks in recent_chat are lightweight recall hints matched from prior thinking output. Treat them as possibly relevant pointers, not as complete facts.
|
|
95
|
+
- 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
|
+
- 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.
|
|
67
97
|
- Use memory_search(query) for full-text search across all memories.
|
|
68
98
|
- To edit an existing memory, use memory_open(id) to get its path, then edit_file with mode="patch" for targeted edits.
|
|
69
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'.
|
|
70
100
|
- 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.
|
|
71
101
|
- 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.
|
|
72
|
-
- Unlike
|
|
102
|
+
- 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.
|
|
73
103
|
- If execution takes a meaningful detour, create or update a memory after the task. A detour means the initial plan or assumption failed, multiple approaches were tried, and the final successful path contains reusable project knowledge. Record the failed assumption, what was tried, and the successful approach. Prefer updating an existing related memory over creating a new one.
|
|
74
104
|
</memory_system>
|
|
@@ -2,7 +2,7 @@ import { expandTags, normalizeText } from "./markdown-format.mjs";
|
|
|
2
2
|
|
|
3
3
|
export function formatRecallHints(source, hints = []) {
|
|
4
4
|
if (!hints.length) return "";
|
|
5
|
-
const lines = [`[
|
|
5
|
+
const lines = [`[recall source="${source}"]`];
|
|
6
6
|
for (const hint of hints) {
|
|
7
7
|
lines.push(`- ${hint.id} | ${hint.name} | ${hint.description}`);
|
|
8
8
|
}
|
|
@@ -59,7 +59,7 @@ export class MarkdownMemoryStore {
|
|
|
59
59
|
continue;
|
|
60
60
|
}
|
|
61
61
|
if (!parsed.frontmatter.description) {
|
|
62
|
-
diagnostics.push({ type: "warning", path, message: "Memory file is missing description; excluded from
|
|
62
|
+
diagnostics.push({ type: "warning", path, message: "Memory file is missing description; excluded from passive recall" });
|
|
63
63
|
}
|
|
64
64
|
const tags = normalizeTags(parsed.frontmatter.tags ?? []);
|
|
65
65
|
const entry = {
|
|
@@ -37,9 +37,9 @@ export function createMarkdownMemoryTools(store, { remoteSources = [] } = {}) {
|
|
|
37
37
|
defineTool({
|
|
38
38
|
name: "memory_open",
|
|
39
39
|
label: "Memory Open",
|
|
40
|
-
description: "Open a Markdown memory by id or by path. Use this after
|
|
40
|
+
description: "Open a Markdown memory by id or by path. Use this after recall hint or memory_search when you need more context. Local memories may be edited with edit_file; remote memories are read-only.",
|
|
41
41
|
parameters: Type.Object({
|
|
42
|
-
id: Type.Optional(Type.String({ description: "Local memory id from
|
|
42
|
+
id: Type.Optional(Type.String({ description: "Local memory id from recall hint, e.g. mem_..." })),
|
|
43
43
|
source: Type.Optional(Type.String({ description: "Memory source: omitted/local or a remote memory name" })),
|
|
44
44
|
path: Type.Optional(Type.String({ description: "Path returned by memory_search" })),
|
|
45
45
|
line: Type.Optional(Type.Number({ description: "Open around this 1-based line number" })),
|
|
@@ -70,13 +70,13 @@ 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
|
|
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.",
|
|
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." })),
|
|
77
|
-
description: Type.Optional(Type.String({ description: "Short natural-language summary shown in
|
|
77
|
+
description: Type.Optional(Type.String({ description: "Short natural-language summary shown in recall hint. Required when creating." })),
|
|
78
78
|
body: Type.Optional(Type.String({ description: "Markdown memory body. Required when creating." })),
|
|
79
|
-
tags: Type.Optional(Type.Array(Type.String(), { description: "Tags used for
|
|
79
|
+
tags: Type.Optional(Type.Array(Type.String(), { description: "Tags used for recall hint. Required and non-empty when creating; replaces tags when updating. Prefer stable retrieval keys: project name, technology, feature/domain, user/person, and decision topic. Use lowercase kebab-case when possible. Examples: ['march-cli', 'tooling', 'permissions'], ['memory', 'sqlite-index']." })),
|
|
80
80
|
}),
|
|
81
81
|
execute: async (_toolCallId, params) => {
|
|
82
82
|
try {
|