agentsmesh 0.20.0 → 0.22.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 +70 -0
- package/README.md +14 -1
- package/dist/canonical.js +81 -13
- package/dist/canonical.js.map +1 -1
- package/dist/cli.js +206 -162
- package/dist/engine.js +181 -15
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +651 -232
- package/dist/index.js.map +1 -1
- package/dist/lessons.d.ts +155 -0
- package/dist/lessons.js +305 -0
- package/dist/lessons.js.map +1 -0
- package/dist/targets.js +22 -2
- package/dist/targets.js.map +1 -1
- package/package.json +12 -3
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
declare function hashBullet(bullet: string): string;
|
|
4
|
+
|
|
5
|
+
interface ParsedBullet {
|
|
6
|
+
text: string;
|
|
7
|
+
lineNumber: number;
|
|
8
|
+
}
|
|
9
|
+
declare function parseBullets(markdown: string): ParsedBullet[];
|
|
10
|
+
|
|
11
|
+
declare const ClusterSchema: z.ZodObject<{
|
|
12
|
+
topic: z.ZodString;
|
|
13
|
+
file: z.ZodString;
|
|
14
|
+
summary: z.ZodString;
|
|
15
|
+
triggers: z.ZodObject<{
|
|
16
|
+
file_globs: z.ZodArray<z.ZodString>;
|
|
17
|
+
command_patterns: z.ZodArray<z.ZodString>;
|
|
18
|
+
keywords: z.ZodArray<z.ZodString>;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
declare const LessonsIndexSchema: z.ZodObject<{
|
|
22
|
+
version: z.ZodLiteral<1>;
|
|
23
|
+
clusters: z.ZodArray<z.ZodObject<{
|
|
24
|
+
topic: z.ZodString;
|
|
25
|
+
file: z.ZodString;
|
|
26
|
+
summary: z.ZodString;
|
|
27
|
+
triggers: z.ZodObject<{
|
|
28
|
+
file_globs: z.ZodArray<z.ZodString>;
|
|
29
|
+
command_patterns: z.ZodArray<z.ZodString>;
|
|
30
|
+
keywords: z.ZodArray<z.ZodString>;
|
|
31
|
+
}, z.core.$strip>;
|
|
32
|
+
}, z.core.$strip>>;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
type LessonsIndex = z.infer<typeof LessonsIndexSchema>;
|
|
35
|
+
type LessonsCluster = z.infer<typeof ClusterSchema>;
|
|
36
|
+
declare function parseIndex(raw: unknown): LessonsIndex;
|
|
37
|
+
|
|
38
|
+
type ToolEvent = {
|
|
39
|
+
kind: 'edit' | 'write';
|
|
40
|
+
filePath: string;
|
|
41
|
+
} | {
|
|
42
|
+
kind: 'bash';
|
|
43
|
+
command: string;
|
|
44
|
+
} | {
|
|
45
|
+
kind: 'task';
|
|
46
|
+
text: string;
|
|
47
|
+
};
|
|
48
|
+
declare function matchTriggers(clusters: readonly LessonsCluster[], event: ToolEvent): LessonsCluster[];
|
|
49
|
+
|
|
50
|
+
declare const LedgerSchema: z.ZodObject<{
|
|
51
|
+
version: z.ZodLiteral<1>;
|
|
52
|
+
assignments: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
type Ledger = z.infer<typeof LedgerSchema>;
|
|
55
|
+
declare function loadLedger(path: string): Ledger;
|
|
56
|
+
declare function saveLedger(path: string, ledger: Ledger): void;
|
|
57
|
+
|
|
58
|
+
interface ScoredCluster {
|
|
59
|
+
cluster: LessonsCluster;
|
|
60
|
+
score: number;
|
|
61
|
+
}
|
|
62
|
+
declare function scoreBullet(bullet: string, clusters: readonly LessonsCluster[]): ScoredCluster[];
|
|
63
|
+
|
|
64
|
+
interface TriggeredLesson {
|
|
65
|
+
readonly cluster: LessonsCluster;
|
|
66
|
+
readonly relativePath: string;
|
|
67
|
+
readonly filePath: string;
|
|
68
|
+
readonly content: string;
|
|
69
|
+
}
|
|
70
|
+
interface LessonCaptureInput {
|
|
71
|
+
readonly heading: string;
|
|
72
|
+
readonly whatWentWrong: string;
|
|
73
|
+
readonly rootCause: string;
|
|
74
|
+
readonly rule: string;
|
|
75
|
+
}
|
|
76
|
+
interface AppendLessonResult {
|
|
77
|
+
readonly journalPath: string;
|
|
78
|
+
readonly bullet: string;
|
|
79
|
+
readonly lineNumber: number;
|
|
80
|
+
}
|
|
81
|
+
declare function loadLessonsIndex(projectRoot: string): LessonsIndex;
|
|
82
|
+
declare function readTriggeredLessons(projectRoot: string, event: ToolEvent): TriggeredLesson[];
|
|
83
|
+
declare function formatLessonBullet(input: LessonCaptureInput): string;
|
|
84
|
+
declare function appendLessonToJournal(projectRoot: string, input: LessonCaptureInput): AppendLessonResult;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Default on-disk locations for the lessons subsystem.
|
|
88
|
+
*
|
|
89
|
+
* All artifacts live under `<projectRoot>/.agentsmesh/lessons/`, so the lessons
|
|
90
|
+
* subsystem is a self-contained canonical feature — `agentsmesh init --lessons`
|
|
91
|
+
* scaffolds this directory and the procedural rule, and removal is a single
|
|
92
|
+
* directory delete.
|
|
93
|
+
*
|
|
94
|
+
* Path values are absolute; `*Rel` helpers return forward-slash project-relative
|
|
95
|
+
* paths suitable for embedding in markdown rules consumed by any agent target.
|
|
96
|
+
*/
|
|
97
|
+
interface LessonsPaths {
|
|
98
|
+
/** Directory containing every lessons artifact. */
|
|
99
|
+
readonly base: string;
|
|
100
|
+
/** Append-only journal — point of capture for new lessons. */
|
|
101
|
+
readonly journal: string;
|
|
102
|
+
/** Trigger index — maps file_globs / command_patterns / keywords to topic files. */
|
|
103
|
+
readonly index: string;
|
|
104
|
+
/** Distill ledger — bullet hash → assigned topic (or "skip"). */
|
|
105
|
+
readonly ledger: string;
|
|
106
|
+
/** Distill proposal file — generated by `distill`, consumed by `distill:apply`. */
|
|
107
|
+
readonly proposal: string;
|
|
108
|
+
/** Directory holding one markdown file per topic cluster. */
|
|
109
|
+
readonly topicsDir: string;
|
|
110
|
+
}
|
|
111
|
+
declare function lessonsPaths(projectRoot: string): LessonsPaths;
|
|
112
|
+
/**
|
|
113
|
+
* Project-relative path for a given absolute path, normalized to forward
|
|
114
|
+
* slashes for cross-platform consistency in markdown rule files.
|
|
115
|
+
*/
|
|
116
|
+
declare function toRelPath(projectRoot: string, absolute: string): string;
|
|
117
|
+
/**
|
|
118
|
+
* Empty-journal contents used by init scaffolding. The journal grows from here
|
|
119
|
+
* as failures get recorded.
|
|
120
|
+
*/
|
|
121
|
+
declare const LESSONS_JOURNAL_TEMPLATE = "# Lessons Learned\n\n";
|
|
122
|
+
/**
|
|
123
|
+
* Empty index used by init scaffolding. Schema allows zero clusters; topics
|
|
124
|
+
* accumulate via `distill:apply` as failures are captured.
|
|
125
|
+
*/
|
|
126
|
+
declare const LESSONS_INDEX_TEMPLATE = "version: 1\nclusters: []\n";
|
|
127
|
+
/**
|
|
128
|
+
* Procedural rule paragraph that must live in the project's root rule
|
|
129
|
+
* (`.agentsmesh/rules/_root.md`) for both recall and capture to be enforced.
|
|
130
|
+
*
|
|
131
|
+
* Universal across every agent target: only requires the ability to read
|
|
132
|
+
* project files. No `Skill` tool, no description-match, no per-target
|
|
133
|
+
* projection.
|
|
134
|
+
*/
|
|
135
|
+
declare const LESSONS_PROCEDURAL_RULE = "## Lessons (MUST do \u2014 non-negotiable)\n\nTwo rituals. Universal across every agent harness \u2014 described in tool-agnostic action verbs (\"read a file\", \"edit a file\", \"run a shell command\"); use whatever read / write-patch / shell-exec tool your harness exposes. Skipping either ritual is a process violation; the same paid-for failure recurs next session.\n\n**Recall \u2014 BEFORE editing any file or running any shell command:**\n\n1. Read `.agentsmesh/lessons/index.yaml`.\n2. For every cluster whose triggers match \u2014 `file_globs` against the target file path, `command_patterns` (regex) against the shell command, or `keywords` (case-insensitive substring) against the active task description \u2014 read its `file` (e.g. `.agentsmesh/lessons/topics/<topic>.md`).\n3. Apply the loaded rules to the action.\n4. Then \u2014 and only then \u2014 perform the edit or run the command.\n\nRejected excuses: *\"the edit is small\"* \u2192 apply it. *\"I read this earlier in the session\"* \u2192 context may have compacted; re-read. *\"no trigger fires\"* \u2192 proceed; you've done your part.\n\n**Capture \u2014 IMMEDIATELY after any failure** (test, CI, lint, code review, user correction, regression you spotted yourself, OR any mistake by anyone in the loop):\n\n1. Append one bullet to `.agentsmesh/lessons/journal.md` in this exact shape:\n `- **<short heading>**: <what went wrong>. <root cause>. <imperative rule that prevents recurrence>.`\n2. Read `.agentsmesh/lessons/index.yaml` and identify which topic the lesson belongs to (use the `summary:` fields).\n3. If the lesson teaches a NEW rule: edit that topic's `## Rules` section to add it, citing `(Evidence: L<journal-line>)`. If it only reinforces an existing rule, the journal entry alone suffices.\n\nRejected excuses: *\"it wasn't really a failure\"* \u2192 if you fixed something, it was. *\"I'll capture it later\"* \u2192 no; do it now while context is fresh. *\"no topic fits\"* \u2192 still record in journal; an unrouted bullet is recoverable, a forgotten one is not.\n\n**These two rituals are load-bearing. Treat them with the same discipline as TDD.**";
|
|
136
|
+
|
|
137
|
+
interface ScaffoldLessonsResult {
|
|
138
|
+
readonly created: string[];
|
|
139
|
+
readonly skipped: string[];
|
|
140
|
+
readonly rootRuleUpdated: boolean;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Idempotent scaffolder for the lessons subsystem. Intended for a future
|
|
144
|
+
* `agentsmesh init --lessons` flag (project mode); safe to call repeatedly.
|
|
145
|
+
*
|
|
146
|
+
* Creates `.agentsmesh/lessons/` with an empty journal, an empty index, and a
|
|
147
|
+
* `topics/` directory. Appends the procedural rule to
|
|
148
|
+
* `.agentsmesh/rules/_root.md` if not already present.
|
|
149
|
+
*
|
|
150
|
+
* Never overwrites existing files. The ledger and proposal are not created —
|
|
151
|
+
* they are auto-managed by the distill tool on first run.
|
|
152
|
+
*/
|
|
153
|
+
declare function scaffoldLessons(projectRoot: string): ScaffoldLessonsResult;
|
|
154
|
+
|
|
155
|
+
export { type AppendLessonResult, LESSONS_INDEX_TEMPLATE, LESSONS_JOURNAL_TEMPLATE, LESSONS_PROCEDURAL_RULE, type Ledger, type LessonCaptureInput, type LessonsCluster, type LessonsIndex, LessonsIndexSchema, type LessonsPaths, type ParsedBullet, type ScaffoldLessonsResult, type ScoredCluster, type ToolEvent, type TriggeredLesson, appendLessonToJournal, formatLessonBullet, hashBullet, lessonsPaths, loadLedger, loadLessonsIndex, matchTriggers, parseBullets, parseIndex, readTriggeredLessons, saveLedger, scaffoldLessons, scoreBullet, toRelPath };
|
package/dist/lessons.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import picomatch from 'picomatch';
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, appendFileSync } from 'fs';
|
|
5
|
+
import { parse, stringify } from 'yaml';
|
|
6
|
+
import { join, relative, sep, dirname, isAbsolute, resolve } from 'path';
|
|
7
|
+
|
|
8
|
+
// src/lessons/bullet-hash.ts
|
|
9
|
+
function normalize(bullet) {
|
|
10
|
+
return bullet.split("\n").map((line) => line.replace(/\s+$/, "")).join("\n").trim().replace(/^[-*]\s+/, "");
|
|
11
|
+
}
|
|
12
|
+
function hashBullet(bullet) {
|
|
13
|
+
return createHash("sha256").update(normalize(bullet)).digest("hex").slice(0, 16);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/lessons/bullet-parser.ts
|
|
17
|
+
function parseBullets(markdown) {
|
|
18
|
+
const bullets = [];
|
|
19
|
+
let current = null;
|
|
20
|
+
let lineNumber = 0;
|
|
21
|
+
for (const line of markdown.split("\n")) {
|
|
22
|
+
lineNumber += 1;
|
|
23
|
+
if (/^[-*]\s+/.test(line)) {
|
|
24
|
+
if (current !== null) bullets.push(current);
|
|
25
|
+
current = { text: line, lineNumber };
|
|
26
|
+
} else if (current !== null) {
|
|
27
|
+
if (line.length === 0) {
|
|
28
|
+
bullets.push(current);
|
|
29
|
+
current = null;
|
|
30
|
+
} else if (/^\s+\S/.test(line)) {
|
|
31
|
+
current.text += `
|
|
32
|
+
${line}`;
|
|
33
|
+
} else {
|
|
34
|
+
bullets.push(current);
|
|
35
|
+
current = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (current !== null) bullets.push(current);
|
|
40
|
+
return bullets;
|
|
41
|
+
}
|
|
42
|
+
var TriggersSchema = z.object({
|
|
43
|
+
file_globs: z.array(z.string()),
|
|
44
|
+
command_patterns: z.array(z.string()),
|
|
45
|
+
keywords: z.array(z.string())
|
|
46
|
+
}).refine((t) => t.file_globs.length + t.command_patterns.length + t.keywords.length > 0, {
|
|
47
|
+
message: "cluster must declare at least one trigger of any type"
|
|
48
|
+
});
|
|
49
|
+
var ClusterSchema = z.object({
|
|
50
|
+
topic: z.string().regex(/^[a-z0-9-]+$/, "topic must be kebab-case"),
|
|
51
|
+
/**
|
|
52
|
+
* Project-relative path (forward slashes) to the cluster's markdown body.
|
|
53
|
+
* Conventionally `.agentsmesh/lessons/topics/<topic>.md`, but any project
|
|
54
|
+
* path is accepted — universal across every agent target.
|
|
55
|
+
*/
|
|
56
|
+
file: z.string().regex(/\.md$/, "file must be a .md path"),
|
|
57
|
+
summary: z.string().min(1),
|
|
58
|
+
triggers: TriggersSchema
|
|
59
|
+
});
|
|
60
|
+
var LessonsIndexSchema = z.object({
|
|
61
|
+
version: z.literal(1),
|
|
62
|
+
/**
|
|
63
|
+
* Zero clusters is valid — supports `agentsmesh init --lessons` scaffolding a
|
|
64
|
+
* fresh project. Topics accumulate via `distill:apply` as failures are
|
|
65
|
+
* captured.
|
|
66
|
+
*/
|
|
67
|
+
clusters: z.array(ClusterSchema)
|
|
68
|
+
});
|
|
69
|
+
function parseIndex(raw) {
|
|
70
|
+
return LessonsIndexSchema.parse(raw);
|
|
71
|
+
}
|
|
72
|
+
function fileMatch(globs, path) {
|
|
73
|
+
return globs.some((g) => picomatch(g, { dot: true })(path));
|
|
74
|
+
}
|
|
75
|
+
function cmdMatch(patterns, cmd) {
|
|
76
|
+
return patterns.some((p) => {
|
|
77
|
+
try {
|
|
78
|
+
return new RegExp(p).test(cmd);
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function kwMatch(keywords, text) {
|
|
85
|
+
const lower = text.toLowerCase();
|
|
86
|
+
return keywords.some((k) => lower.includes(k.toLowerCase()));
|
|
87
|
+
}
|
|
88
|
+
function matchTriggers(clusters, event) {
|
|
89
|
+
return clusters.filter((c) => {
|
|
90
|
+
const t = c.triggers;
|
|
91
|
+
switch (event.kind) {
|
|
92
|
+
case "edit":
|
|
93
|
+
case "write":
|
|
94
|
+
return fileMatch(t.file_globs, event.filePath);
|
|
95
|
+
case "bash":
|
|
96
|
+
return cmdMatch(t.command_patterns, event.command);
|
|
97
|
+
case "task":
|
|
98
|
+
return kwMatch(t.keywords, event.text);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
var LedgerSchema = z.object({
|
|
103
|
+
version: z.literal(1),
|
|
104
|
+
assignments: z.record(z.string(), z.string())
|
|
105
|
+
});
|
|
106
|
+
function loadLedger(path) {
|
|
107
|
+
if (!existsSync(path)) return { version: 1, assignments: {} };
|
|
108
|
+
return LedgerSchema.parse(parse(readFileSync(path, "utf8")));
|
|
109
|
+
}
|
|
110
|
+
function saveLedger(path, ledger) {
|
|
111
|
+
writeFileSync(path, stringify(ledger), "utf8");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/lessons/scoring.ts
|
|
115
|
+
function scoreBullet(bullet, clusters) {
|
|
116
|
+
const lower = bullet.toLowerCase();
|
|
117
|
+
return clusters.map((cluster) => {
|
|
118
|
+
const t = cluster.triggers;
|
|
119
|
+
const kwHits = t.keywords.filter((k) => lower.includes(k.toLowerCase())).length;
|
|
120
|
+
const pathHits = t.file_globs.filter((g) => {
|
|
121
|
+
const stem = g.replace(/[*{}[\]?!]/g, "").replace(/\/+/g, "/").trim();
|
|
122
|
+
return stem.length > 2 && lower.includes(stem.toLowerCase());
|
|
123
|
+
}).length;
|
|
124
|
+
const cmdHits = t.command_patterns.filter((p) => {
|
|
125
|
+
try {
|
|
126
|
+
return new RegExp(p, "i").test(bullet);
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}).length;
|
|
131
|
+
return { cluster, score: kwHits * 2 + pathHits + cmdHits };
|
|
132
|
+
}).filter((s) => s.score > 0).sort((a, b) => b.score - a.score);
|
|
133
|
+
}
|
|
134
|
+
var BASE_REL = ".agentsmesh/lessons";
|
|
135
|
+
function lessonsPaths(projectRoot) {
|
|
136
|
+
const base = join(projectRoot, BASE_REL);
|
|
137
|
+
return {
|
|
138
|
+
base,
|
|
139
|
+
journal: join(base, "journal.md"),
|
|
140
|
+
index: join(base, "index.yaml"),
|
|
141
|
+
ledger: join(base, "distill-ledger.yaml"),
|
|
142
|
+
proposal: join(base, "distill-proposal.md"),
|
|
143
|
+
topicsDir: join(base, "topics")
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function toRelPath(projectRoot, absolute) {
|
|
147
|
+
return relative(projectRoot, absolute).split(sep).join("/");
|
|
148
|
+
}
|
|
149
|
+
var LESSONS_JOURNAL_TEMPLATE = "# Lessons Learned\n\n";
|
|
150
|
+
var LESSONS_INDEX_TEMPLATE = "version: 1\nclusters: []\n";
|
|
151
|
+
var LESSONS_PROCEDURAL_RULE = `## Lessons (MUST do \u2014 non-negotiable)
|
|
152
|
+
|
|
153
|
+
Two rituals. Universal across every agent harness \u2014 described in tool-agnostic action verbs ("read a file", "edit a file", "run a shell command"); use whatever read / write-patch / shell-exec tool your harness exposes. Skipping either ritual is a process violation; the same paid-for failure recurs next session.
|
|
154
|
+
|
|
155
|
+
**Recall \u2014 BEFORE editing any file or running any shell command:**
|
|
156
|
+
|
|
157
|
+
1. Read \`.agentsmesh/lessons/index.yaml\`.
|
|
158
|
+
2. For every cluster whose triggers match \u2014 \`file_globs\` against the target file path, \`command_patterns\` (regex) against the shell command, or \`keywords\` (case-insensitive substring) against the active task description \u2014 read its \`file\` (e.g. \`.agentsmesh/lessons/topics/<topic>.md\`).
|
|
159
|
+
3. Apply the loaded rules to the action.
|
|
160
|
+
4. Then \u2014 and only then \u2014 perform the edit or run the command.
|
|
161
|
+
|
|
162
|
+
Rejected excuses: *"the edit is small"* \u2192 apply it. *"I read this earlier in the session"* \u2192 context may have compacted; re-read. *"no trigger fires"* \u2192 proceed; you've done your part.
|
|
163
|
+
|
|
164
|
+
**Capture \u2014 IMMEDIATELY after any failure** (test, CI, lint, code review, user correction, regression you spotted yourself, OR any mistake by anyone in the loop):
|
|
165
|
+
|
|
166
|
+
1. Append one bullet to \`.agentsmesh/lessons/journal.md\` in this exact shape:
|
|
167
|
+
\`- **<short heading>**: <what went wrong>. <root cause>. <imperative rule that prevents recurrence>.\`
|
|
168
|
+
2. Read \`.agentsmesh/lessons/index.yaml\` and identify which topic the lesson belongs to (use the \`summary:\` fields).
|
|
169
|
+
3. If the lesson teaches a NEW rule: edit that topic's \`## Rules\` section to add it, citing \`(Evidence: L<journal-line>)\`. If it only reinforces an existing rule, the journal entry alone suffices.
|
|
170
|
+
|
|
171
|
+
Rejected excuses: *"it wasn't really a failure"* \u2192 if you fixed something, it was. *"I'll capture it later"* \u2192 no; do it now while context is fresh. *"no topic fits"* \u2192 still record in journal; an unrouted bullet is recoverable, a forgotten one is not.
|
|
172
|
+
|
|
173
|
+
**These two rituals are load-bearing. Treat them with the same discipline as TDD.**`;
|
|
174
|
+
|
|
175
|
+
// src/lessons/store.ts
|
|
176
|
+
function loadLessonsIndex(projectRoot) {
|
|
177
|
+
const raw = readFileSync(lessonsPaths(projectRoot).index, "utf8");
|
|
178
|
+
return parseIndex(parse(raw));
|
|
179
|
+
}
|
|
180
|
+
function readTriggeredLessons(projectRoot, event) {
|
|
181
|
+
const index = loadLessonsIndex(projectRoot);
|
|
182
|
+
const contentByPath = /* @__PURE__ */ new Map();
|
|
183
|
+
return matchTriggers(index.clusters, normalizeToolEvent(projectRoot, event)).map(
|
|
184
|
+
(cluster) => {
|
|
185
|
+
const filePath = resolveProjectFile(projectRoot, cluster.file);
|
|
186
|
+
let content = contentByPath.get(cluster.file);
|
|
187
|
+
if (content === void 0) {
|
|
188
|
+
content = readFileSync(filePath, "utf8");
|
|
189
|
+
contentByPath.set(cluster.file, content);
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
cluster,
|
|
193
|
+
relativePath: cluster.file,
|
|
194
|
+
filePath,
|
|
195
|
+
content
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
function formatLessonBullet(input) {
|
|
201
|
+
const heading = compact(input.heading);
|
|
202
|
+
if (heading.length === 0) throw new Error("Lesson heading must not be empty.");
|
|
203
|
+
return [
|
|
204
|
+
`- **${heading}**:`,
|
|
205
|
+
sentence(input.whatWentWrong),
|
|
206
|
+
sentence(input.rootCause),
|
|
207
|
+
sentence(input.rule)
|
|
208
|
+
].join(" ");
|
|
209
|
+
}
|
|
210
|
+
function appendLessonToJournal(projectRoot, input) {
|
|
211
|
+
const journalPath = lessonsPaths(projectRoot).journal;
|
|
212
|
+
const bullet = formatLessonBullet(input);
|
|
213
|
+
const current = existsSync(journalPath) ? readFileSync(journalPath, "utf8") : "";
|
|
214
|
+
const prefix = current.length === 0 || current.endsWith("\n") ? "" : "\n";
|
|
215
|
+
const lineNumber = nextLineNumber(current);
|
|
216
|
+
mkdirSync(dirname(journalPath), { recursive: true });
|
|
217
|
+
appendFileSync(journalPath, `${prefix}${bullet}
|
|
218
|
+
`, "utf8");
|
|
219
|
+
return { journalPath, bullet, lineNumber };
|
|
220
|
+
}
|
|
221
|
+
function resolveProjectFile(projectRoot, relPath) {
|
|
222
|
+
if (isAbsolute(relPath) || /^[A-Za-z]:[\\/]/.test(relPath)) {
|
|
223
|
+
throw new Error(`Lessons file must be project-relative: ${relPath}`);
|
|
224
|
+
}
|
|
225
|
+
const root = resolve(projectRoot);
|
|
226
|
+
const filePath = resolve(root, relPath);
|
|
227
|
+
const backToRoot = relative(root, filePath);
|
|
228
|
+
if (backToRoot === "" || backToRoot.startsWith("..") || isAbsolute(backToRoot)) {
|
|
229
|
+
throw new Error(`Lessons file escapes the project root: ${relPath}`);
|
|
230
|
+
}
|
|
231
|
+
return filePath;
|
|
232
|
+
}
|
|
233
|
+
function normalizeToolEvent(projectRoot, event) {
|
|
234
|
+
if (event.kind !== "edit" && event.kind !== "write") return event;
|
|
235
|
+
return { ...event, filePath: normalizeEventPath(projectRoot, event.filePath) };
|
|
236
|
+
}
|
|
237
|
+
function normalizeEventPath(projectRoot, filePath) {
|
|
238
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
239
|
+
const root = resolve(projectRoot).replaceAll("\\", "/");
|
|
240
|
+
if (normalized === root) return ".";
|
|
241
|
+
if (normalized.startsWith(`${root}/`)) return normalized.slice(root.length + 1);
|
|
242
|
+
return normalized.replace(/^\.\//, "");
|
|
243
|
+
}
|
|
244
|
+
function compact(value) {
|
|
245
|
+
return value.replace(/\s+/g, " ").trim();
|
|
246
|
+
}
|
|
247
|
+
function sentence(value) {
|
|
248
|
+
const compacted = compact(value);
|
|
249
|
+
if (compacted.length === 0) throw new Error("Lesson sentence must not be empty.");
|
|
250
|
+
return /[.!?]$/.test(compacted) ? compacted : `${compacted}.`;
|
|
251
|
+
}
|
|
252
|
+
function nextLineNumber(current) {
|
|
253
|
+
if (current.length === 0) return 1;
|
|
254
|
+
const lineCount = current.split("\n").length;
|
|
255
|
+
return current.endsWith("\n") ? lineCount : lineCount + 1;
|
|
256
|
+
}
|
|
257
|
+
function scaffoldLessons(projectRoot) {
|
|
258
|
+
const paths = lessonsPaths(projectRoot);
|
|
259
|
+
const created = [];
|
|
260
|
+
const skipped = [];
|
|
261
|
+
mkdirSync(paths.topicsDir, { recursive: true });
|
|
262
|
+
for (const [path, template] of [
|
|
263
|
+
[paths.journal, LESSONS_JOURNAL_TEMPLATE],
|
|
264
|
+
[paths.index, LESSONS_INDEX_TEMPLATE]
|
|
265
|
+
]) {
|
|
266
|
+
if (existsSync(path)) {
|
|
267
|
+
skipped.push(path);
|
|
268
|
+
} else {
|
|
269
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
270
|
+
writeFileSync(path, template, "utf8");
|
|
271
|
+
created.push(path);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const rootRuleUpdated = appendProceduralRule(projectRoot);
|
|
275
|
+
return { created, skipped, rootRuleUpdated };
|
|
276
|
+
}
|
|
277
|
+
function appendProceduralRule(projectRoot) {
|
|
278
|
+
const rootRule = join(projectRoot, ".agentsmesh/rules/_root.md");
|
|
279
|
+
if (!existsSync(rootRule)) {
|
|
280
|
+
mkdirSync(dirname(rootRule), { recursive: true });
|
|
281
|
+
const seeded = `---
|
|
282
|
+
root: true
|
|
283
|
+
description: ""
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
# Operational Guidelines
|
|
287
|
+
|
|
288
|
+
${LESSONS_PROCEDURAL_RULE}
|
|
289
|
+
`;
|
|
290
|
+
writeFileSync(rootRule, seeded, "utf8");
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
const current = readFileSync(rootRule, "utf8");
|
|
294
|
+
if (/^## Lessons \(/m.test(current)) return false;
|
|
295
|
+
const next = current.endsWith("\n") ? current : `${current}
|
|
296
|
+
`;
|
|
297
|
+
writeFileSync(rootRule, `${next}
|
|
298
|
+
${LESSONS_PROCEDURAL_RULE}
|
|
299
|
+
`, "utf8");
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export { LESSONS_INDEX_TEMPLATE, LESSONS_JOURNAL_TEMPLATE, LESSONS_PROCEDURAL_RULE, LessonsIndexSchema, appendLessonToJournal, formatLessonBullet, hashBullet, lessonsPaths, loadLedger, loadLessonsIndex, matchTriggers, parseBullets, parseIndex, readTriggeredLessons, saveLedger, scaffoldLessons, scoreBullet, toRelPath };
|
|
304
|
+
//# sourceMappingURL=lessons.js.map
|
|
305
|
+
//# sourceMappingURL=lessons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lessons/bullet-hash.ts","../src/lessons/bullet-parser.ts","../src/lessons/index-schema.ts","../src/lessons/matcher.ts","../src/lessons/ledger.ts","../src/lessons/scoring.ts","../src/lessons/paths.ts","../src/lessons/store.ts","../src/lessons/init.ts"],"names":["z","parseYaml","yamlStringify","readFileSync","existsSync","relative","mkdirSync","dirname","writeFileSync","join"],"mappings":"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","file":"lessons.js","sourcesContent":["import { createHash } from 'node:crypto';\n\nfunction normalize(bullet: string): string {\n return bullet\n .split('\\n')\n .map((line) => line.replace(/\\s+$/, ''))\n .join('\\n')\n .trim()\n .replace(/^[-*]\\s+/, '');\n}\n\nexport function hashBullet(bullet: string): string {\n return createHash('sha256').update(normalize(bullet)).digest('hex').slice(0, 16);\n}\n","export interface ParsedBullet {\n text: string;\n lineNumber: number;\n}\n\nexport function parseBullets(markdown: string): ParsedBullet[] {\n const bullets: ParsedBullet[] = [];\n let current: ParsedBullet | null = null;\n let lineNumber = 0;\n\n for (const line of markdown.split('\\n')) {\n lineNumber += 1;\n if (/^[-*]\\s+/.test(line)) {\n if (current !== null) bullets.push(current);\n current = { text: line, lineNumber };\n } else if (current !== null) {\n if (line.length === 0) {\n bullets.push(current);\n current = null;\n } else if (/^\\s+\\S/.test(line)) {\n current.text += `\\n${line}`;\n } else {\n bullets.push(current);\n current = null;\n }\n }\n }\n if (current !== null) bullets.push(current);\n return bullets;\n}\n","import { z } from 'zod';\n\nconst TriggersSchema = z\n .object({\n file_globs: z.array(z.string()),\n command_patterns: z.array(z.string()),\n keywords: z.array(z.string()),\n })\n .refine((t) => t.file_globs.length + t.command_patterns.length + t.keywords.length > 0, {\n message: 'cluster must declare at least one trigger of any type',\n });\n\nconst ClusterSchema = z.object({\n topic: z.string().regex(/^[a-z0-9-]+$/, 'topic must be kebab-case'),\n /**\n * Project-relative path (forward slashes) to the cluster's markdown body.\n * Conventionally `.agentsmesh/lessons/topics/<topic>.md`, but any project\n * path is accepted — universal across every agent target.\n */\n file: z.string().regex(/\\.md$/, 'file must be a .md path'),\n summary: z.string().min(1),\n triggers: TriggersSchema,\n});\n\nexport const LessonsIndexSchema = z.object({\n version: z.literal(1),\n /**\n * Zero clusters is valid — supports `agentsmesh init --lessons` scaffolding a\n * fresh project. Topics accumulate via `distill:apply` as failures are\n * captured.\n */\n clusters: z.array(ClusterSchema),\n});\n\nexport type LessonsIndex = z.infer<typeof LessonsIndexSchema>;\nexport type LessonsCluster = z.infer<typeof ClusterSchema>;\n\nexport function parseIndex(raw: unknown): LessonsIndex {\n return LessonsIndexSchema.parse(raw);\n}\n","import picomatch from 'picomatch';\nimport type { LessonsCluster } from './index-schema.js';\n\nexport type ToolEvent =\n | { kind: 'edit' | 'write'; filePath: string }\n | { kind: 'bash'; command: string }\n | { kind: 'task'; text: string };\n\nfunction fileMatch(globs: readonly string[], path: string): boolean {\n return globs.some((g) => picomatch(g, { dot: true })(path));\n}\n\nfunction cmdMatch(patterns: readonly string[], cmd: string): boolean {\n return patterns.some((p) => {\n try {\n return new RegExp(p).test(cmd);\n } catch {\n return false;\n }\n });\n}\n\nfunction kwMatch(keywords: readonly string[], text: string): boolean {\n const lower = text.toLowerCase();\n return keywords.some((k) => lower.includes(k.toLowerCase()));\n}\n\nexport function matchTriggers(\n clusters: readonly LessonsCluster[],\n event: ToolEvent,\n): LessonsCluster[] {\n return clusters.filter((c) => {\n const t = c.triggers;\n switch (event.kind) {\n case 'edit':\n case 'write':\n return fileMatch(t.file_globs, event.filePath);\n case 'bash':\n return cmdMatch(t.command_patterns, event.command);\n case 'task':\n return kwMatch(t.keywords, event.text);\n }\n });\n}\n","import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { parse as parseYaml, stringify as yamlStringify } from 'yaml';\nimport { z } from 'zod';\n\nconst LedgerSchema = z.object({\n version: z.literal(1),\n assignments: z.record(z.string(), z.string()),\n});\n\nexport type Ledger = z.infer<typeof LedgerSchema>;\n\nexport function loadLedger(path: string): Ledger {\n if (!existsSync(path)) return { version: 1, assignments: {} };\n return LedgerSchema.parse(parseYaml(readFileSync(path, 'utf8')));\n}\n\nexport function saveLedger(path: string, ledger: Ledger): void {\n writeFileSync(path, yamlStringify(ledger), 'utf8');\n}\n","import type { LessonsCluster } from './index-schema.js';\n\nexport interface ScoredCluster {\n cluster: LessonsCluster;\n score: number;\n}\n\nexport function scoreBullet(bullet: string, clusters: readonly LessonsCluster[]): ScoredCluster[] {\n const lower = bullet.toLowerCase();\n return clusters\n .map((cluster): ScoredCluster => {\n const t = cluster.triggers;\n const kwHits = t.keywords.filter((k) => lower.includes(k.toLowerCase())).length;\n const pathHits = t.file_globs.filter((g) => {\n const stem = g\n .replace(/[*{}[\\]?!]/g, '')\n .replace(/\\/+/g, '/')\n .trim();\n return stem.length > 2 && lower.includes(stem.toLowerCase());\n }).length;\n const cmdHits = t.command_patterns.filter((p) => {\n try {\n return new RegExp(p, 'i').test(bullet);\n } catch {\n return false;\n }\n }).length;\n return { cluster, score: kwHits * 2 + pathHits + cmdHits };\n })\n .filter((s) => s.score > 0)\n .sort((a, b) => b.score - a.score);\n}\n","import { join, relative, sep } from 'node:path';\n\n/**\n * Default on-disk locations for the lessons subsystem.\n *\n * All artifacts live under `<projectRoot>/.agentsmesh/lessons/`, so the lessons\n * subsystem is a self-contained canonical feature — `agentsmesh init --lessons`\n * scaffolds this directory and the procedural rule, and removal is a single\n * directory delete.\n *\n * Path values are absolute; `*Rel` helpers return forward-slash project-relative\n * paths suitable for embedding in markdown rules consumed by any agent target.\n */\nexport interface LessonsPaths {\n /** Directory containing every lessons artifact. */\n readonly base: string;\n /** Append-only journal — point of capture for new lessons. */\n readonly journal: string;\n /** Trigger index — maps file_globs / command_patterns / keywords to topic files. */\n readonly index: string;\n /** Distill ledger — bullet hash → assigned topic (or \"skip\"). */\n readonly ledger: string;\n /** Distill proposal file — generated by `distill`, consumed by `distill:apply`. */\n readonly proposal: string;\n /** Directory holding one markdown file per topic cluster. */\n readonly topicsDir: string;\n}\n\nconst BASE_REL = '.agentsmesh/lessons';\n\nexport function lessonsPaths(projectRoot: string): LessonsPaths {\n const base = join(projectRoot, BASE_REL);\n return {\n base,\n journal: join(base, 'journal.md'),\n index: join(base, 'index.yaml'),\n ledger: join(base, 'distill-ledger.yaml'),\n proposal: join(base, 'distill-proposal.md'),\n topicsDir: join(base, 'topics'),\n };\n}\n\n/**\n * Project-relative path for a given absolute path, normalized to forward\n * slashes for cross-platform consistency in markdown rule files.\n */\nexport function toRelPath(projectRoot: string, absolute: string): string {\n return relative(projectRoot, absolute).split(sep).join('/');\n}\n\n/**\n * Empty-journal contents used by init scaffolding. The journal grows from here\n * as failures get recorded.\n */\nexport const LESSONS_JOURNAL_TEMPLATE = '# Lessons Learned\\n\\n';\n\n/**\n * Empty index used by init scaffolding. Schema allows zero clusters; topics\n * accumulate via `distill:apply` as failures are captured.\n */\nexport const LESSONS_INDEX_TEMPLATE = 'version: 1\\nclusters: []\\n';\n\n/**\n * Procedural rule paragraph that must live in the project's root rule\n * (`.agentsmesh/rules/_root.md`) for both recall and capture to be enforced.\n *\n * Universal across every agent target: only requires the ability to read\n * project files. No `Skill` tool, no description-match, no per-target\n * projection.\n */\nexport const LESSONS_PROCEDURAL_RULE = `## Lessons (MUST do — non-negotiable)\n\nTwo rituals. Universal across every agent harness — described in tool-agnostic action verbs (\"read a file\", \"edit a file\", \"run a shell command\"); use whatever read / write-patch / shell-exec tool your harness exposes. Skipping either ritual is a process violation; the same paid-for failure recurs next session.\n\n**Recall — BEFORE editing any file or running any shell command:**\n\n1. Read \\`.agentsmesh/lessons/index.yaml\\`.\n2. For every cluster whose triggers match — \\`file_globs\\` against the target file path, \\`command_patterns\\` (regex) against the shell command, or \\`keywords\\` (case-insensitive substring) against the active task description — read its \\`file\\` (e.g. \\`.agentsmesh/lessons/topics/<topic>.md\\`).\n3. Apply the loaded rules to the action.\n4. Then — and only then — perform the edit or run the command.\n\nRejected excuses: *\"the edit is small\"* → apply it. *\"I read this earlier in the session\"* → context may have compacted; re-read. *\"no trigger fires\"* → proceed; you've done your part.\n\n**Capture — IMMEDIATELY after any failure** (test, CI, lint, code review, user correction, regression you spotted yourself, OR any mistake by anyone in the loop):\n\n1. Append one bullet to \\`.agentsmesh/lessons/journal.md\\` in this exact shape:\n \\`- **<short heading>**: <what went wrong>. <root cause>. <imperative rule that prevents recurrence>.\\`\n2. Read \\`.agentsmesh/lessons/index.yaml\\` and identify which topic the lesson belongs to (use the \\`summary:\\` fields).\n3. If the lesson teaches a NEW rule: edit that topic's \\`## Rules\\` section to add it, citing \\`(Evidence: L<journal-line>)\\`. If it only reinforces an existing rule, the journal entry alone suffices.\n\nRejected excuses: *\"it wasn't really a failure\"* → if you fixed something, it was. *\"I'll capture it later\"* → no; do it now while context is fresh. *\"no topic fits\"* → still record in journal; an unrouted bullet is recoverable, a forgotten one is not.\n\n**These two rituals are load-bearing. Treat them with the same discipline as TDD.**`;\n","import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { parseIndex, type LessonsCluster, type LessonsIndex } from './index-schema.js';\nimport { matchTriggers, type ToolEvent } from './matcher.js';\nimport { lessonsPaths } from './paths.js';\n\nexport interface TriggeredLesson {\n readonly cluster: LessonsCluster;\n readonly relativePath: string;\n readonly filePath: string;\n readonly content: string;\n}\n\nexport interface LessonCaptureInput {\n readonly heading: string;\n readonly whatWentWrong: string;\n readonly rootCause: string;\n readonly rule: string;\n}\n\nexport interface AppendLessonResult {\n readonly journalPath: string;\n readonly bullet: string;\n readonly lineNumber: number;\n}\n\nexport function loadLessonsIndex(projectRoot: string): LessonsIndex {\n const raw = readFileSync(lessonsPaths(projectRoot).index, 'utf8');\n return parseIndex(parseYaml(raw) as unknown);\n}\n\nexport function readTriggeredLessons(projectRoot: string, event: ToolEvent): TriggeredLesson[] {\n const index = loadLessonsIndex(projectRoot);\n const contentByPath = new Map<string, string>();\n\n return matchTriggers(index.clusters, normalizeToolEvent(projectRoot, event)).map(\n (cluster): TriggeredLesson => {\n const filePath = resolveProjectFile(projectRoot, cluster.file);\n let content = contentByPath.get(cluster.file);\n if (content === undefined) {\n content = readFileSync(filePath, 'utf8');\n contentByPath.set(cluster.file, content);\n }\n\n return {\n cluster,\n relativePath: cluster.file,\n filePath,\n content,\n };\n },\n );\n}\n\nexport function formatLessonBullet(input: LessonCaptureInput): string {\n const heading = compact(input.heading);\n if (heading.length === 0) throw new Error('Lesson heading must not be empty.');\n\n return [\n `- **${heading}**:`,\n sentence(input.whatWentWrong),\n sentence(input.rootCause),\n sentence(input.rule),\n ].join(' ');\n}\n\nexport function appendLessonToJournal(\n projectRoot: string,\n input: LessonCaptureInput,\n): AppendLessonResult {\n const journalPath = lessonsPaths(projectRoot).journal;\n const bullet = formatLessonBullet(input);\n const current = existsSync(journalPath) ? readFileSync(journalPath, 'utf8') : '';\n const prefix = current.length === 0 || current.endsWith('\\n') ? '' : '\\n';\n const lineNumber = nextLineNumber(current);\n\n mkdirSync(dirname(journalPath), { recursive: true });\n appendFileSync(journalPath, `${prefix}${bullet}\\n`, 'utf8');\n\n return { journalPath, bullet, lineNumber };\n}\n\nfunction resolveProjectFile(projectRoot: string, relPath: string): string {\n if (isAbsolute(relPath) || /^[A-Za-z]:[\\\\/]/.test(relPath)) {\n throw new Error(`Lessons file must be project-relative: ${relPath}`);\n }\n\n const root = resolve(projectRoot);\n const filePath = resolve(root, relPath);\n const backToRoot = relative(root, filePath);\n if (backToRoot === '' || backToRoot.startsWith('..') || isAbsolute(backToRoot)) {\n throw new Error(`Lessons file escapes the project root: ${relPath}`);\n }\n\n return filePath;\n}\n\nfunction normalizeToolEvent(projectRoot: string, event: ToolEvent): ToolEvent {\n if (event.kind !== 'edit' && event.kind !== 'write') return event;\n return { ...event, filePath: normalizeEventPath(projectRoot, event.filePath) };\n}\n\nfunction normalizeEventPath(projectRoot: string, filePath: string): string {\n const normalized = filePath.replaceAll('\\\\', '/');\n const root = resolve(projectRoot).replaceAll('\\\\', '/');\n if (normalized === root) return '.';\n if (normalized.startsWith(`${root}/`)) return normalized.slice(root.length + 1);\n return normalized.replace(/^\\.\\//, '');\n}\n\nfunction compact(value: string): string {\n return value.replace(/\\s+/g, ' ').trim();\n}\n\nfunction sentence(value: string): string {\n const compacted = compact(value);\n if (compacted.length === 0) throw new Error('Lesson sentence must not be empty.');\n return /[.!?]$/.test(compacted) ? compacted : `${compacted}.`;\n}\n\nfunction nextLineNumber(current: string): number {\n if (current.length === 0) return 1;\n const lineCount = current.split('\\n').length;\n return current.endsWith('\\n') ? lineCount : lineCount + 1;\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport {\n LESSONS_INDEX_TEMPLATE,\n LESSONS_JOURNAL_TEMPLATE,\n LESSONS_PROCEDURAL_RULE,\n lessonsPaths,\n} from './paths.js';\n\nexport interface ScaffoldLessonsResult {\n readonly created: string[];\n readonly skipped: string[];\n readonly rootRuleUpdated: boolean;\n}\n\n/**\n * Idempotent scaffolder for the lessons subsystem. Intended for a future\n * `agentsmesh init --lessons` flag (project mode); safe to call repeatedly.\n *\n * Creates `.agentsmesh/lessons/` with an empty journal, an empty index, and a\n * `topics/` directory. Appends the procedural rule to\n * `.agentsmesh/rules/_root.md` if not already present.\n *\n * Never overwrites existing files. The ledger and proposal are not created —\n * they are auto-managed by the distill tool on first run.\n */\nexport function scaffoldLessons(projectRoot: string): ScaffoldLessonsResult {\n const paths = lessonsPaths(projectRoot);\n const created: string[] = [];\n const skipped: string[] = [];\n\n mkdirSync(paths.topicsDir, { recursive: true });\n\n for (const [path, template] of [\n [paths.journal, LESSONS_JOURNAL_TEMPLATE],\n [paths.index, LESSONS_INDEX_TEMPLATE],\n ] as const) {\n if (existsSync(path)) {\n skipped.push(path);\n } else {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, template, 'utf8');\n created.push(path);\n }\n }\n\n const rootRuleUpdated = appendProceduralRule(projectRoot);\n return { created, skipped, rootRuleUpdated };\n}\n\nfunction appendProceduralRule(projectRoot: string): boolean {\n const rootRule = join(projectRoot, '.agentsmesh/rules/_root.md');\n if (!existsSync(rootRule)) {\n mkdirSync(dirname(rootRule), { recursive: true });\n const seeded = `---\\nroot: true\\ndescription: \"\"\\n---\\n\\n# Operational Guidelines\\n\\n${LESSONS_PROCEDURAL_RULE}\\n`;\n writeFileSync(rootRule, seeded, 'utf8');\n return true;\n }\n const current = readFileSync(rootRule, 'utf8');\n // Match any '## Lessons (…)' heading — the parenthetical wording may evolve\n // (mandatory / MUST do / etc.) but the H2 itself is the stable idempotency\n // anchor for this paragraph.\n if (/^## Lessons \\(/m.test(current)) return false;\n const next = current.endsWith('\\n') ? current : `${current}\\n`;\n writeFileSync(rootRule, `${next}\\n${LESSONS_PROCEDURAL_RULE}\\n`, 'utf8');\n return true;\n}\n"]}
|
package/dist/targets.js
CHANGED
|
@@ -885,7 +885,7 @@ ${legacy}`, "");
|
|
|
885
885
|
}
|
|
886
886
|
return result.trim();
|
|
887
887
|
}
|
|
888
|
-
var ROOT_INSTRUCTION_BODY_V1, ROOT_INSTRUCTION_BODY_V2, ROOT_INSTRUCTION_BODY_V3, ROOT_INSTRUCTION_BODY_V4, ROOT_INSTRUCTION_BODY_V5, ROOT_INSTRUCTION_BODY_V6, ROOT_INSTRUCTION_BODY_V7, ROOT_INSTRUCTION_BODY_V8, ROOT_INSTRUCTION_BODY, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION, AGENTSMESH_CONTRACT_WITH_V1_BODY, AGENTSMESH_CONTRACT_WITH_V2_BODY, AGENTSMESH_CONTRACT_WITH_V3_BODY, AGENTSMESH_CONTRACT_WITH_V4_BODY, AGENTSMESH_CONTRACT_WITH_V5_BODY, AGENTSMESH_CONTRACT_WITH_V6_BODY, AGENTSMESH_CONTRACT_WITH_V7_BODY, AGENTSMESH_CONTRACT_WITH_V8_BODY, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_FORMS;
|
|
888
|
+
var ROOT_INSTRUCTION_BODY_V1, ROOT_INSTRUCTION_BODY_V2, ROOT_INSTRUCTION_BODY_V3, ROOT_INSTRUCTION_BODY_V4, ROOT_INSTRUCTION_BODY_V5, ROOT_INSTRUCTION_BODY_V6, ROOT_INSTRUCTION_BODY_V7, ROOT_INSTRUCTION_BODY_V8, ROOT_INSTRUCTION_BODY_V9, ROOT_INSTRUCTION_BODY_V10, ROOT_INSTRUCTION_BODY, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION, AGENTSMESH_CONTRACT_WITH_V1_BODY, AGENTSMESH_CONTRACT_WITH_V2_BODY, AGENTSMESH_CONTRACT_WITH_V3_BODY, AGENTSMESH_CONTRACT_WITH_V4_BODY, AGENTSMESH_CONTRACT_WITH_V5_BODY, AGENTSMESH_CONTRACT_WITH_V6_BODY, AGENTSMESH_CONTRACT_WITH_V7_BODY, AGENTSMESH_CONTRACT_WITH_V8_BODY, AGENTSMESH_CONTRACT_WITH_V9_BODY, AGENTSMESH_CONTRACT_WITH_V10_BODY, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_FORMS;
|
|
889
889
|
var init_root_instruction_paragraph = __esm({
|
|
890
890
|
"src/targets/projection/root-instruction-paragraph.ts"() {
|
|
891
891
|
init_managed_blocks();
|
|
@@ -897,7 +897,9 @@ var init_root_instruction_paragraph = __esm({
|
|
|
897
897
|
ROOT_INSTRUCTION_BODY_V6 = "Create canonical files in `.agentsmesh`: `rules/_root.md` and `rules/*.md` are Markdown rules; `commands/*.md`, `agents/*.md`, and `skills/*/SKILL.md` plus supporting files use Claude-style frontmatter Markdown; `mcp.json` is MCP JSON; `hooks.yaml` and `permissions.yaml` are YAML; `ignore` is gitignore-style text. Then run `agentsmesh generate`.";
|
|
898
898
|
ROOT_INSTRUCTION_BODY_V7 = "`.agentsmesh` is the only folder you edit or add these files in: `rules/_root.md` and `rules/*.md` are Markdown rules; `commands/*.md`, `agents/*.md`, and `skills/*/SKILL.md` plus supporting files use Claude-style frontmatter Markdown; `mcp.json` is MCP JSON; `hooks.yaml` and `permissions.yaml` are YAML; `ignore` is gitignore-style text. Do not edit generated tool files; run `agentsmesh generate`.";
|
|
899
899
|
ROOT_INSTRUCTION_BODY_V8 = "`agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, and `merge` as needed; never edit generated tool files.";
|
|
900
|
-
|
|
900
|
+
ROOT_INSTRUCTION_BODY_V9 = "`agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed; never edit generated tool files.";
|
|
901
|
+
ROOT_INSTRUCTION_BODY_V10 = "**MUST follow when changing any rule, agent, command, skill, hook, MCP server, permission, or ignore pattern.** `agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed; never edit generated tool files.";
|
|
902
|
+
ROOT_INSTRUCTION_BODY = "**NEVER edit generated files** (`.claude/`, `.cursor/`, `AGENTS.md`, `.github/copilot-instructions.md`, and similar target outputs) \u2014 `agentsmesh generate` overwrites them. **All changes MUST go through `.agentsmesh` first**: edit `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; `agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally); if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed.";
|
|
901
903
|
LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = ROOT_INSTRUCTION_BODY_V1;
|
|
902
904
|
LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION = `## Project-Specific Rules
|
|
903
905
|
|
|
@@ -926,12 +928,20 @@ ${ROOT_INSTRUCTION_BODY_V7}`;
|
|
|
926
928
|
AGENTSMESH_CONTRACT_WITH_V8_BODY = `## AgentsMesh Generation Contract
|
|
927
929
|
|
|
928
930
|
${ROOT_INSTRUCTION_BODY_V8}`;
|
|
931
|
+
AGENTSMESH_CONTRACT_WITH_V9_BODY = `## AgentsMesh Generation Contract
|
|
932
|
+
|
|
933
|
+
${ROOT_INSTRUCTION_BODY_V9}`;
|
|
934
|
+
AGENTSMESH_CONTRACT_WITH_V10_BODY = `## AgentsMesh Generation Contract
|
|
935
|
+
|
|
936
|
+
${ROOT_INSTRUCTION_BODY_V10}`;
|
|
929
937
|
AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = `${ROOT_CONTRACT_START}
|
|
930
938
|
## AgentsMesh Generation Contract
|
|
931
939
|
|
|
932
940
|
${ROOT_INSTRUCTION_BODY}
|
|
933
941
|
${ROOT_CONTRACT_END}`;
|
|
934
942
|
LEGACY_FORMS = [
|
|
943
|
+
AGENTSMESH_CONTRACT_WITH_V10_BODY,
|
|
944
|
+
AGENTSMESH_CONTRACT_WITH_V9_BODY,
|
|
935
945
|
AGENTSMESH_CONTRACT_WITH_V8_BODY,
|
|
936
946
|
AGENTSMESH_CONTRACT_WITH_V7_BODY,
|
|
937
947
|
AGENTSMESH_CONTRACT_WITH_V6_BODY,
|
|
@@ -15693,9 +15703,18 @@ function generateIgnore12(canonical) {
|
|
|
15693
15703
|
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
15694
15704
|
return [{ path: QWEN_IGNORE, content: canonical.ignore.join("\n") }];
|
|
15695
15705
|
}
|
|
15706
|
+
function renderQwenGlobalInstructions(canonical) {
|
|
15707
|
+
const root = canonical.rules.find((rule) => rule.root);
|
|
15708
|
+
const nonRootRules = canonical.rules.filter((rule) => {
|
|
15709
|
+
if (rule.root) return false;
|
|
15710
|
+
return rule.targets.length === 0 || rule.targets.includes(QWEN_CODE_TARGET);
|
|
15711
|
+
});
|
|
15712
|
+
return appendEmbeddedRulesBlock(root?.body.trim() ?? "", nonRootRules);
|
|
15713
|
+
}
|
|
15696
15714
|
var init_generator26 = __esm({
|
|
15697
15715
|
"src/targets/qwen-code/generator.ts"() {
|
|
15698
15716
|
init_markdown();
|
|
15717
|
+
init_managed_blocks();
|
|
15699
15718
|
init_constants21();
|
|
15700
15719
|
}
|
|
15701
15720
|
});
|
|
@@ -15778,6 +15797,7 @@ var init_qwen_code2 = __esm({
|
|
|
15778
15797
|
};
|
|
15779
15798
|
globalLayout22 = {
|
|
15780
15799
|
rootInstructionPath: QWEN_GLOBAL_ROOT,
|
|
15800
|
+
renderPrimaryRootInstruction: renderQwenGlobalInstructions,
|
|
15781
15801
|
skillDir: QWEN_GLOBAL_SKILLS_DIR,
|
|
15782
15802
|
managedOutputs: {
|
|
15783
15803
|
dirs: [QWEN_GLOBAL_COMMANDS_DIR, QWEN_GLOBAL_AGENTS_DIR, QWEN_GLOBAL_SKILLS_DIR],
|