wiki-viewer 1.4.3 → 1.5.0
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +5 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/routes-manifest.json +34 -0
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/agent/activity/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js +8 -7
- package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js +17 -0
- package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js +15 -0
- package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/agent/fs/move/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/agent/fs/move/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/agent/fs/move/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/agent/fs/move/route.js +15 -0
- package/.next/standalone/.next/server/app/api/agent/fs/move/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/agent/fs/move/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/agent/fs/move/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/agent/fs/search/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/agent/fs/search/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/agent/fs/search/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/agent/fs/search/route.js +15 -0
- package/.next/standalone/.next/server/app/api/agent/fs/search/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/agent/fs/search/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/agent/fs/search/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/agent/internal/span/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/settings/route.js +1 -1
- package/.next/standalone/.next/server/app/api/agent/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/assets/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/browse/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/clear-root/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/root-status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/system/set-root/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/upload/[...path]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/app/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/content/route.js +2 -2
- package/.next/standalone/.next/server/app/api/wiki/content/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/file/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/folder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/move/route.js +3 -3
- package/.next/standalone/.next/server/app/api/wiki/move/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/new-file/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/page/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/presence/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/wiki/presence/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/wiki/presence/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/wiki/presence/route.js +14 -0
- package/.next/standalone/.next/server/app/api/wiki/presence/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/wiki/presence/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/wiki/presence/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/wiki/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/slugs/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/wiki/watch/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +5 -0
- package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0d2jbo5.js +3 -0
- package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0ytral2.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0269d2e._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05f6x0i._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06-dtd0._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06sek6_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__09s9qkd._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d75a5q._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d_da6q._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e7si9p._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0hhhvm7._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kk26wc._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kur4ro._.js +15 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0p12-6~._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qc2~6l._.js +15 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s~i040._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w47wil._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xq124z._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l..bxz._.js → [root-of-the-server]__0xz-_0v._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10w2q~o._.js +1 -1
- package/.next/standalone/.next/server/chunks/_079xuln._.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_file_[___path]_route_actions_0gb11t~.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_ls_[[___path]]_route_actions_0wfg4nl.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_move_route_actions_0z34mqq.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_agent_fs_search_route_actions_0ut_ug..js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_wiki_presence_route_actions_0x9d7f4.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_diagram-OG6HWLK6_mjs_0t5sus6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_sankeyDiagram-5OEKKPKP_mjs_11ifrnu._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0pqaawz._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0y9k6h~._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0znncy9._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_12-ni1n._.js +1 -1
- package/.next/standalone/.next/server/functions-config-manifest.json +5 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/{11s9zwr7edrs7.js → 14luv~jq7e6-n.js} +12 -12
- package/.next/standalone/README.md +79 -16
- package/.next/standalone/agents/bootstrap-prompt.md +12 -1
- package/.next/standalone/agents/wiki-viewer-skill/SKILL.md +199 -6
- package/.next/standalone/docs/file-vs-collab-authority.md +124 -0
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/packages/wiki-viewer-mcp/README.md +194 -0
- package/.next/standalone/packages/wiki-viewer-mcp/dist/http-client.js +220 -0
- package/.next/standalone/packages/wiki-viewer-mcp/dist/index.js +472 -0
- package/.next/standalone/packages/wiki-viewer-mcp/dist/register.js +94 -0
- package/.next/standalone/packages/wiki-viewer-mcp/dist/state-cache.js +26 -0
- package/.next/standalone/packages/wiki-viewer-mcp/package-lock.json +1716 -0
- package/.next/standalone/packages/wiki-viewer-mcp/package.json +41 -0
- package/.next/standalone/packages/wiki-viewer-mcp/src/http-client.ts +344 -0
- package/.next/standalone/packages/wiki-viewer-mcp/src/index.ts +575 -0
- package/.next/standalone/packages/wiki-viewer-mcp/src/register.ts +144 -0
- package/.next/standalone/packages/wiki-viewer-mcp/src/state-cache.ts +45 -0
- package/.next/standalone/packages/wiki-viewer-mcp/tsconfig.json +19 -0
- package/.next/standalone/pnpm-lock.yaml +9 -9
- package/.next/standalone/src/app/api/agent/files/[...path]/route.ts +11 -1
- package/.next/standalone/src/app/api/agent/fs/file/[...path]/route.ts +344 -0
- package/.next/standalone/src/app/api/agent/fs/ls/[[...path]]/route.ts +147 -0
- package/.next/standalone/src/app/api/agent/fs/move/route.ts +125 -0
- package/.next/standalone/src/app/api/agent/fs/search/route.ts +236 -0
- package/.next/standalone/src/app/api/agent/register/route.ts +3 -3
- package/.next/standalone/src/app/api/agents/install/route.ts +44 -2
- package/.next/standalone/src/app/api/wiki/move/route.ts +10 -1
- package/.next/standalone/src/app/api/wiki/presence/route.ts +55 -0
- package/.next/standalone/src/components/ai-panel/ai-panel.tsx +49 -0
- package/.next/standalone/src/components/editor/editor.tsx +38 -0
- package/.next/standalone/src/lib/proof/audit.ts +79 -0
- package/.next/standalone/src/lib/proof/auth.ts +2 -2
- package/.next/standalone/src/lib/proof/collab-state.ts +79 -0
- package/.next/standalone/src/lib/proof/lease.ts +104 -0
- package/.next/standalone/src/lib/proof/ops-applier.ts +74 -25
- package/.next/standalone/src/lib/proof/raw-fs.ts +198 -0
- package/.next/standalone/src/lib/proof/registry.ts +1 -1
- package/.next/standalone/src/lib/proof/sidecar.ts +38 -1
- package/.next/standalone/src/lib/proof/types.ts +13 -1
- package/.next/standalone/src/tests/proof/agent-fs.test.ts +824 -0
- package/.next/standalone/src/tests/proof/agents-install.test.ts +53 -0
- package/.next/standalone/src/tests/proof/collab-state.test.ts +499 -0
- package/.next/standalone/src/tests/proof/reconcile-sidecar.test.ts +195 -0
- package/.next/standalone/src/tests/proof/sidecar-lifecycle.test.ts +177 -0
- package/.next/standalone/tsconfig.tsbuildinfo +1 -1
- package/README.md +79 -16
- package/package.json +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__09~~-nd._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w87kiz._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__12ssbhv._.js +0 -3
- package/.next/standalone/.next/server/chunks/node_modules__pnpm_0g0fhfk._.js +0 -3
- /package/.next/standalone/.next/static/{kzOuSAQArtvD7uiAJ6ZLh → 19j4-_brJLoulXAMekpM2}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{kzOuSAQArtvD7uiAJ6ZLh → 19j4-_brJLoulXAMekpM2}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{kzOuSAQArtvD7uiAJ6ZLh → 19j4-_brJLoulXAMekpM2}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for Tier-1 raw-fs routes.
|
|
3
|
+
*
|
|
4
|
+
* - Path safety (symlink escape guard, hard-denied paths)
|
|
5
|
+
* - SHA-256 of Buffer
|
|
6
|
+
* - Best-effort MIME by extension
|
|
7
|
+
* - Atomic write (tmp → rename, preserves mode)
|
|
8
|
+
*/
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { open, stat, rename, realpath, mkdir } from "node:fs/promises";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { getRootDir } from "@/lib/root-dir";
|
|
13
|
+
|
|
14
|
+
// ── Denied path checks ──────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* True if the root-relative path is hard-denied regardless of scope.
|
|
18
|
+
* - .proof/ — sidecar storage (Tier-2 internal)
|
|
19
|
+
* - .git/ — git objects (sensitive)
|
|
20
|
+
*/
|
|
21
|
+
export function isDeniedRelPath(rel: string): boolean {
|
|
22
|
+
const norm = rel.replace(/\\/g, "/");
|
|
23
|
+
return (
|
|
24
|
+
norm === ".proof" ||
|
|
25
|
+
norm.startsWith(".proof/") ||
|
|
26
|
+
norm === ".git" ||
|
|
27
|
+
norm.startsWith(".git/")
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isMarkdown(filePath: string): boolean {
|
|
32
|
+
return filePath.endsWith(".md") || filePath.endsWith(".markdown");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Path safety ──────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the absolute path for `rel`, checking:
|
|
39
|
+
* 1. basic traversal (path.join containment)
|
|
40
|
+
* 2. hard-denied prefixes (.proof/, .git/)
|
|
41
|
+
* 3. symlink escape: if file exists, realpath must still be under root
|
|
42
|
+
*
|
|
43
|
+
* For non-existent targets (creates), resolves parent dir instead.
|
|
44
|
+
*
|
|
45
|
+
* Returns absolute path on success, null on rejection.
|
|
46
|
+
*/
|
|
47
|
+
export async function safeAbsPath(rel: string): Promise<string | null> {
|
|
48
|
+
const root = getRootDir();
|
|
49
|
+
if (!root) return null;
|
|
50
|
+
|
|
51
|
+
// Normalise and basic traversal guard
|
|
52
|
+
const normalised = path.normalize(rel);
|
|
53
|
+
if (normalised.startsWith("..") || path.isAbsolute(normalised)) return null;
|
|
54
|
+
|
|
55
|
+
if (isDeniedRelPath(normalised)) return null;
|
|
56
|
+
|
|
57
|
+
const abs = path.join(root, normalised);
|
|
58
|
+
// Re-check containment after normalize
|
|
59
|
+
if (abs !== root && !abs.startsWith(root + path.sep)) return null;
|
|
60
|
+
|
|
61
|
+
// Symlink-escape check: resolve realpath of the nearest existing ancestor
|
|
62
|
+
try {
|
|
63
|
+
const real = await realpath(abs);
|
|
64
|
+
if (real !== root && !real.startsWith(root + path.sep)) return null;
|
|
65
|
+
} catch {
|
|
66
|
+
// Target doesn't exist — check parent
|
|
67
|
+
const parent = path.dirname(abs);
|
|
68
|
+
if (parent !== abs) {
|
|
69
|
+
try {
|
|
70
|
+
const parentReal = await realpath(parent);
|
|
71
|
+
if (parentReal !== root && !parentReal.startsWith(root + path.sep)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Parent doesn't exist either — containment already checked above
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return abs;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Hashing ──────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/** Returns "sha256:<hex>" — the canonical sha format used in ETag / audit. */
|
|
86
|
+
export function sha256ofBuf(buf: Buffer): string {
|
|
87
|
+
return "sha256:" + createHash("sha256").update(buf).digest("hex");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Normalise an If-Match / ETag header value to bare hex.
|
|
92
|
+
* Strips outer double-quotes and "sha256:" prefix.
|
|
93
|
+
*/
|
|
94
|
+
export function extractShaHex(header: string): string {
|
|
95
|
+
return header
|
|
96
|
+
.replace(/^"/, "")
|
|
97
|
+
.replace(/"$/, "")
|
|
98
|
+
.replace(/^sha256:/, "");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── MIME ──────────────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
const MIME_MAP: Record<string, string> = {
|
|
104
|
+
".md": "text/markdown; charset=utf-8",
|
|
105
|
+
".markdown": "text/markdown; charset=utf-8",
|
|
106
|
+
".txt": "text/plain; charset=utf-8",
|
|
107
|
+
".json": "application/json; charset=utf-8",
|
|
108
|
+
".jsonc": "application/json; charset=utf-8",
|
|
109
|
+
".ts": "text/typescript; charset=utf-8",
|
|
110
|
+
".tsx": "text/typescript; charset=utf-8",
|
|
111
|
+
".mts": "text/typescript; charset=utf-8",
|
|
112
|
+
".js": "text/javascript; charset=utf-8",
|
|
113
|
+
".jsx": "text/javascript; charset=utf-8",
|
|
114
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
115
|
+
".html": "text/html; charset=utf-8",
|
|
116
|
+
".htm": "text/html; charset=utf-8",
|
|
117
|
+
".css": "text/css; charset=utf-8",
|
|
118
|
+
".yaml": "text/yaml; charset=utf-8",
|
|
119
|
+
".yml": "text/yaml; charset=utf-8",
|
|
120
|
+
".toml": "text/toml; charset=utf-8",
|
|
121
|
+
".xml": "application/xml; charset=utf-8",
|
|
122
|
+
".svg": "image/svg+xml",
|
|
123
|
+
".png": "image/png",
|
|
124
|
+
".jpg": "image/jpeg",
|
|
125
|
+
".jpeg": "image/jpeg",
|
|
126
|
+
".gif": "image/gif",
|
|
127
|
+
".webp": "image/webp",
|
|
128
|
+
".pdf": "application/pdf",
|
|
129
|
+
".zip": "application/zip",
|
|
130
|
+
".tar": "application/x-tar",
|
|
131
|
+
".gz": "application/gzip",
|
|
132
|
+
".py": "text/x-python; charset=utf-8",
|
|
133
|
+
".rs": "text/x-rustsrc; charset=utf-8",
|
|
134
|
+
".go": "text/x-go; charset=utf-8",
|
|
135
|
+
".sh": "text/x-shellscript; charset=utf-8",
|
|
136
|
+
".csv": "text/csv; charset=utf-8",
|
|
137
|
+
".env": "text/plain; charset=utf-8",
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export function mimeByExt(filePath: string): string {
|
|
141
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
142
|
+
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Binary detection ──────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
/** Heuristic: buffer contains a null byte in first 8 KB → treat as binary. */
|
|
148
|
+
export function looksLikeBinary(buf: Buffer): boolean {
|
|
149
|
+
const sample = buf.slice(0, Math.min(buf.length, 8192));
|
|
150
|
+
return sample.includes(0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Atomic write ──────────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Write `data` to `absPath` atomically:
|
|
157
|
+
* 1. Write to a sibling .tmp file
|
|
158
|
+
* 2. datasync the tmp file
|
|
159
|
+
* 3. rename tmp → target
|
|
160
|
+
*
|
|
161
|
+
* Preserves the unix mode of the existing file.
|
|
162
|
+
* Caller is responsible for creating parent dirs.
|
|
163
|
+
*/
|
|
164
|
+
export async function atomicWrite(absPath: string, data: Buffer): Promise<void> {
|
|
165
|
+
// Preserve mode of existing file
|
|
166
|
+
let mode = 0o644;
|
|
167
|
+
try {
|
|
168
|
+
const st = await stat(absPath);
|
|
169
|
+
mode = st.mode & 0o777;
|
|
170
|
+
} catch {
|
|
171
|
+
// New file — use 0o644 default
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const tmp = absPath + ".~" + process.pid + "." + Date.now() + ".tmp";
|
|
175
|
+
const fh = await open(tmp, "w", mode);
|
|
176
|
+
try {
|
|
177
|
+
await fh.write(data);
|
|
178
|
+
await fh.datasync();
|
|
179
|
+
} finally {
|
|
180
|
+
await fh.close();
|
|
181
|
+
}
|
|
182
|
+
await rename(tmp, absPath);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Ensure parent directory exists; creates intermediate dirs if mkdirs=true. */
|
|
186
|
+
export async function ensureParentDir(absPath: string, mkdirs: boolean): Promise<boolean> {
|
|
187
|
+
const dir = path.dirname(absPath);
|
|
188
|
+
if (mkdirs) {
|
|
189
|
+
await mkdir(dir, { recursive: true });
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
await stat(dir);
|
|
194
|
+
return true;
|
|
195
|
+
} catch {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir, rename } from "node:fs/promises";
|
|
1
|
+
import { readFile, writeFile, mkdir, rename, unlink } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import type { Sidecar } from "./types";
|
|
4
4
|
|
|
@@ -44,6 +44,43 @@ export async function writeSidecar(
|
|
|
44
44
|
await rename(tmp, dest);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Move a sidecar file alongside a renamed .md file.
|
|
49
|
+
* No-op if the sidecar does not exist.
|
|
50
|
+
*/
|
|
51
|
+
export async function moveSidecar(
|
|
52
|
+
rootDir: string,
|
|
53
|
+
fromMdPath: string,
|
|
54
|
+
toMdPath: string,
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
const src = sidecarPath(rootDir, fromMdPath);
|
|
57
|
+
const dest = sidecarPath(rootDir, toMdPath);
|
|
58
|
+
try {
|
|
59
|
+
await mkdir(path.dirname(dest), { recursive: true });
|
|
60
|
+
await rename(src, dest);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") return; // no sidecar — ok
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Delete a sidecar file for a removed .md file.
|
|
69
|
+
* No-op if the sidecar does not exist.
|
|
70
|
+
*/
|
|
71
|
+
export async function deleteSidecar(
|
|
72
|
+
rootDir: string,
|
|
73
|
+
mdPath: string,
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
const filePath = sidecarPath(rootDir, mdPath);
|
|
76
|
+
try {
|
|
77
|
+
await unlink(filePath);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") return; // already gone — ok
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
47
84
|
export function emptySidecar(mdPath: string): Sidecar {
|
|
48
85
|
const now = new Date().toISOString();
|
|
49
86
|
return {
|
|
@@ -40,6 +40,8 @@ export interface Comment {
|
|
|
40
40
|
resolved: boolean;
|
|
41
41
|
createdAt: string;
|
|
42
42
|
turns: CommentTurn[];
|
|
43
|
+
/** Set true when a raw .md overwrite orphans the anchor ref (R2 collab-anchor safety). */
|
|
44
|
+
stale?: boolean;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export type SuggestionKind =
|
|
@@ -61,11 +63,21 @@ export interface Suggestion {
|
|
|
61
63
|
createdAt: string;
|
|
62
64
|
resolvedAt?: string; // when accepted/rejected
|
|
63
65
|
resolvedBy?: string;
|
|
66
|
+
/** Set true when a raw .md overwrite orphans the anchor ref (R2 collab-anchor safety). */
|
|
67
|
+
stale?: boolean;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
export interface ProofEvent {
|
|
67
71
|
id: number;
|
|
68
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Known event types:
|
|
74
|
+
* block.replaced | block.inserted | block.deleted
|
|
75
|
+
* comment.added | comment.replied | comment.resolved | comment.reopened
|
|
76
|
+
* suggestion.added | suggestion.accepted | suggestion.rejected
|
|
77
|
+
* file.externallyEdited — writer unknown (chokidar / external tool)
|
|
78
|
+
* file.rawWritten — writer known (by: "ai:<id>"), emitted by Tier-1 raw-fs write
|
|
79
|
+
*/
|
|
80
|
+
type: string;
|
|
69
81
|
at: string;
|
|
70
82
|
by: string;
|
|
71
83
|
[k: string]: unknown;
|