@delorenj/pjangler 1.2.3 → 1.2.4
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/dist/index.js +97 -187
- package/dist/mcp-server.js +92 -184
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,18 +9,6 @@ import { Command as Command3 } from "commander";
|
|
|
9
9
|
// src/commands/hermes/types.ts
|
|
10
10
|
var HERMES_AGENT_TEMPLATE = "gh:delorenj/hermes-agent-template";
|
|
11
11
|
var SOUL_TONES = ["direct", "playful", "formal", "terse"];
|
|
12
|
-
var ROLE_CHOICES = [
|
|
13
|
-
{ value: "pm", label: "Project Manager (pm)", hint: "triage, planning, ticket authorship, board reconciliation" },
|
|
14
|
-
{ value: "dev", label: "Developer (dev)", hint: "implements tickets" },
|
|
15
|
-
{ value: "review", label: "Reviewer (review)", hint: "adversarial code review" },
|
|
16
|
-
{ value: "ops", label: "Ops (ops)", hint: "deploy / infra" },
|
|
17
|
-
{ value: "qa", label: "QA (qa)", hint: "test authorship + verification" }
|
|
18
|
-
];
|
|
19
|
-
var TICKET_PROVIDERS = [
|
|
20
|
-
{ value: "plane", label: "Plane", hint: "self-hosted at plane.delo.sh (default)" },
|
|
21
|
-
{ value: "linear", label: "Linear", hint: "team board (created in Linear UI)" },
|
|
22
|
-
{ value: "trello", label: "Trello", hint: "board = project" }
|
|
23
|
-
];
|
|
24
12
|
function deriveAgentId(repo, role) {
|
|
25
13
|
return `${repo}-${role}`.toLowerCase();
|
|
26
14
|
}
|
|
@@ -748,19 +736,18 @@ var PromptForAgentConfig = class extends Command {
|
|
|
748
736
|
async invoke() {
|
|
749
737
|
const ctx = this.context;
|
|
750
738
|
const defaultRepo = basename(ctx.targetDir).toLowerCase();
|
|
751
|
-
|
|
739
|
+
ctx.targetRepo = (ctx.targetRepo ?? defaultRepo).toLowerCase();
|
|
740
|
+
ctx.role ??= "pm";
|
|
741
|
+
ctx.agentPurpose ??= `${ctx.role} agent for ${ctx.targetRepo}`;
|
|
742
|
+
ctx.soulTone ??= "direct";
|
|
743
|
+
ctx.modelProvider ??= "";
|
|
744
|
+
ctx.modelName ??= "";
|
|
745
|
+
ctx.ticketProvider ??= detectTicketProvider(ctx.targetDir) ?? "plane";
|
|
746
|
+
ctx.skipEmail ??= true;
|
|
747
|
+
ctx.agentId = deriveAgentId(ctx.targetRepo, ctx.role);
|
|
748
|
+
ctx.profileName = deriveProfileName(ctx.targetRepo, ctx.role);
|
|
752
749
|
if (ctx.yes) {
|
|
753
|
-
ctx.targetRepo = (ctx.targetRepo ?? defaultRepo).toLowerCase();
|
|
754
|
-
ctx.role ??= defaultRole;
|
|
755
|
-
ctx.agentPurpose ??= `${ctx.role} agent for ${ctx.targetRepo}`;
|
|
756
|
-
ctx.soulTone ??= "direct";
|
|
757
|
-
ctx.modelProvider ??= "";
|
|
758
|
-
ctx.modelName ??= "";
|
|
759
|
-
ctx.ticketProvider ??= detectTicketProvider(ctx.targetDir) ?? "plane";
|
|
760
750
|
ctx.skipTelegram ??= true;
|
|
761
|
-
ctx.skipEmail ??= true;
|
|
762
|
-
ctx.agentId = deriveAgentId(ctx.targetRepo, ctx.role);
|
|
763
|
-
ctx.profileName = deriveProfileName(ctx.targetRepo, ctx.role);
|
|
764
751
|
return {
|
|
765
752
|
success: true,
|
|
766
753
|
message: this.formatMessage(
|
|
@@ -768,96 +755,19 @@ var PromptForAgentConfig = class extends Command {
|
|
|
768
755
|
)
|
|
769
756
|
};
|
|
770
757
|
}
|
|
771
|
-
p.intro("\u2695 hermes-agent \xB7
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
placeholder: defaultRepo,
|
|
776
|
-
initialValue: defaultRepo,
|
|
777
|
-
validate: (v) => v && v.trim() ? void 0 : "required"
|
|
778
|
-
});
|
|
779
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
780
|
-
ctx.targetRepo = String(answer).trim().toLowerCase();
|
|
781
|
-
}
|
|
782
|
-
if (!ctx.role) {
|
|
783
|
-
const answer = await p.select({
|
|
784
|
-
message: "Role",
|
|
785
|
-
options: ROLE_CHOICES.map((r) => ({ value: r.value, label: r.label, hint: r.hint })),
|
|
786
|
-
initialValue: defaultRole
|
|
787
|
-
});
|
|
788
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
789
|
-
ctx.role = String(answer).trim();
|
|
790
|
-
}
|
|
791
|
-
if (ctx.ticketProvider === void 0) {
|
|
792
|
-
const detected = detectTicketProvider(ctx.targetDir);
|
|
793
|
-
const answer = await p.select({
|
|
794
|
-
message: "Ticket board provider",
|
|
795
|
-
options: TICKET_PROVIDERS.map((t) => ({
|
|
796
|
-
value: t.value,
|
|
797
|
-
label: t.label,
|
|
798
|
-
hint: t.value === detected ? `${t.hint} \u2014 current .project.json` : t.hint
|
|
799
|
-
})),
|
|
800
|
-
initialValue: detected ?? "plane"
|
|
801
|
-
});
|
|
802
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
803
|
-
ctx.ticketProvider = answer;
|
|
804
|
-
}
|
|
805
|
-
if (!ctx.agentPurpose) {
|
|
806
|
-
const answer = await p.text({
|
|
807
|
-
message: "One-line purpose",
|
|
808
|
-
placeholder: `${ctx.role} agent for ${ctx.targetRepo}`,
|
|
809
|
-
initialValue: `${ctx.role} agent for ${ctx.targetRepo}`
|
|
810
|
-
});
|
|
811
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
812
|
-
ctx.agentPurpose = String(answer).trim();
|
|
813
|
-
}
|
|
814
|
-
if (!ctx.soulTone) {
|
|
815
|
-
const answer = await p.select({
|
|
816
|
-
message: "Personality tone",
|
|
817
|
-
options: SOUL_TONES.map((t) => ({
|
|
818
|
-
value: t,
|
|
819
|
-
label: t,
|
|
820
|
-
hint: t === "direct" ? "decision-forward, no preamble (default)" : t === "terse" ? "minimum words, conclusion-first" : t === "playful" ? "warm, mildly funny" : "precise, structured"
|
|
821
|
-
})),
|
|
822
|
-
initialValue: "direct"
|
|
823
|
-
});
|
|
824
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
825
|
-
ctx.soulTone = answer;
|
|
826
|
-
}
|
|
827
|
-
if (ctx.modelProvider === void 0) {
|
|
828
|
-
const answer = await p.text({
|
|
829
|
-
message: "Provider override (empty = inherit shared default profile)",
|
|
830
|
-
placeholder: ""
|
|
831
|
-
});
|
|
832
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
833
|
-
ctx.modelProvider = String(answer).trim();
|
|
834
|
-
}
|
|
835
|
-
if (ctx.modelName === void 0) {
|
|
836
|
-
const answer = await p.text({
|
|
837
|
-
message: "Model name override (empty = inherit shared default profile)",
|
|
838
|
-
placeholder: ""
|
|
839
|
-
});
|
|
840
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
841
|
-
ctx.modelName = String(answer).trim();
|
|
842
|
-
}
|
|
758
|
+
p.intro("\u2695 hermes-agent \xB7 provision the PM agent for this repo");
|
|
759
|
+
p.log.info(
|
|
760
|
+
`agent ${ctx.agentId} \xB7 board ${ctx.ticketProvider} \xB7 tone ${ctx.soulTone}`
|
|
761
|
+
);
|
|
843
762
|
if (ctx.skipTelegram === void 0) {
|
|
763
|
+
const botHandle = `${ctx.targetRepo.replace(/-/g, "_")}_${ctx.role}_bot`;
|
|
844
764
|
const wire = await p.confirm({
|
|
845
|
-
message: `Wire up the Telegram bot (@${
|
|
765
|
+
message: `Wire up the Telegram bot (@${botHandle}) now?`,
|
|
846
766
|
initialValue: true
|
|
847
767
|
});
|
|
848
768
|
if (p.isCancel(wire)) return this.cancelled();
|
|
849
769
|
ctx.skipTelegram = !wire;
|
|
850
770
|
}
|
|
851
|
-
if (ctx.skipEmail === void 0) {
|
|
852
|
-
const wire = await p.confirm({
|
|
853
|
-
message: `Provision the delo.sh email address (${ctx.targetRepo}-${ctx.role}@delo.sh) now?`,
|
|
854
|
-
initialValue: true
|
|
855
|
-
});
|
|
856
|
-
if (p.isCancel(wire)) return this.cancelled();
|
|
857
|
-
ctx.skipEmail = !wire;
|
|
858
|
-
}
|
|
859
|
-
ctx.agentId = deriveAgentId(ctx.targetRepo, ctx.role);
|
|
860
|
-
ctx.profileName = deriveProfileName(ctx.targetRepo, ctx.role);
|
|
861
771
|
return {
|
|
862
772
|
success: true,
|
|
863
773
|
message: this.formatMessage(
|
|
@@ -1132,7 +1042,7 @@ var WireEmail = class extends Command {
|
|
|
1132
1042
|
async invoke() {
|
|
1133
1043
|
const ctx = this.context;
|
|
1134
1044
|
if (ctx.skipEmail) {
|
|
1135
|
-
return { success: true, message: "
|
|
1045
|
+
return { success: true, message: "" };
|
|
1136
1046
|
}
|
|
1137
1047
|
if (ctx.dryRun) {
|
|
1138
1048
|
return { success: true, message: this.formatMessage("Would create CF Email Routing rule") };
|
|
@@ -1240,7 +1150,7 @@ var PrintHermesSummary = class extends Command {
|
|
|
1240
1150
|
lines.push(`role dir ${ctx.roleDir}`);
|
|
1241
1151
|
lines.push(`runtime gh:${runtimeRepo}`);
|
|
1242
1152
|
lines.push(`telegram @${botHandle}${skipTelegram ? " (NOT yet wired)" : ""}`);
|
|
1243
|
-
lines.push(`email ${email}
|
|
1153
|
+
if (!skipEmail) lines.push(`email ${email}`);
|
|
1244
1154
|
lines.push("");
|
|
1245
1155
|
lines.push("Start daemons:");
|
|
1246
1156
|
lines.push(` systemctl --user start ${csm}`);
|
|
@@ -1253,11 +1163,10 @@ var PrintHermesSummary = class extends Command {
|
|
|
1253
1163
|
lines.push("");
|
|
1254
1164
|
lines.push("Talk locally:");
|
|
1255
1165
|
lines.push(` ${ctx.roleDir}/hermes chat "status"`);
|
|
1256
|
-
if (skipTelegram
|
|
1166
|
+
if (skipTelegram) {
|
|
1257
1167
|
lines.push("");
|
|
1258
|
-
lines.push("
|
|
1259
|
-
|
|
1260
|
-
if (skipEmail) lines.push(" pjangler hermes-agent --skip-email=false # wire just email");
|
|
1168
|
+
lines.push("Wire Telegram later:");
|
|
1169
|
+
lines.push(" pjangler hermes-agent # re-run and answer yes when asked");
|
|
1261
1170
|
}
|
|
1262
1171
|
p5.note(lines.join("\n"), `Provisioned ${agentId}`);
|
|
1263
1172
|
p5.outro("Done.");
|
|
@@ -1616,7 +1525,7 @@ function createRecipe(name, context) {
|
|
|
1616
1525
|
}
|
|
1617
1526
|
|
|
1618
1527
|
// src/index.ts
|
|
1619
|
-
import { cancel as cancel2, multiselect, text as
|
|
1528
|
+
import { cancel as cancel2, multiselect, text as text2, isCancel as isCancel5 } from "@clack/prompts";
|
|
1620
1529
|
|
|
1621
1530
|
// src/parity/index.ts
|
|
1622
1531
|
import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync5, readFileSync as readFileSync3, readlinkSync, readdirSync, renameSync, symlinkSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4, chmodSync as chmodSync2, copyFileSync } from "node:fs";
|
|
@@ -1717,10 +1626,10 @@ function writeText(path, content) {
|
|
|
1717
1626
|
ensureParent(path);
|
|
1718
1627
|
writeFileSync4(path, content);
|
|
1719
1628
|
}
|
|
1720
|
-
function tryParseJson(
|
|
1721
|
-
if (!
|
|
1629
|
+
function tryParseJson(text3) {
|
|
1630
|
+
if (!text3) return null;
|
|
1722
1631
|
try {
|
|
1723
|
-
return JSON.parse(
|
|
1632
|
+
return JSON.parse(text3);
|
|
1724
1633
|
} catch {
|
|
1725
1634
|
return null;
|
|
1726
1635
|
}
|
|
@@ -1779,9 +1688,9 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
|
1779
1688
|
}
|
|
1780
1689
|
return { changedFiles: [], details: [], blocked: "AGENTS.md missing and no CLAUDE.md, GEMINI.md, or README.md source exists" };
|
|
1781
1690
|
}
|
|
1782
|
-
function yamlGet(
|
|
1691
|
+
function yamlGet(text3, keyPath) {
|
|
1783
1692
|
const parts = keyPath.split(".");
|
|
1784
|
-
const lines =
|
|
1693
|
+
const lines = text3.split("\n");
|
|
1785
1694
|
let start = 0;
|
|
1786
1695
|
let indent = 0;
|
|
1787
1696
|
for (let idx = 0; idx < parts.length; idx += 1) {
|
|
@@ -1816,25 +1725,25 @@ function discoverRoles(repoRoot) {
|
|
|
1816
1725
|
const roleDir = join9(rolesDir, entry.name);
|
|
1817
1726
|
const roleYamlPath = join9(roleDir, "role.yaml");
|
|
1818
1727
|
if (!existsSync7(roleYamlPath)) return null;
|
|
1819
|
-
const
|
|
1820
|
-
const runtimeRepoRaw = yamlGet(
|
|
1728
|
+
const text3 = readText(roleYamlPath);
|
|
1729
|
+
const runtimeRepoRaw = yamlGet(text3, "runtime.github_repo");
|
|
1821
1730
|
return {
|
|
1822
|
-
role: yamlGet(
|
|
1731
|
+
role: yamlGet(text3, "role") || entry.name,
|
|
1823
1732
|
roleDir,
|
|
1824
1733
|
roleYamlPath,
|
|
1825
|
-
repo: yamlGet(
|
|
1826
|
-
agentId: yamlGet(
|
|
1827
|
-
profileName: yamlGet(
|
|
1828
|
-
displayName: yamlGet(
|
|
1829
|
-
purpose: yamlGet(
|
|
1830
|
-
botHandle: yamlGet(
|
|
1734
|
+
repo: yamlGet(text3, "repo"),
|
|
1735
|
+
agentId: yamlGet(text3, "agent_id"),
|
|
1736
|
+
profileName: yamlGet(text3, "profile") || yamlGet(text3, "agent_id"),
|
|
1737
|
+
displayName: yamlGet(text3, "display_name"),
|
|
1738
|
+
purpose: yamlGet(text3, "purpose"),
|
|
1739
|
+
botHandle: yamlGet(text3, "telegram.bot_username"),
|
|
1831
1740
|
runtimeRepo: runtimeRepoRaw.includes("/") ? runtimeRepoRaw.split("/").slice(-1)[0] ?? runtimeRepoRaw : runtimeRepoRaw,
|
|
1832
|
-
runtimeOwner: yamlGet(
|
|
1833
|
-
planeWorkspace: yamlGet(
|
|
1834
|
-
ticketProviderName: yamlGet(
|
|
1835
|
-
ticketProviderBoardId: yamlGet(
|
|
1836
|
-
ticketProviderBoardUrl: yamlGet(
|
|
1837
|
-
ticketProviderIdentifier: yamlGet(
|
|
1741
|
+
runtimeOwner: yamlGet(text3, "runtime.github_owner"),
|
|
1742
|
+
planeWorkspace: yamlGet(text3, "ticket_provider.workspace") || yamlGet(text3, "plane.workspace"),
|
|
1743
|
+
ticketProviderName: yamlGet(text3, "ticket_provider.name"),
|
|
1744
|
+
ticketProviderBoardId: yamlGet(text3, "ticket_provider.board_id"),
|
|
1745
|
+
ticketProviderBoardUrl: yamlGet(text3, "ticket_provider.board_url"),
|
|
1746
|
+
ticketProviderIdentifier: yamlGet(text3, "plane.identifier")
|
|
1838
1747
|
};
|
|
1839
1748
|
}).filter((value) => Boolean(value));
|
|
1840
1749
|
}
|
|
@@ -1879,19 +1788,19 @@ function templateVersionFilesConf(ctx, repoRoot) {
|
|
|
1879
1788
|
const packageJson = join9(repoRoot, "package.json");
|
|
1880
1789
|
return existsSync7(packageJson) ? "# mise-versioning manifest: <type> <path>\n# types: json toml cargo csproj gradle plain gittag\njson package.json\ngittag .\n" : "# mise-versioning manifest: <type> <path>\n# types: json toml cargo csproj gradle plain gittag\ngittag .\n";
|
|
1881
1790
|
}
|
|
1882
|
-
function replaceOrAppendManagedBlock(
|
|
1883
|
-
if (startMarker.test(
|
|
1884
|
-
return
|
|
1791
|
+
function replaceOrAppendManagedBlock(text3, startMarker, block, beforePattern) {
|
|
1792
|
+
if (startMarker.test(text3)) {
|
|
1793
|
+
return text3.replace(/# >>> mise-versioning >>>[\s\S]*?# <<< mise-versioning <<</, block);
|
|
1885
1794
|
}
|
|
1886
1795
|
if (beforePattern) {
|
|
1887
|
-
const match =
|
|
1796
|
+
const match = text3.match(beforePattern);
|
|
1888
1797
|
if (match && typeof match.index === "number") {
|
|
1889
|
-
return `${
|
|
1798
|
+
return `${text3.slice(0, match.index).replace(/\s*$/, "\n\n")}${block}
|
|
1890
1799
|
|
|
1891
|
-
${
|
|
1800
|
+
${text3.slice(match.index)}`;
|
|
1892
1801
|
}
|
|
1893
1802
|
}
|
|
1894
|
-
return `${
|
|
1803
|
+
return `${text3.replace(/\s*$/, "")}
|
|
1895
1804
|
|
|
1896
1805
|
${block}
|
|
1897
1806
|
`;
|
|
@@ -1905,18 +1814,18 @@ function requiredMisePathEntries(ctx) {
|
|
|
1905
1814
|
}
|
|
1906
1815
|
return required;
|
|
1907
1816
|
}
|
|
1908
|
-
function upsertMisePath(
|
|
1817
|
+
function upsertMisePath(text3, required = BASE_MISE_PATH_ENTRIES) {
|
|
1909
1818
|
const render = (values) => `_.path = [${values.map((value) => JSON.stringify(value)).join(", ")}]`;
|
|
1910
|
-
const envMatch =
|
|
1819
|
+
const envMatch = text3.match(/(^|\n)(\[env\][\s\S]*?)(?=\n\[[^\]]+\]|$)/);
|
|
1911
1820
|
if (!envMatch || typeof envMatch.index !== "number") {
|
|
1912
1821
|
return `[env]
|
|
1913
1822
|
${render(required)}
|
|
1914
1823
|
|
|
1915
|
-
${
|
|
1824
|
+
${text3.replace(/^\s+/, "")}`;
|
|
1916
1825
|
}
|
|
1917
|
-
const prefix =
|
|
1826
|
+
const prefix = text3.slice(0, envMatch.index + envMatch[1].length);
|
|
1918
1827
|
const section = envMatch[2];
|
|
1919
|
-
const suffix =
|
|
1828
|
+
const suffix = text3.slice(envMatch.index + envMatch[1].length + section.length);
|
|
1920
1829
|
const pathLine = section.match(/^_\.path\s*=\s*\[([^\]]*)\]\s*$/m);
|
|
1921
1830
|
if (!pathLine) {
|
|
1922
1831
|
return `${prefix}${section.replace(/\n?$/, "\n")}${render(required)}${suffix}`;
|
|
@@ -1927,11 +1836,11 @@ ${text4.replace(/^\s+/, "")}`;
|
|
|
1927
1836
|
if (!merged.includes(value)) merged.push(value);
|
|
1928
1837
|
}
|
|
1929
1838
|
const nextLine = render(merged);
|
|
1930
|
-
if (pathLine[0] === nextLine) return
|
|
1839
|
+
if (pathLine[0] === nextLine) return text3;
|
|
1931
1840
|
return `${prefix}${section.replace(pathLine[0], nextLine)}${suffix}`;
|
|
1932
1841
|
}
|
|
1933
|
-
function removeTomlSection(
|
|
1934
|
-
const lines =
|
|
1842
|
+
function removeTomlSection(text3, headerPattern, marker, options) {
|
|
1843
|
+
const lines = text3.split("\n");
|
|
1935
1844
|
let start = -1;
|
|
1936
1845
|
let end = -1;
|
|
1937
1846
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -1956,7 +1865,7 @@ function removeTomlSection(text4, headerPattern, marker, options) {
|
|
|
1956
1865
|
if (end === -1) end = lines.length;
|
|
1957
1866
|
break;
|
|
1958
1867
|
}
|
|
1959
|
-
if (start === -1) return
|
|
1868
|
+
if (start === -1) return text3;
|
|
1960
1869
|
if (options?.includePrecedingComments) {
|
|
1961
1870
|
while (start > 0 && lines[start - 1].trim().startsWith("#")) {
|
|
1962
1871
|
start--;
|
|
@@ -1965,22 +1874,22 @@ function removeTomlSection(text4, headerPattern, marker, options) {
|
|
|
1965
1874
|
const result = lines.slice(0, start).concat(lines.slice(end)).join("\n");
|
|
1966
1875
|
return result.replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "\n");
|
|
1967
1876
|
}
|
|
1968
|
-
function insertTomlBlockBeforeVersioning(
|
|
1969
|
-
const versioningIndex =
|
|
1877
|
+
function insertTomlBlockBeforeVersioning(text3, block) {
|
|
1878
|
+
const versioningIndex = text3.indexOf("# >>> mise-versioning >>>");
|
|
1970
1879
|
if (versioningIndex >= 0) {
|
|
1971
|
-
return `${
|
|
1880
|
+
return `${text3.slice(0, versioningIndex).replace(/\s*$/, "\n\n")}${block}
|
|
1972
1881
|
|
|
1973
|
-
${
|
|
1882
|
+
${text3.slice(versioningIndex)}`;
|
|
1974
1883
|
}
|
|
1975
|
-
return `${
|
|
1884
|
+
return `${text3.replace(/\s*$/, "")}
|
|
1976
1885
|
|
|
1977
1886
|
${block}
|
|
1978
1887
|
`;
|
|
1979
1888
|
}
|
|
1980
|
-
function extractTomlStrings(
|
|
1889
|
+
function extractTomlStrings(text3) {
|
|
1981
1890
|
const values = [];
|
|
1982
1891
|
const stringPattern = /"((?:\\.|[^"\\])*)"|'([^']*)'/g;
|
|
1983
|
-
for (const match of
|
|
1892
|
+
for (const match of text3.matchAll(stringPattern)) {
|
|
1984
1893
|
if (match[1] !== void 0) {
|
|
1985
1894
|
try {
|
|
1986
1895
|
values.push(JSON.parse(`"${match[1]}"`));
|
|
@@ -2004,10 +1913,10 @@ function renderHookEntries(entries, indent = "") {
|
|
|
2004
1913
|
`${indent}]`
|
|
2005
1914
|
];
|
|
2006
1915
|
}
|
|
2007
|
-
function upsertLinkAgentfilesHooks(
|
|
2008
|
-
const lines =
|
|
1916
|
+
function upsertLinkAgentfilesHooks(text3) {
|
|
1917
|
+
const lines = text3.split("\n");
|
|
2009
1918
|
const hooksStart = lines.findIndex((line) => /^\[hooks\]$/.test(line.trim()));
|
|
2010
|
-
if (hooksStart === -1) return insertTomlBlockBeforeVersioning(
|
|
1919
|
+
if (hooksStart === -1) return insertTomlBlockBeforeVersioning(text3, LINK_AGENTFILES_HOOKS_BLOCK);
|
|
2011
1920
|
let hooksEnd = lines.length;
|
|
2012
1921
|
for (let i = hooksStart + 1; i < lines.length; i++) {
|
|
2013
1922
|
if (/^\[[^\]]+\]/.test(lines[i].trim())) {
|
|
@@ -2041,8 +1950,8 @@ function upsertLinkAgentfilesHooks(text4) {
|
|
|
2041
1950
|
}
|
|
2042
1951
|
return lines.slice(0, hooksStart + 1).concat(rendered, lines.slice(hooksStart + 1)).join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "\n");
|
|
2043
1952
|
}
|
|
2044
|
-
function upsertLinkAgentfilesBlock(
|
|
2045
|
-
const withPath = upsertMisePath(
|
|
1953
|
+
function upsertLinkAgentfilesBlock(text3, ctx) {
|
|
1954
|
+
const withPath = upsertMisePath(text3, requiredMisePathEntries(ctx));
|
|
2046
1955
|
if (withPath.includes(LINK_AGENTFILES_BLOCK)) return withPath;
|
|
2047
1956
|
let cleaned = removeTomlSection(withPath, /^\[tasks\.link-agentfiles\]$/, /link-agentfiles/, { includePrecedingComments: false });
|
|
2048
1957
|
cleaned = removeTomlSection(cleaned, /^\[\[watch_files\]\]$/, /AGENTS\.md/, { includePrecedingComments: false });
|
|
@@ -2269,9 +2178,9 @@ ${block}`) : `${current.replace(/\s*$/, "\n")}${block}`;
|
|
|
2269
2178
|
return path;
|
|
2270
2179
|
}
|
|
2271
2180
|
function profileMetaInheritsDefault(path) {
|
|
2272
|
-
const
|
|
2181
|
+
const text3 = safeReadText(path);
|
|
2273
2182
|
return Boolean(
|
|
2274
|
-
|
|
2183
|
+
text3 && /^config:\s*$/m.test(text3) && /^\s+inherit_from:\s*default\s*$/m.test(text3) && /^\s+save_mode:\s*delta\s*$/m.test(text3)
|
|
2275
2184
|
);
|
|
2276
2185
|
}
|
|
2277
2186
|
function upsertInheritedProfileMeta(path, changedFiles, dryRun) {
|
|
@@ -2329,17 +2238,17 @@ var RULES = [
|
|
|
2329
2238
|
if (!existsSync7(misePath)) {
|
|
2330
2239
|
return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
|
|
2331
2240
|
}
|
|
2332
|
-
const
|
|
2241
|
+
const text3 = readText(misePath);
|
|
2333
2242
|
const details = [];
|
|
2334
2243
|
const linkAgentfilesPath = join9(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
2335
2244
|
if (!existsSync7(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
|
|
2336
|
-
const pathValues = [...(
|
|
2245
|
+
const pathValues = [...(text3.match(/^_\.path\s*=\s*\[([^\]]*)\]/m)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
|
2337
2246
|
const missingPathValues = requiredMisePathEntries(ctx).filter((value) => !pathValues.includes(value));
|
|
2338
2247
|
if (missingPathValues.length) details.push(`[env]._.path should include ${missingPathValues.join(", ")}`);
|
|
2339
|
-
if (!
|
|
2340
|
-
if (!
|
|
2341
|
-
if (!
|
|
2342
|
-
if (!
|
|
2248
|
+
if (!text3.includes('"{{config_root}}/.mise/scripts/link-agentfiles.sh"')) details.push("link-agentfiles must use raw {{config_root}} guard");
|
|
2249
|
+
if (!text3.includes("op inject -i .env.op > .env")) details.push("[hooks].enter must materialize .env from .env.op");
|
|
2250
|
+
if (!text3.includes('patterns = ["AGENTS.md"]')) details.push("watch_files must monitor AGENTS.md");
|
|
2251
|
+
if (!text3.includes('task = "link-agentfiles"')) details.push("watch_files must dispatch link-agentfiles task");
|
|
2343
2252
|
return {
|
|
2344
2253
|
id: "mise.config-root",
|
|
2345
2254
|
title: "mise config_root + AGENTS link hooks",
|
|
@@ -2362,12 +2271,12 @@ var RULES = [
|
|
|
2362
2271
|
return { id: finding.id, title: finding.title, status: "applied", summary: "Would initialize mise.toml from generated-project template", changedFiles, details };
|
|
2363
2272
|
}
|
|
2364
2273
|
}
|
|
2365
|
-
let
|
|
2366
|
-
const next = upsertLinkAgentfilesBlock(
|
|
2367
|
-
if (next !==
|
|
2274
|
+
let text3 = readText(path);
|
|
2275
|
+
const next = upsertLinkAgentfilesBlock(text3, ctx);
|
|
2276
|
+
if (next !== text3) {
|
|
2368
2277
|
if (!changedFiles.includes(path)) changedFiles.push(path);
|
|
2369
2278
|
if (!ctx.dryRun) writeText(path, next);
|
|
2370
|
-
|
|
2279
|
+
text3 = next;
|
|
2371
2280
|
}
|
|
2372
2281
|
const linkAgentfilesPath = join9(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
2373
2282
|
const expectedScript = templateLinkAgentfilesScript(ctx);
|
|
@@ -2399,8 +2308,8 @@ var RULES = [
|
|
|
2399
2308
|
const misePath = join9(ctx.repoRoot, "mise.toml");
|
|
2400
2309
|
const versioningPath = join9(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
2401
2310
|
const manifestPath = join9(ctx.repoRoot, ".mise", "version-files.conf");
|
|
2402
|
-
const
|
|
2403
|
-
if (!
|
|
2311
|
+
const text3 = safeReadText(misePath);
|
|
2312
|
+
if (!text3?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
|
|
2404
2313
|
if (!existsSync7(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
|
|
2405
2314
|
if (!existsSync7(manifestPath)) details.push(".mise/version-files.conf missing");
|
|
2406
2315
|
return {
|
|
@@ -2621,19 +2530,19 @@ var RULES = [
|
|
|
2621
2530
|
audit: (ctx) => {
|
|
2622
2531
|
const details = [];
|
|
2623
2532
|
const path = join9(ctx.repoRoot, ".copier-answers.yml");
|
|
2624
|
-
const
|
|
2533
|
+
const text3 = safeReadText(path);
|
|
2625
2534
|
const project = readProjectJson(ctx);
|
|
2626
|
-
if (!
|
|
2535
|
+
if (!text3) {
|
|
2627
2536
|
details.push(".copier-answers.yml missing");
|
|
2628
2537
|
} else {
|
|
2629
|
-
if (!
|
|
2630
|
-
if (!
|
|
2538
|
+
if (!text3.startsWith("# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY")) details.push("missing Copier overwrite warning header");
|
|
2539
|
+
if (!text3.includes("_src_path:")) details.push("_src_path missing");
|
|
2631
2540
|
if (project?.project_name) {
|
|
2632
|
-
const nameMatch =
|
|
2541
|
+
const nameMatch = text3.match(/project_name:\s*(.+)/);
|
|
2633
2542
|
if (!nameMatch || nameMatch[1]?.trim() !== String(project.project_name)) details.push("project_name drift between .copier-answers.yml and .project.json");
|
|
2634
2543
|
}
|
|
2635
2544
|
if (project?.project_description) {
|
|
2636
|
-
const descMatch =
|
|
2545
|
+
const descMatch = text3.match(/project_description:\s*([\s\S]*?)(?=\n\w|$)/);
|
|
2637
2546
|
const yamlDesc = descMatch?.[1]?.replace(/\n\s+/g, " ").trim() ?? "";
|
|
2638
2547
|
if (yamlDesc !== String(project.project_description)) details.push("project_description drift between .copier-answers.yml and .project.json");
|
|
2639
2548
|
}
|
|
@@ -2650,16 +2559,16 @@ var RULES = [
|
|
|
2650
2559
|
migrate: (ctx, finding) => {
|
|
2651
2560
|
const changedFiles = [];
|
|
2652
2561
|
const project = canonicalProjectJson(ctx);
|
|
2653
|
-
const
|
|
2562
|
+
const text3 = `# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
|
|
2654
2563
|
_src_path: ${join9(ctx.pjanglerRoot, "templates", "commonproject")}
|
|
2655
2564
|
project_description: ${String(project.project_description)}
|
|
2656
2565
|
project_name: ${String(project.project_name)}
|
|
2657
2566
|
ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
2658
2567
|
`;
|
|
2659
2568
|
const path = join9(ctx.repoRoot, ".copier-answers.yml");
|
|
2660
|
-
if (safeReadText(path) !==
|
|
2569
|
+
if (safeReadText(path) !== text3) {
|
|
2661
2570
|
changedFiles.push(path);
|
|
2662
|
-
if (!ctx.dryRun) writeText(path,
|
|
2571
|
+
if (!ctx.dryRun) writeText(path, text3);
|
|
2663
2572
|
}
|
|
2664
2573
|
return {
|
|
2665
2574
|
id: finding.id,
|
|
@@ -3467,7 +3376,7 @@ function isInteractiveProjectInit(options) {
|
|
|
3467
3376
|
return !options.json && !options.yes && options.tui !== false && Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
3468
3377
|
}
|
|
3469
3378
|
async function promptTextValue(message, initialValue) {
|
|
3470
|
-
const value = await
|
|
3379
|
+
const value = await text2({
|
|
3471
3380
|
message,
|
|
3472
3381
|
initialValue,
|
|
3473
3382
|
validate: (input) => input?.trim() ? void 0 : "Required"
|
|
@@ -3955,7 +3864,7 @@ program.command("migrate").argument("[rule-id]", "Rule ID to migrate (omit to op
|
|
|
3955
3864
|
process.exit(1);
|
|
3956
3865
|
}
|
|
3957
3866
|
});
|
|
3958
|
-
program.command("hermes-agent").alias("hermes").description("Provision
|
|
3867
|
+
program.command("hermes-agent").alias("hermes").description("Provision the PM agent for the current repo (defaults everything; only asks about Telegram)").option("-y, --yes", "Non-interactive: accept all defaults (also skips the Telegram prompt)").option("--target-repo <name>", "Target repo name (default: basename of cwd)").option("--role <role>", "Agent role override (default: pm \u2014 the only role in the fleet)").option("--purpose <text>", 'One-line agent purpose (default: "pm agent for <repo>")').option(`--tone <tone>`, `Personality tone (default: direct; ${SOUL_TONES.join(" | ")})`).option("--model-provider <name>", 'Inference provider override ("" = inherit shared default profile)').option("--model-name <name>", 'Model name override ("" = inherit shared default profile)').option("--skip-telegram", "Skip the Telegram wire-up (no BotFather prompt)").option("--email", "Also provision the delo.sh email address (off by default; never prompted)").option("--skip-runtime-repo", "Skip creating the per-agent runtime GH repo").option("--skip-plane", "Skip creating the Plane project").option("--skip-bloodbank", "Skip installing the Bloodbank NATS consumer").option("--skip-systemd", "Skip installing systemd --user units").option("--local", "Local-only: skip runtime repo, Plane, Bloodbank, and systemd (safe for laptops/macOS/non-technical operators)").option("--force-config", "Regenerate ~/.config/hermes-agent-template/config.toml even if it exists").option("--dry-run", "Preview what would run; don't execute copier").option("-f, --force", "Re-render even if agents/hermes/<role>/role.yaml already exists").action(async (options) => {
|
|
3959
3868
|
const isDarwin = process.platform === "darwin";
|
|
3960
3869
|
const local = options.local ?? false;
|
|
3961
3870
|
const context = {
|
|
@@ -3972,7 +3881,8 @@ program.command("hermes-agent").alias("hermes").description("Provision a Hermes
|
|
|
3972
3881
|
modelProvider: options.modelProvider,
|
|
3973
3882
|
modelName: options.modelName,
|
|
3974
3883
|
skipTelegram: options.skipTelegram,
|
|
3975
|
-
|
|
3884
|
+
// Email is opt-in only: `--email` wires it, otherwise it's never done.
|
|
3885
|
+
skipEmail: options.email ? false : void 0,
|
|
3976
3886
|
// --local (and macOS, for systemd) flip the heavy/irreversible steps off
|
|
3977
3887
|
// by default so a non-technical operator can't accidentally create cloud
|
|
3978
3888
|
// resources under the wrong account or hit systemd on a Mac. An explicit
|
package/dist/mcp-server.js
CHANGED
|
@@ -702,19 +702,6 @@ import * as p from "@clack/prompts";
|
|
|
702
702
|
|
|
703
703
|
// src/commands/hermes/types.ts
|
|
704
704
|
var HERMES_AGENT_TEMPLATE = "gh:delorenj/hermes-agent-template";
|
|
705
|
-
var SOUL_TONES = ["direct", "playful", "formal", "terse"];
|
|
706
|
-
var ROLE_CHOICES = [
|
|
707
|
-
{ value: "pm", label: "Project Manager (pm)", hint: "triage, planning, ticket authorship, board reconciliation" },
|
|
708
|
-
{ value: "dev", label: "Developer (dev)", hint: "implements tickets" },
|
|
709
|
-
{ value: "review", label: "Reviewer (review)", hint: "adversarial code review" },
|
|
710
|
-
{ value: "ops", label: "Ops (ops)", hint: "deploy / infra" },
|
|
711
|
-
{ value: "qa", label: "QA (qa)", hint: "test authorship + verification" }
|
|
712
|
-
];
|
|
713
|
-
var TICKET_PROVIDERS = [
|
|
714
|
-
{ value: "plane", label: "Plane", hint: "self-hosted at plane.delo.sh (default)" },
|
|
715
|
-
{ value: "linear", label: "Linear", hint: "team board (created in Linear UI)" },
|
|
716
|
-
{ value: "trello", label: "Trello", hint: "board = project" }
|
|
717
|
-
];
|
|
718
705
|
function deriveAgentId(repo, role) {
|
|
719
706
|
return `${repo}-${role}`.toLowerCase();
|
|
720
707
|
}
|
|
@@ -735,19 +722,18 @@ var PromptForAgentConfig = class extends Command {
|
|
|
735
722
|
async invoke() {
|
|
736
723
|
const ctx = this.context;
|
|
737
724
|
const defaultRepo = basename(ctx.targetDir).toLowerCase();
|
|
738
|
-
|
|
725
|
+
ctx.targetRepo = (ctx.targetRepo ?? defaultRepo).toLowerCase();
|
|
726
|
+
ctx.role ??= "pm";
|
|
727
|
+
ctx.agentPurpose ??= `${ctx.role} agent for ${ctx.targetRepo}`;
|
|
728
|
+
ctx.soulTone ??= "direct";
|
|
729
|
+
ctx.modelProvider ??= "";
|
|
730
|
+
ctx.modelName ??= "";
|
|
731
|
+
ctx.ticketProvider ??= detectTicketProvider(ctx.targetDir) ?? "plane";
|
|
732
|
+
ctx.skipEmail ??= true;
|
|
733
|
+
ctx.agentId = deriveAgentId(ctx.targetRepo, ctx.role);
|
|
734
|
+
ctx.profileName = deriveProfileName(ctx.targetRepo, ctx.role);
|
|
739
735
|
if (ctx.yes) {
|
|
740
|
-
ctx.targetRepo = (ctx.targetRepo ?? defaultRepo).toLowerCase();
|
|
741
|
-
ctx.role ??= defaultRole;
|
|
742
|
-
ctx.agentPurpose ??= `${ctx.role} agent for ${ctx.targetRepo}`;
|
|
743
|
-
ctx.soulTone ??= "direct";
|
|
744
|
-
ctx.modelProvider ??= "";
|
|
745
|
-
ctx.modelName ??= "";
|
|
746
|
-
ctx.ticketProvider ??= detectTicketProvider(ctx.targetDir) ?? "plane";
|
|
747
736
|
ctx.skipTelegram ??= true;
|
|
748
|
-
ctx.skipEmail ??= true;
|
|
749
|
-
ctx.agentId = deriveAgentId(ctx.targetRepo, ctx.role);
|
|
750
|
-
ctx.profileName = deriveProfileName(ctx.targetRepo, ctx.role);
|
|
751
737
|
return {
|
|
752
738
|
success: true,
|
|
753
739
|
message: this.formatMessage(
|
|
@@ -755,96 +741,19 @@ var PromptForAgentConfig = class extends Command {
|
|
|
755
741
|
)
|
|
756
742
|
};
|
|
757
743
|
}
|
|
758
|
-
p.intro("\u2695 hermes-agent \xB7
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
placeholder: defaultRepo,
|
|
763
|
-
initialValue: defaultRepo,
|
|
764
|
-
validate: (v) => v && v.trim() ? void 0 : "required"
|
|
765
|
-
});
|
|
766
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
767
|
-
ctx.targetRepo = String(answer).trim().toLowerCase();
|
|
768
|
-
}
|
|
769
|
-
if (!ctx.role) {
|
|
770
|
-
const answer = await p.select({
|
|
771
|
-
message: "Role",
|
|
772
|
-
options: ROLE_CHOICES.map((r) => ({ value: r.value, label: r.label, hint: r.hint })),
|
|
773
|
-
initialValue: defaultRole
|
|
774
|
-
});
|
|
775
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
776
|
-
ctx.role = String(answer).trim();
|
|
777
|
-
}
|
|
778
|
-
if (ctx.ticketProvider === void 0) {
|
|
779
|
-
const detected = detectTicketProvider(ctx.targetDir);
|
|
780
|
-
const answer = await p.select({
|
|
781
|
-
message: "Ticket board provider",
|
|
782
|
-
options: TICKET_PROVIDERS.map((t) => ({
|
|
783
|
-
value: t.value,
|
|
784
|
-
label: t.label,
|
|
785
|
-
hint: t.value === detected ? `${t.hint} \u2014 current .project.json` : t.hint
|
|
786
|
-
})),
|
|
787
|
-
initialValue: detected ?? "plane"
|
|
788
|
-
});
|
|
789
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
790
|
-
ctx.ticketProvider = answer;
|
|
791
|
-
}
|
|
792
|
-
if (!ctx.agentPurpose) {
|
|
793
|
-
const answer = await p.text({
|
|
794
|
-
message: "One-line purpose",
|
|
795
|
-
placeholder: `${ctx.role} agent for ${ctx.targetRepo}`,
|
|
796
|
-
initialValue: `${ctx.role} agent for ${ctx.targetRepo}`
|
|
797
|
-
});
|
|
798
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
799
|
-
ctx.agentPurpose = String(answer).trim();
|
|
800
|
-
}
|
|
801
|
-
if (!ctx.soulTone) {
|
|
802
|
-
const answer = await p.select({
|
|
803
|
-
message: "Personality tone",
|
|
804
|
-
options: SOUL_TONES.map((t) => ({
|
|
805
|
-
value: t,
|
|
806
|
-
label: t,
|
|
807
|
-
hint: t === "direct" ? "decision-forward, no preamble (default)" : t === "terse" ? "minimum words, conclusion-first" : t === "playful" ? "warm, mildly funny" : "precise, structured"
|
|
808
|
-
})),
|
|
809
|
-
initialValue: "direct"
|
|
810
|
-
});
|
|
811
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
812
|
-
ctx.soulTone = answer;
|
|
813
|
-
}
|
|
814
|
-
if (ctx.modelProvider === void 0) {
|
|
815
|
-
const answer = await p.text({
|
|
816
|
-
message: "Provider override (empty = inherit shared default profile)",
|
|
817
|
-
placeholder: ""
|
|
818
|
-
});
|
|
819
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
820
|
-
ctx.modelProvider = String(answer).trim();
|
|
821
|
-
}
|
|
822
|
-
if (ctx.modelName === void 0) {
|
|
823
|
-
const answer = await p.text({
|
|
824
|
-
message: "Model name override (empty = inherit shared default profile)",
|
|
825
|
-
placeholder: ""
|
|
826
|
-
});
|
|
827
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
828
|
-
ctx.modelName = String(answer).trim();
|
|
829
|
-
}
|
|
744
|
+
p.intro("\u2695 hermes-agent \xB7 provision the PM agent for this repo");
|
|
745
|
+
p.log.info(
|
|
746
|
+
`agent ${ctx.agentId} \xB7 board ${ctx.ticketProvider} \xB7 tone ${ctx.soulTone}`
|
|
747
|
+
);
|
|
830
748
|
if (ctx.skipTelegram === void 0) {
|
|
749
|
+
const botHandle = `${ctx.targetRepo.replace(/-/g, "_")}_${ctx.role}_bot`;
|
|
831
750
|
const wire = await p.confirm({
|
|
832
|
-
message: `Wire up the Telegram bot (@${
|
|
751
|
+
message: `Wire up the Telegram bot (@${botHandle}) now?`,
|
|
833
752
|
initialValue: true
|
|
834
753
|
});
|
|
835
754
|
if (p.isCancel(wire)) return this.cancelled();
|
|
836
755
|
ctx.skipTelegram = !wire;
|
|
837
756
|
}
|
|
838
|
-
if (ctx.skipEmail === void 0) {
|
|
839
|
-
const wire = await p.confirm({
|
|
840
|
-
message: `Provision the delo.sh email address (${ctx.targetRepo}-${ctx.role}@delo.sh) now?`,
|
|
841
|
-
initialValue: true
|
|
842
|
-
});
|
|
843
|
-
if (p.isCancel(wire)) return this.cancelled();
|
|
844
|
-
ctx.skipEmail = !wire;
|
|
845
|
-
}
|
|
846
|
-
ctx.agentId = deriveAgentId(ctx.targetRepo, ctx.role);
|
|
847
|
-
ctx.profileName = deriveProfileName(ctx.targetRepo, ctx.role);
|
|
848
757
|
return {
|
|
849
758
|
success: true,
|
|
850
759
|
message: this.formatMessage(
|
|
@@ -1119,7 +1028,7 @@ var WireEmail = class extends Command {
|
|
|
1119
1028
|
async invoke() {
|
|
1120
1029
|
const ctx = this.context;
|
|
1121
1030
|
if (ctx.skipEmail) {
|
|
1122
|
-
return { success: true, message: "
|
|
1031
|
+
return { success: true, message: "" };
|
|
1123
1032
|
}
|
|
1124
1033
|
if (ctx.dryRun) {
|
|
1125
1034
|
return { success: true, message: this.formatMessage("Would create CF Email Routing rule") };
|
|
@@ -1227,7 +1136,7 @@ var PrintHermesSummary = class extends Command {
|
|
|
1227
1136
|
lines.push(`role dir ${ctx.roleDir}`);
|
|
1228
1137
|
lines.push(`runtime gh:${runtimeRepo}`);
|
|
1229
1138
|
lines.push(`telegram @${botHandle}${skipTelegram ? " (NOT yet wired)" : ""}`);
|
|
1230
|
-
lines.push(`email ${email}
|
|
1139
|
+
if (!skipEmail) lines.push(`email ${email}`);
|
|
1231
1140
|
lines.push("");
|
|
1232
1141
|
lines.push("Start daemons:");
|
|
1233
1142
|
lines.push(` systemctl --user start ${csm}`);
|
|
@@ -1240,11 +1149,10 @@ var PrintHermesSummary = class extends Command {
|
|
|
1240
1149
|
lines.push("");
|
|
1241
1150
|
lines.push("Talk locally:");
|
|
1242
1151
|
lines.push(` ${ctx.roleDir}/hermes chat "status"`);
|
|
1243
|
-
if (skipTelegram
|
|
1152
|
+
if (skipTelegram) {
|
|
1244
1153
|
lines.push("");
|
|
1245
|
-
lines.push("
|
|
1246
|
-
|
|
1247
|
-
if (skipEmail) lines.push(" pjangler hermes-agent --skip-email=false # wire just email");
|
|
1154
|
+
lines.push("Wire Telegram later:");
|
|
1155
|
+
lines.push(" pjangler hermes-agent # re-run and answer yes when asked");
|
|
1248
1156
|
}
|
|
1249
1157
|
p5.note(lines.join("\n"), `Provisioned ${agentId}`);
|
|
1250
1158
|
p5.outro("Done.");
|
|
@@ -1707,10 +1615,10 @@ function writeText(path, content) {
|
|
|
1707
1615
|
ensureParent(path);
|
|
1708
1616
|
writeFileSync4(path, content);
|
|
1709
1617
|
}
|
|
1710
|
-
function tryParseJson(
|
|
1711
|
-
if (!
|
|
1618
|
+
function tryParseJson(text2) {
|
|
1619
|
+
if (!text2) return null;
|
|
1712
1620
|
try {
|
|
1713
|
-
return JSON.parse(
|
|
1621
|
+
return JSON.parse(text2);
|
|
1714
1622
|
} catch {
|
|
1715
1623
|
return null;
|
|
1716
1624
|
}
|
|
@@ -1769,9 +1677,9 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
|
1769
1677
|
}
|
|
1770
1678
|
return { changedFiles: [], details: [], blocked: "AGENTS.md missing and no CLAUDE.md, GEMINI.md, or README.md source exists" };
|
|
1771
1679
|
}
|
|
1772
|
-
function yamlGet(
|
|
1680
|
+
function yamlGet(text2, keyPath) {
|
|
1773
1681
|
const parts = keyPath.split(".");
|
|
1774
|
-
const lines =
|
|
1682
|
+
const lines = text2.split("\n");
|
|
1775
1683
|
let start = 0;
|
|
1776
1684
|
let indent = 0;
|
|
1777
1685
|
for (let idx = 0; idx < parts.length; idx += 1) {
|
|
@@ -1806,25 +1714,25 @@ function discoverRoles(repoRoot) {
|
|
|
1806
1714
|
const roleDir = join10(rolesDir, entry.name);
|
|
1807
1715
|
const roleYamlPath = join10(roleDir, "role.yaml");
|
|
1808
1716
|
if (!existsSync7(roleYamlPath)) return null;
|
|
1809
|
-
const
|
|
1810
|
-
const runtimeRepoRaw = yamlGet(
|
|
1717
|
+
const text2 = readText(roleYamlPath);
|
|
1718
|
+
const runtimeRepoRaw = yamlGet(text2, "runtime.github_repo");
|
|
1811
1719
|
return {
|
|
1812
|
-
role: yamlGet(
|
|
1720
|
+
role: yamlGet(text2, "role") || entry.name,
|
|
1813
1721
|
roleDir,
|
|
1814
1722
|
roleYamlPath,
|
|
1815
|
-
repo: yamlGet(
|
|
1816
|
-
agentId: yamlGet(
|
|
1817
|
-
profileName: yamlGet(
|
|
1818
|
-
displayName: yamlGet(
|
|
1819
|
-
purpose: yamlGet(
|
|
1820
|
-
botHandle: yamlGet(
|
|
1723
|
+
repo: yamlGet(text2, "repo"),
|
|
1724
|
+
agentId: yamlGet(text2, "agent_id"),
|
|
1725
|
+
profileName: yamlGet(text2, "profile") || yamlGet(text2, "agent_id"),
|
|
1726
|
+
displayName: yamlGet(text2, "display_name"),
|
|
1727
|
+
purpose: yamlGet(text2, "purpose"),
|
|
1728
|
+
botHandle: yamlGet(text2, "telegram.bot_username"),
|
|
1821
1729
|
runtimeRepo: runtimeRepoRaw.includes("/") ? runtimeRepoRaw.split("/").slice(-1)[0] ?? runtimeRepoRaw : runtimeRepoRaw,
|
|
1822
|
-
runtimeOwner: yamlGet(
|
|
1823
|
-
planeWorkspace: yamlGet(
|
|
1824
|
-
ticketProviderName: yamlGet(
|
|
1825
|
-
ticketProviderBoardId: yamlGet(
|
|
1826
|
-
ticketProviderBoardUrl: yamlGet(
|
|
1827
|
-
ticketProviderIdentifier: yamlGet(
|
|
1730
|
+
runtimeOwner: yamlGet(text2, "runtime.github_owner"),
|
|
1731
|
+
planeWorkspace: yamlGet(text2, "ticket_provider.workspace") || yamlGet(text2, "plane.workspace"),
|
|
1732
|
+
ticketProviderName: yamlGet(text2, "ticket_provider.name"),
|
|
1733
|
+
ticketProviderBoardId: yamlGet(text2, "ticket_provider.board_id"),
|
|
1734
|
+
ticketProviderBoardUrl: yamlGet(text2, "ticket_provider.board_url"),
|
|
1735
|
+
ticketProviderIdentifier: yamlGet(text2, "plane.identifier")
|
|
1828
1736
|
};
|
|
1829
1737
|
}).filter((value) => Boolean(value));
|
|
1830
1738
|
}
|
|
@@ -1869,19 +1777,19 @@ function templateVersionFilesConf(ctx, repoRoot) {
|
|
|
1869
1777
|
const packageJson = join10(repoRoot, "package.json");
|
|
1870
1778
|
return existsSync7(packageJson) ? "# mise-versioning manifest: <type> <path>\n# types: json toml cargo csproj gradle plain gittag\njson package.json\ngittag .\n" : "# mise-versioning manifest: <type> <path>\n# types: json toml cargo csproj gradle plain gittag\ngittag .\n";
|
|
1871
1779
|
}
|
|
1872
|
-
function replaceOrAppendManagedBlock(
|
|
1873
|
-
if (startMarker.test(
|
|
1874
|
-
return
|
|
1780
|
+
function replaceOrAppendManagedBlock(text2, startMarker, block, beforePattern) {
|
|
1781
|
+
if (startMarker.test(text2)) {
|
|
1782
|
+
return text2.replace(/# >>> mise-versioning >>>[\s\S]*?# <<< mise-versioning <<</, block);
|
|
1875
1783
|
}
|
|
1876
1784
|
if (beforePattern) {
|
|
1877
|
-
const match =
|
|
1785
|
+
const match = text2.match(beforePattern);
|
|
1878
1786
|
if (match && typeof match.index === "number") {
|
|
1879
|
-
return `${
|
|
1787
|
+
return `${text2.slice(0, match.index).replace(/\s*$/, "\n\n")}${block}
|
|
1880
1788
|
|
|
1881
|
-
${
|
|
1789
|
+
${text2.slice(match.index)}`;
|
|
1882
1790
|
}
|
|
1883
1791
|
}
|
|
1884
|
-
return `${
|
|
1792
|
+
return `${text2.replace(/\s*$/, "")}
|
|
1885
1793
|
|
|
1886
1794
|
${block}
|
|
1887
1795
|
`;
|
|
@@ -1895,18 +1803,18 @@ function requiredMisePathEntries(ctx) {
|
|
|
1895
1803
|
}
|
|
1896
1804
|
return required;
|
|
1897
1805
|
}
|
|
1898
|
-
function upsertMisePath(
|
|
1806
|
+
function upsertMisePath(text2, required = BASE_MISE_PATH_ENTRIES) {
|
|
1899
1807
|
const render = (values) => `_.path = [${values.map((value) => JSON.stringify(value)).join(", ")}]`;
|
|
1900
|
-
const envMatch =
|
|
1808
|
+
const envMatch = text2.match(/(^|\n)(\[env\][\s\S]*?)(?=\n\[[^\]]+\]|$)/);
|
|
1901
1809
|
if (!envMatch || typeof envMatch.index !== "number") {
|
|
1902
1810
|
return `[env]
|
|
1903
1811
|
${render(required)}
|
|
1904
1812
|
|
|
1905
|
-
${
|
|
1813
|
+
${text2.replace(/^\s+/, "")}`;
|
|
1906
1814
|
}
|
|
1907
|
-
const prefix =
|
|
1815
|
+
const prefix = text2.slice(0, envMatch.index + envMatch[1].length);
|
|
1908
1816
|
const section = envMatch[2];
|
|
1909
|
-
const suffix =
|
|
1817
|
+
const suffix = text2.slice(envMatch.index + envMatch[1].length + section.length);
|
|
1910
1818
|
const pathLine = section.match(/^_\.path\s*=\s*\[([^\]]*)\]\s*$/m);
|
|
1911
1819
|
if (!pathLine) {
|
|
1912
1820
|
return `${prefix}${section.replace(/\n?$/, "\n")}${render(required)}${suffix}`;
|
|
@@ -1917,11 +1825,11 @@ ${text3.replace(/^\s+/, "")}`;
|
|
|
1917
1825
|
if (!merged.includes(value)) merged.push(value);
|
|
1918
1826
|
}
|
|
1919
1827
|
const nextLine = render(merged);
|
|
1920
|
-
if (pathLine[0] === nextLine) return
|
|
1828
|
+
if (pathLine[0] === nextLine) return text2;
|
|
1921
1829
|
return `${prefix}${section.replace(pathLine[0], nextLine)}${suffix}`;
|
|
1922
1830
|
}
|
|
1923
|
-
function removeTomlSection(
|
|
1924
|
-
const lines =
|
|
1831
|
+
function removeTomlSection(text2, headerPattern, marker, options) {
|
|
1832
|
+
const lines = text2.split("\n");
|
|
1925
1833
|
let start = -1;
|
|
1926
1834
|
let end = -1;
|
|
1927
1835
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -1946,7 +1854,7 @@ function removeTomlSection(text3, headerPattern, marker, options) {
|
|
|
1946
1854
|
if (end === -1) end = lines.length;
|
|
1947
1855
|
break;
|
|
1948
1856
|
}
|
|
1949
|
-
if (start === -1) return
|
|
1857
|
+
if (start === -1) return text2;
|
|
1950
1858
|
if (options?.includePrecedingComments) {
|
|
1951
1859
|
while (start > 0 && lines[start - 1].trim().startsWith("#")) {
|
|
1952
1860
|
start--;
|
|
@@ -1955,22 +1863,22 @@ function removeTomlSection(text3, headerPattern, marker, options) {
|
|
|
1955
1863
|
const result = lines.slice(0, start).concat(lines.slice(end)).join("\n");
|
|
1956
1864
|
return result.replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "\n");
|
|
1957
1865
|
}
|
|
1958
|
-
function insertTomlBlockBeforeVersioning(
|
|
1959
|
-
const versioningIndex =
|
|
1866
|
+
function insertTomlBlockBeforeVersioning(text2, block) {
|
|
1867
|
+
const versioningIndex = text2.indexOf("# >>> mise-versioning >>>");
|
|
1960
1868
|
if (versioningIndex >= 0) {
|
|
1961
|
-
return `${
|
|
1869
|
+
return `${text2.slice(0, versioningIndex).replace(/\s*$/, "\n\n")}${block}
|
|
1962
1870
|
|
|
1963
|
-
${
|
|
1871
|
+
${text2.slice(versioningIndex)}`;
|
|
1964
1872
|
}
|
|
1965
|
-
return `${
|
|
1873
|
+
return `${text2.replace(/\s*$/, "")}
|
|
1966
1874
|
|
|
1967
1875
|
${block}
|
|
1968
1876
|
`;
|
|
1969
1877
|
}
|
|
1970
|
-
function extractTomlStrings(
|
|
1878
|
+
function extractTomlStrings(text2) {
|
|
1971
1879
|
const values = [];
|
|
1972
1880
|
const stringPattern = /"((?:\\.|[^"\\])*)"|'([^']*)'/g;
|
|
1973
|
-
for (const match of
|
|
1881
|
+
for (const match of text2.matchAll(stringPattern)) {
|
|
1974
1882
|
if (match[1] !== void 0) {
|
|
1975
1883
|
try {
|
|
1976
1884
|
values.push(JSON.parse(`"${match[1]}"`));
|
|
@@ -1994,10 +1902,10 @@ function renderHookEntries(entries, indent = "") {
|
|
|
1994
1902
|
`${indent}]`
|
|
1995
1903
|
];
|
|
1996
1904
|
}
|
|
1997
|
-
function upsertLinkAgentfilesHooks(
|
|
1998
|
-
const lines =
|
|
1905
|
+
function upsertLinkAgentfilesHooks(text2) {
|
|
1906
|
+
const lines = text2.split("\n");
|
|
1999
1907
|
const hooksStart = lines.findIndex((line) => /^\[hooks\]$/.test(line.trim()));
|
|
2000
|
-
if (hooksStart === -1) return insertTomlBlockBeforeVersioning(
|
|
1908
|
+
if (hooksStart === -1) return insertTomlBlockBeforeVersioning(text2, LINK_AGENTFILES_HOOKS_BLOCK);
|
|
2001
1909
|
let hooksEnd = lines.length;
|
|
2002
1910
|
for (let i = hooksStart + 1; i < lines.length; i++) {
|
|
2003
1911
|
if (/^\[[^\]]+\]/.test(lines[i].trim())) {
|
|
@@ -2031,8 +1939,8 @@ function upsertLinkAgentfilesHooks(text3) {
|
|
|
2031
1939
|
}
|
|
2032
1940
|
return lines.slice(0, hooksStart + 1).concat(rendered, lines.slice(hooksStart + 1)).join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "\n");
|
|
2033
1941
|
}
|
|
2034
|
-
function upsertLinkAgentfilesBlock(
|
|
2035
|
-
const withPath = upsertMisePath(
|
|
1942
|
+
function upsertLinkAgentfilesBlock(text2, ctx) {
|
|
1943
|
+
const withPath = upsertMisePath(text2, requiredMisePathEntries(ctx));
|
|
2036
1944
|
if (withPath.includes(LINK_AGENTFILES_BLOCK)) return withPath;
|
|
2037
1945
|
let cleaned = removeTomlSection(withPath, /^\[tasks\.link-agentfiles\]$/, /link-agentfiles/, { includePrecedingComments: false });
|
|
2038
1946
|
cleaned = removeTomlSection(cleaned, /^\[\[watch_files\]\]$/, /AGENTS\.md/, { includePrecedingComments: false });
|
|
@@ -2259,9 +2167,9 @@ ${block}`) : `${current.replace(/\s*$/, "\n")}${block}`;
|
|
|
2259
2167
|
return path;
|
|
2260
2168
|
}
|
|
2261
2169
|
function profileMetaInheritsDefault(path) {
|
|
2262
|
-
const
|
|
2170
|
+
const text2 = safeReadText(path);
|
|
2263
2171
|
return Boolean(
|
|
2264
|
-
|
|
2172
|
+
text2 && /^config:\s*$/m.test(text2) && /^\s+inherit_from:\s*default\s*$/m.test(text2) && /^\s+save_mode:\s*delta\s*$/m.test(text2)
|
|
2265
2173
|
);
|
|
2266
2174
|
}
|
|
2267
2175
|
function upsertInheritedProfileMeta(path, changedFiles, dryRun) {
|
|
@@ -2319,17 +2227,17 @@ var RULES = [
|
|
|
2319
2227
|
if (!existsSync7(misePath)) {
|
|
2320
2228
|
return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
|
|
2321
2229
|
}
|
|
2322
|
-
const
|
|
2230
|
+
const text2 = readText(misePath);
|
|
2323
2231
|
const details = [];
|
|
2324
2232
|
const linkAgentfilesPath = join10(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
2325
2233
|
if (!existsSync7(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
|
|
2326
|
-
const pathValues = [...(
|
|
2234
|
+
const pathValues = [...(text2.match(/^_\.path\s*=\s*\[([^\]]*)\]/m)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
|
2327
2235
|
const missingPathValues = requiredMisePathEntries(ctx).filter((value) => !pathValues.includes(value));
|
|
2328
2236
|
if (missingPathValues.length) details.push(`[env]._.path should include ${missingPathValues.join(", ")}`);
|
|
2329
|
-
if (!
|
|
2330
|
-
if (!
|
|
2331
|
-
if (!
|
|
2332
|
-
if (!
|
|
2237
|
+
if (!text2.includes('"{{config_root}}/.mise/scripts/link-agentfiles.sh"')) details.push("link-agentfiles must use raw {{config_root}} guard");
|
|
2238
|
+
if (!text2.includes("op inject -i .env.op > .env")) details.push("[hooks].enter must materialize .env from .env.op");
|
|
2239
|
+
if (!text2.includes('patterns = ["AGENTS.md"]')) details.push("watch_files must monitor AGENTS.md");
|
|
2240
|
+
if (!text2.includes('task = "link-agentfiles"')) details.push("watch_files must dispatch link-agentfiles task");
|
|
2333
2241
|
return {
|
|
2334
2242
|
id: "mise.config-root",
|
|
2335
2243
|
title: "mise config_root + AGENTS link hooks",
|
|
@@ -2352,12 +2260,12 @@ var RULES = [
|
|
|
2352
2260
|
return { id: finding.id, title: finding.title, status: "applied", summary: "Would initialize mise.toml from generated-project template", changedFiles, details };
|
|
2353
2261
|
}
|
|
2354
2262
|
}
|
|
2355
|
-
let
|
|
2356
|
-
const next = upsertLinkAgentfilesBlock(
|
|
2357
|
-
if (next !==
|
|
2263
|
+
let text2 = readText(path);
|
|
2264
|
+
const next = upsertLinkAgentfilesBlock(text2, ctx);
|
|
2265
|
+
if (next !== text2) {
|
|
2358
2266
|
if (!changedFiles.includes(path)) changedFiles.push(path);
|
|
2359
2267
|
if (!ctx.dryRun) writeText(path, next);
|
|
2360
|
-
|
|
2268
|
+
text2 = next;
|
|
2361
2269
|
}
|
|
2362
2270
|
const linkAgentfilesPath = join10(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
2363
2271
|
const expectedScript = templateLinkAgentfilesScript(ctx);
|
|
@@ -2389,8 +2297,8 @@ var RULES = [
|
|
|
2389
2297
|
const misePath = join10(ctx.repoRoot, "mise.toml");
|
|
2390
2298
|
const versioningPath = join10(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
2391
2299
|
const manifestPath = join10(ctx.repoRoot, ".mise", "version-files.conf");
|
|
2392
|
-
const
|
|
2393
|
-
if (!
|
|
2300
|
+
const text2 = safeReadText(misePath);
|
|
2301
|
+
if (!text2?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
|
|
2394
2302
|
if (!existsSync7(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
|
|
2395
2303
|
if (!existsSync7(manifestPath)) details.push(".mise/version-files.conf missing");
|
|
2396
2304
|
return {
|
|
@@ -2611,19 +2519,19 @@ var RULES = [
|
|
|
2611
2519
|
audit: (ctx) => {
|
|
2612
2520
|
const details = [];
|
|
2613
2521
|
const path = join10(ctx.repoRoot, ".copier-answers.yml");
|
|
2614
|
-
const
|
|
2522
|
+
const text2 = safeReadText(path);
|
|
2615
2523
|
const project = readProjectJson(ctx);
|
|
2616
|
-
if (!
|
|
2524
|
+
if (!text2) {
|
|
2617
2525
|
details.push(".copier-answers.yml missing");
|
|
2618
2526
|
} else {
|
|
2619
|
-
if (!
|
|
2620
|
-
if (!
|
|
2527
|
+
if (!text2.startsWith("# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY")) details.push("missing Copier overwrite warning header");
|
|
2528
|
+
if (!text2.includes("_src_path:")) details.push("_src_path missing");
|
|
2621
2529
|
if (project?.project_name) {
|
|
2622
|
-
const nameMatch =
|
|
2530
|
+
const nameMatch = text2.match(/project_name:\s*(.+)/);
|
|
2623
2531
|
if (!nameMatch || nameMatch[1]?.trim() !== String(project.project_name)) details.push("project_name drift between .copier-answers.yml and .project.json");
|
|
2624
2532
|
}
|
|
2625
2533
|
if (project?.project_description) {
|
|
2626
|
-
const descMatch =
|
|
2534
|
+
const descMatch = text2.match(/project_description:\s*([\s\S]*?)(?=\n\w|$)/);
|
|
2627
2535
|
const yamlDesc = descMatch?.[1]?.replace(/\n\s+/g, " ").trim() ?? "";
|
|
2628
2536
|
if (yamlDesc !== String(project.project_description)) details.push("project_description drift between .copier-answers.yml and .project.json");
|
|
2629
2537
|
}
|
|
@@ -2640,16 +2548,16 @@ var RULES = [
|
|
|
2640
2548
|
migrate: (ctx, finding) => {
|
|
2641
2549
|
const changedFiles = [];
|
|
2642
2550
|
const project = canonicalProjectJson(ctx);
|
|
2643
|
-
const
|
|
2551
|
+
const text2 = `# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
|
|
2644
2552
|
_src_path: ${join10(ctx.pjanglerRoot, "templates", "commonproject")}
|
|
2645
2553
|
project_description: ${String(project.project_description)}
|
|
2646
2554
|
project_name: ${String(project.project_name)}
|
|
2647
2555
|
ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
2648
2556
|
`;
|
|
2649
2557
|
const path = join10(ctx.repoRoot, ".copier-answers.yml");
|
|
2650
|
-
if (safeReadText(path) !==
|
|
2558
|
+
if (safeReadText(path) !== text2) {
|
|
2651
2559
|
changedFiles.push(path);
|
|
2652
|
-
if (!ctx.dryRun) writeText(path,
|
|
2560
|
+
if (!ctx.dryRun) writeText(path, text2);
|
|
2653
2561
|
}
|
|
2654
2562
|
return {
|
|
2655
2563
|
id: finding.id,
|