@delorenj/pjangler 1.2.33 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +295 -130
- package/dist/mcp-server.js +297 -129
- package/package.json +1 -1
- package/templates/commonproject/template/.agents/hooks/README.md +10 -10
- package/templates/commonproject/template/.agents/hooks/hooks.master.json +1 -1
- package/templates/commonproject/template/.agents/hooks/sync.py +4 -4
- package/templates/commonproject/template/.agents/local.example.json +1 -1
- package/templates/commonproject/template/.mise/scripts/hindsight-setup.sh +1 -1
- package/templates/commonproject/template/mise.toml.jinja +11 -11
- package/templates/hermes-agent/copier.yml +27 -4
- package/templates/hermes-agent/template/.runtime-scaffold/README.md +11 -10
- package/templates/hermes-agent/template/.scripts/01-config.sh +4 -4
- package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +7 -12
- package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +37 -32
- package/templates/hermes-agent/template/.scripts/30-telegram.sh +18 -4
- package/templates/hermes-agent/template/.scripts/70-systemd.sh +72 -12
- package/templates/hermes-agent/template/.scripts/80-registry.sh +13 -3
- package/templates/hermes-agent/template/.scripts/_lib.sh +12 -5
- package/templates/hermes-agent/template/.scripts/config.example.toml +7 -4
- package/templates/hermes-agent/template/.scripts/credential-launch.sh +81 -0
- package/templates/hermes-agent/template/.scripts/heartbeat.sh +2 -1
- package/templates/hermes-agent/template/.scripts/providers/plane.sh +30 -4
- package/templates/hermes-agent/template/.scripts/secret-scan.py +82 -16
- package/templates/hermes-agent/template/SOUL.md.jinja +4 -4
- package/templates/hermes-agent/template/hermes.jinja +36 -7
- package/templates/hermes-agent/template/role.yaml.jinja +15 -5
package/dist/index.js
CHANGED
|
@@ -912,8 +912,26 @@ var OP_INJECT_SCRIPT = `'{{config_root}}/${MATERIALIZE_ENV_SCRIPT_REL}'`;
|
|
|
912
912
|
var PROVISION_PACKS_SCRIPT_REL = ".mise/scripts/provision-packs.py";
|
|
913
913
|
var LEGACY_PROVISION_SCRIPT_REL = ".mise/scripts/provision-bmad-skills.py";
|
|
914
914
|
var SYNC_SKILLS_SCRIPT_REL = ".mise/scripts/sync-skills.py";
|
|
915
|
-
var
|
|
915
|
+
var LINK_AGENTFILES_TASK = "link:agentfiles";
|
|
916
|
+
var SKILLS_SYNC_TASK = "skills:sync";
|
|
917
|
+
var PROVISION_PACKS_TASK = "skills:provision:packs";
|
|
916
918
|
var LEGACY_PROVISION_TASK = "skills-provision-bmad";
|
|
919
|
+
var RETIRED_TASK_RENAMES = [
|
|
920
|
+
["link-agentfiles", LINK_AGENTFILES_TASK],
|
|
921
|
+
["skills-sync", SKILLS_SYNC_TASK],
|
|
922
|
+
["skills-provision-packs", PROVISION_PACKS_TASK],
|
|
923
|
+
["hooks-sync", "hooks:sync"],
|
|
924
|
+
["hooks-check", "hooks:check"],
|
|
925
|
+
["hooks-uninstall", "hooks:uninstall"],
|
|
926
|
+
["hindsight-setup", "hindsight:setup"]
|
|
927
|
+
];
|
|
928
|
+
function taskHeader(name) {
|
|
929
|
+
return `[tasks."${name}"]`;
|
|
930
|
+
}
|
|
931
|
+
function taskHeaderPattern(name) {
|
|
932
|
+
const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
933
|
+
return new RegExp(`^\\[tasks\\.(?:"${esc}"|${esc})\\]$`);
|
|
934
|
+
}
|
|
917
935
|
var PROVISION_PACKS_SCRIPT = `python3 '{{config_root}}/${PROVISION_PACKS_SCRIPT_REL}'`;
|
|
918
936
|
var LEGACY_PROVISION_BMAD_SKILLS_SCRIPT = `python3 '{{config_root}}/${LEGACY_PROVISION_SCRIPT_REL}'`;
|
|
919
937
|
var SYNC_SKILLS_SCRIPT = `python3 '{{config_root}}/${SYNC_SKILLS_SCRIPT_REL}' --scope project`;
|
|
@@ -947,22 +965,22 @@ var LINK_AGENTFILES_HOOK_ENTRIES = [
|
|
|
947
965
|
];
|
|
948
966
|
var LINK_AGENTFILES_WATCH_TASK_BLOCK = `[[watch_files]]
|
|
949
967
|
patterns = ["AGENTS.md"]
|
|
950
|
-
task = "
|
|
968
|
+
task = "${LINK_AGENTFILES_TASK}"
|
|
951
969
|
|
|
952
970
|
[[watch_files]]
|
|
953
971
|
patterns = [".agents/skills.json"]
|
|
954
|
-
task = "
|
|
972
|
+
task = "${SKILLS_SYNC_TASK}"
|
|
955
973
|
|
|
956
|
-
|
|
974
|
+
${taskHeader(LINK_AGENTFILES_TASK)}
|
|
957
975
|
description = "Symlink all agent files to AGENTS.md"
|
|
958
976
|
run = "'{{config_root}}/.mise/scripts/link-agentfiles.sh'"
|
|
959
977
|
|
|
960
|
-
|
|
978
|
+
${taskHeader(SKILLS_SYNC_TASK)}
|
|
961
979
|
description = "Sync skills from manifest to local CLI dirs"
|
|
962
980
|
depends = ["${PROVISION_PACKS_TASK}"]
|
|
963
981
|
run = ${JSON.stringify(SYNC_SKILLS_SCRIPT)}
|
|
964
982
|
|
|
965
|
-
|
|
983
|
+
${taskHeader(PROVISION_PACKS_TASK)}
|
|
966
984
|
description = "Provision every Skillex pack declared in .agents/skills.json"
|
|
967
985
|
run = ${JSON.stringify(PROVISION_PACKS_SCRIPT)}`;
|
|
968
986
|
var VERSIONING_BLOCK = `# >>> mise-versioning >>> (managed block \u2014 do not edit by hand; re-run init to update)
|
|
@@ -1124,6 +1142,7 @@ function discoverRoles(repoRoot) {
|
|
|
1124
1142
|
ticketProviderName: yamlGet(text3, "ticket_provider.name"),
|
|
1125
1143
|
ticketProviderBoardId: yamlGet(text3, "ticket_provider.board_id"),
|
|
1126
1144
|
ticketProviderIdentifier: yamlGet(text3, "plane.identifier"),
|
|
1145
|
+
bloodbankEnabled: yamlGet(text3, "bloodbank.enabled"),
|
|
1127
1146
|
deploymentSystemd: yamlGet(text3, "deployment.systemd"),
|
|
1128
1147
|
legacyReconcileEnabled: yamlGet(text3, "reconcile.enabled"),
|
|
1129
1148
|
legacyReconcileGraceHours: yamlGet(text3, "reconcile.grace_hours"),
|
|
@@ -2326,10 +2345,41 @@ function upsertOpInjectHook(text3) {
|
|
|
2326
2345
|
[OP_INJECT_SCRIPT]
|
|
2327
2346
|
);
|
|
2328
2347
|
}
|
|
2348
|
+
function retiredTaskNameIssues(text3) {
|
|
2349
|
+
const issues = [];
|
|
2350
|
+
for (const [oldName, newName] of RETIRED_TASK_RENAMES) {
|
|
2351
|
+
const esc = oldName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2352
|
+
const present = new RegExp(
|
|
2353
|
+
`^\\[tasks\\.(?:"${esc}"|${esc})\\]|^\\s*task\\s*=\\s*"${esc}"|^\\s*depends\\s*=\\s*\\[[^\\]]*"${esc}"`,
|
|
2354
|
+
"m"
|
|
2355
|
+
).test(text3);
|
|
2356
|
+
if (present) issues.push(`mise.toml still uses the retired task name "${oldName}" (renamed to "${newName}")`);
|
|
2357
|
+
}
|
|
2358
|
+
return issues;
|
|
2359
|
+
}
|
|
2360
|
+
function renameRetiredMiseTasks(text3) {
|
|
2361
|
+
let out = text3;
|
|
2362
|
+
for (const [oldName, newName] of RETIRED_TASK_RENAMES) {
|
|
2363
|
+
const esc = oldName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2364
|
+
out = out.replace(new RegExp(`^\\[tasks\\.(?:"${esc}"|${esc})\\]`, "gm"), taskHeader(newName));
|
|
2365
|
+
out = out.replace(new RegExp(`^(\\s*task\\s*=\\s*)"${esc}"`, "gm"), `$1"${newName}"`);
|
|
2366
|
+
out = out.replace(new RegExp(`\\bmise run ${esc}\\b`, "g"), `mise run ${newName}`);
|
|
2367
|
+
}
|
|
2368
|
+
return out.replace(/^(\s*depends\s*=\s*)(\[[^\]]*\])/gm, (_whole, head, arr) => {
|
|
2369
|
+
let next = arr;
|
|
2370
|
+
for (const [oldName, newName] of RETIRED_TASK_RENAMES) {
|
|
2371
|
+
next = next.split(`"${oldName}"`).join(`"${newName}"`);
|
|
2372
|
+
}
|
|
2373
|
+
return head + next;
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2329
2376
|
function upsertLinkAgentfilesBlock(text3, ctx) {
|
|
2330
|
-
const withPath = upsertMisePath(text3, requiredMisePathEntries(ctx));
|
|
2331
|
-
let cleaned = removeTomlSection(withPath,
|
|
2377
|
+
const withPath = upsertMisePath(renameRetiredMiseTasks(text3), requiredMisePathEntries(ctx));
|
|
2378
|
+
let cleaned = removeTomlSection(withPath, taskHeaderPattern(LINK_AGENTFILES_TASK), /link-agentfiles/, { includePrecedingComments: false });
|
|
2379
|
+
cleaned = removeTomlSection(cleaned, /^\[tasks\.link-agentfiles\]$/, /link-agentfiles/, { includePrecedingComments: false });
|
|
2380
|
+
cleaned = removeTomlSection(cleaned, taskHeaderPattern(SKILLS_SYNC_TASK), void 0, { includePrecedingComments: false });
|
|
2332
2381
|
cleaned = removeTomlSection(cleaned, /^\[tasks\.skills-sync\]$/, void 0, { includePrecedingComments: false });
|
|
2382
|
+
cleaned = removeTomlSection(cleaned, taskHeaderPattern(PROVISION_PACKS_TASK), void 0, { includePrecedingComments: false });
|
|
2333
2383
|
cleaned = removeTomlSection(cleaned, /^\[tasks\.skills-provision-packs\]$/, void 0, { includePrecedingComments: false });
|
|
2334
2384
|
cleaned = removeTomlSection(cleaned, /^\[tasks\.skills-provision-bmad\]$/, void 0, { includePrecedingComments: false });
|
|
2335
2385
|
cleaned = removeTomlSection(cleaned, /^\[tasks\.link-project-skills-to-clis\]$/, void 0, { includePrecedingComments: false });
|
|
@@ -2571,7 +2621,7 @@ You are **${role.displayName || role.agentId}** \u2014 a Hermes agent provisione
|
|
|
2571
2621
|
|
|
2572
2622
|
## Scope
|
|
2573
2623
|
|
|
2574
|
-
You operate only within the working directory of \`${role.repo}\`.
|
|
2624
|
+
You operate only within the working directory of \`${role.repo}\`. HERMES_HOME is the real named profile at \`~/.hermes/profiles/${role.profileName || role.agentId}\`; shared config/auth/skills remain linked to fleet truth while owned state lives in ignored \`./runtime/\`. The launcher supplies the project root through process-local \`TERMINAL_CWD\` and never persists it into shared config.
|
|
2575
2625
|
|
|
2576
2626
|
## Tone
|
|
2577
2627
|
|
|
@@ -2586,51 +2636,62 @@ ${roleSpecific}
|
|
|
2586
2636
|
Your memory is stored locally at \`./runtime/memories/\`. Use durable memory deliberately and keep \`memories/MEMORY.md\` current.
|
|
2587
2637
|
`;
|
|
2588
2638
|
}
|
|
2589
|
-
function renderHermesWrapper(role) {
|
|
2590
|
-
return
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
if
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2639
|
+
function renderHermesWrapper(role, templateRoleDir) {
|
|
2640
|
+
return readText(join3(templateRoleDir, "hermes.jinja")).replace(/\{\{\s*agent_id\s*\}\}/g, role.agentId);
|
|
2641
|
+
}
|
|
2642
|
+
function templateFiles(sourceDir, current = sourceDir) {
|
|
2643
|
+
if (!existsSync2(current)) return [];
|
|
2644
|
+
const files = [];
|
|
2645
|
+
for (const entry of readdirSync2(current, { withFileTypes: true })) {
|
|
2646
|
+
if (entry.name === "__pycache__" || entry.name.endsWith(".pyc") || entry.name.endsWith(".pyo")) continue;
|
|
2647
|
+
const sourcePath = join3(current, entry.name);
|
|
2648
|
+
if (entry.isDirectory()) files.push(...templateFiles(sourceDir, sourcePath));
|
|
2649
|
+
else if (entry.isFile()) files.push(relative2(sourceDir, sourcePath));
|
|
2650
|
+
}
|
|
2651
|
+
return files.sort();
|
|
2652
|
+
}
|
|
2653
|
+
function managedHermesScaffoldRoles(ctx) {
|
|
2654
|
+
const discovered = discoverRoles(ctx.repoRoot);
|
|
2655
|
+
const declared = readDeclaredAgents(ctx).filter((entry) => entry.role === "pm" || entry.role === "director");
|
|
2656
|
+
if (declared.length === 0) {
|
|
2657
|
+
const orchestrators = discovered.filter((role) => role.role === "pm" || role.role === "director");
|
|
2658
|
+
const blockers2 = orchestrators.filter((role) => roleBloodbankEnabled(role) === null).map((role) => `${relative2(ctx.repoRoot, role.roleYamlPath)} bloodbank.enabled must be the strict YAML boolean true or false`);
|
|
2659
|
+
return {
|
|
2660
|
+
roles: orchestrators.filter((role) => roleBloodbankEnabled(role) !== null),
|
|
2661
|
+
blockers: blockers2
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
const roles = [];
|
|
2665
|
+
const blockers = [];
|
|
2666
|
+
for (const entry of declared) {
|
|
2667
|
+
if (!entry.roleDir) {
|
|
2668
|
+
blockers.push(`agents.${entry.agentId}.role_dir missing`);
|
|
2669
|
+
continue;
|
|
2670
|
+
}
|
|
2671
|
+
const roleDir = resolve3(ctx.repoRoot, entry.roleDir);
|
|
2672
|
+
if (!isContainedBy(ctx.repoRoot, roleDir)) {
|
|
2673
|
+
blockers.push(`agents.${entry.agentId}.role_dir resolves outside the project`);
|
|
2674
|
+
continue;
|
|
2675
|
+
}
|
|
2676
|
+
const role = discovered.find((candidate) => resolve3(candidate.roleDir) === roleDir);
|
|
2677
|
+
if (!role) {
|
|
2678
|
+
blockers.push(`agents.${entry.agentId}.role_dir ${entry.roleDir} missing role.yaml`);
|
|
2679
|
+
continue;
|
|
2680
|
+
}
|
|
2681
|
+
if (role.agentId !== entry.agentId || role.role !== entry.role) {
|
|
2682
|
+
blockers.push(`agents.${entry.agentId} identity does not match ${entry.roleDir}/role.yaml`);
|
|
2683
|
+
continue;
|
|
2684
|
+
}
|
|
2685
|
+
if (roleBloodbankEnabled(role) === null) {
|
|
2686
|
+
blockers.push(`${entry.roleDir}/role.yaml bloodbank.enabled must be the strict YAML boolean true or false`);
|
|
2687
|
+
continue;
|
|
2688
|
+
}
|
|
2689
|
+
roles.push(role);
|
|
2690
|
+
}
|
|
2691
|
+
return { roles, blockers };
|
|
2692
|
+
}
|
|
2693
|
+
function renderSentinelPrompt(role, templateRoleDir) {
|
|
2694
|
+
return readText(join3(templateRoleDir, ".scripts", "sentinel.prompt.md.jinja")).replace(/\{\{\s*agent_id\s*\}\}/g, role.agentId).replace(/\{\{\s*role\s*\}\}/g, role.role).replace(/\{\{\s*target_repo\s*\}\}/g, role.repo).replace(/\{\{\s*display_name\s*\}\}/g, role.displayName || role.agentId).replace(/\{\{\s*ticket_provider\s*\}\}/g, role.ticketProviderName || "plane");
|
|
2634
2695
|
}
|
|
2635
2696
|
function copyMissingRecursive(sourceDir, targetDir, changedFiles, dryRun, skip) {
|
|
2636
2697
|
if (!existsSync2(sourceDir)) return;
|
|
@@ -2727,6 +2788,8 @@ function upsertRegistryEntry(role, homeDir, changedFiles, dryRun) {
|
|
|
2727
2788
|
const path = registryPath(homeDir);
|
|
2728
2789
|
const current = safeReadText(path) ?? "# Hermes agent fleet registry.\n# One entry per provisioned agent. Managed by hermes-agent-template/.scripts/80-registry.sh.\nschema_version: 1\nagents: {}\n";
|
|
2729
2790
|
if (current.includes(`${role.agentId}:`)) return null;
|
|
2791
|
+
const enabled = roleBloodbankEnabled(role);
|
|
2792
|
+
if (enabled === null) return null;
|
|
2730
2793
|
const block = ` ${role.agentId}:
|
|
2731
2794
|
repo: ${role.repo}
|
|
2732
2795
|
role: ${role.role}
|
|
@@ -2742,6 +2805,7 @@ function upsertRegistryEntry(role, homeDir, changedFiles, dryRun) {
|
|
|
2742
2805
|
identifier: ${ctxEscape(role.ticketProviderIdentifier)}
|
|
2743
2806
|
runtime_repo: ${ctxEscape(role.runtimeRepo)}
|
|
2744
2807
|
bloodbank:
|
|
2808
|
+
enabled: ${enabled ? "true" : "false"}
|
|
2745
2809
|
gateway_scope: fleet
|
|
2746
2810
|
target_agent_id: ${role.agentId}
|
|
2747
2811
|
systemd:
|
|
@@ -2754,6 +2818,11 @@ ${block}`) : `${current.replace(/\s*$/, "\n")}${block}`;
|
|
|
2754
2818
|
if (!dryRun) writeText(path, next);
|
|
2755
2819
|
return path;
|
|
2756
2820
|
}
|
|
2821
|
+
function roleBloodbankEnabled(role) {
|
|
2822
|
+
if (role.bloodbankEnabled === "" || role.bloodbankEnabled === "false") return false;
|
|
2823
|
+
if (role.bloodbankEnabled === "true") return true;
|
|
2824
|
+
return null;
|
|
2825
|
+
}
|
|
2757
2826
|
function profileMetaInheritsDefault(path) {
|
|
2758
2827
|
const text3 = safeReadText(path);
|
|
2759
2828
|
return Boolean(
|
|
@@ -3220,8 +3289,30 @@ function dropDeclaredAgent(ctx, agentId, changedFiles, details) {
|
|
|
3220
3289
|
if (!ctx.dryRun) writeText(path, `${JSON.stringify(doc, null, 2)}
|
|
3221
3290
|
`);
|
|
3222
3291
|
}
|
|
3223
|
-
function
|
|
3224
|
-
|
|
3292
|
+
function isProfileHomeExpr(assigned) {
|
|
3293
|
+
const bare = assigned.replace(/^["']|["']$/g, "");
|
|
3294
|
+
return bare === "$FLEET_HOME/profiles/$PROFILE_NAME" || /^\$\{?HERMES_FLEET_HOME.*\}?\/profiles\//.test(bare) || /\/\.hermes\/profiles\/[^/]+$/.test(bare);
|
|
3295
|
+
}
|
|
3296
|
+
function rewriteLauncher(text3, profileName) {
|
|
3297
|
+
let next = text3;
|
|
3298
|
+
const assigned = /^HERMES_HOME=(.*)$/m.exec(next)?.[1]?.trim();
|
|
3299
|
+
if (assigned !== void 0 && !isProfileHomeExpr(assigned)) {
|
|
3300
|
+
const name = profileName ? `\${HERMES_PROFILE_NAME:-${profileName}}` : '${HERMES_PROFILE_NAME:-$(basename "$ROLE_DIR")}';
|
|
3301
|
+
next = next.replace(
|
|
3302
|
+
/^HERMES_HOME=(.*)$/m,
|
|
3303
|
+
[
|
|
3304
|
+
`RUNTIME_HOME=$1`,
|
|
3305
|
+
`FLEET_HOME="\${HERMES_FLEET_HOME:-$HOME/.hermes}"`,
|
|
3306
|
+
`PROFILE_NAME="${name}"`,
|
|
3307
|
+
`# Singleton-runtime contract: HERMES_HOME MUST be the named profile dir.`,
|
|
3308
|
+
`HERMES_HOME="$FLEET_HOME/profiles/$PROFILE_NAME"`
|
|
3309
|
+
].join("\n")
|
|
3310
|
+
);
|
|
3311
|
+
next = next.replace(
|
|
3312
|
+
/if \[\[ ! -d "\$HERMES_HOME" \]\]; then\n(\s*)echo "hermes: local runtime not provisioned at \$HERMES_HOME"/,
|
|
3313
|
+
'if [[ ! -d "$RUNTIME_HOME" ]]; then\n$1echo "hermes: local runtime not provisioned at $RUNTIME_HOME"'
|
|
3314
|
+
);
|
|
3315
|
+
}
|
|
3225
3316
|
next = next.replace(/^HERMES_OAUTH_FILE=.*\n/m, "");
|
|
3226
3317
|
next = next.replace(/\s*HERMES_OAUTH_FILE="\$HERMES_OAUTH_FILE"/g, "");
|
|
3227
3318
|
next = next.replace(
|
|
@@ -3770,7 +3861,8 @@ function createMiseChecks() {
|
|
|
3770
3861
|
if (missingPathValues.length) details.push(`[env]._.path should include ${missingPathValues.join(", ")}`);
|
|
3771
3862
|
if (!text3.includes("'{{config_root}}/.mise/scripts/link-agentfiles.sh'")) details.push("link-agentfiles hook must use single-quoted {{config_root}} guard");
|
|
3772
3863
|
if (!text3.includes('patterns = ["AGENTS.md"]')) details.push("watch_files must monitor AGENTS.md");
|
|
3773
|
-
if (!text3.includes(
|
|
3864
|
+
if (!text3.includes(`task = "${LINK_AGENTFILES_TASK}"`)) details.push(`watch_files must dispatch the ${LINK_AGENTFILES_TASK} task`);
|
|
3865
|
+
details.push(...retiredTaskNameIssues(text3));
|
|
3774
3866
|
return {
|
|
3775
3867
|
id: "mise.config-root",
|
|
3776
3868
|
title: "mise config_root + AGENTS link hooks",
|
|
@@ -3818,7 +3910,7 @@ function createMiseChecks() {
|
|
|
3818
3910
|
status: changedFiles.length ? "applied" : "noop",
|
|
3819
3911
|
summary: changedFiles.length ? "Updated mise AGENTS-linking contract" : "No changes required",
|
|
3820
3912
|
changedFiles,
|
|
3821
|
-
details: changedFiles.length ? [
|
|
3913
|
+
details: changedFiles.length ? [`Normalized hooks/watch_files/tasks."${LINK_AGENTFILES_TASK}" block and script`] : []
|
|
3822
3914
|
};
|
|
3823
3915
|
}
|
|
3824
3916
|
},
|
|
@@ -4029,8 +4121,9 @@ function createAgentHooksChecks() {
|
|
|
4029
4121
|
details.push("mise.toml still invokes the missing bare sync-skills.py executable");
|
|
4030
4122
|
}
|
|
4031
4123
|
if (!mise?.includes('patterns = [".agents/skills.json"]')) details.push("mise.toml should watch .agents/skills.json");
|
|
4032
|
-
if (!mise?.includes(
|
|
4033
|
-
if (!mise?.includes(`depends = ["${PROVISION_PACKS_TASK}"]`)) details.push(
|
|
4124
|
+
if (!mise?.includes(taskHeader(SKILLS_SYNC_TASK))) details.push(`mise.toml should define a ${SKILLS_SYNC_TASK} task`);
|
|
4125
|
+
if (!mise?.includes(`depends = ["${PROVISION_PACKS_TASK}"]`)) details.push(`${SKILLS_SYNC_TASK} task should depend on ${PROVISION_PACKS_TASK}`);
|
|
4126
|
+
if (mise) details.push(...retiredTaskNameIssues(mise));
|
|
4034
4127
|
for (const [rel, label] of [
|
|
4035
4128
|
[PROVISION_PACKS_SCRIPT_REL, "Skillex pack provisioning script"],
|
|
4036
4129
|
[SYNC_SKILLS_SCRIPT_REL, "Project-local skills sync engine"]
|
|
@@ -4923,75 +5016,90 @@ function createHermesChecks() {
|
|
|
4923
5016
|
return [
|
|
4924
5017
|
{
|
|
4925
5018
|
id: "hermes.pm-scaffold",
|
|
4926
|
-
title: "Hermes
|
|
5019
|
+
title: "Hermes orchestrator scaffold parity",
|
|
4927
5020
|
audit: (ctx) => {
|
|
4928
|
-
const
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
return { id: "hermes.pm-scaffold", title: "Hermes PM scaffold parity", status: "skip", summary: "No pm role present", details: [], fixable: false };
|
|
4932
|
-
}
|
|
4933
|
-
const details = [];
|
|
4934
|
-
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"]) {
|
|
4935
|
-
if (!existsSync2(join3(role.roleDir, rel))) details.push(`missing ${relative2(ctx.repoRoot, join3(role.roleDir, rel))}`);
|
|
5021
|
+
const selection = managedHermesScaffoldRoles(ctx);
|
|
5022
|
+
if (selection.roles.length === 0 && selection.blockers.length === 0) {
|
|
5023
|
+
return { id: "hermes.pm-scaffold", title: "Hermes orchestrator scaffold parity", status: "skip", summary: "No provisioned pm or director role present", details: [], fixable: false };
|
|
4936
5024
|
}
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
5025
|
+
const details = [...selection.blockers];
|
|
5026
|
+
const templateRoleDir = join3(ctx.pjanglerRoot, "templates", "hermes-agent", "template");
|
|
5027
|
+
const managedScripts = templateFiles(join3(templateRoleDir, ".scripts")).filter((rel) => rel !== "sentinel.prompt.md.jinja");
|
|
5028
|
+
for (const role of selection.roles) {
|
|
5029
|
+
const prefix = role.agentId || role.role;
|
|
5030
|
+
for (const rel of ["role.yaml", "SOUL.md", ".runtime-scaffold/README.md", "runtime/memories/MEMORY.md"]) {
|
|
5031
|
+
if (!existsSync2(join3(role.roleDir, rel))) details.push(`${prefix}: missing ${relative2(ctx.repoRoot, join3(role.roleDir, rel))}`);
|
|
5032
|
+
}
|
|
5033
|
+
const wrapper = join3(role.roleDir, "hermes");
|
|
5034
|
+
const expectedWrapper = renderHermesWrapper(role, templateRoleDir);
|
|
5035
|
+
if (!existsSync2(wrapper)) details.push(`${prefix}: missing ${relative2(ctx.repoRoot, wrapper)}`);
|
|
5036
|
+
else if (safeReadText(wrapper) !== expectedWrapper) details.push(`${prefix}: stale ${relative2(ctx.repoRoot, wrapper)}`);
|
|
5037
|
+
const expectedIgnore = readText(join3(templateRoleDir, ".gitignore.jinja")).replace(/\{\{\s*role\s*\}\}/g, role.role);
|
|
5038
|
+
const ignorePath = join3(role.roleDir, ".gitignore");
|
|
5039
|
+
if (!existsSync2(ignorePath)) details.push(`${prefix}: missing ${relative2(ctx.repoRoot, ignorePath)}`);
|
|
5040
|
+
else if (safeReadText(ignorePath) !== expectedIgnore) details.push(`${prefix}: stale ${relative2(ctx.repoRoot, ignorePath)}`);
|
|
5041
|
+
for (const rel of managedScripts) {
|
|
5042
|
+
const source = join3(templateRoleDir, ".scripts", rel);
|
|
5043
|
+
const target = join3(role.roleDir, ".scripts", rel);
|
|
5044
|
+
if (!existsSync2(target)) details.push(`${prefix}: missing ${relative2(ctx.repoRoot, target)}`);
|
|
5045
|
+
else if (safeReadText(target) !== readText(source)) details.push(`${prefix}: stale ${relative2(ctx.repoRoot, target)}`);
|
|
5046
|
+
}
|
|
5047
|
+
const promptPath = join3(role.roleDir, ".scripts", "sentinel.prompt.md");
|
|
5048
|
+
if (!existsSync2(promptPath)) details.push(`${prefix}: missing ${relative2(ctx.repoRoot, promptPath)}`);
|
|
5049
|
+
else if (safeReadText(promptPath) !== renderSentinelPrompt(role, templateRoleDir)) details.push(`${prefix}: stale ${relative2(ctx.repoRoot, promptPath)}`);
|
|
5050
|
+
if (hasRuntimeSubmoduleMapping(ctx.repoRoot, role)) details.push(`${prefix}: .gitmodules contains retired ${role.role} runtime submodule mapping`);
|
|
5051
|
+
if (!profileMetaInheritsDefault(join3(role.roleDir, "runtime", "profile.yaml"))) details.push(`${prefix}: runtime/profile.yaml missing inherited default config metadata`);
|
|
5052
|
+
const registry = safeReadText(registryPath(ctx.homeDir));
|
|
5053
|
+
if (!registry?.includes(`${role.agentId}:`)) details.push(`fleet registry missing ${role.agentId}`);
|
|
4942
5054
|
}
|
|
4943
|
-
const registry = safeReadText(registryPath(ctx.homeDir));
|
|
4944
|
-
if (!registry?.includes(`${role.agentId}:`)) details.push(`fleet registry missing ${role.agentId}`);
|
|
4945
5055
|
return {
|
|
4946
5056
|
id: "hermes.pm-scaffold",
|
|
4947
|
-
title: "Hermes
|
|
5057
|
+
title: "Hermes orchestrator scaffold parity",
|
|
4948
5058
|
status: details.length === 0 ? "pass" : "fail",
|
|
4949
|
-
summary: details.length === 0 ?
|
|
5059
|
+
summary: details.length === 0 ? `${selection.roles.length} orchestrator scaffold(s) verified` : `${details.length} orchestrator scaffold issue(s) detected`,
|
|
4950
5060
|
details,
|
|
4951
|
-
fixable:
|
|
5061
|
+
fixable: selection.blockers.length === 0
|
|
4952
5062
|
};
|
|
4953
5063
|
},
|
|
4954
5064
|
migrate: (ctx, finding) => {
|
|
4955
|
-
const
|
|
5065
|
+
const selection = managedHermesScaffoldRoles(ctx);
|
|
4956
5066
|
const changedFiles = [];
|
|
4957
5067
|
const details = [];
|
|
4958
|
-
if (
|
|
4959
|
-
return { id: finding.id, title: finding.title, status: "blocked", summary: "
|
|
5068
|
+
if (selection.blockers.length > 0) {
|
|
5069
|
+
return { id: finding.id, title: finding.title, status: "blocked", summary: "Provisioned orchestrator manifest is invalid", changedFiles, details: selection.blockers };
|
|
4960
5070
|
}
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
if (!retirement.ok) {
|
|
4964
|
-
return {
|
|
4965
|
-
id: finding.id,
|
|
4966
|
-
title: finding.title,
|
|
4967
|
-
status: "blocked",
|
|
4968
|
-
summary: "Failed to retire PM runtime submodule metadata safely",
|
|
4969
|
-
changedFiles,
|
|
4970
|
-
details: [retirement.error ?? "unknown runtime retirement failure"]
|
|
4971
|
-
};
|
|
5071
|
+
if (selection.roles.length === 0) {
|
|
5072
|
+
return { id: finding.id, title: finding.title, status: "blocked", summary: "No provisioned pm or director role present", changedFiles, details: [] };
|
|
4972
5073
|
}
|
|
4973
5074
|
const templateRoleDir = join3(ctx.pjanglerRoot, "templates", "hermes-agent", "template");
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
5075
|
+
const managedScripts = templateFiles(join3(templateRoleDir, ".scripts")).filter((rel) => rel !== "sentinel.prompt.md.jinja");
|
|
5076
|
+
for (const role of selection.roles) {
|
|
5077
|
+
const retirement = retireRuntimeSubmodule(ctx.repoRoot, role, changedFiles, ctx.dryRun);
|
|
5078
|
+
details.push(...retirement.details);
|
|
5079
|
+
if (!retirement.ok) {
|
|
5080
|
+
return { id: finding.id, title: finding.title, status: "blocked", summary: `Failed to retire ${role.role} runtime submodule metadata safely`, changedFiles, details: [retirement.error ?? "unknown runtime retirement failure"] };
|
|
5081
|
+
}
|
|
5082
|
+
if (!existsSync2(join3(role.roleDir, "SOUL.md"))) writeIfDifferent(join3(role.roleDir, "SOUL.md"), renderSoul(role), ctx.dryRun, changedFiles);
|
|
5083
|
+
writeIfDifferent(join3(role.roleDir, "hermes"), renderHermesWrapper(role, templateRoleDir), ctx.dryRun, changedFiles, 493);
|
|
5084
|
+
writeIfDifferent(join3(role.roleDir, ".gitignore"), readText(join3(templateRoleDir, ".gitignore.jinja")).replace(/\{\{\s*role\s*\}\}/g, role.role), ctx.dryRun, changedFiles);
|
|
5085
|
+
copyMissingRecursive(join3(templateRoleDir, ".runtime-scaffold"), join3(role.roleDir, ".runtime-scaffold"), changedFiles, ctx.dryRun);
|
|
5086
|
+
copyMissingRecursive(join3(templateRoleDir, ".runtime-scaffold"), join3(role.roleDir, "runtime"), changedFiles, ctx.dryRun);
|
|
5087
|
+
for (const rel of managedScripts) {
|
|
5088
|
+
const source = join3(templateRoleDir, ".scripts", rel);
|
|
5089
|
+
const executable = (lstatSync2(source).mode & 73) !== 0;
|
|
5090
|
+
writeIfDifferent(join3(role.roleDir, ".scripts", rel), readText(source), ctx.dryRun, changedFiles, executable ? 493 : void 0);
|
|
5091
|
+
}
|
|
5092
|
+
writeIfDifferent(join3(role.roleDir, ".scripts", "sentinel.prompt.md"), renderSentinelPrompt(role, templateRoleDir), ctx.dryRun, changedFiles);
|
|
5093
|
+
const profileMetaUpdated = upsertInheritedProfileMeta(join3(role.roleDir, "runtime", "profile.yaml"), changedFiles, ctx.dryRun);
|
|
5094
|
+
if (profileMetaUpdated) details.push(`updated ${profileMetaUpdated}`);
|
|
5095
|
+
const registryUpdated = upsertRegistryEntry(role, ctx.homeDir, changedFiles, ctx.dryRun);
|
|
5096
|
+
if (registryUpdated) details.push(`updated ${registryUpdated}`);
|
|
5097
|
+
}
|
|
4990
5098
|
return {
|
|
4991
5099
|
id: finding.id,
|
|
4992
5100
|
title: finding.title,
|
|
4993
5101
|
status: changedFiles.length ? "applied" : "noop",
|
|
4994
|
-
summary: changedFiles.length ?
|
|
5102
|
+
summary: changedFiles.length ? `${selection.roles.length} orchestrator scaffold(s) normalized` : "No changes required",
|
|
4995
5103
|
changedFiles,
|
|
4996
5104
|
details
|
|
4997
5105
|
};
|
|
@@ -5298,8 +5406,9 @@ function createHermesChecks() {
|
|
|
5298
5406
|
if (text3 === null) {
|
|
5299
5407
|
details.push(`launcher missing: ${relative2(ctx.repoRoot, launcher)}`);
|
|
5300
5408
|
} else {
|
|
5301
|
-
|
|
5302
|
-
|
|
5409
|
+
const assigned = /^HERMES_HOME=(.*)$/m.exec(text3)?.[1]?.trim();
|
|
5410
|
+
if (assigned !== void 0 && !isProfileHomeExpr(assigned)) {
|
|
5411
|
+
details.push(`launcher sets HERMES_HOME=${assigned} instead of the named profile dir (disables shared auth + profile identity): ${relative2(ctx.repoRoot, launcher)}`);
|
|
5303
5412
|
}
|
|
5304
5413
|
if (/HERMES_OAUTH_FILE/.test(text3)) {
|
|
5305
5414
|
details.push(`launcher exports HERMES_OAUTH_FILE, which Hermes does not implement (dead config): ${relative2(ctx.repoRoot, launcher)}`);
|
|
@@ -5337,9 +5446,16 @@ function createHermesChecks() {
|
|
|
5337
5446
|
const launcher = join3(role.roleDir, "hermes");
|
|
5338
5447
|
const text3 = safeReadText(launcher);
|
|
5339
5448
|
if (text3 !== null) {
|
|
5340
|
-
const
|
|
5449
|
+
const before = /^HERMES_HOME=(.*)$/m.exec(text3)?.[1]?.trim();
|
|
5450
|
+
const rewritten = rewriteLauncher(text3, role.profileName || role.agentId);
|
|
5341
5451
|
if (rewritten !== text3) {
|
|
5342
|
-
|
|
5452
|
+
const rel = relative2(ctx.repoRoot, launcher);
|
|
5453
|
+
if (before !== void 0 && !isProfileHomeExpr(before)) {
|
|
5454
|
+
details.push(`rewrite launcher HERMES_HOME ${before} -> ${plan.profileDir}: ${rel}`);
|
|
5455
|
+
}
|
|
5456
|
+
if (/HERMES_OAUTH_FILE/.test(text3)) {
|
|
5457
|
+
details.push(`strip dead HERMES_OAUTH_FILE export: ${rel}`);
|
|
5458
|
+
}
|
|
5343
5459
|
writeIfDifferent(launcher, rewritten, ctx.dryRun, changedFiles, 493);
|
|
5344
5460
|
}
|
|
5345
5461
|
}
|
|
@@ -5376,6 +5492,7 @@ function createHermesChecks() {
|
|
|
5376
5492
|
audit: (ctx) => {
|
|
5377
5493
|
const roles = discoverRoles(ctx.repoRoot);
|
|
5378
5494
|
const details = [];
|
|
5495
|
+
let malformedRoleGate = false;
|
|
5379
5496
|
const registryPath2 = join3(ctx.homeDir, ".hermes", "agents-registry.yaml");
|
|
5380
5497
|
const registry = readRegistry(registryPath2);
|
|
5381
5498
|
if (!registry) {
|
|
@@ -5412,6 +5529,11 @@ function createHermesChecks() {
|
|
|
5412
5529
|
details.push(`.project.json declares agent "${extra}" that no role.yaml claims`);
|
|
5413
5530
|
}
|
|
5414
5531
|
for (const role of roles) {
|
|
5532
|
+
const expectedBloodbankEnabled = roleBloodbankEnabled(role);
|
|
5533
|
+
if (expectedBloodbankEnabled === null) {
|
|
5534
|
+
details.push(`${relative2(ctx.repoRoot, role.roleYamlPath)} bloodbank.enabled must be the strict YAML boolean true or false`);
|
|
5535
|
+
malformedRoleGate = true;
|
|
5536
|
+
}
|
|
5415
5537
|
const entry = registry[role.agentId];
|
|
5416
5538
|
if (!entry) {
|
|
5417
5539
|
details.push(`registry is missing an entry for ${role.agentId}`);
|
|
@@ -5426,6 +5548,11 @@ function createHermesChecks() {
|
|
|
5426
5548
|
details.push(`registry hermes.bin for ${role.agentId} does not exist: ${bin}`);
|
|
5427
5549
|
}
|
|
5428
5550
|
const bloodbank = entry.bloodbank ?? {};
|
|
5551
|
+
if (typeof bloodbank.enabled !== "boolean") {
|
|
5552
|
+
details.push(`registry entry for ${role.agentId} bloodbank.enabled must be a strict boolean`);
|
|
5553
|
+
} else if (expectedBloodbankEnabled !== null && bloodbank.enabled !== expectedBloodbankEnabled) {
|
|
5554
|
+
details.push(`registry entry for ${role.agentId} bloodbank.enabled must match explicit role value ${expectedBloodbankEnabled}`);
|
|
5555
|
+
}
|
|
5429
5556
|
if (bloodbank.gateway_scope !== "fleet" || bloodbank.target_agent_id !== role.agentId) {
|
|
5430
5557
|
details.push(`registry entry for ${role.agentId} must advertise bloodbank { gateway_scope: fleet, target_agent_id: ${role.agentId} }`);
|
|
5431
5558
|
}
|
|
@@ -5446,7 +5573,7 @@ function createHermesChecks() {
|
|
|
5446
5573
|
status: details.length === 0 ? "pass" : "fail",
|
|
5447
5574
|
summary: details.length === 0 ? "Fleet registry is in parity" : `${details.length} registry parity issue(s) detected`,
|
|
5448
5575
|
details,
|
|
5449
|
-
fixable:
|
|
5576
|
+
fixable: !malformedRoleGate
|
|
5450
5577
|
};
|
|
5451
5578
|
},
|
|
5452
5579
|
migrate: (ctx, finding) => {
|
|
@@ -5458,6 +5585,19 @@ function createHermesChecks() {
|
|
|
5458
5585
|
return { id: finding.id, title: finding.title, status: "blocked", summary: `registry unreadable at ${registryPath2}`, changedFiles, details };
|
|
5459
5586
|
}
|
|
5460
5587
|
const roles = discoverRoles(ctx.repoRoot);
|
|
5588
|
+
const malformedRoleGates = roles.filter((role) => roleBloodbankEnabled(role) === null);
|
|
5589
|
+
if (malformedRoleGates.length > 0) {
|
|
5590
|
+
return {
|
|
5591
|
+
id: finding.id,
|
|
5592
|
+
title: finding.title,
|
|
5593
|
+
status: "blocked",
|
|
5594
|
+
summary: "Registry parity is blocked by malformed role Bloodbank gates",
|
|
5595
|
+
changedFiles,
|
|
5596
|
+
details: malformedRoleGates.map(
|
|
5597
|
+
(role) => `${relative2(ctx.repoRoot, role.roleYamlPath)} bloodbank.enabled must be the strict YAML boolean true or false`
|
|
5598
|
+
)
|
|
5599
|
+
};
|
|
5600
|
+
}
|
|
5461
5601
|
const missingRoles = roles.filter((role) => !raw.includes(`${role.agentId}:`));
|
|
5462
5602
|
for (const role of missingRoles) {
|
|
5463
5603
|
const updated = upsertRegistryEntry(role, ctx.homeDir, changedFiles, ctx.dryRun);
|
|
@@ -5521,9 +5661,10 @@ function createHermesChecks() {
|
|
|
5521
5661
|
dirty = true;
|
|
5522
5662
|
}
|
|
5523
5663
|
const bloodbank = entry.bloodbank ?? {};
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5664
|
+
const expectedBloodbankEnabled = roleBloodbankEnabled(role) ?? false;
|
|
5665
|
+
if (bloodbank.enabled !== expectedBloodbankEnabled || bloodbank.gateway_scope !== "fleet" || bloodbank.target_agent_id !== role.agentId) {
|
|
5666
|
+
details.push(`normalize fleet bloodbank routing for ${role.agentId} with enabled=${expectedBloodbankEnabled}`);
|
|
5667
|
+
entry.bloodbank = { ...bloodbank, enabled: expectedBloodbankEnabled, gateway_scope: "fleet", target_agent_id: role.agentId };
|
|
5527
5668
|
dirty = true;
|
|
5528
5669
|
}
|
|
5529
5670
|
const systemd = entry.systemd;
|
|
@@ -5756,10 +5897,10 @@ var AgentHooksRecipe = class extends Recipe {
|
|
|
5756
5897
|
printNextSteps() {
|
|
5757
5898
|
console.log("\u{1FA9D} Agent-hooks layer installed!");
|
|
5758
5899
|
console.log(" Next steps:");
|
|
5759
|
-
console.log(" 1. mise run skills
|
|
5760
|
-
console.log(" 2. mise run hooks
|
|
5900
|
+
console.log(" 1. mise run skills:sync # sync .agents/skills.json into local CLI dirs");
|
|
5901
|
+
console.log(" 2. mise run hooks:sync # generate .claude/settings.json + inject codex/kimi/hermes");
|
|
5761
5902
|
console.log(" 3. git add .claude/settings.json .agents/hooks .agents/skills.json && commit (codex/kimi/hermes are per-dev)");
|
|
5762
|
-
console.log(" 4. mise run hindsight
|
|
5903
|
+
console.log(" 4. mise run hindsight:setup # set HINDSIGHT_OP_KEY_REF to your 1Password item first");
|
|
5763
5904
|
console.log(" 5. Optional per-dev hook opt-out: copy .agents/local.example.json -> .agents/local.json");
|
|
5764
5905
|
}
|
|
5765
5906
|
};
|
|
@@ -5941,6 +6082,9 @@ var PromptForAgentConfig = class extends Command {
|
|
|
5941
6082
|
ctx.soulTone ??= "direct";
|
|
5942
6083
|
ctx.modelProvider ??= "";
|
|
5943
6084
|
ctx.modelName ??= "";
|
|
6085
|
+
ctx.modelBaseUrl ??= "";
|
|
6086
|
+
ctx.modelApiMode ??= "";
|
|
6087
|
+
ctx.modelKeyEnv ??= "";
|
|
5944
6088
|
ctx.ticketProvider ??= detectTicketProvider(ctx.targetDir) ?? "plane";
|
|
5945
6089
|
ctx.skipEmail ??= true;
|
|
5946
6090
|
ctx.agentId = deriveAgentId(ctx.targetRepo, ctx.role);
|
|
@@ -6007,7 +6151,17 @@ function resolveVendoredTemplate(name) {
|
|
|
6007
6151
|
var RunCopierTemplate = class extends Command {
|
|
6008
6152
|
async invoke() {
|
|
6009
6153
|
const ctx = this.context;
|
|
6010
|
-
const {
|
|
6154
|
+
const {
|
|
6155
|
+
targetRepo,
|
|
6156
|
+
role,
|
|
6157
|
+
agentPurpose,
|
|
6158
|
+
soulTone,
|
|
6159
|
+
modelProvider,
|
|
6160
|
+
modelName,
|
|
6161
|
+
modelBaseUrl,
|
|
6162
|
+
modelApiMode,
|
|
6163
|
+
modelKeyEnv
|
|
6164
|
+
} = ctx;
|
|
6011
6165
|
const ticketProvider = ctx.ticketProvider ?? "plane";
|
|
6012
6166
|
const profileName = ctx.profileName ?? (targetRepo && role ? deriveProfileName(targetRepo, role) : void 0);
|
|
6013
6167
|
if (!targetRepo || !role) {
|
|
@@ -6074,6 +6228,12 @@ var RunCopierTemplate = class extends Command {
|
|
|
6074
6228
|
"--data",
|
|
6075
6229
|
`model_name=${modelName ?? ""}`,
|
|
6076
6230
|
"--data",
|
|
6231
|
+
`model_base_url=${modelBaseUrl ?? ""}`,
|
|
6232
|
+
"--data",
|
|
6233
|
+
`model_api_mode=${modelApiMode ?? ""}`,
|
|
6234
|
+
"--data",
|
|
6235
|
+
`model_key_env=${modelKeyEnv ?? ""}`,
|
|
6236
|
+
"--data",
|
|
6077
6237
|
`profile_name=${profileName ?? ""}`,
|
|
6078
6238
|
"--data",
|
|
6079
6239
|
`soul_tone=${soulTone ?? "direct"}`,
|
|
@@ -8597,31 +8757,33 @@ ${leaveBlock}`);
|
|
|
8597
8757
|
const appended = [
|
|
8598
8758
|
"",
|
|
8599
8759
|
_WireMiseAgentHooks.MARKER + " (generated \u2014 see .agents/hooks/README.md)",
|
|
8760
|
+
// PJAN-61: task names use the colon namespace form. A colon is not legal
|
|
8761
|
+
// in a BARE toml key, so every header here MUST stay quoted.
|
|
8600
8762
|
"[[watch_files]]",
|
|
8601
8763
|
'patterns = [".agents/skills.json"]',
|
|
8602
|
-
'task = "skills
|
|
8764
|
+
'task = "skills:sync"',
|
|
8603
8765
|
"",
|
|
8604
|
-
|
|
8766
|
+
'[tasks."skills:sync"]',
|
|
8605
8767
|
'description = "Sync skills from manifest to local CLI dirs"',
|
|
8606
8768
|
'run = "sync-skills.py --scope project"',
|
|
8607
8769
|
"",
|
|
8608
8770
|
"[[watch_files]]",
|
|
8609
8771
|
'patterns = [".agents/hooks/hooks.master.json"]',
|
|
8610
|
-
'task = "hooks
|
|
8772
|
+
'task = "hooks:sync"',
|
|
8611
8773
|
"",
|
|
8612
|
-
|
|
8774
|
+
'[tasks."hooks:sync"]',
|
|
8613
8775
|
'description = "Fan out hooks.master.json to each agent CLI (claude/codex/kimi/hermes)"',
|
|
8614
8776
|
`run = "${cr}/.agents/hooks/sync.py --install"`,
|
|
8615
8777
|
"",
|
|
8616
|
-
|
|
8778
|
+
'[tasks."hooks:check"]',
|
|
8617
8779
|
'description = "Drift gate: verify generated hook configs match hooks.master.json"',
|
|
8618
8780
|
`run = "${cr}/.agents/hooks/sync.py --check"`,
|
|
8619
8781
|
"",
|
|
8620
|
-
|
|
8782
|
+
'[tasks."hooks:uninstall"]',
|
|
8621
8783
|
'description = "Remove per-user agent-hook injections (codex/kimi/hermes)"',
|
|
8622
8784
|
`run = "${cr}/.agents/hooks/sync.py --uninstall"`,
|
|
8623
8785
|
"",
|
|
8624
|
-
|
|
8786
|
+
'[tasks."hindsight:setup"]',
|
|
8625
8787
|
`description = "Provision this dev's shared project Hindsight key from 1Password into .env"`,
|
|
8626
8788
|
`run = "${cr}/.mise/scripts/hindsight-setup.sh"`,
|
|
8627
8789
|
"",
|
|
@@ -9581,7 +9743,7 @@ program.command("migrate").argument("[rule-id]", "Rule ID to migrate (omit to op
|
|
|
9581
9743
|
process.exit(1);
|
|
9582
9744
|
}
|
|
9583
9745
|
});
|
|
9584
|
-
program.command("hermes-agent").alias("hermes").description("Provision the PM agent for the current repo (defaults everything; only asks about Telegram)").option("-y, --yes", "Non-interactive: accept all defaults (also skips the Telegram prompt)").option("--target-repo <name>", "Target repo name (default: basename of cwd)").option("--role <role>", "Agent role override (default: pm \u2014 the only role in the fleet)").option("--purpose <text>", 'One-line agent purpose (default: "pm agent for <repo>")').option(`--tone <tone>`, `Personality tone (default: direct; ${SOUL_TONES.join(" | ")})`).option("--model-provider <name>", 'Inference provider override ("" = inherit shared default profile)').option("--model-name <name>", 'Model name override ("" = inherit shared default profile)').option("--skip-telegram", "Skip the Telegram wire-up (no BotFather prompt)").option("--email", "Also provision the delo.sh email address (off by default; never prompted)").option("--skip-runtime-repo", "Skip creating the per-agent runtime GH repo").option("--skip-plane", "Skip creating or linking the ticket board").option("--skip-bloodbank", "Deprecated compatibility flag; Bloodbank now uses one fleet-shared Hermes gateway").option("--skip-systemd", "Skip installing systemd --user units").option("--local", "Local-only: skip runtime repo, ticket-board creation, Bloodbank, and systemd (safe for laptops/macOS/non-technical operators)").option("--force-config", "Regenerate ~/.config/hermes-agent-template/config.toml even if it exists").option("--dry-run", "Preview what would run; don't execute copier").option("-f, --force", "Re-render even if agents/hermes/<role>/role.yaml already exists").action(async (options) => {
|
|
9746
|
+
program.command("hermes-agent").alias("hermes").description("Provision the PM agent for the current repo (defaults everything; only asks about Telegram)").option("-y, --yes", "Non-interactive: accept all defaults (also skips the Telegram prompt)").option("--target-repo <name>", "Target repo name (default: basename of cwd)").option("--role <role>", "Agent role override (default: pm \u2014 the only role in the fleet)").option("--purpose <text>", 'One-line agent purpose (default: "pm agent for <repo>")').option(`--tone <tone>`, `Personality tone (default: direct; ${SOUL_TONES.join(" | ")})`).option("--model-provider <name>", 'Inference provider override ("" = inherit shared default profile)').option("--model-name <name>", 'Model name override ("" = inherit shared default profile)').option("--model-base-url <url>", 'Inference API base URL override ("" = inherit shared default profile)').option("--model-api-mode <mode>", 'Inference API mode override ("" = inherit shared default profile)').option("--model-key-env <name>", "Environment variable name holding the scoped model credential").option("--skip-telegram", "Skip the Telegram wire-up (no BotFather prompt)").option("--email", "Also provision the delo.sh email address (off by default; never prompted)").option("--skip-runtime-repo", "Skip creating the per-agent runtime GH repo").option("--skip-plane", "Skip creating or linking the ticket board").option("--skip-bloodbank", "Deprecated compatibility flag; Bloodbank now uses one fleet-shared Hermes gateway").option("--skip-systemd", "Skip installing systemd --user units").option("--local", "Local-only: skip runtime repo, ticket-board creation, Bloodbank, and systemd (safe for laptops/macOS/non-technical operators)").option("--force-config", "Regenerate ~/.config/hermes-agent-template/config.toml even if it exists").option("--dry-run", "Preview what would run; don't execute copier").option("-f, --force", "Re-render even if agents/hermes/<role>/role.yaml already exists").action(async (options) => {
|
|
9585
9747
|
const isDarwin = process.platform === "darwin";
|
|
9586
9748
|
const local = options.local ?? false;
|
|
9587
9749
|
const context = {
|
|
@@ -9597,6 +9759,9 @@ program.command("hermes-agent").alias("hermes").description("Provision the PM ag
|
|
|
9597
9759
|
soulTone: options.tone,
|
|
9598
9760
|
modelProvider: options.modelProvider,
|
|
9599
9761
|
modelName: options.modelName,
|
|
9762
|
+
modelBaseUrl: options.modelBaseUrl,
|
|
9763
|
+
modelApiMode: options.modelApiMode,
|
|
9764
|
+
modelKeyEnv: options.modelKeyEnv,
|
|
9600
9765
|
skipTelegram: options.skipTelegram,
|
|
9601
9766
|
// Email is opt-in only: `--email` wires it, otherwise it's never done.
|
|
9602
9767
|
skipEmail: options.email ? false : void 0,
|