@delorenj/pjangler 1.2.2 → 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/mcp-server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/mcp-server.ts
|
|
4
4
|
import { existsSync as existsSync9, statSync as statSync2 } from "node:fs";
|
|
5
|
-
import { basename as basename4, dirname as dirname8, join as
|
|
5
|
+
import { basename as basename4, dirname as dirname8, join as join12, resolve as resolve3 } from "node:path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -44,6 +44,63 @@ var Command = class {
|
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
// src/utils/style.ts
|
|
48
|
+
var env = process.env;
|
|
49
|
+
function detectColor() {
|
|
50
|
+
if ("NO_COLOR" in env && env.NO_COLOR !== "") return false;
|
|
51
|
+
const force = env.FORCE_COLOR;
|
|
52
|
+
if (force === "0" || force === "false") return false;
|
|
53
|
+
if (force !== void 0 && force !== "") return true;
|
|
54
|
+
if (env.TERM === "dumb") return false;
|
|
55
|
+
return Boolean(process.stdout.isTTY);
|
|
56
|
+
}
|
|
57
|
+
var colorEnabled = detectColor();
|
|
58
|
+
function sgr(open, close) {
|
|
59
|
+
const prefix = `\x1B[${open}m`;
|
|
60
|
+
const suffix = `\x1B[${close}m`;
|
|
61
|
+
return (value) => colorEnabled ? `${prefix}${value}${suffix}` : String(value);
|
|
62
|
+
}
|
|
63
|
+
var bold = sgr(1, 22);
|
|
64
|
+
var dim = sgr(2, 22);
|
|
65
|
+
var italic = sgr(3, 23);
|
|
66
|
+
var underline = sgr(4, 24);
|
|
67
|
+
var red = sgr(31, 39);
|
|
68
|
+
var green = sgr(32, 39);
|
|
69
|
+
var yellow = sgr(33, 39);
|
|
70
|
+
var blue = sgr(34, 39);
|
|
71
|
+
var magenta = sgr(35, 39);
|
|
72
|
+
var cyan = sgr(36, 39);
|
|
73
|
+
var gray = sgr(90, 39);
|
|
74
|
+
var glyph = {
|
|
75
|
+
pass: "\u2714",
|
|
76
|
+
fail: "\u2716",
|
|
77
|
+
warn: "\u26A0",
|
|
78
|
+
skip: "\u25CB",
|
|
79
|
+
info: "\u2139",
|
|
80
|
+
arrow: "\u21B3",
|
|
81
|
+
bullet: "\u2022",
|
|
82
|
+
dot: "\xB7",
|
|
83
|
+
add: "+",
|
|
84
|
+
chevron: "\u25B8",
|
|
85
|
+
pointer: "\u276F"
|
|
86
|
+
};
|
|
87
|
+
var STATUS_STYLES = {
|
|
88
|
+
pass: { glyph: glyph.pass, color: green, label: "pass" },
|
|
89
|
+
fail: { glyph: glyph.fail, color: red, label: "fail" },
|
|
90
|
+
warn: { glyph: glyph.warn, color: yellow, label: "warn" },
|
|
91
|
+
skip: { glyph: glyph.skip, color: gray, label: "skip" },
|
|
92
|
+
applied: { glyph: glyph.pass, color: green, label: "applied" },
|
|
93
|
+
noop: { glyph: glyph.skip, color: gray, label: "noop" },
|
|
94
|
+
blocked: { glyph: glyph.fail, color: red, label: "blocked" },
|
|
95
|
+
skipped: { glyph: glyph.skip, color: gray, label: "skipped" }
|
|
96
|
+
};
|
|
97
|
+
function statusStyle(status) {
|
|
98
|
+
return STATUS_STYLES[status] ?? { glyph: glyph.dot, color: dim, label: status };
|
|
99
|
+
}
|
|
100
|
+
function joinDot(fragments) {
|
|
101
|
+
return fragments.join(dim(` ${glyph.dot} `));
|
|
102
|
+
}
|
|
103
|
+
|
|
47
104
|
// src/recipes/Recipe.ts
|
|
48
105
|
var Recipe = class {
|
|
49
106
|
context;
|
|
@@ -56,26 +113,22 @@ var Recipe = class {
|
|
|
56
113
|
return this;
|
|
57
114
|
}
|
|
58
115
|
async execute() {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
116
|
+
const subsystem = this.constructor.name.replace("Recipe", "").toLowerCase();
|
|
117
|
+
const dryRun = this.context.dryRun;
|
|
118
|
+
console.log("");
|
|
119
|
+
console.log(` ${cyan(bold(glyph.chevron))} ${bold(`Initializing ${subsystem} subsystem`)}${dryRun ? ` ${dim(glyph.dot)} ${yellow("dry run")}` : ""}`);
|
|
120
|
+
console.log("");
|
|
65
121
|
for (const command of this.ingredients) {
|
|
66
122
|
const result = await command.invoke();
|
|
67
|
-
|
|
68
|
-
console.log(result.message);
|
|
69
|
-
} else {
|
|
70
|
-
console.log(result.message);
|
|
71
|
-
}
|
|
123
|
+
console.log(result.message.split("\n").map((line) => line ? ` ${line}` : line).join("\n"));
|
|
72
124
|
}
|
|
73
|
-
if (!
|
|
125
|
+
if (!dryRun) {
|
|
74
126
|
this.printNextSteps();
|
|
75
127
|
} else {
|
|
76
128
|
console.log("");
|
|
77
|
-
console.log("
|
|
78
|
-
console.log("
|
|
129
|
+
console.log(` ${green(glyph.pass)} ${dim("Dry-run complete \u2014 no files were modified.")}`);
|
|
130
|
+
console.log(` ${dim("Remove --dry-run to apply changes.")}`);
|
|
131
|
+
console.log("");
|
|
79
132
|
}
|
|
80
133
|
}
|
|
81
134
|
};
|
|
@@ -218,11 +271,111 @@ if __name__ == "__main__":
|
|
|
218
271
|
}
|
|
219
272
|
};
|
|
220
273
|
|
|
274
|
+
// src/commands/AddMiseCodegraphScript.ts
|
|
275
|
+
import { chmodSync } from "fs";
|
|
276
|
+
import { join as join2 } from "path";
|
|
277
|
+
var AddMiseCodegraphScript = class extends Command {
|
|
278
|
+
async invoke() {
|
|
279
|
+
const filePath = ".mise/scripts/codegraph.sh";
|
|
280
|
+
if (this.fileExists(filePath) && !this.context.force) {
|
|
281
|
+
return {
|
|
282
|
+
success: false,
|
|
283
|
+
message: this.formatMessage("\u26A0\uFE0F .mise/scripts/codegraph.sh already exists"),
|
|
284
|
+
filePath
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const content = `#!/usr/bin/env bash
|
|
288
|
+
# Mise enter hook: ensure the CodeGraph CLI is available and initialize the
|
|
289
|
+
# project index. If \`codegraph\` is not installed, this script installs it
|
|
290
|
+
# non-interactively into the project-local .mise/bin directory and retries.
|
|
291
|
+
#
|
|
292
|
+
# This is intended to run from a mise enter hook so onboarding a new host is
|
|
293
|
+
# fully automatic.
|
|
294
|
+
|
|
295
|
+
set -euo pipefail
|
|
296
|
+
|
|
297
|
+
REPO_ROOT="\${MISE_PROJECT_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)}"
|
|
298
|
+
PROJECT_BIN_DIR="$REPO_ROOT/.mise/bin"
|
|
299
|
+
mkdir -p "$PROJECT_BIN_DIR"
|
|
300
|
+
|
|
301
|
+
# Install the CodeGraph CLI into the project-local bin directory.
|
|
302
|
+
install_codegraph() {
|
|
303
|
+
echo "[mise] codegraph not found. Installing non-interactively..."
|
|
304
|
+
export CODEGRAPH_BIN_DIR="$PROJECT_BIN_DIR"
|
|
305
|
+
curl -fsSL https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh
|
|
306
|
+
|
|
307
|
+
if [ -x "$PROJECT_BIN_DIR/codegraph" ]; then
|
|
308
|
+
export PATH="$PROJECT_BIN_DIR:$PATH"
|
|
309
|
+
else
|
|
310
|
+
echo "[mise] codegraph install did not place a binary at $PROJECT_BIN_DIR/codegraph" >&2
|
|
311
|
+
return 1
|
|
312
|
+
fi
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
# Ensure a codegraph binary is available on PATH.
|
|
316
|
+
ensure_codegraph() {
|
|
317
|
+
if command -v codegraph >/dev/null 2>&1; then
|
|
318
|
+
return 0
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
# Check the project-local bin dir first (previous install from this hook).
|
|
322
|
+
if [ -x "$PROJECT_BIN_DIR/codegraph" ]; then
|
|
323
|
+
export PATH="$PROJECT_BIN_DIR:$PATH"
|
|
324
|
+
return 0
|
|
325
|
+
fi
|
|
326
|
+
|
|
327
|
+
# Check typical user-level install locations before fetching anything.
|
|
328
|
+
for d in "$HOME/.local/bin" "$HOME/.codegraph/current/bin"; do
|
|
329
|
+
if [ -x "$d/codegraph" ]; then
|
|
330
|
+
export PATH="$d:$PATH"
|
|
331
|
+
return 0
|
|
332
|
+
fi
|
|
333
|
+
done
|
|
334
|
+
|
|
335
|
+
install_codegraph
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
# Attempt to initialize the project graph. If the command is missing, install
|
|
339
|
+
# it and retry once.
|
|
340
|
+
init_project() {
|
|
341
|
+
local err_file
|
|
342
|
+
err_file="$(mktemp)"
|
|
343
|
+
trap 'rm -f "$err_file"' RETURN
|
|
344
|
+
|
|
345
|
+
if codegraph init -i "$REPO_ROOT" 2>"$err_file"; then
|
|
346
|
+
return 0
|
|
347
|
+
fi
|
|
348
|
+
|
|
349
|
+
# If the failure looks like a missing binary, install and retry.
|
|
350
|
+
if grep -qiE 'command not found|not installed|No such file|executable file not found' "$err_file" 2>/dev/null; then
|
|
351
|
+
ensure_codegraph
|
|
352
|
+
codegraph init -i "$REPO_ROOT"
|
|
353
|
+
return 0
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
cat "$err_file" >&2
|
|
357
|
+
return 1
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
init_project
|
|
361
|
+
`;
|
|
362
|
+
this.writeFile(filePath, content);
|
|
363
|
+
if (!this.context.dryRun) {
|
|
364
|
+
chmodSync(join2(this.context.targetDir, filePath), 493);
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
success: true,
|
|
368
|
+
message: this.formatMessage(this.context.dryRun ? "Would create .mise/scripts/codegraph.sh" : "\u2705 Created .mise/scripts/codegraph.sh"),
|
|
369
|
+
filePath
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
221
374
|
// src/recipes/MiseRecipe.ts
|
|
222
375
|
var MiseRecipe = class extends Recipe {
|
|
223
376
|
constructor(context) {
|
|
224
377
|
super(context);
|
|
225
|
-
this.addIngredient(AddMiseToml).addIngredient(AddDotenv).addIngredient(AddMiseTasksStructure).addIngredient(AddMiseBaseToml).addIngredient(AddMiseBaseScript);
|
|
378
|
+
this.addIngredient(AddMiseToml).addIngredient(AddDotenv).addIngredient(AddMiseTasksStructure).addIngredient(AddMiseBaseToml).addIngredient(AddMiseBaseScript).addIngredient(AddMiseCodegraphScript);
|
|
226
379
|
}
|
|
227
380
|
printNextSteps() {
|
|
228
381
|
console.log("\u{1F389} Mise subsystem initialized successfully!");
|
|
@@ -452,19 +605,19 @@ var NodeRecipe = class extends Recipe {
|
|
|
452
605
|
// src/commands/hermes/EnsureTemplateConfig.ts
|
|
453
606
|
import { homedir, platform } from "node:os";
|
|
454
607
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
455
|
-
import { join as
|
|
608
|
+
import { join as join3, dirname as dirname2 } from "node:path";
|
|
456
609
|
function resolveTemplateConfigPath() {
|
|
457
610
|
const fromEnv = process.env.HERMES_TEMPLATE_CONFIG;
|
|
458
611
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
459
612
|
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
460
|
-
const base = xdg && xdg.length ? xdg :
|
|
461
|
-
return
|
|
613
|
+
const base = xdg && xdg.length ? xdg : join3(homedir(), ".config");
|
|
614
|
+
return join3(base, "hermes-agent-template", "config.toml");
|
|
462
615
|
}
|
|
463
616
|
function detectHermesBin(home) {
|
|
464
617
|
const candidates = [
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
618
|
+
join3(home, "code", "hermes-agent", "venv", "bin", "hermes"),
|
|
619
|
+
join3(home, "code", "hermes-agent", ".venv", "bin", "hermes"),
|
|
620
|
+
join3(home, ".local", "bin", "hermes")
|
|
468
621
|
];
|
|
469
622
|
for (const c of candidates) {
|
|
470
623
|
if (existsSync2(c)) return c;
|
|
@@ -474,9 +627,9 @@ function detectHermesBin(home) {
|
|
|
474
627
|
function renderHostConfig() {
|
|
475
628
|
const home = homedir();
|
|
476
629
|
const hermesBin = detectHermesBin(home);
|
|
477
|
-
const hermesRepo =
|
|
478
|
-
const scaffoldDir =
|
|
479
|
-
const skillsDir =
|
|
630
|
+
const hermesRepo = join3(home, "code", "hermes-agent");
|
|
631
|
+
const scaffoldDir = join3(home, "code", "hermes-agent-template", "runtime-scaffold");
|
|
632
|
+
const skillsDir = join3(home, ".agents", "skills");
|
|
480
633
|
return `# hermes-agent-template \u2014 host configuration
|
|
481
634
|
# Bootstrapped by \`pjangler config bootstrap\` for $HOME=${home} (platform=${platform()}).
|
|
482
635
|
#
|
|
@@ -543,25 +696,12 @@ var EnsureTemplateConfig = class extends Command {
|
|
|
543
696
|
};
|
|
544
697
|
|
|
545
698
|
// src/commands/hermes/PromptForAgentConfig.ts
|
|
546
|
-
import { basename, join as
|
|
699
|
+
import { basename, join as join4 } from "node:path";
|
|
547
700
|
import { readFileSync } from "node:fs";
|
|
548
701
|
import * as p from "@clack/prompts";
|
|
549
702
|
|
|
550
703
|
// src/commands/hermes/types.ts
|
|
551
704
|
var HERMES_AGENT_TEMPLATE = "gh:delorenj/hermes-agent-template";
|
|
552
|
-
var SOUL_TONES = ["direct", "playful", "formal", "terse"];
|
|
553
|
-
var ROLE_CHOICES = [
|
|
554
|
-
{ value: "pm", label: "Project Manager (pm)", hint: "triage, planning, ticket authorship, board reconciliation" },
|
|
555
|
-
{ value: "dev", label: "Developer (dev)", hint: "implements tickets" },
|
|
556
|
-
{ value: "review", label: "Reviewer (review)", hint: "adversarial code review" },
|
|
557
|
-
{ value: "ops", label: "Ops (ops)", hint: "deploy / infra" },
|
|
558
|
-
{ value: "qa", label: "QA (qa)", hint: "test authorship + verification" }
|
|
559
|
-
];
|
|
560
|
-
var TICKET_PROVIDERS = [
|
|
561
|
-
{ value: "plane", label: "Plane", hint: "self-hosted at plane.delo.sh (default)" },
|
|
562
|
-
{ value: "linear", label: "Linear", hint: "team board (created in Linear UI)" },
|
|
563
|
-
{ value: "trello", label: "Trello", hint: "board = project" }
|
|
564
|
-
];
|
|
565
705
|
function deriveAgentId(repo, role) {
|
|
566
706
|
return `${repo}-${role}`.toLowerCase();
|
|
567
707
|
}
|
|
@@ -572,7 +712,7 @@ function deriveProfileName(repo, role) {
|
|
|
572
712
|
// src/commands/hermes/PromptForAgentConfig.ts
|
|
573
713
|
function detectTicketProvider(targetDir) {
|
|
574
714
|
try {
|
|
575
|
-
const t = JSON.parse(readFileSync(
|
|
715
|
+
const t = JSON.parse(readFileSync(join4(targetDir, ".project.json"), "utf8"))?.ticket_provider?.type;
|
|
576
716
|
return t === "plane" || t === "linear" || t === "trello" ? t : void 0;
|
|
577
717
|
} catch {
|
|
578
718
|
return void 0;
|
|
@@ -582,19 +722,18 @@ var PromptForAgentConfig = class extends Command {
|
|
|
582
722
|
async invoke() {
|
|
583
723
|
const ctx = this.context;
|
|
584
724
|
const defaultRepo = basename(ctx.targetDir).toLowerCase();
|
|
585
|
-
|
|
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);
|
|
586
735
|
if (ctx.yes) {
|
|
587
|
-
ctx.targetRepo = (ctx.targetRepo ?? defaultRepo).toLowerCase();
|
|
588
|
-
ctx.role ??= defaultRole;
|
|
589
|
-
ctx.agentPurpose ??= `${ctx.role} agent for ${ctx.targetRepo}`;
|
|
590
|
-
ctx.soulTone ??= "direct";
|
|
591
|
-
ctx.modelProvider ??= "";
|
|
592
|
-
ctx.modelName ??= "";
|
|
593
|
-
ctx.ticketProvider ??= detectTicketProvider(ctx.targetDir) ?? "plane";
|
|
594
736
|
ctx.skipTelegram ??= true;
|
|
595
|
-
ctx.skipEmail ??= true;
|
|
596
|
-
ctx.agentId = deriveAgentId(ctx.targetRepo, ctx.role);
|
|
597
|
-
ctx.profileName = deriveProfileName(ctx.targetRepo, ctx.role);
|
|
598
737
|
return {
|
|
599
738
|
success: true,
|
|
600
739
|
message: this.formatMessage(
|
|
@@ -602,96 +741,19 @@ var PromptForAgentConfig = class extends Command {
|
|
|
602
741
|
)
|
|
603
742
|
};
|
|
604
743
|
}
|
|
605
|
-
p.intro("\u2695 hermes-agent \xB7
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
placeholder: defaultRepo,
|
|
610
|
-
initialValue: defaultRepo,
|
|
611
|
-
validate: (v) => v && v.trim() ? void 0 : "required"
|
|
612
|
-
});
|
|
613
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
614
|
-
ctx.targetRepo = String(answer).trim().toLowerCase();
|
|
615
|
-
}
|
|
616
|
-
if (!ctx.role) {
|
|
617
|
-
const answer = await p.select({
|
|
618
|
-
message: "Role",
|
|
619
|
-
options: ROLE_CHOICES.map((r) => ({ value: r.value, label: r.label, hint: r.hint })),
|
|
620
|
-
initialValue: defaultRole
|
|
621
|
-
});
|
|
622
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
623
|
-
ctx.role = String(answer).trim();
|
|
624
|
-
}
|
|
625
|
-
if (ctx.ticketProvider === void 0) {
|
|
626
|
-
const detected = detectTicketProvider(ctx.targetDir);
|
|
627
|
-
const answer = await p.select({
|
|
628
|
-
message: "Ticket board provider",
|
|
629
|
-
options: TICKET_PROVIDERS.map((t) => ({
|
|
630
|
-
value: t.value,
|
|
631
|
-
label: t.label,
|
|
632
|
-
hint: t.value === detected ? `${t.hint} \u2014 current .project.json` : t.hint
|
|
633
|
-
})),
|
|
634
|
-
initialValue: detected ?? "plane"
|
|
635
|
-
});
|
|
636
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
637
|
-
ctx.ticketProvider = answer;
|
|
638
|
-
}
|
|
639
|
-
if (!ctx.agentPurpose) {
|
|
640
|
-
const answer = await p.text({
|
|
641
|
-
message: "One-line purpose",
|
|
642
|
-
placeholder: `${ctx.role} agent for ${ctx.targetRepo}`,
|
|
643
|
-
initialValue: `${ctx.role} agent for ${ctx.targetRepo}`
|
|
644
|
-
});
|
|
645
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
646
|
-
ctx.agentPurpose = String(answer).trim();
|
|
647
|
-
}
|
|
648
|
-
if (!ctx.soulTone) {
|
|
649
|
-
const answer = await p.select({
|
|
650
|
-
message: "Personality tone",
|
|
651
|
-
options: SOUL_TONES.map((t) => ({
|
|
652
|
-
value: t,
|
|
653
|
-
label: t,
|
|
654
|
-
hint: t === "direct" ? "decision-forward, no preamble (default)" : t === "terse" ? "minimum words, conclusion-first" : t === "playful" ? "warm, mildly funny" : "precise, structured"
|
|
655
|
-
})),
|
|
656
|
-
initialValue: "direct"
|
|
657
|
-
});
|
|
658
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
659
|
-
ctx.soulTone = answer;
|
|
660
|
-
}
|
|
661
|
-
if (ctx.modelProvider === void 0) {
|
|
662
|
-
const answer = await p.text({
|
|
663
|
-
message: "Provider override (empty = inherit shared default profile)",
|
|
664
|
-
placeholder: ""
|
|
665
|
-
});
|
|
666
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
667
|
-
ctx.modelProvider = String(answer).trim();
|
|
668
|
-
}
|
|
669
|
-
if (ctx.modelName === void 0) {
|
|
670
|
-
const answer = await p.text({
|
|
671
|
-
message: "Model name override (empty = inherit shared default profile)",
|
|
672
|
-
placeholder: ""
|
|
673
|
-
});
|
|
674
|
-
if (p.isCancel(answer)) return this.cancelled();
|
|
675
|
-
ctx.modelName = String(answer).trim();
|
|
676
|
-
}
|
|
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
|
+
);
|
|
677
748
|
if (ctx.skipTelegram === void 0) {
|
|
749
|
+
const botHandle = `${ctx.targetRepo.replace(/-/g, "_")}_${ctx.role}_bot`;
|
|
678
750
|
const wire = await p.confirm({
|
|
679
|
-
message: `Wire up the Telegram bot (@${
|
|
751
|
+
message: `Wire up the Telegram bot (@${botHandle}) now?`,
|
|
680
752
|
initialValue: true
|
|
681
753
|
});
|
|
682
754
|
if (p.isCancel(wire)) return this.cancelled();
|
|
683
755
|
ctx.skipTelegram = !wire;
|
|
684
756
|
}
|
|
685
|
-
if (ctx.skipEmail === void 0) {
|
|
686
|
-
const wire = await p.confirm({
|
|
687
|
-
message: `Provision the delo.sh email address (${ctx.targetRepo}-${ctx.role}@delo.sh) now?`,
|
|
688
|
-
initialValue: true
|
|
689
|
-
});
|
|
690
|
-
if (p.isCancel(wire)) return this.cancelled();
|
|
691
|
-
ctx.skipEmail = !wire;
|
|
692
|
-
}
|
|
693
|
-
ctx.agentId = deriveAgentId(ctx.targetRepo, ctx.role);
|
|
694
|
-
ctx.profileName = deriveProfileName(ctx.targetRepo, ctx.role);
|
|
695
757
|
return {
|
|
696
758
|
success: true,
|
|
697
759
|
message: this.formatMessage(
|
|
@@ -708,7 +770,7 @@ var PromptForAgentConfig = class extends Command {
|
|
|
708
770
|
// src/commands/hermes/RunCopierTemplate.ts
|
|
709
771
|
import { spawnSync } from "node:child_process";
|
|
710
772
|
import { homedir as homedir2 } from "node:os";
|
|
711
|
-
import { join as
|
|
773
|
+
import { join as join5, dirname as dirname3 } from "node:path";
|
|
712
774
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
|
|
713
775
|
import { fileURLToPath } from "node:url";
|
|
714
776
|
import * as p2 from "@clack/prompts";
|
|
@@ -720,8 +782,8 @@ function resolveVendoredTemplate(name) {
|
|
|
720
782
|
return void 0;
|
|
721
783
|
}
|
|
722
784
|
for (let i = 0; i < 8; i++) {
|
|
723
|
-
const candidate =
|
|
724
|
-
if (existsSync3(
|
|
785
|
+
const candidate = join5(dir, "templates", name);
|
|
786
|
+
if (existsSync3(join5(candidate, "copier.yml"))) return candidate;
|
|
725
787
|
const parent = dirname3(dir);
|
|
726
788
|
if (parent === dir) break;
|
|
727
789
|
dir = parent;
|
|
@@ -740,7 +802,7 @@ var RunCopierTemplate = class extends Command {
|
|
|
740
802
|
message: "PromptForAgentConfig must run before RunCopierTemplate (targetRepo/role unset)"
|
|
741
803
|
};
|
|
742
804
|
}
|
|
743
|
-
const roleDir =
|
|
805
|
+
const roleDir = join5(ctx.targetDir, "agents", "hermes", role);
|
|
744
806
|
ctx.roleDir = roleDir;
|
|
745
807
|
ctx.runtimeRepo = `delorenj/agent-hm-${targetRepo}-${role}`;
|
|
746
808
|
const which = spawnSync("which", ["copier"], { encoding: "utf8" });
|
|
@@ -750,7 +812,7 @@ var RunCopierTemplate = class extends Command {
|
|
|
750
812
|
message: "\u2717 copier not found on PATH. Install with: `uv tool install copier` or `pip install copier`"
|
|
751
813
|
};
|
|
752
814
|
}
|
|
753
|
-
if (existsSync3(
|
|
815
|
+
if (existsSync3(join5(roleDir, "role.yaml")) && !ctx.force) {
|
|
754
816
|
if (ctx.yes) {
|
|
755
817
|
ctx.force = true;
|
|
756
818
|
} else {
|
|
@@ -767,7 +829,7 @@ var RunCopierTemplate = class extends Command {
|
|
|
767
829
|
ctx.force = true;
|
|
768
830
|
}
|
|
769
831
|
}
|
|
770
|
-
const
|
|
832
|
+
const env2 = {
|
|
771
833
|
...process.env,
|
|
772
834
|
SKIP_TELEGRAM: "1",
|
|
773
835
|
SKIP_EMAIL: "1",
|
|
@@ -777,9 +839,9 @@ var RunCopierTemplate = class extends Command {
|
|
|
777
839
|
SKIP_BLOODBANK: ctx.skipBloodbank ? "1" : "0",
|
|
778
840
|
SKIP_SYSTEMD: ctx.skipSystemd ? "1" : "0"
|
|
779
841
|
};
|
|
780
|
-
const LOCAL_TEMPLATE =
|
|
842
|
+
const LOCAL_TEMPLATE = join5(homedir2(), "code", "hermes-agent-template");
|
|
781
843
|
const vendored = resolveVendoredTemplate("hermes-agent");
|
|
782
|
-
const templateSrc = process.env.PJANGLER_HERMES_TEMPLATE || vendored || (existsSync3(
|
|
844
|
+
const templateSrc = process.env.PJANGLER_HERMES_TEMPLATE || vendored || (existsSync3(join5(LOCAL_TEMPLATE, "copier.yml")) ? LOCAL_TEMPLATE : HERMES_AGENT_TEMPLATE);
|
|
783
845
|
const args = [
|
|
784
846
|
"copy",
|
|
785
847
|
templateSrc,
|
|
@@ -810,13 +872,13 @@ var RunCopierTemplate = class extends Command {
|
|
|
810
872
|
message: this.formatMessage(`Would run: copier ${args.join(" ")}`)
|
|
811
873
|
};
|
|
812
874
|
}
|
|
813
|
-
mkdirSync3(
|
|
875
|
+
mkdirSync3(join5(ctx.targetDir, "agents", "hermes"), { recursive: true });
|
|
814
876
|
const spinner4 = p2.spinner();
|
|
815
877
|
spinner4.start(`Running copier copy (target: agents/hermes/${role})`);
|
|
816
878
|
const result = spawnSync("copier", args, {
|
|
817
879
|
stdio: "inherit",
|
|
818
880
|
// pass the interactive output through; copier prints its own progress
|
|
819
|
-
env,
|
|
881
|
+
env: env2,
|
|
820
882
|
cwd: ctx.targetDir
|
|
821
883
|
});
|
|
822
884
|
spinner4.stop(result.status === 0 ? "\u2713 copier run complete" : "\u2717 copier failed");
|
|
@@ -835,7 +897,7 @@ var RunCopierTemplate = class extends Command {
|
|
|
835
897
|
|
|
836
898
|
// src/commands/hermes/WireTelegram.ts
|
|
837
899
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
838
|
-
import { join as
|
|
900
|
+
import { join as join6 } from "node:path";
|
|
839
901
|
import { existsSync as existsSync4, unlinkSync } from "node:fs";
|
|
840
902
|
import * as p3 from "@clack/prompts";
|
|
841
903
|
var WireTelegram = class extends Command {
|
|
@@ -924,14 +986,14 @@ var WireTelegram = class extends Command {
|
|
|
924
986
|
if (p3.isCancel(allowedAnswer)) {
|
|
925
987
|
return { success: false, message: "\u2717 Aborted; Telegram step deferred." };
|
|
926
988
|
}
|
|
927
|
-
const script =
|
|
989
|
+
const script = join6(roleDir, ".scripts", "30-telegram.sh");
|
|
928
990
|
if (!existsSync4(script)) {
|
|
929
991
|
return {
|
|
930
992
|
success: false,
|
|
931
993
|
message: `\u2717 ${script} not found. Did copier finish? Re-run with --skip-runtime-repo=0 if you skipped it.`
|
|
932
994
|
};
|
|
933
995
|
}
|
|
934
|
-
const marker =
|
|
996
|
+
const marker = join6(roleDir, ".scripts", ".done-30-telegram");
|
|
935
997
|
if (existsSync4(marker)) unlinkSync(marker);
|
|
936
998
|
const spinner4 = p3.spinner();
|
|
937
999
|
spinner4.start("Verifying token + wiring profile");
|
|
@@ -959,14 +1021,14 @@ function cap(s) {
|
|
|
959
1021
|
|
|
960
1022
|
// src/commands/hermes/WireEmail.ts
|
|
961
1023
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
962
|
-
import { join as
|
|
1024
|
+
import { join as join7 } from "node:path";
|
|
963
1025
|
import { existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
|
|
964
1026
|
import * as p4 from "@clack/prompts";
|
|
965
1027
|
var WireEmail = class extends Command {
|
|
966
1028
|
async invoke() {
|
|
967
1029
|
const ctx = this.context;
|
|
968
1030
|
if (ctx.skipEmail) {
|
|
969
|
-
return { success: true, message: "
|
|
1031
|
+
return { success: true, message: "" };
|
|
970
1032
|
}
|
|
971
1033
|
if (ctx.dryRun) {
|
|
972
1034
|
return { success: true, message: this.formatMessage("Would create CF Email Routing rule") };
|
|
@@ -975,7 +1037,7 @@ var WireEmail = class extends Command {
|
|
|
975
1037
|
if (!targetRepo || !role || !roleDir) {
|
|
976
1038
|
return { success: false, message: "Cannot wire email: missing target_repo/role/roleDir" };
|
|
977
1039
|
}
|
|
978
|
-
const script =
|
|
1040
|
+
const script = join7(roleDir, ".scripts", "50-email.sh");
|
|
979
1041
|
if (!existsSync5(script)) {
|
|
980
1042
|
return { success: false, message: `\u2717 ${script} not found` };
|
|
981
1043
|
}
|
|
@@ -1038,7 +1100,7 @@ var WireEmail = class extends Command {
|
|
|
1038
1100
|
}
|
|
1039
1101
|
}
|
|
1040
1102
|
}
|
|
1041
|
-
const marker =
|
|
1103
|
+
const marker = join7(roleDir, ".scripts", ".done-50-email");
|
|
1042
1104
|
if (existsSync5(marker)) unlinkSync2(marker);
|
|
1043
1105
|
const spinner4 = p4.spinner();
|
|
1044
1106
|
spinner4.start("Creating Cloudflare Email Routing rule");
|
|
@@ -1074,7 +1136,7 @@ var PrintHermesSummary = class extends Command {
|
|
|
1074
1136
|
lines.push(`role dir ${ctx.roleDir}`);
|
|
1075
1137
|
lines.push(`runtime gh:${runtimeRepo}`);
|
|
1076
1138
|
lines.push(`telegram @${botHandle}${skipTelegram ? " (NOT yet wired)" : ""}`);
|
|
1077
|
-
lines.push(`email ${email}
|
|
1139
|
+
if (!skipEmail) lines.push(`email ${email}`);
|
|
1078
1140
|
lines.push("");
|
|
1079
1141
|
lines.push("Start daemons:");
|
|
1080
1142
|
lines.push(` systemctl --user start ${csm}`);
|
|
@@ -1087,11 +1149,10 @@ var PrintHermesSummary = class extends Command {
|
|
|
1087
1149
|
lines.push("");
|
|
1088
1150
|
lines.push("Talk locally:");
|
|
1089
1151
|
lines.push(` ${ctx.roleDir}/hermes chat "status"`);
|
|
1090
|
-
if (skipTelegram
|
|
1152
|
+
if (skipTelegram) {
|
|
1091
1153
|
lines.push("");
|
|
1092
|
-
lines.push("
|
|
1093
|
-
|
|
1094
|
-
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");
|
|
1095
1156
|
}
|
|
1096
1157
|
p5.note(lines.join("\n"), `Provisioned ${agentId}`);
|
|
1097
1158
|
p5.outro("Done.");
|
|
@@ -1127,7 +1188,7 @@ var HermesAgentRecipe = class extends Recipe {
|
|
|
1127
1188
|
|
|
1128
1189
|
// src/commands/AgentHooksCommands.ts
|
|
1129
1190
|
import { homedir as homedir3 } from "node:os";
|
|
1130
|
-
import { join as
|
|
1191
|
+
import { join as join8, dirname as dirname4 } from "node:path";
|
|
1131
1192
|
import { existsSync as existsSync6, cpSync, mkdirSync as mkdirSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1132
1193
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1133
1194
|
function resolveTemplateRoot() {
|
|
@@ -1138,16 +1199,16 @@ function resolveTemplateRoot() {
|
|
|
1138
1199
|
try {
|
|
1139
1200
|
let dir = dirname4(fileURLToPath2(import.meta.url));
|
|
1140
1201
|
for (let i = 0; i < 8; i++) {
|
|
1141
|
-
candidates.push(
|
|
1202
|
+
candidates.push(join8(dir, "templates", "commonproject", "template"));
|
|
1142
1203
|
const parent = dirname4(dir);
|
|
1143
1204
|
if (parent === dir) break;
|
|
1144
1205
|
dir = parent;
|
|
1145
1206
|
}
|
|
1146
1207
|
} catch {
|
|
1147
1208
|
}
|
|
1148
|
-
candidates.push(
|
|
1209
|
+
candidates.push(join8(homedir3(), "code", "pjangler", "templates", "commonproject", "template"));
|
|
1149
1210
|
for (const c of candidates) {
|
|
1150
|
-
if (existsSync6(
|
|
1211
|
+
if (existsSync6(join8(c, ".agents", "hooks", "hooks.master.json"))) return c;
|
|
1151
1212
|
}
|
|
1152
1213
|
throw new Error(
|
|
1153
1214
|
"Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
|
|
@@ -1171,8 +1232,8 @@ var CopyAgentHooksTree = class extends Command {
|
|
|
1171
1232
|
const created = [];
|
|
1172
1233
|
const skipped = [];
|
|
1173
1234
|
for (const { rel, dir } of items) {
|
|
1174
|
-
const src =
|
|
1175
|
-
const dest =
|
|
1235
|
+
const src = join8(templateRoot, rel);
|
|
1236
|
+
const dest = join8(this.context.targetDir, rel);
|
|
1176
1237
|
if (!existsSync6(src)) continue;
|
|
1177
1238
|
if (existsSync6(dest) && !this.context.force) {
|
|
1178
1239
|
skipped.push(rel);
|
|
@@ -1197,7 +1258,7 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
|
|
|
1197
1258
|
static CR = "{{config_root}}";
|
|
1198
1259
|
// mise's own runtime var — emitted literally
|
|
1199
1260
|
async invoke() {
|
|
1200
|
-
const misePath =
|
|
1261
|
+
const misePath = join8(this.context.targetDir, "mise.toml");
|
|
1201
1262
|
if (!existsSync6(misePath)) {
|
|
1202
1263
|
return {
|
|
1203
1264
|
success: false,
|
|
@@ -1318,7 +1379,7 @@ var RECIPE_REGISTRY = {
|
|
|
1318
1379
|
name: "mise",
|
|
1319
1380
|
description: "Mise task runner and environment setup",
|
|
1320
1381
|
class: MiseRecipe,
|
|
1321
|
-
commands: ["AddMiseToml", "AddDotenv", "AddMiseTasksStructure", "AddMiseBaseToml", "AddMiseBaseScript"]
|
|
1382
|
+
commands: ["AddMiseToml", "AddDotenv", "AddMiseTasksStructure", "AddMiseBaseToml", "AddMiseBaseScript", "AddMiseCodegraphScript"]
|
|
1322
1383
|
},
|
|
1323
1384
|
docker: {
|
|
1324
1385
|
name: "docker",
|
|
@@ -1408,6 +1469,12 @@ var COMMAND_REGISTRY = {
|
|
|
1408
1469
|
group: "mise",
|
|
1409
1470
|
class: AddMiseBaseScript
|
|
1410
1471
|
},
|
|
1472
|
+
AddMiseCodegraphScript: {
|
|
1473
|
+
name: "AddMiseCodegraphScript",
|
|
1474
|
+
description: "Create .mise/scripts/codegraph.sh enter hook",
|
|
1475
|
+
group: "mise",
|
|
1476
|
+
class: AddMiseCodegraphScript
|
|
1477
|
+
},
|
|
1411
1478
|
AddDotenv: {
|
|
1412
1479
|
name: "AddDotenv",
|
|
1413
1480
|
description: "Create .env.example file",
|
|
@@ -1429,14 +1496,14 @@ function createRecipe(name, context) {
|
|
|
1429
1496
|
|
|
1430
1497
|
// src/utils/version.ts
|
|
1431
1498
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1432
|
-
import { dirname as dirname5, join as
|
|
1499
|
+
import { dirname as dirname5, join as join9 } from "node:path";
|
|
1433
1500
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1434
1501
|
var PJANGLER_VERSION = (() => {
|
|
1435
1502
|
try {
|
|
1436
1503
|
let dir = dirname5(fileURLToPath3(import.meta.url));
|
|
1437
1504
|
for (let i = 0; i < 4; i++) {
|
|
1438
1505
|
try {
|
|
1439
|
-
const raw = readFileSync3(
|
|
1506
|
+
const raw = readFileSync3(join9(dir, "package.json"), "utf8");
|
|
1440
1507
|
return JSON.parse(raw).version ?? "0.0.0";
|
|
1441
1508
|
} catch {
|
|
1442
1509
|
const parent = dirname5(dir);
|
|
@@ -1450,8 +1517,8 @@ var PJANGLER_VERSION = (() => {
|
|
|
1450
1517
|
})();
|
|
1451
1518
|
|
|
1452
1519
|
// src/parity/index.ts
|
|
1453
|
-
import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync5, readFileSync as readFileSync4, readlinkSync, readdirSync, renameSync, symlinkSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4, chmodSync, copyFileSync } from "node:fs";
|
|
1454
|
-
import { basename as basename2, dirname as dirname6, join as
|
|
1520
|
+
import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync5, readFileSync as readFileSync4, readlinkSync, readdirSync, renameSync, symlinkSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4, chmodSync as chmodSync2, copyFileSync } from "node:fs";
|
|
1521
|
+
import { basename as basename2, dirname as dirname6, join as join10, relative, resolve } from "node:path";
|
|
1455
1522
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
1456
1523
|
import { homedir as homedir4 } from "node:os";
|
|
1457
1524
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
@@ -1525,7 +1592,7 @@ run = "{{config_root}}/.mise/scripts/versioning.sh sync"
|
|
|
1525
1592
|
function resolvePjanglerRoot() {
|
|
1526
1593
|
let dir = dirname6(fileURLToPath4(import.meta.url));
|
|
1527
1594
|
while (dir !== dirname6(dir)) {
|
|
1528
|
-
if (existsSync7(
|
|
1595
|
+
if (existsSync7(join10(dir, "package.json")) && existsSync7(join10(dir, "templates", "commonproject", "copier.yml"))) {
|
|
1529
1596
|
return dir;
|
|
1530
1597
|
}
|
|
1531
1598
|
dir = dirname6(dir);
|
|
@@ -1548,10 +1615,10 @@ function writeText(path, content) {
|
|
|
1548
1615
|
ensureParent(path);
|
|
1549
1616
|
writeFileSync4(path, content);
|
|
1550
1617
|
}
|
|
1551
|
-
function tryParseJson(
|
|
1552
|
-
if (!
|
|
1618
|
+
function tryParseJson(text2) {
|
|
1619
|
+
if (!text2) return null;
|
|
1553
1620
|
try {
|
|
1554
|
-
return JSON.parse(
|
|
1621
|
+
return JSON.parse(text2);
|
|
1555
1622
|
} catch {
|
|
1556
1623
|
return null;
|
|
1557
1624
|
}
|
|
@@ -1588,10 +1655,10 @@ function ensureSymlink(path, target, dryRun) {
|
|
|
1588
1655
|
return { changed: true };
|
|
1589
1656
|
}
|
|
1590
1657
|
function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
1591
|
-
const agentsPath =
|
|
1658
|
+
const agentsPath = join10(repoRoot, "AGENTS.md");
|
|
1592
1659
|
if (existsSync7(agentsPath)) return { changedFiles: [], details: [] };
|
|
1593
1660
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
1594
|
-
const source =
|
|
1661
|
+
const source = join10(repoRoot, file);
|
|
1595
1662
|
if (!existsSync7(source)) continue;
|
|
1596
1663
|
const stat = lstatSync(source);
|
|
1597
1664
|
if (stat.isSymbolicLink()) continue;
|
|
@@ -1601,7 +1668,7 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
|
1601
1668
|
}
|
|
1602
1669
|
return { changedFiles: [], details: [], blocked: `${file} exists but is not a regular file; cannot promote to AGENTS.md` };
|
|
1603
1670
|
}
|
|
1604
|
-
const readmePath =
|
|
1671
|
+
const readmePath = join10(repoRoot, "README.md");
|
|
1605
1672
|
if (existsSync7(readmePath)) {
|
|
1606
1673
|
const stat = lstatSync(readmePath);
|
|
1607
1674
|
if (!stat.isFile()) return { changedFiles: [], details: [], blocked: "README.md exists but is not a regular file; cannot copy to AGENTS.md" };
|
|
@@ -1610,9 +1677,9 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
|
1610
1677
|
}
|
|
1611
1678
|
return { changedFiles: [], details: [], blocked: "AGENTS.md missing and no CLAUDE.md, GEMINI.md, or README.md source exists" };
|
|
1612
1679
|
}
|
|
1613
|
-
function yamlGet(
|
|
1680
|
+
function yamlGet(text2, keyPath) {
|
|
1614
1681
|
const parts = keyPath.split(".");
|
|
1615
|
-
const lines =
|
|
1682
|
+
const lines = text2.split("\n");
|
|
1616
1683
|
let start = 0;
|
|
1617
1684
|
let indent = 0;
|
|
1618
1685
|
for (let idx = 0; idx < parts.length; idx += 1) {
|
|
@@ -1641,36 +1708,36 @@ function yamlGet(text3, keyPath) {
|
|
|
1641
1708
|
return "";
|
|
1642
1709
|
}
|
|
1643
1710
|
function discoverRoles(repoRoot) {
|
|
1644
|
-
const rolesDir =
|
|
1711
|
+
const rolesDir = join10(repoRoot, "agents", "hermes");
|
|
1645
1712
|
if (!existsSync7(rolesDir)) return [];
|
|
1646
1713
|
return readdirSync(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
|
|
1647
|
-
const roleDir =
|
|
1648
|
-
const roleYamlPath =
|
|
1714
|
+
const roleDir = join10(rolesDir, entry.name);
|
|
1715
|
+
const roleYamlPath = join10(roleDir, "role.yaml");
|
|
1649
1716
|
if (!existsSync7(roleYamlPath)) return null;
|
|
1650
|
-
const
|
|
1651
|
-
const runtimeRepoRaw = yamlGet(
|
|
1717
|
+
const text2 = readText(roleYamlPath);
|
|
1718
|
+
const runtimeRepoRaw = yamlGet(text2, "runtime.github_repo");
|
|
1652
1719
|
return {
|
|
1653
|
-
role: yamlGet(
|
|
1720
|
+
role: yamlGet(text2, "role") || entry.name,
|
|
1654
1721
|
roleDir,
|
|
1655
1722
|
roleYamlPath,
|
|
1656
|
-
repo: yamlGet(
|
|
1657
|
-
agentId: yamlGet(
|
|
1658
|
-
profileName: yamlGet(
|
|
1659
|
-
displayName: yamlGet(
|
|
1660
|
-
purpose: yamlGet(
|
|
1661
|
-
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"),
|
|
1662
1729
|
runtimeRepo: runtimeRepoRaw.includes("/") ? runtimeRepoRaw.split("/").slice(-1)[0] ?? runtimeRepoRaw : runtimeRepoRaw,
|
|
1663
|
-
runtimeOwner: yamlGet(
|
|
1664
|
-
planeWorkspace: yamlGet(
|
|
1665
|
-
ticketProviderName: yamlGet(
|
|
1666
|
-
ticketProviderBoardId: yamlGet(
|
|
1667
|
-
ticketProviderBoardUrl: yamlGet(
|
|
1668
|
-
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")
|
|
1669
1736
|
};
|
|
1670
1737
|
}).filter((value) => Boolean(value));
|
|
1671
1738
|
}
|
|
1672
1739
|
function registryPath(homeDir) {
|
|
1673
|
-
return
|
|
1740
|
+
return join10(homeDir, ".hermes", "agents-registry.yaml");
|
|
1674
1741
|
}
|
|
1675
1742
|
function systemctlUser(args) {
|
|
1676
1743
|
const result = spawnSync4("systemctl", ["--user", ...args], { encoding: "utf8" });
|
|
@@ -1681,7 +1748,7 @@ function systemctlUser(args) {
|
|
|
1681
1748
|
};
|
|
1682
1749
|
}
|
|
1683
1750
|
function templateScript(ctx, name) {
|
|
1684
|
-
const source =
|
|
1751
|
+
const source = join10(ctx.pjanglerRoot, ".mise", "scripts", name);
|
|
1685
1752
|
return existsSync7(source) ? readText(source) : void 0;
|
|
1686
1753
|
}
|
|
1687
1754
|
function templateVersioningScript(ctx) {
|
|
@@ -1696,9 +1763,9 @@ function renderGeneratedProjectMiseToml(ctx, template) {
|
|
|
1696
1763
|
return template.replace(/\{%\s*raw\s*%\}([\s\S]*?)\{%\s*endraw\s*%\}/g, "$1").replace(/\{\{\s*project_name\s*\}\}/g, projectName);
|
|
1697
1764
|
}
|
|
1698
1765
|
function ensureMiseTomlFromTemplate(ctx, changedFiles) {
|
|
1699
|
-
const targetPath =
|
|
1766
|
+
const targetPath = join10(ctx.repoRoot, "mise.toml");
|
|
1700
1767
|
if (existsSync7(targetPath)) return false;
|
|
1701
|
-
const sourcePath =
|
|
1768
|
+
const sourcePath = join10(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
|
|
1702
1769
|
if (!existsSync7(sourcePath)) return false;
|
|
1703
1770
|
changedFiles.push(targetPath);
|
|
1704
1771
|
if (!ctx.dryRun) {
|
|
@@ -1707,22 +1774,22 @@ function ensureMiseTomlFromTemplate(ctx, changedFiles) {
|
|
|
1707
1774
|
return true;
|
|
1708
1775
|
}
|
|
1709
1776
|
function templateVersionFilesConf(ctx, repoRoot) {
|
|
1710
|
-
const packageJson =
|
|
1777
|
+
const packageJson = join10(repoRoot, "package.json");
|
|
1711
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";
|
|
1712
1779
|
}
|
|
1713
|
-
function replaceOrAppendManagedBlock(
|
|
1714
|
-
if (startMarker.test(
|
|
1715
|
-
return
|
|
1780
|
+
function replaceOrAppendManagedBlock(text2, startMarker, block, beforePattern) {
|
|
1781
|
+
if (startMarker.test(text2)) {
|
|
1782
|
+
return text2.replace(/# >>> mise-versioning >>>[\s\S]*?# <<< mise-versioning <<</, block);
|
|
1716
1783
|
}
|
|
1717
1784
|
if (beforePattern) {
|
|
1718
|
-
const match =
|
|
1785
|
+
const match = text2.match(beforePattern);
|
|
1719
1786
|
if (match && typeof match.index === "number") {
|
|
1720
|
-
return `${
|
|
1787
|
+
return `${text2.slice(0, match.index).replace(/\s*$/, "\n\n")}${block}
|
|
1721
1788
|
|
|
1722
|
-
${
|
|
1789
|
+
${text2.slice(match.index)}`;
|
|
1723
1790
|
}
|
|
1724
1791
|
}
|
|
1725
|
-
return `${
|
|
1792
|
+
return `${text2.replace(/\s*$/, "")}
|
|
1726
1793
|
|
|
1727
1794
|
${block}
|
|
1728
1795
|
`;
|
|
@@ -1732,22 +1799,22 @@ var CONDITIONAL_HERMES_PATHS = ["agents/hermes/pm/hermes", "agent/hermes/pm/herm
|
|
|
1732
1799
|
function requiredMisePathEntries(ctx) {
|
|
1733
1800
|
const required = [...BASE_MISE_PATH_ENTRIES];
|
|
1734
1801
|
for (const candidate of CONDITIONAL_HERMES_PATHS) {
|
|
1735
|
-
if (existsSync7(
|
|
1802
|
+
if (existsSync7(join10(ctx.repoRoot, candidate)) && !required.includes(candidate)) required.push(candidate);
|
|
1736
1803
|
}
|
|
1737
1804
|
return required;
|
|
1738
1805
|
}
|
|
1739
|
-
function upsertMisePath(
|
|
1806
|
+
function upsertMisePath(text2, required = BASE_MISE_PATH_ENTRIES) {
|
|
1740
1807
|
const render = (values) => `_.path = [${values.map((value) => JSON.stringify(value)).join(", ")}]`;
|
|
1741
|
-
const envMatch =
|
|
1808
|
+
const envMatch = text2.match(/(^|\n)(\[env\][\s\S]*?)(?=\n\[[^\]]+\]|$)/);
|
|
1742
1809
|
if (!envMatch || typeof envMatch.index !== "number") {
|
|
1743
1810
|
return `[env]
|
|
1744
1811
|
${render(required)}
|
|
1745
1812
|
|
|
1746
|
-
${
|
|
1813
|
+
${text2.replace(/^\s+/, "")}`;
|
|
1747
1814
|
}
|
|
1748
|
-
const prefix =
|
|
1815
|
+
const prefix = text2.slice(0, envMatch.index + envMatch[1].length);
|
|
1749
1816
|
const section = envMatch[2];
|
|
1750
|
-
const suffix =
|
|
1817
|
+
const suffix = text2.slice(envMatch.index + envMatch[1].length + section.length);
|
|
1751
1818
|
const pathLine = section.match(/^_\.path\s*=\s*\[([^\]]*)\]\s*$/m);
|
|
1752
1819
|
if (!pathLine) {
|
|
1753
1820
|
return `${prefix}${section.replace(/\n?$/, "\n")}${render(required)}${suffix}`;
|
|
@@ -1758,11 +1825,11 @@ ${text3.replace(/^\s+/, "")}`;
|
|
|
1758
1825
|
if (!merged.includes(value)) merged.push(value);
|
|
1759
1826
|
}
|
|
1760
1827
|
const nextLine = render(merged);
|
|
1761
|
-
if (pathLine[0] === nextLine) return
|
|
1828
|
+
if (pathLine[0] === nextLine) return text2;
|
|
1762
1829
|
return `${prefix}${section.replace(pathLine[0], nextLine)}${suffix}`;
|
|
1763
1830
|
}
|
|
1764
|
-
function removeTomlSection(
|
|
1765
|
-
const lines =
|
|
1831
|
+
function removeTomlSection(text2, headerPattern, marker, options) {
|
|
1832
|
+
const lines = text2.split("\n");
|
|
1766
1833
|
let start = -1;
|
|
1767
1834
|
let end = -1;
|
|
1768
1835
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -1787,7 +1854,7 @@ function removeTomlSection(text3, headerPattern, marker, options) {
|
|
|
1787
1854
|
if (end === -1) end = lines.length;
|
|
1788
1855
|
break;
|
|
1789
1856
|
}
|
|
1790
|
-
if (start === -1) return
|
|
1857
|
+
if (start === -1) return text2;
|
|
1791
1858
|
if (options?.includePrecedingComments) {
|
|
1792
1859
|
while (start > 0 && lines[start - 1].trim().startsWith("#")) {
|
|
1793
1860
|
start--;
|
|
@@ -1796,22 +1863,22 @@ function removeTomlSection(text3, headerPattern, marker, options) {
|
|
|
1796
1863
|
const result = lines.slice(0, start).concat(lines.slice(end)).join("\n");
|
|
1797
1864
|
return result.replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "\n");
|
|
1798
1865
|
}
|
|
1799
|
-
function insertTomlBlockBeforeVersioning(
|
|
1800
|
-
const versioningIndex =
|
|
1866
|
+
function insertTomlBlockBeforeVersioning(text2, block) {
|
|
1867
|
+
const versioningIndex = text2.indexOf("# >>> mise-versioning >>>");
|
|
1801
1868
|
if (versioningIndex >= 0) {
|
|
1802
|
-
return `${
|
|
1869
|
+
return `${text2.slice(0, versioningIndex).replace(/\s*$/, "\n\n")}${block}
|
|
1803
1870
|
|
|
1804
|
-
${
|
|
1871
|
+
${text2.slice(versioningIndex)}`;
|
|
1805
1872
|
}
|
|
1806
|
-
return `${
|
|
1873
|
+
return `${text2.replace(/\s*$/, "")}
|
|
1807
1874
|
|
|
1808
1875
|
${block}
|
|
1809
1876
|
`;
|
|
1810
1877
|
}
|
|
1811
|
-
function extractTomlStrings(
|
|
1878
|
+
function extractTomlStrings(text2) {
|
|
1812
1879
|
const values = [];
|
|
1813
1880
|
const stringPattern = /"((?:\\.|[^"\\])*)"|'([^']*)'/g;
|
|
1814
|
-
for (const match of
|
|
1881
|
+
for (const match of text2.matchAll(stringPattern)) {
|
|
1815
1882
|
if (match[1] !== void 0) {
|
|
1816
1883
|
try {
|
|
1817
1884
|
values.push(JSON.parse(`"${match[1]}"`));
|
|
@@ -1835,10 +1902,10 @@ function renderHookEntries(entries, indent = "") {
|
|
|
1835
1902
|
`${indent}]`
|
|
1836
1903
|
];
|
|
1837
1904
|
}
|
|
1838
|
-
function upsertLinkAgentfilesHooks(
|
|
1839
|
-
const lines =
|
|
1905
|
+
function upsertLinkAgentfilesHooks(text2) {
|
|
1906
|
+
const lines = text2.split("\n");
|
|
1840
1907
|
const hooksStart = lines.findIndex((line) => /^\[hooks\]$/.test(line.trim()));
|
|
1841
|
-
if (hooksStart === -1) return insertTomlBlockBeforeVersioning(
|
|
1908
|
+
if (hooksStart === -1) return insertTomlBlockBeforeVersioning(text2, LINK_AGENTFILES_HOOKS_BLOCK);
|
|
1842
1909
|
let hooksEnd = lines.length;
|
|
1843
1910
|
for (let i = hooksStart + 1; i < lines.length; i++) {
|
|
1844
1911
|
if (/^\[[^\]]+\]/.test(lines[i].trim())) {
|
|
@@ -1872,8 +1939,8 @@ function upsertLinkAgentfilesHooks(text3) {
|
|
|
1872
1939
|
}
|
|
1873
1940
|
return lines.slice(0, hooksStart + 1).concat(rendered, lines.slice(hooksStart + 1)).join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "\n");
|
|
1874
1941
|
}
|
|
1875
|
-
function upsertLinkAgentfilesBlock(
|
|
1876
|
-
const withPath = upsertMisePath(
|
|
1942
|
+
function upsertLinkAgentfilesBlock(text2, ctx) {
|
|
1943
|
+
const withPath = upsertMisePath(text2, requiredMisePathEntries(ctx));
|
|
1877
1944
|
if (withPath.includes(LINK_AGENTFILES_BLOCK)) return withPath;
|
|
1878
1945
|
let cleaned = removeTomlSection(withPath, /^\[tasks\.link-agentfiles\]$/, /link-agentfiles/, { includePrecedingComments: false });
|
|
1879
1946
|
cleaned = removeTomlSection(cleaned, /^\[\[watch_files\]\]$/, /AGENTS\.md/, { includePrecedingComments: false });
|
|
@@ -1881,7 +1948,7 @@ function upsertLinkAgentfilesBlock(text3, ctx) {
|
|
|
1881
1948
|
return insertTomlBlockBeforeVersioning(cleaned, LINK_AGENTFILES_WATCH_TASK_BLOCK);
|
|
1882
1949
|
}
|
|
1883
1950
|
function readProjectJson(ctx) {
|
|
1884
|
-
return tryParseJson(safeReadText(
|
|
1951
|
+
return tryParseJson(safeReadText(join10(ctx.repoRoot, ".project.json")));
|
|
1885
1952
|
}
|
|
1886
1953
|
function canonicalProjectJson(ctx) {
|
|
1887
1954
|
const roles = discoverRoles(ctx.repoRoot);
|
|
@@ -1925,8 +1992,8 @@ function canonicalProjectJson(ctx) {
|
|
|
1925
1992
|
};
|
|
1926
1993
|
}
|
|
1927
1994
|
function projectJsonFinding(ctx) {
|
|
1928
|
-
const projectPath =
|
|
1929
|
-
const planeJsonPath =
|
|
1995
|
+
const projectPath = join10(ctx.repoRoot, ".project.json");
|
|
1996
|
+
const planeJsonPath = join10(ctx.repoRoot, ".plane.json");
|
|
1930
1997
|
const details = [];
|
|
1931
1998
|
const data = readProjectJson(ctx);
|
|
1932
1999
|
const roles = discoverRoles(ctx.repoRoot);
|
|
@@ -2040,9 +2107,9 @@ function copyMissingRecursive(sourceDir, targetDir, changedFiles, dryRun, skip)
|
|
|
2040
2107
|
if (!existsSync7(sourceDir)) return;
|
|
2041
2108
|
mkdirSync5(targetDir, { recursive: true });
|
|
2042
2109
|
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
|
2043
|
-
const sourcePath =
|
|
2110
|
+
const sourcePath = join10(sourceDir, entry.name);
|
|
2044
2111
|
if (skip?.(sourcePath)) continue;
|
|
2045
|
-
const targetPath =
|
|
2112
|
+
const targetPath = join10(targetDir, entry.name);
|
|
2046
2113
|
if (entry.isDirectory()) {
|
|
2047
2114
|
copyMissingRecursive(sourcePath, targetPath, changedFiles, dryRun, skip);
|
|
2048
2115
|
continue;
|
|
@@ -2056,7 +2123,7 @@ function copyMissingRecursive(sourceDir, targetDir, changedFiles, dryRun, skip)
|
|
|
2056
2123
|
}
|
|
2057
2124
|
}
|
|
2058
2125
|
function upsertSubmodule(repoRoot, role, changedFiles, dryRun) {
|
|
2059
|
-
const gitmodulesPath =
|
|
2126
|
+
const gitmodulesPath = join10(repoRoot, ".gitmodules");
|
|
2060
2127
|
const repoName = role.runtimeRepo || `agent-hm-${role.repo}-${role.role}`;
|
|
2061
2128
|
const owner = role.runtimeOwner || "delorenj";
|
|
2062
2129
|
const block = `[submodule "agents/hermes/${role.role}/runtime"]
|
|
@@ -2100,9 +2167,9 @@ ${block}`) : `${current.replace(/\s*$/, "\n")}${block}`;
|
|
|
2100
2167
|
return path;
|
|
2101
2168
|
}
|
|
2102
2169
|
function profileMetaInheritsDefault(path) {
|
|
2103
|
-
const
|
|
2170
|
+
const text2 = safeReadText(path);
|
|
2104
2171
|
return Boolean(
|
|
2105
|
-
|
|
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)
|
|
2106
2173
|
);
|
|
2107
2174
|
}
|
|
2108
2175
|
function upsertInheritedProfileMeta(path, changedFiles, dryRun) {
|
|
@@ -2156,21 +2223,21 @@ var RULES = [
|
|
|
2156
2223
|
id: "mise.config-root",
|
|
2157
2224
|
title: "mise config_root + AGENTS link hooks",
|
|
2158
2225
|
audit: (ctx) => {
|
|
2159
|
-
const misePath =
|
|
2226
|
+
const misePath = join10(ctx.repoRoot, "mise.toml");
|
|
2160
2227
|
if (!existsSync7(misePath)) {
|
|
2161
2228
|
return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
|
|
2162
2229
|
}
|
|
2163
|
-
const
|
|
2230
|
+
const text2 = readText(misePath);
|
|
2164
2231
|
const details = [];
|
|
2165
|
-
const linkAgentfilesPath =
|
|
2232
|
+
const linkAgentfilesPath = join10(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
2166
2233
|
if (!existsSync7(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
|
|
2167
|
-
const pathValues = [...(
|
|
2234
|
+
const pathValues = [...(text2.match(/^_\.path\s*=\s*\[([^\]]*)\]/m)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
|
2168
2235
|
const missingPathValues = requiredMisePathEntries(ctx).filter((value) => !pathValues.includes(value));
|
|
2169
2236
|
if (missingPathValues.length) details.push(`[env]._.path should include ${missingPathValues.join(", ")}`);
|
|
2170
|
-
if (!
|
|
2171
|
-
if (!
|
|
2172
|
-
if (!
|
|
2173
|
-
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");
|
|
2174
2241
|
return {
|
|
2175
2242
|
id: "mise.config-root",
|
|
2176
2243
|
title: "mise config_root + AGENTS link hooks",
|
|
@@ -2181,7 +2248,7 @@ var RULES = [
|
|
|
2181
2248
|
};
|
|
2182
2249
|
},
|
|
2183
2250
|
migrate: (ctx, finding) => {
|
|
2184
|
-
const path =
|
|
2251
|
+
const path = join10(ctx.repoRoot, "mise.toml");
|
|
2185
2252
|
const changedFiles = [];
|
|
2186
2253
|
const details = [];
|
|
2187
2254
|
if (!existsSync7(path)) {
|
|
@@ -2193,14 +2260,14 @@ var RULES = [
|
|
|
2193
2260
|
return { id: finding.id, title: finding.title, status: "applied", summary: "Would initialize mise.toml from generated-project template", changedFiles, details };
|
|
2194
2261
|
}
|
|
2195
2262
|
}
|
|
2196
|
-
let
|
|
2197
|
-
const next = upsertLinkAgentfilesBlock(
|
|
2198
|
-
if (next !==
|
|
2263
|
+
let text2 = readText(path);
|
|
2264
|
+
const next = upsertLinkAgentfilesBlock(text2, ctx);
|
|
2265
|
+
if (next !== text2) {
|
|
2199
2266
|
if (!changedFiles.includes(path)) changedFiles.push(path);
|
|
2200
2267
|
if (!ctx.dryRun) writeText(path, next);
|
|
2201
|
-
|
|
2268
|
+
text2 = next;
|
|
2202
2269
|
}
|
|
2203
|
-
const linkAgentfilesPath =
|
|
2270
|
+
const linkAgentfilesPath = join10(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
2204
2271
|
const expectedScript = templateLinkAgentfilesScript(ctx);
|
|
2205
2272
|
if (expectedScript === void 0) {
|
|
2206
2273
|
return { id: finding.id, title: finding.title, status: "blocked", summary: "pjangler install is missing .mise/scripts/link-agentfiles.sh \u2014 update @delorenj/pjangler (broken package)", changedFiles, details: [] };
|
|
@@ -2209,7 +2276,7 @@ var RULES = [
|
|
|
2209
2276
|
changedFiles.push(linkAgentfilesPath);
|
|
2210
2277
|
if (!ctx.dryRun) {
|
|
2211
2278
|
writeText(linkAgentfilesPath, expectedScript);
|
|
2212
|
-
|
|
2279
|
+
chmodSync2(linkAgentfilesPath, 493);
|
|
2213
2280
|
}
|
|
2214
2281
|
}
|
|
2215
2282
|
return {
|
|
@@ -2227,11 +2294,11 @@ var RULES = [
|
|
|
2227
2294
|
title: "managed mise versioning block",
|
|
2228
2295
|
audit: (ctx) => {
|
|
2229
2296
|
const details = [];
|
|
2230
|
-
const misePath =
|
|
2231
|
-
const versioningPath =
|
|
2232
|
-
const manifestPath =
|
|
2233
|
-
const
|
|
2234
|
-
if (!
|
|
2297
|
+
const misePath = join10(ctx.repoRoot, "mise.toml");
|
|
2298
|
+
const versioningPath = join10(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
2299
|
+
const manifestPath = join10(ctx.repoRoot, ".mise", "version-files.conf");
|
|
2300
|
+
const text2 = safeReadText(misePath);
|
|
2301
|
+
if (!text2?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
|
|
2235
2302
|
if (!existsSync7(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
|
|
2236
2303
|
if (!existsSync7(manifestPath)) details.push(".mise/version-files.conf missing");
|
|
2237
2304
|
return {
|
|
@@ -2246,7 +2313,7 @@ var RULES = [
|
|
|
2246
2313
|
migrate: (ctx, finding) => {
|
|
2247
2314
|
const changedFiles = [];
|
|
2248
2315
|
const details = [];
|
|
2249
|
-
const misePath =
|
|
2316
|
+
const misePath = join10(ctx.repoRoot, "mise.toml");
|
|
2250
2317
|
if (!existsSync7(misePath)) {
|
|
2251
2318
|
if (!ensureMiseTomlFromTemplate(ctx, changedFiles)) {
|
|
2252
2319
|
return { id: finding.id, title: finding.title, status: "blocked", summary: "mise.toml missing and no generated-project mise template available to initialize from", changedFiles, details: [] };
|
|
@@ -2262,7 +2329,7 @@ var RULES = [
|
|
|
2262
2329
|
if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
|
|
2263
2330
|
if (!ctx.dryRun) writeText(misePath, nextMise);
|
|
2264
2331
|
}
|
|
2265
|
-
const versioningPath =
|
|
2332
|
+
const versioningPath = join10(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
2266
2333
|
const expectedScript = templateVersioningScript(ctx);
|
|
2267
2334
|
if (expectedScript === void 0) {
|
|
2268
2335
|
return { id: finding.id, title: finding.title, status: "blocked", summary: "pjangler install is missing .mise/scripts/versioning.sh \u2014 update @delorenj/pjangler (broken package)", changedFiles, details: [] };
|
|
@@ -2271,10 +2338,10 @@ var RULES = [
|
|
|
2271
2338
|
changedFiles.push(versioningPath);
|
|
2272
2339
|
if (!ctx.dryRun) {
|
|
2273
2340
|
writeText(versioningPath, expectedScript);
|
|
2274
|
-
|
|
2341
|
+
chmodSync2(versioningPath, 493);
|
|
2275
2342
|
}
|
|
2276
2343
|
}
|
|
2277
|
-
const manifestPath =
|
|
2344
|
+
const manifestPath = join10(ctx.repoRoot, ".mise", "version-files.conf");
|
|
2278
2345
|
const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
|
|
2279
2346
|
if (safeReadText(manifestPath) !== expectedManifest) {
|
|
2280
2347
|
changedFiles.push(manifestPath);
|
|
@@ -2294,9 +2361,9 @@ var RULES = [
|
|
|
2294
2361
|
id: "sot.agent-symlinks",
|
|
2295
2362
|
title: "AGENTS/CLAUDE/GEMINI symlink contract",
|
|
2296
2363
|
audit: (ctx) => {
|
|
2297
|
-
const agentsPath =
|
|
2364
|
+
const agentsPath = join10(ctx.repoRoot, "AGENTS.md");
|
|
2298
2365
|
if (!existsSync7(agentsPath)) {
|
|
2299
|
-
const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync7(
|
|
2366
|
+
const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync7(join10(ctx.repoRoot, file)));
|
|
2300
2367
|
if (fallbackSources.length === 0) {
|
|
2301
2368
|
return { id: "sot.agent-symlinks", title: "AGENTS/CLAUDE/GEMINI symlink contract", status: "skip", summary: "AGENTS.md missing; symlink contract not applicable", details: [], fixable: false };
|
|
2302
2369
|
}
|
|
@@ -2311,7 +2378,7 @@ var RULES = [
|
|
|
2311
2378
|
}
|
|
2312
2379
|
const details = [];
|
|
2313
2380
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
2314
|
-
const full =
|
|
2381
|
+
const full = join10(ctx.repoRoot, file);
|
|
2315
2382
|
const target = readSymlinkTarget(full);
|
|
2316
2383
|
if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
|
|
2317
2384
|
}
|
|
@@ -2335,7 +2402,7 @@ var RULES = [
|
|
|
2335
2402
|
return { id: finding.id, title: finding.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
|
|
2336
2403
|
}
|
|
2337
2404
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
2338
|
-
const full =
|
|
2405
|
+
const full = join10(ctx.repoRoot, file);
|
|
2339
2406
|
const result = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
|
|
2340
2407
|
if (result.blocked) blockedDetails.push(result.blocked);
|
|
2341
2408
|
if (result.changed) changedFiles.push(full);
|
|
@@ -2357,7 +2424,7 @@ var RULES = [
|
|
|
2357
2424
|
migrate: (ctx, finding) => {
|
|
2358
2425
|
const changedFiles = [];
|
|
2359
2426
|
const details = [];
|
|
2360
|
-
const path =
|
|
2427
|
+
const path = join10(ctx.repoRoot, ".project.json");
|
|
2361
2428
|
const existing = readProjectJson(ctx) ?? {};
|
|
2362
2429
|
const canonical = canonicalProjectJson(ctx);
|
|
2363
2430
|
const merged = { ...existing, ...canonical };
|
|
@@ -2367,7 +2434,7 @@ var RULES = [
|
|
|
2367
2434
|
changedFiles.push(path);
|
|
2368
2435
|
if (!ctx.dryRun) writeText(path, expected);
|
|
2369
2436
|
}
|
|
2370
|
-
const planeJson =
|
|
2437
|
+
const planeJson = join10(ctx.repoRoot, ".plane.json");
|
|
2371
2438
|
if (existsSync7(planeJson)) {
|
|
2372
2439
|
const backup = `${planeJson}.migrated-backup`;
|
|
2373
2440
|
if (existsSync7(backup)) {
|
|
@@ -2392,8 +2459,8 @@ var RULES = [
|
|
|
2392
2459
|
title: ".env.op + gitignore secrets contract",
|
|
2393
2460
|
audit: (ctx) => {
|
|
2394
2461
|
const details = [];
|
|
2395
|
-
const envOp = safeReadText(
|
|
2396
|
-
const gitignore = safeReadText(
|
|
2462
|
+
const envOp = safeReadText(join10(ctx.repoRoot, ".env.op"));
|
|
2463
|
+
const gitignore = safeReadText(join10(ctx.repoRoot, ".gitignore"));
|
|
2397
2464
|
if (!envOp) {
|
|
2398
2465
|
details.push(".env.op missing");
|
|
2399
2466
|
} else {
|
|
@@ -2419,12 +2486,12 @@ var RULES = [
|
|
|
2419
2486
|
migrate: (ctx, finding) => {
|
|
2420
2487
|
const changedFiles = [];
|
|
2421
2488
|
const details = [];
|
|
2422
|
-
const envOpPath =
|
|
2489
|
+
const envOpPath = join10(ctx.repoRoot, ".env.op");
|
|
2423
2490
|
if (!existsSync7(envOpPath)) {
|
|
2424
2491
|
changedFiles.push(envOpPath);
|
|
2425
|
-
if (!ctx.dryRun) writeText(envOpPath, readText(
|
|
2492
|
+
if (!ctx.dryRun) writeText(envOpPath, readText(join10(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op")));
|
|
2426
2493
|
}
|
|
2427
|
-
const gitignorePath =
|
|
2494
|
+
const gitignorePath = join10(ctx.repoRoot, ".gitignore");
|
|
2428
2495
|
const gitignore = safeReadText(gitignorePath) ?? "";
|
|
2429
2496
|
const requiredBlock = `# Secrets \u2014 .env is materialized by \`op inject -i .env.op > .env\` on mise enter.
|
|
2430
2497
|
# NEVER commit it. .env.op holds only 1Password references or safe literals and IS committed.
|
|
@@ -2451,20 +2518,20 @@ var RULES = [
|
|
|
2451
2518
|
title: ".copier-answers.yml provenance + drift report",
|
|
2452
2519
|
audit: (ctx) => {
|
|
2453
2520
|
const details = [];
|
|
2454
|
-
const path =
|
|
2455
|
-
const
|
|
2521
|
+
const path = join10(ctx.repoRoot, ".copier-answers.yml");
|
|
2522
|
+
const text2 = safeReadText(path);
|
|
2456
2523
|
const project = readProjectJson(ctx);
|
|
2457
|
-
if (!
|
|
2524
|
+
if (!text2) {
|
|
2458
2525
|
details.push(".copier-answers.yml missing");
|
|
2459
2526
|
} else {
|
|
2460
|
-
if (!
|
|
2461
|
-
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");
|
|
2462
2529
|
if (project?.project_name) {
|
|
2463
|
-
const nameMatch =
|
|
2530
|
+
const nameMatch = text2.match(/project_name:\s*(.+)/);
|
|
2464
2531
|
if (!nameMatch || nameMatch[1]?.trim() !== String(project.project_name)) details.push("project_name drift between .copier-answers.yml and .project.json");
|
|
2465
2532
|
}
|
|
2466
2533
|
if (project?.project_description) {
|
|
2467
|
-
const descMatch =
|
|
2534
|
+
const descMatch = text2.match(/project_description:\s*([\s\S]*?)(?=\n\w|$)/);
|
|
2468
2535
|
const yamlDesc = descMatch?.[1]?.replace(/\n\s+/g, " ").trim() ?? "";
|
|
2469
2536
|
if (yamlDesc !== String(project.project_description)) details.push("project_description drift between .copier-answers.yml and .project.json");
|
|
2470
2537
|
}
|
|
@@ -2481,16 +2548,16 @@ var RULES = [
|
|
|
2481
2548
|
migrate: (ctx, finding) => {
|
|
2482
2549
|
const changedFiles = [];
|
|
2483
2550
|
const project = canonicalProjectJson(ctx);
|
|
2484
|
-
const
|
|
2485
|
-
_src_path: ${
|
|
2551
|
+
const text2 = `# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
|
|
2552
|
+
_src_path: ${join10(ctx.pjanglerRoot, "templates", "commonproject")}
|
|
2486
2553
|
project_description: ${String(project.project_description)}
|
|
2487
2554
|
project_name: ${String(project.project_name)}
|
|
2488
2555
|
ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
2489
2556
|
`;
|
|
2490
|
-
const path =
|
|
2491
|
-
if (safeReadText(path) !==
|
|
2557
|
+
const path = join10(ctx.repoRoot, ".copier-answers.yml");
|
|
2558
|
+
if (safeReadText(path) !== text2) {
|
|
2492
2559
|
changedFiles.push(path);
|
|
2493
|
-
if (!ctx.dryRun) writeText(path,
|
|
2560
|
+
if (!ctx.dryRun) writeText(path, text2);
|
|
2494
2561
|
}
|
|
2495
2562
|
return {
|
|
2496
2563
|
id: finding.id,
|
|
@@ -2506,15 +2573,15 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
2506
2573
|
id: "bmad.scaffold",
|
|
2507
2574
|
title: "BMAD modules/docs scaffold",
|
|
2508
2575
|
audit: (ctx) => {
|
|
2509
|
-
const sourceRoot =
|
|
2510
|
-
const targetRoot =
|
|
2576
|
+
const sourceRoot = join10(ctx.pjanglerRoot, "templates", "commonproject", "_bmad");
|
|
2577
|
+
const targetRoot = join10(ctx.repoRoot, "_bmad");
|
|
2511
2578
|
const sentinels = [
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2579
|
+
join10("core", "config.yaml"),
|
|
2580
|
+
join10("custom", "config.yaml"),
|
|
2581
|
+
join10("custom", "workflows", "ticket-lifecycle", "workflow.yaml"),
|
|
2582
|
+
join10("bmm", "workflows", "workflow-status", "workflow.yaml")
|
|
2516
2583
|
];
|
|
2517
|
-
const missing = sentinels.filter((file) => existsSync7(
|
|
2584
|
+
const missing = sentinels.filter((file) => existsSync7(join10(sourceRoot, file)) && !existsSync7(join10(targetRoot, file)));
|
|
2518
2585
|
return {
|
|
2519
2586
|
id: "bmad.scaffold",
|
|
2520
2587
|
title: "BMAD modules/docs scaffold",
|
|
@@ -2526,7 +2593,7 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
2526
2593
|
},
|
|
2527
2594
|
migrate: (ctx, finding) => {
|
|
2528
2595
|
const changedFiles = [];
|
|
2529
|
-
copyMissingRecursive(
|
|
2596
|
+
copyMissingRecursive(join10(ctx.pjanglerRoot, "templates", "commonproject", "_bmad"), join10(ctx.repoRoot, "_bmad"), changedFiles, ctx.dryRun);
|
|
2530
2597
|
return {
|
|
2531
2598
|
id: finding.id,
|
|
2532
2599
|
title: finding.title,
|
|
@@ -2548,11 +2615,11 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
2548
2615
|
}
|
|
2549
2616
|
const details = [];
|
|
2550
2617
|
for (const rel of ["role.yaml", "SOUL.md", "hermes", ".gitignore", ".scripts/70-systemd.sh", ".scripts/heartbeat.sh", ".scripts/checkpoint.sh", ".runtime-scaffold/README.md", "runtime/memories/MEMORY.md", "runtime/bloodbank-consumer.py"]) {
|
|
2551
|
-
if (!existsSync7(
|
|
2618
|
+
if (!existsSync7(join10(role.roleDir, rel))) details.push(`missing ${relative(ctx.repoRoot, join10(role.roleDir, rel))}`);
|
|
2552
2619
|
}
|
|
2553
|
-
const gitmodules = safeReadText(
|
|
2620
|
+
const gitmodules = safeReadText(join10(ctx.repoRoot, ".gitmodules")) ?? "";
|
|
2554
2621
|
if (!gitmodules.includes(`agents/hermes/${role.role}/runtime`)) details.push(".gitmodules missing pm runtime submodule entry");
|
|
2555
|
-
if (!profileMetaInheritsDefault(
|
|
2622
|
+
if (!profileMetaInheritsDefault(join10(role.roleDir, "runtime", "profile.yaml"))) {
|
|
2556
2623
|
details.push("runtime/profile.yaml missing inherited default config metadata");
|
|
2557
2624
|
}
|
|
2558
2625
|
const registry = safeReadText(registryPath(ctx.homeDir));
|
|
@@ -2573,21 +2640,21 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
2573
2640
|
if (!role) {
|
|
2574
2641
|
return { id: finding.id, title: finding.title, status: "blocked", summary: "No pm role present", changedFiles, details: [] };
|
|
2575
2642
|
}
|
|
2576
|
-
const templateRoleDir =
|
|
2577
|
-
writeIfDifferent(
|
|
2578
|
-
writeIfDifferent(
|
|
2579
|
-
writeIfDifferent(
|
|
2580
|
-
copyMissingRecursive(
|
|
2581
|
-
copyMissingRecursive(
|
|
2582
|
-
copyMissingRecursive(
|
|
2583
|
-
const promptSource =
|
|
2584
|
-
const promptTarget =
|
|
2643
|
+
const templateRoleDir = join10(ctx.pjanglerRoot, "templates", "hermes-agent", "template");
|
|
2644
|
+
writeIfDifferent(join10(role.roleDir, "SOUL.md"), renderSoul(role), ctx.dryRun, changedFiles);
|
|
2645
|
+
writeIfDifferent(join10(role.roleDir, "hermes"), renderHermesWrapper(role), ctx.dryRun, changedFiles, 493);
|
|
2646
|
+
writeIfDifferent(join10(role.roleDir, ".gitignore"), readText(join10(templateRoleDir, ".gitignore.jinja")).replace(/\{\{ role \}\}/g, role.role), ctx.dryRun, changedFiles);
|
|
2647
|
+
copyMissingRecursive(join10(templateRoleDir, ".runtime-scaffold"), join10(role.roleDir, ".runtime-scaffold"), changedFiles, ctx.dryRun);
|
|
2648
|
+
copyMissingRecursive(join10(templateRoleDir, ".runtime-scaffold"), join10(role.roleDir, "runtime"), changedFiles, ctx.dryRun);
|
|
2649
|
+
copyMissingRecursive(join10(templateRoleDir, ".scripts"), join10(role.roleDir, ".scripts"), changedFiles, ctx.dryRun, (source) => source.endsWith("sentinel.prompt.md.jinja"));
|
|
2650
|
+
const promptSource = join10(templateRoleDir, ".scripts", "sentinel.prompt.md.jinja");
|
|
2651
|
+
const promptTarget = join10(role.roleDir, ".scripts", "sentinel.prompt.md");
|
|
2585
2652
|
if (existsSync7(promptSource) && !existsSync7(promptTarget)) {
|
|
2586
2653
|
const prompt = readText(promptSource).replace(/\{\{ agent_id \}\}/g, role.agentId).replace(/\{\{ role \}\}/g, role.role).replace(/\{\{ target_repo \}\}/g, role.repo).replace(/\{\{ display_name \}\}/g, role.displayName || role.agentId);
|
|
2587
2654
|
writeIfDifferent(promptTarget, prompt, ctx.dryRun, changedFiles);
|
|
2588
2655
|
}
|
|
2589
2656
|
upsertSubmodule(ctx.repoRoot, role, changedFiles, ctx.dryRun);
|
|
2590
|
-
const profileMetaUpdated = upsertInheritedProfileMeta(
|
|
2657
|
+
const profileMetaUpdated = upsertInheritedProfileMeta(join10(role.roleDir, "runtime", "profile.yaml"), changedFiles, ctx.dryRun);
|
|
2591
2658
|
if (profileMetaUpdated) details.push(`updated ${profileMetaUpdated}`);
|
|
2592
2659
|
const registryUpdated = upsertRegistryEntry(role, ctx.homeDir, changedFiles, ctx.dryRun);
|
|
2593
2660
|
if (registryUpdated) details.push(`updated ${registryUpdated}`);
|
|
@@ -2641,9 +2708,9 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
2641
2708
|
return { id: finding.id, title: finding.title, status: "blocked", summary: "systemd --user unavailable on this host", changedFiles, details };
|
|
2642
2709
|
}
|
|
2643
2710
|
for (const role of roles) {
|
|
2644
|
-
const sysDir =
|
|
2711
|
+
const sysDir = join10(ctx.homeDir, ".config", "systemd", "user");
|
|
2645
2712
|
const units = [`hermes-${role.agentId}-gateway.service`, `hermes-${role.agentId}-consumer.service`, `hermes-${role.agentId}-heartbeat.timer`];
|
|
2646
|
-
const allUnitsPresent = units.every((unit) => existsSync7(
|
|
2713
|
+
const allUnitsPresent = units.every((unit) => existsSync7(join10(sysDir, unit)));
|
|
2647
2714
|
if (allUnitsPresent) {
|
|
2648
2715
|
if (ctx.dryRun) {
|
|
2649
2716
|
details.push(`would run: systemctl --user enable --now ${units.join(" ")}`);
|
|
@@ -2655,7 +2722,7 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
2655
2722
|
}
|
|
2656
2723
|
continue;
|
|
2657
2724
|
}
|
|
2658
|
-
for (const script of [
|
|
2725
|
+
for (const script of [join10(role.roleDir, ".scripts", "70-systemd.sh")]) {
|
|
2659
2726
|
if (!script || !existsSync7(script)) continue;
|
|
2660
2727
|
if (ctx.dryRun) {
|
|
2661
2728
|
details.push(`would run: bash ${script}`);
|
|
@@ -2683,7 +2750,7 @@ function writeIfDifferent(path, content, dryRun, changedFiles, mode) {
|
|
|
2683
2750
|
changedFiles.push(path);
|
|
2684
2751
|
if (!dryRun) {
|
|
2685
2752
|
writeText(path, normalized);
|
|
2686
|
-
if (mode)
|
|
2753
|
+
if (mode) chmodSync2(path, mode);
|
|
2687
2754
|
}
|
|
2688
2755
|
}
|
|
2689
2756
|
function getParityRuleIds() {
|
|
@@ -2745,21 +2812,38 @@ function runMigration(selector, repoArg, dryRun, all) {
|
|
|
2745
2812
|
const ruleIds = all ? RULES.map((rule) => rule.id) : selector ? [selector] : [];
|
|
2746
2813
|
return runMigrationForRules(ruleIds, repoArg, dryRun);
|
|
2747
2814
|
}
|
|
2815
|
+
function prettyTimestamp(iso) {
|
|
2816
|
+
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})/.exec(iso);
|
|
2817
|
+
return match ? `${match[1]} ${match[2]} UTC` : iso;
|
|
2818
|
+
}
|
|
2748
2819
|
function formatAuditReport(report) {
|
|
2749
|
-
const
|
|
2820
|
+
const counts = {};
|
|
2821
|
+
for (const rule of report.rules) counts[rule.status] = (counts[rule.status] ?? 0) + 1;
|
|
2822
|
+
const idWidth = report.rules.reduce((width, rule) => Math.max(width, rule.id.length), 0);
|
|
2823
|
+
const tally = [];
|
|
2824
|
+
if (counts.pass) tally.push(green(`${counts.pass} passed`));
|
|
2825
|
+
if (counts.fail) tally.push(red(`${counts.fail} failed`));
|
|
2826
|
+
if (counts.warn) tally.push(yellow(`${counts.warn} warning${counts.warn === 1 ? "" : "s"}`));
|
|
2827
|
+
if (counts.skip) tally.push(gray(`${counts.skip} skipped`));
|
|
2828
|
+
const overall = report.ok ? `${green(glyph.pass)} ${bold("Parity audit passed")}` : `${red(glyph.fail)} ${bold("Parity audit failed")}`;
|
|
2829
|
+
const lines = [""];
|
|
2830
|
+
lines.push(` ${overall}${tally.length ? ` ${dim(glyph.dot)} ${joinDot(tally)}` : ""}`);
|
|
2831
|
+
lines.push(` ${dim(report.repo)} ${dim(glyph.dot)} ${dim(prettyTimestamp(report.auditedAt))}`);
|
|
2832
|
+
lines.push("");
|
|
2750
2833
|
for (const rule of report.rules) {
|
|
2751
|
-
|
|
2752
|
-
|
|
2834
|
+
const style = statusStyle(rule.status);
|
|
2835
|
+
lines.push(` ${style.color(style.glyph)} ${style.color(rule.id.padEnd(idWidth))} ${rule.summary}`);
|
|
2836
|
+
for (const detail of rule.details) lines.push(` ${dim(glyph.arrow)} ${dim(detail)}`);
|
|
2753
2837
|
}
|
|
2754
|
-
|
|
2755
|
-
|
|
2838
|
+
lines.push("");
|
|
2839
|
+
return lines.join("\n");
|
|
2756
2840
|
}
|
|
2757
2841
|
|
|
2758
2842
|
// src/project/index.ts
|
|
2759
2843
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
2760
2844
|
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync2, statSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2761
2845
|
import { homedir as homedir5 } from "node:os";
|
|
2762
|
-
import { basename as basename3, dirname as dirname7, join as
|
|
2846
|
+
import { basename as basename3, dirname as dirname7, join as join11, resolve as resolve2 } from "node:path";
|
|
2763
2847
|
import YAML from "yaml";
|
|
2764
2848
|
var PROJECT_REGISTRY_ENV = "PJ_PROJECT_REGISTRY";
|
|
2765
2849
|
var PROJECT_REGISTRY_SCHEMA_VERSION = 1;
|
|
@@ -2767,10 +2851,10 @@ var KNOWN_SKILL_ROOTS = [
|
|
|
2767
2851
|
"/home/delorenj/code/skillex/all-skills",
|
|
2768
2852
|
"/home/delorenj/code/CoachingAgentFramework/.agents/skills",
|
|
2769
2853
|
"/home/delorenj/code/pjangler/.agents/skills",
|
|
2770
|
-
|
|
2854
|
+
join11(homedir5(), ".codex", "skills")
|
|
2771
2855
|
];
|
|
2772
|
-
function projectRegistryPath(
|
|
2773
|
-
return expandHome(
|
|
2856
|
+
function projectRegistryPath(env2 = process.env) {
|
|
2857
|
+
return expandHome(env2[PROJECT_REGISTRY_ENV] || join11(homedir5(), ".config", "pjangler", "projects.yaml"));
|
|
2774
2858
|
}
|
|
2775
2859
|
function emptyProjectRegistry() {
|
|
2776
2860
|
return { schema_version: PROJECT_REGISTRY_SCHEMA_VERSION, projects: {} };
|
|
@@ -2854,7 +2938,7 @@ function resolveSourceSkillPath(sourceSkill) {
|
|
|
2854
2938
|
if (existsSync8(direct)) return direct;
|
|
2855
2939
|
const name = basename3(sourceSkill);
|
|
2856
2940
|
for (const root of KNOWN_SKILL_ROOTS) {
|
|
2857
|
-
const candidate =
|
|
2941
|
+
const candidate = join11(root, name);
|
|
2858
2942
|
if (existsSync8(candidate)) return candidate;
|
|
2859
2943
|
}
|
|
2860
2944
|
const civilWarLetterifier = "/home/delorenj/code/skillex/all-skills/civilwar-letterifier";
|
|
@@ -2934,7 +3018,7 @@ function planProjectInit(input) {
|
|
|
2934
3018
|
}));
|
|
2935
3019
|
}
|
|
2936
3020
|
actions.push(
|
|
2937
|
-
{ kind: "project.write-manifest", path:
|
|
3021
|
+
{ kind: "project.write-manifest", path: join11(targetDir, ".project.json"), manifest },
|
|
2938
3022
|
{
|
|
2939
3023
|
kind: "plane.create-or-link",
|
|
2940
3024
|
enabled: live,
|
|
@@ -3046,7 +3130,7 @@ function getProject(registry, slug) {
|
|
|
3046
3130
|
return project;
|
|
3047
3131
|
}
|
|
3048
3132
|
function buildCommonProjectCopierAction(input) {
|
|
3049
|
-
const templateDir =
|
|
3133
|
+
const templateDir = join11(input.pjanglerRoot, "templates", "commonproject");
|
|
3050
3134
|
const data = {
|
|
3051
3135
|
project_name: input.projectName,
|
|
3052
3136
|
project_description: input.projectDescription ?? "",
|
|
@@ -3072,7 +3156,7 @@ function buildCommonProjectCopierAction(input) {
|
|
|
3072
3156
|
function resolvePjanglerRoot2() {
|
|
3073
3157
|
let dir = dirname7(new URL(import.meta.url).pathname);
|
|
3074
3158
|
while (dir !== dirname7(dir)) {
|
|
3075
|
-
if (existsSync8(
|
|
3159
|
+
if (existsSync8(join11(dir, "package.json")) && existsSync8(join11(dir, "templates", "commonproject", "copier.yml"))) return dir;
|
|
3076
3160
|
dir = dirname7(dir);
|
|
3077
3161
|
}
|
|
3078
3162
|
return resolve2(process.cwd());
|
|
@@ -3104,7 +3188,7 @@ function validateProjectRecord(project, key) {
|
|
|
3104
3188
|
}
|
|
3105
3189
|
function expandHome(path) {
|
|
3106
3190
|
if (path === "~") return homedir5();
|
|
3107
|
-
if (path.startsWith("~/")) return
|
|
3191
|
+
if (path.startsWith("~/")) return join11(homedir5(), path.slice(2));
|
|
3108
3192
|
return path;
|
|
3109
3193
|
}
|
|
3110
3194
|
function isRecord(value) {
|
|
@@ -3130,7 +3214,7 @@ function resolveTargetDir(targetDir) {
|
|
|
3130
3214
|
function resolvePjanglerRoot3() {
|
|
3131
3215
|
let dir = dirname8(fileURLToPath5(import.meta.url));
|
|
3132
3216
|
while (dir !== dirname8(dir)) {
|
|
3133
|
-
if (existsSync9(
|
|
3217
|
+
if (existsSync9(join12(dir, "package.json")) && existsSync9(join12(dir, "templates", "commonproject", "copier.yml"))) {
|
|
3134
3218
|
return dir;
|
|
3135
3219
|
}
|
|
3136
3220
|
dir = dirname8(dir);
|
|
@@ -3330,7 +3414,7 @@ server.registerTool(
|
|
|
3330
3414
|
const projectSlug = input.projectSlug ?? slugify(input.projectName);
|
|
3331
3415
|
const parentDir = resolve3(input.parentDir ?? process.cwd());
|
|
3332
3416
|
if (!existsSync9(parentDir) || !statSync2(parentDir).isDirectory()) throw new Error(`Parent directory does not exist: ${parentDir}`);
|
|
3333
|
-
const targetDir = resolve3(input.targetDir ??
|
|
3417
|
+
const targetDir = resolve3(input.targetDir ?? join12(parentDir, projectSlug));
|
|
3334
3418
|
const overwrite = input.overwrite ?? input.force ?? false;
|
|
3335
3419
|
const dryRun = input.dryRun ?? true;
|
|
3336
3420
|
const local = input.local ?? true;
|