@delorenj/pjangler 1.2.14 → 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 +113 -78
- package/dist/mcp-server.js +113 -78
- 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--;
|
|
@@ -2524,57 +2503,113 @@ function extractTomlStrings(text3) {
|
|
|
2524
2503
|
}
|
|
2525
2504
|
return values;
|
|
2526
2505
|
}
|
|
2506
|
+
function stripTomlStringsAndComments(line) {
|
|
2507
|
+
return line.replace(/"(?:\\.|[^"\\])*"/g, '""').replace(/'[^']*'/g, "''").replace(/#.*$/, "");
|
|
2508
|
+
}
|
|
2527
2509
|
function isManagedHookEntry(value) {
|
|
2528
2510
|
const trimmed = value.trim();
|
|
2529
|
-
|
|
2511
|
+
if (trimmed === OP_INJECT_SCRIPT) return true;
|
|
2512
|
+
return /link-agentfiles\.sh'?\s*$/.test(trimmed);
|
|
2530
2513
|
}
|
|
2531
|
-
function
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
`${indent}]`
|
|
2536
|
-
];
|
|
2514
|
+
function normalizeHookScript(script) {
|
|
2515
|
+
const trimmed = script.trim();
|
|
2516
|
+
if (/codegraph\.sh/.test(trimmed)) return CODEGRAPH_SCRIPT;
|
|
2517
|
+
return trimmed;
|
|
2537
2518
|
}
|
|
2538
|
-
function
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
hooksEnd = i;
|
|
2546
|
-
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--;
|
|
2547
2526
|
}
|
|
2527
|
+
if (depth <= 0) break;
|
|
2548
2528
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
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
|
+
}
|
|
2557
|
+
}
|
|
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;
|
|
2559
2583
|
}
|
|
2560
|
-
break;
|
|
2561
|
-
}
|
|
2562
|
-
const existingBlock = enterStart >= 0 ? lines.slice(enterStart, enterEnd).join("\n") : "";
|
|
2563
|
-
const preserved = extractTomlStrings(existingBlock).filter((entry) => !isManagedHookEntry(entry));
|
|
2564
|
-
const merged = [...LINK_AGENTFILES_HOOK_ENTRIES];
|
|
2565
|
-
for (const entry of preserved) {
|
|
2566
|
-
if (!merged.includes(entry)) merged.push(entry);
|
|
2567
2584
|
}
|
|
2568
|
-
const
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
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);
|
|
2572
2596
|
}
|
|
2573
|
-
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);
|
|
2574
2610
|
}
|
|
2575
2611
|
function upsertLinkAgentfilesBlock(text3, ctx) {
|
|
2576
2612
|
const withPath = upsertMisePath(text3, requiredMisePathEntries(ctx));
|
|
2577
|
-
if (withPath.includes(LINK_AGENTFILES_BLOCK)) return withPath;
|
|
2578
2613
|
let cleaned = removeTomlSection(withPath, /^\[tasks\.link-agentfiles\]$/, /link-agentfiles/, { includePrecedingComments: false });
|
|
2579
2614
|
cleaned = removeTomlSection(cleaned, /^\[\[watch_files\]\]$/, /AGENTS\.md/, { includePrecedingComments: false });
|
|
2580
2615
|
cleaned = upsertLinkAgentfilesHooks(cleaned);
|
|
@@ -3074,8 +3109,8 @@ var RULES = [
|
|
|
3074
3109
|
const pathValues = [...(text3.match(/^_\.path\s*=\s*\[([^\]]*)\]/m)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
|
3075
3110
|
const missingPathValues = requiredMisePathEntries(ctx).filter((value) => !pathValues.includes(value));
|
|
3076
3111
|
if (missingPathValues.length) details.push(`[env]._.path should include ${missingPathValues.join(", ")}`);
|
|
3077
|
-
if (!text3.includes('
|
|
3078
|
-
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");
|
|
3079
3114
|
if (!text3.includes('patterns = ["AGENTS.md"]')) details.push("watch_files must monitor AGENTS.md");
|
|
3080
3115
|
if (!text3.includes('task = "link-agentfiles"')) details.push("watch_files must dispatch link-agentfiles task");
|
|
3081
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--;
|
|
@@ -2451,57 +2430,113 @@ function extractTomlStrings(text2) {
|
|
|
2451
2430
|
}
|
|
2452
2431
|
return values;
|
|
2453
2432
|
}
|
|
2433
|
+
function stripTomlStringsAndComments(line) {
|
|
2434
|
+
return line.replace(/"(?:\\.|[^"\\])*"/g, '""').replace(/'[^']*'/g, "''").replace(/#.*$/, "");
|
|
2435
|
+
}
|
|
2454
2436
|
function isManagedHookEntry(value) {
|
|
2455
2437
|
const trimmed = value.trim();
|
|
2456
|
-
|
|
2438
|
+
if (trimmed === OP_INJECT_SCRIPT) return true;
|
|
2439
|
+
return /link-agentfiles\.sh'?\s*$/.test(trimmed);
|
|
2457
2440
|
}
|
|
2458
|
-
function
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
`${indent}]`
|
|
2463
|
-
];
|
|
2441
|
+
function normalizeHookScript(script) {
|
|
2442
|
+
const trimmed = script.trim();
|
|
2443
|
+
if (/codegraph\.sh/.test(trimmed)) return CODEGRAPH_SCRIPT;
|
|
2444
|
+
return trimmed;
|
|
2464
2445
|
}
|
|
2465
|
-
function
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
hooksEnd = i;
|
|
2473
|
-
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--;
|
|
2474
2453
|
}
|
|
2454
|
+
if (depth <= 0) break;
|
|
2475
2455
|
}
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
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
|
+
}
|
|
2484
|
+
}
|
|
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;
|
|
2486
2510
|
}
|
|
2487
|
-
break;
|
|
2488
|
-
}
|
|
2489
|
-
const existingBlock = enterStart >= 0 ? lines.slice(enterStart, enterEnd).join("\n") : "";
|
|
2490
|
-
const preserved = extractTomlStrings(existingBlock).filter((entry) => !isManagedHookEntry(entry));
|
|
2491
|
-
const merged = [...LINK_AGENTFILES_HOOK_ENTRIES];
|
|
2492
|
-
for (const entry of preserved) {
|
|
2493
|
-
if (!merged.includes(entry)) merged.push(entry);
|
|
2494
2511
|
}
|
|
2495
|
-
const
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
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);
|
|
2499
2523
|
}
|
|
2500
|
-
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);
|
|
2501
2537
|
}
|
|
2502
2538
|
function upsertLinkAgentfilesBlock(text2, ctx) {
|
|
2503
2539
|
const withPath = upsertMisePath(text2, requiredMisePathEntries(ctx));
|
|
2504
|
-
if (withPath.includes(LINK_AGENTFILES_BLOCK)) return withPath;
|
|
2505
2540
|
let cleaned = removeTomlSection(withPath, /^\[tasks\.link-agentfiles\]$/, /link-agentfiles/, { includePrecedingComments: false });
|
|
2506
2541
|
cleaned = removeTomlSection(cleaned, /^\[\[watch_files\]\]$/, /AGENTS\.md/, { includePrecedingComments: false });
|
|
2507
2542
|
cleaned = upsertLinkAgentfilesHooks(cleaned);
|
|
@@ -3001,8 +3036,8 @@ var RULES = [
|
|
|
3001
3036
|
const pathValues = [...(text2.match(/^_\.path\s*=\s*\[([^\]]*)\]/m)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
|
3002
3037
|
const missingPathValues = requiredMisePathEntries(ctx).filter((value) => !pathValues.includes(value));
|
|
3003
3038
|
if (missingPathValues.length) details.push(`[env]._.path should include ${missingPathValues.join(", ")}`);
|
|
3004
|
-
if (!text2.includes('
|
|
3005
|
-
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");
|
|
3006
3041
|
if (!text2.includes('patterns = ["AGENTS.md"]')) details.push("watch_files must monitor AGENTS.md");
|
|
3007
3042
|
if (!text2.includes('task = "link-agentfiles"')) details.push("watch_files must dispatch link-agentfiles task");
|
|
3008
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'"
|