@xiaobuyu/nodesign 0.0.5 → 0.0.7
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/server/api/assets/helpers.js +18 -0
- package/server/api/assets/notes.js +50 -0
- package/server/api/assets.js +11 -6
- package/server/api/instruction.js +3 -3
- package/server/engine/agent/events.js +11 -1
- package/server/engine/agent/hooks/failure.js +11 -1
- package/server/engine/agent/hooks/lifecycle.js +10 -39
- package/server/engine/agent/hooks/post-guidance.js +4 -38
- package/server/engine/agent/hooks/user-prompt-submit.js +11 -40
- package/server/engine/agent/hooks.js +4 -8
- package/server/engine/agent/memory-config.js +61 -0
- package/server/engine/agent/prompts/nodesign-prelude.md +14 -12
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +24 -7
- package/server/engine/agent/prompts/tools/site-reference.md +13 -3
- package/server/engine/agent/session-loop.js +29 -18
- package/server/engine/agents/explorer.md +1 -1
- package/server/engine/agents/index.js +1 -1
- package/server/engine/mcp/index.js +4 -11
- package/server/engine/mcp/tools/arrange-on-board.js +3 -1
- package/server/engine/mcp/tools/browse-computer.js +2 -1
- package/server/engine/mcp/tools/browse-find-batch.js +20 -3
- package/server/engine/mcp/tools/browse.js +24 -2
- package/server/engine/mcp/tools/crystallize-skill.js +1 -1
- package/server/engine/mcp/tools/generate-image.js +50 -70
- package/server/engine/mcp/tools/helpers/codex-imagegen.js +139 -0
- package/server/engine/mcp/tools/helpers/rembg.js +7 -2
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +109 -0
- package/server/engine/mcp/tools/list-pages.js +11 -5
- package/server/engine/mcp/tools/organize-board.js +26 -1
- package/server/engine/mcp/tools/pin-to-board.js +26 -17
- package/server/engine/mcp/tools/read-board.js +19 -5
- package/server/engine/mcp/tools/relate-on-board.js +42 -4
- package/server/engine/mcp/tools/report-issue.js +14 -7
- package/server/engine/mcp/tools/screenshot.js +17 -7
- package/server/engine/mcp/tools/sketch-on-board.js +13 -5
- package/server/engine/mcp/tools/web-search.js +11 -15
- package/server/engine/mcp/tools/write-on-board.js +15 -6
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +3 -4
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +10 -1
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +16 -0
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +4 -4
- package/server/index.js +0 -2
- package/server/lib/artifact-target.js +31 -14
- package/server/lib/board-kind-sizes.js +2 -3
- package/server/lib/canvas-id.js +29 -4
- package/server/lib/export-collect.js +3 -1
- package/server/lib/issues-store.js +3 -1
- package/server/lib/kinds/docx.js +11 -4
- package/server/lib/kinds/index.js +6 -16
- package/server/lib/kinds/site.js +40 -7
- package/server/lib/rewrite-refs.js +110 -0
- package/server/lib/ssrf-guard.js +9 -0
- package/server/lib/task-scan.js +18 -0
- package/server/projects/board-store.js +42 -1
- package/server/projects/memory-migration.js +85 -0
- package/server/projects/move-entry.js +11 -1
- package/server/projects/ui-config.js +2 -1
- package/server/projects/workspace-templates.js +13 -10
- package/server/projects/workspace.js +15 -15
- package/server/services/rembg-launcher.js +5 -2
- package/web/dist/assets/{BrowserWindow-P_vIdh-G.js → BrowserWindow-BoOBaIyz.js} +1 -1
- package/web/dist/assets/DeckWindow-Bg3rgkvi.js +11 -0
- package/web/dist/assets/{DocxWindow-DSAU1-tD.js → DocxWindow-B_u6aX9o.js} +1 -1
- package/web/dist/assets/{MermaidBlock-DR1GikCv.js → MermaidBlock-BO-46Du5.js} +6 -6
- package/web/dist/assets/{SiteWindow-92uBuRRR.js → SiteWindow-ConPmzdM.js} +1 -1
- package/web/dist/assets/{abnfDiagram-VCTEODGH-uA8Esix4.js → abnfDiagram-VCTEODGH-FCO9yf-n.js} +1 -1
- package/web/dist/assets/{arc-D_khH_xN.js → arc-BLcEOSRX.js} +1 -1
- package/web/dist/assets/{architectureDiagram-5GKGNRK7-DydJHqOt.js → architectureDiagram-5GKGNRK7-kxiX4TeN.js} +1 -1
- package/web/dist/assets/{blockDiagram-NRAW4CY4-B7wBbKiW.js → blockDiagram-NRAW4CY4-CptFksix.js} +1 -1
- package/web/dist/assets/{c4Diagram-UCG6FXSJ-6mrH0x7Y.js → c4Diagram-UCG6FXSJ-Dxmxj34F.js} +1 -1
- package/web/dist/assets/channel-DFyiuGL0.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-Cuikho5Q.js → chunk-2Q5K7J3B-D9inx5oo.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-3-KZ5AUz.js → chunk-5VM5RSS4-DydfHlEc.js} +1 -1
- package/web/dist/assets/{chunk-F27PBJKO-Bkwq2gGU.js → chunk-F27PBJKO-BO7GxhSv.js} +1 -1
- package/web/dist/assets/{chunk-G27WJ6UU-BLyQzdGE.js → chunk-G27WJ6UU-BzP6YiQ_.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-B7HxxQds.js → chunk-JWPE2WC7-CHcgcd0s.js} +1 -1
- package/web/dist/assets/{chunk-LCL6LL3I-CtXrnUwF.js → chunk-LCL6LL3I-D2Wg5JtX.js} +1 -1
- package/web/dist/assets/{chunk-POPQ4Y6H-dxddZlXt.js → chunk-POPQ4Y6H-B2fCRfpM.js} +1 -1
- package/web/dist/assets/{chunk-SVP7TREG-DpkmrgHn.js → chunk-SVP7TREG-JSLcsRlZ.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-BwZ7RbpQ.js → chunk-XXDRQBXY-BCjvaeHm.js} +1 -1
- package/web/dist/assets/classDiagram-DTDB5LWJ-Aw1f8z-b.js +1 -0
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-Aw1f8z-b.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-DGFOsMUt.js → cose-bilkent-JH36ORCC-0uKv6oJ7.js} +1 -1
- package/web/dist/assets/{cynefin-OW5HDTMX-DUwn3ehS.js → cynefin-OW5HDTMX-Bvt-gEmA.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-DlLAOZ9x.js → cynefinDiagram-5FMLGOSQ-C9Ht02qC.js} +1 -1
- package/web/dist/assets/{dagre-3AP2YEHR-CyourLfz.js → dagre-3AP2YEHR-BfxZny4s.js} +1 -1
- package/web/dist/assets/{diagram-S7CK7UJ4-BTMbx2np.js → diagram-S7CK7UJ4-CrE0ZETE.js} +1 -1
- package/web/dist/assets/{diagram-UQ7AKVKN-UK94_1-P.js → diagram-UQ7AKVKN-BO8VQ4Fx.js} +1 -1
- package/web/dist/assets/{diagram-VSXAHHWV-C7zS-v9v.js → diagram-VSXAHHWV-CHUZnR9F.js} +1 -1
- package/web/dist/assets/{diagram-VX7I27RA-DKJRa6CK.js → diagram-VX7I27RA-DYQAz1Vu.js} +1 -1
- package/web/dist/assets/{diagram-Z3DM3KII-kUbAfGAp.js → diagram-Z3DM3KII-BDjivgLr.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-PWID7BFC-CVW16_vb.js → ebnfDiagram-PWID7BFC-Bpe_QVbH.js} +1 -1
- package/web/dist/assets/{erDiagram-SSCWMZ5O-BRQMFQ5y.js → erDiagram-SSCWMZ5O-BxhwuAu1.js} +1 -1
- package/web/dist/assets/{flowDiagram-A5DVABFB-S34LpLtK.js → flowDiagram-A5DVABFB-CCw2H08-.js} +1 -1
- package/web/dist/assets/{ganttDiagram-EL5Y4UJY-BFiqk1-G.js → ganttDiagram-EL5Y4UJY-DtTc7MyI.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-LZsrdyyv.js → gitGraphDiagram-WWUBYQGX-BvORxMd2.js} +1 -1
- package/web/dist/assets/index-DGG5wIZs.js +2118 -0
- package/web/dist/assets/{infoDiagram-RXCK75RN-0DvN6KIp.js → infoDiagram-RXCK75RN-DMcrkypD.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CaY3A-Bx.js → ishikawaDiagram-5VMMS53U-CZkTIbe-.js} +1 -1
- package/web/dist/assets/{journeyDiagram-EYS64GPL-BBeiaCrj.js → journeyDiagram-EYS64GPL-DR0XazdE.js} +1 -1
- package/web/dist/assets/{kanban-definition-3QL26DDD-ux42DRXQ.js → kanban-definition-3QL26DDD-DN8VbwVu.js} +1 -1
- package/web/dist/assets/{layout-CnXiF_NB.js → layout-9_paCbyt.js} +1 -1
- package/web/dist/assets/{linear-BMW0u6G6.js → linear-g6Ct012e.js} +1 -1
- package/web/dist/assets/{mindmap-definition-FBJOCRG2-hUxSpdKn.js → mindmap-definition-FBJOCRG2-BGt4omYC.js} +1 -1
- package/web/dist/assets/{pegDiagram-XKGWAZYB-aCaQPH0w.js → pegDiagram-XKGWAZYB-uSfS-PBE.js} +1 -1
- package/web/dist/assets/{pending-edit-apply-AcdOQ9Ij.js → pending-edit-apply-uP4uvrOD.js} +1 -1
- package/web/dist/assets/{pieDiagram-E7YTZNPT-CmOEZ8zg.js → pieDiagram-E7YTZNPT-DpeI935H.js} +1 -1
- package/web/dist/assets/{quadrantDiagram-AXDQQJYC-VW6fAhsV.js → quadrantDiagram-AXDQQJYC-B7IuNvWa.js} +1 -1
- package/web/dist/assets/{railroadDiagram-O6MQD6OU-CKJ7Ba5K.js → railroadDiagram-O6MQD6OU-ARzYpPQI.js} +1 -1
- package/web/dist/assets/{requirementDiagram-EFPCY7ZU-pNGkq9Vw.js → requirementDiagram-EFPCY7ZU-Cj4upkAo.js} +1 -1
- package/web/dist/assets/{rotate-cw-KAfChbGV.js → rotate-cw-CXJcyHWk.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-P5KCCOFB-BIMmDTfZ.js → sankeyDiagram-P5KCCOFB-OaVwSnLP.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-BBW7wzKY.js → sequenceDiagram-WJ2MYXX4-1yrUbq4t.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-DdkXmLYR.js → sizeCapture-X5ZJPWSS-nYH5p9D6.js} +1 -1
- package/web/dist/assets/{square-dashed-mouse-pointer-CDUlhUP7.js → square-dashed-mouse-pointer-CTtTVKoH.js} +1 -1
- package/web/dist/assets/{stateDiagram-HBIQ2CUA-W313Uz16.js → stateDiagram-HBIQ2CUA-Dj5HvyNq.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-kJ1m_zJT.js +1 -0
- package/web/dist/assets/{swimlanes-XN3QIQJK-C29TL26K.js → swimlanes-XN3QIQJK-umeT0HpB.js} +1 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-DPSkMbbl.js +8 -0
- package/web/dist/assets/{timeline-definition-24CTP7MA-CF0JMObX.js → timeline-definition-24CTP7MA-MqnQfHKB.js} +1 -1
- package/web/dist/assets/{vennDiagram-4TSXK5OY-DrDFsaH3.js → vennDiagram-4TSXK5OY-b2liyZra.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-VM6X3IG4-CVrnRIS_.js → wardleyDiagram-VM6X3IG4-QMK-zNh1.js} +1 -1
- package/web/dist/assets/{xychartDiagram-S5SC5T6Z-BvkN1zsT.js → xychartDiagram-S5SC5T6Z-D5Mc6tfA.js} +1 -1
- package/web/dist/index.html +1 -1
- package/server/api/memory.js +0 -185
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +0 -583
- package/server/engine/mcp/tools/record-decision.js +0 -140
- package/web/dist/assets/DeckWindow-BpLv5082.js +0 -16
- package/web/dist/assets/channel-DGkccMO1.js +0 -1
- package/web/dist/assets/classDiagram-DTDB5LWJ-BGscw3OD.js +0 -1
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-BGscw3OD.js +0 -1
- package/web/dist/assets/index-GAamh1p4.js +0 -2152
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-DDhJBZzf.js +0 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BfN-Xdmu.js +0 -8
package/server/lib/kinds/site.js
CHANGED
|
@@ -12,12 +12,16 @@
|
|
|
12
12
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import fs from 'node:fs/promises';
|
|
15
|
-
import { walkTaskFiles, loadIgnore, DRAFTS_DIR, RESERVED_DIRS } from '../task-scan.js';
|
|
15
|
+
import { walkTaskFiles, loadIgnore, readTaskMarker, DRAFTS_DIR, RESERVED_DIRS } from '../task-scan.js';
|
|
16
16
|
|
|
17
17
|
const ENTRY = 'index.html';
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* 构建产物的约定目录,按序探测(谁有 index.html 认谁)。
|
|
21
|
+
* export:assets.js 的文件夹递归要用同一张表把构建目录挡在"独立站点/文件夹"之外
|
|
22
|
+
* (08-24 案:dist/ 被递归成第二张 site 卡)。
|
|
23
|
+
*/
|
|
24
|
+
export const OUTPUT_DIRS = ['dist', 'out', 'build', '_site', 'public'];
|
|
21
25
|
|
|
22
26
|
/** 站点页面扫描深度(产物根之下;子页 / posts/ / pages/ 够用) */
|
|
23
27
|
const PAGE_DEPTH = 4;
|
|
@@ -31,18 +35,36 @@ function normRoot(r) {
|
|
|
31
35
|
return (!s || s === '.' || s.includes('..')) ? '' : s;
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
/**
|
|
39
|
+
* 根 index.html 是不是**构建源**(vite / react / vue 工程的开发入口):
|
|
40
|
+
* module script 引用 src/ 目录或 .tsx/.jsx/.ts/.vue 源文件。构建源不是可浏览的
|
|
41
|
+
* 产物 —— 浏览器直接打开是白屏(脚本路径只有 dev server / 构建器认识)。
|
|
42
|
+
* 只认强证据;读不动 / 不确定一律按"真产物"处理(老行为)。
|
|
43
|
+
*/
|
|
44
|
+
async function isBuildSourceEntry(dirAbs) {
|
|
45
|
+
try {
|
|
46
|
+
const html = await fs.readFile(path.join(dirAbs, ENTRY), 'utf8');
|
|
47
|
+
return /<script[^>]+src=["'](?:\.?\/)?src\/[^"']+["']/i.test(html)
|
|
48
|
+
|| /<script[^>]+src=["'][^"']+\.(?:tsx|jsx|ts|vue)["']/i.test(html);
|
|
49
|
+
} catch { return false; }
|
|
50
|
+
}
|
|
51
|
+
|
|
34
52
|
/**
|
|
35
53
|
* 产物根(相对任务目录,'' = 任务根)。
|
|
36
54
|
* marker.root 是显式声明,最优先 —— 哪怕入口还没构建出来(agent 刚写完源、
|
|
37
55
|
* 还没跑 build 的窗口期),寻址也该指向将要出现的地方而不是源目录。
|
|
56
|
+
* 任务根的 index.html 是构建源且约定目录里已有构建产物时,优先产物
|
|
57
|
+
* (08-24 案:vite 工程源码 index.html 引 /src/main.tsx,预览白屏)。
|
|
38
58
|
*/
|
|
39
59
|
async function artifactRoot(taskDir, marker) {
|
|
40
60
|
const declared = normRoot(marker?.root);
|
|
41
61
|
if (declared) return declared;
|
|
42
|
-
|
|
62
|
+
const hasRootEntry = await exists(path.join(taskDir, ENTRY));
|
|
63
|
+
if (hasRootEntry && !(await isBuildSourceEntry(taskDir))) return '';
|
|
43
64
|
for (const d of OUTPUT_DIRS) {
|
|
44
65
|
if (await exists(path.join(taskDir, d, ENTRY))) return d;
|
|
45
66
|
}
|
|
67
|
+
// 构建源但还没 build 出产物:退回任务根(至少寻址不落空;build 完自动切换)
|
|
46
68
|
return '';
|
|
47
69
|
}
|
|
48
70
|
|
|
@@ -67,7 +89,10 @@ async function discoverInstances(taskDir, marker) {
|
|
|
67
89
|
if (rootSite) {
|
|
68
90
|
out.push({ srcRoot: '', root, single: false });
|
|
69
91
|
} else {
|
|
70
|
-
//
|
|
92
|
+
// 无根站:一级子目录各自为站。每个子目录按自己的 marker + 构建证据算产物根
|
|
93
|
+
// (08-24 案:jet-engine/ 里的 .nd-project.json 以前读不到、dist/ 轮不上,
|
|
94
|
+
// 构建型子目录站没有干净路径)。root 是**任务根相对**(jet-engine/dist),
|
|
95
|
+
// srcRoot 是子目录名 —— 卡片身份挂 srcRoot 那个文件夹,寻址走 root。
|
|
71
96
|
let entries = [];
|
|
72
97
|
try { entries = await fs.readdir(taskDir, { withFileTypes: true }); } catch { /* */ }
|
|
73
98
|
const ignore = await loadIgnore(taskDir);
|
|
@@ -75,8 +100,16 @@ async function discoverInstances(taskDir, marker) {
|
|
|
75
100
|
if (!e.isDirectory() || e.name.startsWith('.') || e.name === DRAFTS_DIR) continue;
|
|
76
101
|
if (RESERVED_DIRS.has(e.name)) continue;
|
|
77
102
|
if (ignore(e.name, true)) continue;
|
|
78
|
-
|
|
79
|
-
|
|
103
|
+
const subDir = path.join(taskDir, e.name);
|
|
104
|
+
const subMarker = await readTaskMarker(subDir);
|
|
105
|
+
const subRoot = await artifactRoot(subDir, subMarker);
|
|
106
|
+
const subDeclared = normRoot(subMarker?.root);
|
|
107
|
+
if (subDeclared || await exists(path.join(subDir, subRoot || '.', ENTRY))) {
|
|
108
|
+
out.push({
|
|
109
|
+
srcRoot: e.name,
|
|
110
|
+
root: subRoot ? `${e.name}/${subRoot}` : e.name,
|
|
111
|
+
single: false,
|
|
112
|
+
});
|
|
80
113
|
}
|
|
81
114
|
}
|
|
82
115
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rewrite-refs.js —— 搬文件后把全工作区的引用改写到新路径(2026-08-24,iss_mt38uih6)
|
|
3
|
+
*
|
|
4
|
+
* 用户的方法论原话:「新建一个文件夹然后改个索引」。organize_board 归纳素材时
|
|
5
|
+
* 磁盘和画布身份跟着走了,但 HTML/MD 里的 src/href 还指旧路径 —— 裂图。这里把
|
|
6
|
+
* "改索引"那半补上:对每个文本文件,把指向被搬条目的引用按**相对该文件目录**
|
|
7
|
+
* 的写法换算后替换。
|
|
8
|
+
*
|
|
9
|
+
* 设计取舍:
|
|
10
|
+
* - 引用是相对路径(站点路径铁律),所以同一个目标在不同文件里的写法不同
|
|
11
|
+
* (根上的 deck 写 `assets/x.png`,子页写 `../assets/x.png`)——逐文件换算。
|
|
12
|
+
* - 被搬的文件**自己**也可能引用别人:它落了新家后自己的相对引用基准变了,
|
|
13
|
+
* 按"旧目录里的写法 → 新目录里的写法"改(哪怕目标没动)。
|
|
14
|
+
* - 边界字符防误伤:只有前后都像"引用上下文"(引号/括号/=/空白/行首尾/`/`
|
|
15
|
+
* 延续)才替换。改了多少处如实报给调用方 —— 自动改写用户内容,必须可核对。
|
|
16
|
+
* - 搬的是文件夹时按前缀匹配(`素材/x.png` 里的 `素材` 段)。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { promises as fs } from 'node:fs';
|
|
21
|
+
import { walkTaskFiles, loadIgnore } from './task-scan.js';
|
|
22
|
+
|
|
23
|
+
const TEXT_EXTS = /\.(html?|css|md|markdown|js|mjs|json|svg|ya?ml|txt)$/i;
|
|
24
|
+
const MAX_BYTES = 2 * 1024 * 1024;
|
|
25
|
+
|
|
26
|
+
const relFrom = (dirRel, targetRel) => path.posix.relative(dirRel || '.', targetRel);
|
|
27
|
+
const escRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 纯函数:一段文本里把 pairs([旧工作区相对路径, 新路径])的引用改写掉。
|
|
31
|
+
* @param {string} text
|
|
32
|
+
* @param {string} oldDirRel 这段文本**写下时**所在目录(工作区相对,'' = 根)
|
|
33
|
+
* @param {string} newDirRel 这段文本现在所在目录(自己没被搬则与 oldDirRel 相同)
|
|
34
|
+
* @param {Array<[string,string]>} pairs
|
|
35
|
+
* @returns {{ text: string, hits: number }}
|
|
36
|
+
*/
|
|
37
|
+
export function rewriteTextRefs(text, oldDirRel, newDirRel, pairs) {
|
|
38
|
+
let out = text; let hits = 0;
|
|
39
|
+
const variants = [];
|
|
40
|
+
for (const [oldRel, newRel] of pairs) {
|
|
41
|
+
const oldRef = relFrom(oldDirRel, oldRel);
|
|
42
|
+
const newRef = relFrom(newDirRel, newRel);
|
|
43
|
+
if (!oldRef || oldRef === newRef) continue;
|
|
44
|
+
variants.push([oldRef, newRef]);
|
|
45
|
+
const encOld = encodeURI(oldRef); const encNew = encodeURI(newRef);
|
|
46
|
+
if (encOld !== oldRef) variants.push([encOld, encNew]);
|
|
47
|
+
}
|
|
48
|
+
// 长的先替:`素材` 与 `素材/子` 并存时避免前缀误吞
|
|
49
|
+
variants.sort((a, b) => b[0].length - a[0].length);
|
|
50
|
+
for (const [o, n] of variants) {
|
|
51
|
+
const re = new RegExp(`(^|["'\\(=,\\s])${escRe(o)}(?=["'\\)\\s#?,]|/|$)`, 'gm');
|
|
52
|
+
out = out.replace(re, (m, p1) => { hits += 1; return `${p1}${n}`; });
|
|
53
|
+
}
|
|
54
|
+
return { text: out, hits };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 自己被搬过的文本文件:**所有**相对引用的基准都变了(不只指向被搬条目的)。
|
|
59
|
+
* 把每个像相对路径的 token 按旧目录解析、按新目录重写;目标自己也搬了就再查
|
|
60
|
+
* moves 表。只认引号/括号里带扩展名的 token,http/data//#/绝对路径不碰。
|
|
61
|
+
*/
|
|
62
|
+
export function rebaseSelfRefs(text, oldDirRel, newDirRel, movesMap) {
|
|
63
|
+
let hits = 0;
|
|
64
|
+
const out = text.replace(
|
|
65
|
+
/(["'(=])((?:\.\.\/|\.\/)?[^"'\s()#?]*\.[A-Za-z0-9]{1,8})(?=["')\s#?]|$)/gm,
|
|
66
|
+
(m, pre, ref) => {
|
|
67
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|\/\/|\/|#)/i.test(ref)) return m;
|
|
68
|
+
const target = path.posix.normalize(path.posix.join(oldDirRel || '.', decodeURI(ref)));
|
|
69
|
+
if (target.startsWith('..')) return m; // 出工作区的不碰
|
|
70
|
+
const finalTarget = movesMap.get(target) || target;
|
|
71
|
+
const next = relFrom(newDirRel, finalTarget);
|
|
72
|
+
if (next === ref) return m;
|
|
73
|
+
hits += 1;
|
|
74
|
+
return `${pre}${next}`;
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
return { text: out, hits };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 扫全工作区文本文件,改写指向 pairs 的引用。
|
|
82
|
+
* @param {string} root 工作区根(绝对路径)
|
|
83
|
+
* @param {Array<{from: string, to: string}>} moves 已完成的搬家(工作区相对)
|
|
84
|
+
* @returns {Promise<{files: number, hits: number, lines: string[]}>}
|
|
85
|
+
*/
|
|
86
|
+
export async function rewriteWorkspaceRefs(root, moves) {
|
|
87
|
+
const pairs = moves.map((m) => [m.from, m.to]);
|
|
88
|
+
const movesMap = new Map(pairs);
|
|
89
|
+
const movedTo = new Map(moves.map((m) => [m.to, m.from]));
|
|
90
|
+
const ignore = await loadIgnore(root);
|
|
91
|
+
const all = await walkTaskFiles(root, { maxDepth: 6, ignore, includeDrafts: true });
|
|
92
|
+
let files = 0; let hits = 0; const lines = [];
|
|
93
|
+
for (const f of all) {
|
|
94
|
+
if (!TEXT_EXTS.test(f.name) || f.size > MAX_BYTES) continue;
|
|
95
|
+
const newDir = path.posix.dirname(f.rel) === '.' ? '' : path.posix.dirname(f.rel);
|
|
96
|
+
const selfOld = movedTo.get(f.rel);
|
|
97
|
+
let raw;
|
|
98
|
+
try { raw = await fs.readFile(f.abs, 'utf8'); } catch { continue; }
|
|
99
|
+
// 自己被搬过 → 整体换基准(含指向没搬目标的引用);没搬 → 只改指向被搬条目的
|
|
100
|
+
const r = selfOld
|
|
101
|
+
? rebaseSelfRefs(raw, path.posix.dirname(selfOld) === '.' ? '' : path.posix.dirname(selfOld), newDir, movesMap)
|
|
102
|
+
: rewriteTextRefs(raw, newDir, newDir, pairs);
|
|
103
|
+
if (r.hits > 0 && r.text !== raw) {
|
|
104
|
+
try { await fs.writeFile(f.abs, r.text, 'utf8'); } catch { continue; }
|
|
105
|
+
files += 1; hits += r.hits;
|
|
106
|
+
lines.push(` ↻ ${f.rel}:${r.hits} 处`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { files, hits, lines };
|
|
110
|
+
}
|
package/server/lib/ssrf-guard.js
CHANGED
|
@@ -267,6 +267,14 @@ export function blockReason(rawIp) {
|
|
|
267
267
|
/** 只允许这两个 scheme 发起网络请求;data:/blob: 不出网所以另算 */
|
|
268
268
|
const NET_SCHEMES = new Set(['http:', 'https:']);
|
|
269
269
|
const OFFLINE_SCHEMES = new Set(['data:', 'blob:', 'about:']);
|
|
270
|
+
/**
|
|
271
|
+
* Chromium 内置 PDF viewer(component extension,id 硬编码在 Chromium 源里)。
|
|
272
|
+
* 它渲染 PDF 时会加载自己的静态资源(pdf_embedder.css 等)——不出网、出不了扩展
|
|
273
|
+
* 包目录,跟 file:// 不是一个量级。不放行的话浏览器里所有 PDF 都渲染不出来
|
|
274
|
+
* (08-24 案)。⚠️ 只放这个 id:别的 chrome-extension:// 照旧拒 + 记账,
|
|
275
|
+
* 将来谁 --load-extension 也不会自动继承放行。
|
|
276
|
+
*/
|
|
277
|
+
const PDF_VIEWER_ORIGIN = 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/';
|
|
270
278
|
|
|
271
279
|
/**
|
|
272
280
|
* 一个 URL 能不能让浏览器去。**解析 DNS 后逐个 IP 判**。
|
|
@@ -297,6 +305,7 @@ export async function checkUrl(raw, { timeoutMs = 4000 } = {}) {
|
|
|
297
305
|
try { u = new URL(String(raw)); } catch { return { ok: false, reason: `not a valid URL: ${raw}` }; }
|
|
298
306
|
|
|
299
307
|
if (OFFLINE_SCHEMES.has(u.protocol)) return { ok: true, ips: [] }; // 不出网
|
|
308
|
+
if (String(raw).startsWith(PDF_VIEWER_ORIGIN)) return { ok: true, ips: [] }; // 内置 PDF viewer 自身资源,不出网
|
|
300
309
|
if (!NET_SCHEMES.has(u.protocol)) {
|
|
301
310
|
return { ok: false, reason: `scheme ${u.protocol} is not allowed (only http/https; file:// would read local files)` };
|
|
302
311
|
}
|
package/server/lib/task-scan.js
CHANGED
|
@@ -54,6 +54,11 @@ export const RESERVED_FILES = new Set([
|
|
|
54
54
|
'.nd-project.json', // 产物标记(形态兜底)
|
|
55
55
|
'.gitignore',
|
|
56
56
|
'.ndignore',
|
|
57
|
+
// 下面三个是基础设施不是用户产物,上墙就是「画布把自己的存档画在自己身上」
|
|
58
|
+
// 家族(08-24 清账补齐;ui-config.json 是黑板模式开关,曾以 .json 卡上墙)
|
|
59
|
+
'ui-config.json',
|
|
60
|
+
'spec.json',
|
|
61
|
+
'pending-changes.json',
|
|
57
62
|
]);
|
|
58
63
|
|
|
59
64
|
/** 永远不扫的目录名(任何深度命中即剪枝) */
|
|
@@ -68,6 +73,19 @@ export const DRAFTS_DIR = '_drafts';
|
|
|
68
73
|
|
|
69
74
|
export const NDIGNORE_FILE = '.ndignore';
|
|
70
75
|
|
|
76
|
+
/**
|
|
77
|
+
* 读产物标记(`.nd-project.json`)。没有 / 读不动 → null。
|
|
78
|
+
* 2026-08-24 从 kinds/index.js 迁来:site.js 现在要按子目录读 marker(构建型
|
|
79
|
+
* 子目录站),住 kinds/index.js 会循环 import。kinds/index.js 原样 re-export。
|
|
80
|
+
*/
|
|
81
|
+
export async function readTaskMarker(root) {
|
|
82
|
+
try {
|
|
83
|
+
const raw = await fs.readFile(path.join(root, '.nd-project.json'), 'utf8');
|
|
84
|
+
const parsed = JSON.parse(raw);
|
|
85
|
+
return (parsed && typeof parsed === 'object') ? parsed : null;
|
|
86
|
+
} catch { return null; }
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
/** gitignore 子集 → RegExp。认不出的行按字面量处理(转义后精确匹配)。 */
|
|
72
90
|
function compilePattern(line) {
|
|
73
91
|
let pat = line.trim();
|
|
@@ -473,7 +473,7 @@ export function pinToZone(pid, { objectId, zoneId = '' }) {
|
|
|
473
473
|
*/
|
|
474
474
|
const CELL_W = 320; const CELL_H = 220; const PAD = 10;
|
|
475
475
|
const dirOf = (id) => {
|
|
476
|
-
if (typeof id !== 'string'
|
|
476
|
+
if (typeof id !== 'string') return '';
|
|
477
477
|
const c = id.indexOf(':');
|
|
478
478
|
const path = (c > 0 && /^[a-z]+$/.test(id.slice(0, c))) ? id.slice(c + 1) : id;
|
|
479
479
|
const i = path.lastIndexOf('/');
|
|
@@ -505,3 +505,44 @@ export function pinToZone(pid, { objectId, zoneId = '' }) {
|
|
|
505
505
|
return { board, zone: zid ? { id: zid } : null, placed: board.objects[oid] };
|
|
506
506
|
});
|
|
507
507
|
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* 悬空关系线清扫(2026-08-24,iss_mszz20zn):删掉产物后 board.json 里会留下
|
|
511
|
+
* 端点指向已不存在文件的 binding —— 关系线只能画不能删,agent 收尾时"我制造的
|
|
512
|
+
* 东西我清不掉"。关系线依附于产物存在:产物没了线自然该没。
|
|
513
|
+
*
|
|
514
|
+
* 端点合法 = 板上有座位(board.objects)或磁盘上真有这个路径(kind 前缀剥掉后)。
|
|
515
|
+
* 两条都不满足才剪 —— 宁可留一条可疑的,不错杀一条活的。30s 节流:挂在
|
|
516
|
+
* /artifacts 热路径上,每张板最多半分钟对账一次。
|
|
517
|
+
*/
|
|
518
|
+
const pruneStamp = new Map(); // pid → last run ms
|
|
519
|
+
const PRUNE_INTERVAL_MS = 30_000;
|
|
520
|
+
|
|
521
|
+
export function pruneDanglingBindings(pid) {
|
|
522
|
+
const now = Date.now();
|
|
523
|
+
if ((pruneStamp.get(pid) || 0) + PRUNE_INTERVAL_MS > now) return Promise.resolve({ pruned: 0 });
|
|
524
|
+
pruneStamp.set(pid, now);
|
|
525
|
+
return withBoardLock(pid, async () => {
|
|
526
|
+
const board = await readBoard(pid);
|
|
527
|
+
const entries = Object.entries(board.bindings || {});
|
|
528
|
+
if (!entries.length) return { pruned: 0 };
|
|
529
|
+
const root = getSharedDir(pid);
|
|
530
|
+
const alive = async (ep) => {
|
|
531
|
+
const id = String(ep || '');
|
|
532
|
+
if (!id) return false;
|
|
533
|
+
if (board.objects?.[id]) return true;
|
|
534
|
+
const bare = id.replace(/^(deck|site|docx|text|scribble):/, '');
|
|
535
|
+
if (!bare || bare.includes('..')) return false;
|
|
536
|
+
try { await fs.access(path.resolve(root, bare)); return true; } catch { return false; }
|
|
537
|
+
};
|
|
538
|
+
const dead = [];
|
|
539
|
+
for (const [bid, b] of entries) {
|
|
540
|
+
if (!(await alive(b?.from)) || !(await alive(b?.to))) dead.push(bid);
|
|
541
|
+
}
|
|
542
|
+
if (!dead.length) return { pruned: 0 };
|
|
543
|
+
for (const bid of dead) delete board.bindings[bid];
|
|
544
|
+
await writeBoard(pid, board);
|
|
545
|
+
console.log(`[board] ${pid} 清掉 ${dead.length} 条悬空关系线`);
|
|
546
|
+
return { pruned: dead.length };
|
|
547
|
+
});
|
|
548
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* projects/memory-migration.js — 记忆体系改版迁移(2026-08-24,幂等)
|
|
3
|
+
*
|
|
4
|
+
* ensureProjectWorkspace 每次调用(源不在了就什么都不做):
|
|
5
|
+
* ① .claude/CLAUDE.md → <root>/CLAUDE.md(根上已有就把旧文件并进末尾再删)
|
|
6
|
+
* ② .claude/agent-memory/memory.md、brand/memory.md → 并进根 CLAUDE.md 的
|
|
7
|
+
* 「用户习惯」/「风格档案」节末尾,源删除
|
|
8
|
+
* ③ .claude/agent-memory/auto/* → 记忆/(SDK auto-memory 新家,画布可见)
|
|
9
|
+
* 三步全是"搬走后源删除",跑几遍结果一样。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { promises as fs } from 'node:fs';
|
|
14
|
+
import { MEMORY_DIR_NAME } from '../engine/agent/memory-config.js';
|
|
15
|
+
import { DEFAULT_CLAUDE_MD } from './workspace-templates.js';
|
|
16
|
+
|
|
17
|
+
/** 改版前的默认模板原文(迁移判据用:全文相等 = 用户没写过一个字,不搬) */
|
|
18
|
+
const OLD_DEFAULT_CLAUDE_MD = `# Project Instructions
|
|
19
|
+
|
|
20
|
+
This file is read by the AI agent at the start of every session as part of its
|
|
21
|
+
system prompt. Write project-specific guidance here — design intent,
|
|
22
|
+
constraints, vocabulary, must-do / must-not-do.
|
|
23
|
+
|
|
24
|
+
The agent will see this verbatim. Keep it concise and actionable.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
- Design tone: minimal, editorial, generous whitespace
|
|
28
|
+
- Hard constraints: never use red as a primary color
|
|
29
|
+
- Vocabulary: refer to the user as "the team"
|
|
30
|
+
|
|
31
|
+
(Edit this file from the NoDesign UI — the agent picks up changes on next session.)
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
export async function migrateMemoryLayout(root, { fileExists }) {
|
|
35
|
+
const rootMd = path.join(root, 'CLAUDE.md');
|
|
36
|
+
const appendTo = async (heading, text, label) => {
|
|
37
|
+
const body = String(text || '').trim();
|
|
38
|
+
if (!body) return;
|
|
39
|
+
let cur = '';
|
|
40
|
+
try { cur = await fs.readFile(rootMd, 'utf8'); } catch { /* 还没有 */ }
|
|
41
|
+
if (!cur.trim()) cur = DEFAULT_CLAUDE_MD; // 起新文件时以「项目档案」模板打底
|
|
42
|
+
await fs.writeFile(rootMd, `${cur ? `${cur.replace(/\n+$/, '')}\n\n` : ''}${heading}\n(${label},2026-08-24 自动迁入)\n\n${body}\n`, 'utf8');
|
|
43
|
+
};
|
|
44
|
+
const oldMd = path.join(root, '.claude', 'CLAUDE.md');
|
|
45
|
+
try {
|
|
46
|
+
const t = await fs.readFile(oldMd, 'utf8');
|
|
47
|
+
// 旧英文默认模板(用户一个字没改,**全文精确比对**才算)没有搬运价值 ——
|
|
48
|
+
// 删掉让 ensure 落新版「项目档案」模板。差一个字都按用户内容原样搬,宁可
|
|
49
|
+
// 多搬不可误删(判据宽了会吃掉用户在模板上的增改)。
|
|
50
|
+
if (t.trim() === OLD_DEFAULT_CLAUDE_MD.trim()) {
|
|
51
|
+
await fs.rm(oldMd, { force: true });
|
|
52
|
+
} else {
|
|
53
|
+
if (await fileExists(rootMd)) await appendTo('## 项目指引', t, '原 .claude/CLAUDE.md');
|
|
54
|
+
else await fs.writeFile(rootMd, t, 'utf8');
|
|
55
|
+
await fs.rm(oldMd, { force: true });
|
|
56
|
+
}
|
|
57
|
+
} catch { /* 没有旧文件:新项目 */ }
|
|
58
|
+
const memRoot = path.join(root, '.claude', 'agent-memory');
|
|
59
|
+
try {
|
|
60
|
+
const t = await fs.readFile(path.join(memRoot, 'memory.md'), 'utf8');
|
|
61
|
+
await appendTo('## 用户习惯', t, '原用户偏好记忆');
|
|
62
|
+
await fs.rm(path.join(memRoot, 'memory.md'), { force: true });
|
|
63
|
+
} catch { /* */ }
|
|
64
|
+
try {
|
|
65
|
+
const t = await fs.readFile(path.join(memRoot, 'brand', 'memory.md'), 'utf8');
|
|
66
|
+
await appendTo('## 风格档案', t, '原风格档案');
|
|
67
|
+
await fs.rm(path.join(memRoot, 'brand'), { recursive: true, force: true });
|
|
68
|
+
} catch { /* */ }
|
|
69
|
+
// auto/ 的存量记忆搬去 记忆/(同名不覆盖:目标已有的跳过,宁可留双份可见)
|
|
70
|
+
const autoDir = path.join(memRoot, 'auto');
|
|
71
|
+
try {
|
|
72
|
+
const entries = await fs.readdir(autoDir, { withFileTypes: true });
|
|
73
|
+
const dest = path.join(root, MEMORY_DIR_NAME);
|
|
74
|
+
await fs.mkdir(dest, { recursive: true });
|
|
75
|
+
for (const e of entries) {
|
|
76
|
+
const from = path.join(autoDir, e.name);
|
|
77
|
+
const to = path.join(dest, e.name);
|
|
78
|
+
if (await fileExists(to)) continue;
|
|
79
|
+
await fs.rename(from, to).catch(() => {});
|
|
80
|
+
}
|
|
81
|
+
const left = await fs.readdir(autoDir).catch(() => []);
|
|
82
|
+
if (left.length === 0) await fs.rm(autoDir, { recursive: true, force: true });
|
|
83
|
+
} catch { /* 没有 auto/:多数项目 */ }
|
|
84
|
+
}
|
|
85
|
+
|
|
@@ -19,6 +19,7 @@ import { getSharedDir } from './workspace.js';
|
|
|
19
19
|
import { renameBoardPaths } from './board-store.js';
|
|
20
20
|
import { taskManifest, KIND_SITE } from '../lib/artifact-target.js';
|
|
21
21
|
import { RESERVED_DIRS } from '../lib/task-scan.js';
|
|
22
|
+
import { CHALK_DIR } from '../lib/chalk.js';
|
|
22
23
|
|
|
23
24
|
export class MoveError extends Error {
|
|
24
25
|
constructor(status, message) { super(message); this.status = status; }
|
|
@@ -62,8 +63,17 @@ export async function moveEntry(pid, fromRaw, toRaw, { createFolder = false } =
|
|
|
62
63
|
if (NO_MOVE_OUT.has(guardSeg(from)) || guardSeg(from).startsWith('.')) {
|
|
63
64
|
throw new MoveError(400, '这个位置的东西不参与搬家');
|
|
64
65
|
}
|
|
66
|
+
// 板书不参与搬家(08-24,上报 iss_mt5qujy1):它是画布上的**话**,不是文件产物。
|
|
67
|
+
// 拖拽误触把它挪出 notes/板书/ 会丢 chalk 身份渲染成普通细条卡,且没有正规
|
|
68
|
+
// 归位通道。搬出一律拒;搬回(下面目标守卫的例外)留给误逃文件的恢复。
|
|
69
|
+
if (from === CHALK_DIR || from.startsWith(`${CHALK_DIR}/`)) {
|
|
70
|
+
throw new MoveError(400, '板书是画布上的话,不参与搬家');
|
|
71
|
+
}
|
|
65
72
|
if (to && (RESERVED_DIRS.has(guardSeg(to)) || guardSeg(to).startsWith('.'))) {
|
|
66
|
-
|
|
73
|
+
// 唯一例外:把误逃的 .md 送**回**板书目录(恢复通道;organize_board 同享。
|
|
74
|
+
// 不限根层 —— groupInto 误触会把板书埋进"新建文件夹/"里,也得捞得回来)
|
|
75
|
+
const isChalkReturn = to === CHALK_DIR && /\.md$/i.test(from);
|
|
76
|
+
if (!isChalkReturn) throw new MoveError(400, '不能搬进这个目录');
|
|
67
77
|
}
|
|
68
78
|
// 搬进自己肚子里(文件夹拖到它自己的子文件夹上)—— fs.rename 会报
|
|
69
79
|
// EINVAL,但那时目录树已经没法自洽了,提前拦住
|
|
@@ -26,8 +26,9 @@ export const DEFAULT_UI_CONFIG = Object.freeze({
|
|
|
26
26
|
* 黑板模式(2026-08-23):用户专注头脑风暴时,画布取代侧栏成为主交互窗口 ——
|
|
27
27
|
* agent 每轮的主体内容落画布(sketch_on_board / edit_sketch),聊天里只留一两句;
|
|
28
28
|
* 草图落定时服务端广播 board.focus,前端把镜头带过去。
|
|
29
|
+
* 2026-08-24 用户拍板默认开(老项目没写过这个键的也按开算;关过的尊重文件里的 false)。
|
|
29
30
|
*/
|
|
30
|
-
blackboard_mode:
|
|
31
|
+
blackboard_mode: true,
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -36,18 +36,21 @@ export const DEFAULT_SPEC_JSON = JSON.stringify(
|
|
|
36
36
|
null, 2,
|
|
37
37
|
) + '\n';
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
// 2026-08-24 记忆体系改版:CLAUDE.md 挪到工作区根(画布可见),承载三类
|
|
40
|
+
// **人工筛选过的**长期事实——项目指引 / 风格档案 / 用户习惯。每会话被 SDK
|
|
41
|
+
// 确定性全量注入(settingSources 'project' 原生行为,二进制实证根目录也读)。
|
|
42
|
+
// 生长中的记忆(agent 自动记)住 记忆/,两层各干各的。
|
|
43
|
+
export const DEFAULT_CLAUDE_MD = `# 项目档案
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
constraints, vocabulary, must-do / must-not-do.
|
|
45
|
+
这份文件每次会话都会完整进入 agent 的上下文。放**定了就不常变**的东西;
|
|
46
|
+
会生长的记忆 agent 自己记在 记忆/ 里。改这份文件里的硬约束要经用户点头。
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
## 项目指引
|
|
49
|
+
(设计意图、硬约束、必须做/不许做。例:主色永远不用红色。)
|
|
46
50
|
|
|
47
|
-
##
|
|
48
|
-
|
|
49
|
-
- Hard constraints: never use red as a primary color
|
|
50
|
-
- Vocabulary: refer to the user as "the team"
|
|
51
|
+
## 风格档案
|
|
52
|
+
(定案的视觉锚:调色板 / 字体 / 材质 / 艺术方向。)
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
## 用户习惯
|
|
55
|
+
(称呼、语气、工作方式偏好。)
|
|
53
56
|
`;
|
|
@@ -6,12 +6,9 @@
|
|
|
6
6
|
* 结构:
|
|
7
7
|
* <PROJECTS_DATA_ROOT>/<projectId>/
|
|
8
8
|
* ├── shared/ ← **项目工作区**:agent 的 cwd,产物的家
|
|
9
|
-
* │ ├── .
|
|
10
|
-
* │
|
|
11
|
-
* │
|
|
12
|
-
* │ │ ├── skills/ agents/ 项目级 skill / 子代理
|
|
13
|
-
* │ │ ├── agent-memory/ 长期记忆(agent 写)
|
|
14
|
-
* │ │ └── projects/ SDK 转录落点(.gitignore)
|
|
9
|
+
* │ ├── CLAUDE.md 项目档案(指引/风格/习惯;08-24 挪到根,画布可见)
|
|
10
|
+
* │ ├── 记忆/ SDK auto-memory(08-24 起,画布可见)
|
|
11
|
+
* │ ├── .claude/ settings.json · skills/ agents/ · projects/(SDK 转录)
|
|
15
12
|
* │ ├── assets/ 上传素材 + 生成图
|
|
16
13
|
* │ ├── .nd/<sid>/ 会话私档(spec.json / design-plan.md)
|
|
17
14
|
* │ ├── .git/ 项目历史
|
|
@@ -51,12 +48,9 @@ import { resolveModelContextWindow } from '../engine/agent/model-context.js';
|
|
|
51
48
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
52
49
|
|
|
53
50
|
/**
|
|
54
|
-
* Per-sessionRoot read-modify-write 串行 utility
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* 同 spec.json,无锁 → 后写者 silent 覆盖前写者全量内容("刚 record 的 decision 没了"
|
|
58
|
-
* 类 bug)。三处统一通过本 helper 写,async-mutex-lite 是 module-singleton,所有
|
|
59
|
-
* import 共享 lock map,按 key 串行。
|
|
51
|
+
* Per-sessionRoot read-modify-write 串行 utility(spec.json)。
|
|
52
|
+
* 08-24 起唯一写入方 = 暂退役的 expose_tweaks(决策贴/PostCompact 摘要已拆);
|
|
53
|
+
* 锁留着 —— tweaks 升级回归时并发结构不变。
|
|
60
54
|
*
|
|
61
55
|
* @param {string} workspaceRoot - sessions/<sid>/ 路径
|
|
62
56
|
* @param {(spec: object) => (object|void|Promise<object|void>)} mutator
|
|
@@ -93,8 +87,9 @@ export function validateSessionId(sid) {
|
|
|
93
87
|
}
|
|
94
88
|
|
|
95
89
|
import {
|
|
96
|
-
DEFAULT_GITIGNORE,
|
|
90
|
+
DEFAULT_GITIGNORE, DEFAULT_CLAUDE_MD,
|
|
97
91
|
} from './workspace-templates.js';
|
|
92
|
+
import { migrateMemoryLayout } from './memory-migration.js';
|
|
98
93
|
|
|
99
94
|
/**
|
|
100
95
|
* NoDesign 全局默认 settings.json — 代码是 source of truth。
|
|
@@ -247,8 +242,13 @@ export async function ensureProjectWorkspace(projectId) {
|
|
|
247
242
|
// .gitignore 每次比对而不是"不存在才写":扁平化新增了 .claude/projects/ 和
|
|
248
243
|
// .nd/ 两条,老项目的文件里没有,不补的话 SDK 转录会被 commit 进项目历史。
|
|
249
244
|
await ensureGitignore(path.join(root, '.gitignore'));
|
|
250
|
-
|
|
251
|
-
|
|
245
|
+
// 记忆体系改版迁移(2026-08-24,幂等:源不在了就什么都不做)——
|
|
246
|
+
// CLAUDE.md 从 .claude/ 挪到工作区根(画布可见,SDK 两处都读、根优先级同级),
|
|
247
|
+
// 老的偏好/风格档案并进去,SDK auto-memory 的存量从 .claude/agent-memory/auto
|
|
248
|
+
// 搬到画布可见的 记忆/。三步全是"搬走后源删除",跑几遍结果一样。
|
|
249
|
+
await migrateMemoryLayout(root, { fileExists });
|
|
250
|
+
if (!(await fileExists(path.join(root, 'CLAUDE.md')))) {
|
|
251
|
+
await fs.writeFile(path.join(root, 'CLAUDE.md'), DEFAULT_CLAUDE_MD, 'utf8');
|
|
252
252
|
}
|
|
253
253
|
// settings.json:每次 merge defaults 让代码层 default 升级时现存 project 自动跟上
|
|
254
254
|
// (用户字段优先,缺失的 NoDesign default 字段补进去)
|
|
@@ -32,7 +32,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
32
32
|
import path from 'node:path';
|
|
33
33
|
import http from 'node:http';
|
|
34
34
|
import { promises as fs, readFileSync, writeFileSync } from 'node:fs';
|
|
35
|
-
import { REMBG_VENV_PYTHON, REMBG_SETUP_HINT } from '../engine/mcp/tools/helpers/rembg.js';
|
|
35
|
+
import { REMBG_VENV_PYTHON, REMBG_SETUP_HINT, resolveRembgSocket } from '../engine/mcp/tools/helpers/rembg.js';
|
|
36
36
|
|
|
37
37
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
38
38
|
// services/ 在 server/services/,server root 上溯 1 层
|
|
@@ -46,7 +46,10 @@ const DEFAULT_SERVICE = path.join(__dirname, 'rembg-service.py');
|
|
|
46
46
|
// 改回多模型预热:env NODESIGN_REMBG_PRELOAD=isnet-general-use,birefnet-general-lite
|
|
47
47
|
const DEFAULT_PRELOAD = 'isnet-general-use';
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
// 按端口派生的实例私有 socket(真相源在 helpers/rembg.js 的 resolveRembgSocket;
|
|
50
|
+
// 08-24 案:prod/exp 共用一个默认路径时,killStaleServices 按 socket 判"自己人"
|
|
51
|
+
// 形同虚设,exp 一启动就把生产的抠图服务当 stale 杀掉再自己占坑)
|
|
52
|
+
const DEFAULT_SOCKET = resolveRembgSocket();
|
|
50
53
|
const MAX_RSS_MB = Number(process.env.NODESIGN_REMBG_MAX_RSS_MB ?? 900);
|
|
51
54
|
const WATCH_MS = Number(process.env.NODESIGN_REMBG_WATCH_MS ?? 60_000);
|
|
52
55
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as G,r as i,B as z,H as ee,j as t,I as te,F as y,a as $,G as u,b as ne,A as ie,C as N,d as c,e as j,L as W,f as se,g as re,h as oe}from"./index-
|
|
1
|
+
import{c as G,r as i,B as z,H as ee,j as t,I as te,F as y,a as $,G as u,b as ne,A as ie,C as N,d as c,e as j,L as W,f as se,g as re,h as oe}from"./index-DGG5wIZs.js";import{A as ae,R as le}from"./rotate-cw-CXJcyHWk.js";/**
|
|
2
2
|
* @license lucide-react v0.468.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|