@delorenj/pjangler 1.2.16 → 1.2.17
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 +109 -82
- package/dist/mcp-server.js +109 -82
- package/package.json +1 -1
- package/templates/commonproject/mise.toml +1 -1
- package/templates/commonproject/template/mise.toml.jinja +26 -20
package/dist/index.js
CHANGED
|
@@ -2122,72 +2122,48 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
|
2122
2122
|
import { homedir as homedir5 } from "node:os";
|
|
2123
2123
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
2124
2124
|
import YAML2 from "yaml";
|
|
2125
|
-
var
|
|
2125
|
+
var LINK_AGENTFILES_SCRIPT = "'{{config_root}}/.mise/scripts/link-agentfiles.sh'";
|
|
2126
|
+
var OP_INJECT_SCRIPT = "op inject -i .env.op > .env";
|
|
2127
|
+
var CODEGRAPH_SCRIPT = "[ -f '{{config_root}}/.mise/scripts/codegraph.sh' ] && '{{config_root}}/.mise/scripts/codegraph.sh' || true";
|
|
2128
|
+
var HOOKS_COMMENT_HEADER = `# This block will handle the linking of
|
|
2126
2129
|
# agent files to the main AGENTS.md file.
|
|
2127
2130
|
#
|
|
2128
2131
|
# TODO: Ensure this works for all levels of nesting.
|
|
2129
2132
|
# i.e. All linked agent files MUST be siblings at
|
|
2130
|
-
# any given level of nesting
|
|
2131
|
-
[
|
|
2132
|
-
enter = [
|
|
2133
|
-
"{{config_root}}/.mise/scripts/link-agentfiles.sh",
|
|
2134
|
-
"op inject -i .env.op > .env",
|
|
2135
|
-
]
|
|
2136
|
-
|
|
2137
|
-
[[watch_files]]
|
|
2138
|
-
patterns = ["AGENTS.md"]
|
|
2139
|
-
task = "link-agentfiles"
|
|
2140
|
-
|
|
2141
|
-
[tasks.link-agentfiles]
|
|
2142
|
-
description = "Symlink all agent files to AGENTS.md"
|
|
2143
|
-
run = "{{config_root}}/.mise/scripts/link-agentfiles.sh"`;
|
|
2144
|
-
var LINK_AGENTFILES_HOOK_ENTRIES = [
|
|
2145
|
-
"{{config_root}}/.mise/scripts/link-agentfiles.sh",
|
|
2146
|
-
"op inject -i .env.op > .env"
|
|
2147
|
-
];
|
|
2148
|
-
var LINK_AGENTFILES_HOOKS_BLOCK = `# This block will handle the linking of
|
|
2149
|
-
# agent files to the main AGENTS.md file.
|
|
2150
|
-
#
|
|
2151
|
-
# TODO: Ensure this works for all levels of nesting.
|
|
2152
|
-
# i.e. All linked agent files MUST be siblings at
|
|
2153
|
-
# any given level of nesting.
|
|
2154
|
-
[hooks]
|
|
2155
|
-
enter = [
|
|
2156
|
-
"{{config_root}}/.mise/scripts/link-agentfiles.sh",
|
|
2157
|
-
"op inject -i .env.op > .env",
|
|
2158
|
-
]`;
|
|
2133
|
+
# any given level of nesting.`;
|
|
2134
|
+
var LINK_AGENTFILES_HOOK_ENTRIES = [LINK_AGENTFILES_SCRIPT, OP_INJECT_SCRIPT];
|
|
2159
2135
|
var LINK_AGENTFILES_WATCH_TASK_BLOCK = `[[watch_files]]
|
|
2160
2136
|
patterns = ["AGENTS.md"]
|
|
2161
2137
|
task = "link-agentfiles"
|
|
2162
2138
|
|
|
2163
2139
|
[tasks.link-agentfiles]
|
|
2164
2140
|
description = "Symlink all agent files to AGENTS.md"
|
|
2165
|
-
run = "{{config_root}}/.mise/scripts/link-agentfiles.sh"`;
|
|
2141
|
+
run = "'{{config_root}}/.mise/scripts/link-agentfiles.sh'"`;
|
|
2166
2142
|
var VERSIONING_BLOCK = `# >>> mise-versioning >>> (managed block \u2014 do not edit by hand; re-run init to update)
|
|
2167
2143
|
[tasks."version"]
|
|
2168
2144
|
description = "Print the current version (vX.Y.Z)"
|
|
2169
|
-
run = "{{config_root}}/.mise/scripts/versioning.sh current"
|
|
2145
|
+
run = "'{{config_root}}/.mise/scripts/versioning.sh' current"
|
|
2170
2146
|
|
|
2171
2147
|
[tasks."version:bump"]
|
|
2172
2148
|
description = "Bump patch version: vX.Y.Z -> vX.Y.(Z+1)"
|
|
2173
2149
|
alias = "version:bump-patch"
|
|
2174
|
-
run = "{{config_root}}/.mise/scripts/versioning.sh bump patch"
|
|
2150
|
+
run = "'{{config_root}}/.mise/scripts/versioning.sh' bump patch"
|
|
2175
2151
|
|
|
2176
2152
|
[tasks."version:bump-minor"]
|
|
2177
2153
|
description = "Bump minor version: vX.Y.Z -> vX.(Y+1).0"
|
|
2178
|
-
run = "{{config_root}}/.mise/scripts/versioning.sh bump minor"
|
|
2154
|
+
run = "'{{config_root}}/.mise/scripts/versioning.sh' bump minor"
|
|
2179
2155
|
|
|
2180
2156
|
[tasks."version:bump-major"]
|
|
2181
2157
|
description = "Bump major version: vX.Y.Z -> v(X+1).0.0"
|
|
2182
|
-
run = "{{config_root}}/.mise/scripts/versioning.sh bump major"
|
|
2158
|
+
run = "'{{config_root}}/.mise/scripts/versioning.sh' bump major"
|
|
2183
2159
|
|
|
2184
2160
|
[tasks."version:check"]
|
|
2185
2161
|
description = "Verify every versioned file is in parity"
|
|
2186
|
-
run = "{{config_root}}/.mise/scripts/versioning.sh check"
|
|
2162
|
+
run = "'{{config_root}}/.mise/scripts/versioning.sh' check"
|
|
2187
2163
|
|
|
2188
2164
|
[tasks."version:sync"]
|
|
2189
2165
|
description = "Force every versioned file up to the highest version"
|
|
2190
|
-
run = "{{config_root}}/.mise/scripts/versioning.sh sync"
|
|
2166
|
+
run = "'{{config_root}}/.mise/scripts/versioning.sh' sync"
|
|
2191
2167
|
# <<< mise-versioning <<<`;
|
|
2192
2168
|
function resolvePjanglerRoot2() {
|
|
2193
2169
|
let dir = dirname6(fileURLToPath3(import.meta.url));
|
|
@@ -2488,6 +2464,9 @@ function removeTomlSection(text3, headerPattern, marker, options) {
|
|
|
2488
2464
|
break;
|
|
2489
2465
|
}
|
|
2490
2466
|
if (start === -1) return text3;
|
|
2467
|
+
while (end > start + 1 && (lines[end - 1].trim() === "" || lines[end - 1].trim().startsWith("#"))) {
|
|
2468
|
+
end--;
|
|
2469
|
+
}
|
|
2491
2470
|
if (options?.includePrecedingComments) {
|
|
2492
2471
|
while (start > 0 && lines[start - 1].trim().startsWith("#")) {
|
|
2493
2472
|
start--;
|
|
@@ -2529,60 +2508,108 @@ function stripTomlStringsAndComments(line) {
|
|
|
2529
2508
|
}
|
|
2530
2509
|
function isManagedHookEntry(value) {
|
|
2531
2510
|
const trimmed = value.trim();
|
|
2532
|
-
|
|
2511
|
+
if (trimmed === OP_INJECT_SCRIPT) return true;
|
|
2512
|
+
return /link-agentfiles\.sh'?\s*$/.test(trimmed);
|
|
2533
2513
|
}
|
|
2534
|
-
function
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
`${indent}]`
|
|
2539
|
-
];
|
|
2514
|
+
function normalizeHookScript(script) {
|
|
2515
|
+
const trimmed = script.trim();
|
|
2516
|
+
if (/codegraph\.sh/.test(trimmed)) return CODEGRAPH_SCRIPT;
|
|
2517
|
+
return trimmed;
|
|
2540
2518
|
}
|
|
2541
|
-
function
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
hooksEnd = i;
|
|
2549
|
-
break;
|
|
2519
|
+
function tomlValueSpanEnd(lines, start, limit) {
|
|
2520
|
+
let depth = 0;
|
|
2521
|
+
let j = start;
|
|
2522
|
+
for (; j < limit; j++) {
|
|
2523
|
+
for (const ch of stripTomlStringsAndComments(lines[j])) {
|
|
2524
|
+
if (ch === "[") depth++;
|
|
2525
|
+
else if (ch === "]") depth--;
|
|
2550
2526
|
}
|
|
2527
|
+
if (depth <= 0) break;
|
|
2551
2528
|
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2529
|
+
return Math.min(j, limit - 1) + 1;
|
|
2530
|
+
}
|
|
2531
|
+
function stripHookBlocks(text3) {
|
|
2532
|
+
const lines = text3.split("\n");
|
|
2533
|
+
const enter = [];
|
|
2534
|
+
const leave = [];
|
|
2535
|
+
const drop = new Array(lines.length).fill(false);
|
|
2536
|
+
const isHeader = (line) => /^\[/.test(line.trim());
|
|
2537
|
+
const dropPrecedingComments = (idx) => {
|
|
2538
|
+
for (let k = idx - 1; k >= 0 && !drop[k] && lines[k].trim().startsWith("#"); k--) drop[k] = true;
|
|
2539
|
+
};
|
|
2540
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2541
|
+
const trimmed = lines[i].trim();
|
|
2542
|
+
const tableMatch = /^\[\[\s*hooks\.(enter|leave)\s*\]\]$/.exec(trimmed);
|
|
2543
|
+
if (tableMatch) {
|
|
2544
|
+
const bucket = tableMatch[1] === "enter" ? enter : leave;
|
|
2545
|
+
dropPrecedingComments(i);
|
|
2546
|
+
drop[i] = true;
|
|
2547
|
+
let j = i + 1;
|
|
2548
|
+
for (; j < lines.length; j++) {
|
|
2549
|
+
const body = lines[j].trim();
|
|
2550
|
+
if (body === "" || body.startsWith("#") || isHeader(lines[j])) break;
|
|
2551
|
+
drop[j] = true;
|
|
2552
|
+
const scriptMatch = /^\s*script\s*=\s*(.+)$/.exec(lines[j]);
|
|
2553
|
+
if (scriptMatch) {
|
|
2554
|
+
const value = extractTomlStrings(scriptMatch[1])[0];
|
|
2555
|
+
if (value !== void 0) bucket.push(value);
|
|
2556
|
+
}
|
|
2563
2557
|
}
|
|
2564
|
-
|
|
2558
|
+
i = j - 1;
|
|
2559
|
+
continue;
|
|
2560
|
+
}
|
|
2561
|
+
if (trimmed === "[hooks]") {
|
|
2562
|
+
dropPrecedingComments(i);
|
|
2563
|
+
let j = i + 1;
|
|
2564
|
+
let lastDrop = i;
|
|
2565
|
+
while (j < lines.length && !isHeader(lines[j])) {
|
|
2566
|
+
const keyMatch = /^\s*(enter|leave)\s*=/.exec(lines[j]);
|
|
2567
|
+
if (keyMatch) {
|
|
2568
|
+
const bucket = keyMatch[1] === "enter" ? enter : leave;
|
|
2569
|
+
const end = tomlValueSpanEnd(lines, j, lines.length);
|
|
2570
|
+
for (const value of extractTomlStrings(lines.slice(j, end).join("\n"))) bucket.push(value);
|
|
2571
|
+
lastDrop = end - 1;
|
|
2572
|
+
j = end;
|
|
2573
|
+
} else if (/^\s*\]\s*$/.test(lines[j])) {
|
|
2574
|
+
lastDrop = j;
|
|
2575
|
+
j++;
|
|
2576
|
+
} else {
|
|
2577
|
+
j++;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
for (let k = i; k <= lastDrop; k++) drop[k] = true;
|
|
2581
|
+
i = j - 1;
|
|
2582
|
+
continue;
|
|
2565
2583
|
}
|
|
2566
|
-
enterEnd = Math.min(j, hooksEnd - 1) + 1;
|
|
2567
|
-
while (enterEnd < hooksEnd && /^\s*\]\s*$/.test(lines[enterEnd])) enterEnd++;
|
|
2568
|
-
break;
|
|
2569
|
-
}
|
|
2570
|
-
const existingBlock = enterStart >= 0 ? lines.slice(enterStart, enterEnd).join("\n") : "";
|
|
2571
|
-
const preserved = extractTomlStrings(existingBlock).filter((entry) => !isManagedHookEntry(entry));
|
|
2572
|
-
const merged = [...LINK_AGENTFILES_HOOK_ENTRIES];
|
|
2573
|
-
for (const entry of preserved) {
|
|
2574
|
-
if (!merged.includes(entry)) merged.push(entry);
|
|
2575
2584
|
}
|
|
2576
|
-
const
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2585
|
+
const kept = lines.filter((_, idx) => !drop[idx]).join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "\n");
|
|
2586
|
+
return { text: kept, enter, leave };
|
|
2587
|
+
}
|
|
2588
|
+
function renderHookTables(scripts, kind) {
|
|
2589
|
+
return scripts.map((script) => `[[hooks.${kind}]]
|
|
2590
|
+
script = ${JSON.stringify(script)}`);
|
|
2591
|
+
}
|
|
2592
|
+
function dedupePreserve(scripts) {
|
|
2593
|
+
const out = [];
|
|
2594
|
+
for (const script of scripts) {
|
|
2595
|
+
if (script && !out.includes(script)) out.push(script);
|
|
2580
2596
|
}
|
|
2581
|
-
return
|
|
2597
|
+
return out;
|
|
2598
|
+
}
|
|
2599
|
+
function upsertLinkAgentfilesHooks(text3) {
|
|
2600
|
+
const { text: stripped, enter, leave } = stripHookBlocks(text3);
|
|
2601
|
+
const preservedEnter = enter.map(normalizeHookScript).filter((script) => !isManagedHookEntry(script));
|
|
2602
|
+
const enterScripts = dedupePreserve([...LINK_AGENTFILES_HOOK_ENTRIES, ...preservedEnter]);
|
|
2603
|
+
const leaveScripts = dedupePreserve(leave.map(normalizeHookScript));
|
|
2604
|
+
const block = [
|
|
2605
|
+
HOOKS_COMMENT_HEADER,
|
|
2606
|
+
...renderHookTables(enterScripts, "enter"),
|
|
2607
|
+
...renderHookTables(leaveScripts, "leave")
|
|
2608
|
+
].join("\n");
|
|
2609
|
+
return insertTomlBlockBeforeVersioning(stripped, block);
|
|
2582
2610
|
}
|
|
2583
2611
|
function upsertLinkAgentfilesBlock(text3, ctx) {
|
|
2584
2612
|
const withPath = upsertMisePath(text3, requiredMisePathEntries(ctx));
|
|
2585
|
-
if (withPath.includes(LINK_AGENTFILES_BLOCK)) return withPath;
|
|
2586
2613
|
let cleaned = removeTomlSection(withPath, /^\[tasks\.link-agentfiles\]$/, /link-agentfiles/, { includePrecedingComments: false });
|
|
2587
2614
|
cleaned = removeTomlSection(cleaned, /^\[\[watch_files\]\]$/, /AGENTS\.md/, { includePrecedingComments: false });
|
|
2588
2615
|
cleaned = upsertLinkAgentfilesHooks(cleaned);
|
|
@@ -3082,8 +3109,8 @@ var RULES = [
|
|
|
3082
3109
|
const pathValues = [...(text3.match(/^_\.path\s*=\s*\[([^\]]*)\]/m)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
|
3083
3110
|
const missingPathValues = requiredMisePathEntries(ctx).filter((value) => !pathValues.includes(value));
|
|
3084
3111
|
if (missingPathValues.length) details.push(`[env]._.path should include ${missingPathValues.join(", ")}`);
|
|
3085
|
-
if (!text3.includes('
|
|
3086
|
-
if (!text3.includes("op inject -i .env.op > .env")) details.push("
|
|
3112
|
+
if (!text3.includes("'{{config_root}}/.mise/scripts/link-agentfiles.sh'")) details.push("link-agentfiles hook must use single-quoted {{config_root}} guard");
|
|
3113
|
+
if (!text3.includes("op inject -i .env.op > .env")) details.push("hooks.enter must materialize .env from .env.op");
|
|
3087
3114
|
if (!text3.includes('patterns = ["AGENTS.md"]')) details.push("watch_files must monitor AGENTS.md");
|
|
3088
3115
|
if (!text3.includes('task = "link-agentfiles"')) details.push("watch_files must dispatch link-agentfiles task");
|
|
3089
3116
|
return {
|
package/dist/mcp-server.js
CHANGED
|
@@ -2049,72 +2049,48 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
|
2049
2049
|
import { homedir as homedir5 } from "node:os";
|
|
2050
2050
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
2051
2051
|
import YAML2 from "yaml";
|
|
2052
|
-
var
|
|
2052
|
+
var LINK_AGENTFILES_SCRIPT = "'{{config_root}}/.mise/scripts/link-agentfiles.sh'";
|
|
2053
|
+
var OP_INJECT_SCRIPT = "op inject -i .env.op > .env";
|
|
2054
|
+
var CODEGRAPH_SCRIPT = "[ -f '{{config_root}}/.mise/scripts/codegraph.sh' ] && '{{config_root}}/.mise/scripts/codegraph.sh' || true";
|
|
2055
|
+
var HOOKS_COMMENT_HEADER = `# This block will handle the linking of
|
|
2053
2056
|
# agent files to the main AGENTS.md file.
|
|
2054
2057
|
#
|
|
2055
2058
|
# TODO: Ensure this works for all levels of nesting.
|
|
2056
2059
|
# i.e. All linked agent files MUST be siblings at
|
|
2057
|
-
# any given level of nesting
|
|
2058
|
-
[
|
|
2059
|
-
enter = [
|
|
2060
|
-
"{{config_root}}/.mise/scripts/link-agentfiles.sh",
|
|
2061
|
-
"op inject -i .env.op > .env",
|
|
2062
|
-
]
|
|
2063
|
-
|
|
2064
|
-
[[watch_files]]
|
|
2065
|
-
patterns = ["AGENTS.md"]
|
|
2066
|
-
task = "link-agentfiles"
|
|
2067
|
-
|
|
2068
|
-
[tasks.link-agentfiles]
|
|
2069
|
-
description = "Symlink all agent files to AGENTS.md"
|
|
2070
|
-
run = "{{config_root}}/.mise/scripts/link-agentfiles.sh"`;
|
|
2071
|
-
var LINK_AGENTFILES_HOOK_ENTRIES = [
|
|
2072
|
-
"{{config_root}}/.mise/scripts/link-agentfiles.sh",
|
|
2073
|
-
"op inject -i .env.op > .env"
|
|
2074
|
-
];
|
|
2075
|
-
var LINK_AGENTFILES_HOOKS_BLOCK = `# This block will handle the linking of
|
|
2076
|
-
# agent files to the main AGENTS.md file.
|
|
2077
|
-
#
|
|
2078
|
-
# TODO: Ensure this works for all levels of nesting.
|
|
2079
|
-
# i.e. All linked agent files MUST be siblings at
|
|
2080
|
-
# any given level of nesting.
|
|
2081
|
-
[hooks]
|
|
2082
|
-
enter = [
|
|
2083
|
-
"{{config_root}}/.mise/scripts/link-agentfiles.sh",
|
|
2084
|
-
"op inject -i .env.op > .env",
|
|
2085
|
-
]`;
|
|
2060
|
+
# any given level of nesting.`;
|
|
2061
|
+
var LINK_AGENTFILES_HOOK_ENTRIES = [LINK_AGENTFILES_SCRIPT, OP_INJECT_SCRIPT];
|
|
2086
2062
|
var LINK_AGENTFILES_WATCH_TASK_BLOCK = `[[watch_files]]
|
|
2087
2063
|
patterns = ["AGENTS.md"]
|
|
2088
2064
|
task = "link-agentfiles"
|
|
2089
2065
|
|
|
2090
2066
|
[tasks.link-agentfiles]
|
|
2091
2067
|
description = "Symlink all agent files to AGENTS.md"
|
|
2092
|
-
run = "{{config_root}}/.mise/scripts/link-agentfiles.sh"`;
|
|
2068
|
+
run = "'{{config_root}}/.mise/scripts/link-agentfiles.sh'"`;
|
|
2093
2069
|
var VERSIONING_BLOCK = `# >>> mise-versioning >>> (managed block \u2014 do not edit by hand; re-run init to update)
|
|
2094
2070
|
[tasks."version"]
|
|
2095
2071
|
description = "Print the current version (vX.Y.Z)"
|
|
2096
|
-
run = "{{config_root}}/.mise/scripts/versioning.sh current"
|
|
2072
|
+
run = "'{{config_root}}/.mise/scripts/versioning.sh' current"
|
|
2097
2073
|
|
|
2098
2074
|
[tasks."version:bump"]
|
|
2099
2075
|
description = "Bump patch version: vX.Y.Z -> vX.Y.(Z+1)"
|
|
2100
2076
|
alias = "version:bump-patch"
|
|
2101
|
-
run = "{{config_root}}/.mise/scripts/versioning.sh bump patch"
|
|
2077
|
+
run = "'{{config_root}}/.mise/scripts/versioning.sh' bump patch"
|
|
2102
2078
|
|
|
2103
2079
|
[tasks."version:bump-minor"]
|
|
2104
2080
|
description = "Bump minor version: vX.Y.Z -> vX.(Y+1).0"
|
|
2105
|
-
run = "{{config_root}}/.mise/scripts/versioning.sh bump minor"
|
|
2081
|
+
run = "'{{config_root}}/.mise/scripts/versioning.sh' bump minor"
|
|
2106
2082
|
|
|
2107
2083
|
[tasks."version:bump-major"]
|
|
2108
2084
|
description = "Bump major version: vX.Y.Z -> v(X+1).0.0"
|
|
2109
|
-
run = "{{config_root}}/.mise/scripts/versioning.sh bump major"
|
|
2085
|
+
run = "'{{config_root}}/.mise/scripts/versioning.sh' bump major"
|
|
2110
2086
|
|
|
2111
2087
|
[tasks."version:check"]
|
|
2112
2088
|
description = "Verify every versioned file is in parity"
|
|
2113
|
-
run = "{{config_root}}/.mise/scripts/versioning.sh check"
|
|
2089
|
+
run = "'{{config_root}}/.mise/scripts/versioning.sh' check"
|
|
2114
2090
|
|
|
2115
2091
|
[tasks."version:sync"]
|
|
2116
2092
|
description = "Force every versioned file up to the highest version"
|
|
2117
|
-
run = "{{config_root}}/.mise/scripts/versioning.sh sync"
|
|
2093
|
+
run = "'{{config_root}}/.mise/scripts/versioning.sh' sync"
|
|
2118
2094
|
# <<< mise-versioning <<<`;
|
|
2119
2095
|
function resolvePjanglerRoot2() {
|
|
2120
2096
|
let dir = dirname7(fileURLToPath4(import.meta.url));
|
|
@@ -2415,6 +2391,9 @@ function removeTomlSection(text2, headerPattern, marker, options) {
|
|
|
2415
2391
|
break;
|
|
2416
2392
|
}
|
|
2417
2393
|
if (start === -1) return text2;
|
|
2394
|
+
while (end > start + 1 && (lines[end - 1].trim() === "" || lines[end - 1].trim().startsWith("#"))) {
|
|
2395
|
+
end--;
|
|
2396
|
+
}
|
|
2418
2397
|
if (options?.includePrecedingComments) {
|
|
2419
2398
|
while (start > 0 && lines[start - 1].trim().startsWith("#")) {
|
|
2420
2399
|
start--;
|
|
@@ -2456,60 +2435,108 @@ function stripTomlStringsAndComments(line) {
|
|
|
2456
2435
|
}
|
|
2457
2436
|
function isManagedHookEntry(value) {
|
|
2458
2437
|
const trimmed = value.trim();
|
|
2459
|
-
|
|
2438
|
+
if (trimmed === OP_INJECT_SCRIPT) return true;
|
|
2439
|
+
return /link-agentfiles\.sh'?\s*$/.test(trimmed);
|
|
2460
2440
|
}
|
|
2461
|
-
function
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
`${indent}]`
|
|
2466
|
-
];
|
|
2441
|
+
function normalizeHookScript(script) {
|
|
2442
|
+
const trimmed = script.trim();
|
|
2443
|
+
if (/codegraph\.sh/.test(trimmed)) return CODEGRAPH_SCRIPT;
|
|
2444
|
+
return trimmed;
|
|
2467
2445
|
}
|
|
2468
|
-
function
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
hooksEnd = i;
|
|
2476
|
-
break;
|
|
2446
|
+
function tomlValueSpanEnd(lines, start, limit) {
|
|
2447
|
+
let depth = 0;
|
|
2448
|
+
let j = start;
|
|
2449
|
+
for (; j < limit; j++) {
|
|
2450
|
+
for (const ch of stripTomlStringsAndComments(lines[j])) {
|
|
2451
|
+
if (ch === "[") depth++;
|
|
2452
|
+
else if (ch === "]") depth--;
|
|
2477
2453
|
}
|
|
2454
|
+
if (depth <= 0) break;
|
|
2478
2455
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2456
|
+
return Math.min(j, limit - 1) + 1;
|
|
2457
|
+
}
|
|
2458
|
+
function stripHookBlocks(text2) {
|
|
2459
|
+
const lines = text2.split("\n");
|
|
2460
|
+
const enter = [];
|
|
2461
|
+
const leave = [];
|
|
2462
|
+
const drop = new Array(lines.length).fill(false);
|
|
2463
|
+
const isHeader = (line) => /^\[/.test(line.trim());
|
|
2464
|
+
const dropPrecedingComments = (idx) => {
|
|
2465
|
+
for (let k = idx - 1; k >= 0 && !drop[k] && lines[k].trim().startsWith("#"); k--) drop[k] = true;
|
|
2466
|
+
};
|
|
2467
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2468
|
+
const trimmed = lines[i].trim();
|
|
2469
|
+
const tableMatch = /^\[\[\s*hooks\.(enter|leave)\s*\]\]$/.exec(trimmed);
|
|
2470
|
+
if (tableMatch) {
|
|
2471
|
+
const bucket = tableMatch[1] === "enter" ? enter : leave;
|
|
2472
|
+
dropPrecedingComments(i);
|
|
2473
|
+
drop[i] = true;
|
|
2474
|
+
let j = i + 1;
|
|
2475
|
+
for (; j < lines.length; j++) {
|
|
2476
|
+
const body = lines[j].trim();
|
|
2477
|
+
if (body === "" || body.startsWith("#") || isHeader(lines[j])) break;
|
|
2478
|
+
drop[j] = true;
|
|
2479
|
+
const scriptMatch = /^\s*script\s*=\s*(.+)$/.exec(lines[j]);
|
|
2480
|
+
if (scriptMatch) {
|
|
2481
|
+
const value = extractTomlStrings(scriptMatch[1])[0];
|
|
2482
|
+
if (value !== void 0) bucket.push(value);
|
|
2483
|
+
}
|
|
2490
2484
|
}
|
|
2491
|
-
|
|
2485
|
+
i = j - 1;
|
|
2486
|
+
continue;
|
|
2487
|
+
}
|
|
2488
|
+
if (trimmed === "[hooks]") {
|
|
2489
|
+
dropPrecedingComments(i);
|
|
2490
|
+
let j = i + 1;
|
|
2491
|
+
let lastDrop = i;
|
|
2492
|
+
while (j < lines.length && !isHeader(lines[j])) {
|
|
2493
|
+
const keyMatch = /^\s*(enter|leave)\s*=/.exec(lines[j]);
|
|
2494
|
+
if (keyMatch) {
|
|
2495
|
+
const bucket = keyMatch[1] === "enter" ? enter : leave;
|
|
2496
|
+
const end = tomlValueSpanEnd(lines, j, lines.length);
|
|
2497
|
+
for (const value of extractTomlStrings(lines.slice(j, end).join("\n"))) bucket.push(value);
|
|
2498
|
+
lastDrop = end - 1;
|
|
2499
|
+
j = end;
|
|
2500
|
+
} else if (/^\s*\]\s*$/.test(lines[j])) {
|
|
2501
|
+
lastDrop = j;
|
|
2502
|
+
j++;
|
|
2503
|
+
} else {
|
|
2504
|
+
j++;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
for (let k = i; k <= lastDrop; k++) drop[k] = true;
|
|
2508
|
+
i = j - 1;
|
|
2509
|
+
continue;
|
|
2492
2510
|
}
|
|
2493
|
-
enterEnd = Math.min(j, hooksEnd - 1) + 1;
|
|
2494
|
-
while (enterEnd < hooksEnd && /^\s*\]\s*$/.test(lines[enterEnd])) enterEnd++;
|
|
2495
|
-
break;
|
|
2496
|
-
}
|
|
2497
|
-
const existingBlock = enterStart >= 0 ? lines.slice(enterStart, enterEnd).join("\n") : "";
|
|
2498
|
-
const preserved = extractTomlStrings(existingBlock).filter((entry) => !isManagedHookEntry(entry));
|
|
2499
|
-
const merged = [...LINK_AGENTFILES_HOOK_ENTRIES];
|
|
2500
|
-
for (const entry of preserved) {
|
|
2501
|
-
if (!merged.includes(entry)) merged.push(entry);
|
|
2502
2511
|
}
|
|
2503
|
-
const
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2512
|
+
const kept = lines.filter((_, idx) => !drop[idx]).join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "\n");
|
|
2513
|
+
return { text: kept, enter, leave };
|
|
2514
|
+
}
|
|
2515
|
+
function renderHookTables(scripts, kind) {
|
|
2516
|
+
return scripts.map((script) => `[[hooks.${kind}]]
|
|
2517
|
+
script = ${JSON.stringify(script)}`);
|
|
2518
|
+
}
|
|
2519
|
+
function dedupePreserve(scripts) {
|
|
2520
|
+
const out = [];
|
|
2521
|
+
for (const script of scripts) {
|
|
2522
|
+
if (script && !out.includes(script)) out.push(script);
|
|
2507
2523
|
}
|
|
2508
|
-
return
|
|
2524
|
+
return out;
|
|
2525
|
+
}
|
|
2526
|
+
function upsertLinkAgentfilesHooks(text2) {
|
|
2527
|
+
const { text: stripped, enter, leave } = stripHookBlocks(text2);
|
|
2528
|
+
const preservedEnter = enter.map(normalizeHookScript).filter((script) => !isManagedHookEntry(script));
|
|
2529
|
+
const enterScripts = dedupePreserve([...LINK_AGENTFILES_HOOK_ENTRIES, ...preservedEnter]);
|
|
2530
|
+
const leaveScripts = dedupePreserve(leave.map(normalizeHookScript));
|
|
2531
|
+
const block = [
|
|
2532
|
+
HOOKS_COMMENT_HEADER,
|
|
2533
|
+
...renderHookTables(enterScripts, "enter"),
|
|
2534
|
+
...renderHookTables(leaveScripts, "leave")
|
|
2535
|
+
].join("\n");
|
|
2536
|
+
return insertTomlBlockBeforeVersioning(stripped, block);
|
|
2509
2537
|
}
|
|
2510
2538
|
function upsertLinkAgentfilesBlock(text2, ctx) {
|
|
2511
2539
|
const withPath = upsertMisePath(text2, requiredMisePathEntries(ctx));
|
|
2512
|
-
if (withPath.includes(LINK_AGENTFILES_BLOCK)) return withPath;
|
|
2513
2540
|
let cleaned = removeTomlSection(withPath, /^\[tasks\.link-agentfiles\]$/, /link-agentfiles/, { includePrecedingComments: false });
|
|
2514
2541
|
cleaned = removeTomlSection(cleaned, /^\[\[watch_files\]\]$/, /AGENTS\.md/, { includePrecedingComments: false });
|
|
2515
2542
|
cleaned = upsertLinkAgentfilesHooks(cleaned);
|
|
@@ -3009,8 +3036,8 @@ var RULES = [
|
|
|
3009
3036
|
const pathValues = [...(text2.match(/^_\.path\s*=\s*\[([^\]]*)\]/m)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
|
3010
3037
|
const missingPathValues = requiredMisePathEntries(ctx).filter((value) => !pathValues.includes(value));
|
|
3011
3038
|
if (missingPathValues.length) details.push(`[env]._.path should include ${missingPathValues.join(", ")}`);
|
|
3012
|
-
if (!text2.includes('
|
|
3013
|
-
if (!text2.includes("op inject -i .env.op > .env")) details.push("
|
|
3039
|
+
if (!text2.includes("'{{config_root}}/.mise/scripts/link-agentfiles.sh'")) details.push("link-agentfiles hook must use single-quoted {{config_root}} guard");
|
|
3040
|
+
if (!text2.includes("op inject -i .env.op > .env")) details.push("hooks.enter must materialize .env from .env.op");
|
|
3014
3041
|
if (!text2.includes('patterns = ["AGENTS.md"]')) details.push("watch_files must monitor AGENTS.md");
|
|
3015
3042
|
if (!text2.includes('task = "link-agentfiles"')) details.push("watch_files must dispatch link-agentfiles task");
|
|
3016
3043
|
return {
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@ CODEX_HOME = { value = ".codex", path = true }
|
|
|
10
10
|
# i.e. All linked agent files MUST be siblings at
|
|
11
11
|
# any given level of nesting.
|
|
12
12
|
[hooks]
|
|
13
|
-
enter = '{{config_root}}/.mise/scripts/link-agentfiles.sh'
|
|
13
|
+
enter = "'{{config_root}}/.mise/scripts/link-agentfiles.sh'"
|
|
14
14
|
|
|
15
15
|
[[watch_files]]
|
|
16
16
|
patterns = ["AGENTS.md"]
|
|
@@ -11,21 +11,27 @@ _.file = [".env"]
|
|
|
11
11
|
# TODO: Ensure this works for all levels of nesting.
|
|
12
12
|
# i.e. All linked agent files MUST be siblings at
|
|
13
13
|
# any given level of nesting.
|
|
14
|
-
|
|
15
|
-
enter = [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
#
|
|
15
|
+
# NOTE: hooks are an array of [[hooks.enter]] tables (NOT enter = [ ... ]) and
|
|
16
|
+
# every {{config_root}} path is single-quoted so it survives a space in the
|
|
17
|
+
# project path — mise runs each `script` via `sh -c`.
|
|
18
|
+
[[hooks.enter]]
|
|
19
|
+
script = "'{% raw %}{{config_root}}{% endraw %}/.mise/scripts/link-agentfiles.sh'"
|
|
20
|
+
[[hooks.enter]]
|
|
21
|
+
script = "op inject -i .env.op > .env"
|
|
22
|
+
[[hooks.enter]]
|
|
23
|
+
script = "[ -f '{% raw %}{{config_root}}{% endraw %}/.mise/scripts/codegraph.sh' ] && '{% raw %}{{config_root}}{% endraw %}/.mise/scripts/codegraph.sh' || true"
|
|
19
24
|
{%- if agent_hooks_layer %}
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
[[hooks.enter]]
|
|
26
|
+
script = "'{% raw %}{{config_root}}{% endraw %}/.mise/scripts/link-project-skills-to-clis.sh'"
|
|
27
|
+
[[hooks.enter]]
|
|
28
|
+
script = "'{% raw %}{{config_root}}{% endraw %}/.agents/hooks/sync.py' --install --quiet"
|
|
22
29
|
{%- endif %}
|
|
23
|
-
]
|
|
24
30
|
{%- if agent_hooks_layer %}
|
|
25
|
-
leave
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
[[hooks.leave]]
|
|
32
|
+
script = "'{% raw %}{{config_root}}{% endraw %}/.mise/scripts/unlink-project-skills-from-clis.sh'"
|
|
33
|
+
[[hooks.leave]]
|
|
34
|
+
script = "'{% raw %}{{config_root}}{% endraw %}/.agents/hooks/sync.py' --uninstall --quiet"
|
|
29
35
|
{%- endif %}
|
|
30
36
|
|
|
31
37
|
[[watch_files]]
|
|
@@ -39,34 +45,34 @@ task = "hooks-sync"
|
|
|
39
45
|
{% endif %}
|
|
40
46
|
[tasks.link-agentfiles]
|
|
41
47
|
description = "Symlink all agent files to AGENTS.md"
|
|
42
|
-
run = "{% raw %}{{config_root}}{% endraw %}/.mise/scripts/link-agentfiles.sh"
|
|
48
|
+
run = "'{% raw %}{{config_root}}{% endraw %}/.mise/scripts/link-agentfiles.sh'"
|
|
43
49
|
{% if agent_hooks_layer %}
|
|
44
50
|
# --- Project-scoped agent hooks + skill fan-out (see .agents/hooks/README.md) ---
|
|
45
51
|
|
|
46
52
|
[tasks.hooks-sync]
|
|
47
53
|
description = "Fan out .agents/hooks/hooks.master.json to each agent CLI (claude committed; codex/kimi injected; hermes adapter)"
|
|
48
|
-
run = "{% raw %}{{config_root}}{% endraw %}/.agents/hooks/sync.py --install"
|
|
54
|
+
run = "'{% raw %}{{config_root}}{% endraw %}/.agents/hooks/sync.py' --install"
|
|
49
55
|
|
|
50
56
|
[tasks.hooks-check]
|
|
51
57
|
description = "Drift gate: verify generated hook configs match hooks.master.json (CI-safe, read-only)"
|
|
52
|
-
run = "{% raw %}{{config_root}}{% endraw %}/.agents/hooks/sync.py --check"
|
|
58
|
+
run = "'{% raw %}{{config_root}}{% endraw %}/.agents/hooks/sync.py' --check"
|
|
53
59
|
|
|
54
60
|
[tasks.hooks-uninstall]
|
|
55
61
|
description = "Remove per-user agent-hook injections (codex/kimi/hermes)"
|
|
56
|
-
run = "{% raw %}{{config_root}}{% endraw %}/.agents/hooks/sync.py --uninstall"
|
|
62
|
+
run = "'{% raw %}{{config_root}}{% endraw %}/.agents/hooks/sync.py' --uninstall"
|
|
57
63
|
|
|
58
64
|
[tasks.link-project-skills-to-clis]
|
|
59
65
|
description = "Fan .agents/skills out to each agent CLI; honors local.json defer_to_global + disabled"
|
|
60
|
-
run = "{% raw %}{{config_root}}{% endraw %}/.mise/scripts/link-project-skills-to-clis.sh"
|
|
66
|
+
run = "'{% raw %}{{config_root}}{% endraw %}/.mise/scripts/link-project-skills-to-clis.sh'"
|
|
61
67
|
|
|
62
68
|
[tasks.unlink-project-skills-from-clis]
|
|
63
69
|
description = "Remove project skill symlinks from shared per-CLI dirs"
|
|
64
|
-
run = "{% raw %}{{config_root}}{% endraw %}/.mise/scripts/unlink-project-skills-from-clis.sh"
|
|
70
|
+
run = "'{% raw %}{{config_root}}{% endraw %}/.mise/scripts/unlink-project-skills-from-clis.sh'"
|
|
65
71
|
|
|
66
72
|
[tasks.skills-relink]
|
|
67
73
|
description = "Re-fan the project skill set to all CLIs"
|
|
68
|
-
run = "{% raw %}{{config_root}}{% endraw %}/.mise/scripts/link-project-skills-to-clis.sh"
|
|
74
|
+
run = "'{% raw %}{{config_root}}{% endraw %}/.mise/scripts/link-project-skills-to-clis.sh'"
|
|
69
75
|
{% endif %}
|
|
70
76
|
[tasks.hindsight-setup]
|
|
71
77
|
description = "Provision this dev's shared project Hindsight key from 1Password into .env (op inject)"
|
|
72
|
-
run = "{% raw %}{{config_root}}{% endraw %}/.mise/scripts/hindsight-setup.sh"
|
|
78
|
+
run = "'{% raw %}{{config_root}}{% endraw %}/.mise/scripts/hindsight-setup.sh'"
|