syntaur 0.65.0 → 0.66.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 +1 -1
- package/dist/dashboard/server.js +90 -20
- package/dist/dashboard/server.js.map +1 -1
- package/dist/db/leases-db.d.ts +4 -3
- package/dist/db/leases-db.js +6 -4
- package/dist/db/leases-db.js.map +1 -1
- package/dist/index.js +148 -27
- package/dist/index.js.map +1 -1
- package/dist/launch/index.js +20 -7
- package/dist/launch/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
- package/platforms/codex/.codex-plugin/plugin.json +1 -1
- package/platforms/hermes/plugins/syntaur/__pycache__/__init__.cpython-312.pyc +0 -0
- package/platforms/hermes/plugins/syntaur/__pycache__/boundary.cpython-312.pyc +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "syntaur",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.0",
|
|
4
4
|
"description": "Syntaur protocol skills for AI coding agents — assignment, project, plan, and session lifecycle. Cross-agent (Claude Code, Codex, Cursor, OpenCode, Gemini CLI, and more via skills.sh).",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Brennen",
|
package/dist/dashboard/server.js
CHANGED
|
@@ -470,7 +470,7 @@ function formatYamlValue(value) {
|
|
|
470
470
|
if (/^(null|~|true|false|-?\d+(\.\d+)?)$/i.test(value)) {
|
|
471
471
|
return `"${value}"`;
|
|
472
472
|
}
|
|
473
|
-
if (/[:#{}[\],&*?|>!%@\`]/.test(value) || /^\s|\s$/.test(value) || value === "") {
|
|
473
|
+
if (/[:#{}[\],&*?|>!%@\`]/.test(value) || /^\s|\s$/.test(value) || /^["']|["']$/.test(value) || value === "") {
|
|
474
474
|
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
475
475
|
return `"${escaped}"`;
|
|
476
476
|
}
|
|
@@ -497,7 +497,7 @@ ${key}: ${formatted}${result.slice(closeIdx)}`;
|
|
|
497
497
|
function findWorkspaceBlock(fmBlock) {
|
|
498
498
|
const headerMatch = fmBlock.match(/^workspace:\s*$/m);
|
|
499
499
|
if (!headerMatch) return null;
|
|
500
|
-
const headerStart = fmBlock.indexOf(headerMatch[0]);
|
|
500
|
+
const headerStart = headerMatch.index ?? fmBlock.indexOf(headerMatch[0]);
|
|
501
501
|
const bodyStart = headerStart + headerMatch[0].length + 1;
|
|
502
502
|
const after = fmBlock.slice(bodyStart);
|
|
503
503
|
const lines = after.split("\n");
|
|
@@ -555,7 +555,7 @@ function renameStatusInHistory(content, oldId, newId) {
|
|
|
555
555
|
if (!fmMatch) return content;
|
|
556
556
|
const esc = oldId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
557
557
|
const re = new RegExp(`^(\\s+(?:from|to|phaseFrom|phaseTo):[ \\t]*)("?)${esc}\\2[ \\t]*$`, "gm");
|
|
558
|
-
const newFm = fmMatch[2].replace(re, (_m, prefix
|
|
558
|
+
const newFm = fmMatch[2].replace(re, (_m, prefix) => `${prefix}${formatYamlValue(newId)}`);
|
|
559
559
|
return `${fmMatch[1]}${newFm}${fmMatch[3]}${content.slice(fmMatch[0].length)}`;
|
|
560
560
|
}
|
|
561
561
|
function findStatusHistoryBlock(fmBlock) {
|
|
@@ -1300,7 +1300,9 @@ function parseDecisionRecord(fileContent) {
|
|
|
1300
1300
|
function parseComments(fileContent) {
|
|
1301
1301
|
const [fm, body] = extractFrontmatter2(fileContent);
|
|
1302
1302
|
const entries = [];
|
|
1303
|
-
const sections = body.split(
|
|
1303
|
+
const sections = body.split(
|
|
1304
|
+
/^## (?=[^\n]*\n\s*\*\*Recorded:\*\*[^\n]*\n\*\*Author:\*\*[^\n]*\n\*\*Type:\*\*\s*(?:question|note|feedback)\b)/m
|
|
1305
|
+
).slice(1);
|
|
1304
1306
|
for (const section of sections) {
|
|
1305
1307
|
const newlineIdx = section.indexOf("\n");
|
|
1306
1308
|
if (newlineIdx === -1) continue;
|
|
@@ -1403,12 +1405,14 @@ var parser_exports = {};
|
|
|
1403
1405
|
__export(parser_exports, {
|
|
1404
1406
|
appendLogEntry: () => appendLogEntry,
|
|
1405
1407
|
archivePath: () => archivePath,
|
|
1408
|
+
assertValidTags: () => assertValidTags,
|
|
1406
1409
|
checklistPath: () => checklistPath,
|
|
1407
1410
|
computeCounts: () => computeCounts,
|
|
1408
1411
|
decodeMetaValue: () => decodeMetaValue,
|
|
1409
1412
|
encodeMetaValue: () => encodeMetaValue,
|
|
1410
1413
|
generateShortId: () => generateShortId,
|
|
1411
1414
|
generateUniqueId: () => generateUniqueId,
|
|
1415
|
+
isValidTag: () => isValidTag,
|
|
1412
1416
|
logPath: () => logPath,
|
|
1413
1417
|
parseChecklist: () => parseChecklist,
|
|
1414
1418
|
parseChecklistItem: () => parseChecklistItem,
|
|
@@ -1438,6 +1442,18 @@ function generateUniqueId(existingIds) {
|
|
|
1438
1442
|
}
|
|
1439
1443
|
return id;
|
|
1440
1444
|
}
|
|
1445
|
+
function isValidTag(tag) {
|
|
1446
|
+
return typeof tag === "string" && VALID_TAG_REGEX.test(tag);
|
|
1447
|
+
}
|
|
1448
|
+
function assertValidTags(tags) {
|
|
1449
|
+
for (const t of tags) {
|
|
1450
|
+
if (!isValidTag(t)) {
|
|
1451
|
+
throw new Error(
|
|
1452
|
+
`Invalid tag ${JSON.stringify(t)}: tags may contain only letters, digits, '-' and '_' (no spaces, newlines, or '#').`
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1441
1457
|
function encodeMetaValue(value) {
|
|
1442
1458
|
let out = "";
|
|
1443
1459
|
for (const ch of value) {
|
|
@@ -1629,6 +1645,7 @@ function parseChecklistItem(line) {
|
|
|
1629
1645
|
}
|
|
1630
1646
|
function serializeChecklistItem(item) {
|
|
1631
1647
|
const marker = statusToMarker(item);
|
|
1648
|
+
assertValidTags(item.tags);
|
|
1632
1649
|
const tagStr = item.tags.map((t) => `#${t}`).join(" ");
|
|
1633
1650
|
const parts = [`- [${marker}] ${escapeDescription(item.description)}`];
|
|
1634
1651
|
if (tagStr) parts.push(tagStr);
|
|
@@ -1818,7 +1835,7 @@ function computeCounts(items) {
|
|
|
1818
1835
|
}
|
|
1819
1836
|
return counts;
|
|
1820
1837
|
}
|
|
1821
|
-
var ITEM_REGEX, ID_REGEX, TAG_REGEX, META_TOKEN_REGEX, META_ENCODE_CHARS;
|
|
1838
|
+
var ITEM_REGEX, ID_REGEX, TAG_REGEX, META_TOKEN_REGEX, META_ENCODE_CHARS, VALID_TAG_REGEX;
|
|
1822
1839
|
var init_parser2 = __esm({
|
|
1823
1840
|
"src/todos/parser.ts"() {
|
|
1824
1841
|
"use strict";
|
|
@@ -1829,6 +1846,7 @@ var init_parser2 = __esm({
|
|
|
1829
1846
|
TAG_REGEX = /#([a-zA-Z0-9_-]+)/g;
|
|
1830
1847
|
META_TOKEN_REGEX = /\[t:[a-f0-9]{4}\]\s+<([^>]*)>\s*$/;
|
|
1831
1848
|
META_ENCODE_CHARS = ["%", "<", ">", "[", "]", "=", ";", "\n", "\r"];
|
|
1849
|
+
VALID_TAG_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
1832
1850
|
}
|
|
1833
1851
|
});
|
|
1834
1852
|
|
|
@@ -3951,6 +3969,16 @@ function parseStatusConfig(content) {
|
|
|
3951
3969
|
}
|
|
3952
3970
|
return t;
|
|
3953
3971
|
};
|
|
3972
|
+
const unquoteAql = (v) => {
|
|
3973
|
+
const t = v.trim();
|
|
3974
|
+
if (t.startsWith('"') && t.endsWith('"') && t.length >= 2) {
|
|
3975
|
+
return t.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
3976
|
+
}
|
|
3977
|
+
if (t.startsWith("'") && t.endsWith("'") && t.length >= 2) {
|
|
3978
|
+
return t.slice(1, -1);
|
|
3979
|
+
}
|
|
3980
|
+
return t;
|
|
3981
|
+
};
|
|
3954
3982
|
let currentSection = null;
|
|
3955
3983
|
const lines = remaining.split("\n");
|
|
3956
3984
|
function parseListEntry(lineIdx, baseIndent) {
|
|
@@ -4030,8 +4058,8 @@ function parseStatusConfig(content) {
|
|
|
4030
4058
|
if (entry["phase"] && entry["when"] !== void 0) {
|
|
4031
4059
|
phaseLadder.push({
|
|
4032
4060
|
phase: unquote(entry["phase"]),
|
|
4033
|
-
when:
|
|
4034
|
-
next: entry["next"] !== void 0 ?
|
|
4061
|
+
when: unquoteAql(entry["when"]),
|
|
4062
|
+
next: entry["next"] !== void 0 ? unquoteAql(entry["next"]) : void 0
|
|
4035
4063
|
});
|
|
4036
4064
|
}
|
|
4037
4065
|
lineIdx += consumed - 1;
|
|
@@ -4042,7 +4070,7 @@ function parseStatusConfig(content) {
|
|
|
4042
4070
|
if (entry["else"] !== void 0) {
|
|
4043
4071
|
disposition.push({ when: null, is: unquote(entry["else"]) });
|
|
4044
4072
|
} else if (entry["when"] !== void 0 && entry["is"]) {
|
|
4045
|
-
disposition.push({ when:
|
|
4073
|
+
disposition.push({ when: unquoteAql(entry["when"]), is: unquote(entry["is"]) });
|
|
4046
4074
|
}
|
|
4047
4075
|
lineIdx += consumed - 1;
|
|
4048
4076
|
continue;
|
|
@@ -4106,6 +4134,7 @@ function buildDefaultStatusConfig() {
|
|
|
4106
4134
|
}
|
|
4107
4135
|
function serializeStatusConfig(statuses) {
|
|
4108
4136
|
const lines = [];
|
|
4137
|
+
const escapeAql = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
4109
4138
|
lines.push("statuses:");
|
|
4110
4139
|
lines.push(" definitions:");
|
|
4111
4140
|
for (const s of statuses.statuses) {
|
|
@@ -4146,15 +4175,15 @@ function serializeStatusConfig(statuses) {
|
|
|
4146
4175
|
lines.push(" phaseLadder:");
|
|
4147
4176
|
for (const rung of d.phaseLadder) {
|
|
4148
4177
|
lines.push(` - phase: ${rung.phase}`);
|
|
4149
|
-
lines.push(` when: "${rung.when
|
|
4150
|
-
if (rung.next) lines.push(` next: "${rung.next
|
|
4178
|
+
lines.push(` when: "${escapeAql(rung.when)}"`);
|
|
4179
|
+
if (rung.next !== void 0) lines.push(` next: "${escapeAql(rung.next)}"`);
|
|
4151
4180
|
}
|
|
4152
4181
|
lines.push(" disposition:");
|
|
4153
4182
|
for (const rule of d.disposition) {
|
|
4154
4183
|
if (rule.when === null) {
|
|
4155
4184
|
lines.push(` - else: ${rule.is}`);
|
|
4156
4185
|
} else {
|
|
4157
|
-
lines.push(` - when: "${rule.when
|
|
4186
|
+
lines.push(` - when: "${escapeAql(rule.when)}"`);
|
|
4158
4187
|
lines.push(` is: ${rule.is}`);
|
|
4159
4188
|
}
|
|
4160
4189
|
}
|
|
@@ -15079,6 +15108,14 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
15079
15108
|
res.status(400).json({ error: "body is required" });
|
|
15080
15109
|
return;
|
|
15081
15110
|
}
|
|
15111
|
+
if (typeof author === "string" && /[\r\n]/.test(author)) {
|
|
15112
|
+
res.status(400).json({ error: "author must not contain newlines" });
|
|
15113
|
+
return;
|
|
15114
|
+
}
|
|
15115
|
+
if (typeof replyTo === "string" && /[\r\n]/.test(replyTo)) {
|
|
15116
|
+
res.status(400).json({ error: "replyTo must not contain newlines" });
|
|
15117
|
+
return;
|
|
15118
|
+
}
|
|
15082
15119
|
const commentType = type && ["question", "note", "feedback"].includes(type) ? type : "note";
|
|
15083
15120
|
const timestamp = nowTimestamp();
|
|
15084
15121
|
const entryAuthor = typeof author === "string" && author.trim() ? author.trim() : "human";
|
|
@@ -16688,6 +16725,14 @@ async function appendCommentTo(assignmentDir, assignmentRef, req2, res, reloadDe
|
|
|
16688
16725
|
}
|
|
16689
16726
|
const commentType = type && ["question", "note", "feedback"].includes(type) ? type : "note";
|
|
16690
16727
|
const timestamp = nowTimestamp();
|
|
16728
|
+
if (typeof author === "string" && /[\r\n]/.test(author)) {
|
|
16729
|
+
res.status(400).json({ error: "author must not contain newlines" });
|
|
16730
|
+
return;
|
|
16731
|
+
}
|
|
16732
|
+
if (typeof replyTo === "string" && /[\r\n]/.test(replyTo)) {
|
|
16733
|
+
res.status(400).json({ error: "replyTo must not contain newlines" });
|
|
16734
|
+
return;
|
|
16735
|
+
}
|
|
16691
16736
|
const entryAuthor = typeof author === "string" && author.trim() ? author.trim() : "human";
|
|
16692
16737
|
let currentContent;
|
|
16693
16738
|
let currentCount = 0;
|
|
@@ -19676,19 +19721,18 @@ function evaluateKind(trigger, job, ctx) {
|
|
|
19676
19721
|
}
|
|
19677
19722
|
}
|
|
19678
19723
|
function evaluateCron(trigger, now, createdAtMs) {
|
|
19679
|
-
let cron;
|
|
19680
19724
|
try {
|
|
19681
|
-
cron = new Cron(trigger.expr, trigger.tz ? { timezone: trigger.tz } : {});
|
|
19725
|
+
const cron = new Cron(trigger.expr, trigger.tz ? { timezone: trigger.tz } : {});
|
|
19726
|
+
const prevs = cron.previousRuns(1, new Date(now.getTime() + 1e3));
|
|
19727
|
+
let prev = prevs.length > 0 ? prevs[0] : null;
|
|
19728
|
+
if (prev && prev.getTime() > now.getTime()) prev = null;
|
|
19729
|
+
if (prev && !Number.isNaN(createdAtMs) && prev.getTime() < createdAtMs) prev = null;
|
|
19730
|
+
const next = cron.nextRun(now);
|
|
19731
|
+
if (!prev) return { due: false, nextFireIso: next ? iso(next) : null };
|
|
19732
|
+
return { due: true, dedupeKey: `cron:${iso(prev)}`, nextFireIso: next ? iso(next) : null };
|
|
19682
19733
|
} catch {
|
|
19683
19734
|
return notDue;
|
|
19684
19735
|
}
|
|
19685
|
-
const prevs = cron.previousRuns(1, new Date(now.getTime() + 1e3));
|
|
19686
|
-
let prev = prevs.length > 0 ? prevs[0] : null;
|
|
19687
|
-
if (prev && prev.getTime() > now.getTime()) prev = null;
|
|
19688
|
-
if (prev && !Number.isNaN(createdAtMs) && prev.getTime() < createdAtMs) prev = null;
|
|
19689
|
-
const next = cron.nextRun(now);
|
|
19690
|
-
if (!prev) return { due: false, nextFireIso: next ? iso(next) : null };
|
|
19691
|
-
return { due: true, dedupeKey: `cron:${iso(prev)}`, nextFireIso: next ? iso(next) : null };
|
|
19692
19736
|
}
|
|
19693
19737
|
function evaluateAfterReset(trigger, now) {
|
|
19694
19738
|
const v = verifyReset(trigger.provider, trigger.anchor, now);
|
|
@@ -22319,6 +22363,10 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
22319
22363
|
res.status(400).json({ error: "description is required" });
|
|
22320
22364
|
return;
|
|
22321
22365
|
}
|
|
22366
|
+
if (tags !== void 0 && (!Array.isArray(tags) || !tags.every(isValidTag))) {
|
|
22367
|
+
res.status(400).json({ error: "tags must be an array of [a-zA-Z0-9_-] strings (no spaces, newlines, or '#')" });
|
|
22368
|
+
return;
|
|
22369
|
+
}
|
|
22322
22370
|
const item = await wsLock(workspace, async () => {
|
|
22323
22371
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
22324
22372
|
const existingIds = new Set(checklist.items.map((i) => i.id));
|
|
@@ -22425,6 +22473,7 @@ workspace: ${workspace}
|
|
|
22425
22473
|
}
|
|
22426
22474
|
const completedItems = checklist.items.filter((i) => completedIds.has(i.id));
|
|
22427
22475
|
for (const item of completedItems) {
|
|
22476
|
+
assertValidTags(item.tags);
|
|
22428
22477
|
archContent += `- [x] ${item.description} ${item.tags.map((t) => `#${t}`).join(" ")} [t:${item.id}]
|
|
22429
22478
|
`;
|
|
22430
22479
|
}
|
|
@@ -22490,6 +22539,14 @@ workspace: ${workspace}
|
|
|
22490
22539
|
router.patch("/:workspace/:id", async (req2, res) => {
|
|
22491
22540
|
try {
|
|
22492
22541
|
const workspace = getWorkspaceParam(req2.params.workspace);
|
|
22542
|
+
if (req2.body.tags !== void 0 && (!Array.isArray(req2.body.tags) || !req2.body.tags.every(isValidTag))) {
|
|
22543
|
+
res.status(400).json({ error: "tags must be an array of [a-zA-Z0-9_-] strings (no spaces, newlines, or '#')" });
|
|
22544
|
+
return;
|
|
22545
|
+
}
|
|
22546
|
+
if (req2.body.description !== void 0 && typeof req2.body.description !== "string") {
|
|
22547
|
+
res.status(400).json({ error: "description must be a string" });
|
|
22548
|
+
return;
|
|
22549
|
+
}
|
|
22493
22550
|
const result = await wsLock(workspace, async () => {
|
|
22494
22551
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
22495
22552
|
const item = checklist.items.find((i) => i.id === req2.params.id);
|
|
@@ -23027,6 +23084,10 @@ function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
|
|
|
23027
23084
|
res.status(400).json({ error: "description is required" });
|
|
23028
23085
|
return;
|
|
23029
23086
|
}
|
|
23087
|
+
if (tags !== void 0 && (!Array.isArray(tags) || !tags.every(isValidTag))) {
|
|
23088
|
+
res.status(400).json({ error: "tags must be an array of [a-zA-Z0-9_-] strings (no spaces, newlines, or '#')" });
|
|
23089
|
+
return;
|
|
23090
|
+
}
|
|
23030
23091
|
if (!await projectExists(projectsDir, slug)) {
|
|
23031
23092
|
notFound(res, slug);
|
|
23032
23093
|
return;
|
|
@@ -23171,6 +23232,7 @@ workspace: ${slug}
|
|
|
23171
23232
|
}
|
|
23172
23233
|
const completedItems = checklist.items.filter((i) => completedIds.has(i.id));
|
|
23173
23234
|
for (const item of completedItems) {
|
|
23235
|
+
assertValidTags(item.tags);
|
|
23174
23236
|
archContent += `- [x] ${item.description} ${item.tags.map((t) => `#${t}`).join(" ")} [t:${item.id}]
|
|
23175
23237
|
`;
|
|
23176
23238
|
}
|
|
@@ -23256,6 +23318,14 @@ workspace: ${slug}
|
|
|
23256
23318
|
router.patch("/:id", async (req2, res) => {
|
|
23257
23319
|
try {
|
|
23258
23320
|
const slug = getProjectIdParam(params(req2).projectId);
|
|
23321
|
+
if (req2.body.tags !== void 0 && (!Array.isArray(req2.body.tags) || !req2.body.tags.every(isValidTag))) {
|
|
23322
|
+
res.status(400).json({ error: "tags must be an array of [a-zA-Z0-9_-] strings (no spaces, newlines, or '#')" });
|
|
23323
|
+
return;
|
|
23324
|
+
}
|
|
23325
|
+
if (req2.body.description !== void 0 && typeof req2.body.description !== "string") {
|
|
23326
|
+
res.status(400).json({ error: "description must be a string" });
|
|
23327
|
+
return;
|
|
23328
|
+
}
|
|
23259
23329
|
if (!await projectExists(projectsDir, slug)) {
|
|
23260
23330
|
notFound(res, slug);
|
|
23261
23331
|
return;
|