@tanstack/intent 0.0.27 → 0.0.32
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/LICENSE +21 -0
- package/README.md +18 -5
- package/dist/cli.d.mts +0 -0
- package/dist/cli.mjs +62 -68
- package/dist/display-DCRCp4-C.mjs +5 -0
- package/dist/{display-hdsqb4w-.mjs → display-DUgtRJkt.mjs} +10 -1
- package/dist/index.d.mts +57 -4
- package/dist/index.mjs +9 -6
- package/dist/install-BmVqcbEi.mjs +506 -0
- package/dist/intent-library.mjs +9 -5
- package/dist/{library-scanner-B51qV5aX.mjs → library-scanner-DFFreLjW.mjs} +10 -3
- package/dist/library-scanner.d.mts +2 -2
- package/dist/library-scanner.mjs +2 -1
- package/dist/{project-context-B7UXTA9F.mjs → project-context-CKG-q4rD.mjs} +1 -1
- package/dist/resolver-aFigTqXi.mjs +70 -0
- package/dist/scanner-CRZITpcY.mjs +6 -0
- package/dist/{scanner-1ZGYK3Qm.mjs → scanner-DKL8v8is.mjs} +176 -117
- package/dist/{setup-BzhEoOBi.mjs → setup-JJvjiGkM.mjs} +2 -2
- package/dist/setup.d.mts +1 -1
- package/dist/setup.mjs +3 -3
- package/dist/skill-paths-8k9K9y26.mjs +33 -0
- package/dist/skill-use-CXOnncWK.mjs +42 -0
- package/dist/{staleness-BtmJtMpz.mjs → staleness-DorwfGrf.mjs} +12 -1
- package/dist/staleness-NF-lxfXf.mjs +4 -0
- package/dist/{types-BTQ9efv-.d.mts → types-CsySN6Vw.d.mts} +7 -1
- package/dist/{workspace-patterns-Boa5mAbf.mjs → workspace-patterns-DbnA0peB.mjs} +1 -1
- package/dist/{workspace-patterns-BuJMfudb.mjs → workspace-patterns-U35B5AO-.mjs} +73 -4
- package/package.json +3 -4
- package/dist/display-DdmZXLZm.mjs +0 -3
- package/dist/install-BzDmD5yI.mjs +0 -69
- package/dist/scanner-CERQgrRN.mjs +0 -5
- package/dist/staleness-SRims-ZP.mjs +0 -4
- /package/dist/{setup-BA9RkENh.d.mts → setup-DDoOLriA.d.mts} +0 -0
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import { i as parseSkillUse, n as formatSkillUse } from "./skill-use-CXOnncWK.mjs";
|
|
2
|
+
import { t as resolveProjectContext } from "./project-context-CKG-q4rD.mjs";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
import { parse } from "yaml";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
//#region src/cli-error.ts
|
|
9
|
+
const CLI_FAILURE = Symbol("CliFailure");
|
|
10
|
+
function fail(message, exitCode = 1) {
|
|
11
|
+
throw {
|
|
12
|
+
[CLI_FAILURE]: true,
|
|
13
|
+
message,
|
|
14
|
+
exitCode
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function isCliFailure(value) {
|
|
18
|
+
return !!value && typeof value === "object" && CLI_FAILURE in value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/cli-output.ts
|
|
23
|
+
function printWarnings(warnings) {
|
|
24
|
+
if (warnings.length === 0) return;
|
|
25
|
+
console.log("Warnings:");
|
|
26
|
+
for (const warning of warnings) console.log(` ⚠ ${warning}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/cli-support.ts
|
|
31
|
+
function getMetaDir() {
|
|
32
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..", "meta");
|
|
33
|
+
}
|
|
34
|
+
async function scanIntentsOrFail(options) {
|
|
35
|
+
const { scanForIntents } = await import("./scanner-CRZITpcY.mjs");
|
|
36
|
+
try {
|
|
37
|
+
return scanForIntents(void 0, options);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function scanOptionsFromGlobalFlags(options) {
|
|
43
|
+
if (options.global && options.globalOnly) fail("Use either --global or --global-only, not both.");
|
|
44
|
+
if (options.globalOnly) return { scope: "global" };
|
|
45
|
+
if (options.global) return { scope: "local-and-global" };
|
|
46
|
+
return { scope: "local" };
|
|
47
|
+
}
|
|
48
|
+
function readPackageName(root) {
|
|
49
|
+
try {
|
|
50
|
+
const pkgJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
51
|
+
return typeof pkgJson.name === "string" ? pkgJson.name : relative(process.cwd(), root) || "unknown";
|
|
52
|
+
} catch {
|
|
53
|
+
return relative(process.cwd(), root) || "unknown";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function resolveStaleTargets(targetDir) {
|
|
57
|
+
const resolvedRoot = targetDir ? resolve(process.cwd(), targetDir) : process.cwd();
|
|
58
|
+
const context = resolveProjectContext({
|
|
59
|
+
cwd: process.cwd(),
|
|
60
|
+
targetPath: targetDir
|
|
61
|
+
});
|
|
62
|
+
const { checkStaleness } = await import("./staleness-NF-lxfXf.mjs");
|
|
63
|
+
if (context.packageRoot && (context.targetSkillsDir !== null || resolvedRoot !== context.workspaceRoot)) return { reports: [await checkStaleness(context.packageRoot, readPackageName(context.packageRoot))] };
|
|
64
|
+
if (existsSync(join(resolvedRoot, "skills"))) return { reports: [await checkStaleness(resolvedRoot, readPackageName(resolvedRoot))] };
|
|
65
|
+
const { findPackagesWithSkills, findWorkspaceRoot } = await import("./workspace-patterns-DbnA0peB.mjs");
|
|
66
|
+
const workspaceRoot = findWorkspaceRoot(resolvedRoot);
|
|
67
|
+
if (workspaceRoot) {
|
|
68
|
+
const packageDirs = findPackagesWithSkills(workspaceRoot);
|
|
69
|
+
if (packageDirs.length > 0) return { reports: await Promise.all(packageDirs.map((packageDir) => checkStaleness(packageDir, readPackageName(packageDir)))) };
|
|
70
|
+
}
|
|
71
|
+
const staleResult = await scanIntentsOrFail();
|
|
72
|
+
return { reports: await Promise.all(staleResult.packages.map((pkg) => checkStaleness(pkg.packageRoot, pkg.name))) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/commands/install-writer.ts
|
|
77
|
+
const INTENT_SKILLS_START = "<!-- intent-skills:start -->";
|
|
78
|
+
const INTENT_SKILLS_END = "<!-- intent-skills:end -->";
|
|
79
|
+
const SUPPORTED_AGENT_CONFIG_FILES = [
|
|
80
|
+
"AGENTS.md",
|
|
81
|
+
"CLAUDE.md",
|
|
82
|
+
".cursorrules",
|
|
83
|
+
".github/copilot-instructions.md"
|
|
84
|
+
];
|
|
85
|
+
const NON_ACTIONABLE_SKILL_TYPES = new Set([
|
|
86
|
+
"maintainer",
|
|
87
|
+
"maintainer-only",
|
|
88
|
+
"meta",
|
|
89
|
+
"reference"
|
|
90
|
+
]);
|
|
91
|
+
function normalizeBlock(content) {
|
|
92
|
+
return content.replace(/\r\n/g, "\n").trimEnd();
|
|
93
|
+
}
|
|
94
|
+
function readManagedBlock(content) {
|
|
95
|
+
const start = content.indexOf(INTENT_SKILLS_START);
|
|
96
|
+
const errors = [];
|
|
97
|
+
if (start === -1) errors.push("Missing intent-skills start marker.");
|
|
98
|
+
const endMarkerStart = start === -1 ? content.indexOf(INTENT_SKILLS_END) : content.indexOf(INTENT_SKILLS_END, start);
|
|
99
|
+
if (endMarkerStart === -1) errors.push("Missing intent-skills end marker.");
|
|
100
|
+
const hasMarker = start !== -1 || endMarkerStart !== -1;
|
|
101
|
+
if (errors.length > 0 || start === -1 || endMarkerStart === -1) return {
|
|
102
|
+
errors,
|
|
103
|
+
hasMarker,
|
|
104
|
+
managedBlock: null
|
|
105
|
+
};
|
|
106
|
+
const end = endMarkerStart + 26;
|
|
107
|
+
return {
|
|
108
|
+
errors,
|
|
109
|
+
hasMarker,
|
|
110
|
+
managedBlock: {
|
|
111
|
+
end,
|
|
112
|
+
start,
|
|
113
|
+
text: content.slice(start, end)
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function parseSkillsList(block) {
|
|
118
|
+
const yamlBody = normalizeBlock(block).split("\n").filter((line) => line !== INTENT_SKILLS_START && line !== INTENT_SKILLS_END).join("\n");
|
|
119
|
+
try {
|
|
120
|
+
const parsed = parse(yamlBody);
|
|
121
|
+
if (!parsed || !Array.isArray(parsed.skills)) return {
|
|
122
|
+
errors: ["Managed block must contain a skills list."],
|
|
123
|
+
skills: []
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
errors: [],
|
|
127
|
+
skills: parsed.skills
|
|
128
|
+
};
|
|
129
|
+
} catch (err) {
|
|
130
|
+
return {
|
|
131
|
+
errors: [`Managed block contains invalid YAML: ${err instanceof Error ? err.message : String(err)}`],
|
|
132
|
+
skills: []
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function verifyIntentSkillsBlockFile({ expectedBlock, expectedMappingCount, targetPath }) {
|
|
137
|
+
const errors = [];
|
|
138
|
+
if (!existsSync(targetPath)) return {
|
|
139
|
+
errors: [`Agent config file was not created: ${targetPath}`],
|
|
140
|
+
ok: false
|
|
141
|
+
};
|
|
142
|
+
const { managedBlock, errors: markerErrors } = readManagedBlock(readFileSync(targetPath, "utf8"));
|
|
143
|
+
errors.push(...markerErrors);
|
|
144
|
+
if (!managedBlock) return {
|
|
145
|
+
errors,
|
|
146
|
+
ok: false
|
|
147
|
+
};
|
|
148
|
+
const block = managedBlock.text;
|
|
149
|
+
if (normalizeBlock(block) !== normalizeBlock(expectedBlock)) errors.push("Managed block does not match generated mappings.");
|
|
150
|
+
if (expectedMappingCount === void 0) return {
|
|
151
|
+
errors,
|
|
152
|
+
ok: errors.length === 0
|
|
153
|
+
};
|
|
154
|
+
const { skills, errors: parseErrors } = parseSkillsList(block);
|
|
155
|
+
errors.push(...parseErrors);
|
|
156
|
+
if (skills.length !== expectedMappingCount) errors.push(`Expected ${expectedMappingCount} skill mappings, found ${skills.length}.`);
|
|
157
|
+
for (const skill of skills) {
|
|
158
|
+
if (!skill || typeof skill !== "object") {
|
|
159
|
+
errors.push("Each skill mapping must be an object.");
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const mapping = skill;
|
|
163
|
+
if (mapping.load !== void 0) errors.push("Skill mappings must use compact `use` entries, not `load`.");
|
|
164
|
+
if (typeof mapping.when !== "string" || mapping.when.trim() === "") errors.push("Each skill mapping must include a non-empty `when` field.");
|
|
165
|
+
if (typeof mapping.use !== "string") errors.push("Each skill mapping must include a `use` field.");
|
|
166
|
+
else try {
|
|
167
|
+
parseSkillUse(mapping.use);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
errors,
|
|
174
|
+
ok: errors.length === 0
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function resolveIntentSkillsBlockTargetPath(root, mappingCount) {
|
|
178
|
+
if (mappingCount === 0) return null;
|
|
179
|
+
return findExistingConfigWithManagedBlock(root)?.filePath ?? join(root, "AGENTS.md");
|
|
180
|
+
}
|
|
181
|
+
function compareNames(a, b) {
|
|
182
|
+
return a.name.localeCompare(b.name);
|
|
183
|
+
}
|
|
184
|
+
function quoteYamlString(value) {
|
|
185
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
|
|
186
|
+
}
|
|
187
|
+
function isActionableSkill(skill) {
|
|
188
|
+
const type = skill.type?.trim().toLowerCase();
|
|
189
|
+
return !type || !NON_ACTIONABLE_SKILL_TYPES.has(type);
|
|
190
|
+
}
|
|
191
|
+
function formatWhen(packageName, skill) {
|
|
192
|
+
return skill.description.replace(/\s+/g, " ").trim() || `Use ${packageName} ${skill.name}`;
|
|
193
|
+
}
|
|
194
|
+
function buildIntentSkillsBlock(scanResult) {
|
|
195
|
+
const lines = [
|
|
196
|
+
INTENT_SKILLS_START,
|
|
197
|
+
"# Skill mappings - load `use` with `npx @tanstack/intent@latest load <use>`.",
|
|
198
|
+
"skills:"
|
|
199
|
+
];
|
|
200
|
+
let mappingCount = 0;
|
|
201
|
+
for (const pkg of [...scanResult.packages].sort(compareNames)) for (const skill of [...pkg.skills].sort(compareNames)) {
|
|
202
|
+
if (!isActionableSkill(skill)) continue;
|
|
203
|
+
mappingCount++;
|
|
204
|
+
lines.push(` - when: ${quoteYamlString(formatWhen(pkg.name, skill))}`);
|
|
205
|
+
lines.push(` use: ${quoteYamlString(formatSkillUse(pkg.name, skill.name))}`);
|
|
206
|
+
}
|
|
207
|
+
if (mappingCount === 0) lines[2] = "skills: []";
|
|
208
|
+
lines.push(INTENT_SKILLS_END);
|
|
209
|
+
return {
|
|
210
|
+
block: `${lines.join("\n")}\n`,
|
|
211
|
+
mappingCount
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function buildIntentSkillGuidanceBlock() {
|
|
215
|
+
return {
|
|
216
|
+
block: `${[
|
|
217
|
+
INTENT_SKILLS_START,
|
|
218
|
+
"## Skill Loading",
|
|
219
|
+
"",
|
|
220
|
+
"Before substantial work:",
|
|
221
|
+
"- Skill check: run `npx @tanstack/intent@latest list`, or use skills already listed in context.",
|
|
222
|
+
"- Skill guidance: if one local skill clearly matches the task, run `npx @tanstack/intent@latest load <package>#<skill>` and follow the returned `SKILL.md`.",
|
|
223
|
+
"- Monorepos: when working across packages, run the skill check from the workspace root and prefer the local skill for the package being changed.",
|
|
224
|
+
"- Multiple matches: prefer the most specific local skill for the package or concern you are changing; load additional skills only when the task spans multiple packages or concerns.",
|
|
225
|
+
INTENT_SKILLS_END
|
|
226
|
+
].join("\n")}\n`,
|
|
227
|
+
mappingCount: 0
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function detectNewline(content) {
|
|
231
|
+
return content.includes("\r\n") ? "\r\n" : "\n";
|
|
232
|
+
}
|
|
233
|
+
function withNewlineStyle(content, newline) {
|
|
234
|
+
return newline === "\n" ? content : content.replace(/\n/g, newline);
|
|
235
|
+
}
|
|
236
|
+
function findExistingConfigWithManagedBlock(root) {
|
|
237
|
+
for (const file of SUPPORTED_AGENT_CONFIG_FILES) {
|
|
238
|
+
const filePath = join(root, file);
|
|
239
|
+
if (!existsSync(filePath)) continue;
|
|
240
|
+
const content = readFileSync(filePath, "utf8");
|
|
241
|
+
const { managedBlock, errors, hasMarker } = readManagedBlock(content);
|
|
242
|
+
if (managedBlock) return {
|
|
243
|
+
content,
|
|
244
|
+
filePath,
|
|
245
|
+
managedBlock
|
|
246
|
+
};
|
|
247
|
+
if (hasMarker) throw new Error(`Invalid intent-skills block in ${filePath}: ${errors.join(" ")}`);
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
function replaceManagedBlock(content, managedBlock, block) {
|
|
252
|
+
const newline = detectNewline(content);
|
|
253
|
+
const styledBlock = withNewlineStyle(block.trimEnd(), newline);
|
|
254
|
+
return `${content.slice(0, managedBlock.start)}${styledBlock}${content.slice(managedBlock.end)}`;
|
|
255
|
+
}
|
|
256
|
+
function writeIntentSkillsBlock({ block, mappingCount, root, skipWhenEmpty = true }) {
|
|
257
|
+
if (mappingCount === 0 && skipWhenEmpty) return {
|
|
258
|
+
mappingCount,
|
|
259
|
+
status: "skipped",
|
|
260
|
+
targetPath: null
|
|
261
|
+
};
|
|
262
|
+
const existingTarget = findExistingConfigWithManagedBlock(root);
|
|
263
|
+
const targetPath = existingTarget?.filePath ?? join(root, "AGENTS.md");
|
|
264
|
+
if (existingTarget) {
|
|
265
|
+
const nextContent = replaceManagedBlock(existingTarget.content, existingTarget.managedBlock, block);
|
|
266
|
+
if (nextContent === existingTarget.content) return {
|
|
267
|
+
mappingCount,
|
|
268
|
+
status: "unchanged",
|
|
269
|
+
targetPath
|
|
270
|
+
};
|
|
271
|
+
writeFileSync(targetPath, nextContent);
|
|
272
|
+
return {
|
|
273
|
+
mappingCount,
|
|
274
|
+
status: "updated",
|
|
275
|
+
targetPath
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (existsSync(targetPath)) {
|
|
279
|
+
const currentContent = readFileSync(targetPath, "utf8");
|
|
280
|
+
const newline = detectNewline(currentContent);
|
|
281
|
+
const separator = currentContent === "" ? "" : newline;
|
|
282
|
+
writeFileSync(targetPath, `${withNewlineStyle(block, newline)}${separator}${currentContent}`);
|
|
283
|
+
return {
|
|
284
|
+
mappingCount,
|
|
285
|
+
status: "updated",
|
|
286
|
+
targetPath
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
290
|
+
writeFileSync(targetPath, block);
|
|
291
|
+
return {
|
|
292
|
+
mappingCount,
|
|
293
|
+
status: "created",
|
|
294
|
+
targetPath
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/commands/install.ts
|
|
300
|
+
const INSTALL_PROMPT = `You are an AI assistant helping a developer set up skill-to-task mappings for their project.
|
|
301
|
+
|
|
302
|
+
Goal: create or update one agent config file with an intent-skills mapping block.
|
|
303
|
+
|
|
304
|
+
Hard rules:
|
|
305
|
+
- Do not report success until a file was created or updated, or an existing mapping block was confirmed.
|
|
306
|
+
- If skills are discovered and no mapping block exists, create AGENTS.md unless the user asks for another supported config file.
|
|
307
|
+
- If a mapping block already exists in a supported config file, update that file.
|
|
308
|
+
- Preserve all content outside the managed block unchanged.
|
|
309
|
+
- Store compact \`use\` values in the managed block; do not write \`load\` paths.
|
|
310
|
+
- Never write absolute local file paths, node_modules paths, or package-manager-internal paths in the managed block.
|
|
311
|
+
- Verify the target file before your final response.
|
|
312
|
+
|
|
313
|
+
Follow these steps in order:
|
|
314
|
+
|
|
315
|
+
1. CHECK FOR EXISTING MAPPINGS
|
|
316
|
+
Search the project's agent config files (AGENTS.md, CLAUDE.md, .cursorrules,
|
|
317
|
+
.github/copilot-instructions.md) for a block delimited by:
|
|
318
|
+
<!-- intent-skills:start -->
|
|
319
|
+
<!-- intent-skills:end -->
|
|
320
|
+
- If found: show the user the current mappings, keep that file as the source of truth,
|
|
321
|
+
and ask "What would you like to update?" Then skip to step 4 with their requested changes.
|
|
322
|
+
- If not found: continue to step 2.
|
|
323
|
+
|
|
324
|
+
2. DISCOVER AVAILABLE SKILLS
|
|
325
|
+
Run: \`npx @tanstack/intent@latest list\`
|
|
326
|
+
This scans project-local node_modules by default and outputs each package and skill's name,
|
|
327
|
+
description, and source.
|
|
328
|
+
If the user explicitly wants globally installed skills included, run:
|
|
329
|
+
\`npx @tanstack/intent@latest list --global\`
|
|
330
|
+
This works best in Node-compatible environments (npm, pnpm, Bun, or Deno npm interop
|
|
331
|
+
with node_modules enabled).
|
|
332
|
+
If no skills are found, do not create a config file. Report: "No intent-enabled skills found."
|
|
333
|
+
|
|
334
|
+
3. SCAN THE REPOSITORY
|
|
335
|
+
Build a picture of the project's structure and patterns:
|
|
336
|
+
- Read package.json for library dependencies
|
|
337
|
+
- Survey the directory layout (src/, app/, routes/, components/, api/, etc.)
|
|
338
|
+
- Note recurring patterns (routing, data fetching, auth, UI components, etc.)
|
|
339
|
+
|
|
340
|
+
Mapping coverage rule:
|
|
341
|
+
- Create mappings for all discovered actionable skills.
|
|
342
|
+
- Do not omit an actionable skill only because the repo does not currently appear to use it.
|
|
343
|
+
- Do not map reference, meta, maintainer, or maintainer-only skills by default.
|
|
344
|
+
- Include slash-named sub-skills when no parent mapping exists, or when they describe distinct user tasks.
|
|
345
|
+
- If the proposed block would exceed 12 mappings, show the full discovered list and ask which packages
|
|
346
|
+
or skill groups to include before writing.
|
|
347
|
+
- Add one fallback note telling the agent to run \`npx @tanstack/intent@latest list\` for less common local skills.
|
|
348
|
+
|
|
349
|
+
Based on the repository scan and the coverage rule, propose the skill-to-task mappings.
|
|
350
|
+
For each one explain:
|
|
351
|
+
- The task or code area (in plain language the user would recognise)
|
|
352
|
+
- Which skill applies and why
|
|
353
|
+
|
|
354
|
+
Then ask: "What other tasks do you commonly use AI coding agents for?
|
|
355
|
+
I'll create mappings for those too."
|
|
356
|
+
Also ask: "I'll default to AGENTS.md unless you want another supported config file.
|
|
357
|
+
Do you have a preference?"
|
|
358
|
+
|
|
359
|
+
4. WRITE THE MAPPINGS BLOCK
|
|
360
|
+
Once you have the full set of mappings, write or update the agent config file.
|
|
361
|
+
- If you found an existing intent-skills block, update that file in place.
|
|
362
|
+
- Otherwise prefer AGENTS.md by default, unless the user asked for another supported file.
|
|
363
|
+
- Do not stop after discovery. If skills were found, the task is incomplete until this file exists
|
|
364
|
+
and contains the managed block.
|
|
365
|
+
|
|
366
|
+
Use this exact block:
|
|
367
|
+
|
|
368
|
+
<!-- intent-skills:start -->
|
|
369
|
+
# Skill mappings - load \`use\` with \`npx @tanstack/intent@latest load <use>\`.
|
|
370
|
+
skills:
|
|
371
|
+
- when: "describe the task or code area here"
|
|
372
|
+
use: "@scope/package#skill-name"
|
|
373
|
+
<!-- intent-skills:end -->
|
|
374
|
+
|
|
375
|
+
Rules:
|
|
376
|
+
- Use the user's own words for \`when\` descriptions
|
|
377
|
+
- Use compact \`use\` values in \`<package>#<skill>\` format
|
|
378
|
+
- Do not include \`load\`
|
|
379
|
+
- Do not include machine-specific directories such as \`/Users/...\`, \`/home/...\`, \`/private/...\`,
|
|
380
|
+
drive letters, temp workspace paths, \`.pnpm/\`, \`.bun/\`, or \`.yarn/\`.
|
|
381
|
+
- Agents should load \`use\` at runtime with \`npx @tanstack/intent@latest load <use>\`
|
|
382
|
+
- Keep entries concise - this block is read on every agent task
|
|
383
|
+
- Preserve all content outside the block tags unchanged
|
|
384
|
+
- If the user is on Deno, note that this setup is best-effort today and relies on npm interop
|
|
385
|
+
|
|
386
|
+
5. VERIFY AND REPORT
|
|
387
|
+
Before reporting completion:
|
|
388
|
+
- Confirm the target file exists
|
|
389
|
+
- Confirm it contains both managed block markers
|
|
390
|
+
- Confirm every mapping has \`when\` and \`use\`
|
|
391
|
+
- Confirm every \`use\` parses as \`<package>#<skill>\`
|
|
392
|
+
- Confirm no mapping includes \`load\`
|
|
393
|
+
- Confirm no path-like machine-specific values are stored in the managed block
|
|
394
|
+
- Confirm every discovered actionable skill is mapped, skipped by rule, or deferred by user choice
|
|
395
|
+
|
|
396
|
+
Final response must include:
|
|
397
|
+
- The target file path
|
|
398
|
+
- Whether it was created, updated, or already contained a valid block
|
|
399
|
+
- The number of mappings
|
|
400
|
+
- The verification result`;
|
|
401
|
+
function formatTargetPath(targetPath) {
|
|
402
|
+
return relative(process.cwd(), targetPath) || targetPath;
|
|
403
|
+
}
|
|
404
|
+
function formatMappingCount(mappingCount) {
|
|
405
|
+
return `${mappingCount} ${mappingCount === 1 ? "mapping" : "mappings"}`;
|
|
406
|
+
}
|
|
407
|
+
function printNoActionableSkills(warnings) {
|
|
408
|
+
console.log("No intent-enabled skills found.");
|
|
409
|
+
printWarnings(warnings);
|
|
410
|
+
}
|
|
411
|
+
function printPlacementTip(targetPath) {
|
|
412
|
+
console.log(`Tip: Keep the intent-skills block near the top of ${formatTargetPath(targetPath)} so agents read it before task-specific instructions.`);
|
|
413
|
+
}
|
|
414
|
+
function printWriteResult({ mappingCount, status, targetPath }) {
|
|
415
|
+
const target = formatTargetPath(targetPath);
|
|
416
|
+
if (mappingCount === 0) {
|
|
417
|
+
switch (status) {
|
|
418
|
+
case "created":
|
|
419
|
+
console.log(`Created ${target} with skill loading guidance.`);
|
|
420
|
+
break;
|
|
421
|
+
case "updated":
|
|
422
|
+
console.log(`Updated ${target} with skill loading guidance.`);
|
|
423
|
+
break;
|
|
424
|
+
case "unchanged":
|
|
425
|
+
console.log(`No changes to ${target}; skill loading guidance already current.`);
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
switch (status) {
|
|
431
|
+
case "created":
|
|
432
|
+
console.log(`Created ${target} with ${formatMappingCount(mappingCount)}.`);
|
|
433
|
+
break;
|
|
434
|
+
case "updated":
|
|
435
|
+
console.log(`Updated ${target} with ${formatMappingCount(mappingCount)}.`);
|
|
436
|
+
break;
|
|
437
|
+
case "unchanged":
|
|
438
|
+
console.log(`No changes to ${target}; ${formatMappingCount(mappingCount)} already current.`);
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async function runInstallCommand(options, scanIntentsOrFail$1) {
|
|
443
|
+
if (options.printPrompt) {
|
|
444
|
+
console.log(INSTALL_PROMPT);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
scanOptionsFromGlobalFlags(options);
|
|
448
|
+
if (!options.map) {
|
|
449
|
+
const generated$1 = buildIntentSkillGuidanceBlock();
|
|
450
|
+
if (options.dryRun) {
|
|
451
|
+
const targetPath = resolveIntentSkillsBlockTargetPath(process.cwd(), 1);
|
|
452
|
+
console.log(`Generated skill loading guidance for ${formatTargetPath(targetPath)}.`);
|
|
453
|
+
console.log(generated$1.block);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const result$1 = writeIntentSkillsBlock({
|
|
457
|
+
...generated$1,
|
|
458
|
+
root: process.cwd(),
|
|
459
|
+
skipWhenEmpty: false
|
|
460
|
+
});
|
|
461
|
+
if (!result$1.targetPath) fail("Install guidance target was not created.");
|
|
462
|
+
const verification$1 = verifyIntentSkillsBlockFile({
|
|
463
|
+
expectedBlock: generated$1.block,
|
|
464
|
+
targetPath: result$1.targetPath
|
|
465
|
+
});
|
|
466
|
+
const target$1 = formatTargetPath(result$1.targetPath);
|
|
467
|
+
if (!verification$1.ok) fail([`Install verification failed for ${target$1}:`, ...verification$1.errors.map((error) => `- ${error}`)].join("\n"));
|
|
468
|
+
printWriteResult(result$1);
|
|
469
|
+
printPlacementTip(result$1.targetPath);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const scanResult = await scanIntentsOrFail$1(scanOptionsFromGlobalFlags(options));
|
|
473
|
+
const generated = buildIntentSkillsBlock(scanResult);
|
|
474
|
+
if (options.dryRun) {
|
|
475
|
+
const targetPath = resolveIntentSkillsBlockTargetPath(process.cwd(), generated.mappingCount);
|
|
476
|
+
if (!targetPath) {
|
|
477
|
+
printNoActionableSkills(scanResult.warnings);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
console.log(`Generated ${formatMappingCount(generated.mappingCount)} for ${formatTargetPath(targetPath)}.`);
|
|
481
|
+
console.log(generated.block);
|
|
482
|
+
printWarnings(scanResult.warnings);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const result = writeIntentSkillsBlock({
|
|
486
|
+
...generated,
|
|
487
|
+
root: process.cwd()
|
|
488
|
+
});
|
|
489
|
+
if (!result.targetPath) {
|
|
490
|
+
printNoActionableSkills(scanResult.warnings);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const target = formatTargetPath(result.targetPath);
|
|
494
|
+
const verification = verifyIntentSkillsBlockFile({
|
|
495
|
+
expectedBlock: generated.block,
|
|
496
|
+
expectedMappingCount: generated.mappingCount,
|
|
497
|
+
targetPath: result.targetPath
|
|
498
|
+
});
|
|
499
|
+
if (!verification.ok) fail([`Install verification failed for ${target}:`, ...verification.errors.map((error) => `- ${error}`)].join("\n"));
|
|
500
|
+
printWriteResult(result);
|
|
501
|
+
printPlacementTip(result.targetPath);
|
|
502
|
+
printWarnings(scanResult.warnings);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
//#endregion
|
|
506
|
+
export { scanIntentsOrFail as a, fail as c, resolveStaleTargets as i, isCliFailure as l, runInstallCommand as n, scanOptionsFromGlobalFlags as o, getMetaDir as r, printWarnings as s, INSTALL_PROMPT as t };
|
package/dist/intent-library.mjs
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./utils-COlDcU72.mjs";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
3
|
+
import "./skill-paths-8k9K9y26.mjs";
|
|
4
|
+
import "./workspace-patterns-U35B5AO-.mjs";
|
|
5
|
+
import "./project-context-CKG-q4rD.mjs";
|
|
6
|
+
import { t as INSTALL_PROMPT } from "./install-BmVqcbEi.mjs";
|
|
7
|
+
import { n as printSkillTree, r as printTable, t as computeSkillNameWidth } from "./display-DUgtRJkt.mjs";
|
|
8
|
+
import { t as scanLibrary } from "./library-scanner-DFFreLjW.mjs";
|
|
6
9
|
|
|
7
10
|
//#region src/intent-library.ts
|
|
8
11
|
function cmdList() {
|
|
9
12
|
let result;
|
|
10
13
|
try {
|
|
11
|
-
result = scanLibrary(process.argv[1]);
|
|
14
|
+
result = scanLibrary(process.argv[1], process.cwd());
|
|
12
15
|
} catch (err) {
|
|
13
16
|
console.error(err.message);
|
|
14
17
|
process.exit(1);
|
|
@@ -39,6 +42,7 @@ function cmdList() {
|
|
|
39
42
|
console.log(` ${pkg.name}`);
|
|
40
43
|
printSkillTree(pkg.skills, {
|
|
41
44
|
nameWidth,
|
|
45
|
+
packageName: pkg.name,
|
|
42
46
|
showTypes
|
|
43
47
|
});
|
|
44
48
|
console.log();
|
|
@@ -60,7 +64,7 @@ const USAGE = `TanStack Intent
|
|
|
60
64
|
Usage:
|
|
61
65
|
intent list List all available skills from this library and its dependencies
|
|
62
66
|
intent install Print a skill that guides your coding agent to scan the project
|
|
63
|
-
and set up skill
|
|
67
|
+
and set up skill loading guidance in your agent config`;
|
|
64
68
|
const command = process.argv[2];
|
|
65
69
|
switch (command) {
|
|
66
70
|
case "list":
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { a as parseFrontmatter, o as resolveDepDir, r as getDeps, s as toPosixPath } from "./utils-COlDcU72.mjs";
|
|
2
|
+
import { r as rewriteSkillLoadPaths } from "./skill-paths-8k9K9y26.mjs";
|
|
2
3
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
4
|
import { dirname, join, relative } from "node:path";
|
|
4
5
|
|
|
@@ -52,14 +53,14 @@ function discoverSkills(skillsDir) {
|
|
|
52
53
|
type: typeof fm?.type === "string" ? fm.type : void 0,
|
|
53
54
|
framework: typeof fm?.framework === "string" ? fm.framework : void 0
|
|
54
55
|
});
|
|
55
|
-
walk(childDir);
|
|
56
56
|
}
|
|
57
|
+
walk(childDir);
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
walk(skillsDir);
|
|
60
61
|
return skills;
|
|
61
62
|
}
|
|
62
|
-
function scanLibrary(scriptPath,
|
|
63
|
+
function scanLibrary(scriptPath, projectRoot) {
|
|
63
64
|
const packages = [];
|
|
64
65
|
const warnings = [];
|
|
65
66
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -74,6 +75,7 @@ function scanLibrary(scriptPath, _projectRoot) {
|
|
|
74
75
|
warnings: ["Could not read home package.json"]
|
|
75
76
|
};
|
|
76
77
|
const homeName = typeof homePkg.name === "string" ? homePkg.name : "";
|
|
78
|
+
const scanRoot = projectRoot ?? homeDir;
|
|
77
79
|
function processPackage(name, dir) {
|
|
78
80
|
if (visited.has(name)) return;
|
|
79
81
|
visited.add(name);
|
|
@@ -84,7 +86,12 @@ function scanLibrary(scriptPath, _projectRoot) {
|
|
|
84
86
|
}
|
|
85
87
|
const skillsDir = join(dir, "skills");
|
|
86
88
|
const skills = existsSync(skillsDir) ? discoverSkills(skillsDir) : [];
|
|
87
|
-
|
|
89
|
+
rewriteSkillLoadPaths({
|
|
90
|
+
packageName: name,
|
|
91
|
+
packageRoot: dir,
|
|
92
|
+
projectRoot: scanRoot,
|
|
93
|
+
skills
|
|
94
|
+
});
|
|
88
95
|
packages.push({
|
|
89
96
|
name,
|
|
90
97
|
version: typeof pkg.version === "string" ? pkg.version : "0.0.0",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { u as SkillEntry } from "./types-CsySN6Vw.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/library-scanner.d.ts
|
|
4
4
|
interface LibraryPackage {
|
|
@@ -11,6 +11,6 @@ interface LibraryScanResult {
|
|
|
11
11
|
packages: Array<LibraryPackage>;
|
|
12
12
|
warnings: Array<string>;
|
|
13
13
|
}
|
|
14
|
-
declare function scanLibrary(scriptPath: string,
|
|
14
|
+
declare function scanLibrary(scriptPath: string, projectRoot?: string): LibraryScanResult;
|
|
15
15
|
//#endregion
|
|
16
16
|
export { LibraryPackage, LibraryScanResult, scanLibrary };
|
package/dist/library-scanner.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as findWorkspaceRoot, r as readWorkspacePatterns } from "./workspace-patterns-
|
|
1
|
+
import { n as findWorkspaceRoot, r as readWorkspacePatterns } from "./workspace-patterns-U35B5AO-.mjs";
|
|
2
2
|
import { existsSync, statSync } from "node:fs";
|
|
3
3
|
import { dirname, join, relative, resolve } from "node:path";
|
|
4
4
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { i as parseSkillUse } from "./skill-use-CXOnncWK.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/resolver.ts
|
|
4
|
+
var ResolveSkillUseError = class extends Error {
|
|
5
|
+
constructor({ availablePackages = [], availableSkills = [], code, packageName, skillName, use }) {
|
|
6
|
+
super(formatResolveSkillUseErrorMessage({
|
|
7
|
+
availablePackages,
|
|
8
|
+
availableSkills,
|
|
9
|
+
code,
|
|
10
|
+
packageName,
|
|
11
|
+
skillName,
|
|
12
|
+
use
|
|
13
|
+
}));
|
|
14
|
+
this.name = "ResolveSkillUseError";
|
|
15
|
+
this.availablePackages = availablePackages;
|
|
16
|
+
this.availableSkills = availableSkills;
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.packageName = packageName;
|
|
19
|
+
this.skillName = skillName;
|
|
20
|
+
this.use = use;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function isResolveSkillUseError(error) {
|
|
24
|
+
return error instanceof ResolveSkillUseError;
|
|
25
|
+
}
|
|
26
|
+
function resolveSkillUse(use, scanResult) {
|
|
27
|
+
const { packageName, skillName } = parseSkillUse(use);
|
|
28
|
+
const packages = scanResult.packages.filter((pkg$1) => pkg$1.name === packageName);
|
|
29
|
+
const pkg = packages.find((candidate) => candidate.source === "local") ?? packages[0];
|
|
30
|
+
if (!pkg) throw new ResolveSkillUseError({
|
|
31
|
+
availablePackages: scanResult.packages.map((candidate) => candidate.name),
|
|
32
|
+
code: "package-not-found",
|
|
33
|
+
packageName,
|
|
34
|
+
skillName,
|
|
35
|
+
use
|
|
36
|
+
});
|
|
37
|
+
const skill = pkg.skills.find((candidate) => candidate.name === skillName);
|
|
38
|
+
if (!skill) throw new ResolveSkillUseError({
|
|
39
|
+
availableSkills: pkg.skills.map((candidate) => candidate.name),
|
|
40
|
+
code: "skill-not-found",
|
|
41
|
+
packageName,
|
|
42
|
+
skillName,
|
|
43
|
+
use
|
|
44
|
+
});
|
|
45
|
+
const conflict = scanResult.conflicts.find((candidate) => candidate.packageName === packageName) ?? null;
|
|
46
|
+
return {
|
|
47
|
+
packageName,
|
|
48
|
+
skillName,
|
|
49
|
+
path: skill.path,
|
|
50
|
+
source: pkg.source,
|
|
51
|
+
version: pkg.version,
|
|
52
|
+
packageRoot: pkg.packageRoot,
|
|
53
|
+
warnings: scanResult.warnings.filter((warning) => {
|
|
54
|
+
const idx = warning.indexOf(packageName);
|
|
55
|
+
if (idx === -1) return false;
|
|
56
|
+
const after = warning[idx + packageName.length];
|
|
57
|
+
return after === void 0 || /[^a-zA-Z0-9_-]/.test(after);
|
|
58
|
+
}),
|
|
59
|
+
conflict
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function formatResolveSkillUseErrorMessage({ availablePackages, availableSkills, code, packageName, skillName, use }) {
|
|
63
|
+
switch (code) {
|
|
64
|
+
case "package-not-found": return `Cannot resolve skill use "${use}": package "${packageName}" was not found.${availablePackages.length > 0 ? ` Available packages: ${availablePackages.join(", ")}.` : ""}`;
|
|
65
|
+
case "skill-not-found": return `Cannot resolve skill use "${use}": skill "${skillName}" was not found in package "${packageName}".${availableSkills.length > 0 ? ` Available skills: ${availableSkills.join(", ")}.` : ""}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { isResolveSkillUseError as n, resolveSkillUse as r, ResolveSkillUseError as t };
|