mixdog 0.9.45 → 0.9.47
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 +5 -2
- package/scripts/agent-dispatch-abort-compose-test.mjs +31 -0
- package/scripts/agent-model-liveness-test.mjs +618 -0
- package/scripts/agent-trace-io-test.mjs +68 -4
- package/scripts/bench-run.mjs +30 -3
- package/scripts/compact-pressure-test.mjs +187 -4
- package/scripts/compact-prior-context-flatten-test.mjs +252 -0
- package/scripts/compact-trigger-migration-smoke.mjs +8 -6
- package/scripts/compacted-placeholder-scrub-test.mjs +20 -34
- package/scripts/context-mcp-metering-test.mjs +75 -0
- package/scripts/explore-prompt-policy-test.mjs +15 -16
- package/scripts/explore-timeout-cancel-test.mjs +345 -0
- package/scripts/headless-pristine-execution-test.mjs +614 -0
- package/scripts/internal-comms-smoke.mjs +15 -6
- package/scripts/live-worker-smoke.mjs +1 -1
- package/scripts/memory-core-input-test.mjs +137 -0
- package/scripts/memory-rule-contract-test.mjs +5 -5
- package/scripts/openai-oauth-refresh-race-test.mjs +120 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +26 -0
- package/scripts/parent-abort-link-test.mjs +22 -0
- package/scripts/provider-toolcall-test.mjs +22 -0
- package/scripts/reactive-compact-persist-smoke.mjs +8 -4
- package/scripts/session-bench.mjs +3 -70
- package/scripts/task-bench.mjs +0 -2
- package/scripts/terminal-bench-isolation-guards-test.mjs +102 -0
- package/scripts/tool-smoke.mjs +21 -21
- package/scripts/tool-tui-presentation-test.mjs +68 -0
- package/scripts/tui-transcript-jitter-harness-entry.jsx +91 -10
- package/src/app.mjs +28 -103
- package/src/cli.mjs +17 -13
- package/src/defaults/cycle3-review-prompt.md +10 -4
- package/src/defaults/memory-promote-prompt.md +4 -3
- package/src/headless-command.mjs +139 -0
- package/src/headless-role.mjs +121 -10
- package/src/help.mjs +4 -1
- package/src/rules/agent/00-common.md +3 -3
- package/src/rules/agent/00-core.md +8 -9
- package/src/rules/agent/20-skip-protocol.md +2 -3
- package/src/rules/agent/30-explorer.md +50 -56
- package/src/rules/agent/40-cycle1-agent.md +10 -12
- package/src/rules/agent/41-cycle2-agent.md +12 -9
- package/src/rules/agent/42-cycle3-agent.md +4 -6
- package/src/rules/lead/01-general.md +5 -6
- package/src/rules/lead/02-channels.md +1 -1
- package/src/rules/lead/lead-brief.md +14 -17
- package/src/rules/lead/lead-tool.md +3 -3
- package/src/rules/shared/01-tool.md +41 -43
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +46 -10
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +44 -31
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +18 -3
- package/src/runtime/agent/orchestrator/config.mjs +96 -30
- package/src/runtime/agent/orchestrator/context/collect.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +18 -5
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +123 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +148 -30
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +5 -7
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +62 -14
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +141 -19
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +85 -17
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -17
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +159 -20
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +83 -10
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +75 -7
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +4 -374
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +0 -75
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +0 -5
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +20 -3
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +32 -16
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +86 -15
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +30 -27
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +1 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +21 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -51
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +26 -0
- package/src/runtime/memory/index.mjs +0 -1
- package/src/runtime/memory/lib/core-memory-store.mjs +44 -24
- package/src/runtime/memory/lib/http-router.mjs +0 -193
- package/src/runtime/memory/lib/memory-action-handlers.mjs +37 -11
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +13 -5
- package/src/runtime/memory/lib/memory-cycle2.mjs +8 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +41 -6
- package/src/runtime/memory/lib/query-handlers.mjs +49 -6
- package/src/runtime/memory/lib/recall-format.mjs +21 -6
- package/src/runtime/memory/tool-defs.mjs +5 -6
- package/src/runtime/shared/config.mjs +11 -34
- package/src/runtime/shared/pristine-execution-contract.json +75 -0
- package/src/runtime/shared/pristine-execution.mjs +356 -0
- package/src/runtime/shared/provider-api-key.mjs +43 -0
- package/src/runtime/shared/provider-auth-binding.mjs +21 -0
- package/src/session-runtime/context-status.mjs +61 -13
- package/src/session-runtime/mcp-glue.mjs +29 -2
- package/src/session-runtime/plugin-mcp.mjs +7 -0
- package/src/session-runtime/resource-api.mjs +38 -5
- package/src/session-runtime/runtime-core.mjs +5 -1
- package/src/session-runtime/session-turn-api.mjs +14 -2
- package/src/session-runtime/settings-api.mjs +5 -0
- package/src/session-runtime/tool-catalog.mjs +13 -2
- package/src/session-runtime/tool-defs.mjs +1 -3
- package/src/standalone/agent-task-status.mjs +50 -11
- package/src/standalone/agent-tool/tool-def.mjs +1 -1
- package/src/standalone/explore-tool.mjs +257 -49
- package/src/standalone/seeds.mjs +1 -0
- package/src/tui/App.jsx +23 -10
- package/src/tui/app/use-transcript-scroll.mjs +4 -3
- package/src/tui/app/use-transcript-window.mjs +12 -21
- package/src/tui/components/ContextPanel.jsx +19 -25
- package/src/tui/dist/index.mjs +77 -65
- package/src/tui/engine/agent-envelope.mjs +16 -5
- package/src/tui/engine/labels.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +21 -51
- package/src/workflows/solo/WORKFLOW.md +12 -17
- package/src/workflows/solo-review/WORKFLOW.md +0 -47
package/scripts/tool-smoke.mjs
CHANGED
|
@@ -739,7 +739,10 @@ const compactedGuardCases = [
|
|
|
739
739
|
];
|
|
740
740
|
for (const [label, patch] of compactedGuardCases) {
|
|
741
741
|
const out = await executePatchTool('apply_patch', { base_path: root, dry_run: true, fuzzy: false, patch }, root);
|
|
742
|
-
if (!/^Error[\s:[]/.test(String(out))
|
|
742
|
+
if (!/^Error[\s:[]/.test(String(out))
|
|
743
|
+
|| !/compacted-history placeholder/i.test(String(out))
|
|
744
|
+
|| !/re-read the current target file contents now/i.test(String(out))
|
|
745
|
+
|| !/fresh full patch/i.test(String(out))) {
|
|
743
746
|
throw new Error(`apply_patch must reject compacted placeholder (${label}):\n${out}`);
|
|
744
747
|
}
|
|
745
748
|
}
|
|
@@ -1366,7 +1369,7 @@ setInternalToolsProvider({
|
|
|
1366
1369
|
if (!/Read-only retrieval role/i.test(visible) || /# environment/i.test(visible) || /git operations deferred to Lead/i.test(visible)) {
|
|
1367
1370
|
throw new Error(`explorer hidden retrieval context should stay slim: ${visible.slice(0, 1200)}`);
|
|
1368
1371
|
}
|
|
1369
|
-
if (!/# Role: explorer/i.test(systemVisible) || /# Role: explorer/i.test(userReminderVisible) || !/
|
|
1372
|
+
if (!/# Role: explorer/i.test(systemVisible) || /# Role: explorer/i.test(userReminderVisible) || !/only WHERE/i.test(systemVisible)) {
|
|
1370
1373
|
throw new Error(`explorer role md must ride BP2 system, not BP3 user reminder: system=${systemVisible.slice(0, 600)} user=${userReminderVisible.slice(0, 600)}`);
|
|
1371
1374
|
}
|
|
1372
1375
|
// System layers (BP1 tool policy + BP2 role md) are shared/frozen and sized
|
|
@@ -1728,10 +1731,10 @@ for (const requiredGrammarLine of [
|
|
|
1728
1731
|
}
|
|
1729
1732
|
const readPathSchema = BUILTIN_TOOLS.find((tool) => tool.name === 'read')?.inputSchema?.properties?.path || {};
|
|
1730
1733
|
const readPathDescription = readPathSchema.description || '';
|
|
1731
|
-
if (
|
|
1734
|
+
if (!/\{path,offset,limit\}\[\]/i.test(readPathDescription) || !/real arrays/i.test(readPathDescription)) {
|
|
1732
1735
|
throw new Error('read schema must keep directory-vs-file guidance');
|
|
1733
1736
|
}
|
|
1734
|
-
if (!/Not for
|
|
1737
|
+
if (!/Not for director/i.test((BUILTIN_TOOLS.find((tool) => tool.name === 'read')?.description) || '')) {
|
|
1735
1738
|
throw new Error('read description must keep directory-vs-file guidance');
|
|
1736
1739
|
}
|
|
1737
1740
|
const readTool = BUILTIN_TOOLS.find((tool) => tool.name === 'read');
|
|
@@ -1742,13 +1745,13 @@ const readArrayItemAnyOf = readArraySchema?.items?.anyOf || [];
|
|
|
1742
1745
|
if (!readArrayItemAnyOf.some((entry) => entry?.type === 'object' && entry?.properties?.offset && entry?.properties?.limit)) {
|
|
1743
1746
|
throw new Error('read schema must expose array-of-region objects for batched spans');
|
|
1744
1747
|
}
|
|
1745
|
-
if (/line\+context/i.test(readDescription) || !/
|
|
1748
|
+
if (/line\+context/i.test(readDescription) || !/Read file contents/i.test(readDescription) || !/\{path,offset,limit\}\[\]/i.test(readDescription)) {
|
|
1746
1749
|
throw new Error('read description must expose offset/limit as the single window form');
|
|
1747
1750
|
}
|
|
1748
1751
|
if (readProps.line || readProps.context) {
|
|
1749
1752
|
throw new Error('read schema must not expose legacy line/context window fields');
|
|
1750
1753
|
}
|
|
1751
|
-
if (readProps.offset?.minimum !== 0 || !/
|
|
1754
|
+
if (readProps.offset?.minimum !== 0 || !/Lines to skip/i.test(readProps.offset?.description || '') || !/Max lines/i.test(readProps.limit?.description || '')) {
|
|
1752
1755
|
throw new Error('read offset schema must describe Mixdog paging cursor semantics');
|
|
1753
1756
|
}
|
|
1754
1757
|
if (/line\/context/i.test(JSON.stringify(readTool?.inputSchema || {}))) {
|
|
@@ -1839,21 +1842,18 @@ if (codeGraphSymbolSearchErr) {
|
|
|
1839
1842
|
// symbol/definition/caller lookups AWAY from repeated grep (the grep_retry +
|
|
1840
1843
|
// find_symbol_noscope anti-patterns). It is allowed to be verbose enough to
|
|
1841
1844
|
// enumerate modes, but must not drift into web-search territory.
|
|
1842
|
-
if (!/code structure
|
|
1845
|
+
if (!/Repo code structure/i.test(codeGraphDescription) || !/find_symbol\/symbol_search\/search\/references\/callers\/callees/i.test(codeGraphDescription)) {
|
|
1843
1846
|
throw new Error('code_graph description must stay structure-oriented and name its symbol modes');
|
|
1844
1847
|
}
|
|
1845
|
-
if (!/files\[\]
|
|
1848
|
+
if (!/take files\[\]/i.test(codeGraphDescription) || !/Batch targets per mode/i.test(codeGraphDescription)) {
|
|
1846
1849
|
throw new Error('code_graph description must route unknown file paths through locators first');
|
|
1847
1850
|
}
|
|
1848
|
-
if (!/
|
|
1849
|
-
throw new Error('code_graph description must mark itself repo-local (not web search)');
|
|
1850
|
-
}
|
|
1851
|
-
if (!/repo-local|not web/i.test(codeGraphProps.mode?.description || '') || !/Verified source file/i.test(codeGraphProps.files?.description || '')) {
|
|
1851
|
+
if (!/files\[\]/i.test(codeGraphProps.mode?.description || '') || !/Source file path/i.test(codeGraphProps.files?.description || '')) {
|
|
1852
1852
|
throw new Error('code_graph schema must keep compact, repo-local field descriptions');
|
|
1853
1853
|
}
|
|
1854
1854
|
const recallTool = MEMORY_TOOL_DEFS.find((tool) => tool.name === 'recall');
|
|
1855
1855
|
const recallProps = recallTool?.inputSchema?.properties || {};
|
|
1856
|
-
if (!/
|
|
1856
|
+
if (!/prior-work context/i.test(recallTool?.description || '') || !recallProps.id?.anyOf || !/Do not invent ids/i.test(recallProps.id?.description || '')) {
|
|
1857
1857
|
throw new Error('recall schema must preserve scoped prior-context guidance and id lookup shape');
|
|
1858
1858
|
}
|
|
1859
1859
|
if (!/array for independent fan-out/i.test(recallProps.query?.description || '') || !/Project pool selector/i.test(recallProps.projectScope?.description || '')) {
|
|
@@ -2155,13 +2155,13 @@ const grepPathDescription = grepTool?.inputSchema?.properties?.path?.description
|
|
|
2155
2155
|
const grepGlobDescription = grepTool?.inputSchema?.properties?.glob?.description || '';
|
|
2156
2156
|
const grepOutputModeDescription = grepTool?.inputSchema?.properties?.output_mode?.description || '';
|
|
2157
2157
|
const grepHeadLimitDescription = grepTool?.inputSchema?.properties?.head_limit?.description || '';
|
|
2158
|
-
if (!/
|
|
2158
|
+
if (!/pattern\[\] batches variants/i.test(grepPatternDescription) || !/File\/dir scope/i.test(grepPathDescription)) {
|
|
2159
2159
|
throw new Error('grep schema must keep compact pattern/path guidance');
|
|
2160
2160
|
}
|
|
2161
|
-
if (!/
|
|
2162
|
-
throw new Error('grep description must
|
|
2161
|
+
if (!/content search/i.test(grepTool?.description || '')) {
|
|
2162
|
+
throw new Error('grep description must state its content-search contract');
|
|
2163
2163
|
}
|
|
2164
|
-
if (!/Glob filter/i.test(grepGlobDescription)
|
|
2164
|
+
if (!/Glob filter/i.test(grepGlobDescription)) {
|
|
2165
2165
|
throw new Error('grep glob schema must describe scope narrowing');
|
|
2166
2166
|
}
|
|
2167
2167
|
if (!/files_with_matches\/count/i.test(grepOutputModeDescription) || !/content_with_context/i.test(grepOutputModeDescription)) {
|
|
@@ -2176,16 +2176,16 @@ if (grepTool?.inputSchema?.properties?.type) {
|
|
|
2176
2176
|
const globTool = BUILTIN_TOOLS.find((tool) => tool.name === 'glob');
|
|
2177
2177
|
const findTool = BUILTIN_TOOLS.find((tool) => tool.name === 'find');
|
|
2178
2178
|
const listTool = BUILTIN_TOOLS.find((tool) => tool.name === 'list');
|
|
2179
|
-
if (!/exact glob
|
|
2179
|
+
if (!/exact glob patterns/i.test(globTool?.description || '')) {
|
|
2180
2180
|
throw new Error('glob description must route exact-pattern unknown paths before read/grep/list');
|
|
2181
2181
|
}
|
|
2182
|
-
if (!/
|
|
2182
|
+
if (!/unknown partial paths\/names/i.test(findTool?.description || '') || !/verifies paths for grep\/glob/i.test(findTool?.description || '')) {
|
|
2183
2183
|
throw new Error('find description must advertise unverified path/name lookup and verified outputs');
|
|
2184
2184
|
}
|
|
2185
|
-
if (!/List
|
|
2185
|
+
if (!/List directory entries/i.test(listTool?.description || '') || !/path\[\]/i.test(listTool?.inputSchema?.properties?.path?.description || '')) {
|
|
2186
2186
|
throw new Error('list description must require verified directories and locator-first unknown dirs');
|
|
2187
2187
|
}
|
|
2188
|
-
if (!/
|
|
2188
|
+
if (!/symbol modes use symbols\[\]/i.test(codeGraphProps.mode?.description || '') || !/symbols \(file outline\) use files\[\]/i.test(codeGraphProps.mode?.description || '') || !/one symbols\[\] call/i.test(codeGraphProps.symbols?.description || '')) {
|
|
2189
2189
|
throw new Error('code_graph schema fields must stay compact and repo-local');
|
|
2190
2190
|
}
|
|
2191
2191
|
|
|
@@ -20,6 +20,11 @@ import {
|
|
|
20
20
|
appendAgentResponseTail,
|
|
21
21
|
formatAgentResponseRaw,
|
|
22
22
|
} from '../src/tui/engine/agent-response-tail.mjs';
|
|
23
|
+
import {
|
|
24
|
+
agentArgsWithResultMetadata,
|
|
25
|
+
parseAgentJob,
|
|
26
|
+
} from '../src/tui/engine/agent-envelope.mjs';
|
|
27
|
+
import { agentActionTitle } from '../src/tui/components/tool-execution/surface-detail.mjs';
|
|
23
28
|
|
|
24
29
|
test('tool action copy keeps Add/Delete patch verbs and human read offsets', () => {
|
|
25
30
|
assert.equal(
|
|
@@ -42,6 +47,69 @@ test('Agent aggregation is batch-scoped and keeps category action wording', () =
|
|
|
42
47
|
);
|
|
43
48
|
});
|
|
44
49
|
|
|
50
|
+
test('agent spawn cards retain resolved model metadata from split result envelopes', () => {
|
|
51
|
+
const parsed = parseAgentJob([
|
|
52
|
+
'agent task: task_worker_1',
|
|
53
|
+
'status: running',
|
|
54
|
+
'type: spawn',
|
|
55
|
+
'target: worker sess_worker_1',
|
|
56
|
+
'agent: worker',
|
|
57
|
+
'provider: openai-oauth',
|
|
58
|
+
'model: gpt-5.6-terra',
|
|
59
|
+
'preset: agent-worker',
|
|
60
|
+
'effort: high',
|
|
61
|
+
].join('\n'));
|
|
62
|
+
const args = agentArgsWithResultMetadata({ type: 'spawn', agent: 'worker' }, parsed);
|
|
63
|
+
|
|
64
|
+
assert.equal(args.tag, 'worker');
|
|
65
|
+
assert.equal(args.provider, 'openai-oauth');
|
|
66
|
+
assert.equal(args.model, 'gpt-5.6-terra');
|
|
67
|
+
assert.equal(args.effort, 'high');
|
|
68
|
+
assert.equal(agentActionTitle(args), 'Spawn Worker (GPT-5.6-Terra, worker)');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('agent model enrichment preserves explicit args and combined envelopes', () => {
|
|
72
|
+
const combined = parseAgentJob([
|
|
73
|
+
'agent task: task_worker_2',
|
|
74
|
+
'type: spawn',
|
|
75
|
+
'model: openai-oauth/gpt-5.6-terra',
|
|
76
|
+
].join('\n'));
|
|
77
|
+
assert.equal(combined.provider, 'openai-oauth');
|
|
78
|
+
assert.equal(combined.model, 'gpt-5.6-terra');
|
|
79
|
+
|
|
80
|
+
const split = parseAgentJob([
|
|
81
|
+
'agent task: task_worker_3',
|
|
82
|
+
'provider: openai-oauth',
|
|
83
|
+
'model: gpt-5.6-terra',
|
|
84
|
+
].join('\n'));
|
|
85
|
+
const explicit = agentArgsWithResultMetadata({
|
|
86
|
+
provider: 'anthropic',
|
|
87
|
+
model: 'claude-opus-4-6',
|
|
88
|
+
}, split);
|
|
89
|
+
assert.equal(explicit.provider, 'anthropic');
|
|
90
|
+
assert.equal(explicit.model, 'claude-opus-4-6');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('non-agent task diagnostics do not enrich model args', () => {
|
|
94
|
+
const shellDiagnostics = parseAgentJob([
|
|
95
|
+
'task_id: shell_task_1',
|
|
96
|
+
'provider: openai-oauth',
|
|
97
|
+
'model: gpt-5.6-terra',
|
|
98
|
+
].join('\n'));
|
|
99
|
+
const args = agentArgsWithResultMetadata({ type: 'shell' }, shellDiagnostics);
|
|
100
|
+
|
|
101
|
+
assert.equal(args.provider, undefined);
|
|
102
|
+
assert.equal(args.model, undefined);
|
|
103
|
+
|
|
104
|
+
const shellCombinedDiagnostics = parseAgentJob([
|
|
105
|
+
'task_id: shell_task_2',
|
|
106
|
+
'model: openai-oauth/gpt-5.6-terra',
|
|
107
|
+
].join('\n'));
|
|
108
|
+
const combinedArgs = agentArgsWithResultMetadata({ type: 'shell' }, shellCombinedDiagnostics);
|
|
109
|
+
assert.equal(combinedArgs.provider, undefined);
|
|
110
|
+
assert.equal(combinedArgs.model, undefined);
|
|
111
|
+
});
|
|
112
|
+
|
|
45
113
|
test('terminal result statuses stay separate from tool-call failure accounting', () => {
|
|
46
114
|
assert.equal(normalizeToolTerminalStatus('denied'), 'denied');
|
|
47
115
|
assert.equal(toolResultTerminalStatus('[status: cancelled]\n'), 'cancelled');
|
|
@@ -44,23 +44,32 @@ let commit = 0;
|
|
|
44
44
|
const identity = (value) => value;
|
|
45
45
|
const noop = () => {};
|
|
46
46
|
|
|
47
|
-
function Harness({
|
|
48
|
-
|
|
47
|
+
function Harness({
|
|
48
|
+
text,
|
|
49
|
+
step,
|
|
50
|
+
initialScroll = INITIAL_SCROLL,
|
|
51
|
+
streamId = STREAM_ID,
|
|
52
|
+
releasedSelection = null,
|
|
53
|
+
onPaint = noop,
|
|
54
|
+
onFrame = noop,
|
|
55
|
+
recordFrame = true,
|
|
56
|
+
}) {
|
|
57
|
+
const [scrollOffset, setScrollOffset] = React.useState(initialScroll);
|
|
49
58
|
const [measuredRowsVersion, setMeasuredRowsVersion] = React.useState(0);
|
|
50
59
|
const transcriptAnchorRef = React.useRef(null);
|
|
51
60
|
const transcriptAnchorDirtyRef = React.useRef(true);
|
|
52
|
-
const scrollTargetRef = React.useRef(
|
|
53
|
-
const scrollPositionRef = React.useRef(
|
|
61
|
+
const scrollTargetRef = React.useRef(initialScroll);
|
|
62
|
+
const scrollPositionRef = React.useRef(initialScroll);
|
|
54
63
|
const maxScrollRowsRef = React.useRef(0);
|
|
55
64
|
const transcriptGeomRef = React.useRef({});
|
|
56
65
|
const followingRef = React.useRef(false);
|
|
57
|
-
const dragRef = React.useRef({ active: false, rect:
|
|
66
|
+
const dragRef = React.useRef({ active: false, rect: releasedSelection });
|
|
58
67
|
const transcriptViewportRef = React.useRef({ top: 0 });
|
|
59
68
|
const selectionLayoutRef = React.useRef(null);
|
|
60
69
|
const contentRef = React.useRef(null);
|
|
61
70
|
const tailRef = React.useRef(null);
|
|
62
71
|
const streamingTail = React.useMemo(() => ({
|
|
63
|
-
id:
|
|
72
|
+
id: streamId,
|
|
64
73
|
kind: 'assistant',
|
|
65
74
|
text,
|
|
66
75
|
streaming: true,
|
|
@@ -100,7 +109,7 @@ function Harness({ text, step }) {
|
|
|
100
109
|
transcriptViewportRef,
|
|
101
110
|
selectionLayoutRef,
|
|
102
111
|
withSelectionClip: identity,
|
|
103
|
-
paintSelectionRect:
|
|
112
|
+
paintSelectionRect: onPaint,
|
|
104
113
|
stopSmoothScroll: noop,
|
|
105
114
|
measuredRowsVersion,
|
|
106
115
|
setMeasuredRowsVersion,
|
|
@@ -121,7 +130,7 @@ function Harness({ text, step }) {
|
|
|
121
130
|
const renderScrollOffset = transcriptWindow.effectiveScrollOffset;
|
|
122
131
|
const visibleTopIndexed = transcriptWindow.totalRows - renderScrollOffset - VIEW_ROWS;
|
|
123
132
|
const visibleTopPhysical = physicalRows - renderScrollOffset - VIEW_ROWS;
|
|
124
|
-
|
|
133
|
+
const frame = {
|
|
125
134
|
commit: ++commit,
|
|
126
135
|
step,
|
|
127
136
|
char: text.at(-1) === '\n' ? '\\n' : (text.at(-1) || ''),
|
|
@@ -145,7 +154,9 @@ function Harness({ text, step }) {
|
|
|
145
154
|
scrollTarget: scrollTargetRef.current,
|
|
146
155
|
following: followingRef.current,
|
|
147
156
|
anchor: transcriptAnchorRef.current?.id || '-',
|
|
148
|
-
}
|
|
157
|
+
};
|
|
158
|
+
if (recordFrame) frames.push(frame);
|
|
159
|
+
onFrame(frame);
|
|
149
160
|
}, [step, text, measuredRowsVersion, transcriptWindow.totalRows, transcriptWindow.effectiveScrollOffset,
|
|
150
161
|
transcriptAnchorRef, transcriptGeomRef]);
|
|
151
162
|
|
|
@@ -159,7 +170,7 @@ function Harness({ text, step }) {
|
|
|
159
170
|
marginBottom={-transcriptWindow.effectiveScrollOffset}
|
|
160
171
|
>
|
|
161
172
|
{renderedTranscriptItems.map((item, index, all) => {
|
|
162
|
-
const hookRef = item.id ===
|
|
173
|
+
const hookRef = item.id === streamId ? combinedTailRef : transcriptMeasureRef(item);
|
|
163
174
|
return (
|
|
164
175
|
<Box key={item.id} ref={hookRef} flexDirection="column" flexShrink={0}>
|
|
165
176
|
<Item
|
|
@@ -295,6 +306,76 @@ if (dips.length > 0) {
|
|
|
295
306
|
if (suppressValues.size !== 1 || !suppressValues.has(true)) {
|
|
296
307
|
throw new Error('repro unexpectedly toggled suppressMeasuredRowHeights');
|
|
297
308
|
}
|
|
309
|
+
|
|
310
|
+
const SELECTION_STREAM_ID = 'released-selection-tail';
|
|
311
|
+
const releasedSelection = { mode: 'linear', x1: 0, y1: 3, x2: 4, y2: 3 };
|
|
312
|
+
const selectionHarvests = [];
|
|
313
|
+
const selectionFrames = [];
|
|
314
|
+
streamingMeasuredRowsById.delete(SELECTION_STREAM_ID);
|
|
315
|
+
const selectionInstance = render(
|
|
316
|
+
<Harness
|
|
317
|
+
text="seed"
|
|
318
|
+
step={0}
|
|
319
|
+
initialScroll={1}
|
|
320
|
+
streamId={SELECTION_STREAM_ID}
|
|
321
|
+
releasedSelection={releasedSelection}
|
|
322
|
+
recordFrame={false}
|
|
323
|
+
onPaint={(rect, options) => {
|
|
324
|
+
if (rect && options?.rememberText === false) selectionHarvests.push({ y1: rect.y1, y2: rect.y2 });
|
|
325
|
+
}}
|
|
326
|
+
onFrame={(frame) => selectionFrames.push(frame)}
|
|
327
|
+
/>,
|
|
328
|
+
{ stdout: fakeTty(COLUMNS, VIEW_ROWS), stderr: fakeTty(COLUMNS, VIEW_ROWS), stdin: fakeTty(COLUMNS, VIEW_ROWS), interactive: true, patchConsole: false, exitOnCtrlC: false, maxFps: 1000 },
|
|
329
|
+
);
|
|
330
|
+
await settle(selectionInstance);
|
|
331
|
+
const harvestsBeforeGrowth = selectionHarvests.length;
|
|
332
|
+
const framesBeforeGrowth = selectionFrames.length;
|
|
333
|
+
selectionInstance.rerender(
|
|
334
|
+
<Harness
|
|
335
|
+
text={'seed\none\nsecond\nthird'}
|
|
336
|
+
step={1}
|
|
337
|
+
initialScroll={1}
|
|
338
|
+
streamId={SELECTION_STREAM_ID}
|
|
339
|
+
releasedSelection={releasedSelection}
|
|
340
|
+
recordFrame={false}
|
|
341
|
+
onPaint={(rect, options) => {
|
|
342
|
+
if (rect && options?.rememberText === false) selectionHarvests.push({ y1: rect.y1, y2: rect.y2 });
|
|
343
|
+
}}
|
|
344
|
+
onFrame={(frame) => selectionFrames.push(frame)}
|
|
345
|
+
/>,
|
|
346
|
+
);
|
|
347
|
+
await settle(selectionInstance);
|
|
348
|
+
selectionInstance.unmount();
|
|
349
|
+
await selectionInstance.waitUntilExit();
|
|
350
|
+
selectionInstance.cleanup();
|
|
351
|
+
const growthFrames = selectionFrames.slice(framesBeforeGrowth);
|
|
352
|
+
if (!growthFrames.some((frame) => frame.scrollTarget > 1)) {
|
|
353
|
+
throw new Error('released-selection repro did not apply anchored growth at offset 1');
|
|
354
|
+
}
|
|
355
|
+
const growthHarvests = selectionHarvests.slice(harvestsBeforeGrowth);
|
|
356
|
+
const initialRenderOffset = selectionFrames[framesBeforeGrowth - 1]?.renderScrollOffset ?? 0;
|
|
357
|
+
const maxGrowthRenderOffset = Math.max(
|
|
358
|
+
initialRenderOffset,
|
|
359
|
+
...growthFrames.map((frame) => frame.renderScrollOffset),
|
|
360
|
+
);
|
|
361
|
+
const maxExpectedSelectionY = releasedSelection.y1 + maxGrowthRenderOffset - initialRenderOffset;
|
|
362
|
+
const doubleCountedHarvest = growthHarvests.find(
|
|
363
|
+
(harvest) => harvest.y1 > maxExpectedSelectionY || harvest.y2 > maxExpectedSelectionY,
|
|
364
|
+
);
|
|
365
|
+
const finalHarvest = growthHarvests.at(-1);
|
|
366
|
+
if (
|
|
367
|
+
doubleCountedHarvest
|
|
368
|
+
|| !finalHarvest
|
|
369
|
+
|| finalHarvest.y1 !== releasedSelection.y1
|
|
370
|
+
|| finalHarvest.y2 !== releasedSelection.y2
|
|
371
|
+
) {
|
|
372
|
+
throw new Error(`released selection harvested wrong row during anchored growth: ${JSON.stringify({
|
|
373
|
+
doubleCountedHarvest,
|
|
374
|
+
finalHarvest,
|
|
375
|
+
expectedFinal: releasedSelection,
|
|
376
|
+
maxExpectedSelectionY,
|
|
377
|
+
})}`);
|
|
378
|
+
}
|
|
298
379
|
console.log('tui-transcript-jitter-harness: ok');
|
|
299
380
|
|
|
300
381
|
|
package/src/app.mjs
CHANGED
|
@@ -3,31 +3,12 @@ import { dirname, join } from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { performance } from 'node:perf_hooks';
|
|
5
5
|
import { ensureProcessListenerHeadroom } from './runtime/shared/process-listener-headroom.mjs';
|
|
6
|
+
import {
|
|
7
|
+
classifyCliInvocation,
|
|
8
|
+
parseHeadlessRoleCommand,
|
|
9
|
+
} from './headless-command.mjs';
|
|
6
10
|
|
|
7
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
// --workflow is consumed (with its value) but ignored on the headless role
|
|
9
|
-
// path: workflow selection is Lead-session-scoped. Accepting it keeps a
|
|
10
|
-
// mistakenly passed `--workflow X` from leaking "X" into the role message.
|
|
11
|
-
const VALUE_OPTIONS = new Set(['--provider', '--model', '--effort', '--workflow']);
|
|
12
|
-
const FLAG_OPTIONS = new Set(['--readonly', '--help', '-h', '--plain', '--react', '--remote', '--onboarding']);
|
|
13
|
-
const HEADLESS_FLAG_OPTIONS = new Set(['--fast']);
|
|
14
|
-
const HEADLESS_ROLE_ALIASES = new Map([
|
|
15
|
-
['explorer', 'explore'],
|
|
16
|
-
['explore', 'explore'],
|
|
17
|
-
['maint', 'maintainer'],
|
|
18
|
-
['maintenance', 'maintainer'],
|
|
19
|
-
['maintainer', 'maintainer'],
|
|
20
|
-
['worker', 'worker'],
|
|
21
|
-
['heavy', 'heavy-worker'],
|
|
22
|
-
['heavyworker', 'heavy-worker'],
|
|
23
|
-
['heavy-worker', 'heavy-worker'],
|
|
24
|
-
['review', 'reviewer'],
|
|
25
|
-
['reviewer', 'reviewer'],
|
|
26
|
-
['debug', 'debugger'],
|
|
27
|
-
['debugger', 'debugger'],
|
|
28
|
-
['web', 'web-researcher'],
|
|
29
|
-
['web-researcher', 'web-researcher'],
|
|
30
|
-
]);
|
|
31
12
|
const BOOT_PROFILE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ''));
|
|
32
13
|
const BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance.now());
|
|
33
14
|
|
|
@@ -49,55 +30,7 @@ function bootProfile(event, fields = {}) {
|
|
|
49
30
|
try { process.stderr.write(`${parts.join(' ')}\n`); } catch {}
|
|
50
31
|
}
|
|
51
32
|
|
|
52
|
-
|
|
53
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
54
|
-
const arg = argv[i];
|
|
55
|
-
if (VALUE_OPTIONS.has(arg)) {
|
|
56
|
-
i += 1;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (FLAG_OPTIONS.has(arg)) continue;
|
|
60
|
-
if (HEADLESS_FLAG_OPTIONS.has(arg)) continue;
|
|
61
|
-
if (String(arg || '').startsWith('-')) return arg;
|
|
62
|
-
}
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function positionalArgs(argv) {
|
|
67
|
-
const out = [];
|
|
68
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
69
|
-
const arg = argv[i];
|
|
70
|
-
if (VALUE_OPTIONS.has(arg)) {
|
|
71
|
-
i += 1;
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (FLAG_OPTIONS.has(arg)) continue;
|
|
75
|
-
if (HEADLESS_FLAG_OPTIONS.has(arg)) continue;
|
|
76
|
-
if (String(arg || '').startsWith('-')) continue;
|
|
77
|
-
out.push(arg);
|
|
78
|
-
}
|
|
79
|
-
return out;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function normalizeHeadlessAgent(value) {
|
|
83
|
-
const key = String(value || '').trim().toLowerCase().replace(/[\s_]+/g, '-');
|
|
84
|
-
return HEADLESS_ROLE_ALIASES.get(key) || null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function parseHeadlessRoleCommand(argv = []) {
|
|
88
|
-
const args = positionalArgs(argv);
|
|
89
|
-
if (args.length === 0) return null;
|
|
90
|
-
|
|
91
|
-
if (String(args[0] || '').toLowerCase() === 'role') {
|
|
92
|
-
return { error: 'usage: mixdog <role> <message...>' };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const agent = normalizeHeadlessAgent(args[0]);
|
|
96
|
-
if (!agent) return null;
|
|
97
|
-
const message = args.slice(1).join(' ').trim();
|
|
98
|
-
if (!message) return { error: `usage: mixdog ${args[0]} <message...>` };
|
|
99
|
-
return { agent, message };
|
|
100
|
-
}
|
|
33
|
+
export { parseHeadlessRoleCommand };
|
|
101
34
|
|
|
102
35
|
/**
|
|
103
36
|
* mixdog launcher.
|
|
@@ -107,57 +40,49 @@ export function parseHeadlessRoleCommand(argv = []) {
|
|
|
107
40
|
* from this entry point.
|
|
108
41
|
*/
|
|
109
42
|
export async function run(argv = []) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
43
|
+
const invocation = classifyCliInvocation(argv);
|
|
44
|
+
// Headless commands establish their own audited environment after route
|
|
45
|
+
// validation; do not let an inherited MIXDOG_BOOT_PROFILE affect that path.
|
|
46
|
+
if (invocation.kind !== 'headless' && invocation.skipHostPrelude !== true) {
|
|
47
|
+
bootProfile('run:start', { argv: argv.join(',') });
|
|
48
|
+
}
|
|
49
|
+
if (invocation.kind === 'error') {
|
|
50
|
+
process.stderr.write(`mixdog: ${invocation.error}\n`);
|
|
114
51
|
return 1;
|
|
115
52
|
}
|
|
116
|
-
|
|
117
|
-
const provIdx = argv.indexOf('--provider');
|
|
118
|
-
const modelIdx = argv.indexOf('--model');
|
|
119
|
-
const effortIdx = argv.indexOf('--effort');
|
|
120
|
-
const toolMode = argv.includes('--readonly') ? 'readonly' : 'full';
|
|
121
|
-
const remote = argv.includes('--remote');
|
|
122
|
-
const forceOnboarding = argv.includes('--onboarding');
|
|
123
|
-
const opts = {
|
|
124
|
-
provider: provIdx >= 0 ? argv[provIdx + 1] : undefined,
|
|
125
|
-
model: modelIdx >= 0 ? argv[modelIdx + 1] : undefined,
|
|
126
|
-
effort: effortIdx >= 0 ? argv[effortIdx + 1] : undefined,
|
|
127
|
-
fast: argv.includes('--fast'),
|
|
128
|
-
toolMode,
|
|
129
|
-
remote,
|
|
130
|
-
forceOnboarding,
|
|
131
|
-
};
|
|
53
|
+
const opts = invocation.options;
|
|
132
54
|
|
|
133
55
|
// `--help` / `-h`: keep this path tiny; do not import the REPL/runtime stack.
|
|
134
|
-
if (
|
|
56
|
+
if (invocation.kind === 'help') {
|
|
135
57
|
const { printHelp } = await import('./help.mjs');
|
|
136
58
|
printHelp();
|
|
137
59
|
return 0;
|
|
138
60
|
}
|
|
139
61
|
|
|
140
62
|
// `--plain`: the OLD readline REPL, kept as a strangler fallback.
|
|
141
|
-
if (
|
|
63
|
+
if (invocation.kind === 'plain') {
|
|
142
64
|
const { runRepl } = await import('./repl.mjs');
|
|
143
65
|
return await runRepl(opts);
|
|
144
66
|
}
|
|
145
67
|
|
|
146
|
-
if (
|
|
68
|
+
if (invocation.kind === 'react') {
|
|
147
69
|
process.stderr.write('mixdog: --react was removed; run `mixdog` for the canonical TUI.\n');
|
|
148
70
|
return 1;
|
|
149
71
|
}
|
|
150
72
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
73
|
+
if (invocation.kind === 'headless') {
|
|
74
|
+
const { validateExplicitPristineRoute } = await import(
|
|
75
|
+
'./runtime/shared/pristine-execution.mjs'
|
|
76
|
+
);
|
|
77
|
+
const routeError = validateExplicitPristineRoute(opts);
|
|
78
|
+
if (routeError) {
|
|
79
|
+
process.stderr.write(`mixdog: ${routeError}\n`);
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
157
82
|
const { runHeadlessRole } = await import('./headless-role.mjs');
|
|
158
83
|
return await runHeadlessRole({
|
|
159
|
-
agent: headless.agent,
|
|
160
|
-
message: headless.message,
|
|
84
|
+
agent: invocation.headless.agent,
|
|
85
|
+
message: invocation.headless.message,
|
|
161
86
|
provider: opts.provider,
|
|
162
87
|
model: opts.model,
|
|
163
88
|
effort: opts.effort,
|
package/src/cli.mjs
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import {
|
|
4
|
+
import { classifyCliInvocation } from './headless-command.mjs';
|
|
5
5
|
|
|
6
6
|
const argv = process.argv.slice(2);
|
|
7
7
|
|
|
8
|
-
// Pre-import self-update swap: BEFORE any runtime module is imported, if a
|
|
9
|
-
// completed staged newer version exists and no other mixdog session is live,
|
|
10
|
-
// atomically swap the global package dir into place. Doing it here (and only
|
|
11
|
-
// here) means the process goes on to load the NEW files, and a live session
|
|
12
|
-
// never has its files overwritten mid-run. Any obstacle → false, run current.
|
|
13
8
|
let swapped = false;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
try {
|
|
9
|
+
const invocation = classifyCliInvocation(argv);
|
|
10
|
+
const skipHostPrelude = invocation.kind === 'headless' || invocation.skipHostPrelude === true;
|
|
11
|
+
if (!skipHostPrelude) {
|
|
12
|
+
// Interactive/general sessions retain the staged-update and live-session
|
|
13
|
+
// semantics. Headless role commands skip both because those helpers touch the
|
|
14
|
+
// host data tree before the pristine boundary exists.
|
|
15
|
+
try {
|
|
16
|
+
const { performPendingSwap, registerLiveSession } = await import(
|
|
17
|
+
'./runtime/shared/staged-update.mjs'
|
|
18
|
+
);
|
|
19
|
+
swapped = performPendingSwap();
|
|
20
|
+
try { registerLiveSession(); } catch { /* advisory refcount only */ }
|
|
21
|
+
} catch {
|
|
22
|
+
swapped = false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
21
25
|
|
|
22
26
|
async function main() {
|
|
23
27
|
// If we actually swapped, re-exec a fresh node process so the new package
|
|
@@ -23,7 +23,9 @@ CORE is durable standing knowledge that lands in one of three layers:
|
|
|
23
23
|
- **L3 — Current map:** one-line project-landscape summaries, live long-running
|
|
24
24
|
goals, environment anchors documented nowhere else.
|
|
25
25
|
|
|
26
|
-
Every entry
|
|
26
|
+
Every entry must be ONE compact ENGLISH clause (≤120 chars), regardless of
|
|
27
|
+
source language. Keep identifiers, paths, and exact phrases coined by the user
|
|
28
|
+
verbatim. CORE is not a log.
|
|
27
29
|
|
|
28
30
|
## The distinction that decides every verdict
|
|
29
31
|
|
|
@@ -48,8 +50,10 @@ or inconclusive, keep the CORE entry.
|
|
|
48
50
|
|
|
49
51
|
## Verdicts
|
|
50
52
|
|
|
51
|
-
- `keep` — durable and already one
|
|
52
|
-
- `update` — durable but verbose
|
|
53
|
+
- `keep` — durable and already one compact ENGLISH clause.
|
|
54
|
+
- `update` — durable but verbose, multi-sentence, or non-English → rewrite as
|
|
55
|
+
one compact ≤120-char ENGLISH clause; keep identifiers, paths, and exact
|
|
56
|
+
phrases coined by the user verbatim.
|
|
53
57
|
- `merge` — duplicates another entry → fold into the survivor (same project pool).
|
|
54
58
|
- `reclassify` — the entry is filed under the WRONG project pool. Its subject
|
|
55
59
|
clearly belongs to a different pool shown among the entries under review: a
|
|
@@ -81,7 +85,9 @@ or inconclusive, keep the CORE entry.
|
|
|
81
85
|
`<id>|delete` with no reason, or any reason outside that set, is treated as
|
|
82
86
|
"needs confirmation" and only removed on an explicit APPLY CYCLE3 run.
|
|
83
87
|
|
|
84
|
-
A verbose durable entry is always `update`,
|
|
88
|
+
A verbose, multi-sentence, or non-English durable entry is always `update`,
|
|
89
|
+
never `keep`; rewrite it as a ≤120-char English clause while keeping
|
|
90
|
+
identifiers, paths, and exact user-coined phrases verbatim.
|
|
85
91
|
Delete is the rarest verdict. Prefer `keep` for durable rules/preferences and
|
|
86
92
|
`update` for compression when the current behavior is still valid.
|
|
87
93
|
Use `superseded` (not `delete`) when the entry WAS a valid durable rule/fact but
|
|
@@ -47,9 +47,10 @@ Use `why|A` for durable cross-session invariants (L1/L2). Use `why|B` for L3
|
|
|
47
47
|
current-map entries: one-line project-landscape summaries, live long-running
|
|
48
48
|
goals, and environment anchors documented nowhere else. `why` is validation
|
|
49
49
|
evidence only; it is
|
|
50
|
-
not stored. `core_summary` is injected into Core Memory, so
|
|
51
|
-
self-contained clause, <=120
|
|
52
|
-
|
|
50
|
+
not stored. `core_summary` is injected into Core Memory, so write it in ENGLISH
|
|
51
|
+
regardless of source language as one compact, self-contained clause, <=120
|
|
52
|
+
chars. Keep code identifiers, paths, and commands verbatim. For `merge`, `why`
|
|
53
|
+
and `core` may use the survivor `target_row`.
|
|
53
54
|
|
|
54
55
|
Allowed primary verbs:
|
|
55
56
|
|