agentsmesh 0.29.0 → 0.30.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 +26 -0
- package/README.md +5 -1
- package/dist/canonical.js +28 -9
- package/dist/canonical.js.map +1 -1
- package/dist/cli.js +223 -199
- package/dist/engine.js +38 -12
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +158 -42
- package/dist/index.js.map +1 -1
- package/dist/{init-B0aI-g8W.d.ts → init-PvpXanVd.d.ts} +17 -3
- package/dist/lessons.d.ts +2 -2
- package/dist/lessons.js +195 -40
- package/dist/lessons.js.map +1 -1
- package/dist/targets.js +28 -9
- package/dist/targets.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { AgentsMeshError, AgentsMeshErrorCode, CheckLockSyncOptions, ComputeDiffResult, ConfigNotFoundError, ConfigValidationError, DiffEntry, DiffSummary, FileSystemError, GenerateContext, GenerationError, ImportError, LintOptions, LintResult, LoadProjectContextOptions, LockAcquisitionError, LockSyncReport, ProjectContext, RemoteFetchError, TargetNotFoundError, check, computeDiff, diff, formatDiffSummary, generate, importFrom, lint, loadConfig, loadConfigFromDirectory, loadProjectContext, resolveOutputCollisions } from './engine.js';
|
|
2
2
|
export { LoadCanonicalOptions, loadCanonical, loadCanonicalFiles } from './canonical.js';
|
|
3
3
|
export { getAllDescriptors, getDescriptor, getTargetCatalog, registerTargetDescriptor } from './targets.js';
|
|
4
|
-
export { A as AddLessonInput, a as AddLessonOptions, b as AddLessonResult, c as AddLessonTriggers, D as DEFAULT_RECALL_LIMIT, I as ImportLegacyOptions, d as ImportLegacyReport, L as LESSONS_PROCEDURAL_RULE, e as Lesson, f as LessonStatus, g as LessonsGraph, h as LessonsGraphSchema, i as LessonsPaths, j as LessonsQuery, M as MatchedLesson, k as MergeLessonsOptions, l as MergeLessonsResult, m as MutateOptions, R as RankOptions, n as RankReason, o as RankedLesson, S as ScaffoldLessonsResult, p as StripMarkersOptions, q as StripMarkersReport, T as Topic, r as Trigger, s as TriggerKind, U as UnknownTopicError, V as ValidationFinding, t as ValidationLevel, u as ValidationReport, v as acquireLessonsLock, w as addLesson, x as graphFilePath, y as importLegacyLessons, z as lessonsPaths, B as loadLessonsGraph, C as mergeLessons, E as mutateLessonsGraph, F as parseGraph, G as queryLessons, H as rankLessons, J as scaffoldLessons, K as serializeGraph, N as stripLegacyMarkers, O as stripMarkersInGraph, P as toRelPath, Q as tryLoadLessonsGraph, W as validateLessonsGraph } from './init-
|
|
4
|
+
export { A as AddLessonInput, a as AddLessonOptions, b as AddLessonResult, c as AddLessonTriggers, D as DEFAULT_RECALL_LIMIT, I as ImportLegacyOptions, d as ImportLegacyReport, L as LESSONS_PROCEDURAL_RULE, e as Lesson, f as LessonStatus, g as LessonsGraph, h as LessonsGraphSchema, i as LessonsPaths, j as LessonsQuery, M as MatchedLesson, k as MergeLessonsOptions, l as MergeLessonsResult, m as MutateOptions, R as RankOptions, n as RankReason, o as RankedLesson, S as ScaffoldLessonsResult, p as StripMarkersOptions, q as StripMarkersReport, T as Topic, r as Trigger, s as TriggerKind, U as UnknownTopicError, V as ValidationFinding, t as ValidationLevel, u as ValidationReport, v as acquireLessonsLock, w as addLesson, x as graphFilePath, y as importLegacyLessons, z as lessonsPaths, B as loadLessonsGraph, C as mergeLessons, E as mutateLessonsGraph, F as parseGraph, G as queryLessons, H as rankLessons, J as scaffoldLessons, K as serializeGraph, N as stripLegacyMarkers, O as stripMarkersInGraph, P as toRelPath, Q as tryLoadLessonsGraph, W as validateLessonsGraph } from './init-PvpXanVd.js';
|
|
5
5
|
export { C as CanonicalAgent, a as CanonicalCommand, b as CanonicalFiles, c as CanonicalRule, d as CanonicalSkill, H as HookEntry, e as Hooks, I as IgnorePatterns, M as McpConfig, f as McpServer, P as Permissions, S as SkillSupportingFile, g as StdioMcpServer, U as UrlMcpServer, V as ValidatedConfig } from './schema-CH_JJep8.js';
|
|
6
6
|
export { E as ExtraRuleOutputContext, a as ExtraRuleOutputResolver, F as FeatureLinter, G as GenerateResult, b as GeneratedOutputMerger, c as GlobalTargetSupport, I as ImportPathBuilder, d as ImportResult, L as LintDiagnostic, R as RuleLinter, S as ScopeExtrasFn, T as TargetCapabilities, e as TargetDescriptor, f as TargetGenerators, g as TargetLayout, h as TargetLayoutScope, i as TargetLintHooks, j as TargetManagedOutputs, k as TargetOutputFamily, l as TargetPathResolvers } from './target-descriptor-CaLUz7SR.js';
|
|
7
7
|
import 'zod';
|
package/dist/index.js
CHANGED
|
@@ -2942,6 +2942,18 @@ var init_linter = __esm({
|
|
|
2942
2942
|
}
|
|
2943
2943
|
});
|
|
2944
2944
|
|
|
2945
|
+
// src/core/hook-types.ts
|
|
2946
|
+
var BEST_EFFORT_HOOK_EVENTS;
|
|
2947
|
+
var init_hook_types = __esm({
|
|
2948
|
+
"src/core/hook-types.ts"() {
|
|
2949
|
+
BEST_EFFORT_HOOK_EVENTS = /* @__PURE__ */ new Set([
|
|
2950
|
+
"UserPromptSubmit",
|
|
2951
|
+
"PostToolUseFailure",
|
|
2952
|
+
"SessionStart"
|
|
2953
|
+
]);
|
|
2954
|
+
}
|
|
2955
|
+
});
|
|
2956
|
+
|
|
2945
2957
|
// src/core/lint/shared/helpers.ts
|
|
2946
2958
|
function createWarning(file, target31, message) {
|
|
2947
2959
|
return {
|
|
@@ -2957,6 +2969,13 @@ function formatOxfordComma(items) {
|
|
|
2957
2969
|
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
2958
2970
|
return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
|
|
2959
2971
|
}
|
|
2972
|
+
function unsupportedHookEventNames(hooks, supportedEvents) {
|
|
2973
|
+
if (!hooks) return [];
|
|
2974
|
+
const supported = new Set(supportedEvents);
|
|
2975
|
+
return Object.keys(hooks).filter(
|
|
2976
|
+
(event) => !supported.has(event) && !BEST_EFFORT_HOOK_EVENTS.has(event)
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2960
2979
|
function createUnsupportedHookWarning(event, target31, supportedEvents, options) {
|
|
2961
2980
|
const by = options?.unsupportedBy ?? target31;
|
|
2962
2981
|
const supported = formatOxfordComma(supportedEvents);
|
|
@@ -2968,6 +2987,7 @@ function createUnsupportedHookWarning(event, target31, supportedEvents, options)
|
|
|
2968
2987
|
}
|
|
2969
2988
|
var init_helpers = __esm({
|
|
2970
2989
|
"src/core/lint/shared/helpers.ts"() {
|
|
2990
|
+
init_hook_types();
|
|
2971
2991
|
}
|
|
2972
2992
|
});
|
|
2973
2993
|
|
|
@@ -6311,8 +6331,7 @@ var init_linter5 = __esm({
|
|
|
6311
6331
|
// src/targets/augment-code/lint.ts
|
|
6312
6332
|
function lintHooks3(canonical) {
|
|
6313
6333
|
if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
|
|
6314
|
-
|
|
6315
|
-
return Object.keys(canonical.hooks).filter((event) => !supportedSet.has(event)).map(
|
|
6334
|
+
return unsupportedHookEventNames(canonical.hooks, SUPPORTED_HOOK_EVENTS).map(
|
|
6316
6335
|
(event) => createUnsupportedHookWarning(event, "augment-code", SUPPORTED_HOOK_EVENTS, {
|
|
6317
6336
|
unsupportedBy: "AugmentCode hooks"
|
|
6318
6337
|
})
|
|
@@ -10317,8 +10336,7 @@ function lintCommands3(canonical) {
|
|
|
10317
10336
|
function lintHooks5(canonical) {
|
|
10318
10337
|
if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
|
|
10319
10338
|
const supported = ["PreToolUse", "PostToolUse", "Notification", "UserPromptSubmit"];
|
|
10320
|
-
const
|
|
10321
|
-
const diagnostics = Object.keys(canonical.hooks).filter((event) => !supportedSet.has(event)).map(
|
|
10339
|
+
const diagnostics = unsupportedHookEventNames(canonical.hooks, supported).map(
|
|
10322
10340
|
(event) => createUnsupportedHookWarning(event, "copilot", supported, {
|
|
10323
10341
|
unsupportedBy: "Copilot hooks"
|
|
10324
10342
|
})
|
|
@@ -11436,7 +11454,7 @@ var init_permissions2 = __esm({
|
|
|
11436
11454
|
// src/targets/cursor/hook-format.ts
|
|
11437
11455
|
function unmappedCursorHookEvents(hooks) {
|
|
11438
11456
|
return Object.keys(hooks).filter(
|
|
11439
|
-
(event) => Array.isArray(hooks[event]) && hooks[event].length > 0 && !(event in CANONICAL_TO_CURSOR)
|
|
11457
|
+
(event) => Array.isArray(hooks[event]) && hooks[event].length > 0 && !(event in CANONICAL_TO_CURSOR) && !BEST_EFFORT_HOOK_EVENTS.has(event)
|
|
11440
11458
|
);
|
|
11441
11459
|
}
|
|
11442
11460
|
function toCursorHooks(hooks) {
|
|
@@ -11484,6 +11502,7 @@ function cursorHooksToCanonical(hooks) {
|
|
|
11484
11502
|
var CANONICAL_TO_CURSOR, CURSOR_TO_CANONICAL;
|
|
11485
11503
|
var init_hook_format = __esm({
|
|
11486
11504
|
"src/targets/cursor/hook-format.ts"() {
|
|
11505
|
+
init_hook_types();
|
|
11487
11506
|
init_hook_command();
|
|
11488
11507
|
CANONICAL_TO_CURSOR = {
|
|
11489
11508
|
PreToolUse: "preToolUse",
|
|
@@ -13934,8 +13953,9 @@ function lintCommands6(canonical) {
|
|
|
13934
13953
|
function lintHooks7(canonical) {
|
|
13935
13954
|
if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
|
|
13936
13955
|
const supported = ["PreToolUse", "PostToolUse", "Notification"];
|
|
13937
|
-
|
|
13938
|
-
|
|
13956
|
+
return unsupportedHookEventNames(canonical.hooks, supported).map(
|
|
13957
|
+
(event) => createUnsupportedHookWarning(event, "gemini-cli", supported)
|
|
13958
|
+
);
|
|
13939
13959
|
}
|
|
13940
13960
|
var init_lint14 = __esm({
|
|
13941
13961
|
"src/targets/gemini-cli/lint.ts"() {
|
|
@@ -16187,8 +16207,7 @@ var init_linter20 = __esm({
|
|
|
16187
16207
|
function lintHooks10(canonical) {
|
|
16188
16208
|
if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
|
|
16189
16209
|
const supported = ["PreToolUse", "PostToolUse", "UserPromptSubmit", "SubagentStop"];
|
|
16190
|
-
|
|
16191
|
-
return Object.keys(canonical.hooks).filter((event) => !supportedSet.has(event)).map(
|
|
16210
|
+
return unsupportedHookEventNames(canonical.hooks, supported).map(
|
|
16192
16211
|
(event) => createUnsupportedHookWarning(event, "kiro", supported, { unsupportedBy: "Kiro hooks" })
|
|
16193
16212
|
);
|
|
16194
16213
|
}
|
|
@@ -24283,7 +24302,8 @@ function lintRuleScopeInversion(input) {
|
|
|
24283
24302
|
}
|
|
24284
24303
|
return out2;
|
|
24285
24304
|
}
|
|
24286
|
-
var CURRENT_GRAPH_VERSION =
|
|
24305
|
+
var CURRENT_GRAPH_VERSION = 2;
|
|
24306
|
+
var VersionSchema = z.union([z.literal(1), z.literal(2)]);
|
|
24287
24307
|
var MAX_RULE_LENGTH = 2e3;
|
|
24288
24308
|
var IdSchema = z.string().regex(/^[a-z0-9-]+$/, "id must be kebab-case");
|
|
24289
24309
|
var DateSchema = z.string().regex(
|
|
@@ -24307,10 +24327,17 @@ var LessonSchema = z.object({
|
|
|
24307
24327
|
evidence: z.array(z.string().min(1)),
|
|
24308
24328
|
status: LessonStatusSchema,
|
|
24309
24329
|
supersededBy: IdSchema.optional(),
|
|
24310
|
-
createdAt: DateSchema
|
|
24330
|
+
createdAt: DateSchema,
|
|
24331
|
+
/**
|
|
24332
|
+
* `'always'` marks an ALWAYS-ON lesson: a universal standard delivered on
|
|
24333
|
+
* every task (via the UserPromptSubmit hook / a `--always` recall) rather than
|
|
24334
|
+
* matched by triggers. Such a lesson needs no trigger and is excluded from
|
|
24335
|
+
* triggered recall. Absent = a normal triggered lesson.
|
|
24336
|
+
*/
|
|
24337
|
+
scope: z.literal("always").optional()
|
|
24311
24338
|
}).strict();
|
|
24312
24339
|
var LessonsGraphSchema = z.object({
|
|
24313
|
-
version:
|
|
24340
|
+
version: VersionSchema,
|
|
24314
24341
|
lessons: z.record(IdSchema, LessonSchema),
|
|
24315
24342
|
topics: z.record(IdSchema, TopicSchema),
|
|
24316
24343
|
triggers: z.record(IdSchema, TriggerSchema)
|
|
@@ -24375,7 +24402,7 @@ var LESSONS_PROCEDURAL_RULE = `## Lessons (BLOCKING)
|
|
|
24375
24402
|
|
|
24376
24403
|
Graph \`.agentsmesh/lessons/lessons.json\` is canonical; never hand-edit it. Manual: \`lessons\` skill.
|
|
24377
24404
|
|
|
24378
|
-
**Recall:** before every file edit or state-changing command, MUST run \`agentsmesh lessons query --file <path> --cmd <command>\` and obey matches. Pure-read commands and recall itself are exempt.
|
|
24405
|
+
**Recall:** before every file edit or state-changing command, MUST run \`agentsmesh lessons query --file <path> --cmd <command>\` and obey matches; at task start, ALSO run \`agentsmesh lessons query --keyword "<task terms>" --always\` for conceptual + universal rules no path/command names. Pure-read commands and recall itself are exempt.
|
|
24379
24406
|
|
|
24380
24407
|
**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>\`.
|
|
24381
24408
|
|
|
@@ -25251,6 +25278,12 @@ function collectDeadFileGlobs(graph, findings, knownPaths) {
|
|
|
25251
25278
|
});
|
|
25252
25279
|
}
|
|
25253
25280
|
}
|
|
25281
|
+
function fileGlobMatchCount(pattern, knownPaths) {
|
|
25282
|
+
const isMatch = picomatch(pattern, { dot: true });
|
|
25283
|
+
let n = 0;
|
|
25284
|
+
for (const p of knownPaths) if (isMatch(p)) n += 1;
|
|
25285
|
+
return n;
|
|
25286
|
+
}
|
|
25254
25287
|
var RUNNER_ANCHOR = /^\^(pnpm|npm|npx|yarn|bun)\b/;
|
|
25255
25288
|
function collectRunnerAnchoredPatterns(graph, findings) {
|
|
25256
25289
|
const active = activeTriggerIds(graph);
|
|
@@ -25881,7 +25914,7 @@ var UnrecallableLessonError = class extends Error {
|
|
|
25881
25914
|
};
|
|
25882
25915
|
|
|
25883
25916
|
// src/lessons/capture-guardrails.ts
|
|
25884
|
-
var
|
|
25917
|
+
var WIDE_GLOB_MATCH_COUNT = 40;
|
|
25885
25918
|
var MAX_RECOMMENDED_TRIGGERS = 8;
|
|
25886
25919
|
function isBroadGlob(pattern) {
|
|
25887
25920
|
const p = pattern.trim();
|
|
@@ -25937,9 +25970,19 @@ function inspectCapturedLesson(graph, lessonId, knownPaths) {
|
|
|
25937
25970
|
message: `Lesson "${lessonId}" has file_glob trigger(s) (${deadHere.join(", ")}) that match no file in the working tree \u2014 likely a rename. Re-point them at the current path, or the lesson is unreachable via those globs.`
|
|
25938
25971
|
});
|
|
25939
25972
|
}
|
|
25973
|
+
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);
|
|
25974
|
+
if (wide.length > 0) {
|
|
25975
|
+
warnings.push({
|
|
25976
|
+
code: "WIDE_GLOB_MATCH",
|
|
25977
|
+
message: `Lesson "${lessonId}" has file glob(s) (${wide.join(", ")}) matching more than ${WIDE_GLOB_MATCH_COUNT} files in the working tree; narrow to the file-CLASS where the rule actually applies so it does not fire on unrelated edits.`
|
|
25978
|
+
});
|
|
25979
|
+
}
|
|
25940
25980
|
}
|
|
25941
25981
|
return warnings;
|
|
25942
25982
|
}
|
|
25983
|
+
|
|
25984
|
+
// src/lessons/capture-near-duplicate.ts
|
|
25985
|
+
var NEAR_DUPLICATE_THRESHOLD = 0.6;
|
|
25943
25986
|
function nearDuplicateWarning(graph, lessonId) {
|
|
25944
25987
|
const subject = graph.lessons[lessonId];
|
|
25945
25988
|
if (subject === void 0) return null;
|
|
@@ -26159,7 +26202,7 @@ async function importLegacyLessons(projectRoot, options) {
|
|
|
26159
26202
|
if (options.force !== true && populated) {
|
|
26160
26203
|
throw new LessonsGraphExistsError();
|
|
26161
26204
|
}
|
|
26162
|
-
g.version =
|
|
26205
|
+
g.version = CURRENT_GRAPH_VERSION;
|
|
26163
26206
|
g.lessons = lessons;
|
|
26164
26207
|
g.topics = topics;
|
|
26165
26208
|
g.triggers = triggers;
|
|
@@ -26310,7 +26353,7 @@ function getHostname() {
|
|
|
26310
26353
|
return hostname();
|
|
26311
26354
|
}
|
|
26312
26355
|
function sleep2(ms) {
|
|
26313
|
-
return new Promise((
|
|
26356
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
26314
26357
|
}
|
|
26315
26358
|
|
|
26316
26359
|
// src/lessons/lessons-lock.ts
|
|
@@ -26326,7 +26369,7 @@ async function acquireLessonsLock(projectRoot, opts = {}) {
|
|
|
26326
26369
|
|
|
26327
26370
|
// src/lessons/mutate.ts
|
|
26328
26371
|
function emptyGraph() {
|
|
26329
|
-
return { version:
|
|
26372
|
+
return { version: CURRENT_GRAPH_VERSION, lessons: {}, topics: {}, triggers: {} };
|
|
26330
26373
|
}
|
|
26331
26374
|
function findingKey(f) {
|
|
26332
26375
|
return `${f.code}|${f.triggerId ?? ""}|${f.lessonId ?? ""}`;
|
|
@@ -26350,6 +26393,7 @@ async function mutateLessonsGraphLocked(projectRoot, mutator, options = {}) {
|
|
|
26350
26393
|
`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.)`
|
|
26351
26394
|
);
|
|
26352
26395
|
}
|
|
26396
|
+
graph.version = CURRENT_GRAPH_VERSION;
|
|
26353
26397
|
saveLessonsGraph(projectRoot, graph);
|
|
26354
26398
|
return result;
|
|
26355
26399
|
} finally {
|
|
@@ -26427,14 +26471,15 @@ function addLessonInto(graph, input, options) {
|
|
|
26427
26471
|
}
|
|
26428
26472
|
graph.topics[input.topic] = { summary: options.topicSummary };
|
|
26429
26473
|
}
|
|
26430
|
-
|
|
26474
|
+
const skipTriggerGates = options.allowNoTrigger === true || input.scope === "always";
|
|
26475
|
+
if (!skipTriggerGates) {
|
|
26431
26476
|
const existingTriggers = existingId !== null ? graph.lessons[existingId]?.triggers.length ?? 0 : 0;
|
|
26432
26477
|
if (countInputTriggers(input.triggers) === 0 && existingTriggers === 0) {
|
|
26433
26478
|
throw new NoTriggerError();
|
|
26434
26479
|
}
|
|
26435
26480
|
}
|
|
26436
26481
|
const { triggerIds, newTriggerIds } = mergeTriggers(graph, input.triggers);
|
|
26437
|
-
if (
|
|
26482
|
+
if (!skipTriggerGates) {
|
|
26438
26483
|
const resultingTriggers = existingId !== null ? union(graph.lessons[existingId].triggers, triggerIds) : triggerIds;
|
|
26439
26484
|
const blockingDead = blockingDeadTriggers(graph, resultingTriggers);
|
|
26440
26485
|
if (resultingTriggers.length > 0 && blockingDead.length === resultingTriggers.length) {
|
|
@@ -26448,7 +26493,9 @@ function addLessonInto(graph, input, options) {
|
|
|
26448
26493
|
topics: union(existing.topics, [input.topic]),
|
|
26449
26494
|
triggers: union(existing.triggers, triggerIds),
|
|
26450
26495
|
evidence: union(existing.evidence, input.evidence ?? []),
|
|
26451
|
-
...existing.rationale === void 0 && input.rationale !== void 0 ? { rationale: input.rationale } : {}
|
|
26496
|
+
...existing.rationale === void 0 && input.rationale !== void 0 ? { rationale: input.rationale } : {},
|
|
26497
|
+
// Re-capturing a rule with --scope always promotes it to always-on.
|
|
26498
|
+
...input.scope === "always" ? { scope: "always" } : {}
|
|
26452
26499
|
};
|
|
26453
26500
|
return {
|
|
26454
26501
|
id: existingId,
|
|
@@ -26468,7 +26515,8 @@ function addLessonInto(graph, input, options) {
|
|
|
26468
26515
|
evidence: input.evidence === void 0 ? [] : [...input.evidence],
|
|
26469
26516
|
status: "active",
|
|
26470
26517
|
createdAt: input.createdAt ?? todayIso(),
|
|
26471
|
-
...input.rationale === void 0 ? {} : { rationale: input.rationale }
|
|
26518
|
+
...input.rationale === void 0 ? {} : { rationale: input.rationale },
|
|
26519
|
+
...input.scope === "always" ? { scope: "always" } : {}
|
|
26472
26520
|
};
|
|
26473
26521
|
const warnings = inspectCapturedLesson(graph, id, options.knownPaths);
|
|
26474
26522
|
const nearDup = nearDuplicateWarning(graph, id);
|
|
@@ -26520,14 +26568,19 @@ function captureLogPath(projectRoot) {
|
|
|
26520
26568
|
}
|
|
26521
26569
|
|
|
26522
26570
|
// src/lessons/keyword-match.ts
|
|
26523
|
-
function splitTokens(text) {
|
|
26524
|
-
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
|
|
26525
|
-
}
|
|
26526
26571
|
function deriveHaystackTokens(query) {
|
|
26527
26572
|
const parts = [];
|
|
26528
26573
|
if (query.file !== void 0) parts.push(query.file);
|
|
26529
26574
|
if (query.command !== void 0) parts.push(query.command);
|
|
26530
|
-
|
|
26575
|
+
if (parts.length === 0) return [];
|
|
26576
|
+
const out2 = [];
|
|
26577
|
+
for (const raw of parts.join(" ").split(/[^A-Za-z0-9]+/)) {
|
|
26578
|
+
if (raw.length === 0) continue;
|
|
26579
|
+
out2.push(raw.toLowerCase());
|
|
26580
|
+
const sub = raw.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(" ").filter((t) => t.length > 0);
|
|
26581
|
+
if (sub.length > 1) out2.push(...sub);
|
|
26582
|
+
}
|
|
26583
|
+
return out2;
|
|
26531
26584
|
}
|
|
26532
26585
|
function containsRun(needle, hay) {
|
|
26533
26586
|
if (needle.length === 0) return false;
|
|
@@ -26560,6 +26613,7 @@ function queryLessons(graph, query) {
|
|
|
26560
26613
|
const matched = [];
|
|
26561
26614
|
for (const [id, lesson] of Object.entries(graph.lessons)) {
|
|
26562
26615
|
if (lesson.status !== "active") continue;
|
|
26616
|
+
if (lesson.scope === "always") continue;
|
|
26563
26617
|
if (lesson.triggers.some((t) => matchedTriggerIds.has(t))) {
|
|
26564
26618
|
matched.push({ id, lesson });
|
|
26565
26619
|
}
|
|
@@ -26605,6 +26659,7 @@ var RRF_K = 60;
|
|
|
26605
26659
|
var SPECIFICITY_WEIGHT = 3;
|
|
26606
26660
|
var TOPIC_COHERENCE_WEIGHT = 2;
|
|
26607
26661
|
var BM25_WEIGHT = 1;
|
|
26662
|
+
var EFFECTIVENESS_WEIGHT = 1;
|
|
26608
26663
|
function rankMap(items) {
|
|
26609
26664
|
const sorted = [...items].sort(
|
|
26610
26665
|
(a, b) => b.value !== a.value ? b.value - a.value : a.id < b.id ? -1 : 1
|
|
@@ -26641,16 +26696,20 @@ function rankLessons(graph, query, matches, options = {}) {
|
|
|
26641
26696
|
specificity,
|
|
26642
26697
|
// `id` is a matched lesson, and buildTopicCoherence keys every matched id.
|
|
26643
26698
|
topicCoherence: coherence.get(id),
|
|
26644
|
-
matchedTriggers: hitTriggers
|
|
26699
|
+
matchedTriggers: hitTriggers,
|
|
26700
|
+
// Absent from the log ⇒ neutral 1, so a lesson with no outcome data never
|
|
26701
|
+
// sinks below one that has merely been recorded.
|
|
26702
|
+
effectiveness: options.effectiveness?.get(id) ?? 1
|
|
26645
26703
|
};
|
|
26646
26704
|
});
|
|
26647
26705
|
const bm25Ranks = rankMap(scored.map((s) => ({ id: s.id, value: s.bm25 })));
|
|
26648
26706
|
const specRanks = rankMap(scored.map((s) => ({ id: s.id, value: s.specificity })));
|
|
26649
26707
|
const topicRanks = rankMap(scored.map((s) => ({ id: s.id, value: s.topicCoherence })));
|
|
26708
|
+
const effRanks = rankMap(scored.map((s) => ({ id: s.id, value: s.effectiveness })));
|
|
26650
26709
|
const ranked = scored.map((s) => ({
|
|
26651
26710
|
id: s.id,
|
|
26652
26711
|
lesson: s.lesson,
|
|
26653
|
-
score: SPECIFICITY_WEIGHT / (RRF_K + specRanks.get(s.id)) + TOPIC_COHERENCE_WEIGHT / (RRF_K + topicRanks.get(s.id)) + BM25_WEIGHT / (RRF_K + bm25Ranks.get(s.id)),
|
|
26712
|
+
score: SPECIFICITY_WEIGHT / (RRF_K + specRanks.get(s.id)) + TOPIC_COHERENCE_WEIGHT / (RRF_K + topicRanks.get(s.id)) + BM25_WEIGHT / (RRF_K + bm25Ranks.get(s.id)) + EFFECTIVENESS_WEIGHT / (RRF_K + effRanks.get(s.id)),
|
|
26654
26713
|
reason: {
|
|
26655
26714
|
matchedTriggers: s.matchedTriggers,
|
|
26656
26715
|
bm25: s.bm25,
|
|
@@ -26689,6 +26748,9 @@ function defaultLessonsConfig() {
|
|
|
26689
26748
|
autoPrune: false
|
|
26690
26749
|
};
|
|
26691
26750
|
}
|
|
26751
|
+
function outcomeLogPath(projectRoot) {
|
|
26752
|
+
return join(lessonsPaths(projectRoot).base, "outcome-log.jsonl");
|
|
26753
|
+
}
|
|
26692
26754
|
|
|
26693
26755
|
// src/lessons/merge.ts
|
|
26694
26756
|
async function mergeLessons(projectRoot, loserId, keeperId, options = {}) {
|
|
@@ -26771,18 +26833,27 @@ async function stripMarkersInGraph(projectRoot, options = {}) {
|
|
|
26771
26833
|
return { changedIds, changedCount: changedIds.length };
|
|
26772
26834
|
}
|
|
26773
26835
|
var RECALL_HOOK_COMMAND = "agentsmesh lessons hook";
|
|
26774
|
-
var
|
|
26775
|
-
var RECALL_EVENTS = [
|
|
26776
|
-
|
|
26836
|
+
var RECALL_HOOK_TOOL_MATCHER = "Edit|Write|Bash";
|
|
26837
|
+
var RECALL_EVENTS = [
|
|
26838
|
+
{ event: "PreToolUse", matcher: RECALL_HOOK_TOOL_MATCHER },
|
|
26839
|
+
{ event: "PostToolUse", matcher: RECALL_HOOK_TOOL_MATCHER },
|
|
26840
|
+
{ event: "UserPromptSubmit", matcher: "*" },
|
|
26841
|
+
// Capture-on-failure nudge (see capture-nudge.ts). BEST-EFFORT: only Claude
|
|
26842
|
+
// Code's passthrough hooks emit it; whitelist targets drop it without warning
|
|
26843
|
+
// (BEST_EFFORT_HOOK_EVENTS). PostToolUse is success-only, so failures need this.
|
|
26844
|
+
{ event: "PostToolUseFailure", matcher: "*" },
|
|
26845
|
+
// Reset recall dedup after a context compaction/clear (see hook.ts SessionStart).
|
|
26846
|
+
// BEST-EFFORT: targets that can't represent SessionStart just keep dedup as-is.
|
|
26847
|
+
{ event: "SessionStart", matcher: "*" }
|
|
26848
|
+
];
|
|
26849
|
+
function injectEvent(doc, event, matcher) {
|
|
26777
26850
|
const existing = doc.get(event);
|
|
26778
26851
|
const seq = existing instanceof YAMLSeq ? existing : new YAMLSeq();
|
|
26779
26852
|
const present = seq.items.some(
|
|
26780
26853
|
(item) => item instanceof YAMLMap && item.get("command") === RECALL_HOOK_COMMAND
|
|
26781
26854
|
);
|
|
26782
26855
|
if (present) return false;
|
|
26783
|
-
seq.add(
|
|
26784
|
-
doc.createNode({ matcher: RECALL_HOOK_MATCHER, type: "command", command: RECALL_HOOK_COMMAND })
|
|
26785
|
-
);
|
|
26856
|
+
seq.add(doc.createNode({ matcher, type: "command", command: RECALL_HOOK_COMMAND }));
|
|
26786
26857
|
doc.set(event, seq);
|
|
26787
26858
|
return true;
|
|
26788
26859
|
}
|
|
@@ -26791,13 +26862,32 @@ function injectRecallHook(projectRoot) {
|
|
|
26791
26862
|
if (!existsSync(path)) return false;
|
|
26792
26863
|
const doc = parseDocument(readFileSync(path, "utf8"));
|
|
26793
26864
|
let changed = false;
|
|
26794
|
-
for (const event of RECALL_EVENTS) {
|
|
26795
|
-
if (injectEvent(doc, event)) changed = true;
|
|
26865
|
+
for (const { event, matcher } of RECALL_EVENTS) {
|
|
26866
|
+
if (injectEvent(doc, event, matcher)) changed = true;
|
|
26796
26867
|
}
|
|
26797
26868
|
if (changed) writeFileSync(path, String(doc), "utf8");
|
|
26798
26869
|
return changed;
|
|
26799
26870
|
}
|
|
26800
26871
|
|
|
26872
|
+
// src/lessons/merge-driver-setup.ts
|
|
26873
|
+
var LESSONS_MERGE_DRIVER = "agentsmesh-lessons";
|
|
26874
|
+
var LESSONS_GITATTRIBUTES_ENTRY = `.agentsmesh/lessons/lessons.json merge=${LESSONS_MERGE_DRIVER}`;
|
|
26875
|
+
|
|
26876
|
+
// src/utils/filesystem/gitattributes.ts
|
|
26877
|
+
init_fs();
|
|
26878
|
+
async function ensureGitattributesEntries(projectRoot, entries) {
|
|
26879
|
+
const path = join(projectRoot, ".gitattributes");
|
|
26880
|
+
const current = await readFileSafe(path) ?? "";
|
|
26881
|
+
const existing = new Set(
|
|
26882
|
+
current.split("\n").map((s) => s.trim()).filter((s) => s.length > 0 && !s.startsWith("#"))
|
|
26883
|
+
);
|
|
26884
|
+
const toAdd = entries.filter((e) => !existing.has(e.trim()));
|
|
26885
|
+
if (toAdd.length === 0) return false;
|
|
26886
|
+
const suffix = current.endsWith("\n") || current === "" ? "" : "\n";
|
|
26887
|
+
await writeFileAtomic(path, current + suffix + toAdd.join("\n") + "\n");
|
|
26888
|
+
return true;
|
|
26889
|
+
}
|
|
26890
|
+
|
|
26801
26891
|
// src/utils/filesystem/gitignore.ts
|
|
26802
26892
|
init_fs();
|
|
26803
26893
|
async function ensureGitignoreEntries(projectRoot, entries) {
|
|
@@ -26874,23 +26964,37 @@ regression / wrong assumption / surprise and you have not captured (nor stated
|
|
|
26874
26964
|
\`Lesson: none\`)? The task is INCOMPLETE \u2014 and the user will check. The graph
|
|
26875
26965
|
\`.agentsmesh/lessons/lessons.json\` is canonical \u2014 never hand-edit.
|
|
26876
26966
|
|
|
26877
|
-
## Recall \u2014 before each
|
|
26967
|
+
## Recall \u2014 before each edit/command, and at task start
|
|
26878
26968
|
|
|
26879
26969
|
\`agentsmesh lessons query --file <path> --cmd <command>\`, then apply every rule.
|
|
26880
26970
|
Pure-read commands (read-only) and the query itself are exempt. **keyword-only recall
|
|
26881
|
-
is the anti-pattern** \u2014
|
|
26971
|
+
for a specific edit is the anti-pattern** \u2014 anchor those to \`--file\`/\`--cmd\`. But at the
|
|
26972
|
+
START of a task (or when planning), run \`agentsmesh lessons query --keyword "<the task's
|
|
26973
|
+
key terms>" --always\`: that surfaces the conceptual rules no file/command names PLUS the
|
|
26974
|
+
universal always-on lessons \u2014 the manual equivalent of the automatic prompt recall on
|
|
26975
|
+
hook-capable tools. Author a \`keyword\` trigger beside a \`file_glob\` on conceptual lessons
|
|
26976
|
+
so they are reachable both ways. No shell \u2192 MCP \`lessons_query\` (\`file\`/\`command\`/\`keyword\`/\`always\`).
|
|
26882
26977
|
|
|
26883
26978
|
## Capture \u2014 Gate Function (before any completion claim)
|
|
26884
26979
|
|
|
26885
26980
|
1. **SELF-CRITIQUE**: any failure, correction, regression, wrong assumption,
|
|
26886
26981
|
useful surprise, repeated friction, or non-obvious fix? Failing
|
|
26887
26982
|
tests/lint/typecheck and user/review corrections \u2014 yours or anyone's \u2014 all count.
|
|
26983
|
+
PROCESS/TOOLING surprises count too (a sandbox command that silently no-op'd, a port
|
|
26984
|
+
already in use, a library's argument shape) \u2014 trigger those with \`--trigger-cmd\`. So
|
|
26985
|
+
does a review finding you REVIEWED then deliberately REJECTED: capture it as a "do NOT
|
|
26986
|
+
refactor" guard so the reasoning survives the session.
|
|
26888
26987
|
2. **CAPTURE** a reusable imperative rule with an effective trigger (else say so):
|
|
26889
|
-
\`agentsmesh lessons add "<rule>" --topic <id> --trigger-file <glob
|
|
26988
|
+
\`agentsmesh lessons add "<rule>" --topic <id> --trigger-file <glob>\`. Trigger on the
|
|
26989
|
+
file-CLASS where it will RECUR (e.g. \`**/lib/use*Form*.ts\`), NOT the single file you
|
|
26990
|
+
discovered it in \u2014 and NOT a broad \`src/**\`. If the rule is a UNIVERSAL standard that
|
|
26991
|
+
applies to EVERY task (a comment/test/style convention no file or command names),
|
|
26992
|
+
capture it with \`--scope always\` instead \u2014 it needs no trigger and is delivered on
|
|
26993
|
+
every task.
|
|
26890
26994
|
3. **RECEIPT**: emit \`Lesson: captured <id>\` or \`Lesson: none\`.
|
|
26891
26995
|
|
|
26892
|
-
At least one _effective_ trigger is required or
|
|
26893
|
-
(\`UNRECALLABLE_LESSON\`); prefer \`--trigger-file\`. No shell \u2192 MCP \`lessons_query\`,
|
|
26996
|
+
At least one _effective_ trigger is required (or \`--scope always\` for a universal rule) or
|
|
26997
|
+
the capture is rejected (\`UNRECALLABLE_LESSON\`); prefer \`--trigger-file\`. No shell \u2192 MCP \`lessons_query\`,
|
|
26894
26998
|
\`lessons_add\`, \`lessons_topics\`, \`lessons_show\`, \`lessons_deprecate\`. Run
|
|
26895
26999
|
\`agentsmesh lessons --help\` for every subcommand and flag: query, add, topics, show,
|
|
26896
27000
|
deprecate, merge, untrigger, strip-markers, prune, journal, validate, stats, import-md.
|
|
@@ -26936,9 +27040,21 @@ async function scaffoldLessons(projectRoot) {
|
|
|
26936
27040
|
const recallHookInjected = injectRecallHook(projectRoot);
|
|
26937
27041
|
const gitignoreUpdated = await ensureGitignoreEntries(projectRoot, [
|
|
26938
27042
|
toRelPath(projectRoot, recallLogPath(projectRoot)),
|
|
26939
|
-
toRelPath(projectRoot, captureLogPath(projectRoot))
|
|
27043
|
+
toRelPath(projectRoot, captureLogPath(projectRoot)),
|
|
27044
|
+
toRelPath(projectRoot, outcomeLogPath(projectRoot))
|
|
27045
|
+
]);
|
|
27046
|
+
const gitattributesUpdated = await ensureGitattributesEntries(projectRoot, [
|
|
27047
|
+
LESSONS_GITATTRIBUTES_ENTRY
|
|
26940
27048
|
]);
|
|
26941
|
-
return {
|
|
27049
|
+
return {
|
|
27050
|
+
created,
|
|
27051
|
+
updated,
|
|
27052
|
+
skipped,
|
|
27053
|
+
rootRuleUpdated,
|
|
27054
|
+
gitignoreUpdated,
|
|
27055
|
+
gitattributesUpdated,
|
|
27056
|
+
recallHookInjected
|
|
27057
|
+
};
|
|
26942
27058
|
}
|
|
26943
27059
|
function seedLessonsSkill(projectRoot, created, updated, skipped) {
|
|
26944
27060
|
const skillPath = join(projectRoot, ".agentsmesh/skills", LESSONS_SKILL_NAME, "SKILL.md");
|