agentsmesh 0.39.0 → 0.41.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/CHANGELOG.md +93 -0
- package/README.md +1 -1
- package/dist/canonical.js +303 -238
- package/dist/canonical.js.map +1 -1
- package/dist/cli.js +271 -332
- package/dist/engine.d.ts +7 -8
- package/dist/engine.js +775 -276
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1737 -521
- package/dist/index.js.map +1 -1
- package/dist/{init-B1qdo3Dl.d.ts → init-DruMEhnc.d.ts} +97 -42
- package/dist/lessons.d.ts +7 -4
- package/dist/lessons.js +2105 -601
- package/dist/lessons.js.map +1 -1
- package/dist/{target-descriptor-CvTJyDxd.d.ts → target-descriptor-CMV6vxVE.d.ts} +7 -0
- package/dist/targets.d.ts +2 -2
- package/dist/targets.js +269 -219
- package/dist/targets.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { stringify, parse, parseDocument, YAMLSeq, YAMLMap, isMap, Document, isSeq, isScalar, Scalar, Pair } from 'yaml';
|
|
3
|
-
import { readFileSync, existsSync, mkdirSync, writeFileSync, constants, rmSync, renameSync, readdirSync, realpathSync, statSync } from 'fs';
|
|
1
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync, constants, rmSync, readdirSync, statSync, chmodSync, renameSync, accessSync, realpathSync, rmdirSync, unlinkSync } from 'fs';
|
|
4
2
|
import { join, resolve, relative, sep, dirname, basename, extname, win32, posix } from 'path';
|
|
5
|
-
import {
|
|
3
|
+
import { stringify, parse, parseDocument, YAMLSeq, isMap, Document, isSeq, YAMLMap, isScalar, Scalar, Pair } from 'yaml';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { access, readdir, mkdir, readFile, rm, writeFile, lstat, open, stat, realpath, rmdir, unlink, rename, mkdtemp, cp } from 'fs/promises';
|
|
6
6
|
import { setTimeout } from 'timers/promises';
|
|
7
7
|
import { createHash, randomUUID } from 'crypto';
|
|
8
|
-
import
|
|
8
|
+
import picomatch from 'picomatch';
|
|
9
9
|
import { parse as parse$1, stringify as stringify$1 } from 'smol-toml';
|
|
10
10
|
import { Buffer } from 'buffer';
|
|
11
11
|
import { homedir, hostname, tmpdir } from 'os';
|
|
12
|
-
import { execFile } from 'child_process';
|
|
12
|
+
import { execFile, spawnSync } from 'child_process';
|
|
13
13
|
import { fileURLToPath, pathToFileURL, URL as URL$1 } from 'url';
|
|
14
14
|
import { promisify } from 'util';
|
|
15
15
|
import * as tar from 'tar';
|
|
@@ -24,6 +24,104 @@ var __export = (target34, all) => {
|
|
|
24
24
|
for (var name in all)
|
|
25
25
|
__defProp(target34, name, { get: all[name], enumerable: true });
|
|
26
26
|
};
|
|
27
|
+
function agentsmeshInvocation(projectRoot) {
|
|
28
|
+
return dependsOnAgentsmesh(projectRoot) ? LOCAL_FIRST : BARE;
|
|
29
|
+
}
|
|
30
|
+
function dependsOnAgentsmesh(projectRoot) {
|
|
31
|
+
try {
|
|
32
|
+
const manifest = JSON.parse(
|
|
33
|
+
readFileSync(join(projectRoot, "package.json"), "utf8")
|
|
34
|
+
);
|
|
35
|
+
return DEPENDENCY_FIELDS.some((field) => manifest?.[field]?.agentsmesh !== void 0);
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
var LOCAL_FIRST, BARE, DEPENDENCY_FIELDS;
|
|
41
|
+
var init_cli_invocation = __esm({
|
|
42
|
+
"src/lessons/cli-invocation.ts"() {
|
|
43
|
+
LOCAL_FIRST = "npx --no --offline agentsmesh";
|
|
44
|
+
BARE = "agentsmesh";
|
|
45
|
+
DEPENDENCY_FIELDS = ["dependencies", "devDependencies", "optionalDependencies"];
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
function recallHookCommand(projectRoot) {
|
|
49
|
+
return `${agentsmeshInvocation(projectRoot)} ${RECALL_SUBCOMMAND}`;
|
|
50
|
+
}
|
|
51
|
+
function isRecallHookCommand(command) {
|
|
52
|
+
return typeof command === "string" && command.includes(RECALL_HOOK_COMMAND);
|
|
53
|
+
}
|
|
54
|
+
function isManagedRecallCommand(command) {
|
|
55
|
+
return typeof command === "string" && MANAGED_COMMAND.test(command.trim());
|
|
56
|
+
}
|
|
57
|
+
function isManaged(item) {
|
|
58
|
+
return item instanceof YAMLMap && isManagedRecallCommand(item.get("command"));
|
|
59
|
+
}
|
|
60
|
+
function removeEvent(doc, event) {
|
|
61
|
+
const existing = doc.get(event);
|
|
62
|
+
if (!(existing instanceof YAMLSeq)) return false;
|
|
63
|
+
const kept = existing.items.filter((item) => !isManaged(item));
|
|
64
|
+
if (kept.length === existing.items.length) return false;
|
|
65
|
+
if (kept.length === 0) doc.delete(event);
|
|
66
|
+
else existing.items = kept;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
function upsertEvent(doc, event, matcher, command) {
|
|
70
|
+
const existing = doc.get(event);
|
|
71
|
+
const seq = existing instanceof YAMLSeq ? existing : new YAMLSeq();
|
|
72
|
+
const [first, ...extra] = seq.items.filter(isManaged);
|
|
73
|
+
if (first === void 0) {
|
|
74
|
+
seq.add(doc.createNode({ matcher, type: "command", command }));
|
|
75
|
+
doc.set(event, seq);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
let changed = extra.length > 0;
|
|
79
|
+
if (changed) seq.items = seq.items.filter((item) => !extra.includes(item));
|
|
80
|
+
const desired = { matcher, type: "command", command };
|
|
81
|
+
for (const [key, value] of Object.entries(desired)) {
|
|
82
|
+
if (first.get(key) === value) continue;
|
|
83
|
+
first.set(key, value);
|
|
84
|
+
changed = true;
|
|
85
|
+
}
|
|
86
|
+
return changed;
|
|
87
|
+
}
|
|
88
|
+
function injectRecallHook(projectRoot) {
|
|
89
|
+
const path = join(projectRoot, ".agentsmesh", "hooks.yaml");
|
|
90
|
+
if (!existsSync(path)) return false;
|
|
91
|
+
const doc = parseDocument(readFileSync(path, "utf8"));
|
|
92
|
+
const command = recallHookCommand(projectRoot);
|
|
93
|
+
let changed = false;
|
|
94
|
+
for (const { event, matcher } of RECALL_EVENTS) {
|
|
95
|
+
if (upsertEvent(doc, event, matcher, command)) changed = true;
|
|
96
|
+
}
|
|
97
|
+
for (const event of RETIRED_EVENTS) {
|
|
98
|
+
if (removeEvent(doc, event)) changed = true;
|
|
99
|
+
}
|
|
100
|
+
if (changed) writeFileSync(path, String(doc), "utf8");
|
|
101
|
+
return changed;
|
|
102
|
+
}
|
|
103
|
+
var RECALL_SUBCOMMAND, RECALL_HOOK_COMMAND, RECALL_HOOK_TOOL_MATCHER, MANAGED_COMMAND, RECALL_EVENTS, RETIRED_EVENTS;
|
|
104
|
+
var init_recall_hook_scaffold = __esm({
|
|
105
|
+
"src/lessons/recall-hook-scaffold.ts"() {
|
|
106
|
+
init_cli_invocation();
|
|
107
|
+
RECALL_SUBCOMMAND = "lessons hook";
|
|
108
|
+
RECALL_HOOK_COMMAND = `agentsmesh ${RECALL_SUBCOMMAND}`;
|
|
109
|
+
RECALL_HOOK_TOOL_MATCHER = "Edit|Write|NotebookEdit|Bash|PowerShell";
|
|
110
|
+
MANAGED_COMMAND = new RegExp(`^(?:npx(?: --?[\\w-]+)* )?${RECALL_HOOK_COMMAND}$`);
|
|
111
|
+
RECALL_EVENTS = [
|
|
112
|
+
{ event: "PreToolUse", matcher: RECALL_HOOK_TOOL_MATCHER },
|
|
113
|
+
{ event: "UserPromptSubmit", matcher: "*" },
|
|
114
|
+
// Capture-on-failure nudge (see capture-nudge.ts). BEST-EFFORT: targets with
|
|
115
|
+
// no failure event drop it without warning (BEST_EFFORT_HOOK_EVENTS).
|
|
116
|
+
// PostToolUse is success-only, so failures need this.
|
|
117
|
+
{ event: "PostToolUseFailure", matcher: "*" },
|
|
118
|
+
// Reset recall dedup after a context compaction/clear (see hook.ts SessionStart).
|
|
119
|
+
// BEST-EFFORT: targets that can't represent SessionStart just keep dedup as-is.
|
|
120
|
+
{ event: "SessionStart", matcher: "*" }
|
|
121
|
+
];
|
|
122
|
+
RETIRED_EVENTS = ["PostToolUse"];
|
|
123
|
+
}
|
|
124
|
+
});
|
|
27
125
|
function capabilityLevel(capability) {
|
|
28
126
|
return typeof capability === "string" ? capability : capability.level;
|
|
29
127
|
}
|
|
@@ -183,6 +281,7 @@ var init_target_descriptor_schema = __esm({
|
|
|
183
281
|
emitScopedSettings: z.function().optional(),
|
|
184
282
|
mergeGeneratedOutputContent: z.function().optional(),
|
|
185
283
|
postProcessHookOutputs: z.function().optional(),
|
|
284
|
+
hookContextEvents: z.array(z.string()).optional(),
|
|
186
285
|
preservesManualActivation: z.boolean().optional()
|
|
187
286
|
}).passthrough();
|
|
188
287
|
targetDescriptorSchema = targetDescriptorSchemaBase.superRefine((value, ctx) => {
|
|
@@ -578,61 +677,6 @@ var init_guards = __esm({
|
|
|
578
677
|
"src/utils/types/guards.ts"() {
|
|
579
678
|
}
|
|
580
679
|
});
|
|
581
|
-
function removeEvent(doc, event) {
|
|
582
|
-
const existing = doc.get(event);
|
|
583
|
-
if (!(existing instanceof YAMLSeq)) return false;
|
|
584
|
-
const kept = existing.items.filter(
|
|
585
|
-
(item) => !(item instanceof YAMLMap && item.get("command") === RECALL_HOOK_COMMAND)
|
|
586
|
-
);
|
|
587
|
-
if (kept.length === existing.items.length) return false;
|
|
588
|
-
if (kept.length === 0) doc.delete(event);
|
|
589
|
-
else existing.items = kept;
|
|
590
|
-
return true;
|
|
591
|
-
}
|
|
592
|
-
function injectEvent(doc, event, matcher) {
|
|
593
|
-
const existing = doc.get(event);
|
|
594
|
-
const seq = existing instanceof YAMLSeq ? existing : new YAMLSeq();
|
|
595
|
-
const present = seq.items.some(
|
|
596
|
-
(item) => item instanceof YAMLMap && item.get("command") === RECALL_HOOK_COMMAND
|
|
597
|
-
);
|
|
598
|
-
if (present) return false;
|
|
599
|
-
seq.add(doc.createNode({ matcher, type: "command", command: RECALL_HOOK_COMMAND }));
|
|
600
|
-
doc.set(event, seq);
|
|
601
|
-
return true;
|
|
602
|
-
}
|
|
603
|
-
function injectRecallHook(projectRoot) {
|
|
604
|
-
const path = join(projectRoot, ".agentsmesh", "hooks.yaml");
|
|
605
|
-
if (!existsSync(path)) return false;
|
|
606
|
-
const doc = parseDocument(readFileSync(path, "utf8"));
|
|
607
|
-
let changed = false;
|
|
608
|
-
for (const { event, matcher } of RECALL_EVENTS) {
|
|
609
|
-
if (injectEvent(doc, event, matcher)) changed = true;
|
|
610
|
-
}
|
|
611
|
-
for (const event of RETIRED_EVENTS) {
|
|
612
|
-
if (removeEvent(doc, event)) changed = true;
|
|
613
|
-
}
|
|
614
|
-
if (changed) writeFileSync(path, String(doc), "utf8");
|
|
615
|
-
return changed;
|
|
616
|
-
}
|
|
617
|
-
var RECALL_HOOK_COMMAND, RECALL_HOOK_TOOL_MATCHER, RECALL_EVENTS, RETIRED_EVENTS;
|
|
618
|
-
var init_recall_hook_scaffold = __esm({
|
|
619
|
-
"src/lessons/recall-hook-scaffold.ts"() {
|
|
620
|
-
RECALL_HOOK_COMMAND = "agentsmesh lessons hook";
|
|
621
|
-
RECALL_HOOK_TOOL_MATCHER = "Edit|Write|Bash";
|
|
622
|
-
RECALL_EVENTS = [
|
|
623
|
-
{ event: "PreToolUse", matcher: RECALL_HOOK_TOOL_MATCHER },
|
|
624
|
-
{ event: "UserPromptSubmit", matcher: "*" },
|
|
625
|
-
// Capture-on-failure nudge (see capture-nudge.ts). BEST-EFFORT: only Claude
|
|
626
|
-
// Code's passthrough hooks emit it; whitelist targets drop it without warning
|
|
627
|
-
// (BEST_EFFORT_HOOK_EVENTS). PostToolUse is success-only, so failures need this.
|
|
628
|
-
{ event: "PostToolUseFailure", matcher: "*" },
|
|
629
|
-
// Reset recall dedup after a context compaction/clear (see hook.ts SessionStart).
|
|
630
|
-
// BEST-EFFORT: targets that can't represent SessionStart just keep dedup as-is.
|
|
631
|
-
{ event: "SessionStart", matcher: "*" }
|
|
632
|
-
];
|
|
633
|
-
RETIRED_EVENTS = ["PostToolUse"];
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
680
|
|
|
637
681
|
// src/core/hook-types.ts
|
|
638
682
|
function isBestEffortHookEvent(event, entries) {
|
|
@@ -960,8 +1004,10 @@ function executableModeFor(path) {
|
|
|
960
1004
|
}
|
|
961
1005
|
function normalizeTextPayload(path, content) {
|
|
962
1006
|
if (!shouldNormalizeLineEndings(path)) return content;
|
|
963
|
-
|
|
964
|
-
|
|
1007
|
+
return normalizeLineEndings(stripBom(content));
|
|
1008
|
+
}
|
|
1009
|
+
function stripBom(text) {
|
|
1010
|
+
return text.startsWith(UTF8_BOM) ? text.slice(UTF8_BOM.length) : text;
|
|
965
1011
|
}
|
|
966
1012
|
var UTF8_BOM, TEXT_EXTENSIONS, TEXT_DOTFILES, BINARY_EXTENSIONS, EXECUTABLE_SCRIPT_EXTENSIONS;
|
|
967
1013
|
var init_fs_text_encoding = __esm({
|
|
@@ -2161,6 +2207,15 @@ var init_constants = __esm({
|
|
|
2161
2207
|
}
|
|
2162
2208
|
});
|
|
2163
2209
|
|
|
2210
|
+
// src/targets/catalog/ignore-output.ts
|
|
2211
|
+
function ignoreOutput(path) {
|
|
2212
|
+
return (canonical) => canonical.ignore.length === 0 ? [] : [{ path, content: canonical.ignore.join("\n") }];
|
|
2213
|
+
}
|
|
2214
|
+
var init_ignore_output = __esm({
|
|
2215
|
+
"src/targets/catalog/ignore-output.ts"() {
|
|
2216
|
+
}
|
|
2217
|
+
});
|
|
2218
|
+
|
|
2164
2219
|
// src/targets/aider/generator.ts
|
|
2165
2220
|
function buildAiderConventions(canonical) {
|
|
2166
2221
|
const root = canonical.rules.find((rule) => rule.root);
|
|
@@ -2190,11 +2245,7 @@ function generateAgents(canonical) {
|
|
|
2190
2245
|
content: serializeProjectedAgentSkill(agent)
|
|
2191
2246
|
}));
|
|
2192
2247
|
}
|
|
2193
|
-
|
|
2194
|
-
if (canonical.ignore.length === 0) return [];
|
|
2195
|
-
return [{ path: AIDER_IGNORE, content: canonical.ignore.join("\n") }];
|
|
2196
|
-
}
|
|
2197
|
-
var generateMcp, generatePermissions;
|
|
2248
|
+
var generateIgnore, generateMcp, generatePermissions;
|
|
2198
2249
|
var init_generator = __esm({
|
|
2199
2250
|
"src/targets/aider/generator.ts"() {
|
|
2200
2251
|
init_no_outputs();
|
|
@@ -2203,6 +2254,8 @@ var init_generator = __esm({
|
|
|
2203
2254
|
init_projected_agent_skill();
|
|
2204
2255
|
init_command_skill();
|
|
2205
2256
|
init_constants();
|
|
2257
|
+
init_ignore_output();
|
|
2258
|
+
generateIgnore = ignoreOutput(AIDER_IGNORE);
|
|
2206
2259
|
generateMcp = NO_OUTPUTS;
|
|
2207
2260
|
generatePermissions = NO_OUTPUTS;
|
|
2208
2261
|
}
|
|
@@ -2918,6 +2971,9 @@ var init_link_token_context = __esm({
|
|
|
2918
2971
|
});
|
|
2919
2972
|
|
|
2920
2973
|
// src/core/reference/link-rebaser.ts
|
|
2974
|
+
function isSlashCommandToken(normalizedToken) {
|
|
2975
|
+
return /^\/[A-Za-z][A-Za-z0-9-]*$/.test(normalizedToken);
|
|
2976
|
+
}
|
|
2921
2977
|
function rewriteFileLinks(input) {
|
|
2922
2978
|
const missing = /* @__PURE__ */ new Set();
|
|
2923
2979
|
const protectedRefRanges = protectedRanges(input.content);
|
|
@@ -2968,6 +3024,7 @@ function rewriteFileLinks(input) {
|
|
|
2968
3024
|
if (targetIsDirectory && !normalizedCandidate.includes("/") && !normalizedCandidate.includes("\\")) {
|
|
2969
3025
|
return match;
|
|
2970
3026
|
}
|
|
3027
|
+
if (isSlashCommandToken(normalizedCandidate)) return match;
|
|
2971
3028
|
if (resolvedBeforeTranslate === null) {
|
|
2972
3029
|
let normTok = normalizeSeparators(punctStripped);
|
|
2973
3030
|
if (normTok.startsWith("agentsmesh/")) {
|
|
@@ -3727,7 +3784,7 @@ var init_importer = __esm({
|
|
|
3727
3784
|
}
|
|
3728
3785
|
});
|
|
3729
3786
|
function globFilter(files, pattern) {
|
|
3730
|
-
const isMatch =
|
|
3787
|
+
const isMatch = picomatch(pattern, OPTIONS);
|
|
3731
3788
|
return files.filter((file) => isMatch(file));
|
|
3732
3789
|
}
|
|
3733
3790
|
var OPTIONS;
|
|
@@ -7092,10 +7149,6 @@ function generateAgents4(canonical) {
|
|
|
7092
7149
|
content: serializeAntigravityAgent(agent)
|
|
7093
7150
|
}));
|
|
7094
7151
|
}
|
|
7095
|
-
function generateIgnore3(canonical) {
|
|
7096
|
-
if (canonical.ignore.length === 0) return [];
|
|
7097
|
-
return [{ path: ANTIGRAVITY_IGNORE_FILE, content: canonical.ignore.join("\n") }];
|
|
7098
|
-
}
|
|
7099
7152
|
function renderAntigravityGlobalInstructions(canonical) {
|
|
7100
7153
|
const root = canonical.rules.find((rule) => rule.root);
|
|
7101
7154
|
const nonRootRules = canonical.rules.filter((rule) => {
|
|
@@ -7110,7 +7163,7 @@ function generateHooks2(canonical) {
|
|
|
7110
7163
|
if (Object.keys(hooks).length === 0) return [];
|
|
7111
7164
|
return [{ path: ANTIGRAVITY_HOOKS_FILE, content: JSON.stringify(hooks, null, 2) }];
|
|
7112
7165
|
}
|
|
7113
|
-
var generatePermissions3;
|
|
7166
|
+
var generateIgnore3, generatePermissions3;
|
|
7114
7167
|
var init_generator4 = __esm({
|
|
7115
7168
|
"src/targets/antigravity/generator.ts"() {
|
|
7116
7169
|
init_no_outputs();
|
|
@@ -7119,6 +7172,8 @@ var init_generator4 = __esm({
|
|
|
7119
7172
|
init_hooks_format2();
|
|
7120
7173
|
init_agents_format();
|
|
7121
7174
|
init_constants5();
|
|
7175
|
+
init_ignore_output();
|
|
7176
|
+
generateIgnore3 = ignoreOutput(ANTIGRAVITY_IGNORE_FILE);
|
|
7122
7177
|
generatePermissions3 = NO_OUTPUTS;
|
|
7123
7178
|
}
|
|
7124
7179
|
});
|
|
@@ -8712,16 +8767,14 @@ function generateHooks3(canonical) {
|
|
|
8712
8767
|
const content = JSON.stringify({ hooks: claudeHooks }, null, 2);
|
|
8713
8768
|
return [{ path: CLAUDE_SETTINGS, content }];
|
|
8714
8769
|
}
|
|
8715
|
-
|
|
8716
|
-
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
8717
|
-
const content = canonical.ignore.join("\n");
|
|
8718
|
-
return [{ path: CLAUDE_IGNORE, content }];
|
|
8719
|
-
}
|
|
8770
|
+
var generateIgnore5;
|
|
8720
8771
|
var init_generator6 = __esm({
|
|
8721
8772
|
"src/targets/claude-code/generator.ts"() {
|
|
8722
8773
|
init_markdown();
|
|
8723
8774
|
init_constants7();
|
|
8724
8775
|
init_hooks_format2();
|
|
8776
|
+
init_ignore_output();
|
|
8777
|
+
generateIgnore5 = ignoreOutput(CLAUDE_IGNORE);
|
|
8725
8778
|
}
|
|
8726
8779
|
});
|
|
8727
8780
|
function ruleSectionTitle(rule) {
|
|
@@ -10540,11 +10593,7 @@ function generateMcp5(canonical) {
|
|
|
10540
10593
|
if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
|
|
10541
10594
|
return [{ path: CODEBUFF_MCP_FILE, content: serializeCodebuffMcp(canonical.mcp) }];
|
|
10542
10595
|
}
|
|
10543
|
-
|
|
10544
|
-
if (canonical.ignore.length === 0) return [];
|
|
10545
|
-
return [{ path: CODEBUFF_IGNORE_FILE, content: canonical.ignore.join("\n") }];
|
|
10546
|
-
}
|
|
10547
|
-
var generateAgents8, generateHooks5, generatePermissions6;
|
|
10596
|
+
var generateIgnore7, generateAgents8, generateHooks5, generatePermissions6;
|
|
10548
10597
|
var init_generator8 = __esm({
|
|
10549
10598
|
"src/targets/codebuff/generator.ts"() {
|
|
10550
10599
|
init_no_outputs();
|
|
@@ -10554,6 +10603,8 @@ var init_generator8 = __esm({
|
|
|
10554
10603
|
init_nested_rules();
|
|
10555
10604
|
init_mcp_format2();
|
|
10556
10605
|
init_constants9();
|
|
10606
|
+
init_ignore_output();
|
|
10607
|
+
generateIgnore7 = ignoreOutput(CODEBUFF_IGNORE_FILE);
|
|
10557
10608
|
generateAgents8 = NO_OUTPUTS;
|
|
10558
10609
|
generateHooks5 = NO_OUTPUTS;
|
|
10559
10610
|
generatePermissions6 = NO_OUTPUTS;
|
|
@@ -13066,53 +13117,37 @@ var init_mcp_generator = __esm({
|
|
|
13066
13117
|
}
|
|
13067
13118
|
});
|
|
13068
13119
|
|
|
13069
|
-
// src/targets/copilot/hook-entry.ts
|
|
13070
|
-
function hasHookCommand2(entry) {
|
|
13071
|
-
return hasHookCommand(entry);
|
|
13072
|
-
}
|
|
13073
|
-
var init_hook_entry = __esm({
|
|
13074
|
-
"src/targets/copilot/hook-entry.ts"() {
|
|
13075
|
-
init_hook_command();
|
|
13076
|
-
}
|
|
13077
|
-
});
|
|
13078
|
-
|
|
13079
13120
|
// src/targets/copilot/hook-format.ts
|
|
13080
|
-
function
|
|
13081
|
-
|
|
13082
|
-
|
|
13083
|
-
|
|
13084
|
-
|
|
13085
|
-
|
|
13086
|
-
|
|
13087
|
-
|
|
13088
|
-
|
|
13089
|
-
|
|
13090
|
-
|
|
13091
|
-
|
|
13092
|
-
}
|
|
13121
|
+
function copilotHookGroups(hooks) {
|
|
13122
|
+
return Object.entries(hooks ?? {}).flatMap(([event, entries]) => {
|
|
13123
|
+
const copilotEvent = CANONICAL_TO_COPILOT.get(event);
|
|
13124
|
+
if (!copilotEvent || !Array.isArray(entries)) return [];
|
|
13125
|
+
const kept = entries.filter(
|
|
13126
|
+
(entry) => typeof entry === "object" && entry !== null && hasHookCommand(entry)
|
|
13127
|
+
);
|
|
13128
|
+
return kept.length > 0 ? [{ event, copilotEvent, entries: kept }] : [];
|
|
13129
|
+
});
|
|
13130
|
+
}
|
|
13131
|
+
function wrapperScriptName(event, index) {
|
|
13132
|
+
return `${event.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase()}-${index}.sh`;
|
|
13093
13133
|
}
|
|
13094
13134
|
function buildCopilotHooksObject(hooks) {
|
|
13095
|
-
|
|
13096
|
-
|
|
13097
|
-
|
|
13098
|
-
|
|
13099
|
-
|
|
13100
|
-
|
|
13101
|
-
(entry) => typeof entry === "object" && entry !== null && hasHookCommand2(entry)
|
|
13102
|
-
).map((entry, index) => {
|
|
13103
|
-
const safePhase = event.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase();
|
|
13135
|
+
const groups = copilotHookGroups(hooks);
|
|
13136
|
+
if (groups.length === 0) return null;
|
|
13137
|
+
return Object.fromEntries(
|
|
13138
|
+
groups.map(({ event, copilotEvent, entries }) => [
|
|
13139
|
+
copilotEvent,
|
|
13140
|
+
entries.map((entry, index) => {
|
|
13104
13141
|
const hook = {
|
|
13105
13142
|
type: "command",
|
|
13106
|
-
bash: `./scripts/${
|
|
13143
|
+
bash: `./scripts/${wrapperScriptName(event, index)}`
|
|
13107
13144
|
};
|
|
13108
13145
|
if (entry.matcher && entry.matcher !== "*") hook.matcher = entry.matcher;
|
|
13109
13146
|
if (entry.timeout !== void 0) hook.timeoutSec = Math.ceil(entry.timeout / 1e3);
|
|
13110
13147
|
return hook;
|
|
13111
|
-
})
|
|
13112
|
-
|
|
13113
|
-
})
|
|
13148
|
+
})
|
|
13149
|
+
])
|
|
13114
13150
|
);
|
|
13115
|
-
return Object.keys(result2).length > 0 ? result2 : null;
|
|
13116
13151
|
}
|
|
13117
13152
|
function generateHooks8(canonical) {
|
|
13118
13153
|
const hooks = buildCopilotHooksObject(canonical.hooks);
|
|
@@ -13124,10 +13159,24 @@ function generateHooks8(canonical) {
|
|
|
13124
13159
|
}
|
|
13125
13160
|
];
|
|
13126
13161
|
}
|
|
13162
|
+
var COPILOT_HOOK_CONTEXT_EVENTS, CANONICAL_TO_COPILOT;
|
|
13127
13163
|
var init_hook_format = __esm({
|
|
13128
13164
|
"src/targets/copilot/hook-format.ts"() {
|
|
13129
13165
|
init_constants32();
|
|
13130
|
-
|
|
13166
|
+
init_hook_command();
|
|
13167
|
+
COPILOT_HOOK_CONTEXT_EVENTS = [
|
|
13168
|
+
"SessionStart",
|
|
13169
|
+
"PostToolUse",
|
|
13170
|
+
"PostToolUseFailure"
|
|
13171
|
+
];
|
|
13172
|
+
CANONICAL_TO_COPILOT = /* @__PURE__ */ new Map([
|
|
13173
|
+
["PreToolUse", "preToolUse"],
|
|
13174
|
+
["PostToolUse", "postToolUse"],
|
|
13175
|
+
["PostToolUseFailure", "postToolUseFailure"],
|
|
13176
|
+
["Notification", "notification"],
|
|
13177
|
+
["UserPromptSubmit", "userPromptSubmitted"],
|
|
13178
|
+
["SessionStart", "sessionStart"]
|
|
13179
|
+
]);
|
|
13131
13180
|
}
|
|
13132
13181
|
});
|
|
13133
13182
|
function ruleSlug2(source) {
|
|
@@ -13233,18 +13282,7 @@ var init_generator12 = __esm({
|
|
|
13233
13282
|
}
|
|
13234
13283
|
});
|
|
13235
13284
|
function mapCopilotHookEvent(event) {
|
|
13236
|
-
|
|
13237
|
-
case "preToolUse":
|
|
13238
|
-
return "PreToolUse";
|
|
13239
|
-
case "postToolUse":
|
|
13240
|
-
return "PostToolUse";
|
|
13241
|
-
case "notification":
|
|
13242
|
-
return "Notification";
|
|
13243
|
-
case "userPromptSubmitted":
|
|
13244
|
-
return "UserPromptSubmit";
|
|
13245
|
-
default:
|
|
13246
|
-
return null;
|
|
13247
|
-
}
|
|
13285
|
+
return COPILOT_TO_CANONICAL.get(event) ?? null;
|
|
13248
13286
|
}
|
|
13249
13287
|
function extractMatcher(comment) {
|
|
13250
13288
|
if (typeof comment !== "string") return "*";
|
|
@@ -13332,11 +13370,16 @@ async function importHooks(projectRoot, results, options = {}) {
|
|
|
13332
13370
|
feature: "hooks"
|
|
13333
13371
|
});
|
|
13334
13372
|
}
|
|
13373
|
+
var COPILOT_TO_CANONICAL;
|
|
13335
13374
|
var init_hook_parser = __esm({
|
|
13336
13375
|
"src/targets/copilot/hook-parser.ts"() {
|
|
13337
13376
|
init_canonical_paths();
|
|
13338
13377
|
init_fs();
|
|
13339
13378
|
init_constants32();
|
|
13379
|
+
init_hook_format();
|
|
13380
|
+
COPILOT_TO_CANONICAL = new Map(
|
|
13381
|
+
[...CANONICAL_TO_COPILOT].map(([canonical, copilot]) => [copilot, canonical])
|
|
13382
|
+
);
|
|
13340
13383
|
}
|
|
13341
13384
|
});
|
|
13342
13385
|
async function importSkills2(projectRoot, results, normalize, skillsDirRel = COPILOT_SKILLS_DIR) {
|
|
@@ -13573,7 +13616,7 @@ function lintCommands4(canonical) {
|
|
|
13573
13616
|
}
|
|
13574
13617
|
function lintHooks9(canonical) {
|
|
13575
13618
|
if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
|
|
13576
|
-
const supported = [
|
|
13619
|
+
const supported = [...CANONICAL_TO_COPILOT.keys()];
|
|
13577
13620
|
const diagnostics = unsupportedHookEventNames(canonical.hooks, supported).map(
|
|
13578
13621
|
(event) => createUnsupportedHookWarning(event, "copilot", supported, {
|
|
13579
13622
|
unsupportedBy: "Copilot hooks"
|
|
@@ -13596,11 +13639,9 @@ function lintHooks9(canonical) {
|
|
|
13596
13639
|
var init_lint10 = __esm({
|
|
13597
13640
|
"src/targets/copilot/lint.ts"() {
|
|
13598
13641
|
init_helpers();
|
|
13642
|
+
init_hook_format();
|
|
13599
13643
|
}
|
|
13600
13644
|
});
|
|
13601
|
-
function safePhaseName(phase) {
|
|
13602
|
-
return phase.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase();
|
|
13603
|
-
}
|
|
13604
13645
|
function toRepoRelative(projectRoot, sourcePath) {
|
|
13605
13646
|
const repoRelative = relative(projectRoot, sourcePath).replace(/\\/g, "/");
|
|
13606
13647
|
if (!repoRelative || repoRelative.startsWith("../")) return null;
|
|
@@ -13628,9 +13669,6 @@ async function buildAssetOutput(projectRoot, command, hooksDirRel) {
|
|
|
13628
13669
|
rewrittenCommand: rewriteWrapperCommand(command, repoRelative)
|
|
13629
13670
|
};
|
|
13630
13671
|
}
|
|
13631
|
-
function wrapperPath(event, index, hooksDirRel) {
|
|
13632
|
-
return `${hooksDirRel}/scripts/${safePhaseName(event)}-${index}.sh`;
|
|
13633
|
-
}
|
|
13634
13672
|
function safeShellLine2(value) {
|
|
13635
13673
|
return value.replace(/[\r\n]+/g, " ");
|
|
13636
13674
|
}
|
|
@@ -13645,15 +13683,13 @@ function buildWrapper(command, matcher) {
|
|
|
13645
13683
|
].join("\n");
|
|
13646
13684
|
}
|
|
13647
13685
|
async function addHookScriptAssets(projectRoot, canonical, outputs, hooksDirRel = COPILOT_HOOKS_DIR) {
|
|
13648
|
-
|
|
13686
|
+
const groups = copilotHookGroups(canonical.hooks);
|
|
13687
|
+
if (groups.length === 0) return outputs;
|
|
13649
13688
|
const wrapperOutputs = [];
|
|
13650
13689
|
const assetOutputs = /* @__PURE__ */ new Map();
|
|
13651
|
-
for (const
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
for (const entry of entries) {
|
|
13655
|
-
if (!hasHookCommand2(entry)) continue;
|
|
13656
|
-
const scriptPath = wrapperPath(event, index, hooksDirRel);
|
|
13690
|
+
for (const { event, entries } of groups) {
|
|
13691
|
+
for (const [index, entry] of entries.entries()) {
|
|
13692
|
+
const scriptPath = `${hooksDirRel}/scripts/${wrapperScriptName(event, index)}`;
|
|
13657
13693
|
let command = entry.command;
|
|
13658
13694
|
const asset = await buildAssetOutput(projectRoot, entry.command, hooksDirRel);
|
|
13659
13695
|
if (asset) {
|
|
@@ -13667,7 +13703,6 @@ async function addHookScriptAssets(projectRoot, canonical, outputs, hooksDirRel
|
|
|
13667
13703
|
'set -eu\nHOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"\n'
|
|
13668
13704
|
);
|
|
13669
13705
|
wrapperOutputs.push({ path: scriptPath, content: wrapper });
|
|
13670
|
-
index++;
|
|
13671
13706
|
}
|
|
13672
13707
|
}
|
|
13673
13708
|
return [...outputs, ...wrapperOutputs, ...assetOutputs.values()];
|
|
@@ -13677,7 +13712,7 @@ var init_hook_assets = __esm({
|
|
|
13677
13712
|
"src/targets/copilot/hook-assets.ts"() {
|
|
13678
13713
|
init_fs();
|
|
13679
13714
|
init_constants32();
|
|
13680
|
-
|
|
13715
|
+
init_hook_format();
|
|
13681
13716
|
SCRIPT_PREFIX_RE = /^(?<prefix>\s*(?:(?:bash|sh|zsh)\s+)?)["']?(?<path>(?:\.\.\/|\.\/|[^/\s"'`]+\/)[^\s"'`]+)["']?(?<suffix>(?:\s.*)?)$/;
|
|
13682
13717
|
}
|
|
13683
13718
|
});
|
|
@@ -13995,6 +14030,7 @@ var init_copilot2 = __esm({
|
|
|
13995
14030
|
init_command_prompt();
|
|
13996
14031
|
init_lint10();
|
|
13997
14032
|
init_hook_assets();
|
|
14033
|
+
init_hook_format();
|
|
13998
14034
|
init_scope_extras2();
|
|
13999
14035
|
init_importer_spec2();
|
|
14000
14036
|
init_capabilities6();
|
|
@@ -14125,6 +14161,7 @@ var init_copilot2 = __esm({
|
|
|
14125
14161
|
permissions: lintPermissions7
|
|
14126
14162
|
},
|
|
14127
14163
|
postProcessHookOutputs: async (projectRoot, canonical, outputs) => addHookScriptAssets(projectRoot, canonical, [...outputs]),
|
|
14164
|
+
hookContextEvents: COPILOT_HOOK_CONTEXT_EVENTS,
|
|
14128
14165
|
mergeGeneratedOutputContent: mergeCopilotMcpJson,
|
|
14129
14166
|
project: project7,
|
|
14130
14167
|
globalSupport: {
|
|
@@ -14212,11 +14249,6 @@ function generatePermissions8(canonical) {
|
|
|
14212
14249
|
});
|
|
14213
14250
|
return [{ path: CRUSH_CONFIG_FILE, content: JSON.stringify(crushConfig, null, 2) }];
|
|
14214
14251
|
}
|
|
14215
|
-
function generateIgnore9(canonical) {
|
|
14216
|
-
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
14217
|
-
const content = canonical.ignore.join("\n");
|
|
14218
|
-
return [{ path: CRUSH_IGNORE, content }];
|
|
14219
|
-
}
|
|
14220
14252
|
function buildCrushHooksFromCanonical(canonical) {
|
|
14221
14253
|
if (!canonical.hooks) return {};
|
|
14222
14254
|
const result2 = {};
|
|
@@ -14234,7 +14266,7 @@ function buildCrushHooksFromCanonical(canonical) {
|
|
|
14234
14266
|
}
|
|
14235
14267
|
return result2;
|
|
14236
14268
|
}
|
|
14237
|
-
var generateRules12;
|
|
14269
|
+
var generateRules12, generateIgnore9;
|
|
14238
14270
|
var init_generator13 = __esm({
|
|
14239
14271
|
"src/targets/crush/generator.ts"() {
|
|
14240
14272
|
init_managed_blocks();
|
|
@@ -14243,7 +14275,9 @@ var init_generator13 = __esm({
|
|
|
14243
14275
|
init_command_skill();
|
|
14244
14276
|
init_constants12();
|
|
14245
14277
|
init_config_format();
|
|
14278
|
+
init_ignore_output();
|
|
14246
14279
|
generateRules12 = (canonical) => embeddedRootRule(canonical, CRUSH_TARGET, CRUSH_ROOT_FILE);
|
|
14280
|
+
generateIgnore9 = ignoreOutput(CRUSH_IGNORE);
|
|
14247
14281
|
}
|
|
14248
14282
|
});
|
|
14249
14283
|
async function importFromCrush(projectRoot, options = {}) {
|
|
@@ -14779,7 +14813,7 @@ function cursorHooksToCanonical(hooks) {
|
|
|
14779
14813
|
}
|
|
14780
14814
|
return result2;
|
|
14781
14815
|
}
|
|
14782
|
-
var CANONICAL_TO_CURSOR, CURSOR_TO_CANONICAL;
|
|
14816
|
+
var CANONICAL_TO_CURSOR, CURSOR_HOOK_CONTEXT_EVENTS, CURSOR_TO_CANONICAL;
|
|
14783
14817
|
var init_hook_format2 = __esm({
|
|
14784
14818
|
"src/targets/cursor/hook-format.ts"() {
|
|
14785
14819
|
init_hook_types();
|
|
@@ -14787,6 +14821,7 @@ var init_hook_format2 = __esm({
|
|
|
14787
14821
|
CANONICAL_TO_CURSOR = {
|
|
14788
14822
|
PreToolUse: "preToolUse",
|
|
14789
14823
|
PostToolUse: "postToolUse",
|
|
14824
|
+
PostToolUseFailure: "postToolUseFailure",
|
|
14790
14825
|
UserPromptSubmit: "beforeSubmitPrompt",
|
|
14791
14826
|
SubagentStart: "subagentStart",
|
|
14792
14827
|
SubagentStop: "subagentStop",
|
|
@@ -14795,6 +14830,11 @@ var init_hook_format2 = __esm({
|
|
|
14795
14830
|
SessionEnd: "sessionEnd",
|
|
14796
14831
|
PreCompact: "preCompact"
|
|
14797
14832
|
};
|
|
14833
|
+
CURSOR_HOOK_CONTEXT_EVENTS = [
|
|
14834
|
+
"SessionStart",
|
|
14835
|
+
"PostToolUse",
|
|
14836
|
+
"PostToolUseFailure"
|
|
14837
|
+
];
|
|
14798
14838
|
CURSOR_TO_CANONICAL = new Map(
|
|
14799
14839
|
Object.entries(CANONICAL_TO_CURSOR).map(([canonical, cursor]) => [cursor, canonical])
|
|
14800
14840
|
);
|
|
@@ -14803,7 +14843,7 @@ var init_hook_format2 = __esm({
|
|
|
14803
14843
|
|
|
14804
14844
|
// src/targets/cursor/generator/hooks.ts
|
|
14805
14845
|
function generateHooks10(canonical) {
|
|
14806
|
-
if (!canonical.hooks
|
|
14846
|
+
if (!canonical.hooks) return [];
|
|
14807
14847
|
const cursorHooks = toCursorHooks(canonical.hooks);
|
|
14808
14848
|
if (Object.keys(cursorHooks).length === 0) return [];
|
|
14809
14849
|
const content = JSON.stringify({ version: 1, hooks: cursorHooks }, null, 2);
|
|
@@ -14817,14 +14857,12 @@ var init_hooks3 = __esm({
|
|
|
14817
14857
|
});
|
|
14818
14858
|
|
|
14819
14859
|
// src/targets/cursor/generator/ignore.ts
|
|
14820
|
-
|
|
14821
|
-
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
14822
|
-
const content = canonical.ignore.join("\n");
|
|
14823
|
-
return [{ path: CURSOR_IGNORE, content }];
|
|
14824
|
-
}
|
|
14860
|
+
var generateIgnore10;
|
|
14825
14861
|
var init_ignore = __esm({
|
|
14826
14862
|
"src/targets/cursor/generator/ignore.ts"() {
|
|
14827
14863
|
init_constants13();
|
|
14864
|
+
init_ignore_output();
|
|
14865
|
+
generateIgnore10 = ignoreOutput(CURSOR_IGNORE);
|
|
14828
14866
|
}
|
|
14829
14867
|
});
|
|
14830
14868
|
|
|
@@ -15520,6 +15558,7 @@ var init_cursor2 = __esm({
|
|
|
15520
15558
|
init_linter13();
|
|
15521
15559
|
init_import_map_builders();
|
|
15522
15560
|
init_lint12();
|
|
15561
|
+
init_hook_format2();
|
|
15523
15562
|
target13 = {
|
|
15524
15563
|
name: "cursor",
|
|
15525
15564
|
primaryRootInstructionPath: CURSOR_GENERAL_RULE,
|
|
@@ -15641,6 +15680,7 @@ var init_cursor2 = __esm({
|
|
|
15641
15680
|
permissions: "native"
|
|
15642
15681
|
},
|
|
15643
15682
|
emptyImportMessage: "No Cursor config found (AGENTS.md or .cursor/rules/*.mdc; with --global: ~/.cursor/{rules/*.mdc,AGENTS.md,mcp.json,hooks.json,cursorignore,skills/,agents/,commands/} and legacy ~/.agentsmesh-exports/cursor/user-rules.md).",
|
|
15683
|
+
hookContextEvents: CURSOR_HOOK_CONTEXT_EVENTS,
|
|
15644
15684
|
lintRules: lintRules13,
|
|
15645
15685
|
lint: {
|
|
15646
15686
|
commands: lintCommands6,
|
|
@@ -16902,25 +16942,127 @@ var init_skills3 = __esm({
|
|
|
16902
16942
|
}
|
|
16903
16943
|
});
|
|
16904
16944
|
|
|
16905
|
-
// src/targets/gemini-cli/
|
|
16906
|
-
function
|
|
16907
|
-
|
|
16908
|
-
|
|
16909
|
-
|
|
16910
|
-
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
|
|
16916
|
-
|
|
16917
|
-
|
|
16918
|
-
|
|
16919
|
-
|
|
16920
|
-
|
|
16921
|
-
|
|
16945
|
+
// src/targets/gemini-cli/hook-map.ts
|
|
16946
|
+
function geminiHookEvent(event) {
|
|
16947
|
+
return CANONICAL_TO_GEMINI.get(event) ?? null;
|
|
16948
|
+
}
|
|
16949
|
+
function mapGeminiHookEvent(event) {
|
|
16950
|
+
return GEMINI_TO_CANONICAL.get(event) ?? null;
|
|
16951
|
+
}
|
|
16952
|
+
function splitNames(list, separator) {
|
|
16953
|
+
return list.split(separator).map((name) => name.trim()).filter((name) => name.length > 0);
|
|
16954
|
+
}
|
|
16955
|
+
function toGeminiMatcher(geminiEvent, matcher) {
|
|
16956
|
+
if (!TOOL_EVENTS.has(geminiEvent) || !NAME_LIST.test(matcher)) return matcher;
|
|
16957
|
+
const tools = unique(
|
|
16958
|
+
splitNames(matcher, /[|,]/).flatMap((name) => CANONICAL_TO_GEMINI_TOOLS.get(name) ?? [name])
|
|
16959
|
+
);
|
|
16960
|
+
return tools.length > 0 ? `^(?:${tools.join("|")})$` : matcher;
|
|
16961
|
+
}
|
|
16962
|
+
function fromGeminiMatcher(geminiEvent, matcher) {
|
|
16963
|
+
if (!TOOL_EVENTS.has(geminiEvent)) return matcher;
|
|
16964
|
+
const list = ANCHORED_LIST.exec(matcher)?.[1] ?? matcher;
|
|
16965
|
+
if (!NAME_LIST.test(list) || list.includes(",")) return matcher;
|
|
16966
|
+
return unique(
|
|
16967
|
+
splitNames(list, /\|/).map((name) => GEMINI_TO_CANONICAL_TOOLS.get(name) ?? name)
|
|
16968
|
+
).join("|");
|
|
16969
|
+
}
|
|
16970
|
+
var GEMINI_HOOK_CONTEXT_EVENTS, CANONICAL_TO_GEMINI, GEMINI_TO_CANONICAL, TOOL_EVENTS, CANONICAL_TO_GEMINI_TOOLS, GEMINI_TO_CANONICAL_TOOLS, NAME_LIST, ANCHORED_LIST, unique;
|
|
16971
|
+
var init_hook_map = __esm({
|
|
16972
|
+
"src/targets/gemini-cli/hook-map.ts"() {
|
|
16973
|
+
GEMINI_HOOK_CONTEXT_EVENTS = [
|
|
16974
|
+
"UserPromptSubmit",
|
|
16975
|
+
"SessionStart",
|
|
16976
|
+
"PostToolUse"
|
|
16977
|
+
];
|
|
16978
|
+
CANONICAL_TO_GEMINI = /* @__PURE__ */ new Map([
|
|
16979
|
+
["PreToolUse", "BeforeTool"],
|
|
16980
|
+
["PostToolUse", "AfterTool"],
|
|
16981
|
+
["Notification", "Notification"],
|
|
16982
|
+
// BeforeAgent fires after the user submits a prompt and carries `prompt`.
|
|
16983
|
+
["UserPromptSubmit", "BeforeAgent"],
|
|
16984
|
+
// Older mapping, kept so existing SubagentStart hooks still reach Gemini.
|
|
16985
|
+
["SubagentStart", "BeforeAgent"],
|
|
16986
|
+
["SubagentStop", "AfterAgent"],
|
|
16987
|
+
["SessionStart", "SessionStart"]
|
|
16988
|
+
]);
|
|
16989
|
+
GEMINI_TO_CANONICAL = new Map([
|
|
16990
|
+
...[...CANONICAL_TO_GEMINI].map(([canonical, gemini]) => [gemini, canonical]),
|
|
16991
|
+
// BeforeAgent is shared with SubagentStart; import it as the prompt event.
|
|
16992
|
+
["BeforeAgent", "UserPromptSubmit"],
|
|
16993
|
+
// Legacy lowercase names.
|
|
16994
|
+
["preToolUse", "PreToolUse"],
|
|
16995
|
+
["postToolUse", "PostToolUse"],
|
|
16996
|
+
["notification", "Notification"]
|
|
16997
|
+
]);
|
|
16998
|
+
TOOL_EVENTS = /* @__PURE__ */ new Set(["BeforeTool", "AfterTool"]);
|
|
16999
|
+
CANONICAL_TO_GEMINI_TOOLS = /* @__PURE__ */ new Map([
|
|
17000
|
+
["Bash", ["run_shell_command"]],
|
|
17001
|
+
["PowerShell", ["run_shell_command"]],
|
|
17002
|
+
["Edit", ["replace"]],
|
|
17003
|
+
["MultiEdit", ["replace"]],
|
|
17004
|
+
["Write", ["write_file"]],
|
|
17005
|
+
["NotebookEdit", []],
|
|
17006
|
+
["Read", ["read_file", "read_many_files"]],
|
|
17007
|
+
["Grep", ["grep_search"]],
|
|
17008
|
+
["Glob", ["glob"]],
|
|
17009
|
+
["LS", ["list_directory"]],
|
|
17010
|
+
["WebFetch", ["web_fetch"]],
|
|
17011
|
+
["WebSearch", ["google_web_search"]],
|
|
17012
|
+
["TodoWrite", ["write_todos"]]
|
|
17013
|
+
]);
|
|
17014
|
+
GEMINI_TO_CANONICAL_TOOLS = /* @__PURE__ */ new Map([
|
|
17015
|
+
["run_shell_command", "Bash"],
|
|
17016
|
+
["replace", "Edit"],
|
|
17017
|
+
["write_file", "Write"],
|
|
17018
|
+
["read_file", "Read"],
|
|
17019
|
+
["read_many_files", "Read"],
|
|
17020
|
+
["grep_search", "Grep"],
|
|
17021
|
+
["search_file_content", "Grep"],
|
|
17022
|
+
["glob", "Glob"],
|
|
17023
|
+
["list_directory", "LS"],
|
|
17024
|
+
["web_fetch", "WebFetch"],
|
|
17025
|
+
["google_web_search", "WebSearch"],
|
|
17026
|
+
["write_todos", "TodoWrite"]
|
|
17027
|
+
]);
|
|
17028
|
+
NAME_LIST = /^[\w\s,|-]+$/;
|
|
17029
|
+
ANCHORED_LIST = /^\^\(\?:(.*)\)\$$/;
|
|
17030
|
+
unique = (values) => [...new Set(values)];
|
|
16922
17031
|
}
|
|
17032
|
+
});
|
|
17033
|
+
|
|
17034
|
+
// src/targets/gemini-cli/generator/hooks.ts
|
|
17035
|
+
function buildGeminiHooks(hooks) {
|
|
17036
|
+
const result2 = {};
|
|
17037
|
+
for (const [event, entries] of Object.entries(hooks ?? {})) {
|
|
17038
|
+
const geminiEvent = geminiHookEvent(event);
|
|
17039
|
+
if (!geminiEvent || !Array.isArray(entries)) continue;
|
|
17040
|
+
for (const entry of entries) {
|
|
17041
|
+
if (typeof entry !== "object" || entry === null || !hasHookCommand(entry)) continue;
|
|
17042
|
+
const list = result2[geminiEvent] ??= [];
|
|
17043
|
+
list.push({
|
|
17044
|
+
matcher: typeof entry.matcher === "string" ? toGeminiMatcher(geminiEvent, entry.matcher) : entry.matcher,
|
|
17045
|
+
hooks: [
|
|
17046
|
+
{
|
|
17047
|
+
name: `${geminiEvent}-${list.length + 1}`,
|
|
17048
|
+
type: "command",
|
|
17049
|
+
command: getHookCommand(entry),
|
|
17050
|
+
timeout: entry.timeout
|
|
17051
|
+
}
|
|
17052
|
+
]
|
|
17053
|
+
});
|
|
17054
|
+
}
|
|
17055
|
+
}
|
|
17056
|
+
return Object.keys(result2).length > 0 ? result2 : null;
|
|
16923
17057
|
}
|
|
17058
|
+
var init_hooks4 = __esm({
|
|
17059
|
+
"src/targets/gemini-cli/generator/hooks.ts"() {
|
|
17060
|
+
init_hook_command();
|
|
17061
|
+
init_hook_map();
|
|
17062
|
+
}
|
|
17063
|
+
});
|
|
17064
|
+
|
|
17065
|
+
// src/targets/gemini-cli/generator/settings.ts
|
|
16924
17066
|
function generateGeminiSettingsFiles(canonical, enabledFeatures) {
|
|
16925
17067
|
const settings = {};
|
|
16926
17068
|
let hasAnyNativeSettings = false;
|
|
@@ -16932,29 +17074,10 @@ function generateGeminiSettingsFiles(canonical, enabledFeatures) {
|
|
|
16932
17074
|
settings.experimental = { enableAgents: true };
|
|
16933
17075
|
hasAnyNativeSettings = true;
|
|
16934
17076
|
}
|
|
16935
|
-
|
|
16936
|
-
|
|
16937
|
-
|
|
16938
|
-
|
|
16939
|
-
const mappedEntries = entries.filter(
|
|
16940
|
-
(entry) => typeof entry === "object" && entry !== null && hasHookCommand(entry)
|
|
16941
|
-
).map((entry, index) => ({
|
|
16942
|
-
matcher: entry.matcher,
|
|
16943
|
-
hooks: [
|
|
16944
|
-
{
|
|
16945
|
-
name: `${mappedEvent}-${index + 1}`,
|
|
16946
|
-
type: "command",
|
|
16947
|
-
command: getHookCommand(entry),
|
|
16948
|
-
timeout: entry.timeout
|
|
16949
|
-
}
|
|
16950
|
-
]
|
|
16951
|
-
}));
|
|
16952
|
-
return mappedEntries.length > 0 ? [[mappedEvent, mappedEntries]] : [];
|
|
16953
|
-
});
|
|
16954
|
-
if (hookEntries.length > 0) {
|
|
16955
|
-
settings.hooks = Object.fromEntries(hookEntries);
|
|
16956
|
-
hasAnyNativeSettings = true;
|
|
16957
|
-
}
|
|
17077
|
+
const hooks = enabledFeatures.has("hooks") ? buildGeminiHooks(canonical.hooks) : null;
|
|
17078
|
+
if (hooks) {
|
|
17079
|
+
settings.hooks = hooks;
|
|
17080
|
+
hasAnyNativeSettings = true;
|
|
16958
17081
|
}
|
|
16959
17082
|
if (hasAnyNativeSettings) {
|
|
16960
17083
|
settings.context = { fileName: [GEMINI_ROOT, GEMINI_COMPAT_AGENTS] };
|
|
@@ -16964,19 +17087,18 @@ function generateGeminiSettingsFiles(canonical, enabledFeatures) {
|
|
|
16964
17087
|
}
|
|
16965
17088
|
var init_settings2 = __esm({
|
|
16966
17089
|
"src/targets/gemini-cli/generator/settings.ts"() {
|
|
16967
|
-
init_hook_command();
|
|
16968
17090
|
init_constants33();
|
|
17091
|
+
init_hooks4();
|
|
16969
17092
|
}
|
|
16970
17093
|
});
|
|
16971
17094
|
|
|
16972
17095
|
// src/targets/gemini-cli/generator/ignore.ts
|
|
16973
|
-
|
|
16974
|
-
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
16975
|
-
return [{ path: GEMINI_IGNORE, content: canonical.ignore.join("\n") }];
|
|
16976
|
-
}
|
|
17096
|
+
var generateIgnore13;
|
|
16977
17097
|
var init_ignore2 = __esm({
|
|
16978
17098
|
"src/targets/gemini-cli/generator/ignore.ts"() {
|
|
16979
17099
|
init_constants33();
|
|
17100
|
+
init_ignore_output();
|
|
17101
|
+
generateIgnore13 = ignoreOutput(GEMINI_IGNORE);
|
|
16980
17102
|
}
|
|
16981
17103
|
});
|
|
16982
17104
|
|
|
@@ -17255,27 +17377,6 @@ var init_layout6 = __esm({
|
|
|
17255
17377
|
};
|
|
17256
17378
|
}
|
|
17257
17379
|
});
|
|
17258
|
-
function mapGeminiHookEvent(event) {
|
|
17259
|
-
switch (event) {
|
|
17260
|
-
case "BeforeTool":
|
|
17261
|
-
case "preToolUse":
|
|
17262
|
-
return "PreToolUse";
|
|
17263
|
-
case "AfterTool":
|
|
17264
|
-
case "postToolUse":
|
|
17265
|
-
return "PostToolUse";
|
|
17266
|
-
case "Notification":
|
|
17267
|
-
case "notification":
|
|
17268
|
-
return "Notification";
|
|
17269
|
-
case "BeforeAgent":
|
|
17270
|
-
return "SubagentStart";
|
|
17271
|
-
case "AfterAgent":
|
|
17272
|
-
return "SubagentStop";
|
|
17273
|
-
case "SessionStart":
|
|
17274
|
-
return "SessionStart";
|
|
17275
|
-
default:
|
|
17276
|
-
return null;
|
|
17277
|
-
}
|
|
17278
|
-
}
|
|
17279
17380
|
function parseFlexibleFrontmatter(content) {
|
|
17280
17381
|
const yamlOpen = content.indexOf("---");
|
|
17281
17382
|
const tomlOpen = content.indexOf("+++");
|
|
@@ -17349,7 +17450,7 @@ async function importGeminiSettings(projectRoot, results) {
|
|
|
17349
17450
|
(entry) => entry.hooks.filter(
|
|
17350
17451
|
(hook) => hook !== null && typeof hook === "object" && hasHookCommand(hook)
|
|
17351
17452
|
).map((hook) => ({
|
|
17352
|
-
matcher: entry.matcher,
|
|
17453
|
+
matcher: fromGeminiMatcher(event, entry.matcher),
|
|
17353
17454
|
command: getHookCommand(hook),
|
|
17354
17455
|
type: "command",
|
|
17355
17456
|
timeout: typeof hook.timeout === "number" ? hook.timeout : void 0
|
|
@@ -17359,7 +17460,7 @@ async function importGeminiSettings(projectRoot, results) {
|
|
|
17359
17460
|
const legacyMapped = value.filter(
|
|
17360
17461
|
(entry) => entry !== null && typeof entry === "object" && typeof entry.matcher === "string" && hasHookCommand(entry)
|
|
17361
17462
|
).map((entry) => ({
|
|
17362
|
-
matcher: entry.matcher,
|
|
17463
|
+
matcher: fromGeminiMatcher(event, entry.matcher),
|
|
17363
17464
|
command: getHookCommand(entry),
|
|
17364
17465
|
type: "command"
|
|
17365
17466
|
}));
|
|
@@ -17388,7 +17489,7 @@ var init_format_helpers_settings = __esm({
|
|
|
17388
17489
|
init_hook_command();
|
|
17389
17490
|
init_fs();
|
|
17390
17491
|
init_constants33();
|
|
17391
|
-
|
|
17492
|
+
init_hook_map();
|
|
17392
17493
|
}
|
|
17393
17494
|
});
|
|
17394
17495
|
async function importGeminiIgnore(projectRoot, results) {
|
|
@@ -17884,14 +17985,7 @@ function lintPermissions10(canonical, options) {
|
|
|
17884
17985
|
}
|
|
17885
17986
|
function lintHooks12(canonical) {
|
|
17886
17987
|
if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
|
|
17887
|
-
const supported = [
|
|
17888
|
-
"PreToolUse",
|
|
17889
|
-
"PostToolUse",
|
|
17890
|
-
"Notification",
|
|
17891
|
-
"SubagentStart",
|
|
17892
|
-
"SubagentStop",
|
|
17893
|
-
"SessionStart"
|
|
17894
|
-
];
|
|
17988
|
+
const supported = [...CANONICAL_TO_GEMINI.keys()];
|
|
17895
17989
|
return unsupportedHookEventNames(canonical.hooks, supported).map(
|
|
17896
17990
|
(event) => createUnsupportedHookWarning(event, "gemini-cli", supported)
|
|
17897
17991
|
);
|
|
@@ -17899,6 +17993,7 @@ function lintHooks12(canonical) {
|
|
|
17899
17993
|
var init_lint15 = __esm({
|
|
17900
17994
|
"src/targets/gemini-cli/lint.ts"() {
|
|
17901
17995
|
init_helpers();
|
|
17996
|
+
init_hook_map();
|
|
17902
17997
|
}
|
|
17903
17998
|
});
|
|
17904
17999
|
|
|
@@ -17935,6 +18030,7 @@ var init_gemini_cli2 = __esm({
|
|
|
17935
18030
|
init_import_map_builders();
|
|
17936
18031
|
init_lint15();
|
|
17937
18032
|
init_scoped_settings_emit();
|
|
18033
|
+
init_hook_map();
|
|
17938
18034
|
init_settings();
|
|
17939
18035
|
target16 = {
|
|
17940
18036
|
name: "gemini-cli",
|
|
@@ -17985,6 +18081,7 @@ var init_gemini_cli2 = __esm({
|
|
|
17985
18081
|
permissions: lintPermissions10
|
|
17986
18082
|
},
|
|
17987
18083
|
emitScopedSettings: emitScopedGeminiSettings,
|
|
18084
|
+
hookContextEvents: GEMINI_HOOK_CONTEXT_EVENTS,
|
|
17988
18085
|
mergeGeneratedOutputContent(existing, pending, newContent, resolvedPath) {
|
|
17989
18086
|
const base = pending?.content ?? existing;
|
|
17990
18087
|
if (base !== null && resolvedPath === GEMINI_SETTINGS) {
|
|
@@ -18077,14 +18174,10 @@ function generateAgents17(canonical) {
|
|
|
18077
18174
|
content: serializeProjectedAgentSkill(agent)
|
|
18078
18175
|
}));
|
|
18079
18176
|
}
|
|
18080
|
-
function generateIgnore14(canonical) {
|
|
18081
|
-
if (canonical.ignore.length === 0) return [];
|
|
18082
|
-
return [{ path: GOOSE_IGNORE, content: canonical.ignore.join("\n") }];
|
|
18083
|
-
}
|
|
18084
18177
|
function generateHooks12(canonical) {
|
|
18085
18178
|
return buildWrappedCommandHooks(canonical, GOOSE_HOOKS_FILE);
|
|
18086
18179
|
}
|
|
18087
|
-
var generateRules17, generatePermissions12;
|
|
18180
|
+
var generateRules17, generateIgnore14, generatePermissions12;
|
|
18088
18181
|
var init_generator20 = __esm({
|
|
18089
18182
|
"src/targets/goose/generator.ts"() {
|
|
18090
18183
|
init_managed_blocks();
|
|
@@ -18094,7 +18187,9 @@ var init_generator20 = __esm({
|
|
|
18094
18187
|
init_command_skill();
|
|
18095
18188
|
init_wrapped_command_hooks();
|
|
18096
18189
|
init_constants16();
|
|
18190
|
+
init_ignore_output();
|
|
18097
18191
|
generateRules17 = (canonical) => embeddedRootRule(canonical, GOOSE_TARGET, GOOSE_ROOT_FILE);
|
|
18192
|
+
generateIgnore14 = ignoreOutput(GOOSE_IGNORE);
|
|
18098
18193
|
generatePermissions12 = NO_OUTPUTS;
|
|
18099
18194
|
}
|
|
18100
18195
|
});
|
|
@@ -18999,10 +19094,6 @@ function generateAgents18(canonical) {
|
|
|
18999
19094
|
};
|
|
19000
19095
|
});
|
|
19001
19096
|
}
|
|
19002
|
-
function generateIgnore16(canonical) {
|
|
19003
|
-
if (canonical.ignore.length === 0) return [];
|
|
19004
|
-
return [{ path: JUNIE_IGNORE, content: canonical.ignore.join("\n") }];
|
|
19005
|
-
}
|
|
19006
19097
|
function generateSkills17(canonical) {
|
|
19007
19098
|
return generateEmbeddedSkills(canonical, JUNIE_SKILLS_DIR);
|
|
19008
19099
|
}
|
|
@@ -19014,6 +19105,7 @@ function renderJunieGlobalInstructions(canonical) {
|
|
|
19014
19105
|
});
|
|
19015
19106
|
return appendEmbeddedRulesBlock(root?.body.trim() ?? "", nonRootRules);
|
|
19016
19107
|
}
|
|
19108
|
+
var generateIgnore16;
|
|
19017
19109
|
var init_generator22 = __esm({
|
|
19018
19110
|
"src/targets/junie/generator.ts"() {
|
|
19019
19111
|
init_mcp_servers();
|
|
@@ -19021,7 +19113,9 @@ var init_generator22 = __esm({
|
|
|
19021
19113
|
init_managed_blocks();
|
|
19022
19114
|
init_markdown();
|
|
19023
19115
|
init_constants18();
|
|
19116
|
+
init_ignore_output();
|
|
19024
19117
|
init_global_config2();
|
|
19118
|
+
generateIgnore16 = ignoreOutput(JUNIE_IGNORE);
|
|
19025
19119
|
}
|
|
19026
19120
|
});
|
|
19027
19121
|
|
|
@@ -19437,10 +19531,6 @@ function generateMcp15(canonical) {
|
|
|
19437
19531
|
}
|
|
19438
19532
|
];
|
|
19439
19533
|
}
|
|
19440
|
-
function generateIgnore17(canonical) {
|
|
19441
|
-
if (canonical.ignore.length === 0) return [];
|
|
19442
|
-
return [{ path: KILO_CODE_IGNORE, content: canonical.ignore.join("\n") }];
|
|
19443
|
-
}
|
|
19444
19534
|
function generatePermissions15(canonical) {
|
|
19445
19535
|
if (!canonical.permissions) return [];
|
|
19446
19536
|
const { allow, deny } = canonical.permissions;
|
|
@@ -19455,11 +19545,14 @@ function generatePermissions15(canonical) {
|
|
|
19455
19545
|
function generateSkills18(canonical) {
|
|
19456
19546
|
return generateEmbeddedSkills(canonical, KILO_CODE_SKILLS_DIR);
|
|
19457
19547
|
}
|
|
19548
|
+
var generateIgnore17;
|
|
19458
19549
|
var init_generator23 = __esm({
|
|
19459
19550
|
"src/targets/kilo-code/generator.ts"() {
|
|
19460
19551
|
init_embedded_skill();
|
|
19461
19552
|
init_markdown();
|
|
19462
19553
|
init_constants19();
|
|
19554
|
+
init_ignore_output();
|
|
19555
|
+
generateIgnore17 = ignoreOutput(KILO_CODE_IGNORE);
|
|
19463
19556
|
}
|
|
19464
19557
|
});
|
|
19465
19558
|
var kiloNonRootRuleMapper, kiloCommandMapper, kiloAgentMapper;
|
|
@@ -21204,11 +21297,7 @@ function buildKiroAgentOutputs(canonical, rules = []) {
|
|
|
21204
21297
|
function generateAgents21(canonical) {
|
|
21205
21298
|
return buildKiroAgentOutputs(canonical);
|
|
21206
21299
|
}
|
|
21207
|
-
|
|
21208
|
-
if (canonical.ignore.length === 0) return [];
|
|
21209
|
-
return [{ path: KIRO_IGNORE, content: canonical.ignore.join("\n") }];
|
|
21210
|
-
}
|
|
21211
|
-
var generatePermissions17;
|
|
21300
|
+
var generateIgnore18, generatePermissions17;
|
|
21212
21301
|
var init_generator25 = __esm({
|
|
21213
21302
|
"src/targets/kiro/generator.ts"() {
|
|
21214
21303
|
init_no_outputs();
|
|
@@ -21217,6 +21306,8 @@ var init_generator25 = __esm({
|
|
|
21217
21306
|
init_markdown();
|
|
21218
21307
|
init_hook_format3();
|
|
21219
21308
|
init_constants21();
|
|
21309
|
+
init_ignore_output();
|
|
21310
|
+
generateIgnore18 = ignoreOutput(KIRO_IGNORE);
|
|
21220
21311
|
generatePermissions17 = NO_OUTPUTS;
|
|
21221
21312
|
}
|
|
21222
21313
|
});
|
|
@@ -23859,10 +23950,6 @@ function generateMcp19(canonical) {
|
|
|
23859
23950
|
const content = JSON.stringify({ mcpServers: canonical.mcp.mcpServers }, null, 2);
|
|
23860
23951
|
return [{ path: QWEN_SETTINGS, content }];
|
|
23861
23952
|
}
|
|
23862
|
-
function generateIgnore21(canonical) {
|
|
23863
|
-
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
23864
|
-
return [{ path: QWEN_IGNORE, content: canonical.ignore.join("\n") }];
|
|
23865
|
-
}
|
|
23866
23953
|
function generateHooks18(canonical) {
|
|
23867
23954
|
if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
|
|
23868
23955
|
const hooks = buildClaudeHooksObjectFromCanonical(canonical);
|
|
@@ -23880,11 +23967,14 @@ function generatePermissions21(canonical) {
|
|
|
23880
23967
|
if (ask.length > 0) permissions.ask = ask;
|
|
23881
23968
|
return [{ path: QWEN_SETTINGS, content: JSON.stringify({ permissions }, null, 2) }];
|
|
23882
23969
|
}
|
|
23970
|
+
var generateIgnore21;
|
|
23883
23971
|
var init_generator29 = __esm({
|
|
23884
23972
|
"src/targets/qwen-code/generator.ts"() {
|
|
23885
23973
|
init_markdown();
|
|
23886
23974
|
init_hooks_format2();
|
|
23887
23975
|
init_constants25();
|
|
23976
|
+
init_ignore_output();
|
|
23977
|
+
generateIgnore21 = ignoreOutput(QWEN_IGNORE);
|
|
23888
23978
|
}
|
|
23889
23979
|
});
|
|
23890
23980
|
|
|
@@ -25582,10 +25672,6 @@ function generateMcp23(canonical) {
|
|
|
25582
25672
|
}
|
|
25583
25673
|
];
|
|
25584
25674
|
}
|
|
25585
|
-
function generateIgnore24(canonical) {
|
|
25586
|
-
if (canonical.ignore.length === 0) return [];
|
|
25587
|
-
return [{ path: TRAE_IGNORE, content: canonical.ignore.join("\n") }];
|
|
25588
|
-
}
|
|
25589
25675
|
function generateHooks20(canonical) {
|
|
25590
25676
|
if (!canonical.hooks) return [];
|
|
25591
25677
|
const hooks = {};
|
|
@@ -25606,12 +25692,15 @@ function generateHooks20(canonical) {
|
|
|
25606
25692
|
if (Object.keys(hooks).length === 0) return [];
|
|
25607
25693
|
return [{ path: TRAE_HOOKS_FILE, content: JSON.stringify({ version: 1, hooks }, null, 2) }];
|
|
25608
25694
|
}
|
|
25695
|
+
var generateIgnore24;
|
|
25609
25696
|
var init_generator33 = __esm({
|
|
25610
25697
|
"src/targets/trae/generator.ts"() {
|
|
25611
25698
|
init_embedded_skill();
|
|
25612
25699
|
init_markdown();
|
|
25613
25700
|
init_hook_command();
|
|
25614
25701
|
init_constants29();
|
|
25702
|
+
init_ignore_output();
|
|
25703
|
+
generateIgnore24 = ignoreOutput(TRAE_IGNORE);
|
|
25615
25704
|
}
|
|
25616
25705
|
});
|
|
25617
25706
|
|
|
@@ -26318,11 +26407,7 @@ function generateMcp24(canonical, ctx) {
|
|
|
26318
26407
|
const content = JSON.stringify({ mcpServers: canonical.mcp.mcpServers }, null, 2);
|
|
26319
26408
|
return [{ path, content }];
|
|
26320
26409
|
}
|
|
26321
|
-
|
|
26322
|
-
if (canonical.ignore.length === 0) return [];
|
|
26323
|
-
return [{ path: WARP_IGNORE_FILE, content: canonical.ignore.join("\n") }];
|
|
26324
|
-
}
|
|
26325
|
-
var generateRules31, generatePermissions24, generateHooks21;
|
|
26410
|
+
var generateRules31, generatePermissions24, generateHooks21, generateIgnore25;
|
|
26326
26411
|
var init_generator34 = __esm({
|
|
26327
26412
|
"src/targets/warp/generator.ts"() {
|
|
26328
26413
|
init_managed_blocks();
|
|
@@ -26331,9 +26416,11 @@ var init_generator34 = __esm({
|
|
|
26331
26416
|
init_projected_agent_skill();
|
|
26332
26417
|
init_command_skill();
|
|
26333
26418
|
init_constants30();
|
|
26419
|
+
init_ignore_output();
|
|
26334
26420
|
generateRules31 = (canonical) => embeddedRootRule(canonical, WARP_TARGET, WARP_ROOT_FILE);
|
|
26335
26421
|
generatePermissions24 = NO_OUTPUTS;
|
|
26336
26422
|
generateHooks21 = NO_OUTPUTS;
|
|
26423
|
+
generateIgnore25 = ignoreOutput(WARP_IGNORE_FILE);
|
|
26337
26424
|
}
|
|
26338
26425
|
});
|
|
26339
26426
|
|
|
@@ -26958,13 +27045,12 @@ var init_rules4 = __esm({
|
|
|
26958
27045
|
});
|
|
26959
27046
|
|
|
26960
27047
|
// src/targets/windsurf/generator/ignore.ts
|
|
26961
|
-
|
|
26962
|
-
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
26963
|
-
return [{ path: CODEIUM_IGNORE, content: canonical.ignore.join("\n") }];
|
|
26964
|
-
}
|
|
27048
|
+
var generateIgnore26;
|
|
26965
27049
|
var init_ignore3 = __esm({
|
|
26966
27050
|
"src/targets/windsurf/generator/ignore.ts"() {
|
|
26967
27051
|
init_constants34();
|
|
27052
|
+
init_ignore_output();
|
|
27053
|
+
generateIgnore26 = ignoreOutput(CODEIUM_IGNORE);
|
|
26968
27054
|
}
|
|
26969
27055
|
});
|
|
26970
27056
|
|
|
@@ -27030,7 +27116,7 @@ function canonicalHookEventName(event) {
|
|
|
27030
27116
|
if (KNOWN_CANONICAL_HOOK_EVENTS.includes(event)) return event;
|
|
27031
27117
|
return WINDSURF_TO_CANONICAL.get(event) ?? null;
|
|
27032
27118
|
}
|
|
27033
|
-
var KNOWN_CANONICAL_HOOK_EVENTS, WINDSURF_TO_CANONICAL;
|
|
27119
|
+
var KNOWN_CANONICAL_HOOK_EVENTS, WINDSURF_HOOK_CONTEXT_EVENTS, WINDSURF_TO_CANONICAL;
|
|
27034
27120
|
var init_hook_events = __esm({
|
|
27035
27121
|
"src/targets/windsurf/hook-events.ts"() {
|
|
27036
27122
|
init_hook_types();
|
|
@@ -27043,6 +27129,7 @@ var init_hook_events = __esm({
|
|
|
27043
27129
|
"SubagentStop",
|
|
27044
27130
|
...BEST_EFFORT_HOOK_EVENTS
|
|
27045
27131
|
];
|
|
27132
|
+
WINDSURF_HOOK_CONTEXT_EVENTS = [];
|
|
27046
27133
|
WINDSURF_TO_CANONICAL = new Map(
|
|
27047
27134
|
KNOWN_CANONICAL_HOOK_EVENTS.map((event) => [windsurfEventName(event), event])
|
|
27048
27135
|
);
|
|
@@ -27068,12 +27155,12 @@ function toWindsurfHooks(hooks) {
|
|
|
27068
27155
|
return result2;
|
|
27069
27156
|
}
|
|
27070
27157
|
function generateHooks22(canonical) {
|
|
27071
|
-
if (!canonical.hooks
|
|
27158
|
+
if (!canonical.hooks) return [];
|
|
27072
27159
|
const hooks = toWindsurfHooks(canonical.hooks);
|
|
27073
27160
|
if (Object.keys(hooks).length === 0) return [];
|
|
27074
27161
|
return [{ path: WINDSURF_HOOKS_FILE, content: JSON.stringify({ hooks }, null, 2) }];
|
|
27075
27162
|
}
|
|
27076
|
-
var
|
|
27163
|
+
var init_hooks5 = __esm({
|
|
27077
27164
|
"src/targets/windsurf/generator/hooks.ts"() {
|
|
27078
27165
|
init_hook_command();
|
|
27079
27166
|
init_constants34();
|
|
@@ -27125,7 +27212,7 @@ var init_generator35 = __esm({
|
|
|
27125
27212
|
init_workflows();
|
|
27126
27213
|
init_agents4();
|
|
27127
27214
|
init_mcp4();
|
|
27128
|
-
|
|
27215
|
+
init_hooks5();
|
|
27129
27216
|
init_skills4();
|
|
27130
27217
|
init_permissions6();
|
|
27131
27218
|
}
|
|
@@ -27244,7 +27331,7 @@ async function parseHooks(hooksPath, onParseError) {
|
|
|
27244
27331
|
return result2;
|
|
27245
27332
|
}
|
|
27246
27333
|
var VALID_TYPES;
|
|
27247
|
-
var
|
|
27334
|
+
var init_hooks6 = __esm({
|
|
27248
27335
|
"src/canonical/features/hooks.ts"() {
|
|
27249
27336
|
init_syntax_error();
|
|
27250
27337
|
init_fs();
|
|
@@ -27325,7 +27412,7 @@ var WILDCARD_MATCHER;
|
|
|
27325
27412
|
var init_importer_hooks2 = __esm({
|
|
27326
27413
|
"src/targets/windsurf/importer-hooks.ts"() {
|
|
27327
27414
|
init_canonical_paths();
|
|
27328
|
-
|
|
27415
|
+
init_hooks6();
|
|
27329
27416
|
init_fs();
|
|
27330
27417
|
init_constants34();
|
|
27331
27418
|
init_hook_events();
|
|
@@ -27626,6 +27713,7 @@ var init_windsurf2 = __esm({
|
|
|
27626
27713
|
init_merge14();
|
|
27627
27714
|
init_linter32();
|
|
27628
27715
|
init_lint31();
|
|
27716
|
+
init_hook_events();
|
|
27629
27717
|
init_import_map_builders();
|
|
27630
27718
|
init_conversions();
|
|
27631
27719
|
init_projected_agent_skill();
|
|
@@ -27757,6 +27845,7 @@ var init_windsurf2 = __esm({
|
|
|
27757
27845
|
},
|
|
27758
27846
|
emptyImportMessage: "No Windsurf config found (.windsurfrules, .windsurf/rules, .windsurfignore, or .codeiumignore).",
|
|
27759
27847
|
supportsConversion: { agents: true },
|
|
27848
|
+
hookContextEvents: WINDSURF_HOOK_CONTEXT_EVENTS,
|
|
27760
27849
|
lintRules: lintRules32,
|
|
27761
27850
|
lint: {
|
|
27762
27851
|
commands: lintCommands10,
|
|
@@ -28901,6 +28990,23 @@ var init_builtin_targets = __esm({
|
|
|
28901
28990
|
}
|
|
28902
28991
|
});
|
|
28903
28992
|
|
|
28993
|
+
// src/targets/catalog/recall-hook-targets.ts
|
|
28994
|
+
init_recall_hook_scaffold();
|
|
28995
|
+
init_builtin_targets();
|
|
28996
|
+
init_registry();
|
|
28997
|
+
function withTargetRecallHooks(canonical, target34) {
|
|
28998
|
+
const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
|
|
28999
|
+
const contextEvents = descriptor34?.hookContextEvents;
|
|
29000
|
+
if (contextEvents === void 0 || canonical.hooks === null) return canonical;
|
|
29001
|
+
const hooks = {};
|
|
29002
|
+
for (const [event, entries] of Object.entries(canonical.hooks)) {
|
|
29003
|
+
if (!Array.isArray(entries)) continue;
|
|
29004
|
+
const kept = contextEvents.includes(event) ? entries : entries.filter((entry) => !isRecallHookCommand(entry?.command));
|
|
29005
|
+
if (kept.length > 0) hooks[event] = kept;
|
|
29006
|
+
}
|
|
29007
|
+
return { ...canonical, hooks };
|
|
29008
|
+
}
|
|
29009
|
+
|
|
28904
29010
|
// src/core/generate/engine.ts
|
|
28905
29011
|
init_builtin_targets();
|
|
28906
29012
|
init_registry();
|
|
@@ -29803,11 +29909,12 @@ async function generateHooksFeature(results, targets, canonical, projectRoot, sc
|
|
|
29803
29909
|
const gen = resolveTargetFeatureGenerator(target34, "hooks", config, scope) ?? getDescriptor(target34)?.generators.generateHooks;
|
|
29804
29910
|
if (!gen) continue;
|
|
29805
29911
|
const ctx = featureContext(target34, "hooks", scope);
|
|
29806
|
-
|
|
29912
|
+
const projected = withTargetRecallHooks(canonical, target34);
|
|
29913
|
+
let outputs = [...gen(projected, ctx)];
|
|
29807
29914
|
const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
|
|
29808
29915
|
const post = descriptor34?.postProcessHookOutputs;
|
|
29809
29916
|
if (post) {
|
|
29810
|
-
outputs = [...await post(projectRoot,
|
|
29917
|
+
outputs = [...await post(projectRoot, projected, outputs)];
|
|
29811
29918
|
}
|
|
29812
29919
|
const options = outputMergeOptions(target34);
|
|
29813
29920
|
for (const out2 of outputs) {
|
|
@@ -29820,7 +29927,7 @@ async function generateScopedSettingsFeature(results, targets, canonical, projec
|
|
|
29820
29927
|
const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
|
|
29821
29928
|
const emit = descriptor34?.emitScopedSettings;
|
|
29822
29929
|
if (!emit) continue;
|
|
29823
|
-
const outputs = emit(canonical, scope, enabledFeatures);
|
|
29930
|
+
const outputs = emit(withTargetRecallHooks(canonical, target34), scope, enabledFeatures);
|
|
29824
29931
|
if (outputs.length === 0) continue;
|
|
29825
29932
|
const options = outputMergeOptions(target34);
|
|
29826
29933
|
for (const out2 of outputs) {
|
|
@@ -29863,7 +29970,12 @@ async function generate(ctx) {
|
|
|
29863
29970
|
const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
|
|
29864
29971
|
const scopeExtras = descriptor34?.globalSupport?.scopeExtras;
|
|
29865
29972
|
if (scopeExtras) {
|
|
29866
|
-
const extras = await scopeExtras(
|
|
29973
|
+
const extras = await scopeExtras(
|
|
29974
|
+
withTargetRecallHooks(canonical, target34),
|
|
29975
|
+
projectRoot,
|
|
29976
|
+
scope,
|
|
29977
|
+
enabledFeatures
|
|
29978
|
+
);
|
|
29867
29979
|
await emitScopeExtras(results, target34, extras, projectRoot);
|
|
29868
29980
|
}
|
|
29869
29981
|
}
|
|
@@ -31170,7 +31282,7 @@ async function parsePermissions(permissionsPath, onParseError) {
|
|
|
31170
31282
|
}
|
|
31171
31283
|
|
|
31172
31284
|
// src/canonical/load/loader.ts
|
|
31173
|
-
|
|
31285
|
+
init_hooks6();
|
|
31174
31286
|
|
|
31175
31287
|
// src/canonical/features/ignore.ts
|
|
31176
31288
|
init_fs();
|
|
@@ -31219,10 +31331,10 @@ function ruleSlug4(r) {
|
|
|
31219
31331
|
function mergeByKey(base, overlay, key) {
|
|
31220
31332
|
return [...new Map([...base, ...overlay].map((item) => [key(item), item])).values()];
|
|
31221
31333
|
}
|
|
31222
|
-
function mergeCanonicalFiles(base, overlay) {
|
|
31334
|
+
function mergeCanonicalFiles(base, overlay, options = {}) {
|
|
31223
31335
|
const mcp = mergeMcp7(base.mcp, overlay.mcp);
|
|
31224
31336
|
const permissions = mergePermissions2(base.permissions, overlay.permissions);
|
|
31225
|
-
const hooks =
|
|
31337
|
+
const hooks = options.hooks === "combine" ? combineHooks(base.hooks, overlay.hooks) : overrideHooks(base.hooks, overlay.hooks);
|
|
31226
31338
|
const ignore = mergeUniqueStrings(base.ignore, overlay.ignore);
|
|
31227
31339
|
return {
|
|
31228
31340
|
rules: mergeByKey(base.rules, overlay.rules, ruleSlug4),
|
|
@@ -31261,17 +31373,31 @@ function mergeUniqueStrings(base, overlay) {
|
|
|
31261
31373
|
}
|
|
31262
31374
|
return merged;
|
|
31263
31375
|
}
|
|
31264
|
-
function
|
|
31376
|
+
function overrideHooks(base, overlay) {
|
|
31265
31377
|
if (!base && !overlay) return null;
|
|
31266
31378
|
const result2 = {};
|
|
31267
|
-
const
|
|
31268
|
-
for (const k of keys) {
|
|
31379
|
+
for (const k of hookEvents(base, overlay)) {
|
|
31269
31380
|
const o = overlay?.[k];
|
|
31270
|
-
|
|
31271
|
-
|
|
31381
|
+
result2[k] = o !== void 0 && o.length > 0 ? o : base?.[k] ?? [];
|
|
31382
|
+
}
|
|
31383
|
+
return result2;
|
|
31384
|
+
}
|
|
31385
|
+
function combineHooks(first, then) {
|
|
31386
|
+
if (!first && !then) return null;
|
|
31387
|
+
const result2 = {};
|
|
31388
|
+
for (const k of hookEvents(first, then)) {
|
|
31389
|
+
const kept = first?.[k] ?? [];
|
|
31390
|
+
const defined = new Set(kept.map(hookKey));
|
|
31391
|
+
result2[k] = [...kept, ...(then?.[k] ?? []).filter((entry) => !defined.has(hookKey(entry)))];
|
|
31272
31392
|
}
|
|
31273
31393
|
return result2;
|
|
31274
31394
|
}
|
|
31395
|
+
function hookEvents(a, b) {
|
|
31396
|
+
return [.../* @__PURE__ */ new Set([...Object.keys(a ?? {}), ...Object.keys(b ?? {})])];
|
|
31397
|
+
}
|
|
31398
|
+
function hookKey(entry) {
|
|
31399
|
+
return JSON.stringify([entry.type ?? "command", entry.matcher, entry.command]);
|
|
31400
|
+
}
|
|
31275
31401
|
|
|
31276
31402
|
// src/config/resolve/native-format-detector.ts
|
|
31277
31403
|
init_fs();
|
|
@@ -32042,7 +32168,7 @@ function gateExtendElevatedArtifacts(canonical, ext) {
|
|
|
32042
32168
|
});
|
|
32043
32169
|
}
|
|
32044
32170
|
init_mcp();
|
|
32045
|
-
|
|
32171
|
+
init_hooks6();
|
|
32046
32172
|
|
|
32047
32173
|
// src/install/pack/pack-reader.ts
|
|
32048
32174
|
init_fs();
|
|
@@ -32129,7 +32255,7 @@ async function loadPacksCanonical(abDir) {
|
|
|
32129
32255
|
const canonical = await loadPackCanonical(packDir);
|
|
32130
32256
|
const filtered = filterCanonicalByFeatures(canonical, meta.features);
|
|
32131
32257
|
const picked = applyExtendPick(filtered, meta.features, meta.pick, meta.name);
|
|
32132
|
-
merged = mergeCanonicalFiles(merged, picked);
|
|
32258
|
+
merged = mergeCanonicalFiles(merged, picked, { hooks: "combine" });
|
|
32133
32259
|
}
|
|
32134
32260
|
return merged;
|
|
32135
32261
|
}
|
|
@@ -32180,9 +32306,10 @@ async function loadCanonicalWithExtends(config, configDir, options = {}, canonic
|
|
|
32180
32306
|
merged = mergeCanonicalFiles(merged, picked);
|
|
32181
32307
|
}
|
|
32182
32308
|
const packsCanonical = await loadPacksCanonical(canonicalDir);
|
|
32183
|
-
merged = mergeCanonicalFiles(merged, packsCanonical);
|
|
32309
|
+
merged = mergeCanonicalFiles(merged, packsCanonical, { hooks: "combine" });
|
|
32184
32310
|
const localCanonical = await loadCanonicalFiles(canonicalDir);
|
|
32185
32311
|
merged = mergeCanonicalFiles(merged, localCanonical);
|
|
32312
|
+
merged = { ...merged, hooks: combineHooks(merged.hooks, packsCanonical.hooks) };
|
|
32186
32313
|
return { canonical: merged, resolvedExtends };
|
|
32187
32314
|
}
|
|
32188
32315
|
|
|
@@ -32461,6 +32588,9 @@ function lintRuleScopeInversion(input) {
|
|
|
32461
32588
|
}
|
|
32462
32589
|
return out2;
|
|
32463
32590
|
}
|
|
32591
|
+
|
|
32592
|
+
// src/lessons/graph-store.ts
|
|
32593
|
+
init_fs_text_encoding();
|
|
32464
32594
|
var CURRENT_GRAPH_VERSION = 2;
|
|
32465
32595
|
var VersionSchema = z.union([z.literal(1), z.literal(2)]);
|
|
32466
32596
|
var MAX_RULE_LENGTH = 2e3;
|
|
@@ -32504,25 +32634,54 @@ var LessonsGraphSchema = z.object({
|
|
|
32504
32634
|
function parseGraph(raw) {
|
|
32505
32635
|
return LessonsGraphSchema.parse(raw);
|
|
32506
32636
|
}
|
|
32637
|
+
function emptyGraph() {
|
|
32638
|
+
return { version: CURRENT_GRAPH_VERSION, lessons: {}, topics: {}, triggers: {} };
|
|
32639
|
+
}
|
|
32507
32640
|
|
|
32508
32641
|
// src/lessons/graph-store.ts
|
|
32509
|
-
var
|
|
32642
|
+
var LESSONS_GRAPH_PATH = ".agentsmesh/lessons/lessons.json";
|
|
32510
32643
|
function graphFilePath(projectRoot) {
|
|
32511
|
-
return resolve(projectRoot,
|
|
32644
|
+
return resolve(projectRoot, LESSONS_GRAPH_PATH);
|
|
32512
32645
|
}
|
|
32513
32646
|
function loadLessonsGraph(projectRoot) {
|
|
32514
32647
|
const raw = readFileSync(graphFilePath(projectRoot), "utf8");
|
|
32515
|
-
return parseGraph(JSON.parse(raw));
|
|
32648
|
+
return parseGraph(JSON.parse(stripBom(raw)));
|
|
32516
32649
|
}
|
|
32517
32650
|
function tryLoadLessonsGraph(projectRoot) {
|
|
32518
32651
|
if (!existsSync(graphFilePath(projectRoot))) return null;
|
|
32519
32652
|
return loadLessonsGraph(projectRoot);
|
|
32520
32653
|
}
|
|
32654
|
+
var LessonsGraphReadOnlyError = class extends Error {
|
|
32655
|
+
constructor() {
|
|
32656
|
+
super(
|
|
32657
|
+
`${LESSONS_GRAPH_PATH} is read-only, so nothing was saved. Make it writable (chmod u+w ${LESSONS_GRAPH_PATH}) to change lessons.`
|
|
32658
|
+
);
|
|
32659
|
+
this.name = "LessonsGraphReadOnlyError";
|
|
32660
|
+
}
|
|
32661
|
+
};
|
|
32662
|
+
function fileMode(path) {
|
|
32663
|
+
try {
|
|
32664
|
+
return statSync(path).mode & 511;
|
|
32665
|
+
} catch {
|
|
32666
|
+
return void 0;
|
|
32667
|
+
}
|
|
32668
|
+
}
|
|
32669
|
+
function isWritable(path) {
|
|
32670
|
+
try {
|
|
32671
|
+
accessSync(path, constants.W_OK);
|
|
32672
|
+
return true;
|
|
32673
|
+
} catch {
|
|
32674
|
+
return false;
|
|
32675
|
+
}
|
|
32676
|
+
}
|
|
32521
32677
|
function saveLessonsGraph(projectRoot, graph) {
|
|
32522
32678
|
const path = graphFilePath(projectRoot);
|
|
32523
32679
|
mkdirSync(dirname(path), { recursive: true });
|
|
32680
|
+
const mode = fileMode(path);
|
|
32681
|
+
if (mode !== void 0 && !isWritable(path)) throw new LessonsGraphReadOnlyError();
|
|
32524
32682
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
32525
32683
|
writeFileSync(tmp, serializeGraph(graph), "utf8");
|
|
32684
|
+
if (mode !== void 0) chmodSync(tmp, mode);
|
|
32526
32685
|
renameSync(tmp, path);
|
|
32527
32686
|
}
|
|
32528
32687
|
function serializeGraph(graph) {
|
|
@@ -32566,9 +32725,76 @@ Graph \`.agentsmesh/lessons/lessons.json\` is canonical; never hand-edit it. Man
|
|
|
32566
32725
|
**Capture:** after any failure, user correction, regression, wrong assumption, useful surprise, repeated friction, or non-obvious fix, MUST self-critique and run \`agentsmesh lessons add "<imperative rule>" --topic <id> --trigger-file <glob> --evidence <sha|lesson-id>\`.
|
|
32567
32726
|
|
|
32568
32727
|
**Before final:** report \`Lesson: captured <id>\` or \`Lesson: none\`. No recall/capture gate = task incomplete. No shell: use \`lessons_query\` / \`lessons_add\`.`;
|
|
32728
|
+
var MAX_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
32729
|
+
function runGit2(cwd, args, timeoutMs) {
|
|
32730
|
+
const r = spawnSync("git", [...args], {
|
|
32731
|
+
cwd,
|
|
32732
|
+
encoding: "utf8",
|
|
32733
|
+
maxBuffer: MAX_OUTPUT_BYTES,
|
|
32734
|
+
timeout: timeoutMs,
|
|
32735
|
+
windowsHide: true
|
|
32736
|
+
});
|
|
32737
|
+
const status = r.error === void 0 ? r.status ?? -1 : -1;
|
|
32738
|
+
return { status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
32739
|
+
}
|
|
32740
|
+
|
|
32741
|
+
// src/lessons/git-path-history.ts
|
|
32742
|
+
var GIT_SCAN_TIMEOUT_MS = 3e3;
|
|
32743
|
+
var cache = /* @__PURE__ */ new Map();
|
|
32744
|
+
function readGitPathHistory(projectRoot) {
|
|
32745
|
+
if (!cache.has(projectRoot)) cache.set(projectRoot, scanGitPathHistory(projectRoot));
|
|
32746
|
+
return cache.get(projectRoot) ?? null;
|
|
32747
|
+
}
|
|
32748
|
+
var LOG_ARGS = [
|
|
32749
|
+
"log",
|
|
32750
|
+
"HEAD",
|
|
32751
|
+
"--relative",
|
|
32752
|
+
"-M",
|
|
32753
|
+
"--diff-filter=DR",
|
|
32754
|
+
"--name-status",
|
|
32755
|
+
"-z",
|
|
32756
|
+
"--no-color",
|
|
32757
|
+
"--no-show-signature",
|
|
32758
|
+
"--pretty=format:"
|
|
32759
|
+
];
|
|
32760
|
+
function scanGitPathHistory(projectRoot, timeoutMs = GIT_SCAN_TIMEOUT_MS) {
|
|
32761
|
+
const tracked = runGit2(projectRoot, ["ls-files", "-z"], timeoutMs);
|
|
32762
|
+
if (tracked.status !== 0) return null;
|
|
32763
|
+
const log = runGit2(projectRoot, LOG_ARGS, timeoutMs);
|
|
32764
|
+
if (log.status !== 0) return null;
|
|
32765
|
+
return {
|
|
32766
|
+
tracked: new Set(tracked.stdout.split("\0").filter(Boolean)),
|
|
32767
|
+
...parseRemovals(log.stdout)
|
|
32768
|
+
};
|
|
32769
|
+
}
|
|
32770
|
+
function parseRemovals(out2) {
|
|
32771
|
+
const deleted = /* @__PURE__ */ new Set();
|
|
32772
|
+
const renamedAway = /* @__PURE__ */ new Set();
|
|
32773
|
+
const tokens = out2.split("\0");
|
|
32774
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
32775
|
+
const status = tokens[i] ?? "";
|
|
32776
|
+
const path = tokens[i + 1] ?? "";
|
|
32777
|
+
if (status.startsWith("D")) {
|
|
32778
|
+
if (path !== "") deleted.add(path);
|
|
32779
|
+
i += 1;
|
|
32780
|
+
} else if (status.startsWith("R")) {
|
|
32781
|
+
if (path !== "") renamedAway.add(path);
|
|
32782
|
+
i += 2;
|
|
32783
|
+
}
|
|
32784
|
+
}
|
|
32785
|
+
return { deleted, renamedAway };
|
|
32786
|
+
}
|
|
32787
|
+
|
|
32788
|
+
// src/lessons/project-files.ts
|
|
32569
32789
|
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
32570
32790
|
var MAX_FILES = 2e5;
|
|
32571
|
-
function
|
|
32791
|
+
function projectFilesOf(paths2, gitHistory) {
|
|
32792
|
+
return Object.assign(new Set(paths2), { gitHistory });
|
|
32793
|
+
}
|
|
32794
|
+
function gitHistoryOf(paths2) {
|
|
32795
|
+
return paths2.gitHistory?.() ?? null;
|
|
32796
|
+
}
|
|
32797
|
+
function listProjectFiles(projectRoot, maxFiles = MAX_FILES) {
|
|
32572
32798
|
const out2 = /* @__PURE__ */ new Set();
|
|
32573
32799
|
try {
|
|
32574
32800
|
const stack = [projectRoot];
|
|
@@ -32579,14 +32805,14 @@ function listProjectFiles(projectRoot) {
|
|
|
32579
32805
|
if (!SKIP_DIRS.has(entry.name)) stack.push(join(dir, entry.name));
|
|
32580
32806
|
} else if (entry.isFile()) {
|
|
32581
32807
|
out2.add(toRelPath(projectRoot, join(dir, entry.name)));
|
|
32582
|
-
if (out2.size >
|
|
32808
|
+
if (out2.size > maxFiles) return null;
|
|
32583
32809
|
}
|
|
32584
32810
|
}
|
|
32585
32811
|
}
|
|
32586
32812
|
} catch {
|
|
32587
32813
|
return null;
|
|
32588
32814
|
}
|
|
32589
|
-
return out2;
|
|
32815
|
+
return projectFilesOf(out2, () => readGitPathHistory(projectRoot));
|
|
32590
32816
|
}
|
|
32591
32817
|
|
|
32592
32818
|
// src/lessons/validate-checks.ts
|
|
@@ -32763,6 +32989,265 @@ function collectOrphans(graph, findings) {
|
|
|
32763
32989
|
}
|
|
32764
32990
|
}
|
|
32765
32991
|
}
|
|
32992
|
+
|
|
32993
|
+
// src/lessons/glob-expand.ts
|
|
32994
|
+
var MARK = "\0";
|
|
32995
|
+
var LED = "";
|
|
32996
|
+
var MAX_EXPANSIONS = 64;
|
|
32997
|
+
function fail(reason) {
|
|
32998
|
+
throw new Error(reason);
|
|
32999
|
+
}
|
|
33000
|
+
function skipClass(s, i) {
|
|
33001
|
+
const end = s.indexOf("]", i + 1);
|
|
33002
|
+
return end === -1 ? i + 1 : end + 1;
|
|
33003
|
+
}
|
|
33004
|
+
function markLedStars(body) {
|
|
33005
|
+
let out2 = "";
|
|
33006
|
+
let depth = 0;
|
|
33007
|
+
for (let i = 0; i < body.length; ) {
|
|
33008
|
+
const c2 = body[i];
|
|
33009
|
+
if (c2 === "[") {
|
|
33010
|
+
const end = skipClass(body, i);
|
|
33011
|
+
out2 += body.slice(i, end);
|
|
33012
|
+
i = end;
|
|
33013
|
+
continue;
|
|
33014
|
+
}
|
|
33015
|
+
if (c2 === "{") depth += 1;
|
|
33016
|
+
if (c2 === "}" && depth > 0) depth -= 1;
|
|
33017
|
+
const prev = body[i - 1];
|
|
33018
|
+
if (c2 === "*" && prev === "." && depth > 0) fail(".* inside {\u2026} is not supported");
|
|
33019
|
+
const segmentStart = i === 0 || prev === "/";
|
|
33020
|
+
const afterLeadingDot = prev === "." && (i === 1 || body[i - 2] === "/");
|
|
33021
|
+
const led = c2 === "*" && body[i + 1] !== "*" && prev !== "*" && (segmentStart || afterLeadingDot);
|
|
33022
|
+
out2 += led ? LED : c2;
|
|
33023
|
+
i += 1;
|
|
33024
|
+
}
|
|
33025
|
+
return out2;
|
|
33026
|
+
}
|
|
33027
|
+
function expandBraces(s) {
|
|
33028
|
+
let open2 = -1;
|
|
33029
|
+
for (let i = 0; i < s.length && open2 === -1; ) {
|
|
33030
|
+
if (s[i] === "[") i = skipClass(s, i);
|
|
33031
|
+
else if (s[i] === "}") fail("unbalanced }");
|
|
33032
|
+
else if (s[i] === "{") open2 = i;
|
|
33033
|
+
else i += 1;
|
|
33034
|
+
}
|
|
33035
|
+
if (open2 === -1) return [s];
|
|
33036
|
+
const options = [];
|
|
33037
|
+
let depth = 0;
|
|
33038
|
+
let start = open2 + 1;
|
|
33039
|
+
let close = -1;
|
|
33040
|
+
for (let i = open2 + 1; i < s.length && close === -1; ) {
|
|
33041
|
+
const c2 = s[i];
|
|
33042
|
+
if (c2 === "[") {
|
|
33043
|
+
i = skipClass(s, i);
|
|
33044
|
+
continue;
|
|
33045
|
+
}
|
|
33046
|
+
if (c2 === "{") depth += 1;
|
|
33047
|
+
else if (c2 === "}" && depth > 0) depth -= 1;
|
|
33048
|
+
else if (c2 === "}") close = i;
|
|
33049
|
+
else if (c2 === "," && depth === 0) {
|
|
33050
|
+
options.push(s.slice(start, i));
|
|
33051
|
+
start = i + 1;
|
|
33052
|
+
}
|
|
33053
|
+
i += 1;
|
|
33054
|
+
}
|
|
33055
|
+
if (close === -1) fail("unclosed {");
|
|
33056
|
+
if (options.length === 0)
|
|
33057
|
+
fail("brace groups need a comma, e.g. {a,b} (ranges are not supported)");
|
|
33058
|
+
options.push(s.slice(start, close));
|
|
33059
|
+
const suffixes = expandBraces(s.slice(close + 1));
|
|
33060
|
+
const out2 = [];
|
|
33061
|
+
for (const option of options) {
|
|
33062
|
+
for (const head of expandBraces(option)) {
|
|
33063
|
+
for (const tail of suffixes) {
|
|
33064
|
+
out2.push(s.slice(0, open2) + MARK + head + MARK + tail);
|
|
33065
|
+
if (out2.length > MAX_EXPANSIONS) fail(`more than ${MAX_EXPANSIONS} brace expansions`);
|
|
33066
|
+
}
|
|
33067
|
+
}
|
|
33068
|
+
}
|
|
33069
|
+
return out2;
|
|
33070
|
+
}
|
|
33071
|
+
|
|
33072
|
+
// src/lessons/glob-parse.ts
|
|
33073
|
+
var MAX_GLOB_LENGTH = 256;
|
|
33074
|
+
function parseGlob(pattern) {
|
|
33075
|
+
try {
|
|
33076
|
+
return parseOrThrow(pattern);
|
|
33077
|
+
} catch (err) {
|
|
33078
|
+
return err instanceof Error ? err.message : String(err);
|
|
33079
|
+
}
|
|
33080
|
+
}
|
|
33081
|
+
function parseOrThrow(pattern) {
|
|
33082
|
+
if (pattern.length > MAX_GLOB_LENGTH) fail(`longer than ${MAX_GLOB_LENGTH} characters`);
|
|
33083
|
+
if (pattern.includes("\\")) fail("backslash escapes are not supported (use / as separator)");
|
|
33084
|
+
if (/["\u0000-\u001f]/.test(pattern)) fail("quotes and control characters are not supported");
|
|
33085
|
+
if (/[()|]/.test(pattern.replace(/\[[^\]/]*\]/g, ""))) {
|
|
33086
|
+
fail("extglobs and (\u2026)/| groups are not supported (use {a,b})");
|
|
33087
|
+
}
|
|
33088
|
+
if (/[[\]{}]\+/.test(pattern)) fail("+ after a class or brace is a regex quantifier");
|
|
33089
|
+
const negated = pattern.startsWith("!");
|
|
33090
|
+
let body = negated ? pattern.slice(1) : pattern;
|
|
33091
|
+
if (body.startsWith("./")) body = body.slice(2);
|
|
33092
|
+
if (body.startsWith("!") || body.startsWith("./")) fail("use a single leading ! and ./");
|
|
33093
|
+
if (body === "") fail("empty pattern");
|
|
33094
|
+
const fastPath = !negated && (body === "*.*" || body === "**/*.*");
|
|
33095
|
+
if (fastPath) body = `${body.slice(0, -1)}?*`;
|
|
33096
|
+
return { negated, alternatives: expandBraces(markLedStars(body)).map(parseAlternative) };
|
|
33097
|
+
}
|
|
33098
|
+
function parseAlternative(alt) {
|
|
33099
|
+
const raw = alt.split("/");
|
|
33100
|
+
const out2 = [];
|
|
33101
|
+
raw.forEach((segment, i) => {
|
|
33102
|
+
if (segment.includes("**")) {
|
|
33103
|
+
if (segment !== "**") fail("** must be a whole segment, outside {\u2026} (use * in a segment)");
|
|
33104
|
+
if (out2.at(-1)?.k === "globstar") return;
|
|
33105
|
+
const before = raw[i - 1] ?? "";
|
|
33106
|
+
const trailing = raw.slice(i + 1).every((s) => s === "**");
|
|
33107
|
+
const min1 = trailing && (before.endsWith("*") || before.endsWith(LED));
|
|
33108
|
+
out2.push({ k: "globstar", min1 });
|
|
33109
|
+
return;
|
|
33110
|
+
}
|
|
33111
|
+
const tokens = tokenize(segment, alt);
|
|
33112
|
+
const guarded = segment.includes(LED);
|
|
33113
|
+
const literal = tokens.every((t) => t.k === "lit") ? tokens.map((t) => t.k === "lit" ? t.ch : "").join("") : null;
|
|
33114
|
+
const matchesEmpty = segment !== "" && !guarded && tokens.every((t) => t.k === "star");
|
|
33115
|
+
out2.push({ k: "segment", tokens, literal, guarded, matchesEmpty });
|
|
33116
|
+
});
|
|
33117
|
+
return out2;
|
|
33118
|
+
}
|
|
33119
|
+
function tokenize(segment, alt) {
|
|
33120
|
+
const tokens = [];
|
|
33121
|
+
for (let i = 0; i < segment.length; i += 1) {
|
|
33122
|
+
const c2 = segment[i];
|
|
33123
|
+
if (c2 === MARK) continue;
|
|
33124
|
+
if (c2 === "*" || c2 === LED) tokens.push({ k: "star" });
|
|
33125
|
+
else if (c2 === "?") tokens.push({ k: "one" });
|
|
33126
|
+
else if (c2 === "[") {
|
|
33127
|
+
const end = segment.indexOf("]", i + 1);
|
|
33128
|
+
if (end === -1) {
|
|
33129
|
+
if (alt.includes("]")) fail("a [...] class cannot span /");
|
|
33130
|
+
tokens.push({ k: "lit", ch: c2 });
|
|
33131
|
+
continue;
|
|
33132
|
+
}
|
|
33133
|
+
tokens.push(parseClass(segment.slice(i + 1, end)));
|
|
33134
|
+
i = end;
|
|
33135
|
+
} else tokens.push({ k: "lit", ch: c2 });
|
|
33136
|
+
}
|
|
33137
|
+
return tokens;
|
|
33138
|
+
}
|
|
33139
|
+
var CLASS_SPECIAL = /[-*+?.^${}(|)[\]]/;
|
|
33140
|
+
function parseClass(body) {
|
|
33141
|
+
if (body === "") fail("empty [] class");
|
|
33142
|
+
if (body.startsWith("!")) fail("[!...] is not a negation here; use [^...]");
|
|
33143
|
+
if (body.includes("[")) fail("POSIX [:classes:] and nested [ are not supported");
|
|
33144
|
+
const negated = body.startsWith("^");
|
|
33145
|
+
const members = negated ? body.slice(1) : body;
|
|
33146
|
+
if (members === "") fail("empty [^] class");
|
|
33147
|
+
const ranges = [];
|
|
33148
|
+
for (let i = 0; i < members.length; i += 1) {
|
|
33149
|
+
const lo = members[i];
|
|
33150
|
+
const hi = members[i + 2];
|
|
33151
|
+
if (members[i + 1] === "-" && hi !== void 0) {
|
|
33152
|
+
if (hi < lo) fail(`class range out of order: ${lo}-${hi}`);
|
|
33153
|
+
ranges.push([lo, hi]);
|
|
33154
|
+
i += 2;
|
|
33155
|
+
} else ranges.push([lo, lo]);
|
|
33156
|
+
}
|
|
33157
|
+
const inSet = (c2) => ranges.some(([lo, hi]) => c2 >= lo && c2 <= hi);
|
|
33158
|
+
const literal = CLASS_SPECIAL.test(body) ? null : `[${body}]`;
|
|
33159
|
+
return { k: "class", test: negated ? (c2) => c2 !== "/" && !inSet(c2) : inSet, literal };
|
|
33160
|
+
}
|
|
33161
|
+
function normalizeRecallFile(file, projectRoot) {
|
|
33162
|
+
const forward = file.replaceAll("\\", "/");
|
|
33163
|
+
const direct = relativize(projectRoot, forward);
|
|
33164
|
+
if (!direct.startsWith("../")) return direct;
|
|
33165
|
+
const viaReal = relativize(safeRealpath(projectRoot), safeRealpath(resolve(projectRoot, forward)));
|
|
33166
|
+
return viaReal.startsWith("../") ? direct : viaReal;
|
|
33167
|
+
}
|
|
33168
|
+
function relativize(root, forward) {
|
|
33169
|
+
const rel2 = relative(root, resolve(root, forward)).replaceAll("\\", "/");
|
|
33170
|
+
return rel2 === "" ? forward.replaceAll("\\", "/") : rel2;
|
|
33171
|
+
}
|
|
33172
|
+
function safeRealpath(path) {
|
|
33173
|
+
try {
|
|
33174
|
+
return realpathSync(path);
|
|
33175
|
+
} catch {
|
|
33176
|
+
const parent = dirname(path);
|
|
33177
|
+
if (parent === path) return path;
|
|
33178
|
+
return resolve(safeRealpath(parent), basename(path));
|
|
33179
|
+
}
|
|
33180
|
+
}
|
|
33181
|
+
|
|
33182
|
+
// src/lessons/trigger-file-glob.ts
|
|
33183
|
+
var ABSOLUTE = /^(?:[A-Za-z]:)?\//;
|
|
33184
|
+
var GLOB_CHARS = /[*?[{]/;
|
|
33185
|
+
var CODES = {
|
|
33186
|
+
outside: "TRIGGER_FILE_OUTSIDE_PROJECT",
|
|
33187
|
+
root: "TRIGGER_FILE_IS_PROJECT_ROOT",
|
|
33188
|
+
folder: "TRIGGER_FILE_IS_DIRECTORY",
|
|
33189
|
+
unsafe: "UNSAFE_GLOB_PATTERN"
|
|
33190
|
+
};
|
|
33191
|
+
function problemMessage(given, problem, detail) {
|
|
33192
|
+
switch (problem) {
|
|
33193
|
+
case "outside":
|
|
33194
|
+
return `--trigger-file ${given} points outside the project root. File triggers match project-relative paths, so it would never fire \u2014 pass a glob relative to the project root (e.g. "src/**/*.ts").`;
|
|
33195
|
+
case "root":
|
|
33196
|
+
return `--trigger-file ${given} is the project root itself, and file triggers match files. Pass a glob such as "src/**/*.ts".`;
|
|
33197
|
+
case "folder":
|
|
33198
|
+
return `--trigger-file ${given} is a folder, and file triggers match files. Use ${JSON.stringify(`${detail}/**`)} to match every file in it.`;
|
|
33199
|
+
case "unsafe":
|
|
33200
|
+
return `--trigger-file ${given} is outside the safe glob subset: ${detail}. Use only *, **, ?, [...] and {a,b}.`;
|
|
33201
|
+
}
|
|
33202
|
+
}
|
|
33203
|
+
var TriggerFileGlobError = class extends Error {
|
|
33204
|
+
constructor(pattern, problem = "outside", detail = "") {
|
|
33205
|
+
super(problemMessage(JSON.stringify(pattern), problem, detail));
|
|
33206
|
+
this.pattern = pattern;
|
|
33207
|
+
this.name = "TriggerFileGlobError";
|
|
33208
|
+
this.code = CODES[problem];
|
|
33209
|
+
}
|
|
33210
|
+
code;
|
|
33211
|
+
};
|
|
33212
|
+
function sameFolder(a, b) {
|
|
33213
|
+
try {
|
|
33214
|
+
return realpathSync(a) === realpathSync(b);
|
|
33215
|
+
} catch {
|
|
33216
|
+
return false;
|
|
33217
|
+
}
|
|
33218
|
+
}
|
|
33219
|
+
function isFolder(projectRoot, path) {
|
|
33220
|
+
if (GLOB_CHARS.test(path)) return false;
|
|
33221
|
+
return statSync(join(projectRoot, path), { throwIfNoEntry: false })?.isDirectory() === true;
|
|
33222
|
+
}
|
|
33223
|
+
function projectRelativeGlob(pattern, projectRoot) {
|
|
33224
|
+
const forward = pattern.trim().replaceAll("\\", "/");
|
|
33225
|
+
let rel2 = forward;
|
|
33226
|
+
if (ABSOLUTE.test(forward)) {
|
|
33227
|
+
const root = projectRoot.replaceAll("\\", "/").replace(/\/+$/, "");
|
|
33228
|
+
rel2 = forward.startsWith(`${root}/`) || forward === root ? forward.slice(root.length + 1) : normalizeRecallFile(forward, projectRoot);
|
|
33229
|
+
if (ABSOLUTE.test(rel2)) {
|
|
33230
|
+
throw new TriggerFileGlobError(
|
|
33231
|
+
pattern,
|
|
33232
|
+
sameFolder(forward, projectRoot) ? "root" : "outside"
|
|
33233
|
+
);
|
|
33234
|
+
}
|
|
33235
|
+
}
|
|
33236
|
+
const normalized = posix.normalize(rel2 === "" ? "." : rel2);
|
|
33237
|
+
if (normalized === ".." || normalized.startsWith("../")) {
|
|
33238
|
+
throw new TriggerFileGlobError(pattern, "outside");
|
|
33239
|
+
}
|
|
33240
|
+
const path = normalized.replace(/\/+$/, "");
|
|
33241
|
+
if (path === "." || path === "") throw new TriggerFileGlobError(pattern, "root");
|
|
33242
|
+
if (path !== normalized || isFolder(projectRoot, path)) {
|
|
33243
|
+
throw new TriggerFileGlobError(pattern, "folder", path);
|
|
33244
|
+
}
|
|
33245
|
+
const unsafe = parseGlob(path);
|
|
33246
|
+
if (typeof unsafe === "string") throw new TriggerFileGlobError(pattern, "unsafe", unsafe);
|
|
33247
|
+
return path;
|
|
33248
|
+
}
|
|
33249
|
+
|
|
33250
|
+
// src/lessons/add-helpers.ts
|
|
32766
33251
|
function normalizeRule(rule) {
|
|
32767
33252
|
return rule.trim().replace(/\s+/g, " ").toLowerCase();
|
|
32768
33253
|
}
|
|
@@ -32771,14 +33256,50 @@ function union(base, extra) {
|
|
|
32771
33256
|
for (const item of extra) if (!out2.includes(item)) out2.push(item);
|
|
32772
33257
|
return out2;
|
|
32773
33258
|
}
|
|
32774
|
-
function
|
|
33259
|
+
function upsertLesson(before, input, triggerIds) {
|
|
33260
|
+
return {
|
|
33261
|
+
...before,
|
|
33262
|
+
topics: union(before.topics, [input.topic]),
|
|
33263
|
+
triggers: union(before.triggers, triggerIds),
|
|
33264
|
+
evidence: union(before.evidence, input.evidence ?? []),
|
|
33265
|
+
...before.rationale === void 0 && input.rationale !== void 0 ? { rationale: input.rationale } : {},
|
|
33266
|
+
...input.scope === "always" ? { scope: "always" } : {}
|
|
33267
|
+
};
|
|
33268
|
+
}
|
|
33269
|
+
function describeUpsert(before, after) {
|
|
33270
|
+
const added = (base, next) => next.filter((item) => !base.includes(item));
|
|
33271
|
+
const topics = added(before.topics, after.topics);
|
|
33272
|
+
const triggers = added(before.triggers, after.triggers);
|
|
33273
|
+
const evidence = added(before.evidence, after.evidence);
|
|
33274
|
+
const changes = [];
|
|
33275
|
+
if (after.scope === "always" && before.scope !== "always") changes.push("scope set to always");
|
|
33276
|
+
if (topics.length > 0) changes.push(`topic added: ${topics.join(", ")}`);
|
|
33277
|
+
if (triggers.length > 0) {
|
|
33278
|
+
changes.push(`trigger${triggers.length === 1 ? "" : "s"} attached: ${triggers.join(", ")}`);
|
|
33279
|
+
}
|
|
33280
|
+
if (evidence.length > 0) changes.push(`evidence added: ${evidence.join(", ")}`);
|
|
33281
|
+
if (before.rationale === void 0 && after.rationale !== void 0) {
|
|
33282
|
+
changes.push("rationale added");
|
|
33283
|
+
}
|
|
33284
|
+
return changes;
|
|
33285
|
+
}
|
|
33286
|
+
function findExistingLessonByRule(graph, ruleKey2) {
|
|
33287
|
+
for (const [id, lesson] of Object.entries(graph.lessons)) {
|
|
33288
|
+
if (lesson.status !== "active") continue;
|
|
33289
|
+
if (normalizeRule(lesson.rule) === ruleKey2) return id;
|
|
33290
|
+
}
|
|
33291
|
+
return null;
|
|
33292
|
+
}
|
|
33293
|
+
function mergeTriggers(graph, spec, projectRoot) {
|
|
32775
33294
|
const requested = [
|
|
32776
|
-
//
|
|
32777
|
-
//
|
|
32778
|
-
//
|
|
32779
|
-
// a backslash pattern dedupe against the forward-slash node it equals.
|
|
33295
|
+
// Recall matches forward-slash, project-relative paths (normalizeRecallFile),
|
|
33296
|
+
// so a backslash or absolute pattern stored raw would silently never fire.
|
|
33297
|
+
// Normalizing here also dedupes it against the node it equals.
|
|
32780
33298
|
...(spec.files ?? []).map(
|
|
32781
|
-
(p) => ({
|
|
33299
|
+
(p) => ({
|
|
33300
|
+
kind: "file_glob",
|
|
33301
|
+
pattern: projectRoot === void 0 ? p.replaceAll("\\", "/") : projectRelativeGlob(p, projectRoot)
|
|
33302
|
+
})
|
|
32782
33303
|
),
|
|
32783
33304
|
...(spec.commands ?? []).map((p) => ({ kind: "command_pattern", pattern: p })),
|
|
32784
33305
|
...(spec.keywords ?? []).map((p) => ({ kind: "keyword", pattern: p }))
|
|
@@ -32835,6 +33356,132 @@ function todayIso() {
|
|
|
32835
33356
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
32836
33357
|
}
|
|
32837
33358
|
|
|
33359
|
+
// src/lessons/glob-dp.ts
|
|
33360
|
+
var isDotSegment = (s) => s === "." || s === "..";
|
|
33361
|
+
function matchSegments(alt, segments, work) {
|
|
33362
|
+
const n = segments.length;
|
|
33363
|
+
let next = new Uint8Array(n + 1);
|
|
33364
|
+
next[n] = 1;
|
|
33365
|
+
for (let i = alt.length - 1; i >= 0; i -= 1) {
|
|
33366
|
+
const seg = alt[i];
|
|
33367
|
+
const cur = new Uint8Array(n + 1);
|
|
33368
|
+
for (let j = n; j >= 0; j -= 1) {
|
|
33369
|
+
if (--work.remaining <= 0) return false;
|
|
33370
|
+
if (seg.k === "globstar") {
|
|
33371
|
+
const eat = j < n && !isDotSegment(segments[j]) && (cur[j + 1] === 1 || next[j + 1] === 1);
|
|
33372
|
+
cur[j] = !seg.min1 && next[j] === 1 || eat ? 1 : 0;
|
|
33373
|
+
} else if (j < n && next[j + 1] === 1) {
|
|
33374
|
+
const text = segments[j];
|
|
33375
|
+
const ok = seg.literal !== null ? text === seg.literal : matchSegment(seg, text, j < n - 1, work);
|
|
33376
|
+
cur[j] = ok ? 1 : 0;
|
|
33377
|
+
}
|
|
33378
|
+
}
|
|
33379
|
+
if (seg.k === "globstar" && i >= 1 && tailMatchesNothing(alt, i + 1)) cur[n] = 1;
|
|
33380
|
+
next = cur;
|
|
33381
|
+
}
|
|
33382
|
+
return next[0] === 1;
|
|
33383
|
+
}
|
|
33384
|
+
function tailMatchesNothing(alt, from) {
|
|
33385
|
+
const empty = alt[from];
|
|
33386
|
+
const rest = alt[from + 1];
|
|
33387
|
+
if (empty?.k !== "segment" || !empty.matchesEmpty) return false;
|
|
33388
|
+
return rest === void 0 || rest.k === "globstar" && !rest.min1 && from + 2 === alt.length;
|
|
33389
|
+
}
|
|
33390
|
+
function matchSegment(seg, text, followedBySlash, work) {
|
|
33391
|
+
if (seg.guarded && (isDotSegment(text) || text === "" && !followedBySlash)) return false;
|
|
33392
|
+
const tokens = seg.tokens;
|
|
33393
|
+
const len = text.length;
|
|
33394
|
+
let next = new Uint8Array(len + 1);
|
|
33395
|
+
next[len] = 1;
|
|
33396
|
+
for (let k = tokens.length - 1; k >= 0; k -= 1) {
|
|
33397
|
+
const tok = tokens[k];
|
|
33398
|
+
const cur = new Uint8Array(len + 1);
|
|
33399
|
+
work.remaining -= len + 1;
|
|
33400
|
+
if (work.remaining <= 0) return false;
|
|
33401
|
+
for (let c2 = len; c2 >= 0; c2 -= 1) {
|
|
33402
|
+
cur[c2] = tokenMatches(tok, text, c2, next, cur) ? 1 : 0;
|
|
33403
|
+
}
|
|
33404
|
+
next = cur;
|
|
33405
|
+
}
|
|
33406
|
+
return next[0] === 1;
|
|
33407
|
+
}
|
|
33408
|
+
function tokenMatches(tok, text, c2, next, cur) {
|
|
33409
|
+
const ch = text[c2];
|
|
33410
|
+
switch (tok.k) {
|
|
33411
|
+
case "star":
|
|
33412
|
+
return next[c2] === 1 || ch !== void 0 && cur[c2 + 1] === 1;
|
|
33413
|
+
case "one":
|
|
33414
|
+
return ch !== void 0 && next[c2 + 1] === 1;
|
|
33415
|
+
case "lit":
|
|
33416
|
+
return ch === tok.ch && next[c2 + 1] === 1;
|
|
33417
|
+
case "class":
|
|
33418
|
+
return ch !== void 0 && tok.test(ch) && next[c2 + 1] === 1 || tok.literal !== null && text.startsWith(tok.literal, c2) && next[c2 + tok.literal.length] === 1;
|
|
33419
|
+
}
|
|
33420
|
+
}
|
|
33421
|
+
|
|
33422
|
+
// src/lessons/glob-safety.ts
|
|
33423
|
+
var MAX_GLOB_PATH_LENGTH = 4096;
|
|
33424
|
+
var MATCH_WORK_LIMIT = 2e5;
|
|
33425
|
+
var CACHE_LIMIT = 2e3;
|
|
33426
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
33427
|
+
function unsafeGlobFinding(triggerId, trigger) {
|
|
33428
|
+
if (trigger.kind !== "file_glob" || trigger.pattern.includes("\\")) return null;
|
|
33429
|
+
const reason = parseGlob(trigger.pattern);
|
|
33430
|
+
if (typeof reason !== "string") return null;
|
|
33431
|
+
return {
|
|
33432
|
+
level: "error",
|
|
33433
|
+
code: "UNSAFE_GLOB_PATTERN",
|
|
33434
|
+
message: `Trigger "${triggerId}" has a file_glob outside the safe glob subset (${trigger.pattern.slice(0, 120)}): ${reason}. Recall treats it as a non-match. Use only *, **, ?, [...] and {a,b}.`,
|
|
33435
|
+
triggerId
|
|
33436
|
+
};
|
|
33437
|
+
}
|
|
33438
|
+
function getGlobMatcher(pattern) {
|
|
33439
|
+
const hit = cache2.get(pattern);
|
|
33440
|
+
if (hit !== void 0) return hit;
|
|
33441
|
+
if (cache2.size >= CACHE_LIMIT) cache2.clear();
|
|
33442
|
+
const composed = pattern.normalize("NFC");
|
|
33443
|
+
const parsed = parseGlob(composed);
|
|
33444
|
+
const matcher = typeof parsed === "string" ? null : build(composed, parsed);
|
|
33445
|
+
cache2.set(pattern, matcher);
|
|
33446
|
+
return matcher;
|
|
33447
|
+
}
|
|
33448
|
+
function build(pattern, parsed) {
|
|
33449
|
+
const alts = parsed.alternatives.map(prepare);
|
|
33450
|
+
return {
|
|
33451
|
+
test(rawPath, budget) {
|
|
33452
|
+
const path = rawPath.normalize("NFC");
|
|
33453
|
+
if (path === pattern) return true;
|
|
33454
|
+
if (path === "" || path.length > MAX_GLOB_PATH_LENGTH) return false;
|
|
33455
|
+
const live = alts.filter((a) => mayMatch(a, path));
|
|
33456
|
+
if (live.length === 0) return parsed.negated;
|
|
33457
|
+
const limit = Math.min(MATCH_WORK_LIMIT, budget?.remaining ?? MATCH_WORK_LIMIT);
|
|
33458
|
+
const work = { remaining: limit };
|
|
33459
|
+
const segments = path.split("/");
|
|
33460
|
+
const hit = live.some((a) => matchSegments(a.alt, segments, work));
|
|
33461
|
+
if (budget !== void 0) budget.remaining -= limit - work.remaining;
|
|
33462
|
+
return work.remaining > 0 && hit !== parsed.negated;
|
|
33463
|
+
}
|
|
33464
|
+
};
|
|
33465
|
+
}
|
|
33466
|
+
function mayMatch({ head, tail }, path) {
|
|
33467
|
+
if (!path.endsWith(tail)) return false;
|
|
33468
|
+
if (head === null) return true;
|
|
33469
|
+
return path.startsWith(head) && (path.length === head.length || path[head.length] === "/");
|
|
33470
|
+
}
|
|
33471
|
+
function prepare(alt) {
|
|
33472
|
+
const first = alt[0];
|
|
33473
|
+
const last = alt[alt.length - 1];
|
|
33474
|
+
let tail = "";
|
|
33475
|
+
if (last?.k === "segment" && !last.matchesEmpty) {
|
|
33476
|
+
for (let k = last.tokens.length - 1; k >= 0; k -= 1) {
|
|
33477
|
+
const tok = last.tokens[k];
|
|
33478
|
+
if (tok.k !== "lit") break;
|
|
33479
|
+
tail = tok.ch + tail;
|
|
33480
|
+
}
|
|
33481
|
+
}
|
|
33482
|
+
return { alt, head: first?.k === "segment" ? first.literal : null, tail };
|
|
33483
|
+
}
|
|
33484
|
+
|
|
32838
33485
|
// src/lessons/regex-linear/nfa-compile.ts
|
|
32839
33486
|
var MAX_NFA_STATES = 2e3;
|
|
32840
33487
|
var Builder = class {
|
|
@@ -33026,6 +33673,9 @@ function escapeClass(c2) {
|
|
|
33026
33673
|
var HEX2 = /^[0-9a-fA-F]{2}$/;
|
|
33027
33674
|
var HEX4 = /^[0-9a-fA-F]{4}$/;
|
|
33028
33675
|
function readUnicodeEscape(src, i, c2) {
|
|
33676
|
+
if (c2 === "u" && src[i] === "{") {
|
|
33677
|
+
throw new UnsupportedRegexError("\\u{\u2026} code point escapes are not supported; use \\uHHHH");
|
|
33678
|
+
}
|
|
33029
33679
|
if (c2 === "x") {
|
|
33030
33680
|
const hex2 = src.slice(i, i + 2);
|
|
33031
33681
|
return HEX2.test(hex2) ? { ch: String.fromCharCode(parseInt(hex2, 16)), len: 2 } : { ch: "x", len: 0 };
|
|
@@ -33115,7 +33765,7 @@ function parseRegex(src) {
|
|
|
33115
33765
|
function parseAtom() {
|
|
33116
33766
|
const c2 = peek();
|
|
33117
33767
|
if (c2 === "(") return parseGroup2();
|
|
33118
|
-
if (c2 === "[") return
|
|
33768
|
+
if (c2 === "[") return parseClass2();
|
|
33119
33769
|
if (c2 === "\\") return parseEscape();
|
|
33120
33770
|
if (c2 === ".") {
|
|
33121
33771
|
i += 1;
|
|
@@ -33174,7 +33824,7 @@ function parseRegex(src) {
|
|
|
33174
33824
|
}
|
|
33175
33825
|
return { k: "char", ch: escapeLiteral(c2) };
|
|
33176
33826
|
}
|
|
33177
|
-
function
|
|
33827
|
+
function parseClass2() {
|
|
33178
33828
|
i += 1;
|
|
33179
33829
|
const negate = peek() === "^";
|
|
33180
33830
|
if (negate) i += 1;
|
|
@@ -33227,17 +33877,17 @@ function parseRegex(src) {
|
|
|
33227
33877
|
}
|
|
33228
33878
|
|
|
33229
33879
|
// src/lessons/regex-linear/index.ts
|
|
33230
|
-
var
|
|
33880
|
+
var cache3 = /* @__PURE__ */ new Map();
|
|
33231
33881
|
function compileLinearMatcher(pattern) {
|
|
33232
|
-
const hit =
|
|
33233
|
-
if (hit !== void 0 ||
|
|
33882
|
+
const hit = cache3.get(pattern);
|
|
33883
|
+
if (hit !== void 0 || cache3.has(pattern)) return hit ?? null;
|
|
33234
33884
|
let matcher;
|
|
33235
33885
|
try {
|
|
33236
33886
|
matcher = buildMatcher(parseRegex(pattern));
|
|
33237
33887
|
} catch {
|
|
33238
33888
|
matcher = null;
|
|
33239
33889
|
}
|
|
33240
|
-
|
|
33890
|
+
cache3.set(pattern, matcher);
|
|
33241
33891
|
return matcher;
|
|
33242
33892
|
}
|
|
33243
33893
|
|
|
@@ -33278,6 +33928,8 @@ function collectDuplicateRules(graph, findings) {
|
|
|
33278
33928
|
}
|
|
33279
33929
|
function collectInvalidTriggerPatterns(graph, findings) {
|
|
33280
33930
|
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
33931
|
+
const globFinding = unsafeGlobFinding(triggerId, trigger);
|
|
33932
|
+
if (globFinding !== null) findings.push(globFinding);
|
|
33281
33933
|
if (trigger.kind !== "command_pattern") continue;
|
|
33282
33934
|
try {
|
|
33283
33935
|
new RegExp(trigger.pattern);
|
|
@@ -33285,7 +33937,7 @@ function collectInvalidTriggerPatterns(graph, findings) {
|
|
|
33285
33937
|
findings.push({
|
|
33286
33938
|
level: "error",
|
|
33287
33939
|
code: "INVALID_TRIGGER_PATTERN",
|
|
33288
|
-
message: `Trigger "${triggerId}" has an invalid command_pattern regex (${trigger.pattern}): ${
|
|
33940
|
+
message: `Trigger "${triggerId}" has an invalid command_pattern regex (${trigger.pattern}): ${regexSyntaxReason(err)}.`,
|
|
33289
33941
|
triggerId
|
|
33290
33942
|
});
|
|
33291
33943
|
continue;
|
|
@@ -33294,12 +33946,16 @@ function collectInvalidTriggerPatterns(graph, findings) {
|
|
|
33294
33946
|
findings.push({
|
|
33295
33947
|
level: "error",
|
|
33296
33948
|
code: "UNSAFE_TRIGGER_PATTERN",
|
|
33297
|
-
message: `Trigger "${triggerId}" has a command_pattern regex
|
|
33949
|
+
message: `Trigger "${triggerId}" has a command_pattern regex the linear matcher cannot run (${trigger.pattern}). It does not support backreferences (\\1, \\k<name>), lookarounds ((?=x), (?!x), (?<=x), (?<!x)), or patterns too large to run, such as (a{1000}){10}. Nested quantifiers such as (a+)+ are fine. Rewrite the pattern without the unsupported part.`,
|
|
33298
33950
|
triggerId
|
|
33299
33951
|
});
|
|
33300
33952
|
}
|
|
33301
33953
|
}
|
|
33302
33954
|
}
|
|
33955
|
+
function regexSyntaxReason(err) {
|
|
33956
|
+
const text = err instanceof Error ? err.message : String(err);
|
|
33957
|
+
return text.replace(/^Invalid regular expression: \/[\s\S]*\/[a-z]*: /, "");
|
|
33958
|
+
}
|
|
33303
33959
|
function collectBackslashGlobPatterns(graph, findings) {
|
|
33304
33960
|
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
33305
33961
|
if (trigger.kind !== "file_glob") continue;
|
|
@@ -33380,7 +34036,11 @@ function collectTriggerSetCollisions(graph, findings) {
|
|
|
33380
34036
|
|
|
33381
34037
|
// src/lessons/glob-breadth.ts
|
|
33382
34038
|
var WILDCARD = /[*?[\]]/;
|
|
34039
|
+
function isNegatedGlob(pattern) {
|
|
34040
|
+
return pattern.startsWith("!");
|
|
34041
|
+
}
|
|
33383
34042
|
function globNarrowness(pattern) {
|
|
34043
|
+
if (isNegatedGlob(pattern)) return 0;
|
|
33384
34044
|
const segments = pattern.replaceAll("\\", "/").split("/").filter((segment) => segment !== "" && segment !== ".");
|
|
33385
34045
|
if (segments.length === 0) return 0;
|
|
33386
34046
|
let literal = 0;
|
|
@@ -33431,6 +34091,15 @@ function isBroadCommandPattern(pattern) {
|
|
|
33431
34091
|
}
|
|
33432
34092
|
return hits > COMMAND_PROBE_CORPUS.length * BROAD_HIT_RATIO;
|
|
33433
34093
|
}
|
|
34094
|
+
function missingGlobState(pattern, history) {
|
|
34095
|
+
const matcher = getGlobMatcher(pattern);
|
|
34096
|
+
if (history === null || matcher === null) return "pending";
|
|
34097
|
+
const matchesAny = (paths2) => [...paths2].some((p) => matcher.test(p));
|
|
34098
|
+
if (matchesAny(history.tracked)) return "live";
|
|
34099
|
+
if (matchesAny(history.renamedAway)) return "dead";
|
|
34100
|
+
if (!picomatch.scan(pattern).isGlob && matchesAny(history.deleted)) return "dead";
|
|
34101
|
+
return "pending";
|
|
34102
|
+
}
|
|
33434
34103
|
|
|
33435
34104
|
// src/lessons/validate-liveness.ts
|
|
33436
34105
|
function activeTriggerIds(graph) {
|
|
@@ -33441,32 +34110,42 @@ function activeTriggerIds(graph) {
|
|
|
33441
34110
|
}
|
|
33442
34111
|
return ids;
|
|
33443
34112
|
}
|
|
33444
|
-
function
|
|
33445
|
-
const active = activeTriggerIds(graph);
|
|
34113
|
+
function fileGlobLiveness(graph, knownPaths, triggerIds) {
|
|
34114
|
+
const active = triggerIds === void 0 ? activeTriggerIds(graph) : new Set(triggerIds);
|
|
33446
34115
|
const paths2 = [...knownPaths];
|
|
33447
|
-
const
|
|
34116
|
+
const missing = [];
|
|
33448
34117
|
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
33449
|
-
if (trigger.kind !== "file_glob") continue;
|
|
33450
|
-
|
|
33451
|
-
|
|
33452
|
-
if (!paths2.some((p) =>
|
|
34118
|
+
if (trigger.kind !== "file_glob" || !active.has(triggerId)) continue;
|
|
34119
|
+
const matcher = getGlobMatcher(trigger.pattern);
|
|
34120
|
+
if (matcher === null) continue;
|
|
34121
|
+
if (!paths2.some((p) => matcher.test(p))) missing.push([triggerId, trigger.pattern]);
|
|
34122
|
+
}
|
|
34123
|
+
const dead = /* @__PURE__ */ new Set();
|
|
34124
|
+
const pending = /* @__PURE__ */ new Set();
|
|
34125
|
+
if (missing.length === 0) return { dead, pending };
|
|
34126
|
+
const history = gitHistoryOf(knownPaths);
|
|
34127
|
+
for (const [triggerId, pattern] of missing) {
|
|
34128
|
+
const state = missingGlobState(pattern, history);
|
|
34129
|
+
if (state === "dead") dead.add(triggerId);
|
|
34130
|
+
else if (state === "pending") pending.add(triggerId);
|
|
33453
34131
|
}
|
|
33454
|
-
return dead;
|
|
34132
|
+
return { dead, pending };
|
|
33455
34133
|
}
|
|
33456
34134
|
function collectDeadFileGlobs(graph, findings, knownPaths) {
|
|
33457
|
-
for (const triggerId of
|
|
34135
|
+
for (const triggerId of fileGlobLiveness(graph, knownPaths).dead) {
|
|
33458
34136
|
findings.push({
|
|
33459
34137
|
level: "warning",
|
|
33460
34138
|
code: "DEAD_FILE_GLOB",
|
|
33461
|
-
message: `file_glob trigger "${triggerId}" (${graph.triggers[triggerId]?.pattern ?? ""}) matches no file
|
|
34139
|
+
message: `file_glob trigger "${triggerId}" (${graph.triggers[triggerId]?.pattern ?? ""}) matches no file, and git history shows its path was renamed or deleted \u2014 the lesson is unreachable via this trigger. Re-point it at the current path, or detach it with \`lessons untrigger\`, or run \`lessons prune --apply\`.`,
|
|
33462
34140
|
triggerId
|
|
33463
34141
|
});
|
|
33464
34142
|
}
|
|
33465
34143
|
}
|
|
33466
34144
|
function fileGlobMatchCount(pattern, knownPaths) {
|
|
33467
|
-
const
|
|
34145
|
+
const matcher = getGlobMatcher(pattern);
|
|
34146
|
+
if (matcher === null) return 0;
|
|
33468
34147
|
let n = 0;
|
|
33469
|
-
for (const p of knownPaths) if (
|
|
34148
|
+
for (const p of knownPaths) if (matcher.test(p)) n += 1;
|
|
33470
34149
|
return n;
|
|
33471
34150
|
}
|
|
33472
34151
|
var RUNNER_ANCHOR = /^\^(pnpm|npm|npx|yarn|bun)\b/;
|
|
@@ -33538,7 +34217,7 @@ var STOP = /* @__PURE__ */ new Set([
|
|
|
33538
34217
|
"its",
|
|
33539
34218
|
"must"
|
|
33540
34219
|
]);
|
|
33541
|
-
function
|
|
34220
|
+
function tokenize2(text) {
|
|
33542
34221
|
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2 && !STOP.has(t));
|
|
33543
34222
|
}
|
|
33544
34223
|
function queryTerms(query) {
|
|
@@ -33546,7 +34225,7 @@ function queryTerms(query) {
|
|
|
33546
34225
|
if (query.keyword !== void 0) parts.push(query.keyword);
|
|
33547
34226
|
if (query.file !== void 0) parts.push(query.file);
|
|
33548
34227
|
if (query.command !== void 0) parts.push(query.command);
|
|
33549
|
-
return
|
|
34228
|
+
return tokenize2(parts.join(" "));
|
|
33550
34229
|
}
|
|
33551
34230
|
function buildCorpus(graph) {
|
|
33552
34231
|
const docs = [];
|
|
@@ -33555,7 +34234,7 @@ function buildCorpus(graph) {
|
|
|
33555
34234
|
let n = 0;
|
|
33556
34235
|
for (const lesson of Object.values(graph.lessons)) {
|
|
33557
34236
|
if (lesson.status !== "active") continue;
|
|
33558
|
-
const toks =
|
|
34237
|
+
const toks = tokenize2(lesson.rule);
|
|
33559
34238
|
n += 1;
|
|
33560
34239
|
total += toks.length;
|
|
33561
34240
|
docs.push(toks.length);
|
|
@@ -33567,7 +34246,7 @@ function buildCorpus(graph) {
|
|
|
33567
34246
|
return { idf, avgdl: total / N || 1 };
|
|
33568
34247
|
}
|
|
33569
34248
|
function bm25(terms, ruleText, corpus) {
|
|
33570
|
-
const toks =
|
|
34249
|
+
const toks = tokenize2(ruleText);
|
|
33571
34250
|
const dl = toks.length || 1;
|
|
33572
34251
|
const tf = /* @__PURE__ */ new Map();
|
|
33573
34252
|
for (const t of toks) tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
@@ -33584,7 +34263,7 @@ function bm25(terms, ruleText, corpus) {
|
|
|
33584
34263
|
// src/lessons/keyword-signal.ts
|
|
33585
34264
|
var MAX_RECOMMENDED_KEYWORD_TOKENS = 5;
|
|
33586
34265
|
function isLowSignalKeyword(pattern) {
|
|
33587
|
-
return
|
|
34266
|
+
return tokenize2(pattern).length > MAX_RECOMMENDED_KEYWORD_TOKENS;
|
|
33588
34267
|
}
|
|
33589
34268
|
function splitRawTokens(pattern) {
|
|
33590
34269
|
return pattern.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
|
|
@@ -33592,7 +34271,7 @@ function splitRawTokens(pattern) {
|
|
|
33592
34271
|
function keywordNeedleLosesTokens(pattern) {
|
|
33593
34272
|
const raw = splitRawTokens(pattern);
|
|
33594
34273
|
if (raw.length < 2) return false;
|
|
33595
|
-
return
|
|
34274
|
+
return tokenize2(pattern).length !== raw.length;
|
|
33596
34275
|
}
|
|
33597
34276
|
|
|
33598
34277
|
// src/lessons/validate-keywords.ts
|
|
@@ -33615,7 +34294,7 @@ function collectStopwordKeywords(graph, findings) {
|
|
|
33615
34294
|
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
33616
34295
|
if (trigger.kind !== "keyword") continue;
|
|
33617
34296
|
if (!active.has(triggerId)) continue;
|
|
33618
|
-
if (
|
|
34297
|
+
if (tokenize2(trigger.pattern).length !== 0 && !keywordNeedleLosesTokens(trigger.pattern)) {
|
|
33619
34298
|
continue;
|
|
33620
34299
|
}
|
|
33621
34300
|
findings.push({
|
|
@@ -33663,7 +34342,6 @@ function validateLessonsGraph(graph, options = {}) {
|
|
|
33663
34342
|
|
|
33664
34343
|
// src/core/lint/shared/lessons.ts
|
|
33665
34344
|
var LESSONS_TARGET = "lessons";
|
|
33666
|
-
var GRAPH_REL = ".agentsmesh/lessons/lessons.json";
|
|
33667
34345
|
var ROOT_RULE_REL = ".agentsmesh/rules/_root.md";
|
|
33668
34346
|
var LESSONS_HEADING = /^## Lessons \(/m;
|
|
33669
34347
|
function lintLessonsSubsystem(projectRoot, scope) {
|
|
@@ -33678,7 +34356,7 @@ function lintLessonsSubsystem(projectRoot, scope) {
|
|
|
33678
34356
|
return [
|
|
33679
34357
|
diag(
|
|
33680
34358
|
"error",
|
|
33681
|
-
|
|
34359
|
+
LESSONS_GRAPH_PATH,
|
|
33682
34360
|
`lessons.json failed to load: ${err instanceof Error ? err.message : String(err)}`
|
|
33683
34361
|
)
|
|
33684
34362
|
];
|
|
@@ -33686,7 +34364,7 @@ function lintLessonsSubsystem(projectRoot, scope) {
|
|
|
33686
34364
|
const knownPaths = listProjectFiles(projectRoot) ?? void 0;
|
|
33687
34365
|
const report = validateLessonsGraph(graph, { knownPaths });
|
|
33688
34366
|
for (const finding of report.findings) {
|
|
33689
|
-
out2.push(diag(finding.level,
|
|
34367
|
+
out2.push(diag(finding.level, LESSONS_GRAPH_PATH, `[${finding.code}] ${finding.message}`));
|
|
33690
34368
|
}
|
|
33691
34369
|
const rootRuleAbs = join(projectRoot, ROOT_RULE_REL);
|
|
33692
34370
|
const rootRuleBody = existsSync(rootRuleAbs) ? readFileSync(rootRuleAbs, "utf8") : "";
|
|
@@ -33708,8 +34386,8 @@ function diag(level, file, message) {
|
|
|
33708
34386
|
// src/core/lint/linter.ts
|
|
33709
34387
|
var EXCLUDE_DIRS = ["node_modules", ".git", "dist", "coverage", ".agentsmesh"];
|
|
33710
34388
|
function isExcludedProjectPath(rel2) {
|
|
33711
|
-
const
|
|
33712
|
-
return EXCLUDE_DIRS.some((d) =>
|
|
34389
|
+
const posix11 = rel2.replaceAll("\\", "/");
|
|
34390
|
+
return EXCLUDE_DIRS.some((d) => posix11.includes(`/${d}/`) || posix11.startsWith(`${d}/`));
|
|
33713
34391
|
}
|
|
33714
34392
|
async function getProjectFiles(projectRoot) {
|
|
33715
34393
|
const all = await readDirRecursive(projectRoot);
|
|
@@ -34102,14 +34780,22 @@ async function findUntrackedManagedDirFiles(args) {
|
|
|
34102
34780
|
return [...found].sort();
|
|
34103
34781
|
}
|
|
34104
34782
|
|
|
34783
|
+
// src/lessons/conflict-markers.ts
|
|
34784
|
+
function hasConflictMarkers(text) {
|
|
34785
|
+
return /^(?:<{7}|>{7})(?: |\r?$)/m.test(text);
|
|
34786
|
+
}
|
|
34787
|
+
|
|
34105
34788
|
// src/core/check/lock-sync.ts
|
|
34789
|
+
init_fs();
|
|
34106
34790
|
async function checkLockSync(opts) {
|
|
34107
34791
|
const { config, configDir, canonicalDir, rootBase, scope = "project" } = opts;
|
|
34108
34792
|
const lock = await readLock(canonicalDir);
|
|
34109
34793
|
if (lock === null) {
|
|
34794
|
+
const text = await readFileSafe(join(canonicalDir, ".lock"));
|
|
34110
34795
|
return {
|
|
34111
34796
|
inSync: false,
|
|
34112
34797
|
hasLock: false,
|
|
34798
|
+
lockConflict: text !== null && hasConflictMarkers(text),
|
|
34113
34799
|
canonicalDrift: false,
|
|
34114
34800
|
outputDrift: false,
|
|
34115
34801
|
modified: [],
|
|
@@ -34179,6 +34865,7 @@ async function checkLockSync(opts) {
|
|
|
34179
34865
|
return {
|
|
34180
34866
|
inSync,
|
|
34181
34867
|
hasLock: true,
|
|
34868
|
+
lockConflict: false,
|
|
34182
34869
|
canonicalDrift,
|
|
34183
34870
|
outputDrift,
|
|
34184
34871
|
modified,
|
|
@@ -34454,6 +35141,27 @@ var UnknownTopicError = class extends Error {
|
|
|
34454
35141
|
}
|
|
34455
35142
|
code = "UNKNOWN_TOPIC";
|
|
34456
35143
|
};
|
|
35144
|
+
var InvalidTopicIdError = class extends Error {
|
|
35145
|
+
constructor(topic) {
|
|
35146
|
+
const slug = topic.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
35147
|
+
super(
|
|
35148
|
+
`Topic id ${JSON.stringify(topic)} must be kebab-case (lowercase letters, digits and -)` + (slug.length > 0 ? `, e.g. ${JSON.stringify(slug)}.` : ".")
|
|
35149
|
+
);
|
|
35150
|
+
this.topic = topic;
|
|
35151
|
+
this.name = "InvalidTopicIdError";
|
|
35152
|
+
}
|
|
35153
|
+
code = "INVALID_TOPIC_ID";
|
|
35154
|
+
};
|
|
35155
|
+
var TopicSummaryRequiredError = class extends Error {
|
|
35156
|
+
constructor(topic) {
|
|
35157
|
+
super(
|
|
35158
|
+
`New topic ${JSON.stringify(topic)} needs a one-line summary (--topic-summary on the CLI, topic_summary over MCP).`
|
|
35159
|
+
);
|
|
35160
|
+
this.topic = topic;
|
|
35161
|
+
this.name = "TopicSummaryRequiredError";
|
|
35162
|
+
}
|
|
35163
|
+
code = "TOPIC_SUMMARY_REQUIRED";
|
|
35164
|
+
};
|
|
34457
35165
|
var RuleTooLongError = class extends Error {
|
|
34458
35166
|
constructor(length, max) {
|
|
34459
35167
|
super(
|
|
@@ -34484,6 +35192,9 @@ var UnrecallableLessonError = class extends Error {
|
|
|
34484
35192
|
}
|
|
34485
35193
|
code = "UNRECALLABLE_LESSON";
|
|
34486
35194
|
};
|
|
35195
|
+
function codePointLength(text) {
|
|
35196
|
+
return [...text].length;
|
|
35197
|
+
}
|
|
34487
35198
|
|
|
34488
35199
|
// src/lessons/trigger-effectiveness.ts
|
|
34489
35200
|
function ineffectiveTriggers(graph, triggerIds) {
|
|
@@ -34498,7 +35209,7 @@ function ineffectiveTriggers(graph, triggerIds) {
|
|
|
34498
35209
|
}
|
|
34499
35210
|
function ineffectiveReason(kind, pattern) {
|
|
34500
35211
|
if (kind === "keyword") {
|
|
34501
|
-
if (
|
|
35212
|
+
if (tokenize2(pattern).length === 0) {
|
|
34502
35213
|
return "keyword has no matchable token after stopword filtering \u2014 it cannot fire on the mandatory --file/--cmd recall path";
|
|
34503
35214
|
}
|
|
34504
35215
|
if (keywordNeedleLosesTokens(pattern)) {
|
|
@@ -34531,9 +35242,19 @@ function blockingDeadTriggers(graph, triggerIds) {
|
|
|
34531
35242
|
function assertRuleShape(rule) {
|
|
34532
35243
|
const trimmed = rule.trim();
|
|
34533
35244
|
if (trimmed.length === 0) throw new EmptyRuleError();
|
|
34534
|
-
|
|
35245
|
+
const length = codePointLength(trimmed);
|
|
35246
|
+
if (length > MAX_RULE_LENGTH) throw new RuleTooLongError(length, MAX_RULE_LENGTH);
|
|
34535
35247
|
return trimmed;
|
|
34536
35248
|
}
|
|
35249
|
+
function ensureTopic(graph, topic, options) {
|
|
35250
|
+
if (!/^[a-z0-9-]+$/.test(topic)) throw new InvalidTopicIdError(topic);
|
|
35251
|
+
if (graph.topics[topic] !== void 0) return false;
|
|
35252
|
+
if (options.allowNewTopic !== true) throw new UnknownTopicError(topic);
|
|
35253
|
+
const summary = options.topicSummary?.trim() ?? "";
|
|
35254
|
+
if (summary.length === 0) throw new TopicSummaryRequiredError(topic);
|
|
35255
|
+
graph.topics[topic] = { summary };
|
|
35256
|
+
return true;
|
|
35257
|
+
}
|
|
34537
35258
|
function skipsTriggerGates(input, options) {
|
|
34538
35259
|
return options.allowNoTrigger === true || input.scope === "always";
|
|
34539
35260
|
}
|
|
@@ -34549,10 +35270,30 @@ function assertTriggerInputs(input, options, existingTriggerCount) {
|
|
|
34549
35270
|
if (broad !== void 0) throw new BroadCommandPatternError(broad);
|
|
34550
35271
|
}
|
|
34551
35272
|
}
|
|
34552
|
-
function
|
|
35273
|
+
function dropDeadCommandTriggers(graph, merged, options) {
|
|
35274
|
+
if (options.allowNoTrigger === true) return { ...merged, dropped: [] };
|
|
35275
|
+
const dropped = ineffectiveTriggers(graph, merged.triggerIds).filter(
|
|
35276
|
+
(t) => t.kind === "command_pattern"
|
|
35277
|
+
);
|
|
35278
|
+
const dead = new Set(dropped.map((t) => t.id));
|
|
35279
|
+
for (const id of merged.newTriggerIds) if (dead.has(id)) delete graph.triggers[id];
|
|
35280
|
+
return {
|
|
35281
|
+
triggerIds: merged.triggerIds.filter((id) => !dead.has(id)),
|
|
35282
|
+
newTriggerIds: merged.newTriggerIds.filter((id) => !dead.has(id)),
|
|
35283
|
+
dropped
|
|
35284
|
+
};
|
|
35285
|
+
}
|
|
35286
|
+
function deadCommandWarning(trigger) {
|
|
35287
|
+
return {
|
|
35288
|
+
code: "DEAD_COMMAND_PATTERN",
|
|
35289
|
+
message: `Dropped command trigger ${JSON.stringify(trigger.pattern)} (not saved): ${trigger.reason}.`
|
|
35290
|
+
};
|
|
35291
|
+
}
|
|
35292
|
+
function assertRecallable(graph, resultingTriggers, dropped) {
|
|
34553
35293
|
const blockingDead = blockingDeadTriggers(graph, resultingTriggers);
|
|
34554
|
-
|
|
34555
|
-
|
|
35294
|
+
const dead = [...dropped, ...blockingDead];
|
|
35295
|
+
if (dead.length > 0 && blockingDead.length === resultingTriggers.length) {
|
|
35296
|
+
throw new UnrecallableLessonError(dead);
|
|
34556
35297
|
}
|
|
34557
35298
|
}
|
|
34558
35299
|
|
|
@@ -34561,7 +35302,7 @@ var WIDE_GLOB_MATCH_COUNT = 40;
|
|
|
34561
35302
|
var MAX_RECOMMENDED_TRIGGERS = 8;
|
|
34562
35303
|
function isBroadGlob(pattern) {
|
|
34563
35304
|
const p = pattern.trim();
|
|
34564
|
-
if (p === "*" || p === "**") return true;
|
|
35305
|
+
if (p === "*" || p === "**" || isNegatedGlob(p)) return true;
|
|
34565
35306
|
if (!p.includes("**")) return false;
|
|
34566
35307
|
const basename78 = p.slice(p.lastIndexOf("/") + 1);
|
|
34567
35308
|
return basename78.startsWith("*");
|
|
@@ -34605,12 +35346,19 @@ function inspectCapturedLesson(graph, lessonId, knownPaths) {
|
|
|
34605
35346
|
});
|
|
34606
35347
|
}
|
|
34607
35348
|
if (knownPaths !== void 0) {
|
|
34608
|
-
const dead =
|
|
34609
|
-
const deadHere = lesson.triggers
|
|
35349
|
+
const { dead, pending } = fileGlobLiveness(graph, knownPaths, lesson.triggers);
|
|
35350
|
+
const deadHere = patternsIn(graph, lesson.triggers, dead);
|
|
34610
35351
|
if (deadHere.length > 0) {
|
|
34611
35352
|
warnings.push({
|
|
34612
35353
|
code: "DEAD_GLOB",
|
|
34613
|
-
message: `Lesson "${lessonId}" has file_glob trigger(s) (${deadHere.join(", ")}) that match no file
|
|
35354
|
+
message: `Lesson "${lessonId}" has file_glob trigger(s) (${deadHere.join(", ")}) that match no file, and git history shows the path was renamed or deleted \u2014 likely a rename. Re-point them at the current path, or the lesson is unreachable via those globs.`
|
|
35355
|
+
});
|
|
35356
|
+
}
|
|
35357
|
+
const pendingHere = patternsIn(graph, lesson.triggers, pending);
|
|
35358
|
+
if (pendingHere.length > 0) {
|
|
35359
|
+
warnings.push({
|
|
35360
|
+
code: "PENDING_GLOB",
|
|
35361
|
+
message: `Lesson "${lessonId}" has file_glob trigger(s) (${pendingHere.join(", ")}) whose path does not exist yet \u2014 the trigger will fire once it does, so it is kept. If the path is a typo, re-point it.`
|
|
34614
35362
|
});
|
|
34615
35363
|
}
|
|
34616
35364
|
const wide = triggers.filter((t) => t.kind === "file_glob" && !isBroadGlob(t.pattern)).filter((t) => fileGlobMatchCount(t.pattern, knownPaths) > WIDE_GLOB_MATCH_COUNT).map((t) => t.pattern);
|
|
@@ -34623,18 +35371,21 @@ function inspectCapturedLesson(graph, lessonId, knownPaths) {
|
|
|
34623
35371
|
}
|
|
34624
35372
|
return warnings;
|
|
34625
35373
|
}
|
|
35374
|
+
function patternsIn(graph, ids, keep) {
|
|
35375
|
+
return ids.filter((id) => keep.has(id)).map((id) => graph.triggers[id]?.pattern).filter((p) => p !== void 0);
|
|
35376
|
+
}
|
|
34626
35377
|
|
|
34627
35378
|
// src/lessons/capture-near-duplicate.ts
|
|
34628
35379
|
var NEAR_DUPLICATE_THRESHOLD = 0.6;
|
|
34629
35380
|
function nearDuplicateWarning(graph, lessonId) {
|
|
34630
35381
|
const subject = graph.lessons[lessonId];
|
|
34631
35382
|
if (subject === void 0) return null;
|
|
34632
|
-
const subjectTokens = new Set(
|
|
35383
|
+
const subjectTokens = new Set(tokenize2(subject.rule));
|
|
34633
35384
|
if (subjectTokens.size === 0) return null;
|
|
34634
35385
|
let best = null;
|
|
34635
35386
|
for (const [id, other] of Object.entries(graph.lessons)) {
|
|
34636
35387
|
if (id === lessonId || other.status !== "active") continue;
|
|
34637
|
-
const otherTokens = new Set(
|
|
35388
|
+
const otherTokens = new Set(tokenize2(other.rule));
|
|
34638
35389
|
if (otherTokens.size === 0) continue;
|
|
34639
35390
|
const score = jaccard(subjectTokens, otherTokens);
|
|
34640
35391
|
if (score >= NEAR_DUPLICATE_THRESHOLD && (best === null || score > best.score)) {
|
|
@@ -34652,63 +35403,375 @@ function jaccard(a, b) {
|
|
|
34652
35403
|
for (const t of a) if (b.has(t)) intersection += 1;
|
|
34653
35404
|
return intersection / (a.size + b.size - intersection);
|
|
34654
35405
|
}
|
|
35406
|
+
var LEFTOVER = /\.(?:\d+\.tmp|[\w-]+\.stale)$/;
|
|
35407
|
+
var LEFTOVER_AGE_MS = 6e4;
|
|
35408
|
+
function sweepLessonsLeftovers(projectRoot, now = Date.now()) {
|
|
35409
|
+
const dir = lessonsPaths(projectRoot).base;
|
|
35410
|
+
let names;
|
|
35411
|
+
try {
|
|
35412
|
+
names = readdirSync(dir);
|
|
35413
|
+
} catch {
|
|
35414
|
+
return;
|
|
35415
|
+
}
|
|
35416
|
+
for (const name of names) {
|
|
35417
|
+
if (!LEFTOVER.test(name)) continue;
|
|
35418
|
+
const path = join(dir, name);
|
|
35419
|
+
try {
|
|
35420
|
+
if (now - statSync(path).mtimeMs > LEFTOVER_AGE_MS)
|
|
35421
|
+
rmSync(path, { recursive: true, force: true });
|
|
35422
|
+
} catch {
|
|
35423
|
+
}
|
|
35424
|
+
}
|
|
35425
|
+
}
|
|
34655
35426
|
|
|
34656
35427
|
// src/utils/filesystem/process-lock.ts
|
|
34657
35428
|
init_errors();
|
|
35429
|
+
var execFileAsync2 = promisify(execFile);
|
|
35430
|
+
var PS_TIMEOUT_MS = 2e3;
|
|
35431
|
+
var self;
|
|
35432
|
+
async function processIdentity(pid, platform = process.platform) {
|
|
35433
|
+
if (!Number.isInteger(pid) || pid <= 0 || platform === "win32") return null;
|
|
35434
|
+
try {
|
|
35435
|
+
return platform === "linux" ? await linuxIdentity(pid) : await psIdentity(pid);
|
|
35436
|
+
} catch {
|
|
35437
|
+
return null;
|
|
35438
|
+
}
|
|
35439
|
+
}
|
|
35440
|
+
function selfIdentity() {
|
|
35441
|
+
self ??= processIdentity(process.pid);
|
|
35442
|
+
return self;
|
|
35443
|
+
}
|
|
35444
|
+
function linuxStartIdentity(stat7, bootId) {
|
|
35445
|
+
const start = stat7.slice(stat7.lastIndexOf(")") + 2).split(" ")[19];
|
|
35446
|
+
const boot = bootId.trim();
|
|
35447
|
+
if (start === void 0 || !/^\d+$/.test(start) || boot === "") return null;
|
|
35448
|
+
return `${boot}:${start}`;
|
|
35449
|
+
}
|
|
35450
|
+
async function linuxIdentity(pid) {
|
|
35451
|
+
const [stat7, bootId] = await Promise.all([
|
|
35452
|
+
readFile(`/proc/${pid}/stat`, "utf-8"),
|
|
35453
|
+
readFile("/proc/sys/kernel/random/boot_id", "utf-8")
|
|
35454
|
+
]);
|
|
35455
|
+
return linuxStartIdentity(stat7, bootId);
|
|
35456
|
+
}
|
|
35457
|
+
async function psIdentity(pid) {
|
|
35458
|
+
const { stdout } = await execFileAsync2("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
35459
|
+
env: { ...process.env, LC_ALL: "C", TZ: "UTC" },
|
|
35460
|
+
timeout: PS_TIMEOUT_MS
|
|
35461
|
+
});
|
|
35462
|
+
const start = stdout.trim();
|
|
35463
|
+
return start === "" ? null : start;
|
|
35464
|
+
}
|
|
35465
|
+
var TRANSIENT_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
35466
|
+
var ATTEMPTS = 5;
|
|
35467
|
+
var BASE_DELAY_MS = 25;
|
|
35468
|
+
new Int32Array(new SharedArrayBuffer(4));
|
|
35469
|
+
function isTransientFsError(err) {
|
|
35470
|
+
const code = err?.code;
|
|
35471
|
+
return typeof code === "string" && TRANSIENT_CODES.has(code);
|
|
35472
|
+
}
|
|
35473
|
+
async function retryTransient(op) {
|
|
35474
|
+
for (let attempt = 1; ; attempt++) {
|
|
35475
|
+
try {
|
|
35476
|
+
return await op();
|
|
35477
|
+
} catch (err) {
|
|
35478
|
+
if (!isTransientFsError(err) || attempt >= ATTEMPTS) throw err;
|
|
35479
|
+
await setTimeout(BASE_DELAY_MS * 2 ** (attempt - 1));
|
|
35480
|
+
}
|
|
35481
|
+
}
|
|
35482
|
+
}
|
|
35483
|
+
|
|
35484
|
+
// src/utils/filesystem/process-lock-ops.ts
|
|
35485
|
+
init_rename_retry();
|
|
35486
|
+
var HOLDER_FILE = "holder.json";
|
|
35487
|
+
var OWNER_PREFIX = "owner-";
|
|
35488
|
+
var YOUNG_LOCK_GRACE_MS = 2e3;
|
|
35489
|
+
var PID_REUSE_PROBE_AFTER_MS = 2e3;
|
|
35490
|
+
function ownerPath(lockPath, token) {
|
|
35491
|
+
return join(lockPath, `${OWNER_PREFIX}${token}`);
|
|
35492
|
+
}
|
|
35493
|
+
function holderPath(lockPath) {
|
|
35494
|
+
return join(lockPath, HOLDER_FILE);
|
|
35495
|
+
}
|
|
35496
|
+
function errorCode(err) {
|
|
35497
|
+
return err?.code;
|
|
35498
|
+
}
|
|
35499
|
+
var LockPathNotFolderError = class extends Error {
|
|
35500
|
+
constructor(lockPath) {
|
|
35501
|
+
super(
|
|
35502
|
+
`${lockPath.replaceAll("\\", "/")} is a file, but agentsmesh keeps its lock there as a folder. Delete it and run the command again.`
|
|
35503
|
+
);
|
|
35504
|
+
this.name = "LockPathNotFolderError";
|
|
35505
|
+
}
|
|
35506
|
+
};
|
|
35507
|
+
async function ownerTokens(dir) {
|
|
35508
|
+
try {
|
|
35509
|
+
const entries = await readdir(dir);
|
|
35510
|
+
return entries.filter((e) => e.startsWith(OWNER_PREFIX)).map((e) => e.slice(OWNER_PREFIX.length));
|
|
35511
|
+
} catch (err) {
|
|
35512
|
+
if (errorCode(err) === "ENOENT") return null;
|
|
35513
|
+
if (errorCode(err) === "ENOTDIR") throw new LockPathNotFolderError(dir);
|
|
35514
|
+
throw err;
|
|
35515
|
+
}
|
|
35516
|
+
}
|
|
35517
|
+
async function readHolderRaw(dir) {
|
|
35518
|
+
return readFile(holderPath(dir), "utf-8").catch(() => null);
|
|
35519
|
+
}
|
|
35520
|
+
function holderToken(raw) {
|
|
35521
|
+
return raw === null ? void 0 : parseMetadata(raw)?.token;
|
|
35522
|
+
}
|
|
35523
|
+
async function inspectLock(lockPath) {
|
|
35524
|
+
const tokens = await ownerTokens(lockPath);
|
|
35525
|
+
if (tokens === null) return { kind: "gone" };
|
|
35526
|
+
const raw = await readHolderRaw(lockPath);
|
|
35527
|
+
const meta = raw === null ? null : parseMetadata(raw);
|
|
35528
|
+
const [only] = tokens;
|
|
35529
|
+
if (meta && tokens.length === 1 && only !== void 0 && meta.token === only) {
|
|
35530
|
+
return { kind: "held", token: only, meta };
|
|
35531
|
+
}
|
|
35532
|
+
if (meta && raw !== null && tokens.length === 0 && meta.token === void 0) {
|
|
35533
|
+
return { kind: "legacy", meta, raw };
|
|
35534
|
+
}
|
|
35535
|
+
const age = await dirAgeMs(lockPath);
|
|
35536
|
+
if (age === null) return { kind: "gone" };
|
|
35537
|
+
const young = age < YOUNG_LOCK_GRACE_MS && age >= -3e5;
|
|
35538
|
+
return young ? { kind: "young" } : { kind: "orphan", tokens, raw };
|
|
35539
|
+
}
|
|
35540
|
+
async function isStale(meta, staleMs, cache4) {
|
|
35541
|
+
const age = Date.now() - meta.started;
|
|
35542
|
+
if (age > staleMs || age < -3e5) return true;
|
|
35543
|
+
if (meta.hostname && meta.hostname !== hostname()) return false;
|
|
35544
|
+
if (!isProcessAlive(meta.pid)) return true;
|
|
35545
|
+
if (meta.procStart === void 0 || age < PID_REUSE_PROBE_AFTER_MS) return false;
|
|
35546
|
+
return pidReused(meta.pid, meta.procStart, cache4);
|
|
35547
|
+
}
|
|
35548
|
+
function describeHolder(state) {
|
|
35549
|
+
if (state.kind !== "held" && state.kind !== "legacy") return "unknown (unreadable lock metadata)";
|
|
35550
|
+
const { meta } = state;
|
|
35551
|
+
const host = meta.hostname ? `${meta.hostname}:` : "";
|
|
35552
|
+
return `${host}pid ${meta.pid} (running ${Math.max(0, Date.now() - meta.started)}ms)`;
|
|
35553
|
+
}
|
|
35554
|
+
async function dirAgeMs(lockPath) {
|
|
35555
|
+
try {
|
|
35556
|
+
return Date.now() - (await stat(lockPath)).mtimeMs;
|
|
35557
|
+
} catch (err) {
|
|
35558
|
+
if (errorCode(err) === "ENOENT") return null;
|
|
35559
|
+
throw err;
|
|
35560
|
+
}
|
|
35561
|
+
}
|
|
35562
|
+
function pidReused(pid, recorded, cache4) {
|
|
35563
|
+
const key = `${pid}:${recorded}`;
|
|
35564
|
+
let verdict = cache4.get(key);
|
|
35565
|
+
if (!verdict) {
|
|
35566
|
+
verdict = processIdentity(pid).then((current) => current !== null && current !== recorded);
|
|
35567
|
+
cache4.set(key, verdict);
|
|
35568
|
+
}
|
|
35569
|
+
return verdict;
|
|
35570
|
+
}
|
|
35571
|
+
function isProcessAlive(pid) {
|
|
35572
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
35573
|
+
try {
|
|
35574
|
+
process.kill(pid, 0);
|
|
35575
|
+
return true;
|
|
35576
|
+
} catch (err) {
|
|
35577
|
+
return errorCode(err) === "EPERM";
|
|
35578
|
+
}
|
|
35579
|
+
}
|
|
35580
|
+
function parseMetadata(raw) {
|
|
35581
|
+
let value;
|
|
35582
|
+
try {
|
|
35583
|
+
value = JSON.parse(raw);
|
|
35584
|
+
} catch {
|
|
35585
|
+
return null;
|
|
35586
|
+
}
|
|
35587
|
+
if (typeof value !== "object" || value === null) return null;
|
|
35588
|
+
const v = value;
|
|
35589
|
+
if (typeof v.pid !== "number" || typeof v.started !== "number") return null;
|
|
35590
|
+
const optionalText = (x) => x === void 0 || typeof x === "string";
|
|
35591
|
+
const textOk = [v.hostname, v.token, v.procStart].every(optionalText);
|
|
35592
|
+
return textOk ? value : null;
|
|
35593
|
+
}
|
|
35594
|
+
|
|
35595
|
+
// src/utils/filesystem/process-lock-ops.ts
|
|
35596
|
+
async function tryAcquire(lockPath, meta) {
|
|
35597
|
+
try {
|
|
35598
|
+
await mkdir(lockPath);
|
|
35599
|
+
} catch (err) {
|
|
35600
|
+
if (errorCode(err) === "EEXIST") return false;
|
|
35601
|
+
throw err;
|
|
35602
|
+
}
|
|
35603
|
+
const owner = ownerPath(lockPath, meta.token);
|
|
35604
|
+
let writingHolder = false;
|
|
35605
|
+
try {
|
|
35606
|
+
await mkdir(owner);
|
|
35607
|
+
if ((await ownerTokens(lockPath))?.length !== 1) return await backOff(lockPath, owner);
|
|
35608
|
+
writingHolder = true;
|
|
35609
|
+
await writeFile(holderPath(lockPath), JSON.stringify(meta), { encoding: "utf-8", flag: "wx" });
|
|
35610
|
+
} catch (err) {
|
|
35611
|
+
const code = errorCode(err);
|
|
35612
|
+
if (code === "ENOENT" || code === "EEXIST") return backOff(lockPath, owner);
|
|
35613
|
+
if (writingHolder) await rm(holderPath(lockPath), { force: true }).catch(() => {
|
|
35614
|
+
});
|
|
35615
|
+
await backOff(lockPath, owner);
|
|
35616
|
+
throw err;
|
|
35617
|
+
}
|
|
35618
|
+
if (existsSync(owner)) return true;
|
|
35619
|
+
await teardown(lockPath, [meta.token]);
|
|
35620
|
+
return false;
|
|
35621
|
+
}
|
|
35622
|
+
function releaseOwnedSync(lockPath, token) {
|
|
35623
|
+
try {
|
|
35624
|
+
rmdirSync(ownerPath(lockPath, token));
|
|
35625
|
+
} catch {
|
|
35626
|
+
return;
|
|
35627
|
+
}
|
|
35628
|
+
try {
|
|
35629
|
+
if (holderToken(readFileSync(holderPath(lockPath), "utf-8")) === token) {
|
|
35630
|
+
unlinkSync(holderPath(lockPath));
|
|
35631
|
+
}
|
|
35632
|
+
} catch {
|
|
35633
|
+
}
|
|
35634
|
+
try {
|
|
35635
|
+
rmdirSync(lockPath);
|
|
35636
|
+
} catch {
|
|
35637
|
+
}
|
|
35638
|
+
}
|
|
35639
|
+
async function evict(lockPath, state) {
|
|
35640
|
+
if (state.kind === "held") return evictOwners(lockPath, [state.token]);
|
|
35641
|
+
if (state.kind === "orphan" && state.tokens.length > 0) {
|
|
35642
|
+
return evictOwners(lockPath, state.tokens);
|
|
35643
|
+
}
|
|
35644
|
+
if (state.kind === "legacy" || state.kind === "orphan") return dropUnowned(lockPath, state.raw);
|
|
35645
|
+
}
|
|
35646
|
+
async function evictOwners(lockPath, tokens) {
|
|
35647
|
+
const removed = [];
|
|
35648
|
+
for (const token of tokens) {
|
|
35649
|
+
if (await removeOwner(lockPath, token)) removed.push(token);
|
|
35650
|
+
}
|
|
35651
|
+
if (removed.length > 0) await teardown(lockPath, removed);
|
|
35652
|
+
}
|
|
35653
|
+
async function removeOwner(lockPath, token) {
|
|
35654
|
+
try {
|
|
35655
|
+
await retryTransient(() => rmdir(ownerPath(lockPath, token)));
|
|
35656
|
+
return true;
|
|
35657
|
+
} catch (err) {
|
|
35658
|
+
if (errorCode(err) === "ENOENT") return false;
|
|
35659
|
+
throw err;
|
|
35660
|
+
}
|
|
35661
|
+
}
|
|
35662
|
+
async function teardown(lockPath, tokens) {
|
|
35663
|
+
const token = holderToken(await readHolderRaw(lockPath));
|
|
35664
|
+
if (token !== void 0 && tokens.includes(token)) {
|
|
35665
|
+
await unlink(holderPath(lockPath)).catch(() => {
|
|
35666
|
+
});
|
|
35667
|
+
}
|
|
35668
|
+
await rmdir(lockPath).catch(() => {
|
|
35669
|
+
});
|
|
35670
|
+
}
|
|
35671
|
+
async function dropUnowned(lockPath, judgedRaw) {
|
|
35672
|
+
const aside = `${lockPath}.${randomUUID()}.stale`;
|
|
35673
|
+
try {
|
|
35674
|
+
await renameWithRetry(lockPath, aside);
|
|
35675
|
+
} catch (err) {
|
|
35676
|
+
if (errorCode(err) === "ENOENT") return;
|
|
35677
|
+
throw err;
|
|
35678
|
+
}
|
|
35679
|
+
const owners = await ownerTokens(aside);
|
|
35680
|
+
if (owners?.length !== 0 || await readHolderRaw(aside) !== judgedRaw) {
|
|
35681
|
+
return putBack(aside, lockPath);
|
|
35682
|
+
}
|
|
35683
|
+
try {
|
|
35684
|
+
await rm(aside, { recursive: true, force: true });
|
|
35685
|
+
} catch (err) {
|
|
35686
|
+
await putBack(aside, lockPath);
|
|
35687
|
+
throw err;
|
|
35688
|
+
}
|
|
35689
|
+
}
|
|
35690
|
+
async function putBack(aside, lockPath) {
|
|
35691
|
+
await rename(aside, lockPath).catch(() => {
|
|
35692
|
+
});
|
|
35693
|
+
}
|
|
35694
|
+
async function backOff(lockPath, owner) {
|
|
35695
|
+
await rmdir(owner).catch(() => {
|
|
35696
|
+
});
|
|
35697
|
+
await rmdir(lockPath).catch(() => {
|
|
35698
|
+
});
|
|
35699
|
+
return false;
|
|
35700
|
+
}
|
|
35701
|
+
|
|
35702
|
+
// src/utils/filesystem/process-lock.ts
|
|
34658
35703
|
var DEFAULT_STALE_MS = 6 * 60 * 60 * 1e3;
|
|
34659
35704
|
var DEFAULT_RETRIES = 30;
|
|
34660
35705
|
var DEFAULT_RETRY_DELAY_MS = 200;
|
|
34661
|
-
var
|
|
35706
|
+
var MAX_IMMEDIATE_RETRIES = 100;
|
|
35707
|
+
var MAX_TRANSIENT_ERRORS = 5;
|
|
34662
35708
|
async function acquireProcessLock(lockPath, opts = {}) {
|
|
34663
35709
|
const retries = opts.retries ?? DEFAULT_RETRIES;
|
|
34664
|
-
const
|
|
34665
|
-
const stale = opts.staleMs ?? DEFAULT_STALE_MS;
|
|
35710
|
+
const staleMs = opts.staleMs ?? DEFAULT_STALE_MS;
|
|
34666
35711
|
await mkdir(dirname(lockPath), { recursive: true });
|
|
35712
|
+
const procStart = await selfIdentity();
|
|
35713
|
+
const probes = /* @__PURE__ */ new Map();
|
|
34667
35714
|
let attempt = 0;
|
|
35715
|
+
let immediate = 0;
|
|
35716
|
+
const waitingSince = Date.now();
|
|
35717
|
+
let noticed = false;
|
|
35718
|
+
let transient = 0;
|
|
34668
35719
|
while (true) {
|
|
34669
|
-
|
|
34670
|
-
|
|
34671
|
-
|
|
34672
|
-
|
|
34673
|
-
await
|
|
35720
|
+
let state;
|
|
35721
|
+
try {
|
|
35722
|
+
const holder = newHolder(procStart);
|
|
35723
|
+
if (await tryAcquire(lockPath, holder)) return holdLock(lockPath, holder.token);
|
|
35724
|
+
state = await inspectLock(lockPath);
|
|
35725
|
+
transient = 0;
|
|
35726
|
+
} catch (err) {
|
|
35727
|
+
if (!isTransientFsError(err) || ++transient >= MAX_TRANSIENT_ERRORS) throw err;
|
|
35728
|
+
await setTimeout(lockRetryDelayMs(transient, opts));
|
|
35729
|
+
continue;
|
|
35730
|
+
}
|
|
35731
|
+
if (immediate < MAX_IMMEDIATE_RETRIES && await clearedNow(lockPath, state, staleMs, probes)) {
|
|
35732
|
+
immediate++;
|
|
34674
35733
|
continue;
|
|
34675
35734
|
}
|
|
34676
35735
|
if (attempt >= retries) {
|
|
34677
|
-
|
|
34678
|
-
throw new LockAcquisitionError(lockPath, describeHolder(holder), { label: opts.label });
|
|
35736
|
+
throw new LockAcquisitionError(lockPath, describeHolder(state), { label: opts.label });
|
|
34679
35737
|
}
|
|
34680
35738
|
attempt++;
|
|
34681
|
-
|
|
35739
|
+
immediate = 0;
|
|
35740
|
+
if (!noticed && opts.onWait && Date.now() - waitingSince >= (opts.waitNoticeMs ?? 2e3)) {
|
|
35741
|
+
noticed = true;
|
|
35742
|
+
opts.onWait(describeHolder(state));
|
|
35743
|
+
}
|
|
35744
|
+
await setTimeout(lockRetryDelayMs(attempt, opts));
|
|
34682
35745
|
}
|
|
34683
35746
|
}
|
|
34684
|
-
|
|
34685
|
-
|
|
34686
|
-
|
|
34687
|
-
|
|
34688
|
-
|
|
34689
|
-
|
|
34690
|
-
|
|
34691
|
-
|
|
34692
|
-
|
|
35747
|
+
function lockRetryDelayMs(attempt, opts, random = Math.random) {
|
|
35748
|
+
const base = opts.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
35749
|
+
const cap2 = Math.max(base, opts.maxRetryDelayMs ?? base);
|
|
35750
|
+
const delay = Math.min(cap2, base * 2 ** (attempt - 1));
|
|
35751
|
+
return opts.jitter ? delay * (0.5 + random() * 0.5) : delay;
|
|
35752
|
+
}
|
|
35753
|
+
async function clearedNow(lockPath, state, staleMs, probes) {
|
|
35754
|
+
if (state.kind === "gone") return true;
|
|
35755
|
+
if (state.kind === "young") return false;
|
|
35756
|
+
if (state.kind !== "orphan" && !await isStale(state.meta, staleMs, probes)) return false;
|
|
35757
|
+
await evict(lockPath, state);
|
|
35758
|
+
return true;
|
|
35759
|
+
}
|
|
35760
|
+
function newHolder(procStart) {
|
|
35761
|
+
return {
|
|
34693
35762
|
pid: process.pid,
|
|
34694
35763
|
started: Date.now(),
|
|
34695
|
-
hostname:
|
|
35764
|
+
hostname: hostname(),
|
|
35765
|
+
token: randomUUID(),
|
|
35766
|
+
...procStart === null ? {} : { procStart }
|
|
34696
35767
|
};
|
|
34697
|
-
|
|
34698
|
-
|
|
34699
|
-
} catch (error) {
|
|
34700
|
-
await rm(lockPath, { recursive: true, force: true }).catch(() => {
|
|
34701
|
-
});
|
|
34702
|
-
throw error;
|
|
34703
|
-
}
|
|
35768
|
+
}
|
|
35769
|
+
function holdLock(lockPath, token) {
|
|
34704
35770
|
let released = false;
|
|
34705
35771
|
const cleanup = () => {
|
|
34706
35772
|
if (released) return;
|
|
34707
35773
|
released = true;
|
|
34708
|
-
|
|
34709
|
-
rmSync(lockPath, { recursive: true, force: true });
|
|
34710
|
-
} catch {
|
|
34711
|
-
}
|
|
35774
|
+
releaseOwnedSync(lockPath, token);
|
|
34712
35775
|
};
|
|
34713
35776
|
const signalHandler = (signal) => {
|
|
34714
35777
|
cleanup();
|
|
@@ -34717,76 +35780,69 @@ async function tryAcquire(lockPath) {
|
|
|
34717
35780
|
process.once("SIGINT", signalHandler);
|
|
34718
35781
|
process.once("SIGTERM", signalHandler);
|
|
34719
35782
|
process.once("exit", cleanup);
|
|
34720
|
-
|
|
35783
|
+
const release = async () => {
|
|
34721
35784
|
if (released) return;
|
|
34722
35785
|
released = true;
|
|
34723
35786
|
process.off("SIGINT", signalHandler);
|
|
34724
35787
|
process.off("SIGTERM", signalHandler);
|
|
34725
35788
|
process.off("exit", cleanup);
|
|
34726
|
-
await
|
|
35789
|
+
await evictOwners(lockPath, [token]).catch(() => {
|
|
34727
35790
|
});
|
|
34728
35791
|
};
|
|
34729
|
-
|
|
34730
|
-
|
|
34731
|
-
try {
|
|
34732
|
-
const raw = await readFile(join(lockPath, "holder.json"), "utf-8");
|
|
34733
|
-
const parsed = JSON.parse(raw);
|
|
34734
|
-
if (!isLockMetadata(parsed)) return null;
|
|
34735
|
-
return parsed;
|
|
34736
|
-
} catch {
|
|
34737
|
-
try {
|
|
34738
|
-
const info = await stat(lockPath);
|
|
34739
|
-
const ageMs = Date.now() - info.mtimeMs;
|
|
34740
|
-
if (ageMs < YOUNG_LOCK_GRACE_MS) return "young";
|
|
34741
|
-
} catch {
|
|
34742
|
-
}
|
|
34743
|
-
return null;
|
|
34744
|
-
}
|
|
34745
|
-
}
|
|
34746
|
-
function isStale(meta, staleMs) {
|
|
34747
|
-
if (!meta) return true;
|
|
34748
|
-
const sameHost = !meta.hostname || meta.hostname === getHostname();
|
|
34749
|
-
if (sameHost && !isProcessAlive(meta.pid)) return true;
|
|
34750
|
-
return Date.now() - meta.started > staleMs;
|
|
34751
|
-
}
|
|
34752
|
-
function isProcessAlive(pid) {
|
|
34753
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
34754
|
-
try {
|
|
34755
|
-
process.kill(pid, 0);
|
|
34756
|
-
return true;
|
|
34757
|
-
} catch (err) {
|
|
34758
|
-
return err.code === "EPERM";
|
|
34759
|
-
}
|
|
34760
|
-
}
|
|
34761
|
-
function describeHolder(meta) {
|
|
34762
|
-
if (!meta) return "unknown (unreadable lock metadata)";
|
|
34763
|
-
const host = meta.hostname ? `${meta.hostname}:` : "";
|
|
34764
|
-
return `${host}pid ${meta.pid} (running ${Date.now() - meta.started}ms)`;
|
|
34765
|
-
}
|
|
34766
|
-
function isLockMetadata(value) {
|
|
34767
|
-
if (typeof value !== "object" || value === null) return false;
|
|
34768
|
-
const v = value;
|
|
34769
|
-
return typeof v.pid === "number" && typeof v.started === "number";
|
|
34770
|
-
}
|
|
34771
|
-
function getHostname() {
|
|
34772
|
-
return hostname();
|
|
35792
|
+
const isHeld = async () => !released && existsSync(ownerPath(lockPath, token));
|
|
35793
|
+
return Object.assign(release, { isHeld });
|
|
34773
35794
|
}
|
|
34774
35795
|
|
|
34775
35796
|
// src/lessons/lessons-lock.ts
|
|
34776
35797
|
var LESSONS_LOCK_FILENAME = ".lessons.lock";
|
|
35798
|
+
var LESSONS_LOCK_OPTIONS = Object.freeze({
|
|
35799
|
+
retries: 500,
|
|
35800
|
+
retryDelayMs: 25,
|
|
35801
|
+
maxRetryDelayMs: 250,
|
|
35802
|
+
jitter: true,
|
|
35803
|
+
staleMs: 6e4
|
|
35804
|
+
});
|
|
34777
35805
|
function lessonsLockPath(projectRoot) {
|
|
34778
35806
|
return resolve(projectRoot, ".agentsmesh/lessons", LESSONS_LOCK_FILENAME);
|
|
34779
35807
|
}
|
|
34780
35808
|
async function acquireLessonsLock(projectRoot, opts = {}) {
|
|
34781
|
-
|
|
34782
|
-
|
|
34783
|
-
|
|
35809
|
+
return acquireProcessLock(lessonsLockPath(projectRoot), {
|
|
35810
|
+
retries: opts.retries ?? LESSONS_LOCK_OPTIONS.retries,
|
|
35811
|
+
retryDelayMs: opts.retryDelayMs ?? LESSONS_LOCK_OPTIONS.retryDelayMs,
|
|
35812
|
+
maxRetryDelayMs: opts.maxRetryDelayMs ?? LESSONS_LOCK_OPTIONS.maxRetryDelayMs,
|
|
35813
|
+
jitter: opts.jitter ?? LESSONS_LOCK_OPTIONS.jitter,
|
|
35814
|
+
staleMs: opts.staleMs ?? LESSONS_LOCK_OPTIONS.staleMs,
|
|
35815
|
+
label: "lessons lock",
|
|
35816
|
+
waitNoticeMs: opts.waitNoticeMs,
|
|
35817
|
+
onWait: opts.onWait ?? ((holder) => logger.warn(
|
|
35818
|
+
`Waiting for the lessons lock, held by ${holder}; a lock older than ${LESSONS_LOCK_OPTIONS.staleMs / 1e3} s is taken over.`
|
|
35819
|
+
))
|
|
35820
|
+
});
|
|
35821
|
+
}
|
|
35822
|
+
var LessonsLockLostError = class extends Error {
|
|
35823
|
+
constructor() {
|
|
35824
|
+
super(
|
|
35825
|
+
`lost the lessons lock while writing (the process was paused longer than the ${LESSONS_LOCK_OPTIONS.staleMs / 1e3} s stale window?); nothing was saved \u2014 retry the command`
|
|
35826
|
+
);
|
|
35827
|
+
this.name = "LessonsLockLostError";
|
|
35828
|
+
}
|
|
35829
|
+
};
|
|
35830
|
+
async function assertLessonsLockHeld(lock) {
|
|
35831
|
+
if (!await lock.isHeld()) throw new LessonsLockLostError();
|
|
34784
35832
|
}
|
|
34785
35833
|
|
|
34786
35834
|
// src/lessons/mutate.ts
|
|
34787
|
-
|
|
34788
|
-
|
|
34789
|
-
|
|
35835
|
+
var LessonsWriteRefusedError = class extends Error {
|
|
35836
|
+
findings;
|
|
35837
|
+
constructor(findings) {
|
|
35838
|
+
const errors = findings.map((f) => `${f.code}: ${f.message.replace(/[.\s]+$/, "")}`).join("; ");
|
|
35839
|
+
super(
|
|
35840
|
+
`Refused to save the lessons graph: this change would add ${errors}. Nothing was written.`
|
|
35841
|
+
);
|
|
35842
|
+
this.name = "LessonsWriteRefusedError";
|
|
35843
|
+
this.findings = findings;
|
|
35844
|
+
}
|
|
35845
|
+
};
|
|
34790
35846
|
function findingKey(f) {
|
|
34791
35847
|
return `${f.code}|${f.triggerId ?? ""}|${f.lessonId ?? ""}`;
|
|
34792
35848
|
}
|
|
@@ -34796,6 +35852,7 @@ function errorSignatures(report) {
|
|
|
34796
35852
|
async function mutateLessonsGraphLocked(projectRoot, mutator, options = {}) {
|
|
34797
35853
|
const release = await acquireLessonsLock(projectRoot, { retries: options.retries });
|
|
34798
35854
|
try {
|
|
35855
|
+
sweepLessonsLeftovers(projectRoot);
|
|
34799
35856
|
const graph = tryLoadLessonsGraph(projectRoot) ?? emptyGraph();
|
|
34800
35857
|
const baseline = errorSignatures(validateLessonsGraph(graph));
|
|
34801
35858
|
const result2 = await mutator(graph);
|
|
@@ -34804,12 +35861,10 @@ async function mutateLessonsGraphLocked(projectRoot, mutator, options = {}) {
|
|
|
34804
35861
|
(f) => f.level === "error" && !baseline.has(findingKey(f))
|
|
34805
35862
|
);
|
|
34806
35863
|
if (introduced.length > 0) {
|
|
34807
|
-
|
|
34808
|
-
throw new Error(
|
|
34809
|
-
`mutateLessonsGraph: refusing to write \u2014 this change introduces ${errors}. (Pre-existing graph issues are not blocking; run \`agentsmesh lessons validate\` to review and \`lessons untrigger\`/\`prune\` to repair them.)`
|
|
34810
|
-
);
|
|
35864
|
+
throw new LessonsWriteRefusedError(introduced);
|
|
34811
35865
|
}
|
|
34812
35866
|
graph.version = CURRENT_GRAPH_VERSION;
|
|
35867
|
+
await assertLessonsLockHeld(release);
|
|
34813
35868
|
saveLessonsGraph(projectRoot, graph);
|
|
34814
35869
|
return result2;
|
|
34815
35870
|
} finally {
|
|
@@ -34823,50 +35878,41 @@ async function mutateLessonsGraph(projectRoot, mutator, options = {}) {
|
|
|
34823
35878
|
|
|
34824
35879
|
// src/lessons/add.ts
|
|
34825
35880
|
async function addLesson(projectRoot, input, options = {}) {
|
|
34826
|
-
return mutateLessonsGraph(
|
|
34827
|
-
|
|
34828
|
-
|
|
35881
|
+
return mutateLessonsGraph(
|
|
35882
|
+
projectRoot,
|
|
35883
|
+
(graph) => addLessonInto(graph, input, { ...options, projectRoot }),
|
|
35884
|
+
{ retries: options.retries }
|
|
35885
|
+
);
|
|
34829
35886
|
}
|
|
34830
35887
|
function addLessonInto(graph, input, options) {
|
|
34831
35888
|
const ruleKey2 = normalizeRule(input.rule);
|
|
34832
35889
|
const trimmedRule = assertRuleShape(input.rule);
|
|
34833
35890
|
const existingId = findExistingLessonByRule(graph, ruleKey2);
|
|
34834
|
-
const isNewTopic = graph
|
|
34835
|
-
if (isNewTopic) {
|
|
34836
|
-
if (options.allowNewTopic !== true) throw new UnknownTopicError(input.topic);
|
|
34837
|
-
if (options.topicSummary === void 0 || options.topicSummary.length === 0) {
|
|
34838
|
-
throw new Error(`addLesson: new topic "${input.topic}" requires topicSummary.`);
|
|
34839
|
-
}
|
|
34840
|
-
graph.topics[input.topic] = { summary: options.topicSummary };
|
|
34841
|
-
}
|
|
35891
|
+
const isNewTopic = ensureTopic(graph, input.topic, options);
|
|
34842
35892
|
const existing = existingId !== null ? graph.lessons[existingId] : void 0;
|
|
34843
35893
|
assertTriggerInputs(input, options, existing?.triggers.length ?? 0);
|
|
34844
|
-
const
|
|
35894
|
+
const merged = mergeTriggers(graph, input.triggers, options.projectRoot);
|
|
35895
|
+
const { triggerIds, newTriggerIds, dropped } = dropDeadCommandTriggers(graph, merged, options);
|
|
34845
35896
|
if (!skipsTriggerGates(input, options)) {
|
|
34846
|
-
|
|
34847
|
-
|
|
34848
|
-
existing === void 0 ? triggerIds : union(existing.triggers, triggerIds)
|
|
34849
|
-
);
|
|
35897
|
+
const resulting = existing === void 0 ? triggerIds : union(existing.triggers, triggerIds);
|
|
35898
|
+
assertRecallable(graph, resulting, dropped);
|
|
34850
35899
|
}
|
|
34851
|
-
|
|
34852
|
-
|
|
34853
|
-
|
|
34854
|
-
|
|
34855
|
-
topics: union(existing2.topics, [input.topic]),
|
|
34856
|
-
triggers: union(existing2.triggers, triggerIds),
|
|
34857
|
-
evidence: union(existing2.evidence, input.evidence ?? []),
|
|
34858
|
-
...existing2.rationale === void 0 && input.rationale !== void 0 ? { rationale: input.rationale } : {},
|
|
34859
|
-
// Re-capturing a rule with --scope always promotes it to always-on.
|
|
34860
|
-
...input.scope === "always" ? { scope: "always" } : {}
|
|
34861
|
-
};
|
|
35900
|
+
const droppedWarnings = dropped.map(deadCommandWarning);
|
|
35901
|
+
if (existingId !== null && existing !== void 0) {
|
|
35902
|
+
const updated = upsertLesson(existing, input, triggerIds);
|
|
35903
|
+
graph.lessons[existingId] = updated;
|
|
34862
35904
|
return {
|
|
34863
35905
|
id: existingId,
|
|
34864
35906
|
isNewLesson: false,
|
|
34865
35907
|
isNewTopic,
|
|
34866
35908
|
newTriggerIds,
|
|
35909
|
+
changes: describeUpsert(existing, updated),
|
|
34867
35910
|
// Near-duplicate detection is meaningless on an upsert (the lesson IS the
|
|
34868
35911
|
// match), so only DEAD_GLOB/hygiene warnings apply here.
|
|
34869
|
-
warnings:
|
|
35912
|
+
warnings: [
|
|
35913
|
+
...inspectCapturedLesson(graph, existingId, options.knownPaths),
|
|
35914
|
+
...droppedWarnings
|
|
35915
|
+
]
|
|
34870
35916
|
};
|
|
34871
35917
|
}
|
|
34872
35918
|
const id = makeLessonId(graph, input.topic, ruleKey2);
|
|
@@ -34874,29 +35920,26 @@ function addLessonInto(graph, input, options) {
|
|
|
34874
35920
|
rule: trimmedRule,
|
|
34875
35921
|
topics: [input.topic],
|
|
34876
35922
|
triggers: triggerIds,
|
|
34877
|
-
evidence: input.evidence
|
|
35923
|
+
evidence: [...new Set(input.evidence ?? [])],
|
|
34878
35924
|
status: "active",
|
|
34879
35925
|
createdAt: input.createdAt ?? todayIso(),
|
|
34880
35926
|
...input.rationale === void 0 ? {} : { rationale: input.rationale },
|
|
34881
35927
|
...input.scope === "always" ? { scope: "always" } : {}
|
|
34882
35928
|
};
|
|
34883
|
-
const warnings = inspectCapturedLesson(graph, id, options.knownPaths);
|
|
34884
35929
|
const nearDup = nearDuplicateWarning(graph, id);
|
|
34885
35930
|
return {
|
|
34886
35931
|
id,
|
|
34887
35932
|
isNewLesson: true,
|
|
34888
35933
|
isNewTopic,
|
|
34889
35934
|
newTriggerIds,
|
|
34890
|
-
|
|
35935
|
+
changes: [],
|
|
35936
|
+
warnings: [
|
|
35937
|
+
...inspectCapturedLesson(graph, id, options.knownPaths),
|
|
35938
|
+
...nearDup === null ? [] : [nearDup],
|
|
35939
|
+
...droppedWarnings
|
|
35940
|
+
]
|
|
34891
35941
|
};
|
|
34892
35942
|
}
|
|
34893
|
-
function findExistingLessonByRule(graph, ruleKey2) {
|
|
34894
|
-
for (const [id, lesson] of Object.entries(graph.lessons)) {
|
|
34895
|
-
if (lesson.status !== "active") continue;
|
|
34896
|
-
if (normalizeRule(lesson.rule) === ruleKey2) return id;
|
|
34897
|
-
}
|
|
34898
|
-
return null;
|
|
34899
|
-
}
|
|
34900
35943
|
|
|
34901
35944
|
// src/lessons/import-legacy-merge.ts
|
|
34902
35945
|
async function mergeLegacy(projectRoot, paths2, specs, summaryByTopic, options) {
|
|
@@ -34929,19 +35972,33 @@ async function mergeLegacy(projectRoot, paths2, specs, summaryByTopic, options)
|
|
|
34929
35972
|
triggerCount: addedTriggers.size
|
|
34930
35973
|
};
|
|
34931
35974
|
}
|
|
34932
|
-
|
|
34933
|
-
|
|
34934
|
-
|
|
34935
|
-
|
|
34936
|
-
|
|
34937
|
-
|
|
34938
|
-
|
|
35975
|
+
var LESSONS_DIR = ".agentsmesh/lessons";
|
|
35976
|
+
var LegacyTopicPathError = class extends Error {
|
|
35977
|
+
code = "LEGACY_TOPIC_PATH_OUTSIDE";
|
|
35978
|
+
constructor(file) {
|
|
35979
|
+
super(
|
|
35980
|
+
`Legacy topic file path is outside .agentsmesh/lessons/: ${file}. Refusing to migrate (legacy artifacts left intact).`
|
|
35981
|
+
);
|
|
35982
|
+
this.name = "LegacyTopicPathError";
|
|
34939
35983
|
}
|
|
34940
35984
|
};
|
|
34941
|
-
async function
|
|
34942
|
-
const
|
|
34943
|
-
const
|
|
34944
|
-
const
|
|
35985
|
+
async function resolveLegacyTopicPath(projectRoot, file) {
|
|
35986
|
+
const forward = file.replaceAll("\\", "/");
|
|
35987
|
+
const normalized = posix.normalize(forward);
|
|
35988
|
+
const relative27 = !/^[A-Za-z]:/.test(forward) && !forward.startsWith("/") && normalized.startsWith(`${LESSONS_DIR}/`);
|
|
35989
|
+
if (!relative27) throw new LegacyTopicPathError(file);
|
|
35990
|
+
const target34 = join(projectRoot, normalized);
|
|
35991
|
+
try {
|
|
35992
|
+
await assertPathInsideRoot(join(projectRoot, LESSONS_DIR), target34);
|
|
35993
|
+
} catch {
|
|
35994
|
+
throw new LegacyTopicPathError(file);
|
|
35995
|
+
}
|
|
35996
|
+
return target34;
|
|
35997
|
+
}
|
|
35998
|
+
async function readLegacySource(projectRoot, migratedAt) {
|
|
35999
|
+
const index = LegacyIndexSchema.parse(
|
|
36000
|
+
parse(readFileSync(lessonsPaths(projectRoot).index, "utf8"))
|
|
36001
|
+
);
|
|
34945
36002
|
const topics = {};
|
|
34946
36003
|
const triggersById = /* @__PURE__ */ new Map();
|
|
34947
36004
|
const triggerIdByKey = /* @__PURE__ */ new Map();
|
|
@@ -34952,14 +36009,15 @@ async function importLegacyLessons(projectRoot, options) {
|
|
|
34952
36009
|
topics[cluster.topic] = { summary: cluster.summary };
|
|
34953
36010
|
summaryByTopic.set(cluster.topic, cluster.summary);
|
|
34954
36011
|
const clusterTriggerIds = collectClusterTriggerIds(cluster, triggersById, triggerIdByKey);
|
|
34955
|
-
const topicFile =
|
|
36012
|
+
const topicFile = await resolveLegacyTopicPath(projectRoot, cluster.file);
|
|
34956
36013
|
if (!existsSync(topicFile)) {
|
|
34957
36014
|
throw new Error(
|
|
34958
|
-
`
|
|
36015
|
+
`Legacy topic file is missing: ${cluster.file}. Refusing to migrate (legacy artifacts left intact).`
|
|
34959
36016
|
);
|
|
34960
36017
|
}
|
|
34961
|
-
const
|
|
34962
|
-
|
|
36018
|
+
for (const { index: ruleIndex, body, evidence } of parseRulesSection(
|
|
36019
|
+
readFileSync(topicFile, "utf8")
|
|
36020
|
+
)) {
|
|
34963
36021
|
const lessonEvidence = [
|
|
34964
36022
|
`legacy:${cluster.file}#rule-${ruleIndex}`,
|
|
34965
36023
|
...evidence.map((e) => `legacy:${e}`)
|
|
@@ -34970,7 +36028,7 @@ async function importLegacyLessons(projectRoot, options) {
|
|
|
34970
36028
|
triggers: clusterTriggerIds,
|
|
34971
36029
|
evidence: lessonEvidence,
|
|
34972
36030
|
status: "active",
|
|
34973
|
-
createdAt:
|
|
36031
|
+
createdAt: migratedAt
|
|
34974
36032
|
};
|
|
34975
36033
|
specs.push({
|
|
34976
36034
|
rule: body,
|
|
@@ -34981,22 +36039,43 @@ async function importLegacyLessons(projectRoot, options) {
|
|
|
34981
36039
|
keywords: cluster.triggers.keywords
|
|
34982
36040
|
},
|
|
34983
36041
|
evidence: lessonEvidence,
|
|
34984
|
-
createdAt:
|
|
36042
|
+
createdAt: migratedAt
|
|
34985
36043
|
});
|
|
34986
36044
|
}
|
|
34987
36045
|
}
|
|
34988
|
-
|
|
36046
|
+
return { topics, triggers: Object.fromEntries(triggersById), lessons, specs, summaryByTopic };
|
|
36047
|
+
}
|
|
36048
|
+
|
|
36049
|
+
// src/lessons/import-legacy.ts
|
|
36050
|
+
var LessonsGraphExistsError = class extends Error {
|
|
36051
|
+
code = "LESSONS_GRAPH_EXISTS";
|
|
36052
|
+
constructor() {
|
|
36053
|
+
super(
|
|
36054
|
+
"A non-empty lessons.json already exists. Pass --force to overwrite it, or --merge to add the legacy lessons to it."
|
|
36055
|
+
);
|
|
36056
|
+
this.name = "LessonsGraphExistsError";
|
|
36057
|
+
}
|
|
36058
|
+
};
|
|
36059
|
+
async function importLegacyLessons(projectRoot, options) {
|
|
36060
|
+
const paths2 = lessonsPaths(projectRoot);
|
|
36061
|
+
if (options.merge === true) {
|
|
36062
|
+
const { specs, summaryByTopic } = await readLegacySource(projectRoot, options.migratedAt);
|
|
34989
36063
|
return mergeLegacy(projectRoot, paths2, specs, summaryByTopic, options);
|
|
34990
|
-
|
|
34991
|
-
await mutateLessonsGraphLocked(projectRoot, (g) => {
|
|
36064
|
+
}
|
|
36065
|
+
const { topics, lessons, triggers } = await mutateLessonsGraphLocked(projectRoot, async (g) => {
|
|
36066
|
+
if (options.requireAbsentGraph === true && existsSync(paths2.graph)) {
|
|
36067
|
+
throw new LessonsGraphExistsError();
|
|
36068
|
+
}
|
|
34992
36069
|
const populated = Object.keys(g.lessons).length > 0 || Object.keys(g.topics).length > 0 || Object.keys(g.triggers).length > 0;
|
|
34993
36070
|
if (options.force !== true && populated) {
|
|
34994
36071
|
throw new LessonsGraphExistsError();
|
|
34995
36072
|
}
|
|
36073
|
+
const source = await readLegacySource(projectRoot, options.migratedAt);
|
|
34996
36074
|
g.version = CURRENT_GRAPH_VERSION;
|
|
34997
|
-
g.lessons = lessons;
|
|
34998
|
-
g.topics = topics;
|
|
34999
|
-
g.triggers = triggers;
|
|
36075
|
+
g.lessons = source.lessons;
|
|
36076
|
+
g.topics = source.topics;
|
|
36077
|
+
g.triggers = source.triggers;
|
|
36078
|
+
return source;
|
|
35000
36079
|
});
|
|
35001
36080
|
const deletedPaths = options.deleteLegacy === false ? [] : deleteLegacyArtifacts(paths2.base);
|
|
35002
36081
|
return {
|
|
@@ -35004,7 +36083,7 @@ async function importLegacyLessons(projectRoot, options) {
|
|
|
35004
36083
|
deletedPaths,
|
|
35005
36084
|
topicCount: Object.keys(topics).length,
|
|
35006
36085
|
lessonCount: Object.keys(lessons).length,
|
|
35007
|
-
triggerCount:
|
|
36086
|
+
triggerCount: Object.keys(triggers).length
|
|
35008
36087
|
};
|
|
35009
36088
|
}
|
|
35010
36089
|
|
|
@@ -35014,13 +36093,14 @@ async function maybeAutoMigrateLessons(projectRoot) {
|
|
|
35014
36093
|
const paths2 = lessonsPaths(projectRoot);
|
|
35015
36094
|
if (!existsSync(paths2.index)) return false;
|
|
35016
36095
|
try {
|
|
35017
|
-
await importLegacyLessons(projectRoot, { migratedAt: todayIso() });
|
|
36096
|
+
await importLegacyLessons(projectRoot, { migratedAt: todayIso(), requireAbsentGraph: true });
|
|
35018
36097
|
return true;
|
|
35019
36098
|
} catch (err) {
|
|
35020
36099
|
if (err instanceof LessonsGraphExistsError) return false;
|
|
35021
36100
|
throw err;
|
|
35022
36101
|
}
|
|
35023
36102
|
}
|
|
36103
|
+
new Int32Array(new SharedArrayBuffer(4));
|
|
35024
36104
|
|
|
35025
36105
|
// src/lessons/keyword-match.ts
|
|
35026
36106
|
function deriveHaystackTokens(query) {
|
|
@@ -35054,7 +36134,7 @@ function containsRun(needle, hay) {
|
|
|
35054
36134
|
return false;
|
|
35055
36135
|
}
|
|
35056
36136
|
function keywordMatches(pattern, query) {
|
|
35057
|
-
const needle =
|
|
36137
|
+
const needle = tokenize2(pattern);
|
|
35058
36138
|
if (query.keyword !== void 0 && containsRun(needle, splitTokens(query.keyword.toLowerCase()))) {
|
|
35059
36139
|
return true;
|
|
35060
36140
|
}
|
|
@@ -35063,6 +36143,7 @@ function keywordMatches(pattern, query) {
|
|
|
35063
36143
|
|
|
35064
36144
|
// src/lessons/query.ts
|
|
35065
36145
|
var COMMAND_MATCH_BUDGET = 5e6;
|
|
36146
|
+
var GLOB_MATCH_BUDGET = 2e6;
|
|
35066
36147
|
function queryLessons(graph, query) {
|
|
35067
36148
|
if (query.file === void 0 && query.command === void 0 && query.keyword === void 0) {
|
|
35068
36149
|
return [];
|
|
@@ -35085,9 +36166,12 @@ function collectMatchedTriggersByKind(graph, query) {
|
|
|
35085
36166
|
command_pattern: /* @__PURE__ */ new Set(),
|
|
35086
36167
|
keyword: /* @__PURE__ */ new Set()
|
|
35087
36168
|
};
|
|
35088
|
-
const
|
|
36169
|
+
const budgets = {
|
|
36170
|
+
command: { remaining: COMMAND_MATCH_BUDGET },
|
|
36171
|
+
glob: { remaining: GLOB_MATCH_BUDGET }
|
|
36172
|
+
};
|
|
35089
36173
|
for (const [id, trigger] of Object.entries(graph.triggers)) {
|
|
35090
|
-
if (triggerMatches(trigger, query,
|
|
36174
|
+
if (triggerMatches(trigger, query, budgets)) byKind[trigger.kind].add(id);
|
|
35091
36175
|
}
|
|
35092
36176
|
return byKind;
|
|
35093
36177
|
}
|
|
@@ -35095,21 +36179,29 @@ function collectMatchedTriggerIds(graph, query) {
|
|
|
35095
36179
|
const { file_glob, command_pattern, keyword } = collectMatchedTriggersByKind(graph, query);
|
|
35096
36180
|
return /* @__PURE__ */ new Set([...file_glob, ...command_pattern, ...keyword]);
|
|
35097
36181
|
}
|
|
35098
|
-
function triggerMatches(trigger, query,
|
|
36182
|
+
function triggerMatches(trigger, query, budgets) {
|
|
35099
36183
|
switch (trigger.kind) {
|
|
35100
|
-
case "file_glob":
|
|
36184
|
+
case "file_glob": {
|
|
35101
36185
|
if (query.file === void 0) return false;
|
|
35102
|
-
|
|
36186
|
+
const matcher = getGlobMatcher(trigger.pattern);
|
|
36187
|
+
return matcher !== null && matcher.test(query.file, budgets.glob);
|
|
36188
|
+
}
|
|
35103
36189
|
case "command_pattern": {
|
|
35104
36190
|
if (query.command === void 0) return false;
|
|
35105
36191
|
const matcher = getCommandMatcher(trigger.pattern);
|
|
35106
|
-
return matcher !== null && matcher.test(query.command,
|
|
36192
|
+
return matcher !== null && matcher.test(query.command, budgets.command);
|
|
35107
36193
|
}
|
|
35108
36194
|
case "keyword":
|
|
35109
36195
|
return keywordMatches(trigger.pattern, query);
|
|
35110
36196
|
}
|
|
35111
36197
|
}
|
|
35112
36198
|
|
|
36199
|
+
// src/lessons/telemetry.ts
|
|
36200
|
+
init_fs_text_encoding();
|
|
36201
|
+
|
|
36202
|
+
// src/lessons/log-record-guards.ts
|
|
36203
|
+
init_guards();
|
|
36204
|
+
|
|
35113
36205
|
// src/lessons/telemetry.ts
|
|
35114
36206
|
function recallLogPath(projectRoot) {
|
|
35115
36207
|
return join(lessonsPaths(projectRoot).base, "recall-log.jsonl");
|
|
@@ -35246,14 +36338,20 @@ function applyCaps(ranked, options) {
|
|
|
35246
36338
|
}
|
|
35247
36339
|
return out2;
|
|
35248
36340
|
}
|
|
36341
|
+
|
|
36342
|
+
// src/lessons/recall-config.ts
|
|
36343
|
+
init_fs_text_encoding();
|
|
35249
36344
|
function defaultLessonsConfig() {
|
|
35250
36345
|
return {
|
|
35251
36346
|
recallLimit: DEFAULT_RECALL_LIMIT,
|
|
35252
36347
|
recallMaxTokens: DEFAULT_RECALL_MAX_TOKENS,
|
|
35253
36348
|
autoPrune: false,
|
|
35254
|
-
telemetry: false
|
|
36349
|
+
telemetry: false,
|
|
36350
|
+
outcomeLog: true
|
|
35255
36351
|
};
|
|
35256
36352
|
}
|
|
36353
|
+
|
|
36354
|
+
// src/lessons/outcome-log.ts
|
|
35257
36355
|
function outcomeLogPath(projectRoot) {
|
|
35258
36356
|
return join(lessonsPaths(projectRoot).base, "outcome-log.jsonl");
|
|
35259
36357
|
}
|
|
@@ -35269,19 +36367,17 @@ async function mergeLessons(projectRoot, loserId, keeperId, options = {}) {
|
|
|
35269
36367
|
}
|
|
35270
36368
|
function mergeInto(graph, loserId, keeperId) {
|
|
35271
36369
|
if (loserId === keeperId) {
|
|
35272
|
-
throw new Error(`
|
|
36370
|
+
throw new Error(`Cannot merge lesson "${loserId}" into itself.`);
|
|
35273
36371
|
}
|
|
35274
36372
|
const loser = graph.lessons[loserId];
|
|
35275
|
-
if (loser === void 0) throw new Error(`
|
|
36373
|
+
if (loser === void 0) throw new Error(`Unknown lesson "${loserId}".`);
|
|
35276
36374
|
const keeper = graph.lessons[keeperId];
|
|
35277
|
-
if (keeper === void 0) throw new Error(`
|
|
36375
|
+
if (keeper === void 0) throw new Error(`Unknown lesson "${keeperId}".`);
|
|
35278
36376
|
if (keeper.status !== "active") {
|
|
35279
|
-
throw new Error(`
|
|
36377
|
+
throw new Error(`Keeper "${keeperId}" is not active (status: ${keeper.status}).`);
|
|
35280
36378
|
}
|
|
35281
36379
|
if (loser.status !== "active") {
|
|
35282
|
-
throw new Error(
|
|
35283
|
-
`mergeLessons: loser "${loserId}" is already ${loser.status}; nothing to merge.`
|
|
35284
|
-
);
|
|
36380
|
+
throw new Error(`Loser "${loserId}" is already ${loser.status}; nothing to merge.`);
|
|
35285
36381
|
}
|
|
35286
36382
|
graph.lessons[keeperId] = {
|
|
35287
36383
|
...keeper,
|
|
@@ -35336,9 +36432,123 @@ async function stripMarkersInGraph(projectRoot, options = {}) {
|
|
|
35336
36432
|
}
|
|
35337
36433
|
init_recall_hook_scaffold();
|
|
35338
36434
|
|
|
36435
|
+
// src/lessons/merge-driver-setup.ts
|
|
36436
|
+
init_cli_invocation();
|
|
36437
|
+
function commandProgram(command) {
|
|
36438
|
+
const m = /^\s*(?:"([^"]*)"|'([^']*)'|(\S+))/.exec(command);
|
|
36439
|
+
return m?.[1] ?? m?.[2] ?? m?.[3] ?? "";
|
|
36440
|
+
}
|
|
36441
|
+
function isTransientBinDir(dir) {
|
|
36442
|
+
const parts = dir.split(/[\\/]+/).filter((p) => p !== "");
|
|
36443
|
+
const [parent, last] = parts.slice(-2);
|
|
36444
|
+
return parts.includes("_npx") || parent === "node_modules" && last === ".bin";
|
|
36445
|
+
}
|
|
36446
|
+
function commandLauncherExists(command, env = process.env, platform = process.platform) {
|
|
36447
|
+
const program = commandProgram(command);
|
|
36448
|
+
if (program === "") return false;
|
|
36449
|
+
if (program.includes("/") || program.includes("\\")) return existsSync(program);
|
|
36450
|
+
const win = platform === "win32";
|
|
36451
|
+
const exts = win ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")] : [""];
|
|
36452
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(win ? ";" : ":").filter((d) => d !== "" && !isTransientBinDir(d));
|
|
36453
|
+
return dirs.some((dir) => exts.some((ext) => existsSync(join(dir, program + ext))));
|
|
36454
|
+
}
|
|
36455
|
+
function localBinExists(fromDir, name) {
|
|
36456
|
+
for (let dir = resolve(fromDir); ; dir = dirname(dir)) {
|
|
36457
|
+
const bin = join(dir, "node_modules", ".bin");
|
|
36458
|
+
if (existsSync(join(bin, name)) || existsSync(join(bin, `${name}.cmd`))) return true;
|
|
36459
|
+
if (dirname(dir) === dir) return false;
|
|
36460
|
+
}
|
|
36461
|
+
}
|
|
36462
|
+
|
|
35339
36463
|
// src/lessons/merge-driver-setup.ts
|
|
35340
36464
|
var LESSONS_MERGE_DRIVER = "agentsmesh-lessons";
|
|
35341
|
-
var LESSONS_GITATTRIBUTES_ENTRY =
|
|
36465
|
+
var LESSONS_GITATTRIBUTES_ENTRY = `${LESSONS_GRAPH_PATH} merge=${LESSONS_MERGE_DRIVER}`;
|
|
36466
|
+
var DRIVER_KEY = `merge.${LESSONS_MERGE_DRIVER}.driver`;
|
|
36467
|
+
var NAME_KEY = `merge.${LESSONS_MERGE_DRIVER}.name`;
|
|
36468
|
+
var DRIVER_NAME = "agentsmesh lessons union";
|
|
36469
|
+
function lessonsMergeDriverCommand(invocation) {
|
|
36470
|
+
return `${invocation.replaceAll("\\", "/")} lessons merge-driver %O %A %B`;
|
|
36471
|
+
}
|
|
36472
|
+
var NPX_INVOCATION = "npx --no --offline agentsmesh";
|
|
36473
|
+
var NPX_COMMAND = lessonsMergeDriverCommand(NPX_INVOCATION);
|
|
36474
|
+
var BARE_COMMAND = lessonsMergeDriverCommand("agentsmesh");
|
|
36475
|
+
var OWN_COMMANDS = /* @__PURE__ */ new Set([BARE_COMMAND, NPX_COMMAND]);
|
|
36476
|
+
function launchable(command, env) {
|
|
36477
|
+
const npxMissing = command === NPX_COMMAND && !commandLauncherExists(NPX_COMMAND, env);
|
|
36478
|
+
return npxMissing && commandLauncherExists(BARE_COMMAND, env) ? BARE_COMMAND : command;
|
|
36479
|
+
}
|
|
36480
|
+
function configValue(git, root, key) {
|
|
36481
|
+
const r = git(root, ["config", "--get", key]);
|
|
36482
|
+
return r.status === 0 ? r.stdout.trim() : null;
|
|
36483
|
+
}
|
|
36484
|
+
function launchProblem(git, projectRoot, command, env) {
|
|
36485
|
+
if (!commandLauncherExists(command, env)) {
|
|
36486
|
+
return `\`${commandProgram(command)}\` is not installed on PATH (npx and package-script bin folders do not count), so git could not start the driver; install agentsmesh globally or as a project devDependency`;
|
|
36487
|
+
}
|
|
36488
|
+
if (command !== NPX_COMMAND) return null;
|
|
36489
|
+
const top = git(projectRoot, ["rev-parse", "--show-toplevel"]);
|
|
36490
|
+
const repoRoot = top.status === 0 ? top.stdout.trim() : projectRoot;
|
|
36491
|
+
if (localBinExists(repoRoot, "agentsmesh") || commandLauncherExists("agentsmesh", env)) {
|
|
36492
|
+
return null;
|
|
36493
|
+
}
|
|
36494
|
+
return `git runs the driver from the repository root (${repoRoot.replaceAll("\\", "/")}), where \`${NPX_INVOCATION}\` cannot find agentsmesh; add agentsmesh to the devDependencies of the root package.json and install, or install agentsmesh globally`;
|
|
36495
|
+
}
|
|
36496
|
+
function ensureLessonsMergeDriver(projectRoot, options = {}) {
|
|
36497
|
+
const git = options.git ?? runGit2;
|
|
36498
|
+
const env = options.env ?? process.env;
|
|
36499
|
+
const command = launchable(
|
|
36500
|
+
lessonsMergeDriverCommand(options.invocation ?? agentsmeshInvocation(projectRoot)),
|
|
36501
|
+
env
|
|
36502
|
+
);
|
|
36503
|
+
const attr = git(projectRoot, ["check-attr", "merge", "--", LESSONS_GRAPH_PATH]);
|
|
36504
|
+
if (attr.status !== 0 || !attr.stdout.trim().endsWith(`: merge: ${LESSONS_MERGE_DRIVER}`)) {
|
|
36505
|
+
return { status: "skipped", command };
|
|
36506
|
+
}
|
|
36507
|
+
const existing = configValue(git, projectRoot, DRIVER_KEY);
|
|
36508
|
+
if (existing !== null && existing !== command && !OWN_COMMANDS.has(existing)) {
|
|
36509
|
+
return { status: "custom", command, existing };
|
|
36510
|
+
}
|
|
36511
|
+
const reason = existing === command ? null : launchProblem(git, projectRoot, command, env);
|
|
36512
|
+
if (reason !== null) return { status: "failed", command, reason };
|
|
36513
|
+
const writes = [];
|
|
36514
|
+
if (existing !== command) writes.push([DRIVER_KEY, command]);
|
|
36515
|
+
if (configValue(git, projectRoot, NAME_KEY) === null) writes.push([NAME_KEY, DRIVER_NAME]);
|
|
36516
|
+
for (const [key, value] of writes) {
|
|
36517
|
+
const r = git(projectRoot, ["config", "--local", key, value]);
|
|
36518
|
+
if (r.status !== 0) {
|
|
36519
|
+
const detail = r.stderr.trim() || `exit ${r.status}`;
|
|
36520
|
+
const reason2 = `git config failed (${detail}); run: git config ${DRIVER_KEY} "${command}"`;
|
|
36521
|
+
return { status: "failed", command, reason: reason2 };
|
|
36522
|
+
}
|
|
36523
|
+
}
|
|
36524
|
+
if (existing === command) return { status: "unchanged", command };
|
|
36525
|
+
return { status: existing === null ? "configured" : "updated", command };
|
|
36526
|
+
}
|
|
36527
|
+
|
|
36528
|
+
// src/lessons/recall-hook-hint.ts
|
|
36529
|
+
init_recall_hook_scaffold();
|
|
36530
|
+
var RECALL_HOOK_TEAM_HINT = "Lessons recall hooks call a global agentsmesh: teammates without a global install will not get lesson recall; add agentsmesh as a devDependency and re-run 'agentsmesh init --lessons'.";
|
|
36531
|
+
function recallHookTeamHint(projectRoot) {
|
|
36532
|
+
const wired = wiredRecallCommands(projectRoot);
|
|
36533
|
+
if (wired.length === 0) return null;
|
|
36534
|
+
const expected = recallHookCommand(projectRoot);
|
|
36535
|
+
const stale = wired.find((command) => command !== expected);
|
|
36536
|
+
if (stale !== void 0) {
|
|
36537
|
+
return `Lessons recall hooks run \`${stale}\`, but this project now calls \`${expected}\`; re-run 'agentsmesh init --lessons' to update them.`;
|
|
36538
|
+
}
|
|
36539
|
+
if (!existsSync(join(projectRoot, "package.json"))) return null;
|
|
36540
|
+
return expected === RECALL_HOOK_COMMAND ? RECALL_HOOK_TEAM_HINT : null;
|
|
36541
|
+
}
|
|
36542
|
+
function wiredRecallCommands(projectRoot) {
|
|
36543
|
+
try {
|
|
36544
|
+
const hooks = parse(
|
|
36545
|
+
readFileSync(join(projectRoot, ".agentsmesh", "hooks.yaml"), "utf8")
|
|
36546
|
+
);
|
|
36547
|
+
return Object.values(hooks ?? {}).filter(Array.isArray).flat().map((entry) => entry?.command).filter(isManagedRecallCommand).map((command) => command.trim());
|
|
36548
|
+
} catch {
|
|
36549
|
+
return [];
|
|
36550
|
+
}
|
|
36551
|
+
}
|
|
35342
36552
|
|
|
35343
36553
|
// src/utils/filesystem/gitattributes.ts
|
|
35344
36554
|
init_fs();
|
|
@@ -35479,7 +36689,8 @@ At least one _effective_ trigger is required (or \`--scope always\` for a univer
|
|
|
35479
36689
|
the capture is rejected (\`UNRECALLABLE_LESSON\`); prefer \`--trigger-file\`. No shell \u2192 MCP \`lessons_query\`,
|
|
35480
36690
|
\`lessons_add\`, \`lessons_topics\`, \`lessons_show\`, \`lessons_deprecate\`. Run
|
|
35481
36691
|
\`agentsmesh lessons --help\` for every subcommand and flag: query, add, topics, show,
|
|
35482
|
-
deprecate, merge, untrigger, strip-markers, prune, journal, validate, stats, import-md.
|
|
36692
|
+
deprecate, merge, untrigger, strip-markers, prune, journal, validate, resolve, stats, import-md.
|
|
36693
|
+
A git merge conflict in \`lessons.json\` \u2192 run \`agentsmesh lessons resolve\`; never hand-edit it.
|
|
35483
36694
|
|
|
35484
36695
|
### Rationalization Prevention \u2014 these excuses mean STOP
|
|
35485
36696
|
|
|
@@ -35523,11 +36734,14 @@ async function scaffoldLessons(projectRoot) {
|
|
|
35523
36734
|
const gitignoreUpdated = await ensureGitignoreEntries(projectRoot, [
|
|
35524
36735
|
toRelPath(projectRoot, recallLogPath(projectRoot)),
|
|
35525
36736
|
toRelPath(projectRoot, captureLogPath(projectRoot)),
|
|
35526
|
-
toRelPath(projectRoot, outcomeLogPath(projectRoot))
|
|
36737
|
+
toRelPath(projectRoot, outcomeLogPath(projectRoot)),
|
|
36738
|
+
`${toRelPath(projectRoot, lessonsLockPath(projectRoot))}/`,
|
|
36739
|
+
`${toRelPath(projectRoot, paths2.base)}/*.tmp`
|
|
35527
36740
|
]);
|
|
35528
36741
|
const gitattributesUpdated = await ensureGitattributesEntries(projectRoot, [
|
|
35529
36742
|
LESSONS_GITATTRIBUTES_ENTRY
|
|
35530
36743
|
]);
|
|
36744
|
+
const mergeDriver = ensureLessonsMergeDriver(projectRoot);
|
|
35531
36745
|
return {
|
|
35532
36746
|
created,
|
|
35533
36747
|
updated,
|
|
@@ -35535,7 +36749,9 @@ async function scaffoldLessons(projectRoot) {
|
|
|
35535
36749
|
rootRuleUpdated,
|
|
35536
36750
|
gitignoreUpdated,
|
|
35537
36751
|
gitattributesUpdated,
|
|
35538
|
-
recallHookInjected
|
|
36752
|
+
recallHookInjected,
|
|
36753
|
+
mergeDriver,
|
|
36754
|
+
recallHookTeamHint: recallHookTeamHint(projectRoot)
|
|
35539
36755
|
};
|
|
35540
36756
|
}
|
|
35541
36757
|
function seedLessonsSkill(projectRoot, created, updated, skipped) {
|