@wrongstack/core 0.302.2 → 0.305.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/README.md +1 -1
- package/dist/chronicle/index.js +117 -30
- package/dist/chronicle/project-server.js +12 -5
- package/dist/coordination/agents/index.js +4645 -3448
- package/dist/coordination/agents/project-agent-auto-optimize.d.ts +116 -0
- package/dist/coordination/agents/project-agent-capture-window.d.ts +29 -0
- package/dist/coordination/agents/project-agent-config-io.d.ts +11 -0
- package/dist/coordination/agents/project-agent-consolidation.d.ts +34 -2
- package/dist/coordination/agents/project-agent-directive-outcome.d.ts +57 -0
- package/dist/coordination/agents/project-agent-files.d.ts +12 -3
- package/dist/coordination/agents/project-agent-identity-types.d.ts +4 -0
- package/dist/coordination/agents/project-agent-identity.d.ts +38 -11
- package/dist/coordination/agents/project-agent-learning-entries.d.ts +8 -2
- package/dist/coordination/agents/project-agent-learning-policy.d.ts +22 -1
- package/dist/coordination/agents/project-agent-learning-structured.d.ts +72 -1
- package/dist/coordination/agents/project-agent-optimizer.d.ts +49 -0
- package/dist/coordination/agents/project-agent-quarantine.d.ts +63 -0
- package/dist/coordination/agents/project-agent-skill-layer.d.ts +146 -0
- package/dist/coordination/agents/role-skills.d.ts +11 -1
- package/dist/coordination/agents/types.d.ts +10 -2
- package/dist/coordination/director-prompts.d.ts +19 -6
- package/dist/coordination/director-tools.d.ts +2 -2
- package/dist/coordination/fleet.d.ts +0 -6
- package/dist/coordination/index.d.ts +1 -1
- package/dist/coordination/index.js +5746 -4326
- package/dist/coordination/mail-tools.d.ts +1 -1
- package/dist/core/agent-types.d.ts +4 -2
- package/dist/core/agent.d.ts +1 -0
- package/dist/core/context.d.ts +19 -0
- package/dist/core/conversation-state.d.ts +14 -0
- package/dist/core/fallback-profile-manager.d.ts +70 -2
- package/dist/core/index.js +317 -108
- package/dist/core/system-prompt-blocks.d.ts +1 -1
- package/dist/core/system-prompt-builder.d.ts +13 -1
- package/dist/core/system-prompt-glossary.d.ts +73 -0
- package/dist/core/system-prompt-memory-skills.d.ts +2 -2
- package/dist/defaults/index.js +1635 -1120
- package/dist/execution/council-orchestrator.d.ts +3 -13
- package/dist/execution/index.js +3136 -2706
- package/dist/execution/one-shot-llm.d.ts +5 -0
- package/dist/goal/index.js +7 -0
- package/dist/hq/index.js +17 -7
- package/dist/hq/protocol/kanban.d.ts +21 -0
- package/dist/hq/protocol.js +5 -1
- package/dist/hq/redaction.d.ts +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5502 -3483
- package/dist/infrastructure/index.js +247 -122
- package/dist/kernel/events/agent-events.d.ts +28 -0
- package/dist/plugin/index.js +111 -7
- package/dist/registry/index.js +11 -0
- package/dist/registry/tool-registry.d.ts +8 -0
- package/dist/replay/hash.d.ts +9 -0
- package/dist/replay/index.js +14 -4
- package/dist/replay/replay-provider-runner.d.ts +31 -1
- package/dist/security/index.js +92 -21
- package/dist/security/kanban-boundary.d.ts +5 -1
- package/dist/security/secret-vault.d.ts +2 -0
- package/dist/session-catalog/index.js +86 -10
- package/dist/session-catalog/project-server.js +100 -17
- package/dist/session-catalog/protocol.d.ts +20 -4
- package/dist/session-catalog/store.d.ts +19 -3
- package/dist/storage/index.js +305 -69
- package/dist/storage/memory-consolidator.d.ts +4 -2
- package/dist/storage/plan-store.d.ts +1 -1
- package/dist/storage/session-resume-validation.d.ts +24 -0
- package/dist/storage/session-store/directory-scan.d.ts +5 -1
- package/dist/storage/session-store/fork-session.d.ts +13 -1
- package/dist/storage/session-store/load-cache.d.ts +11 -0
- package/dist/storage/session-store/prune-helpers.d.ts +5 -0
- package/dist/storage/session-store.d.ts +18 -0
- package/dist/tasking/index.js +5 -0
- package/dist/tools/index.js +1151 -831
- package/dist/types/config/mcp-features.d.ts +31 -1
- package/dist/types/config/root.d.ts +23 -1
- package/dist/types/config/skills-fleet-brain.d.ts +34 -0
- package/dist/types/config/tools.d.ts +22 -0
- package/dist/types/config/ui.d.ts +14 -0
- package/dist/types/config.d.ts +1 -0
- package/dist/types/default-config.d.ts +1 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +23 -0
- package/dist/types/multi-agent.d.ts +7 -0
- package/dist/types/session.d.ts +9 -1
- package/dist/types/task-graph.d.ts +2 -0
- package/dist/types/tool-executor.d.ts +2 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +217 -76
- package/dist/utils/project-state-guard.d.ts +21 -0
- package/dist/utils/session-scoped-path.d.ts +17 -0
- package/dist/utils/todos-format.d.ts +20 -0
- package/instructions/leader-after-task.md +3 -4
- package/instructions/system-lite.md +17 -14
- package/instructions/system-pro.md +29 -30
- package/instructions/system.md +29 -28
- package/package.json +3 -3
- package/skills/wrongstack-kanban/SKILL.md +95 -93
|
@@ -30,9 +30,9 @@ var init_protocol = __esm({
|
|
|
30
30
|
// src/session-catalog/endpoint.ts
|
|
31
31
|
import { createHash as createHash2 } from "node:crypto";
|
|
32
32
|
import * as os3 from "node:os";
|
|
33
|
-
import * as
|
|
33
|
+
import * as path9 from "node:path";
|
|
34
34
|
function normalizedPath(value) {
|
|
35
|
-
const resolved =
|
|
35
|
+
const resolved = path9.resolve(value);
|
|
36
36
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
37
37
|
}
|
|
38
38
|
function sessionCatalogProjectServerKey(projectDir) {
|
|
@@ -43,10 +43,10 @@ function sessionCatalogProjectServerEndpoint(projectDir) {
|
|
|
43
43
|
if (process.platform === "win32") {
|
|
44
44
|
return `\\\\.\\pipe\\wrongstack-session-catalog-v${SESSION_CATALOG_PROTOCOL_VERSION}-${key}`;
|
|
45
45
|
}
|
|
46
|
-
return
|
|
46
|
+
return path9.join(os3.tmpdir(), `wssc-v${SESSION_CATALOG_PROTOCOL_VERSION}`, `${key}.sock`);
|
|
47
47
|
}
|
|
48
48
|
function sessionCatalogProjectServerMetadataPath(projectDir) {
|
|
49
|
-
return
|
|
49
|
+
return path9.join(projectDir, SESSION_CATALOG_METADATA_FILE);
|
|
50
50
|
}
|
|
51
51
|
var SESSION_CATALOG_METADATA_FILE;
|
|
52
52
|
var init_endpoint = __esm({
|
|
@@ -59,9 +59,9 @@ var init_endpoint = __esm({
|
|
|
59
59
|
|
|
60
60
|
// src/session-catalog/client.ts
|
|
61
61
|
import { spawn } from "node:child_process";
|
|
62
|
-
import * as
|
|
62
|
+
import * as fs7 from "node:fs";
|
|
63
63
|
import * as net from "node:net";
|
|
64
|
-
import * as
|
|
64
|
+
import * as path10 from "node:path";
|
|
65
65
|
import { fileURLToPath } from "node:url";
|
|
66
66
|
function locateServer(moduleUrl, exists) {
|
|
67
67
|
for (const candidate of [
|
|
@@ -78,22 +78,22 @@ function locateServer(moduleUrl, exists) {
|
|
|
78
78
|
}
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
81
|
-
function resolveSessionCatalogDaemonAvailability(moduleUrl = import.meta.url, exists =
|
|
81
|
+
function resolveSessionCatalogDaemonAvailability(moduleUrl = import.meta.url, exists = fs7.existsSync) {
|
|
82
82
|
if (process.env["WRONGSTACK_SESSION_CATALOG_INLINE"] || process.env["WRONGSTACK_SESSION_CATALOG_SERVER"] === "0")
|
|
83
83
|
return { kind: "inline-requested" };
|
|
84
84
|
const url = locateServer(moduleUrl, exists);
|
|
85
85
|
return url ? { kind: "available", url } : { kind: "missing-build" };
|
|
86
86
|
}
|
|
87
|
-
function resolveSessionCatalogProjectServerUrl(moduleUrl = import.meta.url, exists =
|
|
87
|
+
function resolveSessionCatalogProjectServerUrl(moduleUrl = import.meta.url, exists = fs7.existsSync) {
|
|
88
88
|
const availability = resolveSessionCatalogDaemonAvailability(moduleUrl, exists);
|
|
89
89
|
return availability.kind === "available" ? availability.url : null;
|
|
90
90
|
}
|
|
91
91
|
function normalize2(value) {
|
|
92
|
-
const resolved =
|
|
92
|
+
const resolved = path10.resolve(value);
|
|
93
93
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
94
94
|
}
|
|
95
95
|
function delay(ms) {
|
|
96
|
-
return new Promise((
|
|
96
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
97
97
|
}
|
|
98
98
|
var CONNECT_TIMEOUT_MS, START_TIMEOUT_MS, CALL_TIMEOUT_MS, MAX_PENDING_REQUESTS, MAX_EVENT_LISTENERS, SessionCatalogProjectClient;
|
|
99
99
|
var init_client = __esm({
|
|
@@ -172,7 +172,7 @@ var init_client = __esm({
|
|
|
172
172
|
if (result.stopped) {
|
|
173
173
|
const metadataPath = sessionCatalogProjectServerMetadataPath(this.options.projectDir);
|
|
174
174
|
const deadline = Date.now() + 5e3;
|
|
175
|
-
while (
|
|
175
|
+
while (fs7.existsSync(metadataPath) && Date.now() < deadline) await delay(20);
|
|
176
176
|
if (result.pid && result.pid !== process.pid) {
|
|
177
177
|
while (Date.now() < deadline) {
|
|
178
178
|
try {
|
|
@@ -194,8 +194,8 @@ var init_client = __esm({
|
|
|
194
194
|
this.socket = null;
|
|
195
195
|
this.info = null;
|
|
196
196
|
if (socket && !socket.destroyed)
|
|
197
|
-
await new Promise((
|
|
198
|
-
socket.once("close",
|
|
197
|
+
await new Promise((resolve9) => {
|
|
198
|
+
socket.once("close", resolve9);
|
|
199
199
|
socket.end();
|
|
200
200
|
});
|
|
201
201
|
}
|
|
@@ -233,7 +233,7 @@ var init_client = __esm({
|
|
|
233
233
|
this.info = null;
|
|
234
234
|
this.authToken = void 0;
|
|
235
235
|
this.buffer = "";
|
|
236
|
-
return new Promise((
|
|
236
|
+
return new Promise((resolve9, reject) => {
|
|
237
237
|
const socket = net.createConnection(this.endpoint);
|
|
238
238
|
this.socket = socket;
|
|
239
239
|
socket.setEncoding("utf8");
|
|
@@ -246,7 +246,7 @@ var init_client = __esm({
|
|
|
246
246
|
clearTimeout(timer);
|
|
247
247
|
this.connectResolve = null;
|
|
248
248
|
this.connectReject = null;
|
|
249
|
-
|
|
249
|
+
resolve9();
|
|
250
250
|
};
|
|
251
251
|
this.connectReject = (error) => {
|
|
252
252
|
clearTimeout(timer);
|
|
@@ -265,7 +265,7 @@ var init_client = __esm({
|
|
|
265
265
|
if (this.authToken === void 0) {
|
|
266
266
|
try {
|
|
267
267
|
const parsed = JSON.parse(
|
|
268
|
-
|
|
268
|
+
fs7.readFileSync(sessionCatalogProjectServerMetadataPath(this.options.projectDir), "utf8")
|
|
269
269
|
);
|
|
270
270
|
if (typeof parsed.authToken === "string" && parsed.authToken)
|
|
271
271
|
this.authToken = parsed.authToken;
|
|
@@ -291,7 +291,7 @@ var init_client = __esm({
|
|
|
291
291
|
});
|
|
292
292
|
if (encoded.length > SESSION_CATALOG_MAX_FRAME_CHARS)
|
|
293
293
|
return Promise.reject(new Error("Session Catalog request exceeded frame limit"));
|
|
294
|
-
return new Promise((
|
|
294
|
+
return new Promise((resolve9, reject) => {
|
|
295
295
|
const timer = setTimeout(() => {
|
|
296
296
|
const pending = this.pending.get(id);
|
|
297
297
|
if (!pending) return;
|
|
@@ -303,7 +303,7 @@ var init_client = __esm({
|
|
|
303
303
|
);
|
|
304
304
|
}, timeoutMs);
|
|
305
305
|
timer.unref?.();
|
|
306
|
-
this.pending.set(id, { resolve:
|
|
306
|
+
this.pending.set(id, { resolve: resolve9, reject, timer });
|
|
307
307
|
socket.write(encoded);
|
|
308
308
|
});
|
|
309
309
|
}
|
|
@@ -420,10 +420,10 @@ __export(registry_exports, {
|
|
|
420
420
|
hasProjectSessionRegistry: () => hasProjectSessionRegistry
|
|
421
421
|
});
|
|
422
422
|
import { randomUUID } from "node:crypto";
|
|
423
|
-
import * as
|
|
424
|
-
import * as
|
|
423
|
+
import * as fs8 from "node:fs/promises";
|
|
424
|
+
import * as path11 from "node:path";
|
|
425
425
|
function getProjectSessionRegistry(globalRoot) {
|
|
426
|
-
const key = globalRoot !== void 0 ?
|
|
426
|
+
const key = globalRoot !== void 0 ? path11.resolve(globalRoot) : lastRegistryKey;
|
|
427
427
|
if (!key)
|
|
428
428
|
throw new Error("SessionRegistry not initialized. Call getSessionRegistry(globalRoot) first.");
|
|
429
429
|
let registry = registries.get(key);
|
|
@@ -446,7 +446,7 @@ function getProjectSessionRegistry(globalRoot) {
|
|
|
446
446
|
}
|
|
447
447
|
function hasProjectSessionRegistry(globalRoot) {
|
|
448
448
|
if (globalRoot === void 0) return registries.size > 0;
|
|
449
|
-
return registries.has(
|
|
449
|
+
return registries.has(path11.resolve(globalRoot));
|
|
450
450
|
}
|
|
451
451
|
var HEARTBEAT_INTERVAL_MS, AGENT_WRITE_THROTTLE_MS, MAX_PROJECT_CLIENTS, MAX_GLOBAL_ROOTS, ProjectSessionRegistry, registries, lastRegistryKey;
|
|
452
452
|
var init_registry = __esm({
|
|
@@ -472,7 +472,7 @@ var init_registry = __esm({
|
|
|
472
472
|
agentTimer;
|
|
473
473
|
lastAgentWriteAt = 0;
|
|
474
474
|
bindingKey(projectDir) {
|
|
475
|
-
const resolved =
|
|
475
|
+
const resolved = path11.resolve(projectDir);
|
|
476
476
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
477
477
|
}
|
|
478
478
|
async closeBinding(binding) {
|
|
@@ -482,7 +482,7 @@ var init_registry = __esm({
|
|
|
482
482
|
await binding.client.close();
|
|
483
483
|
}
|
|
484
484
|
binding(projectSlug2, projectRoot) {
|
|
485
|
-
const projectDir =
|
|
485
|
+
const projectDir = path11.join(this.globalRoot, "projects", projectSlug2);
|
|
486
486
|
const key = this.bindingKey(projectDir);
|
|
487
487
|
let binding = this.clients.get(key);
|
|
488
488
|
if (!binding) {
|
|
@@ -497,7 +497,7 @@ var init_registry = __esm({
|
|
|
497
497
|
}
|
|
498
498
|
binding = {
|
|
499
499
|
projectDir,
|
|
500
|
-
projectRoot:
|
|
500
|
+
projectRoot: path11.resolve(projectRoot),
|
|
501
501
|
client: new SessionCatalogProjectClient({ projectDir, projectRoot })
|
|
502
502
|
};
|
|
503
503
|
this.clients.set(key, binding);
|
|
@@ -658,20 +658,20 @@ var init_registry = __esm({
|
|
|
658
658
|
}
|
|
659
659
|
}
|
|
660
660
|
async list() {
|
|
661
|
-
const projectsDir =
|
|
661
|
+
const projectsDir = path11.join(this.globalRoot, "projects");
|
|
662
662
|
let directories = [];
|
|
663
663
|
try {
|
|
664
|
-
directories = (await
|
|
664
|
+
directories = (await fs8.readdir(projectsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).slice(0, 1e3).map((entry) => entry.name);
|
|
665
665
|
} catch {
|
|
666
666
|
return [];
|
|
667
667
|
}
|
|
668
668
|
const snapshots = await Promise.all(
|
|
669
669
|
directories.map(async (slug) => {
|
|
670
|
-
const projectDir =
|
|
670
|
+
const projectDir = path11.join(projectsDir, slug);
|
|
671
671
|
let client;
|
|
672
672
|
try {
|
|
673
673
|
const metadata = JSON.parse(
|
|
674
|
-
await
|
|
674
|
+
await fs8.readFile(sessionCatalogProjectServerMetadataPath(projectDir), "utf8")
|
|
675
675
|
);
|
|
676
676
|
if (typeof metadata.projectRoot !== "string" || !metadata.projectRoot) return [];
|
|
677
677
|
client = new SessionCatalogProjectClient({
|
|
@@ -690,14 +690,14 @@ var init_registry = __esm({
|
|
|
690
690
|
}
|
|
691
691
|
async listByProject(projectSlug2) {
|
|
692
692
|
const current = this.current;
|
|
693
|
-
if (current &&
|
|
693
|
+
if (current && path11.basename(current.binding.projectDir) === projectSlug2) {
|
|
694
694
|
return current.binding.client.call("list_live", {}).catch(() => []);
|
|
695
695
|
}
|
|
696
|
-
const projectDir =
|
|
696
|
+
const projectDir = path11.join(this.globalRoot, "projects", projectSlug2);
|
|
697
697
|
let client;
|
|
698
698
|
try {
|
|
699
699
|
const metadata = JSON.parse(
|
|
700
|
-
await
|
|
700
|
+
await fs8.readFile(sessionCatalogProjectServerMetadataPath(projectDir), "utf8")
|
|
701
701
|
);
|
|
702
702
|
if (typeof metadata.projectRoot !== "string") return [];
|
|
703
703
|
client = new SessionCatalogProjectClient({ projectDir, projectRoot: metadata.projectRoot });
|
|
@@ -719,7 +719,7 @@ var init_registry = __esm({
|
|
|
719
719
|
});
|
|
720
720
|
}
|
|
721
721
|
get registryPath() {
|
|
722
|
-
return
|
|
722
|
+
return path11.join(this.globalRoot, "projects");
|
|
723
723
|
}
|
|
724
724
|
async dispose() {
|
|
725
725
|
await this.unregister().catch(() => void 0);
|
|
@@ -2192,9 +2192,9 @@ function createContextManagerTool(opts = {}) {
|
|
|
2192
2192
|
var contextManagerTool = createContextManagerTool();
|
|
2193
2193
|
|
|
2194
2194
|
// src/boot.ts
|
|
2195
|
-
import * as
|
|
2195
|
+
import * as fs9 from "node:fs/promises";
|
|
2196
2196
|
import * as os4 from "node:os";
|
|
2197
|
-
import * as
|
|
2197
|
+
import * as path12 from "node:path";
|
|
2198
2198
|
|
|
2199
2199
|
// src/security/secret-vault.ts
|
|
2200
2200
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
|
|
@@ -2571,23 +2571,21 @@ function unwrapDataKey(buf, keyFile) {
|
|
|
2571
2571
|
});
|
|
2572
2572
|
}
|
|
2573
2573
|
}
|
|
2574
|
-
function
|
|
2575
|
-
if (process.platform === "win32") return;
|
|
2574
|
+
function keyFileNeedsHardening(keyFile, opts) {
|
|
2575
|
+
if (process.platform === "win32") return false;
|
|
2576
2576
|
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
2577
2577
|
try {
|
|
2578
|
-
const
|
|
2579
|
-
const actualMode =
|
|
2578
|
+
const stat5 = fs2.statSync(keyFile);
|
|
2579
|
+
const actualMode = stat5.mode & 511;
|
|
2580
2580
|
if (actualMode !== KEY_FILE_MODE) {
|
|
2581
|
-
void restrictFilePermissions(keyFile, {
|
|
2582
|
-
label: "secret-vault",
|
|
2583
|
-
warn
|
|
2584
|
-
}).catch(() => void 0);
|
|
2585
2581
|
warn(
|
|
2586
2582
|
`Key file ${keyFile} has mode ${actualMode.toString(8)} \u2014 expected ${KEY_FILE_MODE.toString(8)}. Hardening\u2026`
|
|
2587
2583
|
);
|
|
2584
|
+
return true;
|
|
2588
2585
|
}
|
|
2589
2586
|
} catch {
|
|
2590
2587
|
}
|
|
2588
|
+
return false;
|
|
2591
2589
|
}
|
|
2592
2590
|
function writeKeyFileAtomicSync(keyFile, content) {
|
|
2593
2591
|
const tmp = `${keyFile}.${randomBytes(4).toString("hex")}.tmp`;
|
|
@@ -2653,6 +2651,14 @@ var DefaultSecretVault = class {
|
|
|
2653
2651
|
}).catch(() => void 0);
|
|
2654
2652
|
this.pendingHardening.push(p);
|
|
2655
2653
|
}
|
|
2654
|
+
/** Detect and schedule repair of a pre-existing POSIX key with loose mode bits. */
|
|
2655
|
+
checkKeyFilePermissions() {
|
|
2656
|
+
if (keyFileNeedsHardening(this.keyFile, {
|
|
2657
|
+
warn: (msg) => this.logWarn(msg)
|
|
2658
|
+
})) {
|
|
2659
|
+
this.scheduleKeyHardening();
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2656
2662
|
/** Flush all pending key-file hardening promises. */
|
|
2657
2663
|
flushHardening() {
|
|
2658
2664
|
if (this.pendingHardening.length === 0) return Promise.resolve();
|
|
@@ -2740,7 +2746,7 @@ var DefaultSecretVault = class {
|
|
|
2740
2746
|
writeKeyFileAtomicSync(this.keyFile, keyFileBuf);
|
|
2741
2747
|
}
|
|
2742
2748
|
this.scheduleKeyHardening();
|
|
2743
|
-
|
|
2749
|
+
this.checkKeyFilePermissions();
|
|
2744
2750
|
this.key = newKey;
|
|
2745
2751
|
this._keyVersion = newVersion;
|
|
2746
2752
|
return { oldVersion, newVersion };
|
|
@@ -2756,12 +2762,9 @@ var DefaultSecretVault = class {
|
|
|
2756
2762
|
const passphrase = getVaultPassphrase();
|
|
2757
2763
|
if (!passphrase || !this.key) return;
|
|
2758
2764
|
try {
|
|
2759
|
-
writeKeyFileAtomicSync(
|
|
2760
|
-
this.keyFile,
|
|
2761
|
-
wrapDataKey(this.key, this._keyVersion, passphrase)
|
|
2762
|
-
);
|
|
2765
|
+
writeKeyFileAtomicSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase));
|
|
2763
2766
|
this.scheduleKeyHardening();
|
|
2764
|
-
|
|
2767
|
+
this.checkKeyFilePermissions();
|
|
2765
2768
|
} catch {
|
|
2766
2769
|
}
|
|
2767
2770
|
}
|
|
@@ -2773,13 +2776,13 @@ var DefaultSecretVault = class {
|
|
|
2773
2776
|
const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
|
|
2774
2777
|
this.key = key2;
|
|
2775
2778
|
this._keyVersion = version;
|
|
2776
|
-
|
|
2779
|
+
this.checkKeyFilePermissions();
|
|
2777
2780
|
return this.key;
|
|
2778
2781
|
}
|
|
2779
2782
|
if (buf.length === KEY_BYTES) {
|
|
2780
2783
|
this.key = buf;
|
|
2781
2784
|
this._keyVersion = 1;
|
|
2782
|
-
|
|
2785
|
+
this.checkKeyFilePermissions();
|
|
2783
2786
|
this.migrateToWrappedIfPassphrase();
|
|
2784
2787
|
return this.key;
|
|
2785
2788
|
}
|
|
@@ -2803,7 +2806,7 @@ var DefaultSecretVault = class {
|
|
|
2803
2806
|
}
|
|
2804
2807
|
this.key = Buffer.from(key2);
|
|
2805
2808
|
this._keyVersion = version;
|
|
2806
|
-
|
|
2809
|
+
this.checkKeyFilePermissions();
|
|
2807
2810
|
this.migrateToWrappedIfPassphrase();
|
|
2808
2811
|
return this.key;
|
|
2809
2812
|
}
|
|
@@ -2830,13 +2833,13 @@ var DefaultSecretVault = class {
|
|
|
2830
2833
|
const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
|
|
2831
2834
|
this.key = winnerKey;
|
|
2832
2835
|
this._keyVersion = version;
|
|
2833
|
-
|
|
2836
|
+
this.checkKeyFilePermissions();
|
|
2834
2837
|
return this.key;
|
|
2835
2838
|
}
|
|
2836
2839
|
if (buf.length === KEY_BYTES) {
|
|
2837
2840
|
this.key = buf;
|
|
2838
2841
|
this._keyVersion = 1;
|
|
2839
|
-
|
|
2842
|
+
this.checkKeyFilePermissions();
|
|
2840
2843
|
return this.key;
|
|
2841
2844
|
}
|
|
2842
2845
|
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
@@ -2852,7 +2855,7 @@ var DefaultSecretVault = class {
|
|
|
2852
2855
|
const winnerKey = buf.subarray(KEY_FILE_MAGIC.length + 1);
|
|
2853
2856
|
this.key = Buffer.from(winnerKey);
|
|
2854
2857
|
this._keyVersion = version;
|
|
2855
|
-
|
|
2858
|
+
this.checkKeyFilePermissions();
|
|
2856
2859
|
return this.key;
|
|
2857
2860
|
}
|
|
2858
2861
|
throw new ConfigError({
|
|
@@ -3190,6 +3193,9 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
|
3190
3193
|
disabledTools: Object.freeze([]),
|
|
3191
3194
|
autoExtendLimit: true,
|
|
3192
3195
|
restrictToProjectRoot: true,
|
|
3196
|
+
// Off by default: the board is a record of the work, not a permit for it.
|
|
3197
|
+
// See ToolsConfig.kanbanGovernance for what turning it on costs and gates.
|
|
3198
|
+
kanbanGovernance: false,
|
|
3193
3199
|
loopDetection: Object.freeze({
|
|
3194
3200
|
mode: "steer-then-cut",
|
|
3195
3201
|
steerThreshold: 3,
|
|
@@ -3247,6 +3253,7 @@ var CONFIG_BEHAVIOR_DEFAULTS = {
|
|
|
3247
3253
|
disabledTools: DEFAULT_TOOLS_CONFIG.disabledTools,
|
|
3248
3254
|
autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit,
|
|
3249
3255
|
restrictToProjectRoot: DEFAULT_TOOLS_CONFIG.restrictToProjectRoot,
|
|
3256
|
+
kanbanGovernance: DEFAULT_TOOLS_CONFIG.kanbanGovernance,
|
|
3250
3257
|
loopDetection: DEFAULT_TOOLS_CONFIG.loopDetection
|
|
3251
3258
|
},
|
|
3252
3259
|
log: { level: "info" },
|
|
@@ -3631,10 +3638,18 @@ var IN_PROJECT_DENIED_PATHS = [
|
|
|
3631
3638
|
{
|
|
3632
3639
|
path: "tools.restrictToProjectRoot",
|
|
3633
3640
|
reason: "The other half of the filesystem confinement switch."
|
|
3641
|
+
},
|
|
3642
|
+
{
|
|
3643
|
+
// Denied for the direction that loosens: the flag defaults to false, so a
|
|
3644
|
+
// repo can only ever use it to turn OFF a gate the user deliberately
|
|
3645
|
+
// enabled. Same class as `tools.restrictToProjectRoot` — a control the
|
|
3646
|
+
// operator owns, not the checked-out repository.
|
|
3647
|
+
path: "tools.kanbanGovernance",
|
|
3648
|
+
reason: "Repo-committed config could disable a Kanban governance gate the operator switched on, letting product mutations run outside any managed card."
|
|
3634
3649
|
}
|
|
3635
3650
|
];
|
|
3636
|
-
function deleteNestedPath(target,
|
|
3637
|
-
const segments =
|
|
3651
|
+
function deleteNestedPath(target, path13) {
|
|
3652
|
+
const segments = path13.split(".");
|
|
3638
3653
|
const last = segments[segments.length - 1];
|
|
3639
3654
|
if (last === void 0) return false;
|
|
3640
3655
|
let cursor = target;
|
|
@@ -3691,16 +3706,16 @@ function assertInProjectAllowListComplete() {
|
|
|
3691
3706
|
);
|
|
3692
3707
|
}
|
|
3693
3708
|
const orphanedPaths = IN_PROJECT_DENIED_PATHS.filter(
|
|
3694
|
-
({ path:
|
|
3695
|
-
).map(({ path:
|
|
3709
|
+
({ path: path13 }) => !IN_PROJECT_ALLOWED_KEYS.has(path13.split(".")[0] ?? "")
|
|
3710
|
+
).map(({ path: path13 }) => path13);
|
|
3696
3711
|
if (orphanedPaths.length > 0) {
|
|
3697
3712
|
problems.push(
|
|
3698
3713
|
`IN_PROJECT_DENIED_PATHS entr(ies) whose top-level parent is not allowed: ` + orphanedPaths.join(", ") + ". The parent is already stripped wholesale, so the nested denial is dead \u2014 remove it."
|
|
3699
3714
|
);
|
|
3700
3715
|
}
|
|
3701
3716
|
const malformedPaths = IN_PROJECT_DENIED_PATHS.filter(
|
|
3702
|
-
({ path:
|
|
3703
|
-
).map(({ path:
|
|
3717
|
+
({ path: path13 }) => path13.split(".").length < 2 || path13.split(".").some((s) => s.length === 0)
|
|
3718
|
+
).map(({ path: path13 }) => path13);
|
|
3704
3719
|
if (malformedPaths.length > 0) {
|
|
3705
3720
|
problems.push(
|
|
3706
3721
|
`IN_PROJECT_DENIED_PATHS entr(ies) are not dotted nested paths: ` + malformedPaths.join(", ") + ". Top-level keys belong in KNOWN_DENIED_IN_PROJECT instead."
|
|
@@ -3728,8 +3743,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
|
|
|
3728
3743
|
}
|
|
3729
3744
|
stripped.push(k);
|
|
3730
3745
|
}
|
|
3731
|
-
for (const { path:
|
|
3732
|
-
if (deleteNestedPath(out,
|
|
3746
|
+
for (const { path: path13 } of IN_PROJECT_DENIED_PATHS) {
|
|
3747
|
+
if (deleteNestedPath(out, path13)) stripped.push(path13);
|
|
3733
3748
|
}
|
|
3734
3749
|
if (stripped.length > 0) {
|
|
3735
3750
|
warn(
|
|
@@ -4338,8 +4353,8 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
|
4338
4353
|
const t0 = Date.now();
|
|
4339
4354
|
let mtimeMs = null;
|
|
4340
4355
|
try {
|
|
4341
|
-
const
|
|
4342
|
-
mtimeMs =
|
|
4356
|
+
const stat5 = await fs5.stat(file);
|
|
4357
|
+
mtimeMs = stat5.mtimeMs;
|
|
4343
4358
|
const cached = this.jsonCache.get(file);
|
|
4344
4359
|
if (cached && cached.mtimeMs === mtimeMs) {
|
|
4345
4360
|
return structuredClone(cached.value);
|
|
@@ -4630,6 +4645,115 @@ function isAlreadyExistsError(error) {
|
|
|
4630
4645
|
return error?.code === "EEXIST";
|
|
4631
4646
|
}
|
|
4632
4647
|
|
|
4648
|
+
// src/utils/project-state-guard.ts
|
|
4649
|
+
import * as fs6 from "node:fs";
|
|
4650
|
+
import * as fsp3 from "node:fs/promises";
|
|
4651
|
+
import * as path8 from "node:path";
|
|
4652
|
+
var PROJECT_STATE_DIRECTORY = ".wrongstack";
|
|
4653
|
+
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
4654
|
+
var REPAIR_DEBOUNCE_MS = 25;
|
|
4655
|
+
var ACTIVE_GUARD_KEY = /* @__PURE__ */ Symbol.for("wrongstack.activeProjectStateGuard");
|
|
4656
|
+
var globalScope = globalThis;
|
|
4657
|
+
globalScope[ACTIVE_GUARD_KEY] ??= {};
|
|
4658
|
+
var activeGuard = globalScope[ACTIVE_GUARD_KEY];
|
|
4659
|
+
function normalizedRoot(root) {
|
|
4660
|
+
const resolved = path8.resolve(root);
|
|
4661
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
4662
|
+
}
|
|
4663
|
+
function concernsProjectState(filename) {
|
|
4664
|
+
if (filename === null) return true;
|
|
4665
|
+
const firstSegment = String(filename).replaceAll("\\", "/").split("/")[0];
|
|
4666
|
+
return firstSegment?.toLowerCase() === PROJECT_STATE_DIRECTORY;
|
|
4667
|
+
}
|
|
4668
|
+
async function startProjectStateGuard(projectRoot, options = {}) {
|
|
4669
|
+
const root = path8.resolve(projectRoot);
|
|
4670
|
+
const directory = path8.join(root, PROJECT_STATE_DIRECTORY);
|
|
4671
|
+
const pollIntervalMs = Math.max(25, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
4672
|
+
let closed = false;
|
|
4673
|
+
let repairPending = false;
|
|
4674
|
+
let repairInFlight;
|
|
4675
|
+
let repairTimer;
|
|
4676
|
+
const ensureDirectory = async () => {
|
|
4677
|
+
await fsp3.mkdir(directory, { recursive: true });
|
|
4678
|
+
};
|
|
4679
|
+
const ensureInvariant = async () => {
|
|
4680
|
+
await ensureDirectory();
|
|
4681
|
+
try {
|
|
4682
|
+
await ensureProjectGitignore(root);
|
|
4683
|
+
} catch (error) {
|
|
4684
|
+
options.onError?.(error);
|
|
4685
|
+
}
|
|
4686
|
+
};
|
|
4687
|
+
const scheduleRepair = () => {
|
|
4688
|
+
if (closed) return;
|
|
4689
|
+
repairPending = true;
|
|
4690
|
+
if (repairInFlight || repairTimer) return;
|
|
4691
|
+
repairTimer = setTimeout(() => {
|
|
4692
|
+
repairTimer = void 0;
|
|
4693
|
+
repairInFlight = (async () => {
|
|
4694
|
+
while (!closed && repairPending) {
|
|
4695
|
+
repairPending = false;
|
|
4696
|
+
try {
|
|
4697
|
+
const rootState = await fsp3.stat(root).catch(() => void 0);
|
|
4698
|
+
if (!rootState?.isDirectory()) return;
|
|
4699
|
+
await ensureInvariant();
|
|
4700
|
+
} catch (error) {
|
|
4701
|
+
options.onError?.(error);
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
})().finally(() => {
|
|
4705
|
+
repairInFlight = void 0;
|
|
4706
|
+
if (repairPending && !closed) scheduleRepair();
|
|
4707
|
+
});
|
|
4708
|
+
}, REPAIR_DEBOUNCE_MS);
|
|
4709
|
+
repairTimer.unref?.();
|
|
4710
|
+
};
|
|
4711
|
+
await ensureDirectory();
|
|
4712
|
+
await ensureProjectGitignore(root).catch((error) => options.onError?.(error));
|
|
4713
|
+
let watcher;
|
|
4714
|
+
try {
|
|
4715
|
+
watcher = fs6.watch(root, { persistent: false }, (_eventType, filename) => {
|
|
4716
|
+
if (concernsProjectState(filename) || String(filename).toLowerCase() === ".gitignore") {
|
|
4717
|
+
scheduleRepair();
|
|
4718
|
+
}
|
|
4719
|
+
});
|
|
4720
|
+
watcher.on("error", (error) => {
|
|
4721
|
+
options.onError?.(error);
|
|
4722
|
+
watcher = void 0;
|
|
4723
|
+
scheduleRepair();
|
|
4724
|
+
});
|
|
4725
|
+
} catch (error) {
|
|
4726
|
+
options.onError?.(error);
|
|
4727
|
+
}
|
|
4728
|
+
const poll = setInterval(scheduleRepair, pollIntervalMs);
|
|
4729
|
+
poll.unref?.();
|
|
4730
|
+
return {
|
|
4731
|
+
directory,
|
|
4732
|
+
close() {
|
|
4733
|
+
if (closed) return;
|
|
4734
|
+
closed = true;
|
|
4735
|
+
clearInterval(poll);
|
|
4736
|
+
if (repairTimer) clearTimeout(repairTimer);
|
|
4737
|
+
repairTimer = void 0;
|
|
4738
|
+
try {
|
|
4739
|
+
watcher?.close();
|
|
4740
|
+
} catch {
|
|
4741
|
+
}
|
|
4742
|
+
watcher = void 0;
|
|
4743
|
+
}
|
|
4744
|
+
};
|
|
4745
|
+
}
|
|
4746
|
+
async function activateProjectStateGuard(projectRoot, options = {}) {
|
|
4747
|
+
const root = normalizedRoot(projectRoot);
|
|
4748
|
+
if (activeGuard.root === root && activeGuard.guard) return activeGuard.guard;
|
|
4749
|
+
const next = await startProjectStateGuard(projectRoot, options);
|
|
4750
|
+
const previous = activeGuard.guard;
|
|
4751
|
+
activeGuard.root = root;
|
|
4752
|
+
activeGuard.guard = next;
|
|
4753
|
+
previous?.close();
|
|
4754
|
+
return next;
|
|
4755
|
+
}
|
|
4756
|
+
|
|
4633
4757
|
// src/boot.ts
|
|
4634
4758
|
async function bootConfig(options = {}) {
|
|
4635
4759
|
const {
|
|
@@ -4638,17 +4762,18 @@ async function bootConfig(options = {}) {
|
|
|
4638
4762
|
loadSyncConfig = true,
|
|
4639
4763
|
skipIdentityValidation = false
|
|
4640
4764
|
} = options;
|
|
4641
|
-
const cwd = typeof flags["cwd"] === "string" ?
|
|
4765
|
+
const cwd = typeof flags["cwd"] === "string" ? path12.resolve(flags["cwd"]) : process.cwd();
|
|
4642
4766
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
4643
4767
|
const projectRoot = pathResolver.projectRoot;
|
|
4644
4768
|
const projectIdentityRoot = canonicalProjectRoot(projectRoot);
|
|
4645
4769
|
const userHome = os4.homedir();
|
|
4646
4770
|
const wpaths = resolveWstackPaths({ projectRoot });
|
|
4647
4771
|
assertProjectRootOutsideStateDir(projectRoot, wpaths.globalRoot);
|
|
4648
|
-
await
|
|
4649
|
-
await
|
|
4650
|
-
await
|
|
4651
|
-
await
|
|
4772
|
+
await activateProjectStateGuard(projectRoot);
|
|
4773
|
+
await fs9.mkdir(wpaths.globalRoot, { recursive: true });
|
|
4774
|
+
await fs9.mkdir(wpaths.profilesDir, { recursive: true });
|
|
4775
|
+
await fs9.mkdir(wpaths.projectDir, { recursive: true });
|
|
4776
|
+
await fs9.mkdir(wpaths.projectSessions, { recursive: true });
|
|
4652
4777
|
let registeredProjectId;
|
|
4653
4778
|
try {
|
|
4654
4779
|
registeredProjectId = (await readProjectIdentity(projectRoot))?.projectId;
|
|
@@ -4823,7 +4948,7 @@ async function migrateLegacyConfig(wpaths) {
|
|
|
4823
4948
|
const rootFp = wpaths.globalConfig;
|
|
4824
4949
|
let legacyRaw;
|
|
4825
4950
|
try {
|
|
4826
|
-
legacyRaw = await
|
|
4951
|
+
legacyRaw = await fs9.readFile(rootFp, "utf8");
|
|
4827
4952
|
} catch {
|
|
4828
4953
|
return;
|
|
4829
4954
|
}
|
|
@@ -4836,12 +4961,12 @@ async function migrateLegacyConfig(wpaths) {
|
|
|
4836
4961
|
);
|
|
4837
4962
|
const profileFp = wpaths.profileConfig(activeProfile);
|
|
4838
4963
|
try {
|
|
4839
|
-
await
|
|
4964
|
+
await fs9.access(profileFp);
|
|
4840
4965
|
return;
|
|
4841
4966
|
} catch (err) {
|
|
4842
4967
|
if (err.code !== "ENOENT") return;
|
|
4843
4968
|
}
|
|
4844
|
-
await
|
|
4969
|
+
await fs9.mkdir(path12.dirname(profileFp), { recursive: true });
|
|
4845
4970
|
const profileContent = { ...result.value };
|
|
4846
4971
|
delete profileContent["activeProfile"];
|
|
4847
4972
|
await atomicWrite(profileFp, JSON.stringify(profileContent, null, 2), { mode: 384 });
|
|
@@ -4853,64 +4978,64 @@ async function migrateLegacyConfig(wpaths) {
|
|
|
4853
4978
|
}
|
|
4854
4979
|
var PROFILE_STATE_PAIRS = [
|
|
4855
4980
|
{
|
|
4856
|
-
globalSrc: (w) =>
|
|
4981
|
+
globalSrc: (w) => path12.join(w.globalRoot, "statusline.json"),
|
|
4857
4982
|
profileDst: (w, n) => w.profileStatuslineConfig(n)
|
|
4858
4983
|
},
|
|
4859
4984
|
{
|
|
4860
|
-
globalSrc: (w) =>
|
|
4985
|
+
globalSrc: (w) => path12.join(w.globalRoot, "mode.json"),
|
|
4861
4986
|
profileDst: (w, n) => w.profileModeConfig(n)
|
|
4862
4987
|
},
|
|
4863
4988
|
{
|
|
4864
|
-
globalSrc: (w) =>
|
|
4989
|
+
globalSrc: (w) => path12.join(w.globalRoot, "provider-status.json"),
|
|
4865
4990
|
profileDst: (w, n) => w.profileProviderStatus(n)
|
|
4866
4991
|
},
|
|
4867
4992
|
{
|
|
4868
|
-
globalSrc: (w) =>
|
|
4993
|
+
globalSrc: (w) => path12.join(w.globalRoot, "update-cache.json"),
|
|
4869
4994
|
profileDst: (w, n) => w.profileUpdateCache(n)
|
|
4870
4995
|
},
|
|
4871
4996
|
{
|
|
4872
|
-
globalSrc: (w) =>
|
|
4873
|
-
profileDst: (w, n) =>
|
|
4997
|
+
globalSrc: (w) => path12.join(w.globalRoot, "memory.md"),
|
|
4998
|
+
profileDst: (w, n) => path12.join(w.profilesDir, n, "memory.md")
|
|
4874
4999
|
},
|
|
4875
5000
|
{
|
|
4876
|
-
globalSrc: (w) =>
|
|
4877
|
-
profileDst: (w, n) =>
|
|
5001
|
+
globalSrc: (w) => path12.join(w.globalRoot, "history"),
|
|
5002
|
+
profileDst: (w, n) => path12.join(w.profilesDir, n, "history")
|
|
4878
5003
|
},
|
|
4879
5004
|
{
|
|
4880
|
-
globalSrc: (w) =>
|
|
4881
|
-
profileDst: (w, n) =>
|
|
5005
|
+
globalSrc: (w) => path12.join(w.globalRoot, "sync.json"),
|
|
5006
|
+
profileDst: (w, n) => path12.join(w.profilesDir, n, "sync.json")
|
|
4882
5007
|
},
|
|
4883
5008
|
{
|
|
4884
|
-
globalSrc: (w) =>
|
|
4885
|
-
profileDst: (w, n) =>
|
|
5009
|
+
globalSrc: (w) => path12.join(w.globalRoot, "sync-state.json"),
|
|
5010
|
+
profileDst: (w, n) => path12.join(w.profilesDir, n, "sync-state.json")
|
|
4886
5011
|
},
|
|
4887
5012
|
{
|
|
4888
|
-
globalSrc: (w) =>
|
|
4889
|
-
profileDst: (w, n) =>
|
|
5013
|
+
globalSrc: (w) => path12.join(w.globalRoot, "prompt-usage.json"),
|
|
5014
|
+
profileDst: (w, n) => path12.join(w.profilesDir, n, "prompt-usage.json")
|
|
4890
5015
|
},
|
|
4891
5016
|
{
|
|
4892
|
-
globalSrc: (w) =>
|
|
4893
|
-
profileDst: (w, n) =>
|
|
5017
|
+
globalSrc: (w) => path12.join(w.globalRoot, "custom-context-modes.json"),
|
|
5018
|
+
profileDst: (w, n) => path12.join(w.profilesDir, n, "custom-context-modes.json")
|
|
4894
5019
|
},
|
|
4895
5020
|
{
|
|
4896
|
-
globalSrc: (w) =>
|
|
4897
|
-
profileDst: (w, n) =>
|
|
5021
|
+
globalSrc: (w) => path12.join(w.globalRoot, "desktop.json"),
|
|
5022
|
+
profileDst: (w, n) => path12.join(w.profilesDir, n, "desktop.json")
|
|
4898
5023
|
},
|
|
4899
5024
|
{
|
|
4900
|
-
globalSrc: (w) =>
|
|
4901
|
-
profileDst: (w, n) =>
|
|
5025
|
+
globalSrc: (w) => path12.join(w.globalRoot, "installed-skills.json"),
|
|
5026
|
+
profileDst: (w, n) => path12.join(w.profilesDir, n, "installed-skills.json")
|
|
4902
5027
|
},
|
|
4903
5028
|
...["skills", "prompts", "instructions", "design-kits", "desktop", "settings"].map(
|
|
4904
5029
|
(directory) => ({
|
|
4905
|
-
globalSrc: (w) =>
|
|
4906
|
-
profileDst: (w, n) =>
|
|
5030
|
+
globalSrc: (w) => path12.join(w.globalRoot, directory),
|
|
5031
|
+
profileDst: (w, n) => path12.join(w.profilesDir, n, directory)
|
|
4907
5032
|
})
|
|
4908
5033
|
)
|
|
4909
5034
|
];
|
|
4910
5035
|
async function migrateProfileFiles(wpaths) {
|
|
4911
5036
|
let profileName = wpaths.profileName;
|
|
4912
5037
|
try {
|
|
4913
|
-
const raw = await
|
|
5038
|
+
const raw = await fs9.readFile(wpaths.globalConfig, "utf8");
|
|
4914
5039
|
const parsed = safeParse(raw);
|
|
4915
5040
|
if (parsed.ok && parsed.value && typeof parsed.value.activeProfile === "string") {
|
|
4916
5041
|
profileName = safeProfileName(parsed.value.activeProfile);
|
|
@@ -4918,7 +5043,7 @@ async function migrateProfileFiles(wpaths) {
|
|
|
4918
5043
|
} catch {
|
|
4919
5044
|
}
|
|
4920
5045
|
try {
|
|
4921
|
-
await
|
|
5046
|
+
await fs9.mkdir(path12.join(wpaths.profilesDir, profileName), { recursive: true });
|
|
4922
5047
|
} catch {
|
|
4923
5048
|
return;
|
|
4924
5049
|
}
|
|
@@ -4927,16 +5052,16 @@ async function migrateProfileFiles(wpaths) {
|
|
|
4927
5052
|
const dst = pair.profileDst(wpaths, profileName);
|
|
4928
5053
|
let srcIsDir;
|
|
4929
5054
|
try {
|
|
4930
|
-
srcIsDir = (await
|
|
5055
|
+
srcIsDir = (await fs9.stat(src)).isDirectory();
|
|
4931
5056
|
} catch {
|
|
4932
5057
|
continue;
|
|
4933
5058
|
}
|
|
4934
5059
|
try {
|
|
4935
|
-
const dstStat = await
|
|
5060
|
+
const dstStat = await fs9.stat(dst);
|
|
4936
5061
|
if (dstStat.isDirectory() === srcIsDir) {
|
|
4937
5062
|
continue;
|
|
4938
5063
|
}
|
|
4939
|
-
await
|
|
5064
|
+
await fs9.rm(dst, { recursive: true, force: true });
|
|
4940
5065
|
} catch (err) {
|
|
4941
5066
|
const code = err.code;
|
|
4942
5067
|
if (code !== "ENOENT") {
|
|
@@ -4947,19 +5072,19 @@ async function migrateProfileFiles(wpaths) {
|
|
|
4947
5072
|
}
|
|
4948
5073
|
try {
|
|
4949
5074
|
if (srcIsDir) {
|
|
4950
|
-
await
|
|
5075
|
+
await fs9.cp(src, dst, { recursive: true, errorOnExist: false, force: false });
|
|
4951
5076
|
} else {
|
|
4952
|
-
const content = await
|
|
4953
|
-
await
|
|
5077
|
+
const content = await fs9.readFile(src);
|
|
5078
|
+
await fs9.writeFile(dst, content, { mode: 384 });
|
|
4954
5079
|
}
|
|
4955
5080
|
} catch {
|
|
4956
5081
|
}
|
|
4957
5082
|
}
|
|
4958
5083
|
}
|
|
4959
5084
|
function assertProjectRootOutsideStateDir(projectRoot, globalRoot) {
|
|
4960
|
-
const stateNamespace =
|
|
4961
|
-
const rel =
|
|
4962
|
-
if (rel.startsWith("..") ||
|
|
5085
|
+
const stateNamespace = path12.resolve(globalRoot, "projects");
|
|
5086
|
+
const rel = path12.relative(stateNamespace, path12.resolve(projectRoot));
|
|
5087
|
+
if (rel.startsWith("..") || path12.isAbsolute(rel)) return;
|
|
4963
5088
|
throw new Error(
|
|
4964
5089
|
`Refusing to start: the resolved project root is inside WrongStack's per-project state directory.
|
|
4965
5090
|
project root: ${projectRoot}
|
|
@@ -4969,7 +5094,7 @@ This usually means a WrongStack process was spawned with a state path as its wor
|
|
|
4969
5094
|
}
|
|
4970
5095
|
async function writeProjectMeta(paths, projectRoot, projectId) {
|
|
4971
5096
|
try {
|
|
4972
|
-
await
|
|
5097
|
+
await fs9.mkdir(paths.projectDir, { recursive: true });
|
|
4973
5098
|
const meta = {
|
|
4974
5099
|
hash: paths.projectHash,
|
|
4975
5100
|
slug: paths.projectSlug,
|
|
@@ -4982,10 +5107,10 @@ async function writeProjectMeta(paths, projectRoot, projectId) {
|
|
|
4982
5107
|
}
|
|
4983
5108
|
}
|
|
4984
5109
|
async function registerProjectInManifest(paths, projectRoot, events, workingDir, projectId) {
|
|
4985
|
-
const manifestPath =
|
|
5110
|
+
const manifestPath = path12.join(paths.globalRoot, "projects.json");
|
|
4986
5111
|
try {
|
|
4987
5112
|
const t0 = Date.now();
|
|
4988
|
-
const raw = await
|
|
5113
|
+
const raw = await fs9.readFile(manifestPath, "utf8");
|
|
4989
5114
|
events?.emit("storage.read", {
|
|
4990
5115
|
sessionId: "~boot~",
|
|
4991
5116
|
store: "project",
|
|
@@ -5011,7 +5136,7 @@ async function registerProjectInManifest(paths, projectRoot, events, workingDir,
|
|
|
5011
5136
|
try {
|
|
5012
5137
|
let manifest;
|
|
5013
5138
|
try {
|
|
5014
|
-
const raw = await
|
|
5139
|
+
const raw = await fs9.readFile(manifestPath, "utf8");
|
|
5015
5140
|
manifest = JSON.parse(raw);
|
|
5016
5141
|
} catch {
|
|
5017
5142
|
manifest = { projects: [] };
|
|
@@ -5024,7 +5149,7 @@ async function registerProjectInManifest(paths, projectRoot, events, workingDir,
|
|
|
5024
5149
|
if (workingDir) existing.lastWorkingDir = workingDir;
|
|
5025
5150
|
} else {
|
|
5026
5151
|
const slug = paths.projectSlug;
|
|
5027
|
-
const name =
|
|
5152
|
+
const name = path12.basename(projectRoot);
|
|
5028
5153
|
const entry = {
|
|
5029
5154
|
name,
|
|
5030
5155
|
root: projectRoot,
|
|
@@ -5058,32 +5183,32 @@ async function registerProjectInManifest(paths, projectRoot, events, workingDir,
|
|
|
5058
5183
|
}
|
|
5059
5184
|
}
|
|
5060
5185
|
async function cleanupStaleProjects(wpaths) {
|
|
5061
|
-
const projectsRoot =
|
|
5186
|
+
const projectsRoot = path12.dirname(wpaths.projectDir);
|
|
5062
5187
|
let entries;
|
|
5063
5188
|
try {
|
|
5064
|
-
entries = await
|
|
5189
|
+
entries = await fs9.readdir(projectsRoot, { withFileTypes: true });
|
|
5065
5190
|
} catch {
|
|
5066
5191
|
return;
|
|
5067
5192
|
}
|
|
5068
|
-
const stateNamespace =
|
|
5193
|
+
const stateNamespace = path12.resolve(projectsRoot);
|
|
5069
5194
|
for (const entry of entries) {
|
|
5070
5195
|
if (!entry.isDirectory()) continue;
|
|
5071
|
-
const projectPath =
|
|
5072
|
-
const metaPath =
|
|
5196
|
+
const projectPath = path12.join(projectsRoot, entry.name);
|
|
5197
|
+
const metaPath = path12.join(projectPath, "meta.json");
|
|
5073
5198
|
try {
|
|
5074
|
-
const raw = await
|
|
5199
|
+
const raw = await fs9.readFile(metaPath, "utf8");
|
|
5075
5200
|
const meta = JSON.parse(raw);
|
|
5076
5201
|
if (typeof meta.root !== "string") continue;
|
|
5077
|
-
const rel =
|
|
5078
|
-
const nested = rel !== "" && !rel.startsWith("..") && !
|
|
5202
|
+
const rel = path12.relative(stateNamespace, path12.resolve(meta.root));
|
|
5203
|
+
const nested = rel !== "" && !rel.startsWith("..") && !path12.isAbsolute(rel);
|
|
5079
5204
|
if (nested) {
|
|
5080
|
-
await
|
|
5205
|
+
await fs9.rm(projectPath, { recursive: true, force: true });
|
|
5081
5206
|
continue;
|
|
5082
5207
|
}
|
|
5083
5208
|
try {
|
|
5084
|
-
await
|
|
5209
|
+
await fs9.access(meta.root);
|
|
5085
5210
|
} catch {
|
|
5086
|
-
await
|
|
5211
|
+
await fs9.rm(projectPath, { recursive: true, force: true });
|
|
5087
5212
|
}
|
|
5088
5213
|
} catch {
|
|
5089
5214
|
}
|