syntaur 0.11.0 → 0.13.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/.claude-plugin/plugin.json +3 -1
- package/README.md +21 -0
- package/dashboard/dist/assets/{_basePickBy-DPIRyskD.js → _basePickBy-qEDLAY_J.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-CwicZZgc.js → _baseUniq-7tDs18Gn.js} +1 -1
- package/dashboard/dist/assets/{arc-CcHe1anq.js → arc-Brdw0dYW.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-4lwARCsm.js → architectureDiagram-2XIMDMQ5-BiEdLT9m.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-SdqET9RP.js → blockDiagram-WCTKOSBZ-HC-j_hPZ.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-BAKj5SV2.js → c4Diagram-IC4MRINW-DA_rWbl7.js} +1 -1
- package/dashboard/dist/assets/channel-D2-caOn6.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-Dn95vjsS.js → chunk-4BX2VUAB-CF3hguyj.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-C6L5bDoe.js → chunk-55IACEB6-BTeCeIc_.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4---Go123J.js → chunk-FMBD7UC4-8qVvxmks.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-C9E63Ryg.js → chunk-JSJVCQXG-DCf8n7EO.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-Yyi0h479.js → chunk-KX2RTZJC-CcpjTmME.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-2Fh1XO45.js → chunk-NQ4KR5QH-BnFPKo4-.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-B4a3DKz-.js → chunk-QZHKN3VN-IPMVLLv2.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-XtMf_tFy.js → chunk-WL4C6EOR-D1pQmHqX.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-HFOb_XEt.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-HFOb_XEt.js +1 -0
- package/dashboard/dist/assets/clone-BrYOwgIq.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-8wHbmnAP.js → cose-bilkent-S5V4N54A-tvJjvBQr.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-uJZOLE_S.js → dagre-KLK3FWXG-qp1vt6LY.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-BZih6Ivf.js → diagram-E7M64L7V-VF5eizNr.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-CtR6WhET.js → diagram-IFDJBPK2-C3pCbJ1e.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-DTGhoDSE.js → diagram-P4PSJMXO-D-taXZwH.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-Yr93h1x4.js → erDiagram-INFDFZHY-QjY-NHVs.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CZ3QY4eZ.js → flowDiagram-PKNHOUZH-FjO8P5Tq.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CSf--huI.js → ganttDiagram-A5KZAMGK-Bsn7CyK3.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-CokKKyLR.js → gitGraphDiagram-K3NZZRJ6-BZ8DjhnF.js} +1 -1
- package/dashboard/dist/assets/{graph-DRs4cE7X.js → graph-AdliIYqX.js} +1 -1
- package/dashboard/dist/assets/index-BoR_oyUZ.css +1 -0
- package/dashboard/dist/assets/index-RCBI4aX2.js +525 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-C4CTdWLD.js → infoDiagram-LFFYTUFH-BfZjLA86.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-DGrfYHHk.js → ishikawaDiagram-PHBUUO56-BSgSGMp5.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CEDbuCY2.js → journeyDiagram-4ABVD52K-BlQ7qeMC.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BjgAWDsE.js → kanban-definition-K7BYSVSG-CifO0AUu.js} +1 -1
- package/dashboard/dist/assets/{layout-Coi0pRnm.js → layout-B9r-gpD9.js} +1 -1
- package/dashboard/dist/assets/{linear-B4xKytgE.js → linear-CaaIRxVZ.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-dkpoLY2K.js → mermaid.core-C8wCWYw-.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-BgUfAXkB.js → mindmap-definition-YRQLILUH-BpX5T7iT.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-BBuMJroZ.js → pieDiagram-SKSYHLDU-BNGYtWrc.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-iXygRpen.js → quadrantDiagram-337W2JSQ-CR4GeM9D.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-DhJGFHd3.js → requirementDiagram-Z7DCOOCP-CXrPkMLl.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-CLkFLBq1.js → sankeyDiagram-WA2Y5GQK-CIzof7Dm.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-DQh28Zt-.js → sequenceDiagram-2WXFIKYE-BJZLt0O2.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-BZKsD9LJ.js → stateDiagram-RAJIS63D-x9gC6zy6.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-CN5Dcv7u.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-DCJlSWVw.js → timeline-definition-YZTLITO2-D_ZSQn1K.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-DEGy-zW9.js → treemap-KZPCXAKY-LPpMEco4.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-CfQAKq7b.js → vennDiagram-LZ73GAT5-CRFZQpr7.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-CgD-asFJ.js → xychartDiagram-JWTSCODW-Bivutynw.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +355 -81
- package/dist/dashboard/server.js.map +1 -1
- package/dist/db/leases-db.d.ts +178 -0
- package/dist/db/leases-db.js +557 -0
- package/dist/db/leases-db.js.map +1 -0
- package/dist/index.js +1219 -357
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/.claude-plugin/plugin.json +3 -1
- package/platforms/claude-code/skills/claim-resource/SKILL.md +89 -0
- package/platforms/claude-code/skills/release-resource/SKILL.md +65 -0
- package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +1 -0
- package/platforms/codex/skills/claim-resource/SKILL.md +89 -0
- package/platforms/codex/skills/release-resource/SKILL.md +65 -0
- package/platforms/codex/skills/syntaur-protocol/SKILL.md +1 -0
- package/skills/claim-resource/SKILL.md +89 -0
- package/skills/release-resource/SKILL.md +65 -0
- package/skills/syntaur-protocol/SKILL.md +1 -0
- package/dashboard/dist/assets/channel-DKdluNz1.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-93dPLc4W.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-93dPLc4W.js +0 -1
- package/dashboard/dist/assets/clone-vnWuXQgZ.js +0 -1
- package/dashboard/dist/assets/index-B9uCPhOL.js +0 -510
- package/dashboard/dist/assets/index-BKoccIZR.css +0 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DU5UnRV-.js +0 -1
package/dist/index.js
CHANGED
|
@@ -5961,7 +5961,7 @@ __export(parser_exports, {
|
|
|
5961
5961
|
});
|
|
5962
5962
|
import { randomBytes } from "crypto";
|
|
5963
5963
|
import { readFile as readFile13 } from "fs/promises";
|
|
5964
|
-
import { resolve as
|
|
5964
|
+
import { resolve as resolve19 } from "path";
|
|
5965
5965
|
function generateShortId() {
|
|
5966
5966
|
return randomBytes(2).toString("hex");
|
|
5967
5967
|
}
|
|
@@ -6199,10 +6199,10 @@ function serializeLogEntry(entry) {
|
|
|
6199
6199
|
return lines.join("\n");
|
|
6200
6200
|
}
|
|
6201
6201
|
function checklistPath(todosDir2, workspace) {
|
|
6202
|
-
return
|
|
6202
|
+
return resolve19(todosDir2, `${workspace}.md`);
|
|
6203
6203
|
}
|
|
6204
6204
|
function logPath(todosDir2, workspace) {
|
|
6205
|
-
return
|
|
6205
|
+
return resolve19(todosDir2, `${workspace}-log.md`);
|
|
6206
6206
|
}
|
|
6207
6207
|
function archivePath(todosDir2, workspace, interval, now = /* @__PURE__ */ new Date()) {
|
|
6208
6208
|
const year = now.getFullYear();
|
|
@@ -6226,7 +6226,7 @@ function archivePath(todosDir2, workspace, interval, now = /* @__PURE__ */ new D
|
|
|
6226
6226
|
default:
|
|
6227
6227
|
suffix = `${year}-${month}-${day}`;
|
|
6228
6228
|
}
|
|
6229
|
-
return
|
|
6229
|
+
return resolve19(todosDir2, "archive", `${workspace}-${suffix}.md`);
|
|
6230
6230
|
}
|
|
6231
6231
|
async function readChecklist(todosDir2, workspace) {
|
|
6232
6232
|
const path = checklistPath(todosDir2, workspace);
|
|
@@ -6855,7 +6855,7 @@ __export(launch_exports, {
|
|
|
6855
6855
|
});
|
|
6856
6856
|
import { spawn as spawn2 } from "child_process";
|
|
6857
6857
|
import { mkdir as mkdir6, writeFile as writeFile9 } from "fs/promises";
|
|
6858
|
-
import { isAbsolute as isAbsolute3, resolve as
|
|
6858
|
+
import { isAbsolute as isAbsolute3, resolve as resolve34 } from "path";
|
|
6859
6859
|
function formatFallbackCwdWarning(opts) {
|
|
6860
6860
|
const missing = [];
|
|
6861
6861
|
if (!opts.worktreePath) missing.push("worktreePath");
|
|
@@ -6899,8 +6899,8 @@ async function launchAgent(options) {
|
|
|
6899
6899
|
console.error(`Assignment not found: ${projectSlug}/${assignmentSlug}`);
|
|
6900
6900
|
process.exit(1);
|
|
6901
6901
|
}
|
|
6902
|
-
const projectDir =
|
|
6903
|
-
const assignmentDir =
|
|
6902
|
+
const projectDir = resolve34(projectsDir2, projectSlug);
|
|
6903
|
+
const assignmentDir = resolve34(projectDir, "assignments", assignmentSlug);
|
|
6904
6904
|
const resolvedFromWorkspace = cwdOverride ?? detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null);
|
|
6905
6905
|
const workspaceDir = resolvedFromWorkspace ?? process.cwd();
|
|
6906
6906
|
if (!cwdOverride) {
|
|
@@ -6912,7 +6912,7 @@ async function launchAgent(options) {
|
|
|
6912
6912
|
});
|
|
6913
6913
|
if (warning) console.warn(warning);
|
|
6914
6914
|
}
|
|
6915
|
-
const contextDir =
|
|
6915
|
+
const contextDir = resolve34(workspaceDir, ".syntaur");
|
|
6916
6916
|
await mkdir6(contextDir, { recursive: true });
|
|
6917
6917
|
const context = {
|
|
6918
6918
|
projectSlug,
|
|
@@ -6925,7 +6925,7 @@ async function launchAgent(options) {
|
|
|
6925
6925
|
grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6926
6926
|
};
|
|
6927
6927
|
await writeFile9(
|
|
6928
|
-
|
|
6928
|
+
resolve34(contextDir, "context.json"),
|
|
6929
6929
|
JSON.stringify(context, null, 2) + "\n"
|
|
6930
6930
|
);
|
|
6931
6931
|
const { argv, shellFallbackWarning } = buildAgentArgv(
|
|
@@ -7082,7 +7082,7 @@ var init_git_worktree = __esm({
|
|
|
7082
7082
|
});
|
|
7083
7083
|
|
|
7084
7084
|
// src/index.ts
|
|
7085
|
-
import { Command as
|
|
7085
|
+
import { Command as Command13 } from "commander";
|
|
7086
7086
|
|
|
7087
7087
|
// src/commands/init.ts
|
|
7088
7088
|
init_paths();
|
|
@@ -7240,7 +7240,7 @@ init_create_assignment();
|
|
|
7240
7240
|
init_config2();
|
|
7241
7241
|
import { spawn } from "child_process";
|
|
7242
7242
|
import { createServer as createNetServer } from "net";
|
|
7243
|
-
import { resolve as
|
|
7243
|
+
import { resolve as resolve23, dirname as dirname7 } from "path";
|
|
7244
7244
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7245
7245
|
|
|
7246
7246
|
// src/dashboard/server.ts
|
|
@@ -7249,7 +7249,7 @@ init_api();
|
|
|
7249
7249
|
init_assignment_resolver();
|
|
7250
7250
|
import express from "express";
|
|
7251
7251
|
import { createServer } from "http";
|
|
7252
|
-
import { resolve as
|
|
7252
|
+
import { resolve as resolve22 } from "path";
|
|
7253
7253
|
import { writeFile as writeFile5, unlink as unlink4 } from "fs/promises";
|
|
7254
7254
|
import { WebSocketServer, WebSocket } from "ws";
|
|
7255
7255
|
|
|
@@ -7464,8 +7464,8 @@ function rowToSession(row) {
|
|
|
7464
7464
|
};
|
|
7465
7465
|
}
|
|
7466
7466
|
async function appendSession(_projectDir, session) {
|
|
7467
|
-
const
|
|
7468
|
-
|
|
7467
|
+
const db4 = getSessionDb();
|
|
7468
|
+
db4.prepare(`
|
|
7469
7469
|
INSERT INTO sessions (session_id, project_slug, assignment_slug, agent, started, status, path, description, transcript_path)
|
|
7470
7470
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7471
7471
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
@@ -7490,36 +7490,36 @@ async function appendSession(_projectDir, session) {
|
|
|
7490
7490
|
);
|
|
7491
7491
|
}
|
|
7492
7492
|
async function updateSessionStatus(_projectDir, sessionId, status) {
|
|
7493
|
-
const
|
|
7493
|
+
const db4 = getSessionDb();
|
|
7494
7494
|
const isTerminal = status === "completed" || status === "stopped";
|
|
7495
|
-
const result = isTerminal ?
|
|
7495
|
+
const result = isTerminal ? db4.prepare(
|
|
7496
7496
|
"UPDATE sessions SET status = ?, ended = datetime('now'), updated_at = datetime('now') WHERE session_id = ?"
|
|
7497
|
-
).run(status, sessionId) :
|
|
7497
|
+
).run(status, sessionId) : db4.prepare(
|
|
7498
7498
|
"UPDATE sessions SET status = ?, updated_at = datetime('now') WHERE session_id = ?"
|
|
7499
7499
|
).run(status, sessionId);
|
|
7500
7500
|
return result.changes > 0;
|
|
7501
7501
|
}
|
|
7502
7502
|
async function listAllSessions(_projectsDir) {
|
|
7503
|
-
const
|
|
7504
|
-
const rows =
|
|
7503
|
+
const db4 = getSessionDb();
|
|
7504
|
+
const rows = db4.prepare("SELECT * FROM sessions ORDER BY started DESC").all();
|
|
7505
7505
|
return rows.map(rowToSession);
|
|
7506
7506
|
}
|
|
7507
7507
|
async function listProjectSessions(_projectsDir, projectSlug, assignmentSlug) {
|
|
7508
|
-
const
|
|
7508
|
+
const db4 = getSessionDb();
|
|
7509
7509
|
if (assignmentSlug) {
|
|
7510
|
-
const rows2 =
|
|
7510
|
+
const rows2 = db4.prepare(
|
|
7511
7511
|
"SELECT * FROM sessions WHERE project_slug = ? AND assignment_slug = ? ORDER BY started DESC"
|
|
7512
7512
|
).all(projectSlug, assignmentSlug);
|
|
7513
7513
|
return rows2.map(rowToSession);
|
|
7514
7514
|
}
|
|
7515
|
-
const rows =
|
|
7515
|
+
const rows = db4.prepare("SELECT * FROM sessions WHERE project_slug = ? ORDER BY started DESC").all(projectSlug);
|
|
7516
7516
|
return rows.map(rowToSession);
|
|
7517
7517
|
}
|
|
7518
7518
|
async function deleteSessions(sessionIds) {
|
|
7519
7519
|
if (sessionIds.length === 0) return 0;
|
|
7520
|
-
const
|
|
7520
|
+
const db4 = getSessionDb();
|
|
7521
7521
|
const placeholders = sessionIds.map(() => "?").join(", ");
|
|
7522
|
-
const result =
|
|
7522
|
+
const result = db4.prepare(`DELETE FROM sessions WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
|
7523
7523
|
return result.changes;
|
|
7524
7524
|
}
|
|
7525
7525
|
var DONE_ASSIGNMENT_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "review"]);
|
|
@@ -7535,8 +7535,8 @@ async function readAssignmentStatus(projectDir, assignmentSlug) {
|
|
|
7535
7535
|
);
|
|
7536
7536
|
}
|
|
7537
7537
|
async function reconcileActiveSessions(projectsDir2, assignmentsDir2) {
|
|
7538
|
-
const
|
|
7539
|
-
const activeSessions =
|
|
7538
|
+
const db4 = getSessionDb();
|
|
7539
|
+
const activeSessions = db4.prepare("SELECT * FROM sessions WHERE status = 'active' AND assignment_slug IS NOT NULL").all();
|
|
7540
7540
|
if (activeSessions.length === 0) return 0;
|
|
7541
7541
|
const assignmentStatuses = /* @__PURE__ */ new Map();
|
|
7542
7542
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7573,10 +7573,10 @@ async function reconcileActiveSessions(projectsDir2, assignmentsDir2) {
|
|
|
7573
7573
|
return totalUpdated;
|
|
7574
7574
|
}
|
|
7575
7575
|
async function listSessionsByAssignment(projectSlug, assignmentSlug) {
|
|
7576
|
-
const
|
|
7577
|
-
const rows = projectSlug === null ?
|
|
7576
|
+
const db4 = getSessionDb();
|
|
7577
|
+
const rows = projectSlug === null ? db4.prepare(
|
|
7578
7578
|
"SELECT * FROM sessions WHERE assignment_slug = ? AND project_slug IS NULL ORDER BY started DESC"
|
|
7579
|
-
).all(assignmentSlug) :
|
|
7579
|
+
).all(assignmentSlug) : db4.prepare(
|
|
7580
7580
|
"SELECT * FROM sessions WHERE project_slug = ? AND assignment_slug = ? ORDER BY started DESC"
|
|
7581
7581
|
).all(projectSlug, assignmentSlug);
|
|
7582
7582
|
return rows.map(rowToSession);
|
|
@@ -7584,9 +7584,9 @@ async function listSessionsByAssignment(projectSlug, assignmentSlug) {
|
|
|
7584
7584
|
|
|
7585
7585
|
// src/dashboard/watcher.ts
|
|
7586
7586
|
import { watch } from "chokidar";
|
|
7587
|
-
import { relative, sep } from "path";
|
|
7587
|
+
import { basename as basename2, dirname as dirname4, relative, sep } from "path";
|
|
7588
7588
|
function createWatcher(options) {
|
|
7589
|
-
const { projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, serversDir: serversDir2, playbooksDir: playbooksDir3, todosDir: todosDir2, onMessage, debounceMs = 300 } = options;
|
|
7589
|
+
const { projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, serversDir: serversDir2, playbooksDir: playbooksDir3, todosDir: todosDir2, dbPath, onMessage, debounceMs = 300 } = options;
|
|
7590
7590
|
const pendingEvents = /* @__PURE__ */ new Map();
|
|
7591
7591
|
const projectsWatcher = watch(projectsDir2, {
|
|
7592
7592
|
ignoreInitial: true,
|
|
@@ -7754,6 +7754,38 @@ function createWatcher(options) {
|
|
|
7754
7754
|
todosWatcher.on("add", handleTodoChange2);
|
|
7755
7755
|
todosWatcher.on("unlink", handleTodoChange2);
|
|
7756
7756
|
}
|
|
7757
|
+
let leasesDbWatcher = null;
|
|
7758
|
+
if (dbPath) {
|
|
7759
|
+
let handleDbChange2 = function(filePath) {
|
|
7760
|
+
if (!basename2(filePath).startsWith(dbBase)) return;
|
|
7761
|
+
const debounceKey = "__leases-db__";
|
|
7762
|
+
const existing = pendingEvents.get(debounceKey);
|
|
7763
|
+
if (existing) clearTimeout(existing);
|
|
7764
|
+
pendingEvents.set(
|
|
7765
|
+
debounceKey,
|
|
7766
|
+
setTimeout(() => {
|
|
7767
|
+
pendingEvents.delete(debounceKey);
|
|
7768
|
+
const message = {
|
|
7769
|
+
type: "leases-updated",
|
|
7770
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7771
|
+
};
|
|
7772
|
+
onMessage(message);
|
|
7773
|
+
}, debounceMs)
|
|
7774
|
+
);
|
|
7775
|
+
};
|
|
7776
|
+
var handleDbChange = handleDbChange2;
|
|
7777
|
+
const dbDir = dirname4(dbPath);
|
|
7778
|
+
const dbBase = basename2(dbPath);
|
|
7779
|
+
leasesDbWatcher = watch(dbDir, {
|
|
7780
|
+
ignoreInitial: true,
|
|
7781
|
+
persistent: true,
|
|
7782
|
+
depth: 0,
|
|
7783
|
+
ignored: /(^|[\/\\])\../
|
|
7784
|
+
});
|
|
7785
|
+
leasesDbWatcher.on("change", handleDbChange2);
|
|
7786
|
+
leasesDbWatcher.on("add", handleDbChange2);
|
|
7787
|
+
leasesDbWatcher.on("unlink", handleDbChange2);
|
|
7788
|
+
}
|
|
7757
7789
|
return {
|
|
7758
7790
|
close: async () => {
|
|
7759
7791
|
pendingEvents.forEach((timeout) => {
|
|
@@ -7765,6 +7797,7 @@ function createWatcher(options) {
|
|
|
7765
7797
|
if (serversWatcher) await serversWatcher.close();
|
|
7766
7798
|
if (playbooksWatcher) await playbooksWatcher.close();
|
|
7767
7799
|
if (todosWatcher) await todosWatcher.close();
|
|
7800
|
+
if (leasesDbWatcher) await leasesDbWatcher.close();
|
|
7768
7801
|
}
|
|
7769
7802
|
};
|
|
7770
7803
|
}
|
|
@@ -7782,7 +7815,7 @@ init_timestamp();
|
|
|
7782
7815
|
init_fs();
|
|
7783
7816
|
init_parser();
|
|
7784
7817
|
import { Router } from "express";
|
|
7785
|
-
import { resolve as resolve15, basename as
|
|
7818
|
+
import { resolve as resolve15, basename as basename3 } from "path";
|
|
7786
7819
|
import { rm, readFile as readFile11, open as fsOpen } from "fs/promises";
|
|
7787
7820
|
|
|
7788
7821
|
// src/dashboard/acceptance-criteria.ts
|
|
@@ -8067,7 +8100,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
|
|
|
8067
8100
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
8068
8101
|
return;
|
|
8069
8102
|
}
|
|
8070
|
-
const document = await getEditableDocument(projectsDir2, "memory",
|
|
8103
|
+
const document = await getEditableDocument(projectsDir2, "memory", basename3(projectDir), itemSlug);
|
|
8071
8104
|
if (!document) {
|
|
8072
8105
|
res.status(404).json({ error: "Memory not found" });
|
|
8073
8106
|
return;
|
|
@@ -8086,7 +8119,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
|
|
|
8086
8119
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
8087
8120
|
return;
|
|
8088
8121
|
}
|
|
8089
|
-
const document = await getEditableDocument(projectsDir2, "resource",
|
|
8122
|
+
const document = await getEditableDocument(projectsDir2, "resource", basename3(projectDir), itemSlug);
|
|
8090
8123
|
if (!document) {
|
|
8091
8124
|
res.status(404).json({ error: "Resource not found" });
|
|
8092
8125
|
return;
|
|
@@ -8171,7 +8204,7 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
8171
8204
|
let content = renderItemStub(kind, {
|
|
8172
8205
|
slug: requestedSlug,
|
|
8173
8206
|
name,
|
|
8174
|
-
projectSlug:
|
|
8207
|
+
projectSlug: basename3(projectDir),
|
|
8175
8208
|
timestamp
|
|
8176
8209
|
});
|
|
8177
8210
|
const customBody = typeof body.body === "string" ? body.body : "";
|
|
@@ -8188,13 +8221,13 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
8188
8221
|
} catch (err2) {
|
|
8189
8222
|
if (err2.code === "EEXIST") {
|
|
8190
8223
|
res.status(409).json({
|
|
8191
|
-
error: `${kind === "memory" ? "Memory" : "Resource"} with slug "${requestedSlug}" already exists in project "${
|
|
8224
|
+
error: `${kind === "memory" ? "Memory" : "Resource"} with slug "${requestedSlug}" already exists in project "${basename3(projectDir)}".`
|
|
8192
8225
|
});
|
|
8193
8226
|
return;
|
|
8194
8227
|
}
|
|
8195
8228
|
throw err2;
|
|
8196
8229
|
}
|
|
8197
|
-
res.status(201).json({ slug: requestedSlug, projectSlug:
|
|
8230
|
+
res.status(201).json({ slug: requestedSlug, projectSlug: basename3(projectDir), content });
|
|
8198
8231
|
} catch (error) {
|
|
8199
8232
|
console.error(`Error creating ${kind}:`, error);
|
|
8200
8233
|
res.status(500).json({ error: `Failed to create ${kind}: ${error.message}` });
|
|
@@ -8229,7 +8262,7 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
8229
8262
|
${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
8230
8263
|
merged = setTopLevelField(merged, "updated", nowTimestamp());
|
|
8231
8264
|
await writeFileForce(filePath, merged);
|
|
8232
|
-
const detail = await getItemDetail(kind,
|
|
8265
|
+
const detail = await getItemDetail(kind, basename3(projectDir), itemSlug);
|
|
8233
8266
|
res.json({ [kind]: detail, content: merged });
|
|
8234
8267
|
} catch (error) {
|
|
8235
8268
|
console.error(`Error updating ${kind}:`, error);
|
|
@@ -9744,6 +9777,575 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
|
|
|
9744
9777
|
return router;
|
|
9745
9778
|
}
|
|
9746
9779
|
|
|
9780
|
+
// src/dashboard/api-leases.ts
|
|
9781
|
+
import { Router as Router4 } from "express";
|
|
9782
|
+
|
|
9783
|
+
// src/db/leases-db.ts
|
|
9784
|
+
init_paths();
|
|
9785
|
+
import Database2 from "better-sqlite3";
|
|
9786
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
9787
|
+
import { resolve as resolve17 } from "path";
|
|
9788
|
+
var db2 = null;
|
|
9789
|
+
var LEASE_SCHEMA_VERSION = "1";
|
|
9790
|
+
var SCHEMA_SQL2 = `
|
|
9791
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
9792
|
+
key TEXT PRIMARY KEY,
|
|
9793
|
+
value TEXT
|
|
9794
|
+
);
|
|
9795
|
+
|
|
9796
|
+
CREATE TABLE IF NOT EXISTS inventories (
|
|
9797
|
+
slug TEXT PRIMARY KEY,
|
|
9798
|
+
kind TEXT NOT NULL,
|
|
9799
|
+
display_name TEXT,
|
|
9800
|
+
default_ttl_s INTEGER NOT NULL CHECK (default_ttl_s > 0),
|
|
9801
|
+
created_at TEXT NOT NULL
|
|
9802
|
+
);
|
|
9803
|
+
|
|
9804
|
+
CREATE TABLE IF NOT EXISTS inventory_members (
|
|
9805
|
+
inventory_slug TEXT NOT NULL,
|
|
9806
|
+
member_id TEXT NOT NULL,
|
|
9807
|
+
status TEXT NOT NULL DEFAULT 'idle'
|
|
9808
|
+
CHECK (status IN ('idle','leased','retired')),
|
|
9809
|
+
generation INTEGER NOT NULL DEFAULT 0,
|
|
9810
|
+
metadata_json TEXT,
|
|
9811
|
+
last_used_at TEXT,
|
|
9812
|
+
retired_at TEXT,
|
|
9813
|
+
PRIMARY KEY (inventory_slug, member_id),
|
|
9814
|
+
FOREIGN KEY (inventory_slug) REFERENCES inventories(slug)
|
|
9815
|
+
);
|
|
9816
|
+
|
|
9817
|
+
CREATE INDEX IF NOT EXISTS idx_members_idle
|
|
9818
|
+
ON inventory_members (inventory_slug, status, last_used_at);
|
|
9819
|
+
|
|
9820
|
+
CREATE TABLE IF NOT EXISTS leases (
|
|
9821
|
+
lease_id TEXT PRIMARY KEY,
|
|
9822
|
+
inventory_slug TEXT NOT NULL,
|
|
9823
|
+
member_id TEXT NOT NULL,
|
|
9824
|
+
member_gen INTEGER NOT NULL,
|
|
9825
|
+
state TEXT NOT NULL
|
|
9826
|
+
CHECK (state IN ('active','released','expired','revoked')),
|
|
9827
|
+
granted_at TEXT NOT NULL,
|
|
9828
|
+
expires_at TEXT NOT NULL,
|
|
9829
|
+
released_at TEXT,
|
|
9830
|
+
requested_for TEXT,
|
|
9831
|
+
FOREIGN KEY (inventory_slug, member_id)
|
|
9832
|
+
REFERENCES inventory_members(inventory_slug, member_id)
|
|
9833
|
+
);
|
|
9834
|
+
|
|
9835
|
+
CREATE INDEX IF NOT EXISTS idx_leases_gc
|
|
9836
|
+
ON leases (state, expires_at);
|
|
9837
|
+
|
|
9838
|
+
CREATE TABLE IF NOT EXISTS lease_events (
|
|
9839
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
9840
|
+
lease_id TEXT NOT NULL,
|
|
9841
|
+
event TEXT NOT NULL,
|
|
9842
|
+
at TEXT NOT NULL,
|
|
9843
|
+
detail_json TEXT,
|
|
9844
|
+
FOREIGN KEY (lease_id) REFERENCES leases(lease_id)
|
|
9845
|
+
);
|
|
9846
|
+
|
|
9847
|
+
CREATE INDEX IF NOT EXISTS idx_events_lease
|
|
9848
|
+
ON lease_events (lease_id, at);
|
|
9849
|
+
`;
|
|
9850
|
+
var NoIdleMemberError = class extends Error {
|
|
9851
|
+
constructor(inventorySlug) {
|
|
9852
|
+
super(`no idle members in '${inventorySlug}'`);
|
|
9853
|
+
this.inventorySlug = inventorySlug;
|
|
9854
|
+
this.name = "NoIdleMemberError";
|
|
9855
|
+
}
|
|
9856
|
+
};
|
|
9857
|
+
var StaleLeaseError = class extends Error {
|
|
9858
|
+
constructor(leaseId) {
|
|
9859
|
+
super(`lease ${leaseId} is no longer active (expired or revoked)`);
|
|
9860
|
+
this.leaseId = leaseId;
|
|
9861
|
+
this.name = "StaleLeaseError";
|
|
9862
|
+
}
|
|
9863
|
+
};
|
|
9864
|
+
var NotFoundError = class extends Error {
|
|
9865
|
+
constructor(id) {
|
|
9866
|
+
super(`lease ${id} not found`);
|
|
9867
|
+
this.id = id;
|
|
9868
|
+
this.name = "NotFoundError";
|
|
9869
|
+
}
|
|
9870
|
+
};
|
|
9871
|
+
var LeaseContentionError = class extends Error {
|
|
9872
|
+
constructor(inventorySlug) {
|
|
9873
|
+
super(`contention timeout on '${inventorySlug}'; retry`);
|
|
9874
|
+
this.inventorySlug = inventorySlug;
|
|
9875
|
+
this.name = "LeaseContentionError";
|
|
9876
|
+
}
|
|
9877
|
+
};
|
|
9878
|
+
var MemberInUseError = class extends Error {
|
|
9879
|
+
constructor(inventorySlug, memberId) {
|
|
9880
|
+
super(`member ${inventorySlug}/${memberId} is currently leased; release before retiring`);
|
|
9881
|
+
this.inventorySlug = inventorySlug;
|
|
9882
|
+
this.memberId = memberId;
|
|
9883
|
+
this.name = "MemberInUseError";
|
|
9884
|
+
}
|
|
9885
|
+
};
|
|
9886
|
+
var DuplicateInventoryError = class extends Error {
|
|
9887
|
+
constructor(slug) {
|
|
9888
|
+
super(`inventory '${slug}' already exists`);
|
|
9889
|
+
this.slug = slug;
|
|
9890
|
+
this.name = "DuplicateInventoryError";
|
|
9891
|
+
}
|
|
9892
|
+
};
|
|
9893
|
+
var DuplicateMemberError = class extends Error {
|
|
9894
|
+
constructor(inventorySlug, memberId) {
|
|
9895
|
+
super(`member ${inventorySlug}/${memberId} already exists`);
|
|
9896
|
+
this.inventorySlug = inventorySlug;
|
|
9897
|
+
this.memberId = memberId;
|
|
9898
|
+
this.name = "DuplicateMemberError";
|
|
9899
|
+
}
|
|
9900
|
+
};
|
|
9901
|
+
function nowIso() {
|
|
9902
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
9903
|
+
}
|
|
9904
|
+
function isBusyError(err2) {
|
|
9905
|
+
if (!err2 || typeof err2 !== "object") return false;
|
|
9906
|
+
const e = err2;
|
|
9907
|
+
return e.code === "SQLITE_BUSY" || e.code === "SQLITE_BUSY_SNAPSHOT";
|
|
9908
|
+
}
|
|
9909
|
+
function initLeasesDb(dbPath) {
|
|
9910
|
+
if (db2) return db2;
|
|
9911
|
+
const finalPath = dbPath ?? resolve17(syntaurRoot(), "syntaur.db");
|
|
9912
|
+
db2 = new Database2(finalPath);
|
|
9913
|
+
db2.pragma("journal_mode = WAL");
|
|
9914
|
+
db2.pragma("busy_timeout = 5000");
|
|
9915
|
+
db2.pragma("foreign_keys = ON");
|
|
9916
|
+
const database = db2;
|
|
9917
|
+
const runMigrations = database.transaction(() => {
|
|
9918
|
+
database.exec(SCHEMA_SQL2);
|
|
9919
|
+
database.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run("lease_schema_version", LEASE_SCHEMA_VERSION);
|
|
9920
|
+
});
|
|
9921
|
+
runMigrations.exclusive();
|
|
9922
|
+
return db2;
|
|
9923
|
+
}
|
|
9924
|
+
function getLeasesDb() {
|
|
9925
|
+
if (!db2) {
|
|
9926
|
+
throw new Error("Leases database not initialized. Call initLeasesDb() first.");
|
|
9927
|
+
}
|
|
9928
|
+
return db2;
|
|
9929
|
+
}
|
|
9930
|
+
function closeLeasesDb() {
|
|
9931
|
+
if (db2) {
|
|
9932
|
+
db2.close();
|
|
9933
|
+
db2 = null;
|
|
9934
|
+
}
|
|
9935
|
+
}
|
|
9936
|
+
function createInventory(input4) {
|
|
9937
|
+
const database = getLeasesDb();
|
|
9938
|
+
const row = {
|
|
9939
|
+
slug: input4.slug,
|
|
9940
|
+
kind: input4.kind,
|
|
9941
|
+
display_name: input4.display_name ?? null,
|
|
9942
|
+
default_ttl_s: input4.default_ttl_s,
|
|
9943
|
+
created_at: nowIso()
|
|
9944
|
+
};
|
|
9945
|
+
try {
|
|
9946
|
+
database.prepare(
|
|
9947
|
+
`INSERT INTO inventories (slug, kind, display_name, default_ttl_s, created_at)
|
|
9948
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
9949
|
+
).run(row.slug, row.kind, row.display_name, row.default_ttl_s, row.created_at);
|
|
9950
|
+
} catch (err2) {
|
|
9951
|
+
if (err2 instanceof Error && err2.message.includes("UNIQUE")) {
|
|
9952
|
+
throw new DuplicateInventoryError(input4.slug);
|
|
9953
|
+
}
|
|
9954
|
+
throw err2;
|
|
9955
|
+
}
|
|
9956
|
+
return row;
|
|
9957
|
+
}
|
|
9958
|
+
function listInventories() {
|
|
9959
|
+
const database = getLeasesDb();
|
|
9960
|
+
return database.prepare(
|
|
9961
|
+
`SELECT slug, kind, display_name, default_ttl_s, created_at
|
|
9962
|
+
FROM inventories ORDER BY slug`
|
|
9963
|
+
).all();
|
|
9964
|
+
}
|
|
9965
|
+
function getInventoryDetail(slug) {
|
|
9966
|
+
const database = getLeasesDb();
|
|
9967
|
+
const inv = database.prepare(
|
|
9968
|
+
`SELECT slug, kind, display_name, default_ttl_s, created_at
|
|
9969
|
+
FROM inventories WHERE slug = ?`
|
|
9970
|
+
).get(slug);
|
|
9971
|
+
if (!inv) return null;
|
|
9972
|
+
const members = database.prepare(
|
|
9973
|
+
`SELECT inventory_slug, member_id, status, generation, metadata_json, last_used_at, retired_at
|
|
9974
|
+
FROM inventory_members WHERE inventory_slug = ?
|
|
9975
|
+
ORDER BY member_id`
|
|
9976
|
+
).all(slug);
|
|
9977
|
+
const active_leases = database.prepare(
|
|
9978
|
+
`SELECT lease_id, inventory_slug, member_id, member_gen, state, granted_at, expires_at, released_at, requested_for
|
|
9979
|
+
FROM leases WHERE inventory_slug = ? AND state = 'active'
|
|
9980
|
+
ORDER BY granted_at`
|
|
9981
|
+
).all(slug);
|
|
9982
|
+
return { inventory: inv, members, active_leases };
|
|
9983
|
+
}
|
|
9984
|
+
function addMember(input4) {
|
|
9985
|
+
const database = getLeasesDb();
|
|
9986
|
+
const row = {
|
|
9987
|
+
inventory_slug: input4.inventory_slug,
|
|
9988
|
+
member_id: input4.member_id,
|
|
9989
|
+
status: "idle",
|
|
9990
|
+
generation: 0,
|
|
9991
|
+
metadata_json: input4.metadata ? JSON.stringify(input4.metadata) : null,
|
|
9992
|
+
last_used_at: null,
|
|
9993
|
+
retired_at: null
|
|
9994
|
+
};
|
|
9995
|
+
try {
|
|
9996
|
+
database.prepare(
|
|
9997
|
+
`INSERT INTO inventory_members
|
|
9998
|
+
(inventory_slug, member_id, status, generation, metadata_json)
|
|
9999
|
+
VALUES (?, ?, 'idle', 0, ?)`
|
|
10000
|
+
).run(row.inventory_slug, row.member_id, row.metadata_json);
|
|
10001
|
+
} catch (err2) {
|
|
10002
|
+
if (err2 instanceof Error && err2.message.includes("UNIQUE")) {
|
|
10003
|
+
throw new DuplicateMemberError(input4.inventory_slug, input4.member_id);
|
|
10004
|
+
}
|
|
10005
|
+
throw err2;
|
|
10006
|
+
}
|
|
10007
|
+
return row;
|
|
10008
|
+
}
|
|
10009
|
+
function retireMember(inventorySlug, memberId) {
|
|
10010
|
+
const database = getLeasesDb();
|
|
10011
|
+
const retire = database.transaction(() => {
|
|
10012
|
+
const row = database.prepare(
|
|
10013
|
+
`SELECT status FROM inventory_members
|
|
10014
|
+
WHERE inventory_slug = ? AND member_id = ?`
|
|
10015
|
+
).get(inventorySlug, memberId);
|
|
10016
|
+
if (!row) throw new NotFoundError(`${inventorySlug}/${memberId}`);
|
|
10017
|
+
if (row.status === "leased") {
|
|
10018
|
+
throw new MemberInUseError(inventorySlug, memberId);
|
|
10019
|
+
}
|
|
10020
|
+
database.prepare(
|
|
10021
|
+
`UPDATE inventory_members
|
|
10022
|
+
SET status = 'retired', generation = generation + 1, retired_at = ?
|
|
10023
|
+
WHERE inventory_slug = ? AND member_id = ?`
|
|
10024
|
+
).run(nowIso(), inventorySlug, memberId);
|
|
10025
|
+
});
|
|
10026
|
+
retire.immediate();
|
|
10027
|
+
}
|
|
10028
|
+
function claimLease(slug, ttl_s, requested_for) {
|
|
10029
|
+
const database = getLeasesDb();
|
|
10030
|
+
try {
|
|
10031
|
+
const fn = database.transaction(() => {
|
|
10032
|
+
const now = nowIso();
|
|
10033
|
+
database.prepare(
|
|
10034
|
+
`UPDATE leases
|
|
10035
|
+
SET state = 'expired'
|
|
10036
|
+
WHERE state = 'active'
|
|
10037
|
+
AND expires_at <= ?
|
|
10038
|
+
AND inventory_slug = ?`
|
|
10039
|
+
).run(now, slug);
|
|
10040
|
+
database.prepare(
|
|
10041
|
+
`UPDATE inventory_members
|
|
10042
|
+
SET status = 'idle', generation = generation + 1
|
|
10043
|
+
WHERE inventory_slug = ?
|
|
10044
|
+
AND status = 'leased'
|
|
10045
|
+
AND (inventory_slug, member_id) IN (
|
|
10046
|
+
SELECT inventory_slug, member_id
|
|
10047
|
+
FROM leases
|
|
10048
|
+
WHERE inventory_slug = ?
|
|
10049
|
+
AND state = 'expired'
|
|
10050
|
+
AND released_at IS NULL
|
|
10051
|
+
)
|
|
10052
|
+
AND generation = (
|
|
10053
|
+
SELECT member_gen FROM leases l
|
|
10054
|
+
WHERE l.inventory_slug = inventory_members.inventory_slug
|
|
10055
|
+
AND l.member_id = inventory_members.member_id
|
|
10056
|
+
AND l.state = 'expired'
|
|
10057
|
+
AND l.released_at IS NULL
|
|
10058
|
+
ORDER BY l.granted_at DESC LIMIT 1
|
|
10059
|
+
)`
|
|
10060
|
+
).run(slug, slug);
|
|
10061
|
+
database.prepare(
|
|
10062
|
+
`UPDATE leases
|
|
10063
|
+
SET released_at = ?
|
|
10064
|
+
WHERE state = 'expired'
|
|
10065
|
+
AND released_at IS NULL
|
|
10066
|
+
AND inventory_slug = ?`
|
|
10067
|
+
).run(now, slug);
|
|
10068
|
+
const picked = database.prepare(
|
|
10069
|
+
`UPDATE inventory_members
|
|
10070
|
+
SET status = 'leased',
|
|
10071
|
+
generation = generation + 1,
|
|
10072
|
+
last_used_at = ?
|
|
10073
|
+
WHERE (inventory_slug, member_id) = (
|
|
10074
|
+
SELECT inventory_slug, member_id
|
|
10075
|
+
FROM inventory_members
|
|
10076
|
+
WHERE inventory_slug = ? AND status = 'idle'
|
|
10077
|
+
ORDER BY last_used_at ASC NULLS FIRST
|
|
10078
|
+
LIMIT 1
|
|
10079
|
+
)
|
|
10080
|
+
RETURNING member_id, generation, metadata_json`
|
|
10081
|
+
).get(now, slug);
|
|
10082
|
+
if (!picked) {
|
|
10083
|
+
throw new NoIdleMemberError(slug);
|
|
10084
|
+
}
|
|
10085
|
+
const lease_id = randomUUID2();
|
|
10086
|
+
const expires_at = new Date(Date.now() + ttl_s * 1e3).toISOString();
|
|
10087
|
+
database.prepare(
|
|
10088
|
+
`INSERT INTO leases
|
|
10089
|
+
(lease_id, inventory_slug, member_id, member_gen,
|
|
10090
|
+
state, granted_at, expires_at, requested_for)
|
|
10091
|
+
VALUES (?, ?, ?, ?, 'active', ?, ?, ?)`
|
|
10092
|
+
).run(
|
|
10093
|
+
lease_id,
|
|
10094
|
+
slug,
|
|
10095
|
+
picked.member_id,
|
|
10096
|
+
picked.generation,
|
|
10097
|
+
now,
|
|
10098
|
+
expires_at,
|
|
10099
|
+
requested_for ?? null
|
|
10100
|
+
);
|
|
10101
|
+
database.prepare(
|
|
10102
|
+
`INSERT INTO lease_events (lease_id, event, at, detail_json)
|
|
10103
|
+
VALUES (?, 'claimed', ?, ?)`
|
|
10104
|
+
).run(lease_id, now, JSON.stringify({ member_id: picked.member_id }));
|
|
10105
|
+
return {
|
|
10106
|
+
lease_id,
|
|
10107
|
+
inventory_slug: slug,
|
|
10108
|
+
member_id: picked.member_id,
|
|
10109
|
+
member_gen: picked.generation,
|
|
10110
|
+
granted_at: now,
|
|
10111
|
+
expires_at,
|
|
10112
|
+
metadata: picked.metadata_json ? JSON.parse(picked.metadata_json) : null
|
|
10113
|
+
};
|
|
10114
|
+
});
|
|
10115
|
+
return fn.immediate();
|
|
10116
|
+
} catch (err2) {
|
|
10117
|
+
if (isBusyError(err2)) throw new LeaseContentionError(slug);
|
|
10118
|
+
throw err2;
|
|
10119
|
+
}
|
|
10120
|
+
}
|
|
10121
|
+
function releaseLease(lease_id) {
|
|
10122
|
+
const database = getLeasesDb();
|
|
10123
|
+
try {
|
|
10124
|
+
const fn = database.transaction(() => {
|
|
10125
|
+
const now = nowIso();
|
|
10126
|
+
const memberRes = database.prepare(
|
|
10127
|
+
`UPDATE inventory_members
|
|
10128
|
+
SET status = 'idle', generation = generation + 1
|
|
10129
|
+
WHERE (inventory_slug, member_id) = (
|
|
10130
|
+
SELECT inventory_slug, member_id FROM leases
|
|
10131
|
+
WHERE lease_id = ? AND state = 'active'
|
|
10132
|
+
)
|
|
10133
|
+
AND generation = (
|
|
10134
|
+
SELECT member_gen FROM leases
|
|
10135
|
+
WHERE lease_id = ? AND state = 'active'
|
|
10136
|
+
)`
|
|
10137
|
+
).run(lease_id, lease_id);
|
|
10138
|
+
if (memberRes.changes === 0) {
|
|
10139
|
+
throw new StaleLeaseError(lease_id);
|
|
10140
|
+
}
|
|
10141
|
+
database.prepare(
|
|
10142
|
+
`UPDATE leases
|
|
10143
|
+
SET state = 'released', released_at = ?
|
|
10144
|
+
WHERE lease_id = ? AND state = 'active'`
|
|
10145
|
+
).run(now, lease_id);
|
|
10146
|
+
database.prepare(
|
|
10147
|
+
`INSERT INTO lease_events (lease_id, event, at)
|
|
10148
|
+
VALUES (?, 'released', ?)`
|
|
10149
|
+
).run(lease_id, now);
|
|
10150
|
+
});
|
|
10151
|
+
fn.immediate();
|
|
10152
|
+
} catch (err2) {
|
|
10153
|
+
if (isBusyError(err2)) throw new LeaseContentionError(lease_id);
|
|
10154
|
+
throw err2;
|
|
10155
|
+
}
|
|
10156
|
+
}
|
|
10157
|
+
function extendLease(lease_id, ttl_s) {
|
|
10158
|
+
const database = getLeasesDb();
|
|
10159
|
+
try {
|
|
10160
|
+
const fn = database.transaction(() => {
|
|
10161
|
+
const now = nowIso();
|
|
10162
|
+
const new_expires_at = new Date(Date.now() + ttl_s * 1e3).toISOString();
|
|
10163
|
+
const res = database.prepare(
|
|
10164
|
+
`UPDATE leases
|
|
10165
|
+
SET expires_at = ?
|
|
10166
|
+
WHERE lease_id = ?
|
|
10167
|
+
AND state = 'active'
|
|
10168
|
+
AND expires_at > ?
|
|
10169
|
+
AND member_gen = (
|
|
10170
|
+
SELECT generation FROM inventory_members
|
|
10171
|
+
WHERE inventory_slug = leases.inventory_slug
|
|
10172
|
+
AND member_id = leases.member_id
|
|
10173
|
+
)`
|
|
10174
|
+
).run(new_expires_at, lease_id, now);
|
|
10175
|
+
if (res.changes === 0) {
|
|
10176
|
+
throw new StaleLeaseError(lease_id);
|
|
10177
|
+
}
|
|
10178
|
+
database.prepare(
|
|
10179
|
+
`INSERT INTO lease_events (lease_id, event, at, detail_json)
|
|
10180
|
+
VALUES (?, 'extended', ?, ?)`
|
|
10181
|
+
).run(lease_id, now, JSON.stringify({ new_expires_at }));
|
|
10182
|
+
return { new_expires_at };
|
|
10183
|
+
});
|
|
10184
|
+
return fn.immediate();
|
|
10185
|
+
} catch (err2) {
|
|
10186
|
+
if (isBusyError(err2)) throw new LeaseContentionError(lease_id);
|
|
10187
|
+
throw err2;
|
|
10188
|
+
}
|
|
10189
|
+
}
|
|
10190
|
+
function getLease(lease_id) {
|
|
10191
|
+
const database = getLeasesDb();
|
|
10192
|
+
const row = database.prepare(
|
|
10193
|
+
`SELECT lease_id, inventory_slug, member_id, member_gen, state,
|
|
10194
|
+
granted_at, expires_at, released_at, requested_for
|
|
10195
|
+
FROM leases WHERE lease_id = ?`
|
|
10196
|
+
).get(lease_id);
|
|
10197
|
+
return row ?? null;
|
|
10198
|
+
}
|
|
10199
|
+
function listLeases(filter) {
|
|
10200
|
+
const database = getLeasesDb();
|
|
10201
|
+
const where = [];
|
|
10202
|
+
const params2 = [];
|
|
10203
|
+
if (filter?.inventory) {
|
|
10204
|
+
where.push("inventory_slug = ?");
|
|
10205
|
+
params2.push(filter.inventory);
|
|
10206
|
+
}
|
|
10207
|
+
if (filter?.state) {
|
|
10208
|
+
where.push("state = ?");
|
|
10209
|
+
params2.push(filter.state);
|
|
10210
|
+
}
|
|
10211
|
+
const whereSql = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
|
|
10212
|
+
return database.prepare(
|
|
10213
|
+
`SELECT lease_id, inventory_slug, member_id, member_gen, state,
|
|
10214
|
+
granted_at, expires_at, released_at, requested_for
|
|
10215
|
+
FROM leases ${whereSql}
|
|
10216
|
+
ORDER BY granted_at DESC`
|
|
10217
|
+
).all(...params2);
|
|
10218
|
+
}
|
|
10219
|
+
function gcExpiredLeases() {
|
|
10220
|
+
const database = getLeasesDb();
|
|
10221
|
+
try {
|
|
10222
|
+
const fn = database.transaction(() => {
|
|
10223
|
+
const now = nowIso();
|
|
10224
|
+
const expRes = database.prepare(
|
|
10225
|
+
`UPDATE leases SET state = 'expired'
|
|
10226
|
+
WHERE state = 'active' AND expires_at <= ?`
|
|
10227
|
+
).run(now);
|
|
10228
|
+
database.prepare(
|
|
10229
|
+
`UPDATE inventory_members
|
|
10230
|
+
SET status = 'idle', generation = generation + 1
|
|
10231
|
+
WHERE status = 'leased'
|
|
10232
|
+
AND (inventory_slug, member_id) IN (
|
|
10233
|
+
SELECT inventory_slug, member_id FROM leases
|
|
10234
|
+
WHERE state = 'expired' AND released_at IS NULL
|
|
10235
|
+
)
|
|
10236
|
+
AND generation = (
|
|
10237
|
+
SELECT member_gen FROM leases l
|
|
10238
|
+
WHERE l.inventory_slug = inventory_members.inventory_slug
|
|
10239
|
+
AND l.member_id = inventory_members.member_id
|
|
10240
|
+
AND l.state = 'expired'
|
|
10241
|
+
AND l.released_at IS NULL
|
|
10242
|
+
ORDER BY l.granted_at DESC LIMIT 1
|
|
10243
|
+
)`
|
|
10244
|
+
).run();
|
|
10245
|
+
database.prepare(
|
|
10246
|
+
`UPDATE leases SET released_at = ?
|
|
10247
|
+
WHERE state = 'expired' AND released_at IS NULL AND expires_at <= ?`
|
|
10248
|
+
).run(now, now);
|
|
10249
|
+
return expRes.changes;
|
|
10250
|
+
});
|
|
10251
|
+
return fn.immediate();
|
|
10252
|
+
} catch (err2) {
|
|
10253
|
+
if (isBusyError(err2)) throw new LeaseContentionError("gc");
|
|
10254
|
+
throw err2;
|
|
10255
|
+
}
|
|
10256
|
+
}
|
|
10257
|
+
function forceReleaseLease(lease_id) {
|
|
10258
|
+
const database = getLeasesDb();
|
|
10259
|
+
try {
|
|
10260
|
+
const fn = database.transaction(() => {
|
|
10261
|
+
const now = nowIso();
|
|
10262
|
+
const lookup = database.prepare(
|
|
10263
|
+
`SELECT inventory_slug, member_id, member_gen, state
|
|
10264
|
+
FROM leases WHERE lease_id = ?`
|
|
10265
|
+
).get(lease_id);
|
|
10266
|
+
if (!lookup) throw new NotFoundError(lease_id);
|
|
10267
|
+
if (lookup.state === "revoked") return { member_freed: false };
|
|
10268
|
+
const memberRes = database.prepare(
|
|
10269
|
+
`UPDATE inventory_members
|
|
10270
|
+
SET status = 'idle', generation = generation + 1
|
|
10271
|
+
WHERE inventory_slug = ? AND member_id = ?
|
|
10272
|
+
AND generation = ?`
|
|
10273
|
+
).run(lookup.inventory_slug, lookup.member_id, lookup.member_gen);
|
|
10274
|
+
database.prepare(
|
|
10275
|
+
`UPDATE leases
|
|
10276
|
+
SET state = 'revoked', released_at = ?
|
|
10277
|
+
WHERE lease_id = ? AND state != 'revoked'`
|
|
10278
|
+
).run(now, lease_id);
|
|
10279
|
+
const member_freed = memberRes.changes > 0;
|
|
10280
|
+
database.prepare(
|
|
10281
|
+
`INSERT INTO lease_events (lease_id, event, at, detail_json)
|
|
10282
|
+
VALUES (?, 'force_released', ?, ?)`
|
|
10283
|
+
).run(lease_id, now, JSON.stringify({ member_freed }));
|
|
10284
|
+
return { member_freed };
|
|
10285
|
+
});
|
|
10286
|
+
return fn.immediate();
|
|
10287
|
+
} catch (err2) {
|
|
10288
|
+
if (isBusyError(err2)) throw new LeaseContentionError(lease_id);
|
|
10289
|
+
throw err2;
|
|
10290
|
+
}
|
|
10291
|
+
}
|
|
10292
|
+
|
|
10293
|
+
// src/dashboard/api-leases.ts
|
|
10294
|
+
function createLeasesRouter(broadcast) {
|
|
10295
|
+
const router = Router4();
|
|
10296
|
+
router.get("/", (_req, res) => {
|
|
10297
|
+
try {
|
|
10298
|
+
initLeasesDb();
|
|
10299
|
+
const inventories = listInventories();
|
|
10300
|
+
const details = inventories.map((inv) => getInventoryDetail(inv.slug)).filter((d) => d !== null);
|
|
10301
|
+
res.json({ inventories: details });
|
|
10302
|
+
} catch (error) {
|
|
10303
|
+
res.status(500).json({
|
|
10304
|
+
error: error instanceof Error ? error.message : "List failed"
|
|
10305
|
+
});
|
|
10306
|
+
}
|
|
10307
|
+
});
|
|
10308
|
+
router.get("/:slug", (req, res) => {
|
|
10309
|
+
try {
|
|
10310
|
+
initLeasesDb();
|
|
10311
|
+
const detail = getInventoryDetail(req.params.slug);
|
|
10312
|
+
if (!detail) {
|
|
10313
|
+
res.status(404).json({ error: "Inventory not found" });
|
|
10314
|
+
return;
|
|
10315
|
+
}
|
|
10316
|
+
res.json(detail);
|
|
10317
|
+
} catch (error) {
|
|
10318
|
+
res.status(500).json({
|
|
10319
|
+
error: error instanceof Error ? error.message : "Read failed"
|
|
10320
|
+
});
|
|
10321
|
+
}
|
|
10322
|
+
});
|
|
10323
|
+
router.post("/:slug/force-release/:lease_id", (req, res) => {
|
|
10324
|
+
try {
|
|
10325
|
+
initLeasesDb();
|
|
10326
|
+
const result = forceReleaseLease(req.params.lease_id);
|
|
10327
|
+
broadcast?.({
|
|
10328
|
+
type: "leases-updated",
|
|
10329
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10330
|
+
});
|
|
10331
|
+
res.json({ ...result, lease_id: req.params.lease_id });
|
|
10332
|
+
} catch (error) {
|
|
10333
|
+
if (error instanceof NotFoundError) {
|
|
10334
|
+
res.status(404).json({ error: "Lease not found" });
|
|
10335
|
+
return;
|
|
10336
|
+
}
|
|
10337
|
+
if (error instanceof LeaseContentionError) {
|
|
10338
|
+
res.status(503).json({ error: "Contention timeout; retry" });
|
|
10339
|
+
return;
|
|
10340
|
+
}
|
|
10341
|
+
res.status(500).json({
|
|
10342
|
+
error: error instanceof Error ? error.message : "Force release failed"
|
|
10343
|
+
});
|
|
10344
|
+
}
|
|
10345
|
+
});
|
|
10346
|
+
return router;
|
|
10347
|
+
}
|
|
10348
|
+
|
|
9747
10349
|
// src/dashboard/api-playbooks.ts
|
|
9748
10350
|
init_api();
|
|
9749
10351
|
init_parser();
|
|
@@ -9752,11 +10354,11 @@ init_timestamp();
|
|
|
9752
10354
|
init_fs();
|
|
9753
10355
|
init_playbook();
|
|
9754
10356
|
init_playbooks();
|
|
9755
|
-
import { Router as
|
|
9756
|
-
import { resolve as
|
|
10357
|
+
import { Router as Router5 } from "express";
|
|
10358
|
+
import { resolve as resolve18 } from "path";
|
|
9757
10359
|
import { readFile as readFile12, unlink as unlink2 } from "fs/promises";
|
|
9758
10360
|
function createPlaybooksRouter(playbooksDir3) {
|
|
9759
|
-
const router =
|
|
10361
|
+
const router = Router5();
|
|
9760
10362
|
router.get("/", async (_req, res) => {
|
|
9761
10363
|
try {
|
|
9762
10364
|
const playbooks = await listPlaybooks(playbooksDir3);
|
|
@@ -9823,7 +10425,7 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
9823
10425
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
9824
10426
|
return;
|
|
9825
10427
|
}
|
|
9826
|
-
const filePath =
|
|
10428
|
+
const filePath = resolve18(playbooksDir3, resolved.filename);
|
|
9827
10429
|
const content = await readFile12(filePath, "utf-8");
|
|
9828
10430
|
res.json({
|
|
9829
10431
|
documentType: "playbook",
|
|
@@ -9849,7 +10451,7 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
9849
10451
|
return;
|
|
9850
10452
|
}
|
|
9851
10453
|
await ensureDir(playbooksDir3);
|
|
9852
|
-
const filePath =
|
|
10454
|
+
const filePath = resolve18(playbooksDir3, `${slug}.md`);
|
|
9853
10455
|
if (await fileExists(filePath)) {
|
|
9854
10456
|
res.status(409).json({ error: `Playbook "${slug}" already exists` });
|
|
9855
10457
|
return;
|
|
@@ -9873,7 +10475,7 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
9873
10475
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
9874
10476
|
return;
|
|
9875
10477
|
}
|
|
9876
|
-
const filePath =
|
|
10478
|
+
const filePath = resolve18(playbooksDir3, resolved.filename);
|
|
9877
10479
|
await writeFileForce(filePath, content);
|
|
9878
10480
|
await rebuildPlaybookManifest(playbooksDir3);
|
|
9879
10481
|
res.json({ slug: resolved.slug, path: filePath });
|
|
@@ -9892,7 +10494,7 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
9892
10494
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
9893
10495
|
return;
|
|
9894
10496
|
}
|
|
9895
|
-
const filePath =
|
|
10497
|
+
const filePath = resolve18(playbooksDir3, resolved.filename);
|
|
9896
10498
|
await unlink2(filePath);
|
|
9897
10499
|
await removeFromDisabledList(resolved.slug);
|
|
9898
10500
|
await rebuildPlaybookManifest(playbooksDir3);
|
|
@@ -9911,9 +10513,9 @@ init_fs_migration();
|
|
|
9911
10513
|
init_parser2();
|
|
9912
10514
|
init_fs();
|
|
9913
10515
|
init_paths();
|
|
9914
|
-
import { Router as
|
|
10516
|
+
import { Router as Router6 } from "express";
|
|
9915
10517
|
import { readdir as readdir9 } from "fs/promises";
|
|
9916
|
-
import { resolve as resolvePath, dirname as
|
|
10518
|
+
import { resolve as resolvePath, dirname as dirname5 } from "path";
|
|
9917
10519
|
import { rename as rename3, mkdir as mkdir2 } from "fs/promises";
|
|
9918
10520
|
|
|
9919
10521
|
// src/dashboard/todos-locks.ts
|
|
@@ -9959,7 +10561,7 @@ function touchItem(item) {
|
|
|
9959
10561
|
item.updatedAt = now;
|
|
9960
10562
|
}
|
|
9961
10563
|
function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
9962
|
-
const router =
|
|
10564
|
+
const router = Router6();
|
|
9963
10565
|
function broadcastUpdate() {
|
|
9964
10566
|
broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9965
10567
|
}
|
|
@@ -10089,7 +10691,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
10089
10691
|
router.post("/:workspace/archive", async (req, res) => {
|
|
10090
10692
|
try {
|
|
10091
10693
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
10092
|
-
const { resolve:
|
|
10694
|
+
const { resolve: resolve63 } = await import("path");
|
|
10093
10695
|
const { readFile: readFile43 } = await import("fs/promises");
|
|
10094
10696
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
10095
10697
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
@@ -10106,7 +10708,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
10106
10708
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
10107
10709
|
);
|
|
10108
10710
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
10109
|
-
await ensureDir(
|
|
10711
|
+
await ensureDir(resolve63(todosDir2, "archive"));
|
|
10110
10712
|
let archContent = "";
|
|
10111
10713
|
if (await fileExists(archFile)) {
|
|
10112
10714
|
archContent = await readFile43(archFile, "utf-8");
|
|
@@ -10513,7 +11115,7 @@ workspace: ${workspace}
|
|
|
10513
11115
|
if (await fileExists(newPlanDir)) {
|
|
10514
11116
|
return { status: 409, error: "plan dir already exists in target" };
|
|
10515
11117
|
}
|
|
10516
|
-
await mkdir2(
|
|
11118
|
+
await mkdir2(dirname5(newPlanDir), { recursive: true });
|
|
10517
11119
|
await rename3(item.planDir, newPlanDir);
|
|
10518
11120
|
item.planDir = newPlanDir;
|
|
10519
11121
|
}
|
|
@@ -10589,9 +11191,9 @@ init_parser2();
|
|
|
10589
11191
|
init_fs();
|
|
10590
11192
|
init_paths();
|
|
10591
11193
|
init_slug();
|
|
10592
|
-
import { Router as
|
|
11194
|
+
import { Router as Router7 } from "express";
|
|
10593
11195
|
import { mkdir as mkdir3, readFile as readFile14, rename as rename4 } from "fs/promises";
|
|
10594
|
-
import { resolve as
|
|
11196
|
+
import { resolve as resolve20, dirname as dirname6 } from "path";
|
|
10595
11197
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
10596
11198
|
function touchItem2(item) {
|
|
10597
11199
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -10606,7 +11208,7 @@ function params(req) {
|
|
|
10606
11208
|
return req.params;
|
|
10607
11209
|
}
|
|
10608
11210
|
async function projectExists(projectsDir2, slug) {
|
|
10609
|
-
return fileExists(
|
|
11211
|
+
return fileExists(resolve20(projectsDir2, slug, "project.md"));
|
|
10610
11212
|
}
|
|
10611
11213
|
async function ensureProjectTodosDir(projectsDir2, slug) {
|
|
10612
11214
|
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
@@ -10623,7 +11225,7 @@ async function ensureProjectTodosDir(projectsDir2, slug) {
|
|
|
10623
11225
|
throw err2;
|
|
10624
11226
|
}
|
|
10625
11227
|
try {
|
|
10626
|
-
await mkdir3(
|
|
11228
|
+
await mkdir3(resolve20(todosDir2, "archive"), { recursive: false });
|
|
10627
11229
|
} catch (err2) {
|
|
10628
11230
|
const code = err2.code;
|
|
10629
11231
|
if (code === "EEXIST") return;
|
|
@@ -10639,7 +11241,7 @@ function notFound(res, slug) {
|
|
|
10639
11241
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
10640
11242
|
}
|
|
10641
11243
|
function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
|
|
10642
|
-
const router =
|
|
11244
|
+
const router = Router7({ mergeParams: true });
|
|
10643
11245
|
function broadcastUpdate(projectSlug) {
|
|
10644
11246
|
broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
10645
11247
|
}
|
|
@@ -11247,18 +11849,18 @@ workspace: ${slug}
|
|
|
11247
11849
|
if (tg.includes("/")) {
|
|
11248
11850
|
const parts = tg.split("/");
|
|
11249
11851
|
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
11250
|
-
assignmentDir =
|
|
11852
|
+
assignmentDir = resolve20(projectsDir2, parts[0], "assignments", parts[1]);
|
|
11251
11853
|
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
11252
11854
|
} else if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tg)) {
|
|
11253
|
-
assignmentDir =
|
|
11855
|
+
assignmentDir = resolve20(assignmentsDirFn(), tg);
|
|
11254
11856
|
assignmentRef = tg;
|
|
11255
11857
|
} else {
|
|
11256
11858
|
return { error: `Invalid target.assignment "${tg}"` };
|
|
11257
11859
|
}
|
|
11258
|
-
const assignmentMdPath3 =
|
|
11860
|
+
const assignmentMdPath3 = resolve20(assignmentDir, "assignment.md");
|
|
11259
11861
|
if (!await fileExists(assignmentMdPath3)) return { error: `Target assignment not found: ${assignmentMdPath3}` };
|
|
11260
11862
|
}
|
|
11261
|
-
const assignmentMdPath2 =
|
|
11863
|
+
const assignmentMdPath2 = resolve20(assignmentDir, "assignment.md");
|
|
11262
11864
|
let content = await readFile14(assignmentMdPath2, "utf-8");
|
|
11263
11865
|
content = appendTodosToAssignmentBody2(
|
|
11264
11866
|
content,
|
|
@@ -11388,7 +11990,7 @@ workspace: ${slug}
|
|
|
11388
11990
|
if (await fileExists(newPlanDir)) {
|
|
11389
11991
|
return { status: 409, error: "plan dir already exists in target" };
|
|
11390
11992
|
}
|
|
11391
|
-
await mkdir3(
|
|
11993
|
+
await mkdir3(dirname6(newPlanDir), { recursive: true });
|
|
11392
11994
|
await rename4(item.planDir, newPlanDir);
|
|
11393
11995
|
item.planDir = newPlanDir;
|
|
11394
11996
|
}
|
|
@@ -11451,7 +12053,7 @@ workspace: ${slug}
|
|
|
11451
12053
|
|
|
11452
12054
|
// src/dashboard/api-backup.ts
|
|
11453
12055
|
init_config2();
|
|
11454
|
-
import { Router as
|
|
12056
|
+
import { Router as Router8 } from "express";
|
|
11455
12057
|
|
|
11456
12058
|
// src/utils/github-backup.ts
|
|
11457
12059
|
init_paths();
|
|
@@ -11460,7 +12062,7 @@ init_config2();
|
|
|
11460
12062
|
import { execFile as execFile2 } from "child_process";
|
|
11461
12063
|
import { promisify as promisify2 } from "util";
|
|
11462
12064
|
import { cp, mkdtemp, rm as rm2, readFile as readFile15, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename5 } from "fs/promises";
|
|
11463
|
-
import { resolve as
|
|
12065
|
+
import { resolve as resolve21, join as join2 } from "path";
|
|
11464
12066
|
import { tmpdir } from "os";
|
|
11465
12067
|
var exec2 = promisify2(execFile2);
|
|
11466
12068
|
var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
|
|
@@ -11500,7 +12102,7 @@ async function resolveCategoryPath(category) {
|
|
|
11500
12102
|
case "servers":
|
|
11501
12103
|
return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
|
|
11502
12104
|
case "config":
|
|
11503
|
-
return { sourcePath:
|
|
12105
|
+
return { sourcePath: resolve21(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
|
|
11504
12106
|
}
|
|
11505
12107
|
}
|
|
11506
12108
|
async function checkGitInstalled() {
|
|
@@ -11511,7 +12113,7 @@ async function checkGitInstalled() {
|
|
|
11511
12113
|
}
|
|
11512
12114
|
}
|
|
11513
12115
|
async function acquireLock() {
|
|
11514
|
-
const lockPath =
|
|
12116
|
+
const lockPath = resolve21(syntaurRoot(), LOCK_FILE_NAME);
|
|
11515
12117
|
await ensureDir(syntaurRoot());
|
|
11516
12118
|
try {
|
|
11517
12119
|
const handle = await open(lockPath, "wx");
|
|
@@ -11558,7 +12160,7 @@ async function copyRecursive(src, dest) {
|
|
|
11558
12160
|
await ensureDir(dest);
|
|
11559
12161
|
await cp(src, dest, { recursive: true, force: true });
|
|
11560
12162
|
} else {
|
|
11561
|
-
await ensureDir(
|
|
12163
|
+
await ensureDir(resolve21(dest, ".."));
|
|
11562
12164
|
await cp(src, dest, { force: true });
|
|
11563
12165
|
}
|
|
11564
12166
|
}
|
|
@@ -11606,7 +12208,7 @@ async function backupToGithub(overrides) {
|
|
|
11606
12208
|
}
|
|
11607
12209
|
if (category === "config") {
|
|
11608
12210
|
const sanitized = await readSanitizedConfig(sourcePath);
|
|
11609
|
-
await ensureDir(
|
|
12211
|
+
await ensureDir(resolve21(destPath, ".."));
|
|
11610
12212
|
await writeFile4(destPath, sanitized, "utf-8");
|
|
11611
12213
|
} else {
|
|
11612
12214
|
await copyRecursive(sourcePath, destPath);
|
|
@@ -11660,7 +12262,7 @@ async function backupToGithub(overrides) {
|
|
|
11660
12262
|
}
|
|
11661
12263
|
async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
11662
12264
|
if (isFile) {
|
|
11663
|
-
await ensureDir(
|
|
12265
|
+
await ensureDir(resolve21(localPath, ".."));
|
|
11664
12266
|
await cp(repoSrcPath, localPath, { force: true });
|
|
11665
12267
|
return;
|
|
11666
12268
|
}
|
|
@@ -11761,7 +12363,7 @@ async function restoreFromGithub(overrides) {
|
|
|
11761
12363
|
}
|
|
11762
12364
|
async function getBackupStatus() {
|
|
11763
12365
|
const config = await readConfig();
|
|
11764
|
-
const lockPath =
|
|
12366
|
+
const lockPath = resolve21(syntaurRoot(), LOCK_FILE_NAME);
|
|
11765
12367
|
const locked = await fileExists(lockPath);
|
|
11766
12368
|
return {
|
|
11767
12369
|
repo: config.backup?.repo ?? null,
|
|
@@ -11774,7 +12376,7 @@ async function getBackupStatus() {
|
|
|
11774
12376
|
|
|
11775
12377
|
// src/dashboard/api-backup.ts
|
|
11776
12378
|
function createBackupRouter() {
|
|
11777
|
-
const router =
|
|
12379
|
+
const router = Router8();
|
|
11778
12380
|
router.get("/", async (_req, res) => {
|
|
11779
12381
|
try {
|
|
11780
12382
|
const status = await getBackupStatus();
|
|
@@ -12095,10 +12697,11 @@ function createDashboardServer(options) {
|
|
|
12095
12697
|
migrateFromMarkdown(projectsDir2).catch((err2) => {
|
|
12096
12698
|
console.error("Session migration from markdown failed:", err2);
|
|
12097
12699
|
});
|
|
12700
|
+
initLeasesDb();
|
|
12098
12701
|
(async () => {
|
|
12099
12702
|
try {
|
|
12100
12703
|
const configResult = await migrateLegacyConfig(
|
|
12101
|
-
|
|
12704
|
+
resolve22(syntaurRoot(), "config.md")
|
|
12102
12705
|
);
|
|
12103
12706
|
const projectResult = await migrateLegacyProjectFiles(projectsDir2);
|
|
12104
12707
|
const summary = summarizeMigration(projectResult, configResult);
|
|
@@ -12428,6 +13031,7 @@ function createDashboardServer(options) {
|
|
|
12428
13031
|
});
|
|
12429
13032
|
app.use(createWriteRouter(projectsDir2, assignmentsDir2));
|
|
12430
13033
|
app.use("/api/servers", createServersRouter(serversDir2, projectsDir2, assignmentsDir2));
|
|
13034
|
+
app.use("/api/leases", createLeasesRouter(broadcast));
|
|
12431
13035
|
app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2));
|
|
12432
13036
|
app.use("/api/playbooks", createPlaybooksRouter(playbooksDir3));
|
|
12433
13037
|
app.get("/api/memories", async (_req, res) => {
|
|
@@ -12453,14 +13057,14 @@ function createDashboardServer(options) {
|
|
|
12453
13057
|
app.use("/api/backup", createBackupRouter());
|
|
12454
13058
|
if (serveStaticUi && dashboardDistPath) {
|
|
12455
13059
|
const sendOpts = { dotfiles: "allow" };
|
|
12456
|
-
app.use("/assets", express.static(
|
|
13060
|
+
app.use("/assets", express.static(resolve22(dashboardDistPath, "assets"), sendOpts));
|
|
12457
13061
|
app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
|
|
12458
13062
|
app.get("{*path}", async (req, res) => {
|
|
12459
13063
|
if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
|
|
12460
13064
|
res.status(404).json({ error: "Not Found" });
|
|
12461
13065
|
return;
|
|
12462
13066
|
}
|
|
12463
|
-
const indexPath =
|
|
13067
|
+
const indexPath = resolve22(dashboardDistPath, "index.html");
|
|
12464
13068
|
if (!await fileExists(indexPath)) {
|
|
12465
13069
|
res.status(503).send(
|
|
12466
13070
|
'Dashboard not built. Run "npm run build:dashboard" first.'
|
|
@@ -12484,6 +13088,7 @@ function createDashboardServer(options) {
|
|
|
12484
13088
|
serversDir: serversDir2,
|
|
12485
13089
|
playbooksDir: playbooksDir3,
|
|
12486
13090
|
todosDir: todosDir2,
|
|
13091
|
+
dbPath: resolve22(syntaurRoot(), "syntaur.db"),
|
|
12487
13092
|
onMessage: broadcast
|
|
12488
13093
|
});
|
|
12489
13094
|
startAutodiscovery({ serversDir: serversDir2, projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, excludePids: /* @__PURE__ */ new Set([process.pid]) });
|
|
@@ -12498,7 +13103,7 @@ function createDashboardServer(options) {
|
|
|
12498
13103
|
}
|
|
12499
13104
|
});
|
|
12500
13105
|
server.listen(port, () => {
|
|
12501
|
-
const portFile =
|
|
13106
|
+
const portFile = resolve22(syntaurRoot(), "dashboard-port");
|
|
12502
13107
|
writeFile5(portFile, String(port), "utf-8").catch(() => {
|
|
12503
13108
|
});
|
|
12504
13109
|
resolvePromise();
|
|
@@ -12511,11 +13116,12 @@ function createDashboardServer(options) {
|
|
|
12511
13116
|
await watcherHandle.close();
|
|
12512
13117
|
}
|
|
12513
13118
|
closeSessionDb();
|
|
13119
|
+
closeLeasesDb();
|
|
12514
13120
|
for (const client of clients) {
|
|
12515
13121
|
client.terminate();
|
|
12516
13122
|
}
|
|
12517
13123
|
clients.clear();
|
|
12518
|
-
const portFile =
|
|
13124
|
+
const portFile = resolve22(syntaurRoot(), "dashboard-port");
|
|
12519
13125
|
await unlink4(portFile).catch(() => {
|
|
12520
13126
|
});
|
|
12521
13127
|
server.closeAllConnections?.();
|
|
@@ -12595,8 +13201,8 @@ async function dashboardCommand(options) {
|
|
|
12595
13201
|
port = availablePort;
|
|
12596
13202
|
}
|
|
12597
13203
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
12598
|
-
const packageRoot =
|
|
12599
|
-
const dashboardDist =
|
|
13204
|
+
const packageRoot = resolve23(dirname7(thisFile), "..");
|
|
13205
|
+
const dashboardDist = resolve23(packageRoot, "dashboard", "dist");
|
|
12600
13206
|
const server = createDashboardServer({
|
|
12601
13207
|
port,
|
|
12602
13208
|
projectsDir: projectsDir2,
|
|
@@ -12610,8 +13216,8 @@ async function dashboardCommand(options) {
|
|
|
12610
13216
|
await server.start();
|
|
12611
13217
|
let viteProcess = null;
|
|
12612
13218
|
if (mode === "dev") {
|
|
12613
|
-
const dashboardDir =
|
|
12614
|
-
const viteBin =
|
|
13219
|
+
const dashboardDir = resolve23(packageRoot, "dashboard");
|
|
13220
|
+
const viteBin = resolve23(dashboardDir, "node_modules", ".bin", "vite");
|
|
12615
13221
|
if (!await fileExists(viteBin)) {
|
|
12616
13222
|
console.error(
|
|
12617
13223
|
'Vite not found. Run "npm ci --prefix dashboard" first, or use the default bundled dashboard mode.'
|
|
@@ -12686,7 +13292,7 @@ init_config2();
|
|
|
12686
13292
|
init_slug();
|
|
12687
13293
|
init_lifecycle();
|
|
12688
13294
|
init_assignment_resolver();
|
|
12689
|
-
import { resolve as
|
|
13295
|
+
import { resolve as resolve24 } from "path";
|
|
12690
13296
|
async function runTransition(assignment, command, options = {}) {
|
|
12691
13297
|
const config = await readConfig();
|
|
12692
13298
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
@@ -12697,8 +13303,8 @@ async function runTransition(assignment, command, options = {}) {
|
|
|
12697
13303
|
if (!isValidSlug(assignment)) {
|
|
12698
13304
|
throw new Error(`Invalid assignment slug "${assignment}".`);
|
|
12699
13305
|
}
|
|
12700
|
-
const projectDir =
|
|
12701
|
-
const projectMdPath =
|
|
13306
|
+
const projectDir = resolve24(baseDir, options.project);
|
|
13307
|
+
const projectMdPath = resolve24(projectDir, "project.md");
|
|
12702
13308
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
12703
13309
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
12704
13310
|
}
|
|
@@ -12729,8 +13335,8 @@ async function runAssign(assignment, agent, options = {}) {
|
|
|
12729
13335
|
if (!isValidSlug(assignment)) {
|
|
12730
13336
|
throw new Error(`Invalid assignment slug "${assignment}".`);
|
|
12731
13337
|
}
|
|
12732
|
-
const projectDir =
|
|
12733
|
-
const projectMdPath =
|
|
13338
|
+
const projectDir = resolve24(baseDir, options.project);
|
|
13339
|
+
const projectMdPath = resolve24(projectDir, "project.md");
|
|
12734
13340
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
12735
13341
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
12736
13342
|
}
|
|
@@ -12825,20 +13431,20 @@ import {
|
|
|
12825
13431
|
} from "fs/promises";
|
|
12826
13432
|
import { existsSync } from "fs";
|
|
12827
13433
|
import { homedir as homedir2 } from "os";
|
|
12828
|
-
import { basename as
|
|
13434
|
+
import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve26 } from "path";
|
|
12829
13435
|
|
|
12830
13436
|
// src/utils/package-root.ts
|
|
12831
13437
|
init_fs();
|
|
12832
|
-
import { dirname as
|
|
13438
|
+
import { dirname as dirname8, resolve as resolve25 } from "path";
|
|
12833
13439
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12834
13440
|
async function findPackageRoot(expectedRelativePath) {
|
|
12835
|
-
let currentDir =
|
|
13441
|
+
let currentDir = dirname8(fileURLToPath3(import.meta.url));
|
|
12836
13442
|
while (true) {
|
|
12837
|
-
const candidate =
|
|
13443
|
+
const candidate = resolve25(currentDir, expectedRelativePath);
|
|
12838
13444
|
if (await fileExists(candidate)) {
|
|
12839
13445
|
return currentDir;
|
|
12840
13446
|
}
|
|
12841
|
-
const parentDir =
|
|
13447
|
+
const parentDir = resolve25(currentDir, "..");
|
|
12842
13448
|
if (parentDir === currentDir) {
|
|
12843
13449
|
throw new Error(
|
|
12844
13450
|
`Could not locate package root containing ${expectedRelativePath}.`
|
|
@@ -12859,25 +13465,25 @@ function getPluginManifestRelativePath(pluginKind) {
|
|
|
12859
13465
|
}
|
|
12860
13466
|
function getDefaultPluginTargetDir(pluginKind) {
|
|
12861
13467
|
const home = homedir2();
|
|
12862
|
-
return pluginKind === "claude" ?
|
|
13468
|
+
return pluginKind === "claude" ? resolve26(home, ".claude", "plugins", "syntaur") : resolve26(home, "plugins", "syntaur");
|
|
12863
13469
|
}
|
|
12864
13470
|
function getDefaultMarketplacePath() {
|
|
12865
|
-
return
|
|
13471
|
+
return resolve26(homedir2(), ".agents", "plugins", "marketplace.json");
|
|
12866
13472
|
}
|
|
12867
13473
|
function getClaudeMarketplacesRoot() {
|
|
12868
|
-
return
|
|
13474
|
+
return resolve26(homedir2(), ".claude", "plugins", "marketplaces");
|
|
12869
13475
|
}
|
|
12870
13476
|
function getClaudeKnownMarketplacesPath() {
|
|
12871
|
-
return
|
|
13477
|
+
return resolve26(homedir2(), ".claude", "plugins", "known_marketplaces.json");
|
|
12872
13478
|
}
|
|
12873
13479
|
function getClaudeInstalledPluginsPath() {
|
|
12874
|
-
return
|
|
13480
|
+
return resolve26(homedir2(), ".claude", "plugins", "installed_plugins.json");
|
|
12875
13481
|
}
|
|
12876
13482
|
function getInstallMarkerPath(targetDir) {
|
|
12877
|
-
return
|
|
13483
|
+
return resolve26(targetDir, INSTALL_MARKER_FILENAME);
|
|
12878
13484
|
}
|
|
12879
13485
|
async function readPackageManifest(packageRoot) {
|
|
12880
|
-
const raw = await readFile16(
|
|
13486
|
+
const raw = await readFile16(resolve26(packageRoot, "package.json"), "utf-8");
|
|
12881
13487
|
return JSON.parse(raw);
|
|
12882
13488
|
}
|
|
12883
13489
|
async function readJsonFileIfExists(pathValue) {
|
|
@@ -12893,11 +13499,11 @@ async function readJsonFileIfExists(pathValue) {
|
|
|
12893
13499
|
}
|
|
12894
13500
|
async function readClaudePluginManifest(pluginDir) {
|
|
12895
13501
|
return await readJsonFileIfExists(
|
|
12896
|
-
|
|
13502
|
+
resolve26(pluginDir, ".claude-plugin", "plugin.json")
|
|
12897
13503
|
) ?? {};
|
|
12898
13504
|
}
|
|
12899
13505
|
async function readPluginManifestName(targetDir, pluginKind) {
|
|
12900
|
-
const manifestPath =
|
|
13506
|
+
const manifestPath = resolve26(targetDir, getPluginManifestRelativePath(pluginKind));
|
|
12901
13507
|
if (!await fileExists(manifestPath)) {
|
|
12902
13508
|
return void 0;
|
|
12903
13509
|
}
|
|
@@ -12924,7 +13530,7 @@ async function getInstallStatus(targetDir, pluginKind) {
|
|
|
12924
13530
|
const info = await lstat(targetDir);
|
|
12925
13531
|
if (info.isSymbolicLink()) {
|
|
12926
13532
|
const symlinkTarget = await readlink(targetDir);
|
|
12927
|
-
const resolvedTarget =
|
|
13533
|
+
const resolvedTarget = resolve26(dirname9(targetDir), symlinkTarget);
|
|
12928
13534
|
const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
|
|
12929
13535
|
return {
|
|
12930
13536
|
exists: true,
|
|
@@ -12961,16 +13567,16 @@ async function writeInstallMetadata(targetDir, pluginKind, installMode, packageM
|
|
|
12961
13567
|
);
|
|
12962
13568
|
}
|
|
12963
13569
|
async function installCopy(paths, pluginKind) {
|
|
12964
|
-
await ensureDir(
|
|
13570
|
+
await ensureDir(dirname9(paths.targetDir));
|
|
12965
13571
|
await cp2(paths.sourceDir, paths.targetDir, { recursive: true });
|
|
12966
13572
|
const packageManifest = await readPackageManifest(paths.packageRoot);
|
|
12967
13573
|
await writeInstallMetadata(paths.targetDir, pluginKind, "copy", packageManifest);
|
|
12968
13574
|
}
|
|
12969
13575
|
async function installLink(paths) {
|
|
12970
|
-
await ensureDir(
|
|
13576
|
+
await ensureDir(dirname9(paths.targetDir));
|
|
12971
13577
|
await rm3(paths.targetDir, { recursive: true, force: true });
|
|
12972
|
-
await ensureDir(
|
|
12973
|
-
await symlink(
|
|
13578
|
+
await ensureDir(dirname9(paths.targetDir));
|
|
13579
|
+
await symlink(resolve26(paths.sourceDir), paths.targetDir, "dir");
|
|
12974
13580
|
}
|
|
12975
13581
|
async function removeInstallMarker(targetDir) {
|
|
12976
13582
|
const markerPath = getInstallMarkerPath(targetDir);
|
|
@@ -12984,13 +13590,13 @@ function normalizeAbsoluteInstallPath(pathValue, label) {
|
|
|
12984
13590
|
if (!isAbsolute2(expanded)) {
|
|
12985
13591
|
throw new Error(`${label} must be an absolute path.`);
|
|
12986
13592
|
}
|
|
12987
|
-
return
|
|
13593
|
+
return resolve26(expanded);
|
|
12988
13594
|
}
|
|
12989
13595
|
async function resolvePluginPaths(pluginKind, targetDir) {
|
|
12990
13596
|
const packageRoot = await findPackageRoot(getPluginRelativePath(pluginKind));
|
|
12991
13597
|
return {
|
|
12992
13598
|
packageRoot,
|
|
12993
|
-
sourceDir:
|
|
13599
|
+
sourceDir: resolve26(packageRoot, getPluginRelativePath(pluginKind)),
|
|
12994
13600
|
targetDir: targetDir ?? getDefaultPluginTargetDir(pluginKind)
|
|
12995
13601
|
};
|
|
12996
13602
|
}
|
|
@@ -13024,7 +13630,7 @@ async function readClaudeMarketplaceFile(manifestPath) {
|
|
|
13024
13630
|
};
|
|
13025
13631
|
}
|
|
13026
13632
|
async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
|
|
13027
|
-
await ensureDir(
|
|
13633
|
+
await ensureDir(dirname9(manifestPath));
|
|
13028
13634
|
if (Array.isArray(marketplace.plugins)) {
|
|
13029
13635
|
marketplace.plugins = [...marketplace.plugins].sort((a, b) => {
|
|
13030
13636
|
const an = a?.name ?? "";
|
|
@@ -13115,8 +13721,8 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
13115
13721
|
if (!entry.isDirectory()) {
|
|
13116
13722
|
continue;
|
|
13117
13723
|
}
|
|
13118
|
-
const candidateRoot =
|
|
13119
|
-
const manifestPath =
|
|
13724
|
+
const candidateRoot = resolve26(rootDir, entry.name);
|
|
13725
|
+
const manifestPath = resolve26(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
13120
13726
|
if (!await fileExists(manifestPath)) {
|
|
13121
13727
|
continue;
|
|
13122
13728
|
}
|
|
@@ -13143,11 +13749,11 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
13143
13749
|
if (!installLocation) {
|
|
13144
13750
|
continue;
|
|
13145
13751
|
}
|
|
13146
|
-
const candidateRoot =
|
|
13752
|
+
const candidateRoot = resolve26(expandHome(installLocation));
|
|
13147
13753
|
if (seen.has(candidateRoot)) {
|
|
13148
13754
|
continue;
|
|
13149
13755
|
}
|
|
13150
|
-
const manifestPath =
|
|
13756
|
+
const manifestPath = resolve26(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
13151
13757
|
if (!await fileExists(manifestPath)) {
|
|
13152
13758
|
continue;
|
|
13153
13759
|
}
|
|
@@ -13175,7 +13781,7 @@ async function getPreferredClaudeMarketplace() {
|
|
|
13175
13781
|
name: candidate.name,
|
|
13176
13782
|
rootDir: candidate.rootDir,
|
|
13177
13783
|
manifestPath: candidate.manifestPath,
|
|
13178
|
-
targetDir:
|
|
13784
|
+
targetDir: resolve26(candidate.rootDir, "plugins", "syntaur")
|
|
13179
13785
|
};
|
|
13180
13786
|
}
|
|
13181
13787
|
async function registerKnownClaudeMarketplace(name, rootDir) {
|
|
@@ -13207,7 +13813,7 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
|
|
|
13207
13813
|
};
|
|
13208
13814
|
existing[name].lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
13209
13815
|
existing[name].autoUpdate = true;
|
|
13210
|
-
await ensureDir(
|
|
13816
|
+
await ensureDir(dirname9(manifestPath));
|
|
13211
13817
|
if (await fileExists(manifestPath)) {
|
|
13212
13818
|
const prev = await readFile16(manifestPath, "utf-8");
|
|
13213
13819
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -13223,7 +13829,7 @@ async function ensureKnownClaudeMarketplaceForRoot(options) {
|
|
|
13223
13829
|
return registerKnownClaudeMarketplace(options.name, options.rootDir);
|
|
13224
13830
|
}
|
|
13225
13831
|
async function setSyntaurPluginEnabled(options) {
|
|
13226
|
-
const settingsPath =
|
|
13832
|
+
const settingsPath = resolve26(homedir2(), ".claude", "settings.json");
|
|
13227
13833
|
const key = `syntaur@${options.marketplaceName}`;
|
|
13228
13834
|
let parsed = {};
|
|
13229
13835
|
if (await fileExists(settingsPath)) {
|
|
@@ -13243,7 +13849,7 @@ async function setSyntaurPluginEnabled(options) {
|
|
|
13243
13849
|
}
|
|
13244
13850
|
enabledPlugins[key] = options.enabled;
|
|
13245
13851
|
parsed.enabledPlugins = enabledPlugins;
|
|
13246
|
-
await ensureDir(
|
|
13852
|
+
await ensureDir(dirname9(settingsPath));
|
|
13247
13853
|
if (await fileExists(settingsPath)) {
|
|
13248
13854
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13249
13855
|
const prev = await readFile16(settingsPath, "utf-8");
|
|
@@ -13260,9 +13866,9 @@ async function ensureClaudeUserMarketplace() {
|
|
|
13260
13866
|
if (existing) {
|
|
13261
13867
|
return existing;
|
|
13262
13868
|
}
|
|
13263
|
-
const rootDir =
|
|
13264
|
-
const manifestPath =
|
|
13265
|
-
await ensureDir(
|
|
13869
|
+
const rootDir = resolve26(getClaudeMarketplacesRoot(), "user-plugins");
|
|
13870
|
+
const manifestPath = resolve26(rootDir, ".claude-plugin", "marketplace.json");
|
|
13871
|
+
await ensureDir(resolve26(rootDir, "plugins"));
|
|
13266
13872
|
if (!await fileExists(manifestPath)) {
|
|
13267
13873
|
const scaffold = {
|
|
13268
13874
|
plugins: []
|
|
@@ -13281,22 +13887,22 @@ async function ensureClaudeUserMarketplace() {
|
|
|
13281
13887
|
name: "user-plugins",
|
|
13282
13888
|
rootDir,
|
|
13283
13889
|
manifestPath,
|
|
13284
|
-
targetDir:
|
|
13890
|
+
targetDir: resolve26(rootDir, "plugins", "syntaur")
|
|
13285
13891
|
};
|
|
13286
13892
|
}
|
|
13287
13893
|
async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
13288
13894
|
const normalizedTargetDir = normalizeAbsoluteInstallPath(targetDir, "Claude plugin target");
|
|
13289
|
-
const pluginsDir =
|
|
13290
|
-
if (
|
|
13895
|
+
const pluginsDir = dirname9(normalizedTargetDir);
|
|
13896
|
+
if (basename4(pluginsDir) !== "plugins") {
|
|
13291
13897
|
return null;
|
|
13292
13898
|
}
|
|
13293
|
-
const rootDir =
|
|
13294
|
-
const manifestPath =
|
|
13899
|
+
const rootDir = dirname9(pluginsDir);
|
|
13900
|
+
const manifestPath = resolve26(rootDir, ".claude-plugin", "marketplace.json");
|
|
13295
13901
|
if (!await fileExists(manifestPath)) {
|
|
13296
13902
|
return null;
|
|
13297
13903
|
}
|
|
13298
13904
|
const marketplace = await readClaudeMarketplaceFile(manifestPath);
|
|
13299
|
-
const name = typeof marketplace.name === "string" && marketplace.name.trim() !== "" ? marketplace.name :
|
|
13905
|
+
const name = typeof marketplace.name === "string" && marketplace.name.trim() !== "" ? marketplace.name : basename4(rootDir);
|
|
13300
13906
|
return {
|
|
13301
13907
|
name,
|
|
13302
13908
|
rootDir,
|
|
@@ -13307,7 +13913,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
|
13307
13913
|
async function findManagedClaudeMarketplacePluginDir() {
|
|
13308
13914
|
const marketplaces = await listClaudeMarketplaceCandidates();
|
|
13309
13915
|
for (const marketplace of marketplaces) {
|
|
13310
|
-
const targetDir =
|
|
13916
|
+
const targetDir = resolve26(marketplace.rootDir, "plugins", "syntaur");
|
|
13311
13917
|
const status = await getInstallStatus(targetDir, "claude");
|
|
13312
13918
|
if (status.exists && status.managed) {
|
|
13313
13919
|
return targetDir;
|
|
@@ -13432,7 +14038,7 @@ async function installManagedPlugin(options) {
|
|
|
13432
14038
|
`${paths.targetDir} already exists and is not a Syntaur-managed install. Remove it manually before installing Syntaur there.`
|
|
13433
14039
|
);
|
|
13434
14040
|
}
|
|
13435
|
-
if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget ===
|
|
14041
|
+
if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve26(paths.sourceDir) && !force) {
|
|
13436
14042
|
return {
|
|
13437
14043
|
targetDir: paths.targetDir,
|
|
13438
14044
|
sourceDir: paths.sourceDir,
|
|
@@ -13456,7 +14062,7 @@ async function installManagedPlugin(options) {
|
|
|
13456
14062
|
};
|
|
13457
14063
|
}
|
|
13458
14064
|
function buildMarketplaceSourcePath(pluginTargetDir, marketplacePath) {
|
|
13459
|
-
const relPath = relative2(
|
|
14065
|
+
const relPath = relative2(dirname9(marketplacePath), pluginTargetDir).replaceAll("\\", "/");
|
|
13460
14066
|
if (relPath === "") {
|
|
13461
14067
|
return ".";
|
|
13462
14068
|
}
|
|
@@ -13493,7 +14099,7 @@ async function readMarketplaceFile(marketplacePath) {
|
|
|
13493
14099
|
};
|
|
13494
14100
|
}
|
|
13495
14101
|
async function writeMarketplaceFile(marketplacePath, marketplace) {
|
|
13496
|
-
await ensureDir(
|
|
14102
|
+
await ensureDir(dirname9(marketplacePath));
|
|
13497
14103
|
await writeFile6(marketplacePath, `${JSON.stringify(marketplace, null, 2)}
|
|
13498
14104
|
`, "utf-8");
|
|
13499
14105
|
}
|
|
@@ -13645,13 +14251,13 @@ async function recommendMarketplacePath() {
|
|
|
13645
14251
|
return configuredOrManaged ?? getDefaultMarketplacePath();
|
|
13646
14252
|
}
|
|
13647
14253
|
async function isSyntaurDataInstalled() {
|
|
13648
|
-
return fileExists(
|
|
14254
|
+
return fileExists(resolve26(syntaurRoot(), "config.md"));
|
|
13649
14255
|
}
|
|
13650
14256
|
async function removeSyntaurData() {
|
|
13651
14257
|
await rm3(syntaurRoot(), { recursive: true, force: true });
|
|
13652
14258
|
}
|
|
13653
14259
|
async function getConfiguredProjectDir() {
|
|
13654
|
-
if (!await fileExists(
|
|
14260
|
+
if (!await fileExists(resolve26(syntaurRoot(), "config.md"))) {
|
|
13655
14261
|
return null;
|
|
13656
14262
|
}
|
|
13657
14263
|
return (await readConfig()).defaultProjectDir;
|
|
@@ -13721,16 +14327,16 @@ async function textPrompt(question, defaultValue) {
|
|
|
13721
14327
|
// src/utils/install-skills.ts
|
|
13722
14328
|
init_fs();
|
|
13723
14329
|
import { readFile as readFile18, readdir as readdir11, mkdir as mkdir4, copyFile, rm as rm4, lstat as lstat2 } from "fs/promises";
|
|
13724
|
-
import { resolve as
|
|
14330
|
+
import { resolve as resolve28, relative as relative3, join as join3 } from "path";
|
|
13725
14331
|
import { homedir as homedir4 } from "os";
|
|
13726
14332
|
|
|
13727
14333
|
// src/utils/plugin-state.ts
|
|
13728
14334
|
init_fs();
|
|
13729
14335
|
import { readFile as readFile17 } from "fs/promises";
|
|
13730
|
-
import { resolve as
|
|
14336
|
+
import { resolve as resolve27 } from "path";
|
|
13731
14337
|
import { homedir as homedir3 } from "os";
|
|
13732
14338
|
function settingsPathFor(agent) {
|
|
13733
|
-
if (agent === "claude") return
|
|
14339
|
+
if (agent === "claude") return resolve27(homedir3(), ".claude", "settings.json");
|
|
13734
14340
|
return null;
|
|
13735
14341
|
}
|
|
13736
14342
|
async function readJsonOrNull(path) {
|
|
@@ -13781,11 +14387,11 @@ var KNOWN_SKILL_NAMES = [
|
|
|
13781
14387
|
var KNOWN_SKILLS = KNOWN_SKILL_NAMES;
|
|
13782
14388
|
async function getSkillsDir() {
|
|
13783
14389
|
const packageRoot = await findPackageRoot("skills");
|
|
13784
|
-
return
|
|
14390
|
+
return resolve28(packageRoot, "skills");
|
|
13785
14391
|
}
|
|
13786
14392
|
function defaultSkillTargetDir(target) {
|
|
13787
|
-
if (target === "claude") return
|
|
13788
|
-
return
|
|
14393
|
+
if (target === "claude") return resolve28(homedir4(), ".claude", "skills");
|
|
14394
|
+
return resolve28(homedir4(), ".codex", "skills");
|
|
13789
14395
|
}
|
|
13790
14396
|
async function walkFiles(root) {
|
|
13791
14397
|
const out = [];
|
|
@@ -14117,7 +14723,7 @@ async function installPluginCommand(options) {
|
|
|
14117
14723
|
init_paths();
|
|
14118
14724
|
init_fs();
|
|
14119
14725
|
import { readFile as readFile20, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat2, symlink as symlink2, unlink as unlink6, lstat as lstat3 } from "fs/promises";
|
|
14120
|
-
import { resolve as
|
|
14726
|
+
import { resolve as resolve30, dirname as dirname11 } from "path";
|
|
14121
14727
|
import { homedir as homedir5 } from "os";
|
|
14122
14728
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
14123
14729
|
|
|
@@ -14125,7 +14731,7 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
14125
14731
|
init_paths();
|
|
14126
14732
|
init_fs();
|
|
14127
14733
|
import { readFile as readFile19, writeFile as writeFile7 } from "fs/promises";
|
|
14128
|
-
import { resolve as
|
|
14734
|
+
import { resolve as resolve29, dirname as dirname10 } from "path";
|
|
14129
14735
|
import { spawnSync } from "child_process";
|
|
14130
14736
|
import { checkbox, input as input2, confirm } from "@inquirer/prompts";
|
|
14131
14737
|
var AVAILABLE_SEGMENTS = [
|
|
@@ -14146,7 +14752,7 @@ var PRESETS = {
|
|
|
14146
14752
|
tracker: { segments: ["git", "assignment", "external", "session"], separator: " \xB7 " }
|
|
14147
14753
|
};
|
|
14148
14754
|
function getConfigPath(installRoot) {
|
|
14149
|
-
return
|
|
14755
|
+
return resolve29(installRoot, "statusline.config.json");
|
|
14150
14756
|
}
|
|
14151
14757
|
async function readConfig2(path) {
|
|
14152
14758
|
if (!await fileExists(path)) return null;
|
|
@@ -14246,7 +14852,7 @@ function renderPreview(config, statuslineScript, cwd) {
|
|
|
14246
14852
|
env: {
|
|
14247
14853
|
...process.env,
|
|
14248
14854
|
// Force the child to pick up the freshly-written config from install root.
|
|
14249
|
-
HOME:
|
|
14855
|
+
HOME: dirname10(dirname10(statuslineScript))
|
|
14250
14856
|
}
|
|
14251
14857
|
});
|
|
14252
14858
|
if (res.status !== 0) return null;
|
|
@@ -14297,14 +14903,14 @@ async function configureStatuslineCommand(options = {}) {
|
|
|
14297
14903
|
console.log("(preview mode \u2014 config NOT written)");
|
|
14298
14904
|
return;
|
|
14299
14905
|
}
|
|
14300
|
-
await ensureDir(
|
|
14906
|
+
await ensureDir(dirname10(configPath));
|
|
14301
14907
|
await writeFile7(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
14302
14908
|
console.log("Wrote statusline config:");
|
|
14303
14909
|
console.log(` path: ${configPath}`);
|
|
14304
14910
|
console.log(` segments: ${config.segments.join(", ")}`);
|
|
14305
14911
|
console.log(` separator: ${JSON.stringify(config.separator)}`);
|
|
14306
14912
|
if (config.wrap) console.log(` wrap: ${config.wrap}`);
|
|
14307
|
-
const script = options.statuslineScript ??
|
|
14913
|
+
const script = options.statuslineScript ?? resolve29(installRoot, "statusline.sh");
|
|
14308
14914
|
if (await fileExists(script)) {
|
|
14309
14915
|
console.log("");
|
|
14310
14916
|
console.log("Live preview:");
|
|
@@ -14324,7 +14930,7 @@ async function configureStatuslineCommand(options = {}) {
|
|
|
14324
14930
|
async function writeDefaultConfigIfMissing(installRoot) {
|
|
14325
14931
|
const path = getConfigPath(installRoot);
|
|
14326
14932
|
if (await fileExists(path)) return;
|
|
14327
|
-
await ensureDir(
|
|
14933
|
+
await ensureDir(dirname10(path));
|
|
14328
14934
|
const defaultConfig = {
|
|
14329
14935
|
segments: ["git", "assignment", "session"],
|
|
14330
14936
|
separator: " \xB7 "
|
|
@@ -14334,8 +14940,8 @@ async function writeDefaultConfigIfMissing(installRoot) {
|
|
|
14334
14940
|
|
|
14335
14941
|
// src/commands/install-statusline.ts
|
|
14336
14942
|
function getPackageStatuslineSource() {
|
|
14337
|
-
const here =
|
|
14338
|
-
return
|
|
14943
|
+
const here = dirname11(fileURLToPath4(import.meta.url));
|
|
14944
|
+
return resolve30(here, "..", "statusline", "statusline.sh");
|
|
14339
14945
|
}
|
|
14340
14946
|
async function readSettingsJson(settingsPath) {
|
|
14341
14947
|
if (!await fileExists(settingsPath)) return {};
|
|
@@ -14351,7 +14957,7 @@ async function readSettingsJson(settingsPath) {
|
|
|
14351
14957
|
}
|
|
14352
14958
|
}
|
|
14353
14959
|
async function writeSettingsJson(settingsPath, data) {
|
|
14354
|
-
await ensureDir(
|
|
14960
|
+
await ensureDir(dirname11(settingsPath));
|
|
14355
14961
|
await writeFile8(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
14356
14962
|
}
|
|
14357
14963
|
async function resolveMode(mode, existingCommand, ourCommand) {
|
|
@@ -14386,7 +14992,7 @@ function extractExistingCommand(settings) {
|
|
|
14386
14992
|
};
|
|
14387
14993
|
}
|
|
14388
14994
|
async function backupSettings(settingsSnapshot, backupPath) {
|
|
14389
|
-
await ensureDir(
|
|
14995
|
+
await ensureDir(dirname11(backupPath));
|
|
14390
14996
|
await writeFile8(
|
|
14391
14997
|
backupPath,
|
|
14392
14998
|
JSON.stringify(
|
|
@@ -14403,7 +15009,7 @@ async function backupSettings(settingsSnapshot, backupPath) {
|
|
|
14403
15009
|
);
|
|
14404
15010
|
}
|
|
14405
15011
|
async function installScript(sourceScript, destScript, link) {
|
|
14406
|
-
await ensureDir(
|
|
15012
|
+
await ensureDir(dirname11(destScript));
|
|
14407
15013
|
try {
|
|
14408
15014
|
const s = await lstat3(destScript);
|
|
14409
15015
|
if (s.isSymbolicLink() || s.isFile()) {
|
|
@@ -14419,12 +15025,12 @@ async function installScript(sourceScript, destScript, link) {
|
|
|
14419
15025
|
}
|
|
14420
15026
|
async function installStatuslineCommand(options = {}) {
|
|
14421
15027
|
const mode = options.mode ?? "ask";
|
|
14422
|
-
const settingsPath = options.settingsPath ??
|
|
15028
|
+
const settingsPath = options.settingsPath ?? resolve30(homedir5(), ".claude", "settings.json");
|
|
14423
15029
|
const installRoot = options.installRoot ?? syntaurRoot();
|
|
14424
15030
|
const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
|
|
14425
|
-
const destScript =
|
|
14426
|
-
const confPath =
|
|
14427
|
-
const backupPath =
|
|
15031
|
+
const destScript = resolve30(installRoot, "statusline.sh");
|
|
15032
|
+
const confPath = resolve30(installRoot, "statusline.conf");
|
|
15033
|
+
const backupPath = resolve30(installRoot, "statusline.backup.json");
|
|
14428
15034
|
if (!await fileExists(sourceScript)) {
|
|
14429
15035
|
throw new Error(
|
|
14430
15036
|
`Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
|
|
@@ -14460,7 +15066,7 @@ async function installStatuslineCommand(options = {}) {
|
|
|
14460
15066
|
if (parsed) {
|
|
14461
15067
|
wrapTarget = parsed;
|
|
14462
15068
|
} else {
|
|
14463
|
-
const wrapperPath =
|
|
15069
|
+
const wrapperPath = resolve30(installRoot, "statusline-wrapped.sh");
|
|
14464
15070
|
const wrapperBody = `#!/usr/bin/env bash
|
|
14465
15071
|
# Auto-generated by syntaur install-statusline.
|
|
14466
15072
|
# Executes the previously configured statusLine command.
|
|
@@ -14471,7 +15077,7 @@ exec ${existingCommand}
|
|
|
14471
15077
|
wrapTarget = wrapperPath;
|
|
14472
15078
|
}
|
|
14473
15079
|
}
|
|
14474
|
-
await ensureDir(
|
|
15080
|
+
await ensureDir(dirname11(confPath));
|
|
14475
15081
|
await writeFile8(
|
|
14476
15082
|
confPath,
|
|
14477
15083
|
wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
|
|
@@ -14515,12 +15121,12 @@ async function chmodExec(path) {
|
|
|
14515
15121
|
}
|
|
14516
15122
|
}
|
|
14517
15123
|
async function uninstallStatuslineCommand(options = {}) {
|
|
14518
|
-
const settingsPath = options.settingsPath ??
|
|
15124
|
+
const settingsPath = options.settingsPath ?? resolve30(homedir5(), ".claude", "settings.json");
|
|
14519
15125
|
const installRoot = options.installRoot ?? syntaurRoot();
|
|
14520
|
-
const destScript =
|
|
14521
|
-
const confPath =
|
|
14522
|
-
const backupPath =
|
|
14523
|
-
const wrapperPath =
|
|
15126
|
+
const destScript = resolve30(installRoot, "statusline.sh");
|
|
15127
|
+
const confPath = resolve30(installRoot, "statusline.conf");
|
|
15128
|
+
const backupPath = resolve30(installRoot, "statusline.backup.json");
|
|
15129
|
+
const wrapperPath = resolve30(installRoot, "statusline-wrapped.sh");
|
|
14524
15130
|
const settings = await readSettingsJson(settingsPath);
|
|
14525
15131
|
const existing = extractExistingCommand(settings);
|
|
14526
15132
|
const ourCommand = `bash ${destScript}`;
|
|
@@ -14548,7 +15154,7 @@ async function uninstallStatuslineCommand(options = {}) {
|
|
|
14548
15154
|
await writeSettingsJson(settingsPath, settings);
|
|
14549
15155
|
}
|
|
14550
15156
|
if (!options.keepScript) {
|
|
14551
|
-
const configPath =
|
|
15157
|
+
const configPath = resolve30(installRoot, "statusline.config.json");
|
|
14552
15158
|
for (const path of [destScript, confPath, backupPath, wrapperPath, configPath]) {
|
|
14553
15159
|
try {
|
|
14554
15160
|
await rm5(path, { force: true });
|
|
@@ -14805,7 +15411,7 @@ async function setupCommand(options) {
|
|
|
14805
15411
|
}
|
|
14806
15412
|
|
|
14807
15413
|
// src/commands/uninstall.ts
|
|
14808
|
-
import { resolve as
|
|
15414
|
+
import { resolve as resolve31 } from "path";
|
|
14809
15415
|
init_paths();
|
|
14810
15416
|
function expandTargets(options) {
|
|
14811
15417
|
if (options.all) {
|
|
@@ -14885,7 +15491,7 @@ async function uninstallCommand(options) {
|
|
|
14885
15491
|
const configuredProjectDir = await getConfiguredProjectDir();
|
|
14886
15492
|
await removeSyntaurData();
|
|
14887
15493
|
console.log(`Removed ${syntaurRoot()}`);
|
|
14888
|
-
if (configuredProjectDir &&
|
|
15494
|
+
if (configuredProjectDir && resolve31(configuredProjectDir) !== resolve31(syntaurRoot(), "projects")) {
|
|
14889
15495
|
console.warn(
|
|
14890
15496
|
`Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
|
|
14891
15497
|
);
|
|
@@ -14904,7 +15510,7 @@ init_slug();
|
|
|
14904
15510
|
init_cursor_rules();
|
|
14905
15511
|
init_codex_agents();
|
|
14906
15512
|
init_opencode_config();
|
|
14907
|
-
import { resolve as
|
|
15513
|
+
import { resolve as resolve32 } from "path";
|
|
14908
15514
|
var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
|
|
14909
15515
|
async function setupAdapterCommand(framework, options) {
|
|
14910
15516
|
if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
|
|
@@ -14930,19 +15536,19 @@ async function setupAdapterCommand(framework, options) {
|
|
|
14930
15536
|
}
|
|
14931
15537
|
const config = await readConfig();
|
|
14932
15538
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
14933
|
-
const projectDir =
|
|
14934
|
-
const assignmentDir =
|
|
15539
|
+
const projectDir = resolve32(baseDir, options.project);
|
|
15540
|
+
const assignmentDir = resolve32(
|
|
14935
15541
|
projectDir,
|
|
14936
15542
|
"assignments",
|
|
14937
15543
|
options.assignment
|
|
14938
15544
|
);
|
|
14939
|
-
const projectMdPath =
|
|
15545
|
+
const projectMdPath = resolve32(projectDir, "project.md");
|
|
14940
15546
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
14941
15547
|
throw new Error(
|
|
14942
15548
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
14943
15549
|
);
|
|
14944
15550
|
}
|
|
14945
|
-
const assignmentMdPath2 =
|
|
15551
|
+
const assignmentMdPath2 = resolve32(assignmentDir, "assignment.md");
|
|
14946
15552
|
if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath2)) {
|
|
14947
15553
|
throw new Error(
|
|
14948
15554
|
`Assignment "${options.assignment}" not found at ${assignmentDir}.`
|
|
@@ -14970,15 +15576,15 @@ async function setupAdapterCommand(framework, options) {
|
|
|
14970
15576
|
}
|
|
14971
15577
|
}
|
|
14972
15578
|
if (framework === "cursor") {
|
|
14973
|
-
const protocolPath =
|
|
14974
|
-
const assignmentPath =
|
|
15579
|
+
const protocolPath = resolve32(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
|
|
15580
|
+
const assignmentPath = resolve32(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
|
|
14975
15581
|
await writeAdapterFile(protocolPath, renderCursorProtocol());
|
|
14976
15582
|
await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
|
|
14977
15583
|
} else if (framework === "codex" || framework === "opencode") {
|
|
14978
|
-
const agentsPath =
|
|
15584
|
+
const agentsPath = resolve32(cwd, "AGENTS.md");
|
|
14979
15585
|
await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
|
|
14980
15586
|
if (framework === "opencode") {
|
|
14981
|
-
const configPath =
|
|
15587
|
+
const configPath = resolve32(cwd, "opencode.json");
|
|
14982
15588
|
await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
|
|
14983
15589
|
}
|
|
14984
15590
|
}
|
|
@@ -15003,7 +15609,7 @@ async function setupAdapterCommand(framework, options) {
|
|
|
15003
15609
|
init_paths();
|
|
15004
15610
|
init_fs();
|
|
15005
15611
|
init_config2();
|
|
15006
|
-
import { resolve as
|
|
15612
|
+
import { resolve as resolve33 } from "path";
|
|
15007
15613
|
async function trackSessionCommand(options) {
|
|
15008
15614
|
if (!options.agent) {
|
|
15009
15615
|
throw new Error("--agent <name> is required.");
|
|
@@ -15016,7 +15622,7 @@ async function trackSessionCommand(options) {
|
|
|
15016
15622
|
if (options.project) {
|
|
15017
15623
|
const config = await readConfig();
|
|
15018
15624
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
15019
|
-
const projectDir =
|
|
15625
|
+
const projectDir = resolve33(baseDir, options.project);
|
|
15020
15626
|
if (!await fileExists(projectDir)) {
|
|
15021
15627
|
throw new Error(
|
|
15022
15628
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
@@ -15050,7 +15656,7 @@ init_config2();
|
|
|
15050
15656
|
init_paths();
|
|
15051
15657
|
init_fs();
|
|
15052
15658
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
15053
|
-
import { resolve as
|
|
15659
|
+
import { resolve as resolve35, isAbsolute as isAbsolute4 } from "path";
|
|
15054
15660
|
import { readFile as readFile22 } from "fs/promises";
|
|
15055
15661
|
import { select, confirm as confirm2, input as input3 } from "@inquirer/prompts";
|
|
15056
15662
|
async function browseCommand(options) {
|
|
@@ -15120,7 +15726,7 @@ async function pickAgent(agents) {
|
|
|
15120
15726
|
return picked;
|
|
15121
15727
|
}
|
|
15122
15728
|
async function ensureWorktree(opts) {
|
|
15123
|
-
const assignmentPath =
|
|
15729
|
+
const assignmentPath = resolve35(
|
|
15124
15730
|
opts.projectsDir,
|
|
15125
15731
|
opts.projectSlug,
|
|
15126
15732
|
"assignments",
|
|
@@ -15201,7 +15807,7 @@ function computeWorktreeDefaults(opts) {
|
|
|
15201
15807
|
const repository = opts.existing.repository ?? detectCurrentGitRoot();
|
|
15202
15808
|
const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
|
|
15203
15809
|
const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch() ?? "main";
|
|
15204
|
-
const worktreeBase = repository ?
|
|
15810
|
+
const worktreeBase = repository ? resolve35(repository, ".worktrees", branch) : resolve35(
|
|
15205
15811
|
syntaurRoot(),
|
|
15206
15812
|
"worktrees",
|
|
15207
15813
|
opts.projectSlug || "standalone",
|
|
@@ -15234,7 +15840,7 @@ function detectCurrentBranch() {
|
|
|
15234
15840
|
async function runCreate(opts) {
|
|
15235
15841
|
const { createWorktreeAndRecord: createWorktreeAndRecord2, GitWorktreeError: GitWorktreeError2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
15236
15842
|
const expandedWorktree = expandHome(opts.worktreePath);
|
|
15237
|
-
const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree :
|
|
15843
|
+
const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree : resolve35(expandedWorktree);
|
|
15238
15844
|
try {
|
|
15239
15845
|
await createWorktreeAndRecord2({
|
|
15240
15846
|
assignmentPath: opts.assignmentPath,
|
|
@@ -15263,7 +15869,7 @@ init_paths();
|
|
|
15263
15869
|
init_fs();
|
|
15264
15870
|
init_playbook();
|
|
15265
15871
|
init_playbooks();
|
|
15266
|
-
import { resolve as
|
|
15872
|
+
import { resolve as resolve36 } from "path";
|
|
15267
15873
|
async function createPlaybookCommand(name, options) {
|
|
15268
15874
|
if (!name.trim()) {
|
|
15269
15875
|
throw new Error("Playbook name cannot be empty.");
|
|
@@ -15276,7 +15882,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
15276
15882
|
}
|
|
15277
15883
|
const dir = playbooksDir();
|
|
15278
15884
|
await ensureDir(dir);
|
|
15279
|
-
const filePath =
|
|
15885
|
+
const filePath = resolve36(dir, `${slug}.md`);
|
|
15280
15886
|
if (await fileExists(filePath)) {
|
|
15281
15887
|
throw new Error(
|
|
15282
15888
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -15299,7 +15905,7 @@ init_fs();
|
|
|
15299
15905
|
init_parser();
|
|
15300
15906
|
init_config2();
|
|
15301
15907
|
import { readdir as readdir12, readFile as readFile23 } from "fs/promises";
|
|
15302
|
-
import { resolve as
|
|
15908
|
+
import { resolve as resolve37 } from "path";
|
|
15303
15909
|
async function listPlaybooksCommand(options = {}) {
|
|
15304
15910
|
const dir = playbooksDir();
|
|
15305
15911
|
if (!await fileExists(dir)) {
|
|
@@ -15314,7 +15920,7 @@ async function listPlaybooksCommand(options = {}) {
|
|
|
15314
15920
|
);
|
|
15315
15921
|
const rows = [];
|
|
15316
15922
|
for (const entry of mdFiles) {
|
|
15317
|
-
const filePath =
|
|
15923
|
+
const filePath = resolve37(dir, entry.name);
|
|
15318
15924
|
const raw = await readFile23(filePath, "utf-8");
|
|
15319
15925
|
const parsed = parsePlaybook(raw);
|
|
15320
15926
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
@@ -15414,7 +16020,7 @@ init_config2();
|
|
|
15414
16020
|
init_slug();
|
|
15415
16021
|
import { Command } from "commander";
|
|
15416
16022
|
import { readFile as readFile24 } from "fs/promises";
|
|
15417
|
-
import { resolve as
|
|
16023
|
+
import { resolve as resolve38 } from "path";
|
|
15418
16024
|
var WORKSPACE_REGEX3 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
15419
16025
|
async function resolveScope(options) {
|
|
15420
16026
|
const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
|
|
@@ -15426,7 +16032,7 @@ async function resolveScope(options) {
|
|
|
15426
16032
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
15427
16033
|
}
|
|
15428
16034
|
const config = await readConfig();
|
|
15429
|
-
const projectMd =
|
|
16035
|
+
const projectMd = resolve38(config.defaultProjectDir, options.project, "project.md");
|
|
15430
16036
|
if (!await fileExists(projectMd)) {
|
|
15431
16037
|
throw new Error(`Project "${options.project}" not found.`);
|
|
15432
16038
|
}
|
|
@@ -15745,7 +16351,7 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
15745
16351
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
15746
16352
|
);
|
|
15747
16353
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
15748
|
-
await ensureDir(
|
|
16354
|
+
await ensureDir(resolve38(todosPath, "archive"));
|
|
15749
16355
|
let archContent = "";
|
|
15750
16356
|
if (await fileExists(archFile)) {
|
|
15751
16357
|
archContent = await readFile24(archFile, "utf-8");
|
|
@@ -15977,7 +16583,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
|
|
|
15977
16583
|
);
|
|
15978
16584
|
let target;
|
|
15979
16585
|
if (existingFiles.length === 0) {
|
|
15980
|
-
target =
|
|
16586
|
+
target = resolve38(planDir, "plan.md");
|
|
15981
16587
|
} else {
|
|
15982
16588
|
const versions = /* @__PURE__ */ new Set();
|
|
15983
16589
|
for (const f of existingFiles) {
|
|
@@ -15987,7 +16593,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
|
|
|
15987
16593
|
}
|
|
15988
16594
|
let n = 2;
|
|
15989
16595
|
while (versions.has(n)) n++;
|
|
15990
|
-
target =
|
|
16596
|
+
target = resolve38(planDir, `plan-v${n}.md`);
|
|
15991
16597
|
}
|
|
15992
16598
|
if (!await fileExists(target)) {
|
|
15993
16599
|
const stub = `---
|
|
@@ -16058,8 +16664,8 @@ async function moveTodo(id, options) {
|
|
|
16058
16664
|
throw new Error(`Plan directory already exists at target: ${newPlanDir}; refusing to move.`);
|
|
16059
16665
|
}
|
|
16060
16666
|
const { rename: rename8, mkdir: mkdir8 } = await import("fs/promises");
|
|
16061
|
-
const { dirname:
|
|
16062
|
-
await mkdir8(
|
|
16667
|
+
const { dirname: dirname17 } = await import("path");
|
|
16668
|
+
await mkdir8(dirname17(newPlanDir), { recursive: true });
|
|
16063
16669
|
await rename8(item.planDir, newPlanDir);
|
|
16064
16670
|
item.planDir = newPlanDir;
|
|
16065
16671
|
}
|
|
@@ -16177,31 +16783,31 @@ backupCommand.command("config").description("Show or update backup configuration
|
|
|
16177
16783
|
// src/commands/doctor.ts
|
|
16178
16784
|
import { Command as Command3 } from "commander";
|
|
16179
16785
|
import { readFile as readFile31 } from "fs/promises";
|
|
16180
|
-
import { isAbsolute as isAbsolute7, resolve as
|
|
16786
|
+
import { isAbsolute as isAbsolute7, resolve as resolve48 } from "path";
|
|
16181
16787
|
|
|
16182
16788
|
// src/utils/doctor/index.ts
|
|
16183
16789
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
16184
16790
|
import { readFile as readFile30 } from "fs/promises";
|
|
16185
|
-
import { dirname as
|
|
16791
|
+
import { dirname as dirname14, join as join6 } from "path";
|
|
16186
16792
|
|
|
16187
16793
|
// src/utils/doctor/context.ts
|
|
16188
16794
|
init_config2();
|
|
16189
16795
|
init_paths();
|
|
16190
16796
|
init_fs();
|
|
16191
|
-
import
|
|
16192
|
-
import { resolve as
|
|
16797
|
+
import Database3 from "better-sqlite3";
|
|
16798
|
+
import { resolve as resolve39 } from "path";
|
|
16193
16799
|
async function buildCheckContext(cwd = process.cwd()) {
|
|
16194
16800
|
const config = await readConfig();
|
|
16195
16801
|
const root = syntaurRoot();
|
|
16196
|
-
const dbPath =
|
|
16197
|
-
let
|
|
16802
|
+
const dbPath = resolve39(root, "syntaur.db");
|
|
16803
|
+
let db4 = null;
|
|
16198
16804
|
let dbError = null;
|
|
16199
16805
|
if (await fileExists(dbPath)) {
|
|
16200
16806
|
try {
|
|
16201
|
-
|
|
16807
|
+
db4 = new Database3(dbPath, { readonly: true, fileMustExist: true });
|
|
16202
16808
|
} catch (err2) {
|
|
16203
16809
|
dbError = err2 instanceof Error ? err2.message : String(err2);
|
|
16204
|
-
|
|
16810
|
+
db4 = null;
|
|
16205
16811
|
}
|
|
16206
16812
|
} else {
|
|
16207
16813
|
dbError = `database file not found at ${dbPath}`;
|
|
@@ -16209,7 +16815,7 @@ async function buildCheckContext(cwd = process.cwd()) {
|
|
|
16209
16815
|
return {
|
|
16210
16816
|
config,
|
|
16211
16817
|
syntaurRoot: root,
|
|
16212
|
-
db:
|
|
16818
|
+
db: db4,
|
|
16213
16819
|
dbError,
|
|
16214
16820
|
cwd,
|
|
16215
16821
|
now: /* @__PURE__ */ new Date()
|
|
@@ -16227,10 +16833,10 @@ function closeCheckContext(ctx) {
|
|
|
16227
16833
|
// src/utils/doctor/checks/env.ts
|
|
16228
16834
|
init_fs();
|
|
16229
16835
|
init_paths();
|
|
16230
|
-
import { resolve as
|
|
16836
|
+
import { resolve as resolve40, isAbsolute as isAbsolute5 } from "path";
|
|
16231
16837
|
import { readFile as readFile25, stat as stat3 } from "fs/promises";
|
|
16232
16838
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
16233
|
-
import { dirname as
|
|
16839
|
+
import { dirname as dirname12, join as join4 } from "path";
|
|
16234
16840
|
var CATEGORY = "env";
|
|
16235
16841
|
var syntaurRootExists = {
|
|
16236
16842
|
id: "env.syntaur-root-exists",
|
|
@@ -16268,7 +16874,7 @@ var configValid = {
|
|
|
16268
16874
|
category: CATEGORY,
|
|
16269
16875
|
title: "~/.syntaur/config.md is valid",
|
|
16270
16876
|
async run(ctx) {
|
|
16271
|
-
const configPath =
|
|
16877
|
+
const configPath = resolve40(ctx.syntaurRoot, "config.md");
|
|
16272
16878
|
if (!await fileExists(configPath)) {
|
|
16273
16879
|
return {
|
|
16274
16880
|
id: this.id,
|
|
@@ -16561,14 +17167,14 @@ async function readLocalVersion() {
|
|
|
16561
17167
|
async function readLocalPkg() {
|
|
16562
17168
|
try {
|
|
16563
17169
|
const here = fileURLToPath5(import.meta.url);
|
|
16564
|
-
let dir =
|
|
17170
|
+
let dir = dirname12(here);
|
|
16565
17171
|
for (let i = 0; i < 6; i++) {
|
|
16566
17172
|
const candidate = join4(dir, "package.json");
|
|
16567
17173
|
try {
|
|
16568
17174
|
const text = await readFile25(candidate, "utf-8");
|
|
16569
17175
|
return JSON.parse(text);
|
|
16570
17176
|
} catch {
|
|
16571
|
-
dir =
|
|
17177
|
+
dir = dirname12(dir);
|
|
16572
17178
|
}
|
|
16573
17179
|
}
|
|
16574
17180
|
return null;
|
|
@@ -16617,7 +17223,7 @@ function versionGte(a, b) {
|
|
|
16617
17223
|
|
|
16618
17224
|
// src/utils/doctor/checks/structure.ts
|
|
16619
17225
|
init_fs();
|
|
16620
|
-
import { resolve as
|
|
17226
|
+
import { resolve as resolve41 } from "path";
|
|
16621
17227
|
import { readdir as readdir13, stat as stat4 } from "fs/promises";
|
|
16622
17228
|
var CATEGORY2 = "structure";
|
|
16623
17229
|
var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
@@ -16637,7 +17243,7 @@ var projectsDir = {
|
|
|
16637
17243
|
category: CATEGORY2,
|
|
16638
17244
|
title: "projects/ directory exists",
|
|
16639
17245
|
async run(ctx) {
|
|
16640
|
-
const p =
|
|
17246
|
+
const p = resolve41(ctx.syntaurRoot, "projects");
|
|
16641
17247
|
if (!await fileExists(p)) {
|
|
16642
17248
|
return {
|
|
16643
17249
|
id: this.id,
|
|
@@ -16662,7 +17268,7 @@ var playbooksDir2 = {
|
|
|
16662
17268
|
category: CATEGORY2,
|
|
16663
17269
|
title: "playbooks/ directory exists",
|
|
16664
17270
|
async run(ctx) {
|
|
16665
|
-
const p =
|
|
17271
|
+
const p = resolve41(ctx.syntaurRoot, "playbooks");
|
|
16666
17272
|
if (!await fileExists(p)) {
|
|
16667
17273
|
return {
|
|
16668
17274
|
id: this.id,
|
|
@@ -16687,7 +17293,7 @@ var todosDirValid = {
|
|
|
16687
17293
|
category: CATEGORY2,
|
|
16688
17294
|
title: "todos/ directory is readable (if present)",
|
|
16689
17295
|
async run(ctx) {
|
|
16690
|
-
const p =
|
|
17296
|
+
const p = resolve41(ctx.syntaurRoot, "todos");
|
|
16691
17297
|
if (!await fileExists(p)) {
|
|
16692
17298
|
return {
|
|
16693
17299
|
id: this.id,
|
|
@@ -16718,7 +17324,7 @@ var serversDirValid = {
|
|
|
16718
17324
|
category: CATEGORY2,
|
|
16719
17325
|
title: "servers/ directory is readable (if present)",
|
|
16720
17326
|
async run(ctx) {
|
|
16721
|
-
const p =
|
|
17327
|
+
const p = resolve41(ctx.syntaurRoot, "servers");
|
|
16722
17328
|
if (!await fileExists(p)) {
|
|
16723
17329
|
return {
|
|
16724
17330
|
id: this.id,
|
|
@@ -16763,7 +17369,7 @@ var knownFilesRecognized = {
|
|
|
16763
17369
|
title: this.title,
|
|
16764
17370
|
status: "warn",
|
|
16765
17371
|
detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
|
|
16766
|
-
affected: unexpected.map((n) =>
|
|
17372
|
+
affected: unexpected.map((n) => resolve41(ctx.syntaurRoot, n)),
|
|
16767
17373
|
remediation: {
|
|
16768
17374
|
kind: "manual",
|
|
16769
17375
|
suggestion: "Review these entries \u2014 they may be leftover state from older versions",
|
|
@@ -16792,7 +17398,7 @@ function pass2(check) {
|
|
|
16792
17398
|
|
|
16793
17399
|
// src/utils/doctor/checks/project.ts
|
|
16794
17400
|
init_fs();
|
|
16795
|
-
import { resolve as
|
|
17401
|
+
import { resolve as resolve42 } from "path";
|
|
16796
17402
|
import { readdir as readdir14, stat as stat5 } from "fs/promises";
|
|
16797
17403
|
var CATEGORY3 = "project";
|
|
16798
17404
|
var REQUIRED_PROJECT_FILES = [
|
|
@@ -16822,10 +17428,10 @@ async function listProjects2(ctx) {
|
|
|
16822
17428
|
for (const e of entries) {
|
|
16823
17429
|
if (!e.isDirectory()) continue;
|
|
16824
17430
|
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
16825
|
-
const projectDir =
|
|
17431
|
+
const projectDir = resolve42(dir, e.name);
|
|
16826
17432
|
let looksLikeProject = false;
|
|
16827
17433
|
for (const marker of PROJECT_MARKERS) {
|
|
16828
|
-
if (await fileExists(
|
|
17434
|
+
if (await fileExists(resolve42(projectDir, marker))) {
|
|
16829
17435
|
looksLikeProject = true;
|
|
16830
17436
|
break;
|
|
16831
17437
|
}
|
|
@@ -16844,7 +17450,7 @@ var requiredFiles = {
|
|
|
16844
17450
|
for (const projectDir of projects) {
|
|
16845
17451
|
const missing = [];
|
|
16846
17452
|
for (const rel of REQUIRED_PROJECT_FILES) {
|
|
16847
|
-
const p =
|
|
17453
|
+
const p = resolve42(projectDir, rel);
|
|
16848
17454
|
if (!await fileExists(p)) missing.push(rel);
|
|
16849
17455
|
}
|
|
16850
17456
|
if (missing.length === 0) continue;
|
|
@@ -16854,7 +17460,7 @@ var requiredFiles = {
|
|
|
16854
17460
|
title: this.title,
|
|
16855
17461
|
status: "error",
|
|
16856
17462
|
detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
|
|
16857
|
-
affected: missing.map((m) =>
|
|
17463
|
+
affected: missing.map((m) => resolve42(projectDir, m)),
|
|
16858
17464
|
remediation: {
|
|
16859
17465
|
kind: "manual",
|
|
16860
17466
|
suggestion: "Recreate the missing scaffold files from templates",
|
|
@@ -16877,7 +17483,7 @@ var manifestStale = {
|
|
|
16877
17483
|
const projects = await listProjects2(ctx);
|
|
16878
17484
|
const results = [];
|
|
16879
17485
|
for (const projectDir of projects) {
|
|
16880
|
-
const manifestPath =
|
|
17486
|
+
const manifestPath = resolve42(projectDir, "manifest.md");
|
|
16881
17487
|
if (!await fileExists(manifestPath)) continue;
|
|
16882
17488
|
const manifestMtime = (await stat5(manifestPath)).mtimeMs;
|
|
16883
17489
|
const newestAssignment = await newestAssignmentMtime(projectDir);
|
|
@@ -16926,7 +17532,7 @@ var orphanFiles = {
|
|
|
16926
17532
|
title: this.title,
|
|
16927
17533
|
status: "warn",
|
|
16928
17534
|
detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
|
|
16929
|
-
affected: orphans.map((o) =>
|
|
17535
|
+
affected: orphans.map((o) => resolve42(projectDir, o)),
|
|
16930
17536
|
autoFixable: false
|
|
16931
17537
|
});
|
|
16932
17538
|
}
|
|
@@ -16936,7 +17542,7 @@ var orphanFiles = {
|
|
|
16936
17542
|
};
|
|
16937
17543
|
var projectChecks = [requiredFiles, manifestStale, orphanFiles];
|
|
16938
17544
|
async function newestAssignmentMtime(projectDir) {
|
|
16939
|
-
const assignmentsRoot =
|
|
17545
|
+
const assignmentsRoot = resolve42(projectDir, "assignments");
|
|
16940
17546
|
if (!await fileExists(assignmentsRoot)) return 0;
|
|
16941
17547
|
let newest = 0;
|
|
16942
17548
|
let entries;
|
|
@@ -16947,7 +17553,7 @@ async function newestAssignmentMtime(projectDir) {
|
|
|
16947
17553
|
}
|
|
16948
17554
|
for (const e of entries) {
|
|
16949
17555
|
if (!e.isDirectory()) continue;
|
|
16950
|
-
const assignmentMd =
|
|
17556
|
+
const assignmentMd = resolve42(assignmentsRoot, e.name, "assignment.md");
|
|
16951
17557
|
try {
|
|
16952
17558
|
const s = await stat5(assignmentMd);
|
|
16953
17559
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
@@ -16971,7 +17577,7 @@ init_fs();
|
|
|
16971
17577
|
init_parser();
|
|
16972
17578
|
init_types();
|
|
16973
17579
|
init_paths();
|
|
16974
|
-
import { resolve as
|
|
17580
|
+
import { resolve as resolve43 } from "path";
|
|
16975
17581
|
import { readdir as readdir15, readFile as readFile26 } from "fs/promises";
|
|
16976
17582
|
var CATEGORY4 = "assignment";
|
|
16977
17583
|
var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
|
|
@@ -16983,16 +17589,16 @@ async function listAssignments(ctx) {
|
|
|
16983
17589
|
for (const m of projects) {
|
|
16984
17590
|
if (!m.isDirectory()) continue;
|
|
16985
17591
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
16986
|
-
const assignmentsDir2 =
|
|
17592
|
+
const assignmentsDir2 = resolve43(projectsDir2, m.name, "assignments");
|
|
16987
17593
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
16988
17594
|
const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
|
|
16989
17595
|
for (const a of entries) {
|
|
16990
17596
|
if (!a.isDirectory()) continue;
|
|
16991
17597
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
16992
|
-
const assignmentDir =
|
|
16993
|
-
const assignmentMd =
|
|
17598
|
+
const assignmentDir = resolve43(assignmentsDir2, a.name);
|
|
17599
|
+
const assignmentMd = resolve43(assignmentDir, "assignment.md");
|
|
16994
17600
|
const entry = {
|
|
16995
|
-
projectDir:
|
|
17601
|
+
projectDir: resolve43(projectsDir2, m.name),
|
|
16996
17602
|
projectSlug: m.name,
|
|
16997
17603
|
assignmentDir,
|
|
16998
17604
|
assignmentSlug: a.name,
|
|
@@ -17012,8 +17618,8 @@ async function listAssignments(ctx) {
|
|
|
17012
17618
|
for (const a of entries) {
|
|
17013
17619
|
if (!a.isDirectory()) continue;
|
|
17014
17620
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
17015
|
-
const assignmentDir =
|
|
17016
|
-
const assignmentMd =
|
|
17621
|
+
const assignmentDir = resolve43(standaloneRoot, a.name);
|
|
17622
|
+
const assignmentMd = resolve43(assignmentDir, "assignment.md");
|
|
17017
17623
|
const entry = {
|
|
17018
17624
|
projectDir: standaloneRoot,
|
|
17019
17625
|
projectSlug: null,
|
|
@@ -17091,7 +17697,7 @@ var invalidStatus = {
|
|
|
17091
17697
|
const allowed = configuredStatuses(ctx);
|
|
17092
17698
|
const results = [];
|
|
17093
17699
|
for (const a of withAssignmentMd) {
|
|
17094
|
-
const path =
|
|
17700
|
+
const path = resolve43(a.assignmentDir, "assignment.md");
|
|
17095
17701
|
const parsed = await parseSafe(path);
|
|
17096
17702
|
if (!parsed) continue;
|
|
17097
17703
|
if (!allowed.has(parsed.status)) {
|
|
@@ -17124,7 +17730,7 @@ var workspaceMissing = {
|
|
|
17124
17730
|
const terminal = terminalStatuses(ctx);
|
|
17125
17731
|
const results = [];
|
|
17126
17732
|
for (const a of withAssignmentMd) {
|
|
17127
|
-
const path =
|
|
17733
|
+
const path = resolve43(a.assignmentDir, "assignment.md");
|
|
17128
17734
|
const parsed = await parseSafe(path);
|
|
17129
17735
|
if (!parsed) continue;
|
|
17130
17736
|
if (terminal.has(parsed.status)) continue;
|
|
@@ -17171,12 +17777,12 @@ var requiredFilesByStatus = {
|
|
|
17171
17777
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
17172
17778
|
const results = [];
|
|
17173
17779
|
for (const a of withAssignmentMd) {
|
|
17174
|
-
const assignmentPath =
|
|
17780
|
+
const assignmentPath = resolve43(a.assignmentDir, "assignment.md");
|
|
17175
17781
|
const parsed = await parseSafe(assignmentPath);
|
|
17176
17782
|
if (!parsed) continue;
|
|
17177
17783
|
const missing = [];
|
|
17178
17784
|
if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
|
|
17179
|
-
const handoffPath =
|
|
17785
|
+
const handoffPath = resolve43(a.assignmentDir, "handoff.md");
|
|
17180
17786
|
if (!await fileExists(handoffPath)) missing.push("handoff.md");
|
|
17181
17787
|
}
|
|
17182
17788
|
if (missing.length === 0) continue;
|
|
@@ -17186,7 +17792,7 @@ var requiredFilesByStatus = {
|
|
|
17186
17792
|
title: this.title,
|
|
17187
17793
|
status: "warn",
|
|
17188
17794
|
detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
|
|
17189
|
-
affected: missing.map((m) =>
|
|
17795
|
+
affected: missing.map((m) => resolve43(a.assignmentDir, m)),
|
|
17190
17796
|
remediation: {
|
|
17191
17797
|
kind: "manual",
|
|
17192
17798
|
suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
|
|
@@ -17209,7 +17815,7 @@ var companionFilesScaffolded = {
|
|
|
17209
17815
|
for (const a of withAssignmentMd) {
|
|
17210
17816
|
const missing = [];
|
|
17211
17817
|
for (const filename of ["progress.md", "comments.md"]) {
|
|
17212
|
-
if (!await fileExists(
|
|
17818
|
+
if (!await fileExists(resolve43(a.assignmentDir, filename))) {
|
|
17213
17819
|
missing.push(filename);
|
|
17214
17820
|
}
|
|
17215
17821
|
}
|
|
@@ -17221,7 +17827,7 @@ var companionFilesScaffolded = {
|
|
|
17221
17827
|
title: this.title,
|
|
17222
17828
|
status: "warn",
|
|
17223
17829
|
detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
|
|
17224
|
-
affected: missing.map((m) =>
|
|
17830
|
+
affected: missing.map((m) => resolve43(a.assignmentDir, m)),
|
|
17225
17831
|
remediation: {
|
|
17226
17832
|
kind: "manual",
|
|
17227
17833
|
suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
|
|
@@ -17254,7 +17860,7 @@ var typeDefinition = {
|
|
|
17254
17860
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
17255
17861
|
const results = [];
|
|
17256
17862
|
for (const a of withAssignmentMd) {
|
|
17257
|
-
const path =
|
|
17863
|
+
const path = resolve43(a.assignmentDir, "assignment.md");
|
|
17258
17864
|
const parsed = await parseSafe(path);
|
|
17259
17865
|
if (!parsed) continue;
|
|
17260
17866
|
if (!parsed.type) continue;
|
|
@@ -17288,7 +17894,7 @@ var projectFrontmatterMatchesContainer = {
|
|
|
17288
17894
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
17289
17895
|
const results = [];
|
|
17290
17896
|
for (const a of withAssignmentMd) {
|
|
17291
|
-
const path =
|
|
17897
|
+
const path = resolve43(a.assignmentDir, "assignment.md");
|
|
17292
17898
|
const parsed = await parseSafe(path);
|
|
17293
17899
|
if (!parsed) continue;
|
|
17294
17900
|
if (a.standalone) {
|
|
@@ -17362,7 +17968,7 @@ function pass4(check, detail) {
|
|
|
17362
17968
|
|
|
17363
17969
|
// src/utils/doctor/checks/dashboard.ts
|
|
17364
17970
|
init_fs();
|
|
17365
|
-
import { resolve as
|
|
17971
|
+
import { resolve as resolve44 } from "path";
|
|
17366
17972
|
var CATEGORY5 = "dashboard";
|
|
17367
17973
|
var dbReachable = {
|
|
17368
17974
|
id: "dashboard.db-reachable",
|
|
@@ -17376,7 +17982,7 @@ var dbReachable = {
|
|
|
17376
17982
|
title: this.title,
|
|
17377
17983
|
status: "error",
|
|
17378
17984
|
detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
|
|
17379
|
-
affected: [
|
|
17985
|
+
affected: [resolve44(ctx.syntaurRoot, "syntaur.db")],
|
|
17380
17986
|
remediation: {
|
|
17381
17987
|
kind: "manual",
|
|
17382
17988
|
suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
|
|
@@ -17394,7 +18000,7 @@ var dbReachable = {
|
|
|
17394
18000
|
title: this.title,
|
|
17395
18001
|
status: "error",
|
|
17396
18002
|
detail: 'syntaur.db is missing the expected "sessions" table',
|
|
17397
|
-
affected: [
|
|
18003
|
+
affected: [resolve44(ctx.syntaurRoot, "syntaur.db")],
|
|
17398
18004
|
autoFixable: false
|
|
17399
18005
|
};
|
|
17400
18006
|
}
|
|
@@ -17406,7 +18012,7 @@ var dbReachable = {
|
|
|
17406
18012
|
title: this.title,
|
|
17407
18013
|
status: "error",
|
|
17408
18014
|
detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
17409
|
-
affected: [
|
|
18015
|
+
affected: [resolve44(ctx.syntaurRoot, "syntaur.db")],
|
|
17410
18016
|
autoFixable: false
|
|
17411
18017
|
};
|
|
17412
18018
|
}
|
|
@@ -17432,7 +18038,7 @@ var ghostSessions = {
|
|
|
17432
18038
|
const results = [];
|
|
17433
18039
|
for (const row of rows) {
|
|
17434
18040
|
if (!row.project_slug) continue;
|
|
17435
|
-
const projectPath =
|
|
18041
|
+
const projectPath = resolve44(projectsDir2, row.project_slug, "project.md");
|
|
17436
18042
|
if (!await fileExists(projectPath)) {
|
|
17437
18043
|
results.push({
|
|
17438
18044
|
id: this.id,
|
|
@@ -17451,7 +18057,7 @@ var ghostSessions = {
|
|
|
17451
18057
|
continue;
|
|
17452
18058
|
}
|
|
17453
18059
|
if (row.assignment_slug) {
|
|
17454
|
-
const assignmentPath =
|
|
18060
|
+
const assignmentPath = resolve44(
|
|
17455
18061
|
projectsDir2,
|
|
17456
18062
|
row.project_slug,
|
|
17457
18063
|
"assignments",
|
|
@@ -17503,7 +18109,7 @@ function skipped(check, reason) {
|
|
|
17503
18109
|
|
|
17504
18110
|
// src/utils/doctor/checks/integrations.ts
|
|
17505
18111
|
init_fs();
|
|
17506
|
-
import { resolve as
|
|
18112
|
+
import { resolve as resolve45, dirname as dirname13, basename as basename5 } from "path";
|
|
17507
18113
|
import { readdir as readdir16, readFile as readFile27 } from "fs/promises";
|
|
17508
18114
|
import { homedir as homedir6 } from "os";
|
|
17509
18115
|
var CATEGORY6 = "integrations";
|
|
@@ -17586,7 +18192,7 @@ var backupConfigured = {
|
|
|
17586
18192
|
}
|
|
17587
18193
|
};
|
|
17588
18194
|
async function readKnownMarketplaces() {
|
|
17589
|
-
const path =
|
|
18195
|
+
const path = resolve45(homedir6(), ".claude", "plugins", "known_marketplaces.json");
|
|
17590
18196
|
if (!await fileExists(path)) return {};
|
|
17591
18197
|
try {
|
|
17592
18198
|
const raw = await readFile27(path, "utf-8");
|
|
@@ -17605,8 +18211,8 @@ var claudeMarketplaceRegistered = {
|
|
|
17605
18211
|
if (!await fileExists(dir)) {
|
|
17606
18212
|
return skipped2(this, "claudePluginDir does not exist (run install-plugin)");
|
|
17607
18213
|
}
|
|
17608
|
-
const pluginsParent =
|
|
17609
|
-
if (
|
|
18214
|
+
const pluginsParent = dirname13(dir);
|
|
18215
|
+
if (basename5(pluginsParent) !== "plugins") {
|
|
17610
18216
|
return {
|
|
17611
18217
|
id: this.id,
|
|
17612
18218
|
category: this.category,
|
|
@@ -17622,8 +18228,8 @@ var claudeMarketplaceRegistered = {
|
|
|
17622
18228
|
autoFixable: false
|
|
17623
18229
|
};
|
|
17624
18230
|
}
|
|
17625
|
-
const marketplaceRoot =
|
|
17626
|
-
const marketplaceManifest =
|
|
18231
|
+
const marketplaceRoot = dirname13(pluginsParent);
|
|
18232
|
+
const marketplaceManifest = resolve45(marketplaceRoot, ".claude-plugin", "marketplace.json");
|
|
17627
18233
|
if (!await fileExists(marketplaceManifest)) {
|
|
17628
18234
|
return {
|
|
17629
18235
|
id: this.id,
|
|
@@ -17654,7 +18260,7 @@ var claudeMarketplaceRegistered = {
|
|
|
17654
18260
|
autoFixable: false
|
|
17655
18261
|
};
|
|
17656
18262
|
}
|
|
17657
|
-
const marketplaceName = parsed.name ??
|
|
18263
|
+
const marketplaceName = parsed.name ?? basename5(marketplaceRoot);
|
|
17658
18264
|
const hasSyntaurEntry = (parsed.plugins ?? []).some((p) => p?.name === "syntaur");
|
|
17659
18265
|
const known = await readKnownMarketplaces();
|
|
17660
18266
|
const registered = known[marketplaceName]?.installLocation === marketplaceRoot || Object.values(known).some((v) => v.installLocation === marketplaceRoot);
|
|
@@ -17674,7 +18280,7 @@ var claudeMarketplaceRegistered = {
|
|
|
17674
18280
|
title: this.title,
|
|
17675
18281
|
status: "error",
|
|
17676
18282
|
detail: issues.join("; "),
|
|
17677
|
-
affected: [marketplaceManifest,
|
|
18283
|
+
affected: [marketplaceManifest, resolve45(homedir6(), ".claude", "plugins", "known_marketplaces.json")],
|
|
17678
18284
|
remediation: {
|
|
17679
18285
|
kind: "manual",
|
|
17680
18286
|
suggestion: "Re-run install-plugin to ensure both files are in sync.",
|
|
@@ -17714,7 +18320,7 @@ function skipped2(check, reason) {
|
|
|
17714
18320
|
init_fs();
|
|
17715
18321
|
init_parser();
|
|
17716
18322
|
init_types();
|
|
17717
|
-
import { resolve as
|
|
18323
|
+
import { resolve as resolve46 } from "path";
|
|
17718
18324
|
import { readFile as readFile28 } from "fs/promises";
|
|
17719
18325
|
var CATEGORY7 = "workspace";
|
|
17720
18326
|
var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
|
|
@@ -17727,7 +18333,7 @@ function isStandaloneSession(ctx) {
|
|
|
17727
18333
|
return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
|
|
17728
18334
|
}
|
|
17729
18335
|
async function loadContext(ctx) {
|
|
17730
|
-
const path =
|
|
18336
|
+
const path = resolve46(ctx.cwd, ".syntaur", "context.json");
|
|
17731
18337
|
if (!await fileExists(path)) {
|
|
17732
18338
|
return { data: null, path, exists: false, parseError: null };
|
|
17733
18339
|
}
|
|
@@ -17807,7 +18413,7 @@ var contextAssignmentResolves = {
|
|
|
17807
18413
|
if (!exists) return skipped3(this, "no context to resolve");
|
|
17808
18414
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
|
|
17809
18415
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
17810
|
-
const assignmentMd =
|
|
18416
|
+
const assignmentMd = resolve46(data.assignmentDir, "assignment.md");
|
|
17811
18417
|
if (!await fileExists(assignmentMd)) {
|
|
17812
18418
|
return {
|
|
17813
18419
|
id: this.id,
|
|
@@ -17836,7 +18442,7 @@ var contextTerminal = {
|
|
|
17836
18442
|
if (!exists) return skipped3(this, "no context to check");
|
|
17837
18443
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
|
|
17838
18444
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
17839
|
-
const assignmentMd =
|
|
18445
|
+
const assignmentMd = resolve46(data.assignmentDir, "assignment.md");
|
|
17840
18446
|
if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
|
|
17841
18447
|
try {
|
|
17842
18448
|
const content = await readFile28(assignmentMd, "utf-8");
|
|
@@ -17984,13 +18590,13 @@ var agentChecks = [agentsResolvable];
|
|
|
17984
18590
|
|
|
17985
18591
|
// src/utils/doctor/checks/skills.ts
|
|
17986
18592
|
init_fs();
|
|
17987
|
-
import { resolve as
|
|
18593
|
+
import { resolve as resolve47, join as join5 } from "path";
|
|
17988
18594
|
import { readdir as readdir17, readFile as readFile29, lstat as lstat4 } from "fs/promises";
|
|
17989
18595
|
import { homedir as homedir7 } from "os";
|
|
17990
18596
|
var CATEGORY9 = "skills";
|
|
17991
18597
|
var skillTargets = [
|
|
17992
|
-
{ agent: "claude", dir:
|
|
17993
|
-
{ agent: "codex", dir:
|
|
18598
|
+
{ agent: "claude", dir: resolve47(homedir7(), ".claude", "skills"), label: "~/.claude/skills" },
|
|
18599
|
+
{ agent: "codex", dir: resolve47(homedir7(), ".codex", "skills"), label: "~/.codex/skills" }
|
|
17994
18600
|
];
|
|
17995
18601
|
var skillsDedupCheck = {
|
|
17996
18602
|
id: "skills.dedup",
|
|
@@ -18156,14 +18762,14 @@ async function finalize(checks) {
|
|
|
18156
18762
|
async function readVersion() {
|
|
18157
18763
|
try {
|
|
18158
18764
|
const here = fileURLToPath6(import.meta.url);
|
|
18159
|
-
let dir =
|
|
18765
|
+
let dir = dirname14(here);
|
|
18160
18766
|
for (let i = 0; i < 6; i++) {
|
|
18161
18767
|
try {
|
|
18162
18768
|
const raw = await readFile30(join6(dir, "package.json"), "utf-8");
|
|
18163
18769
|
const parsed = JSON.parse(raw);
|
|
18164
18770
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
18165
18771
|
} catch {
|
|
18166
|
-
dir =
|
|
18772
|
+
dir = dirname14(dir);
|
|
18167
18773
|
}
|
|
18168
18774
|
}
|
|
18169
18775
|
return null;
|
|
@@ -18256,7 +18862,7 @@ var REQUIRED_WORKSPACE_FIELDS = [
|
|
|
18256
18862
|
];
|
|
18257
18863
|
var ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
|
|
18258
18864
|
async function validateAssignmentFile(inputPath, cwd = process.cwd()) {
|
|
18259
|
-
const absolute = isAbsolute7(inputPath) ? inputPath :
|
|
18865
|
+
const absolute = isAbsolute7(inputPath) ? inputPath : resolve48(cwd, inputPath);
|
|
18260
18866
|
const errors = [];
|
|
18261
18867
|
const warnings = [];
|
|
18262
18868
|
if (!await fileExists(absolute)) {
|
|
@@ -18593,7 +19199,7 @@ init_uuid();
|
|
|
18593
19199
|
init_timestamp();
|
|
18594
19200
|
init_assignment_resolver();
|
|
18595
19201
|
init_templates();
|
|
18596
|
-
import { resolve as
|
|
19202
|
+
import { resolve as resolve49 } from "path";
|
|
18597
19203
|
import { readFile as readFile32 } from "fs/promises";
|
|
18598
19204
|
function shortId() {
|
|
18599
19205
|
return generateId().split("-")[0];
|
|
@@ -18624,7 +19230,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
18624
19230
|
if (!isValidSlug(target)) {
|
|
18625
19231
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
18626
19232
|
}
|
|
18627
|
-
assignmentDir =
|
|
19233
|
+
assignmentDir = resolve49(baseDir, options.project, "assignments", target);
|
|
18628
19234
|
assignmentRef = target;
|
|
18629
19235
|
} else {
|
|
18630
19236
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -18634,7 +19240,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
18634
19240
|
assignmentDir = resolved.assignmentDir;
|
|
18635
19241
|
assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
18636
19242
|
}
|
|
18637
|
-
const commentsPath =
|
|
19243
|
+
const commentsPath = resolve49(assignmentDir, "comments.md");
|
|
18638
19244
|
const timestamp = nowTimestamp();
|
|
18639
19245
|
const author = options.author ?? process.env.USER ?? "unknown";
|
|
18640
19246
|
let currentContent;
|
|
@@ -18674,7 +19280,7 @@ ${entry}`;
|
|
|
18674
19280
|
}
|
|
18675
19281
|
|
|
18676
19282
|
// src/commands/capture.ts
|
|
18677
|
-
import { resolve as
|
|
19283
|
+
import { resolve as resolve52, relative as relative4 } from "path";
|
|
18678
19284
|
import { copyFile as copyFile3, mkdir as mkdir7, realpath as realpath2, stat as stat6 } from "fs/promises";
|
|
18679
19285
|
|
|
18680
19286
|
// src/utils/assignment-target.ts
|
|
@@ -18684,12 +19290,12 @@ init_config2();
|
|
|
18684
19290
|
init_slug();
|
|
18685
19291
|
init_assignment_resolver();
|
|
18686
19292
|
init_parser();
|
|
18687
|
-
import { resolve as
|
|
19293
|
+
import { resolve as resolve50 } from "path";
|
|
18688
19294
|
import { readFile as readFile33 } from "fs/promises";
|
|
18689
19295
|
var AssignmentTargetError = class extends Error {
|
|
18690
19296
|
};
|
|
18691
19297
|
async function readAssignmentFrontmatterId(assignmentDir) {
|
|
18692
|
-
const path =
|
|
19298
|
+
const path = resolve50(assignmentDir, "assignment.md");
|
|
18693
19299
|
if (!await fileExists(path)) return null;
|
|
18694
19300
|
try {
|
|
18695
19301
|
const content = await readFile33(path, "utf-8");
|
|
@@ -18700,7 +19306,7 @@ async function readAssignmentFrontmatterId(assignmentDir) {
|
|
|
18700
19306
|
}
|
|
18701
19307
|
}
|
|
18702
19308
|
async function readContextJson(cwd) {
|
|
18703
|
-
const path =
|
|
19309
|
+
const path = resolve50(cwd, ".syntaur", "context.json");
|
|
18704
19310
|
if (!await fileExists(path)) return null;
|
|
18705
19311
|
try {
|
|
18706
19312
|
const raw = await readFile33(path, "utf-8");
|
|
@@ -18724,15 +19330,15 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
18724
19330
|
if (!isValidSlug(input4)) {
|
|
18725
19331
|
throw new AssignmentTargetError(`Invalid assignment slug "${input4}".`);
|
|
18726
19332
|
}
|
|
18727
|
-
const projectDir =
|
|
18728
|
-
const projectMdPath =
|
|
19333
|
+
const projectDir = resolve50(baseDir, opts.project);
|
|
19334
|
+
const projectMdPath = resolve50(projectDir, "project.md");
|
|
18729
19335
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
18730
19336
|
throw new AssignmentTargetError(
|
|
18731
19337
|
`Project "${opts.project}" not found at ${projectDir}.`
|
|
18732
19338
|
);
|
|
18733
19339
|
}
|
|
18734
|
-
const assignmentDir =
|
|
18735
|
-
const assignmentMdPath2 =
|
|
19340
|
+
const assignmentDir = resolve50(projectDir, "assignments", input4);
|
|
19341
|
+
const assignmentMdPath2 = resolve50(assignmentDir, "assignment.md");
|
|
18736
19342
|
if (!await fileExists(assignmentMdPath2)) {
|
|
18737
19343
|
throw new AssignmentTargetError(
|
|
18738
19344
|
`Assignment "${input4}" not found in project "${opts.project}".`
|
|
@@ -18766,7 +19372,7 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
18766
19372
|
}
|
|
18767
19373
|
if (ctx.assignmentDir) {
|
|
18768
19374
|
const dir = expandHome(ctx.assignmentDir);
|
|
18769
|
-
const assignmentMdPath2 =
|
|
19375
|
+
const assignmentMdPath2 = resolve50(dir, "assignment.md");
|
|
18770
19376
|
if (!await fileExists(assignmentMdPath2)) {
|
|
18771
19377
|
throw new AssignmentTargetError(
|
|
18772
19378
|
`.syntaur/context.json points to a missing assignment dir: ${dir}.`
|
|
@@ -18795,8 +19401,8 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
18795
19401
|
`.syntaur/context.json contains invalid slugs: project="${ctx.projectSlug}" assignment="${ctx.assignmentSlug}".`
|
|
18796
19402
|
);
|
|
18797
19403
|
}
|
|
18798
|
-
const assignmentDir =
|
|
18799
|
-
const assignmentMdPath2 =
|
|
19404
|
+
const assignmentDir = resolve50(baseDir, ctx.projectSlug, "assignments", ctx.assignmentSlug);
|
|
19405
|
+
const assignmentMdPath2 = resolve50(assignmentDir, "assignment.md");
|
|
18800
19406
|
if (!await fileExists(assignmentMdPath2)) {
|
|
18801
19407
|
throw new AssignmentTargetError(
|
|
18802
19408
|
`.syntaur/context.json points to a missing assignment: ${assignmentDir}.`
|
|
@@ -18857,11 +19463,11 @@ init_fs();
|
|
|
18857
19463
|
|
|
18858
19464
|
// src/db/proof-db.ts
|
|
18859
19465
|
init_paths();
|
|
18860
|
-
import
|
|
18861
|
-
import { resolve as
|
|
18862
|
-
var
|
|
19466
|
+
import Database4 from "better-sqlite3";
|
|
19467
|
+
import { resolve as resolve51 } from "path";
|
|
19468
|
+
var db3 = null;
|
|
18863
19469
|
var PROOF_SCHEMA_VERSION = "1";
|
|
18864
|
-
var
|
|
19470
|
+
var SCHEMA_SQL3 = `
|
|
18865
19471
|
CREATE TABLE IF NOT EXISTS artifacts (
|
|
18866
19472
|
id TEXT PRIMARY KEY,
|
|
18867
19473
|
assignment_id TEXT NOT NULL,
|
|
@@ -18877,26 +19483,26 @@ CREATE INDEX IF NOT EXISTS idx_artifacts_assignment_criterion ON artifacts(assig
|
|
|
18877
19483
|
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
|
|
18878
19484
|
`;
|
|
18879
19485
|
function initProofDb(dbPath) {
|
|
18880
|
-
if (
|
|
18881
|
-
const finalPath = dbPath ??
|
|
18882
|
-
|
|
18883
|
-
|
|
18884
|
-
|
|
18885
|
-
|
|
19486
|
+
if (db3) return db3;
|
|
19487
|
+
const finalPath = dbPath ?? resolve51(syntaurRoot(), "syntaur.db");
|
|
19488
|
+
db3 = new Database4(finalPath);
|
|
19489
|
+
db3.pragma("journal_mode = WAL");
|
|
19490
|
+
db3.exec(SCHEMA_SQL3);
|
|
19491
|
+
db3.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run(
|
|
18886
19492
|
"proof_schema_version",
|
|
18887
19493
|
PROOF_SCHEMA_VERSION
|
|
18888
19494
|
);
|
|
18889
|
-
const database =
|
|
19495
|
+
const database = db3;
|
|
18890
19496
|
const runMigrations = database.transaction(() => {
|
|
18891
19497
|
});
|
|
18892
19498
|
runMigrations.exclusive();
|
|
18893
|
-
return
|
|
19499
|
+
return db3;
|
|
18894
19500
|
}
|
|
18895
19501
|
function getProofDb() {
|
|
18896
|
-
if (!
|
|
19502
|
+
if (!db3) {
|
|
18897
19503
|
throw new Error("Proof database not initialized. Call initProofDb() first.");
|
|
18898
19504
|
}
|
|
18899
|
-
return
|
|
19505
|
+
return db3;
|
|
18900
19506
|
}
|
|
18901
19507
|
function insertArtifact(input4) {
|
|
18902
19508
|
const database = getProofDb();
|
|
@@ -18982,7 +19588,7 @@ async function captureCommand(target, options = {}) {
|
|
|
18982
19588
|
});
|
|
18983
19589
|
let resolvedSource = null;
|
|
18984
19590
|
if (options.file) {
|
|
18985
|
-
const expanded = options.file.startsWith("~/") ?
|
|
19591
|
+
const expanded = options.file.startsWith("~/") ? resolve52(process.env.HOME ?? "", options.file.slice(2)) : resolve52(options.file);
|
|
18986
19592
|
if (!await fileExists(expanded)) {
|
|
18987
19593
|
throw new Error(`--file does not exist: ${options.file}`);
|
|
18988
19594
|
}
|
|
@@ -19007,7 +19613,7 @@ async function captureCommand(target, options = {}) {
|
|
|
19007
19613
|
}
|
|
19008
19614
|
initProofDb();
|
|
19009
19615
|
const subdir = criterionIndex === null ? "untagged" : String(criterionIndex);
|
|
19010
|
-
const destDir =
|
|
19616
|
+
const destDir = resolve52(proofDir(resolved.assignmentDir), subdir);
|
|
19011
19617
|
if (resolvedSource) await mkdir7(destDir, { recursive: true });
|
|
19012
19618
|
const ext = resolvedSource ? extensionForKind(kind) : null;
|
|
19013
19619
|
let id = null;
|
|
@@ -19016,7 +19622,7 @@ async function captureCommand(target, options = {}) {
|
|
|
19016
19622
|
let lastErr = null;
|
|
19017
19623
|
for (let attempt = 0; attempt < MAX_ID_RETRIES; attempt += 1) {
|
|
19018
19624
|
const candidate = generateArtifactId();
|
|
19019
|
-
const candidateAbsPath = resolvedSource && ext ?
|
|
19625
|
+
const candidateAbsPath = resolvedSource && ext ? resolve52(destDir, `${candidate}.${ext}`) : null;
|
|
19020
19626
|
const candidateRel = candidateAbsPath ? relative4(resolved.assignmentDir, candidateAbsPath) : null;
|
|
19021
19627
|
try {
|
|
19022
19628
|
insertArtifact({
|
|
@@ -19052,14 +19658,14 @@ async function captureCommand(target, options = {}) {
|
|
|
19052
19658
|
const tagSuffix = criterionIndex === null ? "untagged" : `criterion ${criterionIndex}`;
|
|
19053
19659
|
console.log(`Captured artifact ${id} (${kind}) for ${ref} \u2014 ${tagSuffix}.`);
|
|
19054
19660
|
if (relativeFilePath) {
|
|
19055
|
-
console.log(` file: ${
|
|
19661
|
+
console.log(` file: ${resolve52(resolved.assignmentDir, relativeFilePath)}`);
|
|
19056
19662
|
}
|
|
19057
19663
|
}
|
|
19058
19664
|
|
|
19059
19665
|
// src/commands/proof.ts
|
|
19060
19666
|
import { Command as Command5 } from "commander";
|
|
19061
19667
|
import { readFile as readFile34, writeFile as writeFile10, rename as rename7, stat as stat7 } from "fs/promises";
|
|
19062
|
-
import { resolve as
|
|
19668
|
+
import { resolve as resolve53, relative as relative5, isAbsolute as isAbsolute8 } from "path";
|
|
19063
19669
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
19064
19670
|
|
|
19065
19671
|
// src/utils/acceptance-criteria-parse.ts
|
|
@@ -19262,7 +19868,7 @@ function renderProofHtml(params2, inlineFiles = /* @__PURE__ */ new Map()) {
|
|
|
19262
19868
|
|
|
19263
19869
|
// src/commands/proof.ts
|
|
19264
19870
|
async function readAssignmentMeta(assignmentDir) {
|
|
19265
|
-
const path =
|
|
19871
|
+
const path = resolve53(assignmentDir, "assignment.md");
|
|
19266
19872
|
if (!await fileExists(path)) {
|
|
19267
19873
|
return { title: "", body: "" };
|
|
19268
19874
|
}
|
|
@@ -19313,7 +19919,7 @@ async function loadInlineFiles(rows, assignmentDir) {
|
|
|
19313
19919
|
for (const r of rows) {
|
|
19314
19920
|
if (!r.file_path) continue;
|
|
19315
19921
|
if (r.kind !== "http" && r.kind !== "text") continue;
|
|
19316
|
-
const abs =
|
|
19922
|
+
const abs = resolve53(assignmentDir, r.file_path);
|
|
19317
19923
|
if (!isWithin(proofRoot, abs)) {
|
|
19318
19924
|
out.set(r.file_path, null);
|
|
19319
19925
|
continue;
|
|
@@ -19361,8 +19967,8 @@ async function proofBuildCommand(target, options = {}) {
|
|
|
19361
19967
|
};
|
|
19362
19968
|
const md = renderProofMarkdown(renderParams);
|
|
19363
19969
|
const html = renderProofHtml(renderParams, inlineFiles);
|
|
19364
|
-
const mdPath =
|
|
19365
|
-
const htmlPath =
|
|
19970
|
+
const mdPath = resolve53(resolved.assignmentDir, "proof.md");
|
|
19971
|
+
const htmlPath = resolve53(resolved.assignmentDir, "proof.html");
|
|
19366
19972
|
await atomicWrite(mdPath, md);
|
|
19367
19973
|
await atomicWrite(htmlPath, html);
|
|
19368
19974
|
console.log(`Wrote ${htmlPath}`);
|
|
@@ -19379,6 +19985,261 @@ proofCommand.command("build").description("Render proof.html and proof.md for an
|
|
|
19379
19985
|
}
|
|
19380
19986
|
});
|
|
19381
19987
|
|
|
19988
|
+
// src/commands/lease.ts
|
|
19989
|
+
import { Command as Command6 } from "commander";
|
|
19990
|
+
var DURATION_REGEX = /^(\d+)\s*(s|m|h|d)?$/i;
|
|
19991
|
+
function parseDuration(input4, fallbackSeconds) {
|
|
19992
|
+
if (!input4) return fallbackSeconds;
|
|
19993
|
+
const match = DURATION_REGEX.exec(input4.trim());
|
|
19994
|
+
if (!match) {
|
|
19995
|
+
throw new Error(
|
|
19996
|
+
`invalid duration "${input4}" \u2014 use e.g. 30s, 5m, 2h, 1d`
|
|
19997
|
+
);
|
|
19998
|
+
}
|
|
19999
|
+
const n = Number.parseInt(match[1], 10);
|
|
20000
|
+
const unit = (match[2] ?? "s").toLowerCase();
|
|
20001
|
+
const mult = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
20002
|
+
return n * mult[unit];
|
|
20003
|
+
}
|
|
20004
|
+
function parseMetadataFlags(values) {
|
|
20005
|
+
if (!values || values.length === 0) return void 0;
|
|
20006
|
+
const out = {};
|
|
20007
|
+
for (const raw of values) {
|
|
20008
|
+
const eq = raw.indexOf("=");
|
|
20009
|
+
if (eq < 0) {
|
|
20010
|
+
throw new Error(`invalid -m flag "${raw}" \u2014 use key=value`);
|
|
20011
|
+
}
|
|
20012
|
+
out[raw.slice(0, eq)] = raw.slice(eq + 1);
|
|
20013
|
+
}
|
|
20014
|
+
return out;
|
|
20015
|
+
}
|
|
20016
|
+
var leaseCommand = new Command6("lease").description(
|
|
20017
|
+
"Resource leases \u2014 claim and release coordinated shared resources"
|
|
20018
|
+
);
|
|
20019
|
+
leaseCommand.command("create-inventory").description("Create a new inventory of leaseable resources").argument("<slug>", "Inventory slug (e.g. dev-envs, prod-migration-lock)").requiredOption("--kind <kind>", "Inventory kind (free-form label, e.g. dev-env, db-instance, lock)").option("--display-name <text>", "Human-readable display name").option("--default-ttl <duration>", "Default lease TTL (e.g. 30m, 2h). Default: 30m", "30m").action(async (slug, opts) => {
|
|
20020
|
+
try {
|
|
20021
|
+
initLeasesDb();
|
|
20022
|
+
const ttl_s = parseDuration(opts.defaultTtl, 30 * 60);
|
|
20023
|
+
const row = createInventory({
|
|
20024
|
+
slug,
|
|
20025
|
+
kind: opts.kind,
|
|
20026
|
+
display_name: opts.displayName,
|
|
20027
|
+
default_ttl_s: ttl_s
|
|
20028
|
+
});
|
|
20029
|
+
console.log(`Created inventory '${row.slug}' (kind=${row.kind}, default_ttl=${row.default_ttl_s}s).`);
|
|
20030
|
+
} catch (error) {
|
|
20031
|
+
if (error instanceof DuplicateInventoryError) {
|
|
20032
|
+
console.error(`Error: inventory '${error.slug}' already exists`);
|
|
20033
|
+
process.exit(1);
|
|
20034
|
+
}
|
|
20035
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20036
|
+
console.error(`Error: ${message}`);
|
|
20037
|
+
process.exit(1);
|
|
20038
|
+
}
|
|
20039
|
+
});
|
|
20040
|
+
var memberCommand = leaseCommand.command("member").description("Manage members of an inventory");
|
|
20041
|
+
memberCommand.command("add").description("Add a member to an inventory").argument("<inventory>", "Inventory slug").argument("<member-id>", "Member id (unique within the inventory)").option("-m, --metadata <key=value...>", "Metadata key=value pairs (repeatable)").action(async (inventory, memberId, opts) => {
|
|
20042
|
+
try {
|
|
20043
|
+
initLeasesDb();
|
|
20044
|
+
const metadata = parseMetadataFlags(opts.metadata);
|
|
20045
|
+
addMember({ inventory_slug: inventory, member_id: memberId, metadata });
|
|
20046
|
+
console.log(`Added member '${memberId}' to '${inventory}'.`);
|
|
20047
|
+
} catch (error) {
|
|
20048
|
+
if (error instanceof DuplicateMemberError) {
|
|
20049
|
+
console.error(`Error: member ${error.inventorySlug}/${error.memberId} already exists`);
|
|
20050
|
+
process.exit(1);
|
|
20051
|
+
}
|
|
20052
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20053
|
+
console.error(`Error: ${message}`);
|
|
20054
|
+
process.exit(1);
|
|
20055
|
+
}
|
|
20056
|
+
});
|
|
20057
|
+
memberCommand.command("retire").description("Retire a member (no longer claimable). Refuses if member is currently leased.").argument("<inventory>", "Inventory slug").argument("<member-id>", "Member id").action(async (inventory, memberId) => {
|
|
20058
|
+
try {
|
|
20059
|
+
initLeasesDb();
|
|
20060
|
+
retireMember(inventory, memberId);
|
|
20061
|
+
console.log(`Retired member '${inventory}/${memberId}'.`);
|
|
20062
|
+
} catch (error) {
|
|
20063
|
+
if (error instanceof MemberInUseError) {
|
|
20064
|
+
console.error(
|
|
20065
|
+
`Error: member ${error.inventorySlug}/${error.memberId} is currently leased; release before retiring`
|
|
20066
|
+
);
|
|
20067
|
+
process.exit(1);
|
|
20068
|
+
}
|
|
20069
|
+
if (error instanceof NotFoundError) {
|
|
20070
|
+
console.error(`Error: member not found`);
|
|
20071
|
+
process.exit(1);
|
|
20072
|
+
}
|
|
20073
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20074
|
+
console.error(`Error: ${message}`);
|
|
20075
|
+
process.exit(1);
|
|
20076
|
+
}
|
|
20077
|
+
});
|
|
20078
|
+
leaseCommand.command("claim").description("Claim an idle member of an inventory. Fail-fast if none idle.").argument("<inventory>", "Inventory slug").option("--ttl <duration>", "Lease TTL (overrides inventory default)").option("--for <tag>", "Free-form requester tag (session id, assignment slug, etc.)").option("--json", "Output JSON instead of a one-line summary").action(async (inventory, opts) => {
|
|
20079
|
+
try {
|
|
20080
|
+
initLeasesDb();
|
|
20081
|
+
const detail = getInventoryDetail(inventory);
|
|
20082
|
+
if (!detail) {
|
|
20083
|
+
console.error(`Error: inventory '${inventory}' not found`);
|
|
20084
|
+
process.exit(1);
|
|
20085
|
+
return;
|
|
20086
|
+
}
|
|
20087
|
+
const ttl_s = parseDuration(opts.ttl, detail.inventory.default_ttl_s);
|
|
20088
|
+
const result = claimLease(inventory, ttl_s, opts.for);
|
|
20089
|
+
if (opts.json) {
|
|
20090
|
+
console.log(JSON.stringify(result, null, 2));
|
|
20091
|
+
} else {
|
|
20092
|
+
console.log(
|
|
20093
|
+
`Claimed ${result.member_id} as ${result.lease_id} (expires ${result.expires_at})`
|
|
20094
|
+
);
|
|
20095
|
+
}
|
|
20096
|
+
} catch (error) {
|
|
20097
|
+
if (error instanceof NoIdleMemberError) {
|
|
20098
|
+
console.error(`Error: no idle members in '${error.inventorySlug}'`);
|
|
20099
|
+
process.exit(1);
|
|
20100
|
+
}
|
|
20101
|
+
if (error instanceof LeaseContentionError) {
|
|
20102
|
+
console.error(`Error: contention timeout on '${error.inventorySlug}'; retry`);
|
|
20103
|
+
process.exit(1);
|
|
20104
|
+
}
|
|
20105
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20106
|
+
console.error(`Error: ${message}`);
|
|
20107
|
+
process.exit(1);
|
|
20108
|
+
}
|
|
20109
|
+
});
|
|
20110
|
+
leaseCommand.command("release").description("Release a lease by its lease_id").argument("<lease-id>", "Opaque lease id returned by `claim`").action(async (leaseId) => {
|
|
20111
|
+
try {
|
|
20112
|
+
initLeasesDb();
|
|
20113
|
+
releaseLease(leaseId);
|
|
20114
|
+
console.log(`Released lease ${leaseId}.`);
|
|
20115
|
+
} catch (error) {
|
|
20116
|
+
if (error instanceof StaleLeaseError) {
|
|
20117
|
+
console.error(
|
|
20118
|
+
`Error: lease ${error.leaseId} is no longer active (expired or revoked)`
|
|
20119
|
+
);
|
|
20120
|
+
process.exit(1);
|
|
20121
|
+
}
|
|
20122
|
+
if (error instanceof LeaseContentionError) {
|
|
20123
|
+
console.error(`Error: contention timeout; retry`);
|
|
20124
|
+
process.exit(1);
|
|
20125
|
+
}
|
|
20126
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20127
|
+
console.error(`Error: ${message}`);
|
|
20128
|
+
process.exit(1);
|
|
20129
|
+
}
|
|
20130
|
+
});
|
|
20131
|
+
leaseCommand.command("extend").description("Extend a lease \u2014 sets new expires_at = now + ttl. Refuses if expired or stale.").argument("<lease-id>", "Opaque lease id").requiredOption("--ttl <duration>", "New TTL from now (e.g. 15m)").action(async (leaseId, opts) => {
|
|
20132
|
+
try {
|
|
20133
|
+
initLeasesDb();
|
|
20134
|
+
const ttl_s = parseDuration(opts.ttl, 0);
|
|
20135
|
+
if (ttl_s <= 0) {
|
|
20136
|
+
throw new Error("--ttl must be positive");
|
|
20137
|
+
}
|
|
20138
|
+
const res = extendLease(leaseId, ttl_s);
|
|
20139
|
+
console.log(`Extended lease ${leaseId} (new expires_at=${res.new_expires_at}).`);
|
|
20140
|
+
} catch (error) {
|
|
20141
|
+
if (error instanceof StaleLeaseError) {
|
|
20142
|
+
console.error(
|
|
20143
|
+
`Error: lease ${error.leaseId} cannot be extended (expired, revoked, or member generation advanced)`
|
|
20144
|
+
);
|
|
20145
|
+
process.exit(1);
|
|
20146
|
+
}
|
|
20147
|
+
if (error instanceof LeaseContentionError) {
|
|
20148
|
+
console.error(`Error: contention timeout; retry`);
|
|
20149
|
+
process.exit(1);
|
|
20150
|
+
}
|
|
20151
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20152
|
+
console.error(`Error: ${message}`);
|
|
20153
|
+
process.exit(1);
|
|
20154
|
+
}
|
|
20155
|
+
});
|
|
20156
|
+
leaseCommand.command("show").description("Show a single lease as JSON").argument("<lease-id>", "Lease id").action(async (leaseId) => {
|
|
20157
|
+
try {
|
|
20158
|
+
initLeasesDb();
|
|
20159
|
+
const row = getLease(leaseId);
|
|
20160
|
+
if (!row) {
|
|
20161
|
+
console.error(`Error: lease ${leaseId} not found`);
|
|
20162
|
+
process.exit(1);
|
|
20163
|
+
return;
|
|
20164
|
+
}
|
|
20165
|
+
console.log(JSON.stringify(row, null, 2));
|
|
20166
|
+
} catch (error) {
|
|
20167
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20168
|
+
console.error(`Error: ${message}`);
|
|
20169
|
+
process.exit(1);
|
|
20170
|
+
}
|
|
20171
|
+
});
|
|
20172
|
+
var VALID_STATES = /* @__PURE__ */ new Set([
|
|
20173
|
+
"active",
|
|
20174
|
+
"expired",
|
|
20175
|
+
"released",
|
|
20176
|
+
"revoked"
|
|
20177
|
+
]);
|
|
20178
|
+
leaseCommand.command("list").description("List leases (optionally filtered by inventory and/or state)").option("--inventory <slug>", "Filter by inventory").option("--state <state>", "Filter by state: active|expired|released|revoked").option("--json", "Output JSON instead of a table").action(async (opts) => {
|
|
20179
|
+
try {
|
|
20180
|
+
initLeasesDb();
|
|
20181
|
+
let state;
|
|
20182
|
+
if (opts.state) {
|
|
20183
|
+
if (!VALID_STATES.has(opts.state)) {
|
|
20184
|
+
throw new Error(
|
|
20185
|
+
`--state must be one of: ${[...VALID_STATES].join("|")}`
|
|
20186
|
+
);
|
|
20187
|
+
}
|
|
20188
|
+
state = opts.state;
|
|
20189
|
+
}
|
|
20190
|
+
if (!opts.inventory && !state && !opts.json) {
|
|
20191
|
+
const inventories = listInventories();
|
|
20192
|
+
if (inventories.length === 0) {
|
|
20193
|
+
console.log("(no inventories \u2014 create one with `syntaur lease create-inventory <slug> --kind <kind>`)");
|
|
20194
|
+
return;
|
|
20195
|
+
}
|
|
20196
|
+
for (const inv of inventories) {
|
|
20197
|
+
const detail = getInventoryDetail(inv.slug);
|
|
20198
|
+
if (!detail) continue;
|
|
20199
|
+
const idleCount = detail.members.filter((m) => m.status === "idle").length;
|
|
20200
|
+
const leasedCount = detail.members.filter((m) => m.status === "leased").length;
|
|
20201
|
+
console.log(
|
|
20202
|
+
`${inv.slug.padEnd(24)} kind=${inv.kind} idle=${idleCount} leased=${leasedCount} active_leases=${detail.active_leases.length}`
|
|
20203
|
+
);
|
|
20204
|
+
}
|
|
20205
|
+
return;
|
|
20206
|
+
}
|
|
20207
|
+
const rows = listLeases({ inventory: opts.inventory, state });
|
|
20208
|
+
if (opts.json) {
|
|
20209
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
20210
|
+
return;
|
|
20211
|
+
}
|
|
20212
|
+
if (rows.length === 0) {
|
|
20213
|
+
console.log("(no leases match)");
|
|
20214
|
+
return;
|
|
20215
|
+
}
|
|
20216
|
+
for (const r of rows) {
|
|
20217
|
+
console.log(
|
|
20218
|
+
`${r.lease_id} ${r.inventory_slug}/${r.member_id} ${r.state} expires=${r.expires_at}`
|
|
20219
|
+
);
|
|
20220
|
+
}
|
|
20221
|
+
} catch (error) {
|
|
20222
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20223
|
+
console.error(`Error: ${message}`);
|
|
20224
|
+
process.exit(1);
|
|
20225
|
+
}
|
|
20226
|
+
});
|
|
20227
|
+
leaseCommand.command("gc").description("Sweep expired leases across all inventories (idempotent, concurrency-safe)").action(async () => {
|
|
20228
|
+
try {
|
|
20229
|
+
initLeasesDb();
|
|
20230
|
+
const n = gcExpiredLeases();
|
|
20231
|
+
console.log(`Expired ${n} lease(s).`);
|
|
20232
|
+
} catch (error) {
|
|
20233
|
+
if (error instanceof LeaseContentionError) {
|
|
20234
|
+
console.error(`Error: contention timeout; retry`);
|
|
20235
|
+
process.exit(1);
|
|
20236
|
+
}
|
|
20237
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20238
|
+
console.error(`Error: ${message}`);
|
|
20239
|
+
process.exit(1);
|
|
20240
|
+
}
|
|
20241
|
+
});
|
|
20242
|
+
|
|
19382
20243
|
// src/commands/request.ts
|
|
19383
20244
|
init_paths();
|
|
19384
20245
|
init_fs();
|
|
@@ -19387,7 +20248,7 @@ init_slug();
|
|
|
19387
20248
|
init_timestamp();
|
|
19388
20249
|
init_assignment_resolver();
|
|
19389
20250
|
init_assignment_todos();
|
|
19390
|
-
import { resolve as
|
|
20251
|
+
import { resolve as resolve54 } from "path";
|
|
19391
20252
|
import { readFile as readFile35 } from "fs/promises";
|
|
19392
20253
|
async function requestCommand(target, text, options = {}) {
|
|
19393
20254
|
if (!text || !text.trim()) {
|
|
@@ -19404,7 +20265,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
19404
20265
|
if (!isValidSlug(target)) {
|
|
19405
20266
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
19406
20267
|
}
|
|
19407
|
-
assignmentDir =
|
|
20268
|
+
assignmentDir = resolve54(baseDir, options.project, "assignments", target);
|
|
19408
20269
|
targetRef = target;
|
|
19409
20270
|
} else {
|
|
19410
20271
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -19414,7 +20275,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
19414
20275
|
assignmentDir = resolved.assignmentDir;
|
|
19415
20276
|
targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
19416
20277
|
}
|
|
19417
|
-
const assignmentMdPath2 =
|
|
20278
|
+
const assignmentMdPath2 = resolve54(assignmentDir, "assignment.md");
|
|
19418
20279
|
if (!await fileExists(assignmentMdPath2)) {
|
|
19419
20280
|
throw new Error(`assignment.md not found at ${assignmentMdPath2}`);
|
|
19420
20281
|
}
|
|
@@ -19431,11 +20292,11 @@ async function requestCommand(target, text, options = {}) {
|
|
|
19431
20292
|
// src/commands/plan.ts
|
|
19432
20293
|
init_fs();
|
|
19433
20294
|
init_paths();
|
|
19434
|
-
import { Command as
|
|
20295
|
+
import { Command as Command7 } from "commander";
|
|
19435
20296
|
import { readFile as readFile36, readdir as readdir18 } from "fs/promises";
|
|
19436
|
-
import { resolve as
|
|
20297
|
+
import { resolve as resolve55 } from "path";
|
|
19437
20298
|
async function readContextAssignmentDir(cwd) {
|
|
19438
|
-
const path =
|
|
20299
|
+
const path = resolve55(cwd, ".syntaur", "context.json");
|
|
19439
20300
|
if (!await fileExists(path)) return null;
|
|
19440
20301
|
try {
|
|
19441
20302
|
const raw = await readFile36(path, "utf-8");
|
|
@@ -19452,9 +20313,9 @@ async function resolveAssignmentDir(opts) {
|
|
|
19452
20313
|
const cwd = opts.cwd ?? process.cwd();
|
|
19453
20314
|
if (opts.assignment) {
|
|
19454
20315
|
if (opts.project) {
|
|
19455
|
-
return
|
|
20316
|
+
return resolve55(defaultProjectDir(), opts.project, "assignments", opts.assignment);
|
|
19456
20317
|
}
|
|
19457
|
-
return
|
|
20318
|
+
return resolve55(assignmentsDir(), opts.assignment);
|
|
19458
20319
|
}
|
|
19459
20320
|
const fromCtx = await readContextAssignmentDir(cwd);
|
|
19460
20321
|
if (fromCtx) return fromCtx;
|
|
@@ -19597,7 +20458,7 @@ async function runPlanVersion(options) {
|
|
|
19597
20458
|
if (!await fileExists(assignmentDir)) {
|
|
19598
20459
|
throw new Error(`Assignment directory does not exist: ${assignmentDir}`);
|
|
19599
20460
|
}
|
|
19600
|
-
const assignmentMdPath2 =
|
|
20461
|
+
const assignmentMdPath2 = resolve55(assignmentDir, "assignment.md");
|
|
19601
20462
|
if (!await fileExists(assignmentMdPath2)) {
|
|
19602
20463
|
throw new Error(`Missing assignment.md at: ${assignmentMdPath2}`);
|
|
19603
20464
|
}
|
|
@@ -19609,14 +20470,14 @@ async function runPlanVersion(options) {
|
|
|
19609
20470
|
}
|
|
19610
20471
|
const current = planFiles[planFiles.length - 1];
|
|
19611
20472
|
const next = nextPlanFileName(current.version);
|
|
19612
|
-
const newPath =
|
|
20473
|
+
const newPath = resolve55(assignmentDir, next.fileName);
|
|
19613
20474
|
if (await fileExists(newPath) && !options.force) {
|
|
19614
20475
|
throw new Error(`${next.fileName} already exists. Use --force to overwrite.`);
|
|
19615
20476
|
}
|
|
19616
20477
|
const assignmentMd = await readFile36(assignmentMdPath2, "utf-8");
|
|
19617
20478
|
const slugMatch = assignmentMd.match(/^slug:\s*(.+?)\s*$/m);
|
|
19618
20479
|
const slug = slugMatch ? slugMatch[1].trim() : assignmentDir.split("/").pop() ?? "";
|
|
19619
|
-
const oldPlanPath =
|
|
20480
|
+
const oldPlanPath = resolve55(assignmentDir, current.fileName);
|
|
19620
20481
|
const oldPlanContent = await readFile36(oldPlanPath, "utf-8");
|
|
19621
20482
|
const oldBody = oldPlanContent.replace(/^---[\s\S]*?\n---\n?/, "");
|
|
19622
20483
|
const carriedTodos = extractUncheckedTodos(oldBody);
|
|
@@ -19639,7 +20500,7 @@ async function runPlanVersion(options) {
|
|
|
19639
20500
|
console.log(`Path: ${newPath}`);
|
|
19640
20501
|
console.log(`Carried forward: ${carriedTodos.length} unchecked task(s).`);
|
|
19641
20502
|
}
|
|
19642
|
-
var planCommand = new
|
|
20503
|
+
var planCommand = new Command7("plan").description("Manage plan files for the active assignment");
|
|
19643
20504
|
planCommand.command("version").description(
|
|
19644
20505
|
"Create the next plan-v<N>.md, supersede the prior plan in assignment.md ## Todos, and carry forward unchecked tasks"
|
|
19645
20506
|
).option("--assignment <slug>", "Assignment slug (UUID for standalone). Defaults to .syntaur/context.json").option("--project <slug>", "Project slug. Required when --assignment is given for a project-nested assignment").option("--force", "Overwrite if the next plan-v<N>.md already exists").action(async (options) => {
|
|
@@ -19653,11 +20514,11 @@ planCommand.command("version").description(
|
|
|
19653
20514
|
|
|
19654
20515
|
// src/commands/session.ts
|
|
19655
20516
|
init_fs();
|
|
19656
|
-
import { Command as
|
|
20517
|
+
import { Command as Command8 } from "commander";
|
|
19657
20518
|
import { readFile as readFile37, readdir as readdir19, stat as stat8 } from "fs/promises";
|
|
19658
|
-
import { resolve as
|
|
20519
|
+
import { resolve as resolve56 } from "path";
|
|
19659
20520
|
async function readContext(cwd) {
|
|
19660
|
-
const path =
|
|
20521
|
+
const path = resolve56(cwd, ".syntaur", "context.json");
|
|
19661
20522
|
if (!await fileExists(path)) return null;
|
|
19662
20523
|
try {
|
|
19663
20524
|
const raw = await readFile37(path, "utf-8");
|
|
@@ -19667,13 +20528,13 @@ async function readContext(cwd) {
|
|
|
19667
20528
|
}
|
|
19668
20529
|
}
|
|
19669
20530
|
async function findLatestSessionSummary(assignmentDir) {
|
|
19670
|
-
const sessionsRoot =
|
|
20531
|
+
const sessionsRoot = resolve56(assignmentDir, "sessions");
|
|
19671
20532
|
if (!await fileExists(sessionsRoot)) return null;
|
|
19672
20533
|
const entries = await readdir19(sessionsRoot, { withFileTypes: true });
|
|
19673
20534
|
let best = null;
|
|
19674
20535
|
for (const entry of entries) {
|
|
19675
20536
|
if (!entry.isDirectory()) continue;
|
|
19676
|
-
const summaryPath =
|
|
20537
|
+
const summaryPath = resolve56(sessionsRoot, entry.name, "summary.md");
|
|
19677
20538
|
if (!await fileExists(summaryPath)) continue;
|
|
19678
20539
|
const st = await stat8(summaryPath);
|
|
19679
20540
|
if (best === null || st.mtime.getTime() > best.mtime.getTime()) {
|
|
@@ -19683,7 +20544,7 @@ async function findLatestSessionSummary(assignmentDir) {
|
|
|
19683
20544
|
return best;
|
|
19684
20545
|
}
|
|
19685
20546
|
async function findOpenHandoff(assignmentDir) {
|
|
19686
|
-
const handoffPath =
|
|
20547
|
+
const handoffPath = resolve56(assignmentDir, "handoff.md");
|
|
19687
20548
|
if (!await fileExists(handoffPath)) return null;
|
|
19688
20549
|
const content = await readFile37(handoffPath, "utf-8");
|
|
19689
20550
|
const body = content.replace(/^---[\s\S]*?\n---\n?/, "").trim();
|
|
@@ -19775,7 +20636,7 @@ async function runSessionResume(options, cwd = process.cwd()) {
|
|
|
19775
20636
|
}
|
|
19776
20637
|
return out;
|
|
19777
20638
|
}
|
|
19778
|
-
var sessionCommand = new
|
|
20639
|
+
var sessionCommand = new Command8("session").description("Manage agent sessions for the active assignment");
|
|
19779
20640
|
sessionCommand.command("resume").description(
|
|
19780
20641
|
"Re-orient a fresh session: print active assignment context, latest saved session summary, and any open handoff. Idempotent \u2014 does not mutate state."
|
|
19781
20642
|
).option("--json", "Emit machine-readable JSON instead of human-readable text").action(async (options) => {
|
|
@@ -19792,11 +20653,11 @@ sessionCommand.command("resume").description(
|
|
|
19792
20653
|
init_git_worktree();
|
|
19793
20654
|
init_fs();
|
|
19794
20655
|
init_paths();
|
|
19795
|
-
import { Command as
|
|
20656
|
+
import { Command as Command9 } from "commander";
|
|
19796
20657
|
import { readFile as readFile38 } from "fs/promises";
|
|
19797
|
-
import { resolve as
|
|
20658
|
+
import { resolve as resolve57 } from "path";
|
|
19798
20659
|
async function readContext2(cwd) {
|
|
19799
|
-
const path =
|
|
20660
|
+
const path = resolve57(cwd, ".syntaur", "context.json");
|
|
19800
20661
|
if (!await fileExists(path)) return null;
|
|
19801
20662
|
try {
|
|
19802
20663
|
return JSON.parse(await readFile38(path, "utf-8"));
|
|
@@ -19807,7 +20668,7 @@ async function readContext2(cwd) {
|
|
|
19807
20668
|
async function resolveAssignmentPath2(opts) {
|
|
19808
20669
|
if (opts.assignment) {
|
|
19809
20670
|
if (opts.project) {
|
|
19810
|
-
return
|
|
20671
|
+
return resolve57(
|
|
19811
20672
|
defaultProjectDir(),
|
|
19812
20673
|
opts.project,
|
|
19813
20674
|
"assignments",
|
|
@@ -19815,10 +20676,10 @@ async function resolveAssignmentPath2(opts) {
|
|
|
19815
20676
|
"assignment.md"
|
|
19816
20677
|
);
|
|
19817
20678
|
}
|
|
19818
|
-
return
|
|
20679
|
+
return resolve57(assignmentsDir(), opts.assignment, "assignment.md");
|
|
19819
20680
|
}
|
|
19820
20681
|
const ctx = await readContext2(opts.cwd);
|
|
19821
|
-
if (ctx?.assignmentDir) return
|
|
20682
|
+
if (ctx?.assignmentDir) return resolve57(ctx.assignmentDir, "assignment.md");
|
|
19822
20683
|
throw new Error(
|
|
19823
20684
|
"No active assignment. Pass --assignment <slug> [--project <slug>] or run from a workspace with .syntaur/context.json."
|
|
19824
20685
|
);
|
|
@@ -19829,7 +20690,7 @@ async function runWorktreeCreate(options, cwd = process.cwd()) {
|
|
|
19829
20690
|
}
|
|
19830
20691
|
const repository = options.repository ?? cwd;
|
|
19831
20692
|
const parentBranch = options.parentBranch ?? "main";
|
|
19832
|
-
const worktreePath = options.worktreePath ??
|
|
20693
|
+
const worktreePath = options.worktreePath ?? resolve57(repository, ".worktrees", options.branch);
|
|
19833
20694
|
const assignmentPath = await resolveAssignmentPath2({
|
|
19834
20695
|
assignment: options.assignment,
|
|
19835
20696
|
project: options.project,
|
|
@@ -19847,7 +20708,7 @@ async function runWorktreeCreate(options, cwd = process.cwd()) {
|
|
|
19847
20708
|
});
|
|
19848
20709
|
return { worktreePath, assignmentPath };
|
|
19849
20710
|
}
|
|
19850
|
-
var worktreeCommand = new
|
|
20711
|
+
var worktreeCommand = new Command9("worktree").description("Manage git worktrees bound to Syntaur assignments");
|
|
19851
20712
|
worktreeCommand.command("create").description(
|
|
19852
20713
|
"Create a worktree at <repository>/.worktrees/<branch> and record it in the assignment workspace block. Atomic \u2014 rolls back the worktree if writing assignment.md fails."
|
|
19853
20714
|
).requiredOption("--branch <name>", "Branch name to create (also used as worktree dir name)").option("--repository <path>", "Repository root (defaults to current working directory)").option("--parent-branch <name>", "Parent branch to fork from", "main").option("--assignment <slug>", "Assignment slug (UUID for standalone). Defaults to .syntaur/context.json").option("--project <slug>", "Project slug. Required when --assignment is given for a project-nested assignment").option("--worktree-path <path>", "Override the computed <repository>/.worktrees/<branch> path").action(async (options) => {
|
|
@@ -19865,15 +20726,15 @@ worktreeCommand.command("create").description(
|
|
|
19865
20726
|
init_paths();
|
|
19866
20727
|
init_fs();
|
|
19867
20728
|
init_slug();
|
|
19868
|
-
import { Command as
|
|
19869
|
-
import { resolve as
|
|
20729
|
+
import { Command as Command10 } from "commander";
|
|
20730
|
+
import { resolve as resolve59 } from "path";
|
|
19870
20731
|
|
|
19871
20732
|
// src/utils/project-indexes.ts
|
|
19872
20733
|
init_parser();
|
|
19873
20734
|
init_fs();
|
|
19874
20735
|
import { readdir as readdir20, readFile as readFile39 } from "fs/promises";
|
|
19875
|
-
import { resolve as
|
|
19876
|
-
function
|
|
20736
|
+
import { resolve as resolve58 } from "path";
|
|
20737
|
+
function nowIso2() {
|
|
19877
20738
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
19878
20739
|
}
|
|
19879
20740
|
function readProjectSlug(projectDir) {
|
|
@@ -19891,14 +20752,14 @@ function joinList(items) {
|
|
|
19891
20752
|
return items.length === 0 ? "\u2014" : items.map(escapeCell).join(", ");
|
|
19892
20753
|
}
|
|
19893
20754
|
async function rebuildResourcesIndex(projectDir) {
|
|
19894
|
-
const dir =
|
|
20755
|
+
const dir = resolve58(projectDir, "resources");
|
|
19895
20756
|
await ensureDir(dir);
|
|
19896
20757
|
const files = await listSlugFiles(dir);
|
|
19897
20758
|
const slug = readProjectSlug(projectDir);
|
|
19898
20759
|
const lines = [];
|
|
19899
20760
|
lines.push("---");
|
|
19900
20761
|
lines.push(`project: ${slug}`);
|
|
19901
|
-
lines.push(`generated: "${
|
|
20762
|
+
lines.push(`generated: "${nowIso2()}"`);
|
|
19902
20763
|
lines.push(`total: ${files.length}`);
|
|
19903
20764
|
lines.push("---");
|
|
19904
20765
|
lines.push("");
|
|
@@ -19907,7 +20768,7 @@ async function rebuildResourcesIndex(projectDir) {
|
|
|
19907
20768
|
lines.push("| Name | Category | Source | Related Assignments | Updated |");
|
|
19908
20769
|
lines.push("|------|----------|--------|---------------------|---------|");
|
|
19909
20770
|
for (const fileName of files) {
|
|
19910
|
-
const content = await readFile39(
|
|
20771
|
+
const content = await readFile39(resolve58(dir, fileName), "utf-8");
|
|
19911
20772
|
const parsed = parseResource(content);
|
|
19912
20773
|
const slugBase = fileName.replace(/\.md$/, "");
|
|
19913
20774
|
const name = parsed.name || slugBase;
|
|
@@ -19917,19 +20778,19 @@ async function rebuildResourcesIndex(projectDir) {
|
|
|
19917
20778
|
);
|
|
19918
20779
|
}
|
|
19919
20780
|
lines.push("");
|
|
19920
|
-
const indexPath =
|
|
20781
|
+
const indexPath = resolve58(dir, "_index.md");
|
|
19921
20782
|
await writeFileForce(indexPath, lines.join("\n"));
|
|
19922
20783
|
return { total: files.length, path: indexPath };
|
|
19923
20784
|
}
|
|
19924
20785
|
async function rebuildMemoriesIndex(projectDir) {
|
|
19925
|
-
const dir =
|
|
20786
|
+
const dir = resolve58(projectDir, "memories");
|
|
19926
20787
|
await ensureDir(dir);
|
|
19927
20788
|
const files = await listSlugFiles(dir);
|
|
19928
20789
|
const slug = readProjectSlug(projectDir);
|
|
19929
20790
|
const lines = [];
|
|
19930
20791
|
lines.push("---");
|
|
19931
20792
|
lines.push(`project: ${slug}`);
|
|
19932
|
-
lines.push(`generated: "${
|
|
20793
|
+
lines.push(`generated: "${nowIso2()}"`);
|
|
19933
20794
|
lines.push(`total: ${files.length}`);
|
|
19934
20795
|
lines.push("---");
|
|
19935
20796
|
lines.push("");
|
|
@@ -19938,7 +20799,7 @@ async function rebuildMemoriesIndex(projectDir) {
|
|
|
19938
20799
|
lines.push("| Name | Source | Scope | Source Assignment | Updated |");
|
|
19939
20800
|
lines.push("|------|--------|-------|-------------------|---------|");
|
|
19940
20801
|
for (const fileName of files) {
|
|
19941
|
-
const content = await readFile39(
|
|
20802
|
+
const content = await readFile39(resolve58(dir, fileName), "utf-8");
|
|
19942
20803
|
const parsed = parseMemory(content);
|
|
19943
20804
|
const slugBase = fileName.replace(/\.md$/, "");
|
|
19944
20805
|
const name = parsed.name || slugBase;
|
|
@@ -19948,13 +20809,13 @@ async function rebuildMemoriesIndex(projectDir) {
|
|
|
19948
20809
|
);
|
|
19949
20810
|
}
|
|
19950
20811
|
lines.push("");
|
|
19951
|
-
const indexPath =
|
|
20812
|
+
const indexPath = resolve58(dir, "_index.md");
|
|
19952
20813
|
await writeFileForce(indexPath, lines.join("\n"));
|
|
19953
20814
|
return { total: files.length, path: indexPath };
|
|
19954
20815
|
}
|
|
19955
20816
|
|
|
19956
20817
|
// src/commands/resource.ts
|
|
19957
|
-
function
|
|
20818
|
+
function nowIso3() {
|
|
19958
20819
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
19959
20820
|
}
|
|
19960
20821
|
function parseList(value) {
|
|
@@ -19965,7 +20826,7 @@ function yamlQuote(value) {
|
|
|
19965
20826
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
19966
20827
|
}
|
|
19967
20828
|
function renderResourceFile(opts) {
|
|
19968
|
-
const created =
|
|
20829
|
+
const created = nowIso3();
|
|
19969
20830
|
const related = opts.relatedAssignments.length === 0 ? "[]" : `
|
|
19970
20831
|
${opts.relatedAssignments.map((a) => ` - ${a}`).join("\n")}`;
|
|
19971
20832
|
return `---
|
|
@@ -19986,8 +20847,8 @@ async function runResourceAdd(options) {
|
|
|
19986
20847
|
if (!isValidSlug(options.project)) {
|
|
19987
20848
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
19988
20849
|
}
|
|
19989
|
-
const projectDir =
|
|
19990
|
-
if (!await fileExists(
|
|
20850
|
+
const projectDir = resolve59(defaultProjectDir(), options.project);
|
|
20851
|
+
if (!await fileExists(resolve59(projectDir, "project.md"))) {
|
|
19991
20852
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
19992
20853
|
}
|
|
19993
20854
|
if (!options.name) throw new Error("--name is required.");
|
|
@@ -19996,7 +20857,7 @@ async function runResourceAdd(options) {
|
|
|
19996
20857
|
if (!isValidSlug(slug)) {
|
|
19997
20858
|
throw new Error(`Invalid resource slug: "${slug}".`);
|
|
19998
20859
|
}
|
|
19999
|
-
const filePath =
|
|
20860
|
+
const filePath = resolve59(projectDir, "resources", `${slug}.md`);
|
|
20000
20861
|
if (await fileExists(filePath) && !options.force) {
|
|
20001
20862
|
throw new Error(
|
|
20002
20863
|
`Resource "${slug}" already exists at ${filePath}. Use --force to overwrite.`
|
|
@@ -20012,7 +20873,7 @@ async function runResourceAdd(options) {
|
|
|
20012
20873
|
const { path: indexPath, total } = await rebuildResourcesIndex(projectDir);
|
|
20013
20874
|
return { filePath, indexPath, total };
|
|
20014
20875
|
}
|
|
20015
|
-
var resourceCommand = new
|
|
20876
|
+
var resourceCommand = new Command10("resource").description("Manage project-level resource entries");
|
|
20016
20877
|
resourceCommand.command("add").description(
|
|
20017
20878
|
"Add a project resource at <projectDir>/resources/<slug>.md and regenerate _index.md"
|
|
20018
20879
|
).requiredOption("--project <slug>", "Project slug").requiredOption("--name <name>", "Human-readable resource name").requiredOption("--source <url-or-path>", "Resource source (URL or path)").option("--category <name>", "Optional category (e.g. dashboard, doc, ticket)").option("--slug <slug>", "Override the slug (default: slugify(--name))").option("--related-assignments <slugs>", "Comma-separated related assignment slugs").option("--force", "Overwrite if the resource file already exists").action(async (options) => {
|
|
@@ -20030,9 +20891,9 @@ resourceCommand.command("add").description(
|
|
|
20030
20891
|
init_paths();
|
|
20031
20892
|
init_fs();
|
|
20032
20893
|
init_slug();
|
|
20033
|
-
import { Command as
|
|
20034
|
-
import { resolve as
|
|
20035
|
-
function
|
|
20894
|
+
import { Command as Command11 } from "commander";
|
|
20895
|
+
import { resolve as resolve60 } from "path";
|
|
20896
|
+
function nowIso4() {
|
|
20036
20897
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
20037
20898
|
}
|
|
20038
20899
|
function parseList2(value) {
|
|
@@ -20043,7 +20904,7 @@ function yamlQuote2(value) {
|
|
|
20043
20904
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
20044
20905
|
}
|
|
20045
20906
|
function renderMemoryFile(opts) {
|
|
20046
|
-
const created =
|
|
20907
|
+
const created = nowIso4();
|
|
20047
20908
|
const related = opts.relatedAssignments.length === 0 ? "[]" : `
|
|
20048
20909
|
${opts.relatedAssignments.map((a) => ` - ${a}`).join("\n")}`;
|
|
20049
20910
|
const sourceAssignment = opts.sourceAssignment === null ? "null" : yamlQuote2(opts.sourceAssignment);
|
|
@@ -20066,8 +20927,8 @@ async function runMemoryAdd(options) {
|
|
|
20066
20927
|
if (!isValidSlug(options.project)) {
|
|
20067
20928
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
20068
20929
|
}
|
|
20069
|
-
const projectDir =
|
|
20070
|
-
if (!await fileExists(
|
|
20930
|
+
const projectDir = resolve60(defaultProjectDir(), options.project);
|
|
20931
|
+
if (!await fileExists(resolve60(projectDir, "project.md"))) {
|
|
20071
20932
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
20072
20933
|
}
|
|
20073
20934
|
if (!options.name) throw new Error("--name is required.");
|
|
@@ -20076,7 +20937,7 @@ async function runMemoryAdd(options) {
|
|
|
20076
20937
|
if (!isValidSlug(slug)) {
|
|
20077
20938
|
throw new Error(`Invalid memory slug: "${slug}".`);
|
|
20078
20939
|
}
|
|
20079
|
-
const filePath =
|
|
20940
|
+
const filePath = resolve60(projectDir, "memories", `${slug}.md`);
|
|
20080
20941
|
if (await fileExists(filePath) && !options.force) {
|
|
20081
20942
|
throw new Error(
|
|
20082
20943
|
`Memory "${slug}" already exists at ${filePath}. Use --force to overwrite.`
|
|
@@ -20093,7 +20954,7 @@ async function runMemoryAdd(options) {
|
|
|
20093
20954
|
const { path: indexPath, total } = await rebuildMemoriesIndex(projectDir);
|
|
20094
20955
|
return { filePath, indexPath, total };
|
|
20095
20956
|
}
|
|
20096
|
-
var memoryCommand = new
|
|
20957
|
+
var memoryCommand = new Command11("memory").description("Manage project-level memory entries");
|
|
20097
20958
|
memoryCommand.command("add").description(
|
|
20098
20959
|
"Add a project memory at <projectDir>/memories/<slug>.md and regenerate _index.md"
|
|
20099
20960
|
).requiredOption("--project <slug>", "Project slug").requiredOption("--name <name>", "Human-readable memory name").requiredOption("--source <text>", "Where this memory came from (conversation, decision, doc URL, etc.)").option("--scope <scope>", "Memory scope (default: project)", "project").option("--source-assignment <slug>", "Assignment slug this memory was captured during").option("--slug <slug>", "Override the slug (default: slugify(--name))").option("--related-assignments <slugs>", "Comma-separated related assignment slugs").option("--force", "Overwrite if the memory file already exists").action(async (options) => {
|
|
@@ -20112,9 +20973,9 @@ init_api();
|
|
|
20112
20973
|
init_paths();
|
|
20113
20974
|
init_fs();
|
|
20114
20975
|
init_frontmatter();
|
|
20115
|
-
import { Command as
|
|
20976
|
+
import { Command as Command12 } from "commander";
|
|
20116
20977
|
import { readFile as readFile40 } from "fs/promises";
|
|
20117
|
-
import { resolve as
|
|
20978
|
+
import { resolve as resolve61 } from "path";
|
|
20118
20979
|
var AGE_PATTERN = /^(\d+)([dhwm])$/i;
|
|
20119
20980
|
function parseAgeToCutoff(age) {
|
|
20120
20981
|
const match = age.match(AGE_PATTERN);
|
|
@@ -20133,7 +20994,7 @@ function parseAgeToCutoff(age) {
|
|
|
20133
20994
|
}
|
|
20134
20995
|
function assignmentMdPath(item) {
|
|
20135
20996
|
if (item.projectSlug) {
|
|
20136
|
-
return
|
|
20997
|
+
return resolve61(
|
|
20137
20998
|
defaultProjectDir(),
|
|
20138
20999
|
item.projectSlug,
|
|
20139
21000
|
"assignments",
|
|
@@ -20141,7 +21002,7 @@ function assignmentMdPath(item) {
|
|
|
20141
21002
|
"assignment.md"
|
|
20142
21003
|
);
|
|
20143
21004
|
}
|
|
20144
|
-
return
|
|
21005
|
+
return resolve61(assignmentsDir(), item.id, "assignment.md");
|
|
20145
21006
|
}
|
|
20146
21007
|
async function loadTags(item) {
|
|
20147
21008
|
const path = assignmentMdPath(item);
|
|
@@ -20207,7 +21068,7 @@ function renderTable(items) {
|
|
|
20207
21068
|
);
|
|
20208
21069
|
return all.map((row) => row.map((cell, c2) => pad(cell ?? "", widths[c2])).join(" ")).join("\n");
|
|
20209
21070
|
}
|
|
20210
|
-
var lsCommand = new
|
|
21071
|
+
var lsCommand = new Command12("ls").description(
|
|
20211
21072
|
"List assignments across all projects with optional filters by status, project, tag, or age."
|
|
20212
21073
|
).option("--status <list>", "Comma-separated status filter (e.g. pending,in_progress)").option("--project <slug>", "Filter to one project").option("--tag <list>", "Comma-separated tag filter (assignment must have ALL tags)").option("--age <duration>", "Only include assignments updated within duration (e.g. 7d, 24h, 2w, 1m)").option("--json", "Emit JSON instead of a table").action(async (options) => {
|
|
20213
21074
|
try {
|
|
@@ -20266,18 +21127,18 @@ init_paths();
|
|
|
20266
21127
|
init_fs();
|
|
20267
21128
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
20268
21129
|
import { readFile as readFile42 } from "fs/promises";
|
|
20269
|
-
import { dirname as
|
|
21130
|
+
import { dirname as dirname16, join as join8, resolve as resolve62 } from "path";
|
|
20270
21131
|
import { spawn as spawn4 } from "child_process";
|
|
20271
21132
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
20272
21133
|
|
|
20273
21134
|
// src/utils/version.ts
|
|
20274
21135
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
20275
21136
|
import { readFile as readFile41 } from "fs/promises";
|
|
20276
|
-
import { dirname as
|
|
21137
|
+
import { dirname as dirname15, join as join7 } from "path";
|
|
20277
21138
|
async function readPackageVersion(scriptUrl) {
|
|
20278
21139
|
try {
|
|
20279
21140
|
const scriptPath = fileURLToPath7(scriptUrl);
|
|
20280
|
-
const pkgRoot =
|
|
21141
|
+
const pkgRoot = dirname15(dirname15(scriptPath));
|
|
20281
21142
|
const raw = await readFile41(join7(pkgRoot, "package.json"), "utf-8");
|
|
20282
21143
|
const parsed = JSON.parse(raw);
|
|
20283
21144
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
@@ -20287,7 +21148,7 @@ async function readPackageVersion(scriptUrl) {
|
|
|
20287
21148
|
}
|
|
20288
21149
|
|
|
20289
21150
|
// src/utils/npx-prompt.ts
|
|
20290
|
-
var STATE_FILE =
|
|
21151
|
+
var STATE_FILE = resolve62(syntaurRoot(), "npx-install.json");
|
|
20291
21152
|
var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
|
|
20292
21153
|
var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
|
|
20293
21154
|
function isRunningViaNpx(scriptUrl) {
|
|
@@ -20319,7 +21180,7 @@ async function writeState(state) {
|
|
|
20319
21180
|
`);
|
|
20320
21181
|
}
|
|
20321
21182
|
async function resolveNpmBin() {
|
|
20322
|
-
const nodeDir =
|
|
21183
|
+
const nodeDir = dirname16(process.execPath);
|
|
20323
21184
|
const isWin = process.platform === "win32";
|
|
20324
21185
|
const npmName = isWin ? "npm.cmd" : "npm";
|
|
20325
21186
|
const nearNode = join8(nodeDir, npmName);
|
|
@@ -20486,7 +21347,7 @@ async function maybePromptInstall(scriptUrl) {
|
|
|
20486
21347
|
|
|
20487
21348
|
// src/index.ts
|
|
20488
21349
|
await maybePromptInstall(import.meta.url);
|
|
20489
|
-
var program = new
|
|
21350
|
+
var program = new Command13();
|
|
20490
21351
|
var version = await readPackageVersion(import.meta.url) ?? "0.0.0";
|
|
20491
21352
|
program.name("syntaur").description("CLI scaffolding tool for the Syntaur protocol").version(version);
|
|
20492
21353
|
program.command("init").description("Initialize ~/.syntaur/ directory structure and config").option("--force", "Overwrite existing config file").action(async (options) => {
|
|
@@ -20865,6 +21726,7 @@ program.addCommand(worktreeCommand);
|
|
|
20865
21726
|
program.addCommand(resourceCommand);
|
|
20866
21727
|
program.addCommand(memoryCommand);
|
|
20867
21728
|
program.addCommand(lsCommand);
|
|
21729
|
+
program.addCommand(leaseCommand);
|
|
20868
21730
|
if (process.argv.length <= 2) {
|
|
20869
21731
|
process.argv.push(await getDefaultCommandName());
|
|
20870
21732
|
}
|