@coreyuan/vector-mind 1.0.54 → 1.1.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.
Files changed (112) hide show
  1. package/README.md +99 -72
  2. package/admin-panel/README.md +77 -0
  3. package/admin-panel/dist/client/assets/index-Bzyjdhh2.css +1 -0
  4. package/admin-panel/dist/client/assets/index-Ph73ewzt.js +9 -0
  5. package/admin-panel/dist/client/index.html +14 -0
  6. package/admin-panel/package.json +25 -0
  7. package/admin-panel/server/index.mjs +1424 -0
  8. package/admin-panel/server/security.mjs +117 -0
  9. package/admin-panel/server/security.test.mjs +113 -0
  10. package/admin-panel/server/storage.mjs +49 -0
  11. package/admin-panel/server/storage.test.mjs +36 -0
  12. package/dist/checkpoint-snapshot.d.ts +56 -0
  13. package/dist/checkpoint-snapshot.js +411 -0
  14. package/dist/checkpoint-snapshot.js.map +1 -0
  15. package/dist/config.d.ts +1 -1
  16. package/dist/config.js +1 -1
  17. package/dist/config.js.map +1 -1
  18. package/dist/context-governance.d.ts +56 -0
  19. package/dist/context-governance.js +176 -0
  20. package/dist/context-governance.js.map +1 -0
  21. package/dist/database-runtime.d.ts +3 -1
  22. package/dist/database-runtime.js +232 -127
  23. package/dist/database-runtime.js.map +1 -1
  24. package/dist/development-warnings.d.ts +2 -1
  25. package/dist/development-warnings.js +90 -20
  26. package/dist/development-warnings.js.map +1 -1
  27. package/dist/file-indexing.d.ts +2 -1
  28. package/dist/file-indexing.js +127 -47
  29. package/dist/file-indexing.js.map +1 -1
  30. package/dist/grep.js +4 -0
  31. package/dist/grep.js.map +1 -1
  32. package/dist/index.js +5 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/large-file-split.d.ts +52 -8
  35. package/dist/large-file-split.js +290 -76
  36. package/dist/large-file-split.js.map +1 -1
  37. package/dist/memory-maintenance/compaction.js +3 -2
  38. package/dist/memory-maintenance/compaction.js.map +1 -1
  39. package/dist/memory-maintenance/metrics.d.ts +1 -1
  40. package/dist/memory-maintenance/metrics.js +11 -3
  41. package/dist/memory-maintenance/metrics.js.map +1 -1
  42. package/dist/memory-maintenance/purge.js +1 -1
  43. package/dist/memory-maintenance/purge.js.map +1 -1
  44. package/dist/memory-maintenance/runner.d.ts +1 -0
  45. package/dist/memory-maintenance/runner.js +15 -3
  46. package/dist/memory-maintenance/runner.js.map +1 -1
  47. package/dist/memory-maintenance/types.d.ts +1 -0
  48. package/dist/memory-mutations.d.ts +3 -3
  49. package/dist/memory-mutations.js +109 -43
  50. package/dist/memory-mutations.js.map +1 -1
  51. package/dist/memory-recall.js +16 -1
  52. package/dist/memory-recall.js.map +1 -1
  53. package/dist/operation-scope.js +42 -12
  54. package/dist/operation-scope.js.map +1 -1
  55. package/dist/path-containment.d.ts +5 -0
  56. package/dist/path-containment.js +64 -0
  57. package/dist/path-containment.js.map +1 -0
  58. package/dist/path-filters.d.ts +1 -1
  59. package/dist/path-filters.js +7 -6
  60. package/dist/path-filters.js.map +1 -1
  61. package/dist/pending-changes.d.ts +5 -0
  62. package/dist/pending-changes.js +26 -16
  63. package/dist/pending-changes.js.map +1 -1
  64. package/dist/project-files.d.ts +1 -0
  65. package/dist/project-files.js +19 -10
  66. package/dist/project-files.js.map +1 -1
  67. package/dist/root.d.ts +4 -1
  68. package/dist/root.js +34 -4
  69. package/dist/root.js.map +1 -1
  70. package/dist/rtk-integrity.d.ts +5 -0
  71. package/dist/rtk-integrity.js +35 -0
  72. package/dist/rtk-integrity.js.map +1 -0
  73. package/dist/rtk-shim.js +22 -9
  74. package/dist/rtk-shim.js.map +1 -1
  75. package/dist/rtk-tools.d.ts +8 -0
  76. package/dist/rtk-tools.js +59 -42
  77. package/dist/rtk-tools.js.map +1 -1
  78. package/dist/server-instructions.js +8 -88
  79. package/dist/server-instructions.js.map +1 -1
  80. package/dist/tool-catalog.d.ts +2 -0
  81. package/dist/tool-catalog.js +76 -23
  82. package/dist/tool-catalog.js.map +1 -1
  83. package/dist/tool-handlers/context-recovery.js +79 -54
  84. package/dist/tool-handlers/context-recovery.js.map +1 -1
  85. package/dist/tool-handlers/large-files.js +600 -44
  86. package/dist/tool-handlers/large-files.js.map +1 -1
  87. package/dist/tool-handlers/memory-diagnostics.js +240 -45
  88. package/dist/tool-handlers/memory-diagnostics.js.map +1 -1
  89. package/dist/tool-handlers/memory.js +167 -22
  90. package/dist/tool-handlers/memory.js.map +1 -1
  91. package/dist/tool-handlers/operations.js +28 -8
  92. package/dist/tool-handlers/operations.js.map +1 -1
  93. package/dist/tool-handlers/requirement-status.d.ts +5 -0
  94. package/dist/tool-handlers/requirement-status.js +267 -0
  95. package/dist/tool-handlers/requirement-status.js.map +1 -0
  96. package/dist/tool-handlers/requirements.js +786 -97
  97. package/dist/tool-handlers/requirements.js.map +1 -1
  98. package/dist/tool-handlers.d.ts +4 -0
  99. package/dist/tool-handlers.js +183 -10
  100. package/dist/tool-handlers.js.map +1 -1
  101. package/dist/tool-output.d.ts +32 -1
  102. package/dist/tool-output.js +31 -12
  103. package/dist/tool-output.js.map +1 -1
  104. package/dist/tool-schemas.d.ts +235 -0
  105. package/dist/tool-schemas.js +98 -26
  106. package/dist/tool-schemas.js.map +1 -1
  107. package/dist/types.d.ts +1 -0
  108. package/docs/capability-matrix.md +23 -17
  109. package/package.json +19 -6
  110. package/skills/vector-mind-autopilot/SKILL.md +82 -23
  111. package/skills/vector-mind-autopilot/references/claude-project-instructions.md +14 -11
  112. package/skills/vector-mind-autopilot/references/universal-system-prompt.md +24 -16
@@ -0,0 +1,117 @@
1
+ import crypto from "node:crypto";
2
+
3
+ export const ADMIN_TOKEN_HEADER = "x-vectormind-admin-token";
4
+
5
+ function normalizeHostname(value) {
6
+ return String(value ?? "")
7
+ .trim()
8
+ .toLowerCase()
9
+ .replace(/^\[|\]$/g, "")
10
+ .replace(/\.$/, "");
11
+ }
12
+
13
+ function effectivePort(protocol, port) {
14
+ if (port) return Number(port);
15
+ return protocol === "https:" ? 443 : 80;
16
+ }
17
+
18
+ function parseRequestHost(hostHeader, protocol = "http") {
19
+ if (!hostHeader) return null;
20
+ const normalizedProtocol = String(protocol).endsWith(":") ? String(protocol) : `${protocol}:`;
21
+ try {
22
+ const url = new URL(`${normalizedProtocol}//${hostHeader}`);
23
+ return {
24
+ hostname: normalizeHostname(url.hostname),
25
+ port: effectivePort(url.protocol, url.port),
26
+ protocol: url.protocol,
27
+ };
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ export function isLoopbackHostname(hostname) {
34
+ const normalized = normalizeHostname(hostname);
35
+ if (normalized === "localhost" || normalized === "::1") return true;
36
+ const match = normalized.match(/^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
37
+ return Boolean(match && match.slice(1).every((part) => Number(part) <= 255));
38
+ }
39
+
40
+ export function isLoopbackAddress(address) {
41
+ const normalized = String(address ?? "").trim().toLowerCase().split("%")[0];
42
+ if (normalized === "::1") return true;
43
+ if (normalized.startsWith("::ffff:")) return isLoopbackHostname(normalized.slice(7));
44
+ return isLoopbackHostname(normalized);
45
+ }
46
+
47
+ export function isAllowedAdminOrigin({ origin, hostHeader, protocol = "http" }) {
48
+ if (!origin) return true;
49
+ const requestHost = parseRequestHost(hostHeader, protocol);
50
+ if (!requestHost) return false;
51
+ try {
52
+ const parsedOrigin = new URL(origin);
53
+ if (parsedOrigin.protocol !== "http:" && parsedOrigin.protocol !== "https:") return false;
54
+ return (
55
+ normalizeHostname(parsedOrigin.hostname) === requestHost.hostname &&
56
+ effectivePort(parsedOrigin.protocol, parsedOrigin.port) === requestHost.port &&
57
+ parsedOrigin.protocol === requestHost.protocol
58
+ );
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ function tokensEqual(providedToken, expectedToken) {
65
+ if (!providedToken || !expectedToken) return false;
66
+ const provided = Buffer.from(String(providedToken));
67
+ const expected = Buffer.from(String(expectedToken));
68
+ return provided.length === expected.length && crypto.timingSafeEqual(provided, expected);
69
+ }
70
+
71
+ export function createAdminSecurityPolicy({ host, port, configuredToken, generateToken } = {}) {
72
+ const normalizedHost = normalizeHostname(host);
73
+ const normalizedPort = Number(port);
74
+ if (!normalizedHost || !Number.isInteger(normalizedPort) || normalizedPort < 1 || normalizedPort > 65535) {
75
+ throw new Error("管理面板监听地址或端口无效。");
76
+ }
77
+
78
+ const mode = isLoopbackHostname(normalizedHost) ? "automatic" : "explicit";
79
+ const explicitToken = String(configuredToken ?? "").trim();
80
+ if (mode === "explicit" && !explicitToken) {
81
+ throw new Error("非回环监听必须设置 VECTORMIND_ADMIN_TOKEN。");
82
+ }
83
+
84
+ const token = explicitToken || (generateToken ? generateToken() : crypto.randomBytes(32).toString("hex"));
85
+ return Object.freeze({ host: normalizedHost, port: normalizedPort, mode, token });
86
+ }
87
+
88
+ export function evaluateAdminRequest({
89
+ policy,
90
+ path,
91
+ origin,
92
+ hostHeader,
93
+ protocol = "http",
94
+ remoteAddress,
95
+ providedToken,
96
+ }) {
97
+ const originAllowed = isAllowedAdminOrigin({ origin, hostHeader, protocol });
98
+ const requestHost = parseRequestHost(hostHeader, protocol);
99
+ const tokenValid = tokensEqual(providedToken, policy.token);
100
+ const publicEndpoint = path === "/config" || path === "/health";
101
+ const automaticSessionAvailable = Boolean(
102
+ policy.mode === "automatic" &&
103
+ originAllowed &&
104
+ requestHost &&
105
+ requestHost.port === policy.port &&
106
+ isLoopbackHostname(requestHost.hostname) &&
107
+ isLoopbackAddress(remoteAddress),
108
+ );
109
+
110
+ return {
111
+ originAllowed,
112
+ tokenValid,
113
+ publicEndpoint,
114
+ authorized: originAllowed && (publicEndpoint || tokenValid),
115
+ exposeSessionToken: path === "/config" && automaticSessionAvailable,
116
+ };
117
+ }
@@ -0,0 +1,113 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ createAdminSecurityPolicy,
6
+ evaluateAdminRequest,
7
+ isAllowedAdminOrigin,
8
+ isLoopbackAddress,
9
+ isLoopbackHostname,
10
+ } from "./security.mjs";
11
+
12
+ test("loopback detection accepts only concrete loopback hosts and addresses", () => {
13
+ assert.equal(isLoopbackHostname("localhost"), true);
14
+ assert.equal(isLoopbackHostname("127.15.2.9"), true);
15
+ assert.equal(isLoopbackHostname("localhost.example.com"), false);
16
+ assert.equal(isLoopbackAddress("::1"), true);
17
+ assert.equal(isLoopbackAddress("::ffff:127.0.0.1"), true);
18
+ assert.equal(isLoopbackAddress("192.168.1.8"), false);
19
+ });
20
+
21
+ test("origin validation requires the browser origin to match the request host", () => {
22
+ assert.equal(
23
+ isAllowedAdminOrigin({ origin: "http://localhost:16860", hostHeader: "localhost:16860", protocol: "http" }),
24
+ true,
25
+ );
26
+ assert.equal(
27
+ isAllowedAdminOrigin({ origin: "http://evil.example:16860", hostHeader: "localhost:16860", protocol: "http" }),
28
+ false,
29
+ );
30
+ });
31
+
32
+ test("loopback mode exposes an automatic session only to a loopback request host", () => {
33
+ const policy = createAdminSecurityPolicy({
34
+ host: "127.0.0.1",
35
+ port: 16860,
36
+ generateToken: () => "automatic-test-token",
37
+ });
38
+ const localConfig = evaluateAdminRequest({
39
+ policy,
40
+ path: "/config",
41
+ hostHeader: "localhost:16860",
42
+ remoteAddress: "::ffff:127.0.0.1",
43
+ });
44
+ const reboundConfig = evaluateAdminRequest({
45
+ policy,
46
+ path: "/config",
47
+ hostHeader: "attacker.example:16860",
48
+ remoteAddress: "127.0.0.1",
49
+ });
50
+
51
+ assert.equal(policy.mode, "automatic");
52
+ assert.equal(localConfig.exposeSessionToken, true);
53
+ assert.equal(reboundConfig.exposeSessionToken, false);
54
+ });
55
+
56
+ test("non-loopback mode requires an explicit token and never exposes it through config", () => {
57
+ assert.throws(
58
+ () => createAdminSecurityPolicy({ host: "0.0.0.0", port: 16860 }),
59
+ /VECTORMIND_ADMIN_TOKEN/,
60
+ );
61
+ const policy = createAdminSecurityPolicy({ host: "0.0.0.0", port: 16860, configuredToken: "explicit-test-token" });
62
+ const configRequest = evaluateAdminRequest({
63
+ policy,
64
+ path: "/config",
65
+ hostHeader: "192.168.1.20:16860",
66
+ remoteAddress: "192.168.1.30",
67
+ });
68
+ const unauthenticatedRequest = evaluateAdminRequest({
69
+ policy,
70
+ path: "/projects",
71
+ hostHeader: "192.168.1.20:16860",
72
+ remoteAddress: "192.168.1.30",
73
+ });
74
+ const authenticatedRequest = evaluateAdminRequest({
75
+ policy,
76
+ path: "/projects",
77
+ hostHeader: "192.168.1.20:16860",
78
+ remoteAddress: "192.168.1.30",
79
+ providedToken: "explicit-test-token",
80
+ });
81
+
82
+ assert.equal(policy.mode, "explicit");
83
+ assert.equal(configRequest.exposeSessionToken, false);
84
+ assert.equal(unauthenticatedRequest.authorized, false);
85
+ assert.equal(authenticatedRequest.authorized, true);
86
+ });
87
+
88
+ test("a missing Origin does not authorize a protected endpoint without a token", () => {
89
+ const policy = createAdminSecurityPolicy({ host: "10.0.0.4", port: 16860, configuredToken: "explicit-test-token" });
90
+ const decision = evaluateAdminRequest({
91
+ policy,
92
+ path: "/projects/discover",
93
+ origin: undefined,
94
+ hostHeader: "10.0.0.4:16860",
95
+ remoteAddress: "10.0.0.5",
96
+ });
97
+ assert.equal(decision.originAllowed, true);
98
+ assert.equal(decision.authorized, false);
99
+ });
100
+
101
+ test("a mismatched Origin is rejected even when the token is valid", () => {
102
+ const policy = createAdminSecurityPolicy({ host: "0.0.0.0", port: 16860, configuredToken: "explicit-test-token" });
103
+ const decision = evaluateAdminRequest({
104
+ policy,
105
+ path: "/projects",
106
+ origin: "http://evil.example:16860",
107
+ hostHeader: "192.168.1.20:16860",
108
+ remoteAddress: "192.168.1.30",
109
+ providedToken: "explicit-test-token",
110
+ });
111
+ assert.equal(decision.originAllowed, false);
112
+ assert.equal(decision.authorized, false);
113
+ });
@@ -0,0 +1,49 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ export function projectPathIdentity(projectPath, platform = process.platform) {
6
+ const raw = String(projectPath ?? "").trim();
7
+ if (!raw) return "";
8
+ const resolved = path.resolve(raw);
9
+ return platform === "win32" ? resolved.toLowerCase() : resolved;
10
+ }
11
+
12
+ export function atomicWriteTextFile(filePath, content, options = {}) {
13
+ const fsImpl = options.fsImpl ?? fs;
14
+ const directory = path.dirname(filePath);
15
+ const tempPath = path.join(
16
+ directory,
17
+ `.${path.basename(filePath)}.${process.pid}.${crypto.randomUUID()}.tmp`,
18
+ );
19
+ let fd = null;
20
+ fsImpl.mkdirSync(directory, { recursive: true });
21
+ try {
22
+ fd = fsImpl.openSync(tempPath, "wx", 0o600);
23
+ fsImpl.writeFileSync(fd, content, "utf8");
24
+ fsImpl.fsyncSync(fd);
25
+ fsImpl.closeSync(fd);
26
+ fd = null;
27
+ fsImpl.renameSync(tempPath, filePath);
28
+ try {
29
+ const directoryFd = fsImpl.openSync(directory, "r");
30
+ try {
31
+ fsImpl.fsyncSync(directoryFd);
32
+ } finally {
33
+ fsImpl.closeSync(directoryFd);
34
+ }
35
+ } catch {
36
+ // Directory fsync is not available on every platform/filesystem.
37
+ }
38
+ } catch (err) {
39
+ if (fd != null) {
40
+ try {
41
+ fsImpl.closeSync(fd);
42
+ } catch {}
43
+ }
44
+ try {
45
+ fsImpl.rmSync(tempPath, { force: true });
46
+ } catch {}
47
+ throw err;
48
+ }
49
+ }
@@ -0,0 +1,36 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import { atomicWriteTextFile, projectPathIdentity } from "./storage.mjs";
8
+
9
+ test("project path identity is case-sensitive except on Windows", () => {
10
+ assert.equal(projectPathIdentity("", "linux"), "");
11
+ assert.notEqual(projectPathIdentity("/tmp/VectorMind", "linux"), projectPathIdentity("/tmp/vectormind", "linux"));
12
+ assert.equal(projectPathIdentity("/tmp/VectorMind", "win32"), projectPathIdentity("/tmp/vectormind", "win32"));
13
+ });
14
+
15
+ test("atomic writes replace the index and preserve the prior file when rename fails", () => {
16
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "vectormind-admin-storage-"));
17
+ const indexFile = path.join(root, "projects.json");
18
+ try {
19
+ fs.writeFileSync(indexFile, "old\n", "utf8");
20
+ atomicWriteTextFile(indexFile, "new\n");
21
+ assert.equal(fs.readFileSync(indexFile, "utf8"), "new\n");
22
+
23
+ const failingFs = Object.create(fs);
24
+ failingFs.renameSync = () => {
25
+ throw new Error("simulated interrupted replace");
26
+ };
27
+ assert.throws(
28
+ () => atomicWriteTextFile(indexFile, "truncated\n", { fsImpl: failingFs }),
29
+ /simulated interrupted replace/,
30
+ );
31
+ assert.equal(fs.readFileSync(indexFile, "utf8"), "new\n");
32
+ assert.deepEqual(fs.readdirSync(root).filter((name) => name.endsWith(".tmp")), []);
33
+ } finally {
34
+ fs.rmSync(root, { recursive: true, force: true });
35
+ }
36
+ });
@@ -0,0 +1,56 @@
1
+ import type Database from "better-sqlite3";
2
+ import type { ChangeLogRow, MemoryItemRow, PendingChangeRow } from "./types.js";
3
+ import { toMemoryItemPreview } from "./memory-recall.js";
4
+ export declare const CHECKPOINT_SNAPSHOT_VERSION = 2;
5
+ export declare const CHECKPOINT_SNAPSHOT_MAX_CHARS = 24000;
6
+ export declare const CHECKPOINT_METADATA_MAX_CHARS = 1200;
7
+ export declare const CHECKPOINT_DECISION_LIMIT = 10;
8
+ export declare const CHECKPOINT_RECENT_CHANGE_LIMIT = 10;
9
+ export declare const CHECKPOINT_CHANGE_FILES_LIMIT = 20;
10
+ export declare const CHECKPOINT_MEMORY_KINDS: readonly ["requirement", "change_intent", "note", "decision", "memory_compaction", "project_summary", "convention", "fix_pattern"];
11
+ export declare const LEGACY_CHECKPOINT_MEMORY_KINDS: readonly ["requirement", "change_intent", "note", "decision", "memory_compaction", "project_summary", "convention"];
12
+ type JsonRecord = Record<string, unknown>;
13
+ export type CheckpointCollectionState = {
14
+ total: number;
15
+ returned: number;
16
+ truncated: boolean;
17
+ window_truncated?: boolean;
18
+ total_is_lower_bound?: boolean;
19
+ };
20
+ export type CheckpointSnapshotBasis = {
21
+ snapshot_version: number;
22
+ recent_limit: number;
23
+ decision_limit: number;
24
+ recent_change_limit: number;
25
+ pending_limit: number;
26
+ included_kinds: string[];
27
+ };
28
+ export declare function boundedMetadataJson(raw: string | null | undefined, options?: {
29
+ maxChars?: number;
30
+ omitKeys?: readonly string[];
31
+ }): string | null;
32
+ export declare function toCheckpointSnapshotMemoryPreview(row: MemoryItemRow): JsonRecord;
33
+ export declare function toCheckpointRecordPreview(row: MemoryItemRow, includeContent: boolean, previewChars: number, contentMaxChars: number): ReturnType<typeof toMemoryItemPreview>;
34
+ export declare function toCheckpointChangePreview(change: ChangeLogRow): JsonRecord;
35
+ export declare function checkpointPendingRows(rows: PendingChangeRow[]): JsonRecord[];
36
+ export declare function checkpointSnapshotBasis(recentLimit: number, pendingLimit: number): CheckpointSnapshotBasis;
37
+ export declare function getVisibleCheckpointMemory(db: Database.Database, limit: number, excludeIds?: readonly number[], includedKinds?: readonly string[]): {
38
+ rows: MemoryItemRow[];
39
+ truncated: boolean;
40
+ };
41
+ export declare function getVisibleCheckpointDecisions(db: Database.Database, limit: number): {
42
+ rows: MemoryItemRow[];
43
+ truncated: boolean;
44
+ };
45
+ export declare function readCheckpointBasis(value: unknown, fallback: CheckpointSnapshotBasis): CheckpointSnapshotBasis;
46
+ export declare function isCheckpointSnapshot(value: unknown): value is JsonRecord;
47
+ export declare function normalizeCheckpointSnapshot(raw: JsonRecord, options?: {
48
+ maxChars?: number;
49
+ fallbackBasis?: CheckpointSnapshotBasis;
50
+ }): JsonRecord;
51
+ export declare function checkpointSnapshotFromMetadata(metadataJson: string | null | undefined, fallbackBasis: CheckpointSnapshotBasis): {
52
+ valid: boolean;
53
+ snapshot: JsonRecord;
54
+ metadata: JsonRecord;
55
+ };
56
+ export {};