@delorenj/pjangler 1.2.2 → 1.2.3
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,7 +696,7 @@ 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
|
|
|
@@ -572,7 +725,7 @@ function deriveProfileName(repo, role) {
|
|
|
572
725
|
// src/commands/hermes/PromptForAgentConfig.ts
|
|
573
726
|
function detectTicketProvider(targetDir) {
|
|
574
727
|
try {
|
|
575
|
-
const t = JSON.parse(readFileSync(
|
|
728
|
+
const t = JSON.parse(readFileSync(join4(targetDir, ".project.json"), "utf8"))?.ticket_provider?.type;
|
|
576
729
|
return t === "plane" || t === "linear" || t === "trello" ? t : void 0;
|
|
577
730
|
} catch {
|
|
578
731
|
return void 0;
|
|
@@ -708,7 +861,7 @@ var PromptForAgentConfig = class extends Command {
|
|
|
708
861
|
// src/commands/hermes/RunCopierTemplate.ts
|
|
709
862
|
import { spawnSync } from "node:child_process";
|
|
710
863
|
import { homedir as homedir2 } from "node:os";
|
|
711
|
-
import { join as
|
|
864
|
+
import { join as join5, dirname as dirname3 } from "node:path";
|
|
712
865
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
|
|
713
866
|
import { fileURLToPath } from "node:url";
|
|
714
867
|
import * as p2 from "@clack/prompts";
|
|
@@ -720,8 +873,8 @@ function resolveVendoredTemplate(name) {
|
|
|
720
873
|
return void 0;
|
|
721
874
|
}
|
|
722
875
|
for (let i = 0; i < 8; i++) {
|
|
723
|
-
const candidate =
|
|
724
|
-
if (existsSync3(
|
|
876
|
+
const candidate = join5(dir, "templates", name);
|
|
877
|
+
if (existsSync3(join5(candidate, "copier.yml"))) return candidate;
|
|
725
878
|
const parent = dirname3(dir);
|
|
726
879
|
if (parent === dir) break;
|
|
727
880
|
dir = parent;
|
|
@@ -740,7 +893,7 @@ var RunCopierTemplate = class extends Command {
|
|
|
740
893
|
message: "PromptForAgentConfig must run before RunCopierTemplate (targetRepo/role unset)"
|
|
741
894
|
};
|
|
742
895
|
}
|
|
743
|
-
const roleDir =
|
|
896
|
+
const roleDir = join5(ctx.targetDir, "agents", "hermes", role);
|
|
744
897
|
ctx.roleDir = roleDir;
|
|
745
898
|
ctx.runtimeRepo = `delorenj/agent-hm-${targetRepo}-${role}`;
|
|
746
899
|
const which = spawnSync("which", ["copier"], { encoding: "utf8" });
|
|
@@ -750,7 +903,7 @@ var RunCopierTemplate = class extends Command {
|
|
|
750
903
|
message: "\u2717 copier not found on PATH. Install with: `uv tool install copier` or `pip install copier`"
|
|
751
904
|
};
|
|
752
905
|
}
|
|
753
|
-
if (existsSync3(
|
|
906
|
+
if (existsSync3(join5(roleDir, "role.yaml")) && !ctx.force) {
|
|
754
907
|
if (ctx.yes) {
|
|
755
908
|
ctx.force = true;
|
|
756
909
|
} else {
|
|
@@ -767,7 +920,7 @@ var RunCopierTemplate = class extends Command {
|
|
|
767
920
|
ctx.force = true;
|
|
768
921
|
}
|
|
769
922
|
}
|
|
770
|
-
const
|
|
923
|
+
const env2 = {
|
|
771
924
|
...process.env,
|
|
772
925
|
SKIP_TELEGRAM: "1",
|
|
773
926
|
SKIP_EMAIL: "1",
|
|
@@ -777,9 +930,9 @@ var RunCopierTemplate = class extends Command {
|
|
|
777
930
|
SKIP_BLOODBANK: ctx.skipBloodbank ? "1" : "0",
|
|
778
931
|
SKIP_SYSTEMD: ctx.skipSystemd ? "1" : "0"
|
|
779
932
|
};
|
|
780
|
-
const LOCAL_TEMPLATE =
|
|
933
|
+
const LOCAL_TEMPLATE = join5(homedir2(), "code", "hermes-agent-template");
|
|
781
934
|
const vendored = resolveVendoredTemplate("hermes-agent");
|
|
782
|
-
const templateSrc = process.env.PJANGLER_HERMES_TEMPLATE || vendored || (existsSync3(
|
|
935
|
+
const templateSrc = process.env.PJANGLER_HERMES_TEMPLATE || vendored || (existsSync3(join5(LOCAL_TEMPLATE, "copier.yml")) ? LOCAL_TEMPLATE : HERMES_AGENT_TEMPLATE);
|
|
783
936
|
const args = [
|
|
784
937
|
"copy",
|
|
785
938
|
templateSrc,
|
|
@@ -810,13 +963,13 @@ var RunCopierTemplate = class extends Command {
|
|
|
810
963
|
message: this.formatMessage(`Would run: copier ${args.join(" ")}`)
|
|
811
964
|
};
|
|
812
965
|
}
|
|
813
|
-
mkdirSync3(
|
|
966
|
+
mkdirSync3(join5(ctx.targetDir, "agents", "hermes"), { recursive: true });
|
|
814
967
|
const spinner4 = p2.spinner();
|
|
815
968
|
spinner4.start(`Running copier copy (target: agents/hermes/${role})`);
|
|
816
969
|
const result = spawnSync("copier", args, {
|
|
817
970
|
stdio: "inherit",
|
|
818
971
|
// pass the interactive output through; copier prints its own progress
|
|
819
|
-
env,
|
|
972
|
+
env: env2,
|
|
820
973
|
cwd: ctx.targetDir
|
|
821
974
|
});
|
|
822
975
|
spinner4.stop(result.status === 0 ? "\u2713 copier run complete" : "\u2717 copier failed");
|
|
@@ -835,7 +988,7 @@ var RunCopierTemplate = class extends Command {
|
|
|
835
988
|
|
|
836
989
|
// src/commands/hermes/WireTelegram.ts
|
|
837
990
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
838
|
-
import { join as
|
|
991
|
+
import { join as join6 } from "node:path";
|
|
839
992
|
import { existsSync as existsSync4, unlinkSync } from "node:fs";
|
|
840
993
|
import * as p3 from "@clack/prompts";
|
|
841
994
|
var WireTelegram = class extends Command {
|
|
@@ -924,14 +1077,14 @@ var WireTelegram = class extends Command {
|
|
|
924
1077
|
if (p3.isCancel(allowedAnswer)) {
|
|
925
1078
|
return { success: false, message: "\u2717 Aborted; Telegram step deferred." };
|
|
926
1079
|
}
|
|
927
|
-
const script =
|
|
1080
|
+
const script = join6(roleDir, ".scripts", "30-telegram.sh");
|
|
928
1081
|
if (!existsSync4(script)) {
|
|
929
1082
|
return {
|
|
930
1083
|
success: false,
|
|
931
1084
|
message: `\u2717 ${script} not found. Did copier finish? Re-run with --skip-runtime-repo=0 if you skipped it.`
|
|
932
1085
|
};
|
|
933
1086
|
}
|
|
934
|
-
const marker =
|
|
1087
|
+
const marker = join6(roleDir, ".scripts", ".done-30-telegram");
|
|
935
1088
|
if (existsSync4(marker)) unlinkSync(marker);
|
|
936
1089
|
const spinner4 = p3.spinner();
|
|
937
1090
|
spinner4.start("Verifying token + wiring profile");
|
|
@@ -959,7 +1112,7 @@ function cap(s) {
|
|
|
959
1112
|
|
|
960
1113
|
// src/commands/hermes/WireEmail.ts
|
|
961
1114
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
962
|
-
import { join as
|
|
1115
|
+
import { join as join7 } from "node:path";
|
|
963
1116
|
import { existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
|
|
964
1117
|
import * as p4 from "@clack/prompts";
|
|
965
1118
|
var WireEmail = class extends Command {
|
|
@@ -975,7 +1128,7 @@ var WireEmail = class extends Command {
|
|
|
975
1128
|
if (!targetRepo || !role || !roleDir) {
|
|
976
1129
|
return { success: false, message: "Cannot wire email: missing target_repo/role/roleDir" };
|
|
977
1130
|
}
|
|
978
|
-
const script =
|
|
1131
|
+
const script = join7(roleDir, ".scripts", "50-email.sh");
|
|
979
1132
|
if (!existsSync5(script)) {
|
|
980
1133
|
return { success: false, message: `\u2717 ${script} not found` };
|
|
981
1134
|
}
|
|
@@ -1038,7 +1191,7 @@ var WireEmail = class extends Command {
|
|
|
1038
1191
|
}
|
|
1039
1192
|
}
|
|
1040
1193
|
}
|
|
1041
|
-
const marker =
|
|
1194
|
+
const marker = join7(roleDir, ".scripts", ".done-50-email");
|
|
1042
1195
|
if (existsSync5(marker)) unlinkSync2(marker);
|
|
1043
1196
|
const spinner4 = p4.spinner();
|
|
1044
1197
|
spinner4.start("Creating Cloudflare Email Routing rule");
|
|
@@ -1127,7 +1280,7 @@ var HermesAgentRecipe = class extends Recipe {
|
|
|
1127
1280
|
|
|
1128
1281
|
// src/commands/AgentHooksCommands.ts
|
|
1129
1282
|
import { homedir as homedir3 } from "node:os";
|
|
1130
|
-
import { join as
|
|
1283
|
+
import { join as join8, dirname as dirname4 } from "node:path";
|
|
1131
1284
|
import { existsSync as existsSync6, cpSync, mkdirSync as mkdirSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1132
1285
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1133
1286
|
function resolveTemplateRoot() {
|
|
@@ -1138,16 +1291,16 @@ function resolveTemplateRoot() {
|
|
|
1138
1291
|
try {
|
|
1139
1292
|
let dir = dirname4(fileURLToPath2(import.meta.url));
|
|
1140
1293
|
for (let i = 0; i < 8; i++) {
|
|
1141
|
-
candidates.push(
|
|
1294
|
+
candidates.push(join8(dir, "templates", "commonproject", "template"));
|
|
1142
1295
|
const parent = dirname4(dir);
|
|
1143
1296
|
if (parent === dir) break;
|
|
1144
1297
|
dir = parent;
|
|
1145
1298
|
}
|
|
1146
1299
|
} catch {
|
|
1147
1300
|
}
|
|
1148
|
-
candidates.push(
|
|
1301
|
+
candidates.push(join8(homedir3(), "code", "pjangler", "templates", "commonproject", "template"));
|
|
1149
1302
|
for (const c of candidates) {
|
|
1150
|
-
if (existsSync6(
|
|
1303
|
+
if (existsSync6(join8(c, ".agents", "hooks", "hooks.master.json"))) return c;
|
|
1151
1304
|
}
|
|
1152
1305
|
throw new Error(
|
|
1153
1306
|
"Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
|
|
@@ -1171,8 +1324,8 @@ var CopyAgentHooksTree = class extends Command {
|
|
|
1171
1324
|
const created = [];
|
|
1172
1325
|
const skipped = [];
|
|
1173
1326
|
for (const { rel, dir } of items) {
|
|
1174
|
-
const src =
|
|
1175
|
-
const dest =
|
|
1327
|
+
const src = join8(templateRoot, rel);
|
|
1328
|
+
const dest = join8(this.context.targetDir, rel);
|
|
1176
1329
|
if (!existsSync6(src)) continue;
|
|
1177
1330
|
if (existsSync6(dest) && !this.context.force) {
|
|
1178
1331
|
skipped.push(rel);
|
|
@@ -1197,7 +1350,7 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
|
|
|
1197
1350
|
static CR = "{{config_root}}";
|
|
1198
1351
|
// mise's own runtime var — emitted literally
|
|
1199
1352
|
async invoke() {
|
|
1200
|
-
const misePath =
|
|
1353
|
+
const misePath = join8(this.context.targetDir, "mise.toml");
|
|
1201
1354
|
if (!existsSync6(misePath)) {
|
|
1202
1355
|
return {
|
|
1203
1356
|
success: false,
|
|
@@ -1318,7 +1471,7 @@ var RECIPE_REGISTRY = {
|
|
|
1318
1471
|
name: "mise",
|
|
1319
1472
|
description: "Mise task runner and environment setup",
|
|
1320
1473
|
class: MiseRecipe,
|
|
1321
|
-
commands: ["AddMiseToml", "AddDotenv", "AddMiseTasksStructure", "AddMiseBaseToml", "AddMiseBaseScript"]
|
|
1474
|
+
commands: ["AddMiseToml", "AddDotenv", "AddMiseTasksStructure", "AddMiseBaseToml", "AddMiseBaseScript", "AddMiseCodegraphScript"]
|
|
1322
1475
|
},
|
|
1323
1476
|
docker: {
|
|
1324
1477
|
name: "docker",
|
|
@@ -1408,6 +1561,12 @@ var COMMAND_REGISTRY = {
|
|
|
1408
1561
|
group: "mise",
|
|
1409
1562
|
class: AddMiseBaseScript
|
|
1410
1563
|
},
|
|
1564
|
+
AddMiseCodegraphScript: {
|
|
1565
|
+
name: "AddMiseCodegraphScript",
|
|
1566
|
+
description: "Create .mise/scripts/codegraph.sh enter hook",
|
|
1567
|
+
group: "mise",
|
|
1568
|
+
class: AddMiseCodegraphScript
|
|
1569
|
+
},
|
|
1411
1570
|
AddDotenv: {
|
|
1412
1571
|
name: "AddDotenv",
|
|
1413
1572
|
description: "Create .env.example file",
|
|
@@ -1429,14 +1588,14 @@ function createRecipe(name, context) {
|
|
|
1429
1588
|
|
|
1430
1589
|
// src/utils/version.ts
|
|
1431
1590
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1432
|
-
import { dirname as dirname5, join as
|
|
1591
|
+
import { dirname as dirname5, join as join9 } from "node:path";
|
|
1433
1592
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1434
1593
|
var PJANGLER_VERSION = (() => {
|
|
1435
1594
|
try {
|
|
1436
1595
|
let dir = dirname5(fileURLToPath3(import.meta.url));
|
|
1437
1596
|
for (let i = 0; i < 4; i++) {
|
|
1438
1597
|
try {
|
|
1439
|
-
const raw = readFileSync3(
|
|
1598
|
+
const raw = readFileSync3(join9(dir, "package.json"), "utf8");
|
|
1440
1599
|
return JSON.parse(raw).version ?? "0.0.0";
|
|
1441
1600
|
} catch {
|
|
1442
1601
|
const parent = dirname5(dir);
|
|
@@ -1450,8 +1609,8 @@ var PJANGLER_VERSION = (() => {
|
|
|
1450
1609
|
})();
|
|
1451
1610
|
|
|
1452
1611
|
// 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
|
|
1612
|
+
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";
|
|
1613
|
+
import { basename as basename2, dirname as dirname6, join as join10, relative, resolve } from "node:path";
|
|
1455
1614
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
1456
1615
|
import { homedir as homedir4 } from "node:os";
|
|
1457
1616
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
@@ -1525,7 +1684,7 @@ run = "{{config_root}}/.mise/scripts/versioning.sh sync"
|
|
|
1525
1684
|
function resolvePjanglerRoot() {
|
|
1526
1685
|
let dir = dirname6(fileURLToPath4(import.meta.url));
|
|
1527
1686
|
while (dir !== dirname6(dir)) {
|
|
1528
|
-
if (existsSync7(
|
|
1687
|
+
if (existsSync7(join10(dir, "package.json")) && existsSync7(join10(dir, "templates", "commonproject", "copier.yml"))) {
|
|
1529
1688
|
return dir;
|
|
1530
1689
|
}
|
|
1531
1690
|
dir = dirname6(dir);
|
|
@@ -1588,10 +1747,10 @@ function ensureSymlink(path, target, dryRun) {
|
|
|
1588
1747
|
return { changed: true };
|
|
1589
1748
|
}
|
|
1590
1749
|
function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
1591
|
-
const agentsPath =
|
|
1750
|
+
const agentsPath = join10(repoRoot, "AGENTS.md");
|
|
1592
1751
|
if (existsSync7(agentsPath)) return { changedFiles: [], details: [] };
|
|
1593
1752
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
1594
|
-
const source =
|
|
1753
|
+
const source = join10(repoRoot, file);
|
|
1595
1754
|
if (!existsSync7(source)) continue;
|
|
1596
1755
|
const stat = lstatSync(source);
|
|
1597
1756
|
if (stat.isSymbolicLink()) continue;
|
|
@@ -1601,7 +1760,7 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
|
|
|
1601
1760
|
}
|
|
1602
1761
|
return { changedFiles: [], details: [], blocked: `${file} exists but is not a regular file; cannot promote to AGENTS.md` };
|
|
1603
1762
|
}
|
|
1604
|
-
const readmePath =
|
|
1763
|
+
const readmePath = join10(repoRoot, "README.md");
|
|
1605
1764
|
if (existsSync7(readmePath)) {
|
|
1606
1765
|
const stat = lstatSync(readmePath);
|
|
1607
1766
|
if (!stat.isFile()) return { changedFiles: [], details: [], blocked: "README.md exists but is not a regular file; cannot copy to AGENTS.md" };
|
|
@@ -1641,11 +1800,11 @@ function yamlGet(text3, keyPath) {
|
|
|
1641
1800
|
return "";
|
|
1642
1801
|
}
|
|
1643
1802
|
function discoverRoles(repoRoot) {
|
|
1644
|
-
const rolesDir =
|
|
1803
|
+
const rolesDir = join10(repoRoot, "agents", "hermes");
|
|
1645
1804
|
if (!existsSync7(rolesDir)) return [];
|
|
1646
1805
|
return readdirSync(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
|
|
1647
|
-
const roleDir =
|
|
1648
|
-
const roleYamlPath =
|
|
1806
|
+
const roleDir = join10(rolesDir, entry.name);
|
|
1807
|
+
const roleYamlPath = join10(roleDir, "role.yaml");
|
|
1649
1808
|
if (!existsSync7(roleYamlPath)) return null;
|
|
1650
1809
|
const text3 = readText(roleYamlPath);
|
|
1651
1810
|
const runtimeRepoRaw = yamlGet(text3, "runtime.github_repo");
|
|
@@ -1670,7 +1829,7 @@ function discoverRoles(repoRoot) {
|
|
|
1670
1829
|
}).filter((value) => Boolean(value));
|
|
1671
1830
|
}
|
|
1672
1831
|
function registryPath(homeDir) {
|
|
1673
|
-
return
|
|
1832
|
+
return join10(homeDir, ".hermes", "agents-registry.yaml");
|
|
1674
1833
|
}
|
|
1675
1834
|
function systemctlUser(args) {
|
|
1676
1835
|
const result = spawnSync4("systemctl", ["--user", ...args], { encoding: "utf8" });
|
|
@@ -1681,7 +1840,7 @@ function systemctlUser(args) {
|
|
|
1681
1840
|
};
|
|
1682
1841
|
}
|
|
1683
1842
|
function templateScript(ctx, name) {
|
|
1684
|
-
const source =
|
|
1843
|
+
const source = join10(ctx.pjanglerRoot, ".mise", "scripts", name);
|
|
1685
1844
|
return existsSync7(source) ? readText(source) : void 0;
|
|
1686
1845
|
}
|
|
1687
1846
|
function templateVersioningScript(ctx) {
|
|
@@ -1696,9 +1855,9 @@ function renderGeneratedProjectMiseToml(ctx, template) {
|
|
|
1696
1855
|
return template.replace(/\{%\s*raw\s*%\}([\s\S]*?)\{%\s*endraw\s*%\}/g, "$1").replace(/\{\{\s*project_name\s*\}\}/g, projectName);
|
|
1697
1856
|
}
|
|
1698
1857
|
function ensureMiseTomlFromTemplate(ctx, changedFiles) {
|
|
1699
|
-
const targetPath =
|
|
1858
|
+
const targetPath = join10(ctx.repoRoot, "mise.toml");
|
|
1700
1859
|
if (existsSync7(targetPath)) return false;
|
|
1701
|
-
const sourcePath =
|
|
1860
|
+
const sourcePath = join10(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
|
|
1702
1861
|
if (!existsSync7(sourcePath)) return false;
|
|
1703
1862
|
changedFiles.push(targetPath);
|
|
1704
1863
|
if (!ctx.dryRun) {
|
|
@@ -1707,7 +1866,7 @@ function ensureMiseTomlFromTemplate(ctx, changedFiles) {
|
|
|
1707
1866
|
return true;
|
|
1708
1867
|
}
|
|
1709
1868
|
function templateVersionFilesConf(ctx, repoRoot) {
|
|
1710
|
-
const packageJson =
|
|
1869
|
+
const packageJson = join10(repoRoot, "package.json");
|
|
1711
1870
|
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
1871
|
}
|
|
1713
1872
|
function replaceOrAppendManagedBlock(text3, startMarker, block, beforePattern) {
|
|
@@ -1732,7 +1891,7 @@ var CONDITIONAL_HERMES_PATHS = ["agents/hermes/pm/hermes", "agent/hermes/pm/herm
|
|
|
1732
1891
|
function requiredMisePathEntries(ctx) {
|
|
1733
1892
|
const required = [...BASE_MISE_PATH_ENTRIES];
|
|
1734
1893
|
for (const candidate of CONDITIONAL_HERMES_PATHS) {
|
|
1735
|
-
if (existsSync7(
|
|
1894
|
+
if (existsSync7(join10(ctx.repoRoot, candidate)) && !required.includes(candidate)) required.push(candidate);
|
|
1736
1895
|
}
|
|
1737
1896
|
return required;
|
|
1738
1897
|
}
|
|
@@ -1881,7 +2040,7 @@ function upsertLinkAgentfilesBlock(text3, ctx) {
|
|
|
1881
2040
|
return insertTomlBlockBeforeVersioning(cleaned, LINK_AGENTFILES_WATCH_TASK_BLOCK);
|
|
1882
2041
|
}
|
|
1883
2042
|
function readProjectJson(ctx) {
|
|
1884
|
-
return tryParseJson(safeReadText(
|
|
2043
|
+
return tryParseJson(safeReadText(join10(ctx.repoRoot, ".project.json")));
|
|
1885
2044
|
}
|
|
1886
2045
|
function canonicalProjectJson(ctx) {
|
|
1887
2046
|
const roles = discoverRoles(ctx.repoRoot);
|
|
@@ -1925,8 +2084,8 @@ function canonicalProjectJson(ctx) {
|
|
|
1925
2084
|
};
|
|
1926
2085
|
}
|
|
1927
2086
|
function projectJsonFinding(ctx) {
|
|
1928
|
-
const projectPath =
|
|
1929
|
-
const planeJsonPath =
|
|
2087
|
+
const projectPath = join10(ctx.repoRoot, ".project.json");
|
|
2088
|
+
const planeJsonPath = join10(ctx.repoRoot, ".plane.json");
|
|
1930
2089
|
const details = [];
|
|
1931
2090
|
const data = readProjectJson(ctx);
|
|
1932
2091
|
const roles = discoverRoles(ctx.repoRoot);
|
|
@@ -2040,9 +2199,9 @@ function copyMissingRecursive(sourceDir, targetDir, changedFiles, dryRun, skip)
|
|
|
2040
2199
|
if (!existsSync7(sourceDir)) return;
|
|
2041
2200
|
mkdirSync5(targetDir, { recursive: true });
|
|
2042
2201
|
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
|
2043
|
-
const sourcePath =
|
|
2202
|
+
const sourcePath = join10(sourceDir, entry.name);
|
|
2044
2203
|
if (skip?.(sourcePath)) continue;
|
|
2045
|
-
const targetPath =
|
|
2204
|
+
const targetPath = join10(targetDir, entry.name);
|
|
2046
2205
|
if (entry.isDirectory()) {
|
|
2047
2206
|
copyMissingRecursive(sourcePath, targetPath, changedFiles, dryRun, skip);
|
|
2048
2207
|
continue;
|
|
@@ -2056,7 +2215,7 @@ function copyMissingRecursive(sourceDir, targetDir, changedFiles, dryRun, skip)
|
|
|
2056
2215
|
}
|
|
2057
2216
|
}
|
|
2058
2217
|
function upsertSubmodule(repoRoot, role, changedFiles, dryRun) {
|
|
2059
|
-
const gitmodulesPath =
|
|
2218
|
+
const gitmodulesPath = join10(repoRoot, ".gitmodules");
|
|
2060
2219
|
const repoName = role.runtimeRepo || `agent-hm-${role.repo}-${role.role}`;
|
|
2061
2220
|
const owner = role.runtimeOwner || "delorenj";
|
|
2062
2221
|
const block = `[submodule "agents/hermes/${role.role}/runtime"]
|
|
@@ -2156,13 +2315,13 @@ var RULES = [
|
|
|
2156
2315
|
id: "mise.config-root",
|
|
2157
2316
|
title: "mise config_root + AGENTS link hooks",
|
|
2158
2317
|
audit: (ctx) => {
|
|
2159
|
-
const misePath =
|
|
2318
|
+
const misePath = join10(ctx.repoRoot, "mise.toml");
|
|
2160
2319
|
if (!existsSync7(misePath)) {
|
|
2161
2320
|
return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
|
|
2162
2321
|
}
|
|
2163
2322
|
const text3 = readText(misePath);
|
|
2164
2323
|
const details = [];
|
|
2165
|
-
const linkAgentfilesPath =
|
|
2324
|
+
const linkAgentfilesPath = join10(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
2166
2325
|
if (!existsSync7(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
|
|
2167
2326
|
const pathValues = [...(text3.match(/^_\.path\s*=\s*\[([^\]]*)\]/m)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
|
2168
2327
|
const missingPathValues = requiredMisePathEntries(ctx).filter((value) => !pathValues.includes(value));
|
|
@@ -2181,7 +2340,7 @@ var RULES = [
|
|
|
2181
2340
|
};
|
|
2182
2341
|
},
|
|
2183
2342
|
migrate: (ctx, finding) => {
|
|
2184
|
-
const path =
|
|
2343
|
+
const path = join10(ctx.repoRoot, "mise.toml");
|
|
2185
2344
|
const changedFiles = [];
|
|
2186
2345
|
const details = [];
|
|
2187
2346
|
if (!existsSync7(path)) {
|
|
@@ -2200,7 +2359,7 @@ var RULES = [
|
|
|
2200
2359
|
if (!ctx.dryRun) writeText(path, next);
|
|
2201
2360
|
text3 = next;
|
|
2202
2361
|
}
|
|
2203
|
-
const linkAgentfilesPath =
|
|
2362
|
+
const linkAgentfilesPath = join10(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
|
|
2204
2363
|
const expectedScript = templateLinkAgentfilesScript(ctx);
|
|
2205
2364
|
if (expectedScript === void 0) {
|
|
2206
2365
|
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 +2368,7 @@ var RULES = [
|
|
|
2209
2368
|
changedFiles.push(linkAgentfilesPath);
|
|
2210
2369
|
if (!ctx.dryRun) {
|
|
2211
2370
|
writeText(linkAgentfilesPath, expectedScript);
|
|
2212
|
-
|
|
2371
|
+
chmodSync2(linkAgentfilesPath, 493);
|
|
2213
2372
|
}
|
|
2214
2373
|
}
|
|
2215
2374
|
return {
|
|
@@ -2227,9 +2386,9 @@ var RULES = [
|
|
|
2227
2386
|
title: "managed mise versioning block",
|
|
2228
2387
|
audit: (ctx) => {
|
|
2229
2388
|
const details = [];
|
|
2230
|
-
const misePath =
|
|
2231
|
-
const versioningPath =
|
|
2232
|
-
const manifestPath =
|
|
2389
|
+
const misePath = join10(ctx.repoRoot, "mise.toml");
|
|
2390
|
+
const versioningPath = join10(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
2391
|
+
const manifestPath = join10(ctx.repoRoot, ".mise", "version-files.conf");
|
|
2233
2392
|
const text3 = safeReadText(misePath);
|
|
2234
2393
|
if (!text3?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
|
|
2235
2394
|
if (!existsSync7(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
|
|
@@ -2246,7 +2405,7 @@ var RULES = [
|
|
|
2246
2405
|
migrate: (ctx, finding) => {
|
|
2247
2406
|
const changedFiles = [];
|
|
2248
2407
|
const details = [];
|
|
2249
|
-
const misePath =
|
|
2408
|
+
const misePath = join10(ctx.repoRoot, "mise.toml");
|
|
2250
2409
|
if (!existsSync7(misePath)) {
|
|
2251
2410
|
if (!ensureMiseTomlFromTemplate(ctx, changedFiles)) {
|
|
2252
2411
|
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 +2421,7 @@ var RULES = [
|
|
|
2262
2421
|
if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
|
|
2263
2422
|
if (!ctx.dryRun) writeText(misePath, nextMise);
|
|
2264
2423
|
}
|
|
2265
|
-
const versioningPath =
|
|
2424
|
+
const versioningPath = join10(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
|
|
2266
2425
|
const expectedScript = templateVersioningScript(ctx);
|
|
2267
2426
|
if (expectedScript === void 0) {
|
|
2268
2427
|
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 +2430,10 @@ var RULES = [
|
|
|
2271
2430
|
changedFiles.push(versioningPath);
|
|
2272
2431
|
if (!ctx.dryRun) {
|
|
2273
2432
|
writeText(versioningPath, expectedScript);
|
|
2274
|
-
|
|
2433
|
+
chmodSync2(versioningPath, 493);
|
|
2275
2434
|
}
|
|
2276
2435
|
}
|
|
2277
|
-
const manifestPath =
|
|
2436
|
+
const manifestPath = join10(ctx.repoRoot, ".mise", "version-files.conf");
|
|
2278
2437
|
const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
|
|
2279
2438
|
if (safeReadText(manifestPath) !== expectedManifest) {
|
|
2280
2439
|
changedFiles.push(manifestPath);
|
|
@@ -2294,9 +2453,9 @@ var RULES = [
|
|
|
2294
2453
|
id: "sot.agent-symlinks",
|
|
2295
2454
|
title: "AGENTS/CLAUDE/GEMINI symlink contract",
|
|
2296
2455
|
audit: (ctx) => {
|
|
2297
|
-
const agentsPath =
|
|
2456
|
+
const agentsPath = join10(ctx.repoRoot, "AGENTS.md");
|
|
2298
2457
|
if (!existsSync7(agentsPath)) {
|
|
2299
|
-
const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync7(
|
|
2458
|
+
const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync7(join10(ctx.repoRoot, file)));
|
|
2300
2459
|
if (fallbackSources.length === 0) {
|
|
2301
2460
|
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
2461
|
}
|
|
@@ -2311,7 +2470,7 @@ var RULES = [
|
|
|
2311
2470
|
}
|
|
2312
2471
|
const details = [];
|
|
2313
2472
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
2314
|
-
const full =
|
|
2473
|
+
const full = join10(ctx.repoRoot, file);
|
|
2315
2474
|
const target = readSymlinkTarget(full);
|
|
2316
2475
|
if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
|
|
2317
2476
|
}
|
|
@@ -2335,7 +2494,7 @@ var RULES = [
|
|
|
2335
2494
|
return { id: finding.id, title: finding.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
|
|
2336
2495
|
}
|
|
2337
2496
|
for (const file of ["CLAUDE.md", "GEMINI.md"]) {
|
|
2338
|
-
const full =
|
|
2497
|
+
const full = join10(ctx.repoRoot, file);
|
|
2339
2498
|
const result = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
|
|
2340
2499
|
if (result.blocked) blockedDetails.push(result.blocked);
|
|
2341
2500
|
if (result.changed) changedFiles.push(full);
|
|
@@ -2357,7 +2516,7 @@ var RULES = [
|
|
|
2357
2516
|
migrate: (ctx, finding) => {
|
|
2358
2517
|
const changedFiles = [];
|
|
2359
2518
|
const details = [];
|
|
2360
|
-
const path =
|
|
2519
|
+
const path = join10(ctx.repoRoot, ".project.json");
|
|
2361
2520
|
const existing = readProjectJson(ctx) ?? {};
|
|
2362
2521
|
const canonical = canonicalProjectJson(ctx);
|
|
2363
2522
|
const merged = { ...existing, ...canonical };
|
|
@@ -2367,7 +2526,7 @@ var RULES = [
|
|
|
2367
2526
|
changedFiles.push(path);
|
|
2368
2527
|
if (!ctx.dryRun) writeText(path, expected);
|
|
2369
2528
|
}
|
|
2370
|
-
const planeJson =
|
|
2529
|
+
const planeJson = join10(ctx.repoRoot, ".plane.json");
|
|
2371
2530
|
if (existsSync7(planeJson)) {
|
|
2372
2531
|
const backup = `${planeJson}.migrated-backup`;
|
|
2373
2532
|
if (existsSync7(backup)) {
|
|
@@ -2392,8 +2551,8 @@ var RULES = [
|
|
|
2392
2551
|
title: ".env.op + gitignore secrets contract",
|
|
2393
2552
|
audit: (ctx) => {
|
|
2394
2553
|
const details = [];
|
|
2395
|
-
const envOp = safeReadText(
|
|
2396
|
-
const gitignore = safeReadText(
|
|
2554
|
+
const envOp = safeReadText(join10(ctx.repoRoot, ".env.op"));
|
|
2555
|
+
const gitignore = safeReadText(join10(ctx.repoRoot, ".gitignore"));
|
|
2397
2556
|
if (!envOp) {
|
|
2398
2557
|
details.push(".env.op missing");
|
|
2399
2558
|
} else {
|
|
@@ -2419,12 +2578,12 @@ var RULES = [
|
|
|
2419
2578
|
migrate: (ctx, finding) => {
|
|
2420
2579
|
const changedFiles = [];
|
|
2421
2580
|
const details = [];
|
|
2422
|
-
const envOpPath =
|
|
2581
|
+
const envOpPath = join10(ctx.repoRoot, ".env.op");
|
|
2423
2582
|
if (!existsSync7(envOpPath)) {
|
|
2424
2583
|
changedFiles.push(envOpPath);
|
|
2425
|
-
if (!ctx.dryRun) writeText(envOpPath, readText(
|
|
2584
|
+
if (!ctx.dryRun) writeText(envOpPath, readText(join10(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op")));
|
|
2426
2585
|
}
|
|
2427
|
-
const gitignorePath =
|
|
2586
|
+
const gitignorePath = join10(ctx.repoRoot, ".gitignore");
|
|
2428
2587
|
const gitignore = safeReadText(gitignorePath) ?? "";
|
|
2429
2588
|
const requiredBlock = `# Secrets \u2014 .env is materialized by \`op inject -i .env.op > .env\` on mise enter.
|
|
2430
2589
|
# NEVER commit it. .env.op holds only 1Password references or safe literals and IS committed.
|
|
@@ -2451,7 +2610,7 @@ var RULES = [
|
|
|
2451
2610
|
title: ".copier-answers.yml provenance + drift report",
|
|
2452
2611
|
audit: (ctx) => {
|
|
2453
2612
|
const details = [];
|
|
2454
|
-
const path =
|
|
2613
|
+
const path = join10(ctx.repoRoot, ".copier-answers.yml");
|
|
2455
2614
|
const text3 = safeReadText(path);
|
|
2456
2615
|
const project = readProjectJson(ctx);
|
|
2457
2616
|
if (!text3) {
|
|
@@ -2482,12 +2641,12 @@ var RULES = [
|
|
|
2482
2641
|
const changedFiles = [];
|
|
2483
2642
|
const project = canonicalProjectJson(ctx);
|
|
2484
2643
|
const text3 = `# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
|
|
2485
|
-
_src_path: ${
|
|
2644
|
+
_src_path: ${join10(ctx.pjanglerRoot, "templates", "commonproject")}
|
|
2486
2645
|
project_description: ${String(project.project_description)}
|
|
2487
2646
|
project_name: ${String(project.project_name)}
|
|
2488
2647
|
ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
2489
2648
|
`;
|
|
2490
|
-
const path =
|
|
2649
|
+
const path = join10(ctx.repoRoot, ".copier-answers.yml");
|
|
2491
2650
|
if (safeReadText(path) !== text3) {
|
|
2492
2651
|
changedFiles.push(path);
|
|
2493
2652
|
if (!ctx.dryRun) writeText(path, text3);
|
|
@@ -2506,15 +2665,15 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
2506
2665
|
id: "bmad.scaffold",
|
|
2507
2666
|
title: "BMAD modules/docs scaffold",
|
|
2508
2667
|
audit: (ctx) => {
|
|
2509
|
-
const sourceRoot =
|
|
2510
|
-
const targetRoot =
|
|
2668
|
+
const sourceRoot = join10(ctx.pjanglerRoot, "templates", "commonproject", "_bmad");
|
|
2669
|
+
const targetRoot = join10(ctx.repoRoot, "_bmad");
|
|
2511
2670
|
const sentinels = [
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2671
|
+
join10("core", "config.yaml"),
|
|
2672
|
+
join10("custom", "config.yaml"),
|
|
2673
|
+
join10("custom", "workflows", "ticket-lifecycle", "workflow.yaml"),
|
|
2674
|
+
join10("bmm", "workflows", "workflow-status", "workflow.yaml")
|
|
2516
2675
|
];
|
|
2517
|
-
const missing = sentinels.filter((file) => existsSync7(
|
|
2676
|
+
const missing = sentinels.filter((file) => existsSync7(join10(sourceRoot, file)) && !existsSync7(join10(targetRoot, file)));
|
|
2518
2677
|
return {
|
|
2519
2678
|
id: "bmad.scaffold",
|
|
2520
2679
|
title: "BMAD modules/docs scaffold",
|
|
@@ -2526,7 +2685,7 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
2526
2685
|
},
|
|
2527
2686
|
migrate: (ctx, finding) => {
|
|
2528
2687
|
const changedFiles = [];
|
|
2529
|
-
copyMissingRecursive(
|
|
2688
|
+
copyMissingRecursive(join10(ctx.pjanglerRoot, "templates", "commonproject", "_bmad"), join10(ctx.repoRoot, "_bmad"), changedFiles, ctx.dryRun);
|
|
2530
2689
|
return {
|
|
2531
2690
|
id: finding.id,
|
|
2532
2691
|
title: finding.title,
|
|
@@ -2548,11 +2707,11 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
2548
2707
|
}
|
|
2549
2708
|
const details = [];
|
|
2550
2709
|
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(
|
|
2710
|
+
if (!existsSync7(join10(role.roleDir, rel))) details.push(`missing ${relative(ctx.repoRoot, join10(role.roleDir, rel))}`);
|
|
2552
2711
|
}
|
|
2553
|
-
const gitmodules = safeReadText(
|
|
2712
|
+
const gitmodules = safeReadText(join10(ctx.repoRoot, ".gitmodules")) ?? "";
|
|
2554
2713
|
if (!gitmodules.includes(`agents/hermes/${role.role}/runtime`)) details.push(".gitmodules missing pm runtime submodule entry");
|
|
2555
|
-
if (!profileMetaInheritsDefault(
|
|
2714
|
+
if (!profileMetaInheritsDefault(join10(role.roleDir, "runtime", "profile.yaml"))) {
|
|
2556
2715
|
details.push("runtime/profile.yaml missing inherited default config metadata");
|
|
2557
2716
|
}
|
|
2558
2717
|
const registry = safeReadText(registryPath(ctx.homeDir));
|
|
@@ -2573,21 +2732,21 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
2573
2732
|
if (!role) {
|
|
2574
2733
|
return { id: finding.id, title: finding.title, status: "blocked", summary: "No pm role present", changedFiles, details: [] };
|
|
2575
2734
|
}
|
|
2576
|
-
const templateRoleDir =
|
|
2577
|
-
writeIfDifferent(
|
|
2578
|
-
writeIfDifferent(
|
|
2579
|
-
writeIfDifferent(
|
|
2580
|
-
copyMissingRecursive(
|
|
2581
|
-
copyMissingRecursive(
|
|
2582
|
-
copyMissingRecursive(
|
|
2583
|
-
const promptSource =
|
|
2584
|
-
const promptTarget =
|
|
2735
|
+
const templateRoleDir = join10(ctx.pjanglerRoot, "templates", "hermes-agent", "template");
|
|
2736
|
+
writeIfDifferent(join10(role.roleDir, "SOUL.md"), renderSoul(role), ctx.dryRun, changedFiles);
|
|
2737
|
+
writeIfDifferent(join10(role.roleDir, "hermes"), renderHermesWrapper(role), ctx.dryRun, changedFiles, 493);
|
|
2738
|
+
writeIfDifferent(join10(role.roleDir, ".gitignore"), readText(join10(templateRoleDir, ".gitignore.jinja")).replace(/\{\{ role \}\}/g, role.role), ctx.dryRun, changedFiles);
|
|
2739
|
+
copyMissingRecursive(join10(templateRoleDir, ".runtime-scaffold"), join10(role.roleDir, ".runtime-scaffold"), changedFiles, ctx.dryRun);
|
|
2740
|
+
copyMissingRecursive(join10(templateRoleDir, ".runtime-scaffold"), join10(role.roleDir, "runtime"), changedFiles, ctx.dryRun);
|
|
2741
|
+
copyMissingRecursive(join10(templateRoleDir, ".scripts"), join10(role.roleDir, ".scripts"), changedFiles, ctx.dryRun, (source) => source.endsWith("sentinel.prompt.md.jinja"));
|
|
2742
|
+
const promptSource = join10(templateRoleDir, ".scripts", "sentinel.prompt.md.jinja");
|
|
2743
|
+
const promptTarget = join10(role.roleDir, ".scripts", "sentinel.prompt.md");
|
|
2585
2744
|
if (existsSync7(promptSource) && !existsSync7(promptTarget)) {
|
|
2586
2745
|
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
2746
|
writeIfDifferent(promptTarget, prompt, ctx.dryRun, changedFiles);
|
|
2588
2747
|
}
|
|
2589
2748
|
upsertSubmodule(ctx.repoRoot, role, changedFiles, ctx.dryRun);
|
|
2590
|
-
const profileMetaUpdated = upsertInheritedProfileMeta(
|
|
2749
|
+
const profileMetaUpdated = upsertInheritedProfileMeta(join10(role.roleDir, "runtime", "profile.yaml"), changedFiles, ctx.dryRun);
|
|
2591
2750
|
if (profileMetaUpdated) details.push(`updated ${profileMetaUpdated}`);
|
|
2592
2751
|
const registryUpdated = upsertRegistryEntry(role, ctx.homeDir, changedFiles, ctx.dryRun);
|
|
2593
2752
|
if (registryUpdated) details.push(`updated ${registryUpdated}`);
|
|
@@ -2641,9 +2800,9 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
2641
2800
|
return { id: finding.id, title: finding.title, status: "blocked", summary: "systemd --user unavailable on this host", changedFiles, details };
|
|
2642
2801
|
}
|
|
2643
2802
|
for (const role of roles) {
|
|
2644
|
-
const sysDir =
|
|
2803
|
+
const sysDir = join10(ctx.homeDir, ".config", "systemd", "user");
|
|
2645
2804
|
const units = [`hermes-${role.agentId}-gateway.service`, `hermes-${role.agentId}-consumer.service`, `hermes-${role.agentId}-heartbeat.timer`];
|
|
2646
|
-
const allUnitsPresent = units.every((unit) => existsSync7(
|
|
2805
|
+
const allUnitsPresent = units.every((unit) => existsSync7(join10(sysDir, unit)));
|
|
2647
2806
|
if (allUnitsPresent) {
|
|
2648
2807
|
if (ctx.dryRun) {
|
|
2649
2808
|
details.push(`would run: systemctl --user enable --now ${units.join(" ")}`);
|
|
@@ -2655,7 +2814,7 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
2655
2814
|
}
|
|
2656
2815
|
continue;
|
|
2657
2816
|
}
|
|
2658
|
-
for (const script of [
|
|
2817
|
+
for (const script of [join10(role.roleDir, ".scripts", "70-systemd.sh")]) {
|
|
2659
2818
|
if (!script || !existsSync7(script)) continue;
|
|
2660
2819
|
if (ctx.dryRun) {
|
|
2661
2820
|
details.push(`would run: bash ${script}`);
|
|
@@ -2683,7 +2842,7 @@ function writeIfDifferent(path, content, dryRun, changedFiles, mode) {
|
|
|
2683
2842
|
changedFiles.push(path);
|
|
2684
2843
|
if (!dryRun) {
|
|
2685
2844
|
writeText(path, normalized);
|
|
2686
|
-
if (mode)
|
|
2845
|
+
if (mode) chmodSync2(path, mode);
|
|
2687
2846
|
}
|
|
2688
2847
|
}
|
|
2689
2848
|
function getParityRuleIds() {
|
|
@@ -2745,21 +2904,38 @@ function runMigration(selector, repoArg, dryRun, all) {
|
|
|
2745
2904
|
const ruleIds = all ? RULES.map((rule) => rule.id) : selector ? [selector] : [];
|
|
2746
2905
|
return runMigrationForRules(ruleIds, repoArg, dryRun);
|
|
2747
2906
|
}
|
|
2907
|
+
function prettyTimestamp(iso) {
|
|
2908
|
+
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})/.exec(iso);
|
|
2909
|
+
return match ? `${match[1]} ${match[2]} UTC` : iso;
|
|
2910
|
+
}
|
|
2748
2911
|
function formatAuditReport(report) {
|
|
2749
|
-
const
|
|
2912
|
+
const counts = {};
|
|
2913
|
+
for (const rule of report.rules) counts[rule.status] = (counts[rule.status] ?? 0) + 1;
|
|
2914
|
+
const idWidth = report.rules.reduce((width, rule) => Math.max(width, rule.id.length), 0);
|
|
2915
|
+
const tally = [];
|
|
2916
|
+
if (counts.pass) tally.push(green(`${counts.pass} passed`));
|
|
2917
|
+
if (counts.fail) tally.push(red(`${counts.fail} failed`));
|
|
2918
|
+
if (counts.warn) tally.push(yellow(`${counts.warn} warning${counts.warn === 1 ? "" : "s"}`));
|
|
2919
|
+
if (counts.skip) tally.push(gray(`${counts.skip} skipped`));
|
|
2920
|
+
const overall = report.ok ? `${green(glyph.pass)} ${bold("Parity audit passed")}` : `${red(glyph.fail)} ${bold("Parity audit failed")}`;
|
|
2921
|
+
const lines = [""];
|
|
2922
|
+
lines.push(` ${overall}${tally.length ? ` ${dim(glyph.dot)} ${joinDot(tally)}` : ""}`);
|
|
2923
|
+
lines.push(` ${dim(report.repo)} ${dim(glyph.dot)} ${dim(prettyTimestamp(report.auditedAt))}`);
|
|
2924
|
+
lines.push("");
|
|
2750
2925
|
for (const rule of report.rules) {
|
|
2751
|
-
|
|
2752
|
-
|
|
2926
|
+
const style = statusStyle(rule.status);
|
|
2927
|
+
lines.push(` ${style.color(style.glyph)} ${style.color(rule.id.padEnd(idWidth))} ${rule.summary}`);
|
|
2928
|
+
for (const detail of rule.details) lines.push(` ${dim(glyph.arrow)} ${dim(detail)}`);
|
|
2753
2929
|
}
|
|
2754
|
-
|
|
2755
|
-
|
|
2930
|
+
lines.push("");
|
|
2931
|
+
return lines.join("\n");
|
|
2756
2932
|
}
|
|
2757
2933
|
|
|
2758
2934
|
// src/project/index.ts
|
|
2759
2935
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
2760
2936
|
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync2, statSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2761
2937
|
import { homedir as homedir5 } from "node:os";
|
|
2762
|
-
import { basename as basename3, dirname as dirname7, join as
|
|
2938
|
+
import { basename as basename3, dirname as dirname7, join as join11, resolve as resolve2 } from "node:path";
|
|
2763
2939
|
import YAML from "yaml";
|
|
2764
2940
|
var PROJECT_REGISTRY_ENV = "PJ_PROJECT_REGISTRY";
|
|
2765
2941
|
var PROJECT_REGISTRY_SCHEMA_VERSION = 1;
|
|
@@ -2767,10 +2943,10 @@ var KNOWN_SKILL_ROOTS = [
|
|
|
2767
2943
|
"/home/delorenj/code/skillex/all-skills",
|
|
2768
2944
|
"/home/delorenj/code/CoachingAgentFramework/.agents/skills",
|
|
2769
2945
|
"/home/delorenj/code/pjangler/.agents/skills",
|
|
2770
|
-
|
|
2946
|
+
join11(homedir5(), ".codex", "skills")
|
|
2771
2947
|
];
|
|
2772
|
-
function projectRegistryPath(
|
|
2773
|
-
return expandHome(
|
|
2948
|
+
function projectRegistryPath(env2 = process.env) {
|
|
2949
|
+
return expandHome(env2[PROJECT_REGISTRY_ENV] || join11(homedir5(), ".config", "pjangler", "projects.yaml"));
|
|
2774
2950
|
}
|
|
2775
2951
|
function emptyProjectRegistry() {
|
|
2776
2952
|
return { schema_version: PROJECT_REGISTRY_SCHEMA_VERSION, projects: {} };
|
|
@@ -2854,7 +3030,7 @@ function resolveSourceSkillPath(sourceSkill) {
|
|
|
2854
3030
|
if (existsSync8(direct)) return direct;
|
|
2855
3031
|
const name = basename3(sourceSkill);
|
|
2856
3032
|
for (const root of KNOWN_SKILL_ROOTS) {
|
|
2857
|
-
const candidate =
|
|
3033
|
+
const candidate = join11(root, name);
|
|
2858
3034
|
if (existsSync8(candidate)) return candidate;
|
|
2859
3035
|
}
|
|
2860
3036
|
const civilWarLetterifier = "/home/delorenj/code/skillex/all-skills/civilwar-letterifier";
|
|
@@ -2934,7 +3110,7 @@ function planProjectInit(input) {
|
|
|
2934
3110
|
}));
|
|
2935
3111
|
}
|
|
2936
3112
|
actions.push(
|
|
2937
|
-
{ kind: "project.write-manifest", path:
|
|
3113
|
+
{ kind: "project.write-manifest", path: join11(targetDir, ".project.json"), manifest },
|
|
2938
3114
|
{
|
|
2939
3115
|
kind: "plane.create-or-link",
|
|
2940
3116
|
enabled: live,
|
|
@@ -3046,7 +3222,7 @@ function getProject(registry, slug) {
|
|
|
3046
3222
|
return project;
|
|
3047
3223
|
}
|
|
3048
3224
|
function buildCommonProjectCopierAction(input) {
|
|
3049
|
-
const templateDir =
|
|
3225
|
+
const templateDir = join11(input.pjanglerRoot, "templates", "commonproject");
|
|
3050
3226
|
const data = {
|
|
3051
3227
|
project_name: input.projectName,
|
|
3052
3228
|
project_description: input.projectDescription ?? "",
|
|
@@ -3072,7 +3248,7 @@ function buildCommonProjectCopierAction(input) {
|
|
|
3072
3248
|
function resolvePjanglerRoot2() {
|
|
3073
3249
|
let dir = dirname7(new URL(import.meta.url).pathname);
|
|
3074
3250
|
while (dir !== dirname7(dir)) {
|
|
3075
|
-
if (existsSync8(
|
|
3251
|
+
if (existsSync8(join11(dir, "package.json")) && existsSync8(join11(dir, "templates", "commonproject", "copier.yml"))) return dir;
|
|
3076
3252
|
dir = dirname7(dir);
|
|
3077
3253
|
}
|
|
3078
3254
|
return resolve2(process.cwd());
|
|
@@ -3104,7 +3280,7 @@ function validateProjectRecord(project, key) {
|
|
|
3104
3280
|
}
|
|
3105
3281
|
function expandHome(path) {
|
|
3106
3282
|
if (path === "~") return homedir5();
|
|
3107
|
-
if (path.startsWith("~/")) return
|
|
3283
|
+
if (path.startsWith("~/")) return join11(homedir5(), path.slice(2));
|
|
3108
3284
|
return path;
|
|
3109
3285
|
}
|
|
3110
3286
|
function isRecord(value) {
|
|
@@ -3130,7 +3306,7 @@ function resolveTargetDir(targetDir) {
|
|
|
3130
3306
|
function resolvePjanglerRoot3() {
|
|
3131
3307
|
let dir = dirname8(fileURLToPath5(import.meta.url));
|
|
3132
3308
|
while (dir !== dirname8(dir)) {
|
|
3133
|
-
if (existsSync9(
|
|
3309
|
+
if (existsSync9(join12(dir, "package.json")) && existsSync9(join12(dir, "templates", "commonproject", "copier.yml"))) {
|
|
3134
3310
|
return dir;
|
|
3135
3311
|
}
|
|
3136
3312
|
dir = dirname8(dir);
|
|
@@ -3330,7 +3506,7 @@ server.registerTool(
|
|
|
3330
3506
|
const projectSlug = input.projectSlug ?? slugify(input.projectName);
|
|
3331
3507
|
const parentDir = resolve3(input.parentDir ?? process.cwd());
|
|
3332
3508
|
if (!existsSync9(parentDir) || !statSync2(parentDir).isDirectory()) throw new Error(`Parent directory does not exist: ${parentDir}`);
|
|
3333
|
-
const targetDir = resolve3(input.targetDir ??
|
|
3509
|
+
const targetDir = resolve3(input.targetDir ?? join12(parentDir, projectSlug));
|
|
3334
3510
|
const overwrite = input.overwrite ?? input.force ?? false;
|
|
3335
3511
|
const dryRun = input.dryRun ?? true;
|
|
3336
3512
|
const local = input.local ?? true;
|