agentsmesh 0.40.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 +75 -0
- package/README.md +1 -1
- package/dist/canonical.js +299 -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 +771 -276
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1733 -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 +2091 -613
- 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 +265 -219
- package/dist/targets.js.map +1 -1
- package/package.json +3 -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
|
}
|
|
@@ -3731,7 +3784,7 @@ var init_importer = __esm({
|
|
|
3731
3784
|
}
|
|
3732
3785
|
});
|
|
3733
3786
|
function globFilter(files, pattern) {
|
|
3734
|
-
const isMatch =
|
|
3787
|
+
const isMatch = picomatch(pattern, OPTIONS);
|
|
3735
3788
|
return files.filter((file) => isMatch(file));
|
|
3736
3789
|
}
|
|
3737
3790
|
var OPTIONS;
|
|
@@ -7096,10 +7149,6 @@ function generateAgents4(canonical) {
|
|
|
7096
7149
|
content: serializeAntigravityAgent(agent)
|
|
7097
7150
|
}));
|
|
7098
7151
|
}
|
|
7099
|
-
function generateIgnore3(canonical) {
|
|
7100
|
-
if (canonical.ignore.length === 0) return [];
|
|
7101
|
-
return [{ path: ANTIGRAVITY_IGNORE_FILE, content: canonical.ignore.join("\n") }];
|
|
7102
|
-
}
|
|
7103
7152
|
function renderAntigravityGlobalInstructions(canonical) {
|
|
7104
7153
|
const root = canonical.rules.find((rule) => rule.root);
|
|
7105
7154
|
const nonRootRules = canonical.rules.filter((rule) => {
|
|
@@ -7114,7 +7163,7 @@ function generateHooks2(canonical) {
|
|
|
7114
7163
|
if (Object.keys(hooks).length === 0) return [];
|
|
7115
7164
|
return [{ path: ANTIGRAVITY_HOOKS_FILE, content: JSON.stringify(hooks, null, 2) }];
|
|
7116
7165
|
}
|
|
7117
|
-
var generatePermissions3;
|
|
7166
|
+
var generateIgnore3, generatePermissions3;
|
|
7118
7167
|
var init_generator4 = __esm({
|
|
7119
7168
|
"src/targets/antigravity/generator.ts"() {
|
|
7120
7169
|
init_no_outputs();
|
|
@@ -7123,6 +7172,8 @@ var init_generator4 = __esm({
|
|
|
7123
7172
|
init_hooks_format2();
|
|
7124
7173
|
init_agents_format();
|
|
7125
7174
|
init_constants5();
|
|
7175
|
+
init_ignore_output();
|
|
7176
|
+
generateIgnore3 = ignoreOutput(ANTIGRAVITY_IGNORE_FILE);
|
|
7126
7177
|
generatePermissions3 = NO_OUTPUTS;
|
|
7127
7178
|
}
|
|
7128
7179
|
});
|
|
@@ -8716,16 +8767,14 @@ function generateHooks3(canonical) {
|
|
|
8716
8767
|
const content = JSON.stringify({ hooks: claudeHooks }, null, 2);
|
|
8717
8768
|
return [{ path: CLAUDE_SETTINGS, content }];
|
|
8718
8769
|
}
|
|
8719
|
-
|
|
8720
|
-
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
8721
|
-
const content = canonical.ignore.join("\n");
|
|
8722
|
-
return [{ path: CLAUDE_IGNORE, content }];
|
|
8723
|
-
}
|
|
8770
|
+
var generateIgnore5;
|
|
8724
8771
|
var init_generator6 = __esm({
|
|
8725
8772
|
"src/targets/claude-code/generator.ts"() {
|
|
8726
8773
|
init_markdown();
|
|
8727
8774
|
init_constants7();
|
|
8728
8775
|
init_hooks_format2();
|
|
8776
|
+
init_ignore_output();
|
|
8777
|
+
generateIgnore5 = ignoreOutput(CLAUDE_IGNORE);
|
|
8729
8778
|
}
|
|
8730
8779
|
});
|
|
8731
8780
|
function ruleSectionTitle(rule) {
|
|
@@ -10544,11 +10593,7 @@ function generateMcp5(canonical) {
|
|
|
10544
10593
|
if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
|
|
10545
10594
|
return [{ path: CODEBUFF_MCP_FILE, content: serializeCodebuffMcp(canonical.mcp) }];
|
|
10546
10595
|
}
|
|
10547
|
-
|
|
10548
|
-
if (canonical.ignore.length === 0) return [];
|
|
10549
|
-
return [{ path: CODEBUFF_IGNORE_FILE, content: canonical.ignore.join("\n") }];
|
|
10550
|
-
}
|
|
10551
|
-
var generateAgents8, generateHooks5, generatePermissions6;
|
|
10596
|
+
var generateIgnore7, generateAgents8, generateHooks5, generatePermissions6;
|
|
10552
10597
|
var init_generator8 = __esm({
|
|
10553
10598
|
"src/targets/codebuff/generator.ts"() {
|
|
10554
10599
|
init_no_outputs();
|
|
@@ -10558,6 +10603,8 @@ var init_generator8 = __esm({
|
|
|
10558
10603
|
init_nested_rules();
|
|
10559
10604
|
init_mcp_format2();
|
|
10560
10605
|
init_constants9();
|
|
10606
|
+
init_ignore_output();
|
|
10607
|
+
generateIgnore7 = ignoreOutput(CODEBUFF_IGNORE_FILE);
|
|
10561
10608
|
generateAgents8 = NO_OUTPUTS;
|
|
10562
10609
|
generateHooks5 = NO_OUTPUTS;
|
|
10563
10610
|
generatePermissions6 = NO_OUTPUTS;
|
|
@@ -13070,53 +13117,37 @@ var init_mcp_generator = __esm({
|
|
|
13070
13117
|
}
|
|
13071
13118
|
});
|
|
13072
13119
|
|
|
13073
|
-
// src/targets/copilot/hook-entry.ts
|
|
13074
|
-
function hasHookCommand2(entry) {
|
|
13075
|
-
return hasHookCommand(entry);
|
|
13076
|
-
}
|
|
13077
|
-
var init_hook_entry = __esm({
|
|
13078
|
-
"src/targets/copilot/hook-entry.ts"() {
|
|
13079
|
-
init_hook_command();
|
|
13080
|
-
}
|
|
13081
|
-
});
|
|
13082
|
-
|
|
13083
13120
|
// src/targets/copilot/hook-format.ts
|
|
13084
|
-
function
|
|
13085
|
-
|
|
13086
|
-
|
|
13087
|
-
|
|
13088
|
-
|
|
13089
|
-
|
|
13090
|
-
|
|
13091
|
-
|
|
13092
|
-
|
|
13093
|
-
|
|
13094
|
-
|
|
13095
|
-
|
|
13096
|
-
}
|
|
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`;
|
|
13097
13133
|
}
|
|
13098
13134
|
function buildCopilotHooksObject(hooks) {
|
|
13099
|
-
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
(entry) => typeof entry === "object" && entry !== null && hasHookCommand2(entry)
|
|
13106
|
-
).map((entry, index) => {
|
|
13107
|
-
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) => {
|
|
13108
13141
|
const hook = {
|
|
13109
13142
|
type: "command",
|
|
13110
|
-
bash: `./scripts/${
|
|
13143
|
+
bash: `./scripts/${wrapperScriptName(event, index)}`
|
|
13111
13144
|
};
|
|
13112
13145
|
if (entry.matcher && entry.matcher !== "*") hook.matcher = entry.matcher;
|
|
13113
13146
|
if (entry.timeout !== void 0) hook.timeoutSec = Math.ceil(entry.timeout / 1e3);
|
|
13114
13147
|
return hook;
|
|
13115
|
-
})
|
|
13116
|
-
|
|
13117
|
-
})
|
|
13148
|
+
})
|
|
13149
|
+
])
|
|
13118
13150
|
);
|
|
13119
|
-
return Object.keys(result2).length > 0 ? result2 : null;
|
|
13120
13151
|
}
|
|
13121
13152
|
function generateHooks8(canonical) {
|
|
13122
13153
|
const hooks = buildCopilotHooksObject(canonical.hooks);
|
|
@@ -13128,10 +13159,24 @@ function generateHooks8(canonical) {
|
|
|
13128
13159
|
}
|
|
13129
13160
|
];
|
|
13130
13161
|
}
|
|
13162
|
+
var COPILOT_HOOK_CONTEXT_EVENTS, CANONICAL_TO_COPILOT;
|
|
13131
13163
|
var init_hook_format = __esm({
|
|
13132
13164
|
"src/targets/copilot/hook-format.ts"() {
|
|
13133
13165
|
init_constants32();
|
|
13134
|
-
|
|
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
|
+
]);
|
|
13135
13180
|
}
|
|
13136
13181
|
});
|
|
13137
13182
|
function ruleSlug2(source) {
|
|
@@ -13237,18 +13282,7 @@ var init_generator12 = __esm({
|
|
|
13237
13282
|
}
|
|
13238
13283
|
});
|
|
13239
13284
|
function mapCopilotHookEvent(event) {
|
|
13240
|
-
|
|
13241
|
-
case "preToolUse":
|
|
13242
|
-
return "PreToolUse";
|
|
13243
|
-
case "postToolUse":
|
|
13244
|
-
return "PostToolUse";
|
|
13245
|
-
case "notification":
|
|
13246
|
-
return "Notification";
|
|
13247
|
-
case "userPromptSubmitted":
|
|
13248
|
-
return "UserPromptSubmit";
|
|
13249
|
-
default:
|
|
13250
|
-
return null;
|
|
13251
|
-
}
|
|
13285
|
+
return COPILOT_TO_CANONICAL.get(event) ?? null;
|
|
13252
13286
|
}
|
|
13253
13287
|
function extractMatcher(comment) {
|
|
13254
13288
|
if (typeof comment !== "string") return "*";
|
|
@@ -13336,11 +13370,16 @@ async function importHooks(projectRoot, results, options = {}) {
|
|
|
13336
13370
|
feature: "hooks"
|
|
13337
13371
|
});
|
|
13338
13372
|
}
|
|
13373
|
+
var COPILOT_TO_CANONICAL;
|
|
13339
13374
|
var init_hook_parser = __esm({
|
|
13340
13375
|
"src/targets/copilot/hook-parser.ts"() {
|
|
13341
13376
|
init_canonical_paths();
|
|
13342
13377
|
init_fs();
|
|
13343
13378
|
init_constants32();
|
|
13379
|
+
init_hook_format();
|
|
13380
|
+
COPILOT_TO_CANONICAL = new Map(
|
|
13381
|
+
[...CANONICAL_TO_COPILOT].map(([canonical, copilot]) => [copilot, canonical])
|
|
13382
|
+
);
|
|
13344
13383
|
}
|
|
13345
13384
|
});
|
|
13346
13385
|
async function importSkills2(projectRoot, results, normalize, skillsDirRel = COPILOT_SKILLS_DIR) {
|
|
@@ -13577,7 +13616,7 @@ function lintCommands4(canonical) {
|
|
|
13577
13616
|
}
|
|
13578
13617
|
function lintHooks9(canonical) {
|
|
13579
13618
|
if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
|
|
13580
|
-
const supported = [
|
|
13619
|
+
const supported = [...CANONICAL_TO_COPILOT.keys()];
|
|
13581
13620
|
const diagnostics = unsupportedHookEventNames(canonical.hooks, supported).map(
|
|
13582
13621
|
(event) => createUnsupportedHookWarning(event, "copilot", supported, {
|
|
13583
13622
|
unsupportedBy: "Copilot hooks"
|
|
@@ -13600,11 +13639,9 @@ function lintHooks9(canonical) {
|
|
|
13600
13639
|
var init_lint10 = __esm({
|
|
13601
13640
|
"src/targets/copilot/lint.ts"() {
|
|
13602
13641
|
init_helpers();
|
|
13642
|
+
init_hook_format();
|
|
13603
13643
|
}
|
|
13604
13644
|
});
|
|
13605
|
-
function safePhaseName(phase) {
|
|
13606
|
-
return phase.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase();
|
|
13607
|
-
}
|
|
13608
13645
|
function toRepoRelative(projectRoot, sourcePath) {
|
|
13609
13646
|
const repoRelative = relative(projectRoot, sourcePath).replace(/\\/g, "/");
|
|
13610
13647
|
if (!repoRelative || repoRelative.startsWith("../")) return null;
|
|
@@ -13632,9 +13669,6 @@ async function buildAssetOutput(projectRoot, command, hooksDirRel) {
|
|
|
13632
13669
|
rewrittenCommand: rewriteWrapperCommand(command, repoRelative)
|
|
13633
13670
|
};
|
|
13634
13671
|
}
|
|
13635
|
-
function wrapperPath(event, index, hooksDirRel) {
|
|
13636
|
-
return `${hooksDirRel}/scripts/${safePhaseName(event)}-${index}.sh`;
|
|
13637
|
-
}
|
|
13638
13672
|
function safeShellLine2(value) {
|
|
13639
13673
|
return value.replace(/[\r\n]+/g, " ");
|
|
13640
13674
|
}
|
|
@@ -13649,15 +13683,13 @@ function buildWrapper(command, matcher) {
|
|
|
13649
13683
|
].join("\n");
|
|
13650
13684
|
}
|
|
13651
13685
|
async function addHookScriptAssets(projectRoot, canonical, outputs, hooksDirRel = COPILOT_HOOKS_DIR) {
|
|
13652
|
-
|
|
13686
|
+
const groups = copilotHookGroups(canonical.hooks);
|
|
13687
|
+
if (groups.length === 0) return outputs;
|
|
13653
13688
|
const wrapperOutputs = [];
|
|
13654
13689
|
const assetOutputs = /* @__PURE__ */ new Map();
|
|
13655
|
-
for (const
|
|
13656
|
-
|
|
13657
|
-
|
|
13658
|
-
for (const entry of entries) {
|
|
13659
|
-
if (!hasHookCommand2(entry)) continue;
|
|
13660
|
-
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)}`;
|
|
13661
13693
|
let command = entry.command;
|
|
13662
13694
|
const asset = await buildAssetOutput(projectRoot, entry.command, hooksDirRel);
|
|
13663
13695
|
if (asset) {
|
|
@@ -13671,7 +13703,6 @@ async function addHookScriptAssets(projectRoot, canonical, outputs, hooksDirRel
|
|
|
13671
13703
|
'set -eu\nHOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"\n'
|
|
13672
13704
|
);
|
|
13673
13705
|
wrapperOutputs.push({ path: scriptPath, content: wrapper });
|
|
13674
|
-
index++;
|
|
13675
13706
|
}
|
|
13676
13707
|
}
|
|
13677
13708
|
return [...outputs, ...wrapperOutputs, ...assetOutputs.values()];
|
|
@@ -13681,7 +13712,7 @@ var init_hook_assets = __esm({
|
|
|
13681
13712
|
"src/targets/copilot/hook-assets.ts"() {
|
|
13682
13713
|
init_fs();
|
|
13683
13714
|
init_constants32();
|
|
13684
|
-
|
|
13715
|
+
init_hook_format();
|
|
13685
13716
|
SCRIPT_PREFIX_RE = /^(?<prefix>\s*(?:(?:bash|sh|zsh)\s+)?)["']?(?<path>(?:\.\.\/|\.\/|[^/\s"'`]+\/)[^\s"'`]+)["']?(?<suffix>(?:\s.*)?)$/;
|
|
13686
13717
|
}
|
|
13687
13718
|
});
|
|
@@ -13999,6 +14030,7 @@ var init_copilot2 = __esm({
|
|
|
13999
14030
|
init_command_prompt();
|
|
14000
14031
|
init_lint10();
|
|
14001
14032
|
init_hook_assets();
|
|
14033
|
+
init_hook_format();
|
|
14002
14034
|
init_scope_extras2();
|
|
14003
14035
|
init_importer_spec2();
|
|
14004
14036
|
init_capabilities6();
|
|
@@ -14129,6 +14161,7 @@ var init_copilot2 = __esm({
|
|
|
14129
14161
|
permissions: lintPermissions7
|
|
14130
14162
|
},
|
|
14131
14163
|
postProcessHookOutputs: async (projectRoot, canonical, outputs) => addHookScriptAssets(projectRoot, canonical, [...outputs]),
|
|
14164
|
+
hookContextEvents: COPILOT_HOOK_CONTEXT_EVENTS,
|
|
14132
14165
|
mergeGeneratedOutputContent: mergeCopilotMcpJson,
|
|
14133
14166
|
project: project7,
|
|
14134
14167
|
globalSupport: {
|
|
@@ -14216,11 +14249,6 @@ function generatePermissions8(canonical) {
|
|
|
14216
14249
|
});
|
|
14217
14250
|
return [{ path: CRUSH_CONFIG_FILE, content: JSON.stringify(crushConfig, null, 2) }];
|
|
14218
14251
|
}
|
|
14219
|
-
function generateIgnore9(canonical) {
|
|
14220
|
-
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
14221
|
-
const content = canonical.ignore.join("\n");
|
|
14222
|
-
return [{ path: CRUSH_IGNORE, content }];
|
|
14223
|
-
}
|
|
14224
14252
|
function buildCrushHooksFromCanonical(canonical) {
|
|
14225
14253
|
if (!canonical.hooks) return {};
|
|
14226
14254
|
const result2 = {};
|
|
@@ -14238,7 +14266,7 @@ function buildCrushHooksFromCanonical(canonical) {
|
|
|
14238
14266
|
}
|
|
14239
14267
|
return result2;
|
|
14240
14268
|
}
|
|
14241
|
-
var generateRules12;
|
|
14269
|
+
var generateRules12, generateIgnore9;
|
|
14242
14270
|
var init_generator13 = __esm({
|
|
14243
14271
|
"src/targets/crush/generator.ts"() {
|
|
14244
14272
|
init_managed_blocks();
|
|
@@ -14247,7 +14275,9 @@ var init_generator13 = __esm({
|
|
|
14247
14275
|
init_command_skill();
|
|
14248
14276
|
init_constants12();
|
|
14249
14277
|
init_config_format();
|
|
14278
|
+
init_ignore_output();
|
|
14250
14279
|
generateRules12 = (canonical) => embeddedRootRule(canonical, CRUSH_TARGET, CRUSH_ROOT_FILE);
|
|
14280
|
+
generateIgnore9 = ignoreOutput(CRUSH_IGNORE);
|
|
14251
14281
|
}
|
|
14252
14282
|
});
|
|
14253
14283
|
async function importFromCrush(projectRoot, options = {}) {
|
|
@@ -14783,7 +14813,7 @@ function cursorHooksToCanonical(hooks) {
|
|
|
14783
14813
|
}
|
|
14784
14814
|
return result2;
|
|
14785
14815
|
}
|
|
14786
|
-
var CANONICAL_TO_CURSOR, CURSOR_TO_CANONICAL;
|
|
14816
|
+
var CANONICAL_TO_CURSOR, CURSOR_HOOK_CONTEXT_EVENTS, CURSOR_TO_CANONICAL;
|
|
14787
14817
|
var init_hook_format2 = __esm({
|
|
14788
14818
|
"src/targets/cursor/hook-format.ts"() {
|
|
14789
14819
|
init_hook_types();
|
|
@@ -14791,6 +14821,7 @@ var init_hook_format2 = __esm({
|
|
|
14791
14821
|
CANONICAL_TO_CURSOR = {
|
|
14792
14822
|
PreToolUse: "preToolUse",
|
|
14793
14823
|
PostToolUse: "postToolUse",
|
|
14824
|
+
PostToolUseFailure: "postToolUseFailure",
|
|
14794
14825
|
UserPromptSubmit: "beforeSubmitPrompt",
|
|
14795
14826
|
SubagentStart: "subagentStart",
|
|
14796
14827
|
SubagentStop: "subagentStop",
|
|
@@ -14799,6 +14830,11 @@ var init_hook_format2 = __esm({
|
|
|
14799
14830
|
SessionEnd: "sessionEnd",
|
|
14800
14831
|
PreCompact: "preCompact"
|
|
14801
14832
|
};
|
|
14833
|
+
CURSOR_HOOK_CONTEXT_EVENTS = [
|
|
14834
|
+
"SessionStart",
|
|
14835
|
+
"PostToolUse",
|
|
14836
|
+
"PostToolUseFailure"
|
|
14837
|
+
];
|
|
14802
14838
|
CURSOR_TO_CANONICAL = new Map(
|
|
14803
14839
|
Object.entries(CANONICAL_TO_CURSOR).map(([canonical, cursor]) => [cursor, canonical])
|
|
14804
14840
|
);
|
|
@@ -14807,7 +14843,7 @@ var init_hook_format2 = __esm({
|
|
|
14807
14843
|
|
|
14808
14844
|
// src/targets/cursor/generator/hooks.ts
|
|
14809
14845
|
function generateHooks10(canonical) {
|
|
14810
|
-
if (!canonical.hooks
|
|
14846
|
+
if (!canonical.hooks) return [];
|
|
14811
14847
|
const cursorHooks = toCursorHooks(canonical.hooks);
|
|
14812
14848
|
if (Object.keys(cursorHooks).length === 0) return [];
|
|
14813
14849
|
const content = JSON.stringify({ version: 1, hooks: cursorHooks }, null, 2);
|
|
@@ -14821,14 +14857,12 @@ var init_hooks3 = __esm({
|
|
|
14821
14857
|
});
|
|
14822
14858
|
|
|
14823
14859
|
// src/targets/cursor/generator/ignore.ts
|
|
14824
|
-
|
|
14825
|
-
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
14826
|
-
const content = canonical.ignore.join("\n");
|
|
14827
|
-
return [{ path: CURSOR_IGNORE, content }];
|
|
14828
|
-
}
|
|
14860
|
+
var generateIgnore10;
|
|
14829
14861
|
var init_ignore = __esm({
|
|
14830
14862
|
"src/targets/cursor/generator/ignore.ts"() {
|
|
14831
14863
|
init_constants13();
|
|
14864
|
+
init_ignore_output();
|
|
14865
|
+
generateIgnore10 = ignoreOutput(CURSOR_IGNORE);
|
|
14832
14866
|
}
|
|
14833
14867
|
});
|
|
14834
14868
|
|
|
@@ -15524,6 +15558,7 @@ var init_cursor2 = __esm({
|
|
|
15524
15558
|
init_linter13();
|
|
15525
15559
|
init_import_map_builders();
|
|
15526
15560
|
init_lint12();
|
|
15561
|
+
init_hook_format2();
|
|
15527
15562
|
target13 = {
|
|
15528
15563
|
name: "cursor",
|
|
15529
15564
|
primaryRootInstructionPath: CURSOR_GENERAL_RULE,
|
|
@@ -15645,6 +15680,7 @@ var init_cursor2 = __esm({
|
|
|
15645
15680
|
permissions: "native"
|
|
15646
15681
|
},
|
|
15647
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,
|
|
15648
15684
|
lintRules: lintRules13,
|
|
15649
15685
|
lint: {
|
|
15650
15686
|
commands: lintCommands6,
|
|
@@ -16906,25 +16942,127 @@ var init_skills3 = __esm({
|
|
|
16906
16942
|
}
|
|
16907
16943
|
});
|
|
16908
16944
|
|
|
16909
|
-
// src/targets/gemini-cli/
|
|
16910
|
-
function
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
|
|
16916
|
-
|
|
16917
|
-
|
|
16918
|
-
|
|
16919
|
-
|
|
16920
|
-
|
|
16921
|
-
|
|
16922
|
-
|
|
16923
|
-
|
|
16924
|
-
|
|
16925
|
-
|
|
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)];
|
|
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
|
+
}
|
|
16926
17055
|
}
|
|
17056
|
+
return Object.keys(result2).length > 0 ? result2 : null;
|
|
16927
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
|
|
16928
17066
|
function generateGeminiSettingsFiles(canonical, enabledFeatures) {
|
|
16929
17067
|
const settings = {};
|
|
16930
17068
|
let hasAnyNativeSettings = false;
|
|
@@ -16936,29 +17074,10 @@ function generateGeminiSettingsFiles(canonical, enabledFeatures) {
|
|
|
16936
17074
|
settings.experimental = { enableAgents: true };
|
|
16937
17075
|
hasAnyNativeSettings = true;
|
|
16938
17076
|
}
|
|
16939
|
-
|
|
16940
|
-
|
|
16941
|
-
|
|
16942
|
-
|
|
16943
|
-
const mappedEntries = entries.filter(
|
|
16944
|
-
(entry) => typeof entry === "object" && entry !== null && hasHookCommand(entry)
|
|
16945
|
-
).map((entry, index) => ({
|
|
16946
|
-
matcher: entry.matcher,
|
|
16947
|
-
hooks: [
|
|
16948
|
-
{
|
|
16949
|
-
name: `${mappedEvent}-${index + 1}`,
|
|
16950
|
-
type: "command",
|
|
16951
|
-
command: getHookCommand(entry),
|
|
16952
|
-
timeout: entry.timeout
|
|
16953
|
-
}
|
|
16954
|
-
]
|
|
16955
|
-
}));
|
|
16956
|
-
return mappedEntries.length > 0 ? [[mappedEvent, mappedEntries]] : [];
|
|
16957
|
-
});
|
|
16958
|
-
if (hookEntries.length > 0) {
|
|
16959
|
-
settings.hooks = Object.fromEntries(hookEntries);
|
|
16960
|
-
hasAnyNativeSettings = true;
|
|
16961
|
-
}
|
|
17077
|
+
const hooks = enabledFeatures.has("hooks") ? buildGeminiHooks(canonical.hooks) : null;
|
|
17078
|
+
if (hooks) {
|
|
17079
|
+
settings.hooks = hooks;
|
|
17080
|
+
hasAnyNativeSettings = true;
|
|
16962
17081
|
}
|
|
16963
17082
|
if (hasAnyNativeSettings) {
|
|
16964
17083
|
settings.context = { fileName: [GEMINI_ROOT, GEMINI_COMPAT_AGENTS] };
|
|
@@ -16968,19 +17087,18 @@ function generateGeminiSettingsFiles(canonical, enabledFeatures) {
|
|
|
16968
17087
|
}
|
|
16969
17088
|
var init_settings2 = __esm({
|
|
16970
17089
|
"src/targets/gemini-cli/generator/settings.ts"() {
|
|
16971
|
-
init_hook_command();
|
|
16972
17090
|
init_constants33();
|
|
17091
|
+
init_hooks4();
|
|
16973
17092
|
}
|
|
16974
17093
|
});
|
|
16975
17094
|
|
|
16976
17095
|
// src/targets/gemini-cli/generator/ignore.ts
|
|
16977
|
-
|
|
16978
|
-
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
16979
|
-
return [{ path: GEMINI_IGNORE, content: canonical.ignore.join("\n") }];
|
|
16980
|
-
}
|
|
17096
|
+
var generateIgnore13;
|
|
16981
17097
|
var init_ignore2 = __esm({
|
|
16982
17098
|
"src/targets/gemini-cli/generator/ignore.ts"() {
|
|
16983
17099
|
init_constants33();
|
|
17100
|
+
init_ignore_output();
|
|
17101
|
+
generateIgnore13 = ignoreOutput(GEMINI_IGNORE);
|
|
16984
17102
|
}
|
|
16985
17103
|
});
|
|
16986
17104
|
|
|
@@ -17259,27 +17377,6 @@ var init_layout6 = __esm({
|
|
|
17259
17377
|
};
|
|
17260
17378
|
}
|
|
17261
17379
|
});
|
|
17262
|
-
function mapGeminiHookEvent(event) {
|
|
17263
|
-
switch (event) {
|
|
17264
|
-
case "BeforeTool":
|
|
17265
|
-
case "preToolUse":
|
|
17266
|
-
return "PreToolUse";
|
|
17267
|
-
case "AfterTool":
|
|
17268
|
-
case "postToolUse":
|
|
17269
|
-
return "PostToolUse";
|
|
17270
|
-
case "Notification":
|
|
17271
|
-
case "notification":
|
|
17272
|
-
return "Notification";
|
|
17273
|
-
case "BeforeAgent":
|
|
17274
|
-
return "SubagentStart";
|
|
17275
|
-
case "AfterAgent":
|
|
17276
|
-
return "SubagentStop";
|
|
17277
|
-
case "SessionStart":
|
|
17278
|
-
return "SessionStart";
|
|
17279
|
-
default:
|
|
17280
|
-
return null;
|
|
17281
|
-
}
|
|
17282
|
-
}
|
|
17283
17380
|
function parseFlexibleFrontmatter(content) {
|
|
17284
17381
|
const yamlOpen = content.indexOf("---");
|
|
17285
17382
|
const tomlOpen = content.indexOf("+++");
|
|
@@ -17353,7 +17450,7 @@ async function importGeminiSettings(projectRoot, results) {
|
|
|
17353
17450
|
(entry) => entry.hooks.filter(
|
|
17354
17451
|
(hook) => hook !== null && typeof hook === "object" && hasHookCommand(hook)
|
|
17355
17452
|
).map((hook) => ({
|
|
17356
|
-
matcher: entry.matcher,
|
|
17453
|
+
matcher: fromGeminiMatcher(event, entry.matcher),
|
|
17357
17454
|
command: getHookCommand(hook),
|
|
17358
17455
|
type: "command",
|
|
17359
17456
|
timeout: typeof hook.timeout === "number" ? hook.timeout : void 0
|
|
@@ -17363,7 +17460,7 @@ async function importGeminiSettings(projectRoot, results) {
|
|
|
17363
17460
|
const legacyMapped = value.filter(
|
|
17364
17461
|
(entry) => entry !== null && typeof entry === "object" && typeof entry.matcher === "string" && hasHookCommand(entry)
|
|
17365
17462
|
).map((entry) => ({
|
|
17366
|
-
matcher: entry.matcher,
|
|
17463
|
+
matcher: fromGeminiMatcher(event, entry.matcher),
|
|
17367
17464
|
command: getHookCommand(entry),
|
|
17368
17465
|
type: "command"
|
|
17369
17466
|
}));
|
|
@@ -17392,7 +17489,7 @@ var init_format_helpers_settings = __esm({
|
|
|
17392
17489
|
init_hook_command();
|
|
17393
17490
|
init_fs();
|
|
17394
17491
|
init_constants33();
|
|
17395
|
-
|
|
17492
|
+
init_hook_map();
|
|
17396
17493
|
}
|
|
17397
17494
|
});
|
|
17398
17495
|
async function importGeminiIgnore(projectRoot, results) {
|
|
@@ -17888,14 +17985,7 @@ function lintPermissions10(canonical, options) {
|
|
|
17888
17985
|
}
|
|
17889
17986
|
function lintHooks12(canonical) {
|
|
17890
17987
|
if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
|
|
17891
|
-
const supported = [
|
|
17892
|
-
"PreToolUse",
|
|
17893
|
-
"PostToolUse",
|
|
17894
|
-
"Notification",
|
|
17895
|
-
"SubagentStart",
|
|
17896
|
-
"SubagentStop",
|
|
17897
|
-
"SessionStart"
|
|
17898
|
-
];
|
|
17988
|
+
const supported = [...CANONICAL_TO_GEMINI.keys()];
|
|
17899
17989
|
return unsupportedHookEventNames(canonical.hooks, supported).map(
|
|
17900
17990
|
(event) => createUnsupportedHookWarning(event, "gemini-cli", supported)
|
|
17901
17991
|
);
|
|
@@ -17903,6 +17993,7 @@ function lintHooks12(canonical) {
|
|
|
17903
17993
|
var init_lint15 = __esm({
|
|
17904
17994
|
"src/targets/gemini-cli/lint.ts"() {
|
|
17905
17995
|
init_helpers();
|
|
17996
|
+
init_hook_map();
|
|
17906
17997
|
}
|
|
17907
17998
|
});
|
|
17908
17999
|
|
|
@@ -17939,6 +18030,7 @@ var init_gemini_cli2 = __esm({
|
|
|
17939
18030
|
init_import_map_builders();
|
|
17940
18031
|
init_lint15();
|
|
17941
18032
|
init_scoped_settings_emit();
|
|
18033
|
+
init_hook_map();
|
|
17942
18034
|
init_settings();
|
|
17943
18035
|
target16 = {
|
|
17944
18036
|
name: "gemini-cli",
|
|
@@ -17989,6 +18081,7 @@ var init_gemini_cli2 = __esm({
|
|
|
17989
18081
|
permissions: lintPermissions10
|
|
17990
18082
|
},
|
|
17991
18083
|
emitScopedSettings: emitScopedGeminiSettings,
|
|
18084
|
+
hookContextEvents: GEMINI_HOOK_CONTEXT_EVENTS,
|
|
17992
18085
|
mergeGeneratedOutputContent(existing, pending, newContent, resolvedPath) {
|
|
17993
18086
|
const base = pending?.content ?? existing;
|
|
17994
18087
|
if (base !== null && resolvedPath === GEMINI_SETTINGS) {
|
|
@@ -18081,14 +18174,10 @@ function generateAgents17(canonical) {
|
|
|
18081
18174
|
content: serializeProjectedAgentSkill(agent)
|
|
18082
18175
|
}));
|
|
18083
18176
|
}
|
|
18084
|
-
function generateIgnore14(canonical) {
|
|
18085
|
-
if (canonical.ignore.length === 0) return [];
|
|
18086
|
-
return [{ path: GOOSE_IGNORE, content: canonical.ignore.join("\n") }];
|
|
18087
|
-
}
|
|
18088
18177
|
function generateHooks12(canonical) {
|
|
18089
18178
|
return buildWrappedCommandHooks(canonical, GOOSE_HOOKS_FILE);
|
|
18090
18179
|
}
|
|
18091
|
-
var generateRules17, generatePermissions12;
|
|
18180
|
+
var generateRules17, generateIgnore14, generatePermissions12;
|
|
18092
18181
|
var init_generator20 = __esm({
|
|
18093
18182
|
"src/targets/goose/generator.ts"() {
|
|
18094
18183
|
init_managed_blocks();
|
|
@@ -18098,7 +18187,9 @@ var init_generator20 = __esm({
|
|
|
18098
18187
|
init_command_skill();
|
|
18099
18188
|
init_wrapped_command_hooks();
|
|
18100
18189
|
init_constants16();
|
|
18190
|
+
init_ignore_output();
|
|
18101
18191
|
generateRules17 = (canonical) => embeddedRootRule(canonical, GOOSE_TARGET, GOOSE_ROOT_FILE);
|
|
18192
|
+
generateIgnore14 = ignoreOutput(GOOSE_IGNORE);
|
|
18102
18193
|
generatePermissions12 = NO_OUTPUTS;
|
|
18103
18194
|
}
|
|
18104
18195
|
});
|
|
@@ -19003,10 +19094,6 @@ function generateAgents18(canonical) {
|
|
|
19003
19094
|
};
|
|
19004
19095
|
});
|
|
19005
19096
|
}
|
|
19006
|
-
function generateIgnore16(canonical) {
|
|
19007
|
-
if (canonical.ignore.length === 0) return [];
|
|
19008
|
-
return [{ path: JUNIE_IGNORE, content: canonical.ignore.join("\n") }];
|
|
19009
|
-
}
|
|
19010
19097
|
function generateSkills17(canonical) {
|
|
19011
19098
|
return generateEmbeddedSkills(canonical, JUNIE_SKILLS_DIR);
|
|
19012
19099
|
}
|
|
@@ -19018,6 +19105,7 @@ function renderJunieGlobalInstructions(canonical) {
|
|
|
19018
19105
|
});
|
|
19019
19106
|
return appendEmbeddedRulesBlock(root?.body.trim() ?? "", nonRootRules);
|
|
19020
19107
|
}
|
|
19108
|
+
var generateIgnore16;
|
|
19021
19109
|
var init_generator22 = __esm({
|
|
19022
19110
|
"src/targets/junie/generator.ts"() {
|
|
19023
19111
|
init_mcp_servers();
|
|
@@ -19025,7 +19113,9 @@ var init_generator22 = __esm({
|
|
|
19025
19113
|
init_managed_blocks();
|
|
19026
19114
|
init_markdown();
|
|
19027
19115
|
init_constants18();
|
|
19116
|
+
init_ignore_output();
|
|
19028
19117
|
init_global_config2();
|
|
19118
|
+
generateIgnore16 = ignoreOutput(JUNIE_IGNORE);
|
|
19029
19119
|
}
|
|
19030
19120
|
});
|
|
19031
19121
|
|
|
@@ -19441,10 +19531,6 @@ function generateMcp15(canonical) {
|
|
|
19441
19531
|
}
|
|
19442
19532
|
];
|
|
19443
19533
|
}
|
|
19444
|
-
function generateIgnore17(canonical) {
|
|
19445
|
-
if (canonical.ignore.length === 0) return [];
|
|
19446
|
-
return [{ path: KILO_CODE_IGNORE, content: canonical.ignore.join("\n") }];
|
|
19447
|
-
}
|
|
19448
19534
|
function generatePermissions15(canonical) {
|
|
19449
19535
|
if (!canonical.permissions) return [];
|
|
19450
19536
|
const { allow, deny } = canonical.permissions;
|
|
@@ -19459,11 +19545,14 @@ function generatePermissions15(canonical) {
|
|
|
19459
19545
|
function generateSkills18(canonical) {
|
|
19460
19546
|
return generateEmbeddedSkills(canonical, KILO_CODE_SKILLS_DIR);
|
|
19461
19547
|
}
|
|
19548
|
+
var generateIgnore17;
|
|
19462
19549
|
var init_generator23 = __esm({
|
|
19463
19550
|
"src/targets/kilo-code/generator.ts"() {
|
|
19464
19551
|
init_embedded_skill();
|
|
19465
19552
|
init_markdown();
|
|
19466
19553
|
init_constants19();
|
|
19554
|
+
init_ignore_output();
|
|
19555
|
+
generateIgnore17 = ignoreOutput(KILO_CODE_IGNORE);
|
|
19467
19556
|
}
|
|
19468
19557
|
});
|
|
19469
19558
|
var kiloNonRootRuleMapper, kiloCommandMapper, kiloAgentMapper;
|
|
@@ -21208,11 +21297,7 @@ function buildKiroAgentOutputs(canonical, rules = []) {
|
|
|
21208
21297
|
function generateAgents21(canonical) {
|
|
21209
21298
|
return buildKiroAgentOutputs(canonical);
|
|
21210
21299
|
}
|
|
21211
|
-
|
|
21212
|
-
if (canonical.ignore.length === 0) return [];
|
|
21213
|
-
return [{ path: KIRO_IGNORE, content: canonical.ignore.join("\n") }];
|
|
21214
|
-
}
|
|
21215
|
-
var generatePermissions17;
|
|
21300
|
+
var generateIgnore18, generatePermissions17;
|
|
21216
21301
|
var init_generator25 = __esm({
|
|
21217
21302
|
"src/targets/kiro/generator.ts"() {
|
|
21218
21303
|
init_no_outputs();
|
|
@@ -21221,6 +21306,8 @@ var init_generator25 = __esm({
|
|
|
21221
21306
|
init_markdown();
|
|
21222
21307
|
init_hook_format3();
|
|
21223
21308
|
init_constants21();
|
|
21309
|
+
init_ignore_output();
|
|
21310
|
+
generateIgnore18 = ignoreOutput(KIRO_IGNORE);
|
|
21224
21311
|
generatePermissions17 = NO_OUTPUTS;
|
|
21225
21312
|
}
|
|
21226
21313
|
});
|
|
@@ -23863,10 +23950,6 @@ function generateMcp19(canonical) {
|
|
|
23863
23950
|
const content = JSON.stringify({ mcpServers: canonical.mcp.mcpServers }, null, 2);
|
|
23864
23951
|
return [{ path: QWEN_SETTINGS, content }];
|
|
23865
23952
|
}
|
|
23866
|
-
function generateIgnore21(canonical) {
|
|
23867
|
-
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
23868
|
-
return [{ path: QWEN_IGNORE, content: canonical.ignore.join("\n") }];
|
|
23869
|
-
}
|
|
23870
23953
|
function generateHooks18(canonical) {
|
|
23871
23954
|
if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
|
|
23872
23955
|
const hooks = buildClaudeHooksObjectFromCanonical(canonical);
|
|
@@ -23884,11 +23967,14 @@ function generatePermissions21(canonical) {
|
|
|
23884
23967
|
if (ask.length > 0) permissions.ask = ask;
|
|
23885
23968
|
return [{ path: QWEN_SETTINGS, content: JSON.stringify({ permissions }, null, 2) }];
|
|
23886
23969
|
}
|
|
23970
|
+
var generateIgnore21;
|
|
23887
23971
|
var init_generator29 = __esm({
|
|
23888
23972
|
"src/targets/qwen-code/generator.ts"() {
|
|
23889
23973
|
init_markdown();
|
|
23890
23974
|
init_hooks_format2();
|
|
23891
23975
|
init_constants25();
|
|
23976
|
+
init_ignore_output();
|
|
23977
|
+
generateIgnore21 = ignoreOutput(QWEN_IGNORE);
|
|
23892
23978
|
}
|
|
23893
23979
|
});
|
|
23894
23980
|
|
|
@@ -25586,10 +25672,6 @@ function generateMcp23(canonical) {
|
|
|
25586
25672
|
}
|
|
25587
25673
|
];
|
|
25588
25674
|
}
|
|
25589
|
-
function generateIgnore24(canonical) {
|
|
25590
|
-
if (canonical.ignore.length === 0) return [];
|
|
25591
|
-
return [{ path: TRAE_IGNORE, content: canonical.ignore.join("\n") }];
|
|
25592
|
-
}
|
|
25593
25675
|
function generateHooks20(canonical) {
|
|
25594
25676
|
if (!canonical.hooks) return [];
|
|
25595
25677
|
const hooks = {};
|
|
@@ -25610,12 +25692,15 @@ function generateHooks20(canonical) {
|
|
|
25610
25692
|
if (Object.keys(hooks).length === 0) return [];
|
|
25611
25693
|
return [{ path: TRAE_HOOKS_FILE, content: JSON.stringify({ version: 1, hooks }, null, 2) }];
|
|
25612
25694
|
}
|
|
25695
|
+
var generateIgnore24;
|
|
25613
25696
|
var init_generator33 = __esm({
|
|
25614
25697
|
"src/targets/trae/generator.ts"() {
|
|
25615
25698
|
init_embedded_skill();
|
|
25616
25699
|
init_markdown();
|
|
25617
25700
|
init_hook_command();
|
|
25618
25701
|
init_constants29();
|
|
25702
|
+
init_ignore_output();
|
|
25703
|
+
generateIgnore24 = ignoreOutput(TRAE_IGNORE);
|
|
25619
25704
|
}
|
|
25620
25705
|
});
|
|
25621
25706
|
|
|
@@ -26322,11 +26407,7 @@ function generateMcp24(canonical, ctx) {
|
|
|
26322
26407
|
const content = JSON.stringify({ mcpServers: canonical.mcp.mcpServers }, null, 2);
|
|
26323
26408
|
return [{ path, content }];
|
|
26324
26409
|
}
|
|
26325
|
-
|
|
26326
|
-
if (canonical.ignore.length === 0) return [];
|
|
26327
|
-
return [{ path: WARP_IGNORE_FILE, content: canonical.ignore.join("\n") }];
|
|
26328
|
-
}
|
|
26329
|
-
var generateRules31, generatePermissions24, generateHooks21;
|
|
26410
|
+
var generateRules31, generatePermissions24, generateHooks21, generateIgnore25;
|
|
26330
26411
|
var init_generator34 = __esm({
|
|
26331
26412
|
"src/targets/warp/generator.ts"() {
|
|
26332
26413
|
init_managed_blocks();
|
|
@@ -26335,9 +26416,11 @@ var init_generator34 = __esm({
|
|
|
26335
26416
|
init_projected_agent_skill();
|
|
26336
26417
|
init_command_skill();
|
|
26337
26418
|
init_constants30();
|
|
26419
|
+
init_ignore_output();
|
|
26338
26420
|
generateRules31 = (canonical) => embeddedRootRule(canonical, WARP_TARGET, WARP_ROOT_FILE);
|
|
26339
26421
|
generatePermissions24 = NO_OUTPUTS;
|
|
26340
26422
|
generateHooks21 = NO_OUTPUTS;
|
|
26423
|
+
generateIgnore25 = ignoreOutput(WARP_IGNORE_FILE);
|
|
26341
26424
|
}
|
|
26342
26425
|
});
|
|
26343
26426
|
|
|
@@ -26962,13 +27045,12 @@ var init_rules4 = __esm({
|
|
|
26962
27045
|
});
|
|
26963
27046
|
|
|
26964
27047
|
// src/targets/windsurf/generator/ignore.ts
|
|
26965
|
-
|
|
26966
|
-
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
26967
|
-
return [{ path: CODEIUM_IGNORE, content: canonical.ignore.join("\n") }];
|
|
26968
|
-
}
|
|
27048
|
+
var generateIgnore26;
|
|
26969
27049
|
var init_ignore3 = __esm({
|
|
26970
27050
|
"src/targets/windsurf/generator/ignore.ts"() {
|
|
26971
27051
|
init_constants34();
|
|
27052
|
+
init_ignore_output();
|
|
27053
|
+
generateIgnore26 = ignoreOutput(CODEIUM_IGNORE);
|
|
26972
27054
|
}
|
|
26973
27055
|
});
|
|
26974
27056
|
|
|
@@ -27034,7 +27116,7 @@ function canonicalHookEventName(event) {
|
|
|
27034
27116
|
if (KNOWN_CANONICAL_HOOK_EVENTS.includes(event)) return event;
|
|
27035
27117
|
return WINDSURF_TO_CANONICAL.get(event) ?? null;
|
|
27036
27118
|
}
|
|
27037
|
-
var KNOWN_CANONICAL_HOOK_EVENTS, WINDSURF_TO_CANONICAL;
|
|
27119
|
+
var KNOWN_CANONICAL_HOOK_EVENTS, WINDSURF_HOOK_CONTEXT_EVENTS, WINDSURF_TO_CANONICAL;
|
|
27038
27120
|
var init_hook_events = __esm({
|
|
27039
27121
|
"src/targets/windsurf/hook-events.ts"() {
|
|
27040
27122
|
init_hook_types();
|
|
@@ -27047,6 +27129,7 @@ var init_hook_events = __esm({
|
|
|
27047
27129
|
"SubagentStop",
|
|
27048
27130
|
...BEST_EFFORT_HOOK_EVENTS
|
|
27049
27131
|
];
|
|
27132
|
+
WINDSURF_HOOK_CONTEXT_EVENTS = [];
|
|
27050
27133
|
WINDSURF_TO_CANONICAL = new Map(
|
|
27051
27134
|
KNOWN_CANONICAL_HOOK_EVENTS.map((event) => [windsurfEventName(event), event])
|
|
27052
27135
|
);
|
|
@@ -27072,12 +27155,12 @@ function toWindsurfHooks(hooks) {
|
|
|
27072
27155
|
return result2;
|
|
27073
27156
|
}
|
|
27074
27157
|
function generateHooks22(canonical) {
|
|
27075
|
-
if (!canonical.hooks
|
|
27158
|
+
if (!canonical.hooks) return [];
|
|
27076
27159
|
const hooks = toWindsurfHooks(canonical.hooks);
|
|
27077
27160
|
if (Object.keys(hooks).length === 0) return [];
|
|
27078
27161
|
return [{ path: WINDSURF_HOOKS_FILE, content: JSON.stringify({ hooks }, null, 2) }];
|
|
27079
27162
|
}
|
|
27080
|
-
var
|
|
27163
|
+
var init_hooks5 = __esm({
|
|
27081
27164
|
"src/targets/windsurf/generator/hooks.ts"() {
|
|
27082
27165
|
init_hook_command();
|
|
27083
27166
|
init_constants34();
|
|
@@ -27129,7 +27212,7 @@ var init_generator35 = __esm({
|
|
|
27129
27212
|
init_workflows();
|
|
27130
27213
|
init_agents4();
|
|
27131
27214
|
init_mcp4();
|
|
27132
|
-
|
|
27215
|
+
init_hooks5();
|
|
27133
27216
|
init_skills4();
|
|
27134
27217
|
init_permissions6();
|
|
27135
27218
|
}
|
|
@@ -27248,7 +27331,7 @@ async function parseHooks(hooksPath, onParseError) {
|
|
|
27248
27331
|
return result2;
|
|
27249
27332
|
}
|
|
27250
27333
|
var VALID_TYPES;
|
|
27251
|
-
var
|
|
27334
|
+
var init_hooks6 = __esm({
|
|
27252
27335
|
"src/canonical/features/hooks.ts"() {
|
|
27253
27336
|
init_syntax_error();
|
|
27254
27337
|
init_fs();
|
|
@@ -27329,7 +27412,7 @@ var WILDCARD_MATCHER;
|
|
|
27329
27412
|
var init_importer_hooks2 = __esm({
|
|
27330
27413
|
"src/targets/windsurf/importer-hooks.ts"() {
|
|
27331
27414
|
init_canonical_paths();
|
|
27332
|
-
|
|
27415
|
+
init_hooks6();
|
|
27333
27416
|
init_fs();
|
|
27334
27417
|
init_constants34();
|
|
27335
27418
|
init_hook_events();
|
|
@@ -27630,6 +27713,7 @@ var init_windsurf2 = __esm({
|
|
|
27630
27713
|
init_merge14();
|
|
27631
27714
|
init_linter32();
|
|
27632
27715
|
init_lint31();
|
|
27716
|
+
init_hook_events();
|
|
27633
27717
|
init_import_map_builders();
|
|
27634
27718
|
init_conversions();
|
|
27635
27719
|
init_projected_agent_skill();
|
|
@@ -27761,6 +27845,7 @@ var init_windsurf2 = __esm({
|
|
|
27761
27845
|
},
|
|
27762
27846
|
emptyImportMessage: "No Windsurf config found (.windsurfrules, .windsurf/rules, .windsurfignore, or .codeiumignore).",
|
|
27763
27847
|
supportsConversion: { agents: true },
|
|
27848
|
+
hookContextEvents: WINDSURF_HOOK_CONTEXT_EVENTS,
|
|
27764
27849
|
lintRules: lintRules32,
|
|
27765
27850
|
lint: {
|
|
27766
27851
|
commands: lintCommands10,
|
|
@@ -28905,6 +28990,23 @@ var init_builtin_targets = __esm({
|
|
|
28905
28990
|
}
|
|
28906
28991
|
});
|
|
28907
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
|
+
|
|
28908
29010
|
// src/core/generate/engine.ts
|
|
28909
29011
|
init_builtin_targets();
|
|
28910
29012
|
init_registry();
|
|
@@ -29807,11 +29909,12 @@ async function generateHooksFeature(results, targets, canonical, projectRoot, sc
|
|
|
29807
29909
|
const gen = resolveTargetFeatureGenerator(target34, "hooks", config, scope) ?? getDescriptor(target34)?.generators.generateHooks;
|
|
29808
29910
|
if (!gen) continue;
|
|
29809
29911
|
const ctx = featureContext(target34, "hooks", scope);
|
|
29810
|
-
|
|
29912
|
+
const projected = withTargetRecallHooks(canonical, target34);
|
|
29913
|
+
let outputs = [...gen(projected, ctx)];
|
|
29811
29914
|
const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
|
|
29812
29915
|
const post = descriptor34?.postProcessHookOutputs;
|
|
29813
29916
|
if (post) {
|
|
29814
|
-
outputs = [...await post(projectRoot,
|
|
29917
|
+
outputs = [...await post(projectRoot, projected, outputs)];
|
|
29815
29918
|
}
|
|
29816
29919
|
const options = outputMergeOptions(target34);
|
|
29817
29920
|
for (const out2 of outputs) {
|
|
@@ -29824,7 +29927,7 @@ async function generateScopedSettingsFeature(results, targets, canonical, projec
|
|
|
29824
29927
|
const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
|
|
29825
29928
|
const emit = descriptor34?.emitScopedSettings;
|
|
29826
29929
|
if (!emit) continue;
|
|
29827
|
-
const outputs = emit(canonical, scope, enabledFeatures);
|
|
29930
|
+
const outputs = emit(withTargetRecallHooks(canonical, target34), scope, enabledFeatures);
|
|
29828
29931
|
if (outputs.length === 0) continue;
|
|
29829
29932
|
const options = outputMergeOptions(target34);
|
|
29830
29933
|
for (const out2 of outputs) {
|
|
@@ -29867,7 +29970,12 @@ async function generate(ctx) {
|
|
|
29867
29970
|
const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
|
|
29868
29971
|
const scopeExtras = descriptor34?.globalSupport?.scopeExtras;
|
|
29869
29972
|
if (scopeExtras) {
|
|
29870
|
-
const extras = await scopeExtras(
|
|
29973
|
+
const extras = await scopeExtras(
|
|
29974
|
+
withTargetRecallHooks(canonical, target34),
|
|
29975
|
+
projectRoot,
|
|
29976
|
+
scope,
|
|
29977
|
+
enabledFeatures
|
|
29978
|
+
);
|
|
29871
29979
|
await emitScopeExtras(results, target34, extras, projectRoot);
|
|
29872
29980
|
}
|
|
29873
29981
|
}
|
|
@@ -31174,7 +31282,7 @@ async function parsePermissions(permissionsPath, onParseError) {
|
|
|
31174
31282
|
}
|
|
31175
31283
|
|
|
31176
31284
|
// src/canonical/load/loader.ts
|
|
31177
|
-
|
|
31285
|
+
init_hooks6();
|
|
31178
31286
|
|
|
31179
31287
|
// src/canonical/features/ignore.ts
|
|
31180
31288
|
init_fs();
|
|
@@ -31223,10 +31331,10 @@ function ruleSlug4(r) {
|
|
|
31223
31331
|
function mergeByKey(base, overlay, key) {
|
|
31224
31332
|
return [...new Map([...base, ...overlay].map((item) => [key(item), item])).values()];
|
|
31225
31333
|
}
|
|
31226
|
-
function mergeCanonicalFiles(base, overlay) {
|
|
31334
|
+
function mergeCanonicalFiles(base, overlay, options = {}) {
|
|
31227
31335
|
const mcp = mergeMcp7(base.mcp, overlay.mcp);
|
|
31228
31336
|
const permissions = mergePermissions2(base.permissions, overlay.permissions);
|
|
31229
|
-
const hooks =
|
|
31337
|
+
const hooks = options.hooks === "combine" ? combineHooks(base.hooks, overlay.hooks) : overrideHooks(base.hooks, overlay.hooks);
|
|
31230
31338
|
const ignore = mergeUniqueStrings(base.ignore, overlay.ignore);
|
|
31231
31339
|
return {
|
|
31232
31340
|
rules: mergeByKey(base.rules, overlay.rules, ruleSlug4),
|
|
@@ -31265,17 +31373,31 @@ function mergeUniqueStrings(base, overlay) {
|
|
|
31265
31373
|
}
|
|
31266
31374
|
return merged;
|
|
31267
31375
|
}
|
|
31268
|
-
function
|
|
31376
|
+
function overrideHooks(base, overlay) {
|
|
31269
31377
|
if (!base && !overlay) return null;
|
|
31270
31378
|
const result2 = {};
|
|
31271
|
-
const
|
|
31272
|
-
for (const k of keys) {
|
|
31379
|
+
for (const k of hookEvents(base, overlay)) {
|
|
31273
31380
|
const o = overlay?.[k];
|
|
31274
|
-
|
|
31275
|
-
|
|
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)))];
|
|
31276
31392
|
}
|
|
31277
31393
|
return result2;
|
|
31278
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
|
+
}
|
|
31279
31401
|
|
|
31280
31402
|
// src/config/resolve/native-format-detector.ts
|
|
31281
31403
|
init_fs();
|
|
@@ -32046,7 +32168,7 @@ function gateExtendElevatedArtifacts(canonical, ext) {
|
|
|
32046
32168
|
});
|
|
32047
32169
|
}
|
|
32048
32170
|
init_mcp();
|
|
32049
|
-
|
|
32171
|
+
init_hooks6();
|
|
32050
32172
|
|
|
32051
32173
|
// src/install/pack/pack-reader.ts
|
|
32052
32174
|
init_fs();
|
|
@@ -32133,7 +32255,7 @@ async function loadPacksCanonical(abDir) {
|
|
|
32133
32255
|
const canonical = await loadPackCanonical(packDir);
|
|
32134
32256
|
const filtered = filterCanonicalByFeatures(canonical, meta.features);
|
|
32135
32257
|
const picked = applyExtendPick(filtered, meta.features, meta.pick, meta.name);
|
|
32136
|
-
merged = mergeCanonicalFiles(merged, picked);
|
|
32258
|
+
merged = mergeCanonicalFiles(merged, picked, { hooks: "combine" });
|
|
32137
32259
|
}
|
|
32138
32260
|
return merged;
|
|
32139
32261
|
}
|
|
@@ -32184,9 +32306,10 @@ async function loadCanonicalWithExtends(config, configDir, options = {}, canonic
|
|
|
32184
32306
|
merged = mergeCanonicalFiles(merged, picked);
|
|
32185
32307
|
}
|
|
32186
32308
|
const packsCanonical = await loadPacksCanonical(canonicalDir);
|
|
32187
|
-
merged = mergeCanonicalFiles(merged, packsCanonical);
|
|
32309
|
+
merged = mergeCanonicalFiles(merged, packsCanonical, { hooks: "combine" });
|
|
32188
32310
|
const localCanonical = await loadCanonicalFiles(canonicalDir);
|
|
32189
32311
|
merged = mergeCanonicalFiles(merged, localCanonical);
|
|
32312
|
+
merged = { ...merged, hooks: combineHooks(merged.hooks, packsCanonical.hooks) };
|
|
32190
32313
|
return { canonical: merged, resolvedExtends };
|
|
32191
32314
|
}
|
|
32192
32315
|
|
|
@@ -32465,6 +32588,9 @@ function lintRuleScopeInversion(input) {
|
|
|
32465
32588
|
}
|
|
32466
32589
|
return out2;
|
|
32467
32590
|
}
|
|
32591
|
+
|
|
32592
|
+
// src/lessons/graph-store.ts
|
|
32593
|
+
init_fs_text_encoding();
|
|
32468
32594
|
var CURRENT_GRAPH_VERSION = 2;
|
|
32469
32595
|
var VersionSchema = z.union([z.literal(1), z.literal(2)]);
|
|
32470
32596
|
var MAX_RULE_LENGTH = 2e3;
|
|
@@ -32508,25 +32634,54 @@ var LessonsGraphSchema = z.object({
|
|
|
32508
32634
|
function parseGraph(raw) {
|
|
32509
32635
|
return LessonsGraphSchema.parse(raw);
|
|
32510
32636
|
}
|
|
32637
|
+
function emptyGraph() {
|
|
32638
|
+
return { version: CURRENT_GRAPH_VERSION, lessons: {}, topics: {}, triggers: {} };
|
|
32639
|
+
}
|
|
32511
32640
|
|
|
32512
32641
|
// src/lessons/graph-store.ts
|
|
32513
|
-
var
|
|
32642
|
+
var LESSONS_GRAPH_PATH = ".agentsmesh/lessons/lessons.json";
|
|
32514
32643
|
function graphFilePath(projectRoot) {
|
|
32515
|
-
return resolve(projectRoot,
|
|
32644
|
+
return resolve(projectRoot, LESSONS_GRAPH_PATH);
|
|
32516
32645
|
}
|
|
32517
32646
|
function loadLessonsGraph(projectRoot) {
|
|
32518
32647
|
const raw = readFileSync(graphFilePath(projectRoot), "utf8");
|
|
32519
|
-
return parseGraph(JSON.parse(raw));
|
|
32648
|
+
return parseGraph(JSON.parse(stripBom(raw)));
|
|
32520
32649
|
}
|
|
32521
32650
|
function tryLoadLessonsGraph(projectRoot) {
|
|
32522
32651
|
if (!existsSync(graphFilePath(projectRoot))) return null;
|
|
32523
32652
|
return loadLessonsGraph(projectRoot);
|
|
32524
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
|
+
}
|
|
32525
32677
|
function saveLessonsGraph(projectRoot, graph) {
|
|
32526
32678
|
const path = graphFilePath(projectRoot);
|
|
32527
32679
|
mkdirSync(dirname(path), { recursive: true });
|
|
32680
|
+
const mode = fileMode(path);
|
|
32681
|
+
if (mode !== void 0 && !isWritable(path)) throw new LessonsGraphReadOnlyError();
|
|
32528
32682
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
32529
32683
|
writeFileSync(tmp, serializeGraph(graph), "utf8");
|
|
32684
|
+
if (mode !== void 0) chmodSync(tmp, mode);
|
|
32530
32685
|
renameSync(tmp, path);
|
|
32531
32686
|
}
|
|
32532
32687
|
function serializeGraph(graph) {
|
|
@@ -32570,9 +32725,76 @@ Graph \`.agentsmesh/lessons/lessons.json\` is canonical; never hand-edit it. Man
|
|
|
32570
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>\`.
|
|
32571
32726
|
|
|
32572
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
|
|
32573
32789
|
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
32574
32790
|
var MAX_FILES = 2e5;
|
|
32575
|
-
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) {
|
|
32576
32798
|
const out2 = /* @__PURE__ */ new Set();
|
|
32577
32799
|
try {
|
|
32578
32800
|
const stack = [projectRoot];
|
|
@@ -32583,14 +32805,14 @@ function listProjectFiles(projectRoot) {
|
|
|
32583
32805
|
if (!SKIP_DIRS.has(entry.name)) stack.push(join(dir, entry.name));
|
|
32584
32806
|
} else if (entry.isFile()) {
|
|
32585
32807
|
out2.add(toRelPath(projectRoot, join(dir, entry.name)));
|
|
32586
|
-
if (out2.size >
|
|
32808
|
+
if (out2.size > maxFiles) return null;
|
|
32587
32809
|
}
|
|
32588
32810
|
}
|
|
32589
32811
|
}
|
|
32590
32812
|
} catch {
|
|
32591
32813
|
return null;
|
|
32592
32814
|
}
|
|
32593
|
-
return out2;
|
|
32815
|
+
return projectFilesOf(out2, () => readGitPathHistory(projectRoot));
|
|
32594
32816
|
}
|
|
32595
32817
|
|
|
32596
32818
|
// src/lessons/validate-checks.ts
|
|
@@ -32767,6 +32989,265 @@ function collectOrphans(graph, findings) {
|
|
|
32767
32989
|
}
|
|
32768
32990
|
}
|
|
32769
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
|
|
32770
33251
|
function normalizeRule(rule) {
|
|
32771
33252
|
return rule.trim().replace(/\s+/g, " ").toLowerCase();
|
|
32772
33253
|
}
|
|
@@ -32775,14 +33256,50 @@ function union(base, extra) {
|
|
|
32775
33256
|
for (const item of extra) if (!out2.includes(item)) out2.push(item);
|
|
32776
33257
|
return out2;
|
|
32777
33258
|
}
|
|
32778
|
-
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) {
|
|
32779
33294
|
const requested = [
|
|
32780
|
-
//
|
|
32781
|
-
//
|
|
32782
|
-
//
|
|
32783
|
-
// 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.
|
|
32784
33298
|
...(spec.files ?? []).map(
|
|
32785
|
-
(p) => ({
|
|
33299
|
+
(p) => ({
|
|
33300
|
+
kind: "file_glob",
|
|
33301
|
+
pattern: projectRoot === void 0 ? p.replaceAll("\\", "/") : projectRelativeGlob(p, projectRoot)
|
|
33302
|
+
})
|
|
32786
33303
|
),
|
|
32787
33304
|
...(spec.commands ?? []).map((p) => ({ kind: "command_pattern", pattern: p })),
|
|
32788
33305
|
...(spec.keywords ?? []).map((p) => ({ kind: "keyword", pattern: p }))
|
|
@@ -32839,6 +33356,132 @@ function todayIso() {
|
|
|
32839
33356
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
32840
33357
|
}
|
|
32841
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
|
+
|
|
32842
33485
|
// src/lessons/regex-linear/nfa-compile.ts
|
|
32843
33486
|
var MAX_NFA_STATES = 2e3;
|
|
32844
33487
|
var Builder = class {
|
|
@@ -33030,6 +33673,9 @@ function escapeClass(c2) {
|
|
|
33030
33673
|
var HEX2 = /^[0-9a-fA-F]{2}$/;
|
|
33031
33674
|
var HEX4 = /^[0-9a-fA-F]{4}$/;
|
|
33032
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
|
+
}
|
|
33033
33679
|
if (c2 === "x") {
|
|
33034
33680
|
const hex2 = src.slice(i, i + 2);
|
|
33035
33681
|
return HEX2.test(hex2) ? { ch: String.fromCharCode(parseInt(hex2, 16)), len: 2 } : { ch: "x", len: 0 };
|
|
@@ -33119,7 +33765,7 @@ function parseRegex(src) {
|
|
|
33119
33765
|
function parseAtom() {
|
|
33120
33766
|
const c2 = peek();
|
|
33121
33767
|
if (c2 === "(") return parseGroup2();
|
|
33122
|
-
if (c2 === "[") return
|
|
33768
|
+
if (c2 === "[") return parseClass2();
|
|
33123
33769
|
if (c2 === "\\") return parseEscape();
|
|
33124
33770
|
if (c2 === ".") {
|
|
33125
33771
|
i += 1;
|
|
@@ -33178,7 +33824,7 @@ function parseRegex(src) {
|
|
|
33178
33824
|
}
|
|
33179
33825
|
return { k: "char", ch: escapeLiteral(c2) };
|
|
33180
33826
|
}
|
|
33181
|
-
function
|
|
33827
|
+
function parseClass2() {
|
|
33182
33828
|
i += 1;
|
|
33183
33829
|
const negate = peek() === "^";
|
|
33184
33830
|
if (negate) i += 1;
|
|
@@ -33231,17 +33877,17 @@ function parseRegex(src) {
|
|
|
33231
33877
|
}
|
|
33232
33878
|
|
|
33233
33879
|
// src/lessons/regex-linear/index.ts
|
|
33234
|
-
var
|
|
33880
|
+
var cache3 = /* @__PURE__ */ new Map();
|
|
33235
33881
|
function compileLinearMatcher(pattern) {
|
|
33236
|
-
const hit =
|
|
33237
|
-
if (hit !== void 0 ||
|
|
33882
|
+
const hit = cache3.get(pattern);
|
|
33883
|
+
if (hit !== void 0 || cache3.has(pattern)) return hit ?? null;
|
|
33238
33884
|
let matcher;
|
|
33239
33885
|
try {
|
|
33240
33886
|
matcher = buildMatcher(parseRegex(pattern));
|
|
33241
33887
|
} catch {
|
|
33242
33888
|
matcher = null;
|
|
33243
33889
|
}
|
|
33244
|
-
|
|
33890
|
+
cache3.set(pattern, matcher);
|
|
33245
33891
|
return matcher;
|
|
33246
33892
|
}
|
|
33247
33893
|
|
|
@@ -33282,6 +33928,8 @@ function collectDuplicateRules(graph, findings) {
|
|
|
33282
33928
|
}
|
|
33283
33929
|
function collectInvalidTriggerPatterns(graph, findings) {
|
|
33284
33930
|
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
33931
|
+
const globFinding = unsafeGlobFinding(triggerId, trigger);
|
|
33932
|
+
if (globFinding !== null) findings.push(globFinding);
|
|
33285
33933
|
if (trigger.kind !== "command_pattern") continue;
|
|
33286
33934
|
try {
|
|
33287
33935
|
new RegExp(trigger.pattern);
|
|
@@ -33289,7 +33937,7 @@ function collectInvalidTriggerPatterns(graph, findings) {
|
|
|
33289
33937
|
findings.push({
|
|
33290
33938
|
level: "error",
|
|
33291
33939
|
code: "INVALID_TRIGGER_PATTERN",
|
|
33292
|
-
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)}.`,
|
|
33293
33941
|
triggerId
|
|
33294
33942
|
});
|
|
33295
33943
|
continue;
|
|
@@ -33298,12 +33946,16 @@ function collectInvalidTriggerPatterns(graph, findings) {
|
|
|
33298
33946
|
findings.push({
|
|
33299
33947
|
level: "error",
|
|
33300
33948
|
code: "UNSAFE_TRIGGER_PATTERN",
|
|
33301
|
-
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.`,
|
|
33302
33950
|
triggerId
|
|
33303
33951
|
});
|
|
33304
33952
|
}
|
|
33305
33953
|
}
|
|
33306
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
|
+
}
|
|
33307
33959
|
function collectBackslashGlobPatterns(graph, findings) {
|
|
33308
33960
|
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
33309
33961
|
if (trigger.kind !== "file_glob") continue;
|
|
@@ -33384,7 +34036,11 @@ function collectTriggerSetCollisions(graph, findings) {
|
|
|
33384
34036
|
|
|
33385
34037
|
// src/lessons/glob-breadth.ts
|
|
33386
34038
|
var WILDCARD = /[*?[\]]/;
|
|
34039
|
+
function isNegatedGlob(pattern) {
|
|
34040
|
+
return pattern.startsWith("!");
|
|
34041
|
+
}
|
|
33387
34042
|
function globNarrowness(pattern) {
|
|
34043
|
+
if (isNegatedGlob(pattern)) return 0;
|
|
33388
34044
|
const segments = pattern.replaceAll("\\", "/").split("/").filter((segment) => segment !== "" && segment !== ".");
|
|
33389
34045
|
if (segments.length === 0) return 0;
|
|
33390
34046
|
let literal = 0;
|
|
@@ -33435,6 +34091,15 @@ function isBroadCommandPattern(pattern) {
|
|
|
33435
34091
|
}
|
|
33436
34092
|
return hits > COMMAND_PROBE_CORPUS.length * BROAD_HIT_RATIO;
|
|
33437
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
|
+
}
|
|
33438
34103
|
|
|
33439
34104
|
// src/lessons/validate-liveness.ts
|
|
33440
34105
|
function activeTriggerIds(graph) {
|
|
@@ -33445,32 +34110,42 @@ function activeTriggerIds(graph) {
|
|
|
33445
34110
|
}
|
|
33446
34111
|
return ids;
|
|
33447
34112
|
}
|
|
33448
|
-
function
|
|
33449
|
-
const active = activeTriggerIds(graph);
|
|
34113
|
+
function fileGlobLiveness(graph, knownPaths, triggerIds) {
|
|
34114
|
+
const active = triggerIds === void 0 ? activeTriggerIds(graph) : new Set(triggerIds);
|
|
33450
34115
|
const paths2 = [...knownPaths];
|
|
33451
|
-
const
|
|
34116
|
+
const missing = [];
|
|
33452
34117
|
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
33453
|
-
if (trigger.kind !== "file_glob") continue;
|
|
33454
|
-
|
|
33455
|
-
|
|
33456
|
-
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);
|
|
33457
34131
|
}
|
|
33458
|
-
return dead;
|
|
34132
|
+
return { dead, pending };
|
|
33459
34133
|
}
|
|
33460
34134
|
function collectDeadFileGlobs(graph, findings, knownPaths) {
|
|
33461
|
-
for (const triggerId of
|
|
34135
|
+
for (const triggerId of fileGlobLiveness(graph, knownPaths).dead) {
|
|
33462
34136
|
findings.push({
|
|
33463
34137
|
level: "warning",
|
|
33464
34138
|
code: "DEAD_FILE_GLOB",
|
|
33465
|
-
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\`.`,
|
|
33466
34140
|
triggerId
|
|
33467
34141
|
});
|
|
33468
34142
|
}
|
|
33469
34143
|
}
|
|
33470
34144
|
function fileGlobMatchCount(pattern, knownPaths) {
|
|
33471
|
-
const
|
|
34145
|
+
const matcher = getGlobMatcher(pattern);
|
|
34146
|
+
if (matcher === null) return 0;
|
|
33472
34147
|
let n = 0;
|
|
33473
|
-
for (const p of knownPaths) if (
|
|
34148
|
+
for (const p of knownPaths) if (matcher.test(p)) n += 1;
|
|
33474
34149
|
return n;
|
|
33475
34150
|
}
|
|
33476
34151
|
var RUNNER_ANCHOR = /^\^(pnpm|npm|npx|yarn|bun)\b/;
|
|
@@ -33542,7 +34217,7 @@ var STOP = /* @__PURE__ */ new Set([
|
|
|
33542
34217
|
"its",
|
|
33543
34218
|
"must"
|
|
33544
34219
|
]);
|
|
33545
|
-
function
|
|
34220
|
+
function tokenize2(text) {
|
|
33546
34221
|
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2 && !STOP.has(t));
|
|
33547
34222
|
}
|
|
33548
34223
|
function queryTerms(query) {
|
|
@@ -33550,7 +34225,7 @@ function queryTerms(query) {
|
|
|
33550
34225
|
if (query.keyword !== void 0) parts.push(query.keyword);
|
|
33551
34226
|
if (query.file !== void 0) parts.push(query.file);
|
|
33552
34227
|
if (query.command !== void 0) parts.push(query.command);
|
|
33553
|
-
return
|
|
34228
|
+
return tokenize2(parts.join(" "));
|
|
33554
34229
|
}
|
|
33555
34230
|
function buildCorpus(graph) {
|
|
33556
34231
|
const docs = [];
|
|
@@ -33559,7 +34234,7 @@ function buildCorpus(graph) {
|
|
|
33559
34234
|
let n = 0;
|
|
33560
34235
|
for (const lesson of Object.values(graph.lessons)) {
|
|
33561
34236
|
if (lesson.status !== "active") continue;
|
|
33562
|
-
const toks =
|
|
34237
|
+
const toks = tokenize2(lesson.rule);
|
|
33563
34238
|
n += 1;
|
|
33564
34239
|
total += toks.length;
|
|
33565
34240
|
docs.push(toks.length);
|
|
@@ -33571,7 +34246,7 @@ function buildCorpus(graph) {
|
|
|
33571
34246
|
return { idf, avgdl: total / N || 1 };
|
|
33572
34247
|
}
|
|
33573
34248
|
function bm25(terms, ruleText, corpus) {
|
|
33574
|
-
const toks =
|
|
34249
|
+
const toks = tokenize2(ruleText);
|
|
33575
34250
|
const dl = toks.length || 1;
|
|
33576
34251
|
const tf = /* @__PURE__ */ new Map();
|
|
33577
34252
|
for (const t of toks) tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
@@ -33588,7 +34263,7 @@ function bm25(terms, ruleText, corpus) {
|
|
|
33588
34263
|
// src/lessons/keyword-signal.ts
|
|
33589
34264
|
var MAX_RECOMMENDED_KEYWORD_TOKENS = 5;
|
|
33590
34265
|
function isLowSignalKeyword(pattern) {
|
|
33591
|
-
return
|
|
34266
|
+
return tokenize2(pattern).length > MAX_RECOMMENDED_KEYWORD_TOKENS;
|
|
33592
34267
|
}
|
|
33593
34268
|
function splitRawTokens(pattern) {
|
|
33594
34269
|
return pattern.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
|
|
@@ -33596,7 +34271,7 @@ function splitRawTokens(pattern) {
|
|
|
33596
34271
|
function keywordNeedleLosesTokens(pattern) {
|
|
33597
34272
|
const raw = splitRawTokens(pattern);
|
|
33598
34273
|
if (raw.length < 2) return false;
|
|
33599
|
-
return
|
|
34274
|
+
return tokenize2(pattern).length !== raw.length;
|
|
33600
34275
|
}
|
|
33601
34276
|
|
|
33602
34277
|
// src/lessons/validate-keywords.ts
|
|
@@ -33619,7 +34294,7 @@ function collectStopwordKeywords(graph, findings) {
|
|
|
33619
34294
|
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
33620
34295
|
if (trigger.kind !== "keyword") continue;
|
|
33621
34296
|
if (!active.has(triggerId)) continue;
|
|
33622
|
-
if (
|
|
34297
|
+
if (tokenize2(trigger.pattern).length !== 0 && !keywordNeedleLosesTokens(trigger.pattern)) {
|
|
33623
34298
|
continue;
|
|
33624
34299
|
}
|
|
33625
34300
|
findings.push({
|
|
@@ -33667,7 +34342,6 @@ function validateLessonsGraph(graph, options = {}) {
|
|
|
33667
34342
|
|
|
33668
34343
|
// src/core/lint/shared/lessons.ts
|
|
33669
34344
|
var LESSONS_TARGET = "lessons";
|
|
33670
|
-
var GRAPH_REL = ".agentsmesh/lessons/lessons.json";
|
|
33671
34345
|
var ROOT_RULE_REL = ".agentsmesh/rules/_root.md";
|
|
33672
34346
|
var LESSONS_HEADING = /^## Lessons \(/m;
|
|
33673
34347
|
function lintLessonsSubsystem(projectRoot, scope) {
|
|
@@ -33682,7 +34356,7 @@ function lintLessonsSubsystem(projectRoot, scope) {
|
|
|
33682
34356
|
return [
|
|
33683
34357
|
diag(
|
|
33684
34358
|
"error",
|
|
33685
|
-
|
|
34359
|
+
LESSONS_GRAPH_PATH,
|
|
33686
34360
|
`lessons.json failed to load: ${err instanceof Error ? err.message : String(err)}`
|
|
33687
34361
|
)
|
|
33688
34362
|
];
|
|
@@ -33690,7 +34364,7 @@ function lintLessonsSubsystem(projectRoot, scope) {
|
|
|
33690
34364
|
const knownPaths = listProjectFiles(projectRoot) ?? void 0;
|
|
33691
34365
|
const report = validateLessonsGraph(graph, { knownPaths });
|
|
33692
34366
|
for (const finding of report.findings) {
|
|
33693
|
-
out2.push(diag(finding.level,
|
|
34367
|
+
out2.push(diag(finding.level, LESSONS_GRAPH_PATH, `[${finding.code}] ${finding.message}`));
|
|
33694
34368
|
}
|
|
33695
34369
|
const rootRuleAbs = join(projectRoot, ROOT_RULE_REL);
|
|
33696
34370
|
const rootRuleBody = existsSync(rootRuleAbs) ? readFileSync(rootRuleAbs, "utf8") : "";
|
|
@@ -33712,8 +34386,8 @@ function diag(level, file, message) {
|
|
|
33712
34386
|
// src/core/lint/linter.ts
|
|
33713
34387
|
var EXCLUDE_DIRS = ["node_modules", ".git", "dist", "coverage", ".agentsmesh"];
|
|
33714
34388
|
function isExcludedProjectPath(rel2) {
|
|
33715
|
-
const
|
|
33716
|
-
return EXCLUDE_DIRS.some((d) =>
|
|
34389
|
+
const posix11 = rel2.replaceAll("\\", "/");
|
|
34390
|
+
return EXCLUDE_DIRS.some((d) => posix11.includes(`/${d}/`) || posix11.startsWith(`${d}/`));
|
|
33717
34391
|
}
|
|
33718
34392
|
async function getProjectFiles(projectRoot) {
|
|
33719
34393
|
const all = await readDirRecursive(projectRoot);
|
|
@@ -34106,14 +34780,22 @@ async function findUntrackedManagedDirFiles(args) {
|
|
|
34106
34780
|
return [...found].sort();
|
|
34107
34781
|
}
|
|
34108
34782
|
|
|
34783
|
+
// src/lessons/conflict-markers.ts
|
|
34784
|
+
function hasConflictMarkers(text) {
|
|
34785
|
+
return /^(?:<{7}|>{7})(?: |\r?$)/m.test(text);
|
|
34786
|
+
}
|
|
34787
|
+
|
|
34109
34788
|
// src/core/check/lock-sync.ts
|
|
34789
|
+
init_fs();
|
|
34110
34790
|
async function checkLockSync(opts) {
|
|
34111
34791
|
const { config, configDir, canonicalDir, rootBase, scope = "project" } = opts;
|
|
34112
34792
|
const lock = await readLock(canonicalDir);
|
|
34113
34793
|
if (lock === null) {
|
|
34794
|
+
const text = await readFileSafe(join(canonicalDir, ".lock"));
|
|
34114
34795
|
return {
|
|
34115
34796
|
inSync: false,
|
|
34116
34797
|
hasLock: false,
|
|
34798
|
+
lockConflict: text !== null && hasConflictMarkers(text),
|
|
34117
34799
|
canonicalDrift: false,
|
|
34118
34800
|
outputDrift: false,
|
|
34119
34801
|
modified: [],
|
|
@@ -34183,6 +34865,7 @@ async function checkLockSync(opts) {
|
|
|
34183
34865
|
return {
|
|
34184
34866
|
inSync,
|
|
34185
34867
|
hasLock: true,
|
|
34868
|
+
lockConflict: false,
|
|
34186
34869
|
canonicalDrift,
|
|
34187
34870
|
outputDrift,
|
|
34188
34871
|
modified,
|
|
@@ -34458,6 +35141,27 @@ var UnknownTopicError = class extends Error {
|
|
|
34458
35141
|
}
|
|
34459
35142
|
code = "UNKNOWN_TOPIC";
|
|
34460
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
|
+
};
|
|
34461
35165
|
var RuleTooLongError = class extends Error {
|
|
34462
35166
|
constructor(length, max) {
|
|
34463
35167
|
super(
|
|
@@ -34488,6 +35192,9 @@ var UnrecallableLessonError = class extends Error {
|
|
|
34488
35192
|
}
|
|
34489
35193
|
code = "UNRECALLABLE_LESSON";
|
|
34490
35194
|
};
|
|
35195
|
+
function codePointLength(text) {
|
|
35196
|
+
return [...text].length;
|
|
35197
|
+
}
|
|
34491
35198
|
|
|
34492
35199
|
// src/lessons/trigger-effectiveness.ts
|
|
34493
35200
|
function ineffectiveTriggers(graph, triggerIds) {
|
|
@@ -34502,7 +35209,7 @@ function ineffectiveTriggers(graph, triggerIds) {
|
|
|
34502
35209
|
}
|
|
34503
35210
|
function ineffectiveReason(kind, pattern) {
|
|
34504
35211
|
if (kind === "keyword") {
|
|
34505
|
-
if (
|
|
35212
|
+
if (tokenize2(pattern).length === 0) {
|
|
34506
35213
|
return "keyword has no matchable token after stopword filtering \u2014 it cannot fire on the mandatory --file/--cmd recall path";
|
|
34507
35214
|
}
|
|
34508
35215
|
if (keywordNeedleLosesTokens(pattern)) {
|
|
@@ -34535,9 +35242,19 @@ function blockingDeadTriggers(graph, triggerIds) {
|
|
|
34535
35242
|
function assertRuleShape(rule) {
|
|
34536
35243
|
const trimmed = rule.trim();
|
|
34537
35244
|
if (trimmed.length === 0) throw new EmptyRuleError();
|
|
34538
|
-
|
|
35245
|
+
const length = codePointLength(trimmed);
|
|
35246
|
+
if (length > MAX_RULE_LENGTH) throw new RuleTooLongError(length, MAX_RULE_LENGTH);
|
|
34539
35247
|
return trimmed;
|
|
34540
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
|
+
}
|
|
34541
35258
|
function skipsTriggerGates(input, options) {
|
|
34542
35259
|
return options.allowNoTrigger === true || input.scope === "always";
|
|
34543
35260
|
}
|
|
@@ -34553,10 +35270,30 @@ function assertTriggerInputs(input, options, existingTriggerCount) {
|
|
|
34553
35270
|
if (broad !== void 0) throw new BroadCommandPatternError(broad);
|
|
34554
35271
|
}
|
|
34555
35272
|
}
|
|
34556
|
-
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) {
|
|
34557
35293
|
const blockingDead = blockingDeadTriggers(graph, resultingTriggers);
|
|
34558
|
-
|
|
34559
|
-
|
|
35294
|
+
const dead = [...dropped, ...blockingDead];
|
|
35295
|
+
if (dead.length > 0 && blockingDead.length === resultingTriggers.length) {
|
|
35296
|
+
throw new UnrecallableLessonError(dead);
|
|
34560
35297
|
}
|
|
34561
35298
|
}
|
|
34562
35299
|
|
|
@@ -34565,7 +35302,7 @@ var WIDE_GLOB_MATCH_COUNT = 40;
|
|
|
34565
35302
|
var MAX_RECOMMENDED_TRIGGERS = 8;
|
|
34566
35303
|
function isBroadGlob(pattern) {
|
|
34567
35304
|
const p = pattern.trim();
|
|
34568
|
-
if (p === "*" || p === "**") return true;
|
|
35305
|
+
if (p === "*" || p === "**" || isNegatedGlob(p)) return true;
|
|
34569
35306
|
if (!p.includes("**")) return false;
|
|
34570
35307
|
const basename78 = p.slice(p.lastIndexOf("/") + 1);
|
|
34571
35308
|
return basename78.startsWith("*");
|
|
@@ -34609,12 +35346,19 @@ function inspectCapturedLesson(graph, lessonId, knownPaths) {
|
|
|
34609
35346
|
});
|
|
34610
35347
|
}
|
|
34611
35348
|
if (knownPaths !== void 0) {
|
|
34612
|
-
const dead =
|
|
34613
|
-
const deadHere = lesson.triggers
|
|
35349
|
+
const { dead, pending } = fileGlobLiveness(graph, knownPaths, lesson.triggers);
|
|
35350
|
+
const deadHere = patternsIn(graph, lesson.triggers, dead);
|
|
34614
35351
|
if (deadHere.length > 0) {
|
|
34615
35352
|
warnings.push({
|
|
34616
35353
|
code: "DEAD_GLOB",
|
|
34617
|
-
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.`
|
|
34618
35362
|
});
|
|
34619
35363
|
}
|
|
34620
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);
|
|
@@ -34627,18 +35371,21 @@ function inspectCapturedLesson(graph, lessonId, knownPaths) {
|
|
|
34627
35371
|
}
|
|
34628
35372
|
return warnings;
|
|
34629
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
|
+
}
|
|
34630
35377
|
|
|
34631
35378
|
// src/lessons/capture-near-duplicate.ts
|
|
34632
35379
|
var NEAR_DUPLICATE_THRESHOLD = 0.6;
|
|
34633
35380
|
function nearDuplicateWarning(graph, lessonId) {
|
|
34634
35381
|
const subject = graph.lessons[lessonId];
|
|
34635
35382
|
if (subject === void 0) return null;
|
|
34636
|
-
const subjectTokens = new Set(
|
|
35383
|
+
const subjectTokens = new Set(tokenize2(subject.rule));
|
|
34637
35384
|
if (subjectTokens.size === 0) return null;
|
|
34638
35385
|
let best = null;
|
|
34639
35386
|
for (const [id, other] of Object.entries(graph.lessons)) {
|
|
34640
35387
|
if (id === lessonId || other.status !== "active") continue;
|
|
34641
|
-
const otherTokens = new Set(
|
|
35388
|
+
const otherTokens = new Set(tokenize2(other.rule));
|
|
34642
35389
|
if (otherTokens.size === 0) continue;
|
|
34643
35390
|
const score = jaccard(subjectTokens, otherTokens);
|
|
34644
35391
|
if (score >= NEAR_DUPLICATE_THRESHOLD && (best === null || score > best.score)) {
|
|
@@ -34656,63 +35403,375 @@ function jaccard(a, b) {
|
|
|
34656
35403
|
for (const t of a) if (b.has(t)) intersection += 1;
|
|
34657
35404
|
return intersection / (a.size + b.size - intersection);
|
|
34658
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
|
+
}
|
|
34659
35426
|
|
|
34660
35427
|
// src/utils/filesystem/process-lock.ts
|
|
34661
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
|
|
34662
35703
|
var DEFAULT_STALE_MS = 6 * 60 * 60 * 1e3;
|
|
34663
35704
|
var DEFAULT_RETRIES = 30;
|
|
34664
35705
|
var DEFAULT_RETRY_DELAY_MS = 200;
|
|
34665
|
-
var
|
|
35706
|
+
var MAX_IMMEDIATE_RETRIES = 100;
|
|
35707
|
+
var MAX_TRANSIENT_ERRORS = 5;
|
|
34666
35708
|
async function acquireProcessLock(lockPath, opts = {}) {
|
|
34667
35709
|
const retries = opts.retries ?? DEFAULT_RETRIES;
|
|
34668
|
-
const
|
|
34669
|
-
const stale = opts.staleMs ?? DEFAULT_STALE_MS;
|
|
35710
|
+
const staleMs = opts.staleMs ?? DEFAULT_STALE_MS;
|
|
34670
35711
|
await mkdir(dirname(lockPath), { recursive: true });
|
|
35712
|
+
const procStart = await selfIdentity();
|
|
35713
|
+
const probes = /* @__PURE__ */ new Map();
|
|
34671
35714
|
let attempt = 0;
|
|
35715
|
+
let immediate = 0;
|
|
35716
|
+
const waitingSince = Date.now();
|
|
35717
|
+
let noticed = false;
|
|
35718
|
+
let transient = 0;
|
|
34672
35719
|
while (true) {
|
|
34673
|
-
|
|
34674
|
-
|
|
34675
|
-
|
|
34676
|
-
|
|
34677
|
-
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++;
|
|
34678
35733
|
continue;
|
|
34679
35734
|
}
|
|
34680
35735
|
if (attempt >= retries) {
|
|
34681
|
-
|
|
34682
|
-
throw new LockAcquisitionError(lockPath, describeHolder(holder), { label: opts.label });
|
|
35736
|
+
throw new LockAcquisitionError(lockPath, describeHolder(state), { label: opts.label });
|
|
34683
35737
|
}
|
|
34684
35738
|
attempt++;
|
|
34685
|
-
|
|
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));
|
|
34686
35745
|
}
|
|
34687
35746
|
}
|
|
34688
|
-
|
|
34689
|
-
|
|
34690
|
-
|
|
34691
|
-
|
|
34692
|
-
|
|
34693
|
-
|
|
34694
|
-
|
|
34695
|
-
|
|
34696
|
-
|
|
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 {
|
|
34697
35762
|
pid: process.pid,
|
|
34698
35763
|
started: Date.now(),
|
|
34699
|
-
hostname:
|
|
35764
|
+
hostname: hostname(),
|
|
35765
|
+
token: randomUUID(),
|
|
35766
|
+
...procStart === null ? {} : { procStart }
|
|
34700
35767
|
};
|
|
34701
|
-
|
|
34702
|
-
|
|
34703
|
-
} catch (error) {
|
|
34704
|
-
await rm(lockPath, { recursive: true, force: true }).catch(() => {
|
|
34705
|
-
});
|
|
34706
|
-
throw error;
|
|
34707
|
-
}
|
|
35768
|
+
}
|
|
35769
|
+
function holdLock(lockPath, token) {
|
|
34708
35770
|
let released = false;
|
|
34709
35771
|
const cleanup = () => {
|
|
34710
35772
|
if (released) return;
|
|
34711
35773
|
released = true;
|
|
34712
|
-
|
|
34713
|
-
rmSync(lockPath, { recursive: true, force: true });
|
|
34714
|
-
} catch {
|
|
34715
|
-
}
|
|
35774
|
+
releaseOwnedSync(lockPath, token);
|
|
34716
35775
|
};
|
|
34717
35776
|
const signalHandler = (signal) => {
|
|
34718
35777
|
cleanup();
|
|
@@ -34721,76 +35780,69 @@ async function tryAcquire(lockPath) {
|
|
|
34721
35780
|
process.once("SIGINT", signalHandler);
|
|
34722
35781
|
process.once("SIGTERM", signalHandler);
|
|
34723
35782
|
process.once("exit", cleanup);
|
|
34724
|
-
|
|
35783
|
+
const release = async () => {
|
|
34725
35784
|
if (released) return;
|
|
34726
35785
|
released = true;
|
|
34727
35786
|
process.off("SIGINT", signalHandler);
|
|
34728
35787
|
process.off("SIGTERM", signalHandler);
|
|
34729
35788
|
process.off("exit", cleanup);
|
|
34730
|
-
await
|
|
35789
|
+
await evictOwners(lockPath, [token]).catch(() => {
|
|
34731
35790
|
});
|
|
34732
35791
|
};
|
|
34733
|
-
|
|
34734
|
-
|
|
34735
|
-
try {
|
|
34736
|
-
const raw = await readFile(join(lockPath, "holder.json"), "utf-8");
|
|
34737
|
-
const parsed = JSON.parse(raw);
|
|
34738
|
-
if (!isLockMetadata(parsed)) return null;
|
|
34739
|
-
return parsed;
|
|
34740
|
-
} catch {
|
|
34741
|
-
try {
|
|
34742
|
-
const info = await stat(lockPath);
|
|
34743
|
-
const ageMs = Date.now() - info.mtimeMs;
|
|
34744
|
-
if (ageMs < YOUNG_LOCK_GRACE_MS) return "young";
|
|
34745
|
-
} catch {
|
|
34746
|
-
}
|
|
34747
|
-
return null;
|
|
34748
|
-
}
|
|
34749
|
-
}
|
|
34750
|
-
function isStale(meta, staleMs) {
|
|
34751
|
-
if (!meta) return true;
|
|
34752
|
-
const sameHost = !meta.hostname || meta.hostname === getHostname();
|
|
34753
|
-
if (sameHost && !isProcessAlive(meta.pid)) return true;
|
|
34754
|
-
return Date.now() - meta.started > staleMs;
|
|
34755
|
-
}
|
|
34756
|
-
function isProcessAlive(pid) {
|
|
34757
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
34758
|
-
try {
|
|
34759
|
-
process.kill(pid, 0);
|
|
34760
|
-
return true;
|
|
34761
|
-
} catch (err) {
|
|
34762
|
-
return err.code === "EPERM";
|
|
34763
|
-
}
|
|
34764
|
-
}
|
|
34765
|
-
function describeHolder(meta) {
|
|
34766
|
-
if (!meta) return "unknown (unreadable lock metadata)";
|
|
34767
|
-
const host = meta.hostname ? `${meta.hostname}:` : "";
|
|
34768
|
-
return `${host}pid ${meta.pid} (running ${Date.now() - meta.started}ms)`;
|
|
34769
|
-
}
|
|
34770
|
-
function isLockMetadata(value) {
|
|
34771
|
-
if (typeof value !== "object" || value === null) return false;
|
|
34772
|
-
const v = value;
|
|
34773
|
-
return typeof v.pid === "number" && typeof v.started === "number";
|
|
34774
|
-
}
|
|
34775
|
-
function getHostname() {
|
|
34776
|
-
return hostname();
|
|
35792
|
+
const isHeld = async () => !released && existsSync(ownerPath(lockPath, token));
|
|
35793
|
+
return Object.assign(release, { isHeld });
|
|
34777
35794
|
}
|
|
34778
35795
|
|
|
34779
35796
|
// src/lessons/lessons-lock.ts
|
|
34780
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
|
+
});
|
|
34781
35805
|
function lessonsLockPath(projectRoot) {
|
|
34782
35806
|
return resolve(projectRoot, ".agentsmesh/lessons", LESSONS_LOCK_FILENAME);
|
|
34783
35807
|
}
|
|
34784
35808
|
async function acquireLessonsLock(projectRoot, opts = {}) {
|
|
34785
|
-
|
|
34786
|
-
|
|
34787
|
-
|
|
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();
|
|
34788
35832
|
}
|
|
34789
35833
|
|
|
34790
35834
|
// src/lessons/mutate.ts
|
|
34791
|
-
|
|
34792
|
-
|
|
34793
|
-
|
|
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
|
+
};
|
|
34794
35846
|
function findingKey(f) {
|
|
34795
35847
|
return `${f.code}|${f.triggerId ?? ""}|${f.lessonId ?? ""}`;
|
|
34796
35848
|
}
|
|
@@ -34800,6 +35852,7 @@ function errorSignatures(report) {
|
|
|
34800
35852
|
async function mutateLessonsGraphLocked(projectRoot, mutator, options = {}) {
|
|
34801
35853
|
const release = await acquireLessonsLock(projectRoot, { retries: options.retries });
|
|
34802
35854
|
try {
|
|
35855
|
+
sweepLessonsLeftovers(projectRoot);
|
|
34803
35856
|
const graph = tryLoadLessonsGraph(projectRoot) ?? emptyGraph();
|
|
34804
35857
|
const baseline = errorSignatures(validateLessonsGraph(graph));
|
|
34805
35858
|
const result2 = await mutator(graph);
|
|
@@ -34808,12 +35861,10 @@ async function mutateLessonsGraphLocked(projectRoot, mutator, options = {}) {
|
|
|
34808
35861
|
(f) => f.level === "error" && !baseline.has(findingKey(f))
|
|
34809
35862
|
);
|
|
34810
35863
|
if (introduced.length > 0) {
|
|
34811
|
-
|
|
34812
|
-
throw new Error(
|
|
34813
|
-
`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.)`
|
|
34814
|
-
);
|
|
35864
|
+
throw new LessonsWriteRefusedError(introduced);
|
|
34815
35865
|
}
|
|
34816
35866
|
graph.version = CURRENT_GRAPH_VERSION;
|
|
35867
|
+
await assertLessonsLockHeld(release);
|
|
34817
35868
|
saveLessonsGraph(projectRoot, graph);
|
|
34818
35869
|
return result2;
|
|
34819
35870
|
} finally {
|
|
@@ -34827,50 +35878,41 @@ async function mutateLessonsGraph(projectRoot, mutator, options = {}) {
|
|
|
34827
35878
|
|
|
34828
35879
|
// src/lessons/add.ts
|
|
34829
35880
|
async function addLesson(projectRoot, input, options = {}) {
|
|
34830
|
-
return mutateLessonsGraph(
|
|
34831
|
-
|
|
34832
|
-
|
|
35881
|
+
return mutateLessonsGraph(
|
|
35882
|
+
projectRoot,
|
|
35883
|
+
(graph) => addLessonInto(graph, input, { ...options, projectRoot }),
|
|
35884
|
+
{ retries: options.retries }
|
|
35885
|
+
);
|
|
34833
35886
|
}
|
|
34834
35887
|
function addLessonInto(graph, input, options) {
|
|
34835
35888
|
const ruleKey2 = normalizeRule(input.rule);
|
|
34836
35889
|
const trimmedRule = assertRuleShape(input.rule);
|
|
34837
35890
|
const existingId = findExistingLessonByRule(graph, ruleKey2);
|
|
34838
|
-
const isNewTopic = graph
|
|
34839
|
-
if (isNewTopic) {
|
|
34840
|
-
if (options.allowNewTopic !== true) throw new UnknownTopicError(input.topic);
|
|
34841
|
-
if (options.topicSummary === void 0 || options.topicSummary.length === 0) {
|
|
34842
|
-
throw new Error(`addLesson: new topic "${input.topic}" requires topicSummary.`);
|
|
34843
|
-
}
|
|
34844
|
-
graph.topics[input.topic] = { summary: options.topicSummary };
|
|
34845
|
-
}
|
|
35891
|
+
const isNewTopic = ensureTopic(graph, input.topic, options);
|
|
34846
35892
|
const existing = existingId !== null ? graph.lessons[existingId] : void 0;
|
|
34847
35893
|
assertTriggerInputs(input, options, existing?.triggers.length ?? 0);
|
|
34848
|
-
const
|
|
35894
|
+
const merged = mergeTriggers(graph, input.triggers, options.projectRoot);
|
|
35895
|
+
const { triggerIds, newTriggerIds, dropped } = dropDeadCommandTriggers(graph, merged, options);
|
|
34849
35896
|
if (!skipsTriggerGates(input, options)) {
|
|
34850
|
-
|
|
34851
|
-
|
|
34852
|
-
existing === void 0 ? triggerIds : union(existing.triggers, triggerIds)
|
|
34853
|
-
);
|
|
35897
|
+
const resulting = existing === void 0 ? triggerIds : union(existing.triggers, triggerIds);
|
|
35898
|
+
assertRecallable(graph, resulting, dropped);
|
|
34854
35899
|
}
|
|
34855
|
-
|
|
34856
|
-
|
|
34857
|
-
|
|
34858
|
-
|
|
34859
|
-
topics: union(existing2.topics, [input.topic]),
|
|
34860
|
-
triggers: union(existing2.triggers, triggerIds),
|
|
34861
|
-
evidence: union(existing2.evidence, input.evidence ?? []),
|
|
34862
|
-
...existing2.rationale === void 0 && input.rationale !== void 0 ? { rationale: input.rationale } : {},
|
|
34863
|
-
// Re-capturing a rule with --scope always promotes it to always-on.
|
|
34864
|
-
...input.scope === "always" ? { scope: "always" } : {}
|
|
34865
|
-
};
|
|
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;
|
|
34866
35904
|
return {
|
|
34867
35905
|
id: existingId,
|
|
34868
35906
|
isNewLesson: false,
|
|
34869
35907
|
isNewTopic,
|
|
34870
35908
|
newTriggerIds,
|
|
35909
|
+
changes: describeUpsert(existing, updated),
|
|
34871
35910
|
// Near-duplicate detection is meaningless on an upsert (the lesson IS the
|
|
34872
35911
|
// match), so only DEAD_GLOB/hygiene warnings apply here.
|
|
34873
|
-
warnings:
|
|
35912
|
+
warnings: [
|
|
35913
|
+
...inspectCapturedLesson(graph, existingId, options.knownPaths),
|
|
35914
|
+
...droppedWarnings
|
|
35915
|
+
]
|
|
34874
35916
|
};
|
|
34875
35917
|
}
|
|
34876
35918
|
const id = makeLessonId(graph, input.topic, ruleKey2);
|
|
@@ -34878,29 +35920,26 @@ function addLessonInto(graph, input, options) {
|
|
|
34878
35920
|
rule: trimmedRule,
|
|
34879
35921
|
topics: [input.topic],
|
|
34880
35922
|
triggers: triggerIds,
|
|
34881
|
-
evidence: input.evidence
|
|
35923
|
+
evidence: [...new Set(input.evidence ?? [])],
|
|
34882
35924
|
status: "active",
|
|
34883
35925
|
createdAt: input.createdAt ?? todayIso(),
|
|
34884
35926
|
...input.rationale === void 0 ? {} : { rationale: input.rationale },
|
|
34885
35927
|
...input.scope === "always" ? { scope: "always" } : {}
|
|
34886
35928
|
};
|
|
34887
|
-
const warnings = inspectCapturedLesson(graph, id, options.knownPaths);
|
|
34888
35929
|
const nearDup = nearDuplicateWarning(graph, id);
|
|
34889
35930
|
return {
|
|
34890
35931
|
id,
|
|
34891
35932
|
isNewLesson: true,
|
|
34892
35933
|
isNewTopic,
|
|
34893
35934
|
newTriggerIds,
|
|
34894
|
-
|
|
35935
|
+
changes: [],
|
|
35936
|
+
warnings: [
|
|
35937
|
+
...inspectCapturedLesson(graph, id, options.knownPaths),
|
|
35938
|
+
...nearDup === null ? [] : [nearDup],
|
|
35939
|
+
...droppedWarnings
|
|
35940
|
+
]
|
|
34895
35941
|
};
|
|
34896
35942
|
}
|
|
34897
|
-
function findExistingLessonByRule(graph, ruleKey2) {
|
|
34898
|
-
for (const [id, lesson] of Object.entries(graph.lessons)) {
|
|
34899
|
-
if (lesson.status !== "active") continue;
|
|
34900
|
-
if (normalizeRule(lesson.rule) === ruleKey2) return id;
|
|
34901
|
-
}
|
|
34902
|
-
return null;
|
|
34903
|
-
}
|
|
34904
35943
|
|
|
34905
35944
|
// src/lessons/import-legacy-merge.ts
|
|
34906
35945
|
async function mergeLegacy(projectRoot, paths2, specs, summaryByTopic, options) {
|
|
@@ -34933,19 +35972,33 @@ async function mergeLegacy(projectRoot, paths2, specs, summaryByTopic, options)
|
|
|
34933
35972
|
triggerCount: addedTriggers.size
|
|
34934
35973
|
};
|
|
34935
35974
|
}
|
|
34936
|
-
|
|
34937
|
-
|
|
34938
|
-
|
|
34939
|
-
|
|
34940
|
-
|
|
34941
|
-
|
|
34942
|
-
|
|
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";
|
|
34943
35983
|
}
|
|
34944
35984
|
};
|
|
34945
|
-
async function
|
|
34946
|
-
const
|
|
34947
|
-
const
|
|
34948
|
-
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
|
+
);
|
|
34949
36002
|
const topics = {};
|
|
34950
36003
|
const triggersById = /* @__PURE__ */ new Map();
|
|
34951
36004
|
const triggerIdByKey = /* @__PURE__ */ new Map();
|
|
@@ -34956,14 +36009,15 @@ async function importLegacyLessons(projectRoot, options) {
|
|
|
34956
36009
|
topics[cluster.topic] = { summary: cluster.summary };
|
|
34957
36010
|
summaryByTopic.set(cluster.topic, cluster.summary);
|
|
34958
36011
|
const clusterTriggerIds = collectClusterTriggerIds(cluster, triggersById, triggerIdByKey);
|
|
34959
|
-
const topicFile =
|
|
36012
|
+
const topicFile = await resolveLegacyTopicPath(projectRoot, cluster.file);
|
|
34960
36013
|
if (!existsSync(topicFile)) {
|
|
34961
36014
|
throw new Error(
|
|
34962
|
-
`
|
|
36015
|
+
`Legacy topic file is missing: ${cluster.file}. Refusing to migrate (legacy artifacts left intact).`
|
|
34963
36016
|
);
|
|
34964
36017
|
}
|
|
34965
|
-
const
|
|
34966
|
-
|
|
36018
|
+
for (const { index: ruleIndex, body, evidence } of parseRulesSection(
|
|
36019
|
+
readFileSync(topicFile, "utf8")
|
|
36020
|
+
)) {
|
|
34967
36021
|
const lessonEvidence = [
|
|
34968
36022
|
`legacy:${cluster.file}#rule-${ruleIndex}`,
|
|
34969
36023
|
...evidence.map((e) => `legacy:${e}`)
|
|
@@ -34974,7 +36028,7 @@ async function importLegacyLessons(projectRoot, options) {
|
|
|
34974
36028
|
triggers: clusterTriggerIds,
|
|
34975
36029
|
evidence: lessonEvidence,
|
|
34976
36030
|
status: "active",
|
|
34977
|
-
createdAt:
|
|
36031
|
+
createdAt: migratedAt
|
|
34978
36032
|
};
|
|
34979
36033
|
specs.push({
|
|
34980
36034
|
rule: body,
|
|
@@ -34985,22 +36039,43 @@ async function importLegacyLessons(projectRoot, options) {
|
|
|
34985
36039
|
keywords: cluster.triggers.keywords
|
|
34986
36040
|
},
|
|
34987
36041
|
evidence: lessonEvidence,
|
|
34988
|
-
createdAt:
|
|
36042
|
+
createdAt: migratedAt
|
|
34989
36043
|
});
|
|
34990
36044
|
}
|
|
34991
36045
|
}
|
|
34992
|
-
|
|
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);
|
|
34993
36063
|
return mergeLegacy(projectRoot, paths2, specs, summaryByTopic, options);
|
|
34994
|
-
|
|
34995
|
-
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
|
+
}
|
|
34996
36069
|
const populated = Object.keys(g.lessons).length > 0 || Object.keys(g.topics).length > 0 || Object.keys(g.triggers).length > 0;
|
|
34997
36070
|
if (options.force !== true && populated) {
|
|
34998
36071
|
throw new LessonsGraphExistsError();
|
|
34999
36072
|
}
|
|
36073
|
+
const source = await readLegacySource(projectRoot, options.migratedAt);
|
|
35000
36074
|
g.version = CURRENT_GRAPH_VERSION;
|
|
35001
|
-
g.lessons = lessons;
|
|
35002
|
-
g.topics = topics;
|
|
35003
|
-
g.triggers = triggers;
|
|
36075
|
+
g.lessons = source.lessons;
|
|
36076
|
+
g.topics = source.topics;
|
|
36077
|
+
g.triggers = source.triggers;
|
|
36078
|
+
return source;
|
|
35004
36079
|
});
|
|
35005
36080
|
const deletedPaths = options.deleteLegacy === false ? [] : deleteLegacyArtifacts(paths2.base);
|
|
35006
36081
|
return {
|
|
@@ -35008,7 +36083,7 @@ async function importLegacyLessons(projectRoot, options) {
|
|
|
35008
36083
|
deletedPaths,
|
|
35009
36084
|
topicCount: Object.keys(topics).length,
|
|
35010
36085
|
lessonCount: Object.keys(lessons).length,
|
|
35011
|
-
triggerCount:
|
|
36086
|
+
triggerCount: Object.keys(triggers).length
|
|
35012
36087
|
};
|
|
35013
36088
|
}
|
|
35014
36089
|
|
|
@@ -35018,13 +36093,14 @@ async function maybeAutoMigrateLessons(projectRoot) {
|
|
|
35018
36093
|
const paths2 = lessonsPaths(projectRoot);
|
|
35019
36094
|
if (!existsSync(paths2.index)) return false;
|
|
35020
36095
|
try {
|
|
35021
|
-
await importLegacyLessons(projectRoot, { migratedAt: todayIso() });
|
|
36096
|
+
await importLegacyLessons(projectRoot, { migratedAt: todayIso(), requireAbsentGraph: true });
|
|
35022
36097
|
return true;
|
|
35023
36098
|
} catch (err) {
|
|
35024
36099
|
if (err instanceof LessonsGraphExistsError) return false;
|
|
35025
36100
|
throw err;
|
|
35026
36101
|
}
|
|
35027
36102
|
}
|
|
36103
|
+
new Int32Array(new SharedArrayBuffer(4));
|
|
35028
36104
|
|
|
35029
36105
|
// src/lessons/keyword-match.ts
|
|
35030
36106
|
function deriveHaystackTokens(query) {
|
|
@@ -35058,7 +36134,7 @@ function containsRun(needle, hay) {
|
|
|
35058
36134
|
return false;
|
|
35059
36135
|
}
|
|
35060
36136
|
function keywordMatches(pattern, query) {
|
|
35061
|
-
const needle =
|
|
36137
|
+
const needle = tokenize2(pattern);
|
|
35062
36138
|
if (query.keyword !== void 0 && containsRun(needle, splitTokens(query.keyword.toLowerCase()))) {
|
|
35063
36139
|
return true;
|
|
35064
36140
|
}
|
|
@@ -35067,6 +36143,7 @@ function keywordMatches(pattern, query) {
|
|
|
35067
36143
|
|
|
35068
36144
|
// src/lessons/query.ts
|
|
35069
36145
|
var COMMAND_MATCH_BUDGET = 5e6;
|
|
36146
|
+
var GLOB_MATCH_BUDGET = 2e6;
|
|
35070
36147
|
function queryLessons(graph, query) {
|
|
35071
36148
|
if (query.file === void 0 && query.command === void 0 && query.keyword === void 0) {
|
|
35072
36149
|
return [];
|
|
@@ -35089,9 +36166,12 @@ function collectMatchedTriggersByKind(graph, query) {
|
|
|
35089
36166
|
command_pattern: /* @__PURE__ */ new Set(),
|
|
35090
36167
|
keyword: /* @__PURE__ */ new Set()
|
|
35091
36168
|
};
|
|
35092
|
-
const
|
|
36169
|
+
const budgets = {
|
|
36170
|
+
command: { remaining: COMMAND_MATCH_BUDGET },
|
|
36171
|
+
glob: { remaining: GLOB_MATCH_BUDGET }
|
|
36172
|
+
};
|
|
35093
36173
|
for (const [id, trigger] of Object.entries(graph.triggers)) {
|
|
35094
|
-
if (triggerMatches(trigger, query,
|
|
36174
|
+
if (triggerMatches(trigger, query, budgets)) byKind[trigger.kind].add(id);
|
|
35095
36175
|
}
|
|
35096
36176
|
return byKind;
|
|
35097
36177
|
}
|
|
@@ -35099,21 +36179,29 @@ function collectMatchedTriggerIds(graph, query) {
|
|
|
35099
36179
|
const { file_glob, command_pattern, keyword } = collectMatchedTriggersByKind(graph, query);
|
|
35100
36180
|
return /* @__PURE__ */ new Set([...file_glob, ...command_pattern, ...keyword]);
|
|
35101
36181
|
}
|
|
35102
|
-
function triggerMatches(trigger, query,
|
|
36182
|
+
function triggerMatches(trigger, query, budgets) {
|
|
35103
36183
|
switch (trigger.kind) {
|
|
35104
|
-
case "file_glob":
|
|
36184
|
+
case "file_glob": {
|
|
35105
36185
|
if (query.file === void 0) return false;
|
|
35106
|
-
|
|
36186
|
+
const matcher = getGlobMatcher(trigger.pattern);
|
|
36187
|
+
return matcher !== null && matcher.test(query.file, budgets.glob);
|
|
36188
|
+
}
|
|
35107
36189
|
case "command_pattern": {
|
|
35108
36190
|
if (query.command === void 0) return false;
|
|
35109
36191
|
const matcher = getCommandMatcher(trigger.pattern);
|
|
35110
|
-
return matcher !== null && matcher.test(query.command,
|
|
36192
|
+
return matcher !== null && matcher.test(query.command, budgets.command);
|
|
35111
36193
|
}
|
|
35112
36194
|
case "keyword":
|
|
35113
36195
|
return keywordMatches(trigger.pattern, query);
|
|
35114
36196
|
}
|
|
35115
36197
|
}
|
|
35116
36198
|
|
|
36199
|
+
// src/lessons/telemetry.ts
|
|
36200
|
+
init_fs_text_encoding();
|
|
36201
|
+
|
|
36202
|
+
// src/lessons/log-record-guards.ts
|
|
36203
|
+
init_guards();
|
|
36204
|
+
|
|
35117
36205
|
// src/lessons/telemetry.ts
|
|
35118
36206
|
function recallLogPath(projectRoot) {
|
|
35119
36207
|
return join(lessonsPaths(projectRoot).base, "recall-log.jsonl");
|
|
@@ -35250,14 +36338,20 @@ function applyCaps(ranked, options) {
|
|
|
35250
36338
|
}
|
|
35251
36339
|
return out2;
|
|
35252
36340
|
}
|
|
36341
|
+
|
|
36342
|
+
// src/lessons/recall-config.ts
|
|
36343
|
+
init_fs_text_encoding();
|
|
35253
36344
|
function defaultLessonsConfig() {
|
|
35254
36345
|
return {
|
|
35255
36346
|
recallLimit: DEFAULT_RECALL_LIMIT,
|
|
35256
36347
|
recallMaxTokens: DEFAULT_RECALL_MAX_TOKENS,
|
|
35257
36348
|
autoPrune: false,
|
|
35258
|
-
telemetry: false
|
|
36349
|
+
telemetry: false,
|
|
36350
|
+
outcomeLog: true
|
|
35259
36351
|
};
|
|
35260
36352
|
}
|
|
36353
|
+
|
|
36354
|
+
// src/lessons/outcome-log.ts
|
|
35261
36355
|
function outcomeLogPath(projectRoot) {
|
|
35262
36356
|
return join(lessonsPaths(projectRoot).base, "outcome-log.jsonl");
|
|
35263
36357
|
}
|
|
@@ -35273,19 +36367,17 @@ async function mergeLessons(projectRoot, loserId, keeperId, options = {}) {
|
|
|
35273
36367
|
}
|
|
35274
36368
|
function mergeInto(graph, loserId, keeperId) {
|
|
35275
36369
|
if (loserId === keeperId) {
|
|
35276
|
-
throw new Error(`
|
|
36370
|
+
throw new Error(`Cannot merge lesson "${loserId}" into itself.`);
|
|
35277
36371
|
}
|
|
35278
36372
|
const loser = graph.lessons[loserId];
|
|
35279
|
-
if (loser === void 0) throw new Error(`
|
|
36373
|
+
if (loser === void 0) throw new Error(`Unknown lesson "${loserId}".`);
|
|
35280
36374
|
const keeper = graph.lessons[keeperId];
|
|
35281
|
-
if (keeper === void 0) throw new Error(`
|
|
36375
|
+
if (keeper === void 0) throw new Error(`Unknown lesson "${keeperId}".`);
|
|
35282
36376
|
if (keeper.status !== "active") {
|
|
35283
|
-
throw new Error(`
|
|
36377
|
+
throw new Error(`Keeper "${keeperId}" is not active (status: ${keeper.status}).`);
|
|
35284
36378
|
}
|
|
35285
36379
|
if (loser.status !== "active") {
|
|
35286
|
-
throw new Error(
|
|
35287
|
-
`mergeLessons: loser "${loserId}" is already ${loser.status}; nothing to merge.`
|
|
35288
|
-
);
|
|
36380
|
+
throw new Error(`Loser "${loserId}" is already ${loser.status}; nothing to merge.`);
|
|
35289
36381
|
}
|
|
35290
36382
|
graph.lessons[keeperId] = {
|
|
35291
36383
|
...keeper,
|
|
@@ -35340,9 +36432,123 @@ async function stripMarkersInGraph(projectRoot, options = {}) {
|
|
|
35340
36432
|
}
|
|
35341
36433
|
init_recall_hook_scaffold();
|
|
35342
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
|
+
|
|
35343
36463
|
// src/lessons/merge-driver-setup.ts
|
|
35344
36464
|
var LESSONS_MERGE_DRIVER = "agentsmesh-lessons";
|
|
35345
|
-
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
|
+
}
|
|
35346
36552
|
|
|
35347
36553
|
// src/utils/filesystem/gitattributes.ts
|
|
35348
36554
|
init_fs();
|
|
@@ -35483,7 +36689,8 @@ At least one _effective_ trigger is required (or \`--scope always\` for a univer
|
|
|
35483
36689
|
the capture is rejected (\`UNRECALLABLE_LESSON\`); prefer \`--trigger-file\`. No shell \u2192 MCP \`lessons_query\`,
|
|
35484
36690
|
\`lessons_add\`, \`lessons_topics\`, \`lessons_show\`, \`lessons_deprecate\`. Run
|
|
35485
36691
|
\`agentsmesh lessons --help\` for every subcommand and flag: query, add, topics, show,
|
|
35486
|
-
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.
|
|
35487
36694
|
|
|
35488
36695
|
### Rationalization Prevention \u2014 these excuses mean STOP
|
|
35489
36696
|
|
|
@@ -35527,11 +36734,14 @@ async function scaffoldLessons(projectRoot) {
|
|
|
35527
36734
|
const gitignoreUpdated = await ensureGitignoreEntries(projectRoot, [
|
|
35528
36735
|
toRelPath(projectRoot, recallLogPath(projectRoot)),
|
|
35529
36736
|
toRelPath(projectRoot, captureLogPath(projectRoot)),
|
|
35530
|
-
toRelPath(projectRoot, outcomeLogPath(projectRoot))
|
|
36737
|
+
toRelPath(projectRoot, outcomeLogPath(projectRoot)),
|
|
36738
|
+
`${toRelPath(projectRoot, lessonsLockPath(projectRoot))}/`,
|
|
36739
|
+
`${toRelPath(projectRoot, paths2.base)}/*.tmp`
|
|
35531
36740
|
]);
|
|
35532
36741
|
const gitattributesUpdated = await ensureGitattributesEntries(projectRoot, [
|
|
35533
36742
|
LESSONS_GITATTRIBUTES_ENTRY
|
|
35534
36743
|
]);
|
|
36744
|
+
const mergeDriver = ensureLessonsMergeDriver(projectRoot);
|
|
35535
36745
|
return {
|
|
35536
36746
|
created,
|
|
35537
36747
|
updated,
|
|
@@ -35539,7 +36749,9 @@ async function scaffoldLessons(projectRoot) {
|
|
|
35539
36749
|
rootRuleUpdated,
|
|
35540
36750
|
gitignoreUpdated,
|
|
35541
36751
|
gitattributesUpdated,
|
|
35542
|
-
recallHookInjected
|
|
36752
|
+
recallHookInjected,
|
|
36753
|
+
mergeDriver,
|
|
36754
|
+
recallHookTeamHint: recallHookTeamHint(projectRoot)
|
|
35543
36755
|
};
|
|
35544
36756
|
}
|
|
35545
36757
|
function seedLessonsSkill(projectRoot, created, updated, skipped) {
|