@tanstack/intent 0.0.29 → 0.0.33

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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -12
  3. package/dist/artifact-coverage-BAN2W6aH.mjs +3 -0
  4. package/dist/artifact-coverage-wLNVX8yC.mjs +128 -0
  5. package/dist/cli.d.mts +0 -0
  6. package/dist/cli.mjs +77 -76
  7. package/dist/{display-hdsqb4w-.mjs → display-BTZWCjzT.mjs} +10 -1
  8. package/dist/display-DvLbcWzq.mjs +5 -0
  9. package/dist/index.d.mts +76 -5
  10. package/dist/index.mjs +126 -6
  11. package/dist/install-PUnIfBNC.mjs +542 -0
  12. package/dist/intent-library.mjs +9 -5
  13. package/dist/{library-scanner-B51qV5aX.mjs → library-scanner-Cl-XPEMf.mjs} +10 -3
  14. package/dist/library-scanner.d.mts +2 -2
  15. package/dist/library-scanner.mjs +2 -1
  16. package/dist/{project-context-D6A5sBBO.mjs → project-context-IDLpJU3S.mjs} +1 -1
  17. package/dist/resolver-D2CgIYGg.mjs +70 -0
  18. package/dist/{scanner-B-bbXBLY.mjs → scanner-CW59cxE_.mjs} +176 -117
  19. package/dist/scanner-DlkcbVye.mjs +6 -0
  20. package/dist/{setup-B4ZwN5Hg.mjs → setup-DfLsziXU.mjs} +2 -2
  21. package/dist/setup.d.mts +1 -1
  22. package/dist/setup.mjs +3 -3
  23. package/dist/skill-paths-8k9K9y26.mjs +33 -0
  24. package/dist/skill-use-uwGleSOz.mjs +42 -0
  25. package/dist/staleness-DpbmYod4.mjs +5 -0
  26. package/dist/staleness-PdgakrCQ.mjs +243 -0
  27. package/dist/{types-BTQ9efv-.d.mts → types-_y9b00bI.d.mts} +54 -1
  28. package/dist/{workspace-patterns-Cndd-7vB.mjs → workspace-patterns-BN2A_60g.mjs} +6 -1
  29. package/dist/workspace-patterns-x-dLZxx4.mjs +4 -0
  30. package/meta/templates/workflows/check-skills.yml +199 -91
  31. package/package.json +3 -4
  32. package/dist/display-DdmZXLZm.mjs +0 -3
  33. package/dist/install-BzDmD5yI.mjs +0 -69
  34. package/dist/scanner-CP4U8F_n.mjs +0 -5
  35. package/dist/staleness-LRbiWWZK.mjs +0 -4
  36. package/dist/staleness-SY7-mZMH.mjs +0 -104
  37. package/dist/workspace-patterns-D_y6rlqX.mjs +0 -4
  38. package/meta/templates/workflows/notify-intent.yml +0 -51
  39. /package/dist/{setup-BA9RkENh.d.mts → setup-D2CGdTsx.d.mts} +0 -0
package/dist/index.mjs CHANGED
@@ -1,13 +1,133 @@
1
1
  import { a as parseFrontmatter, n as findSkillFiles, o as resolveDepDir, r as getDeps } from "./utils-COlDcU72.mjs";
2
- import "./workspace-patterns-Cndd-7vB.mjs";
3
- import { t as scanForIntents } from "./scanner-B-bbXBLY.mjs";
4
- import { t as checkStaleness } from "./staleness-SY7-mZMH.mjs";
5
- import "./project-context-D6A5sBBO.mjs";
6
- import { r as runSetupGithubActions, t as runEditPackageJson } from "./setup-B4ZwN5Hg.mjs";
2
+ import "./skill-paths-8k9K9y26.mjs";
3
+ import { t as scanForIntents } from "./scanner-CW59cxE_.mjs";
4
+ import "./workspace-patterns-BN2A_60g.mjs";
5
+ import { t as readIntentArtifacts } from "./artifact-coverage-wLNVX8yC.mjs";
6
+ import { n as checkStaleness } from "./staleness-PdgakrCQ.mjs";
7
+ import { i as parseSkillUse, n as formatSkillUse, r as isSkillUseParseError, t as SkillUseParseError } from "./skill-use-uwGleSOz.mjs";
8
+ import { n as isResolveSkillUseError, r as resolveSkillUse, t as ResolveSkillUseError } from "./resolver-D2CgIYGg.mjs";
9
+ import "./project-context-IDLpJU3S.mjs";
10
+ import { r as runSetupGithubActions, t as runEditPackageJson } from "./setup-DfLsziXU.mjs";
7
11
  import { readFileSync, writeFileSync } from "node:fs";
8
12
  import { join } from "node:path";
9
13
  import { execFileSync, execSync } from "node:child_process";
10
14
 
15
+ //#region src/workflow-review.ts
16
+ function collectStaleReviewItems(reports) {
17
+ const items = [];
18
+ for (const report of reports) {
19
+ for (const skill of report.skills ?? []) {
20
+ if (!skill?.needsReview) continue;
21
+ items.push({
22
+ type: "stale-skill",
23
+ library: report.library,
24
+ subject: skill.name,
25
+ reasons: skill.reasons ?? []
26
+ });
27
+ }
28
+ for (const signal of report.signals ?? []) {
29
+ if (signal?.needsReview === false) continue;
30
+ items.push({
31
+ type: signal?.type ?? "review-signal",
32
+ library: signal?.library ?? report.library,
33
+ subject: signal?.packageName ?? signal?.packageRoot ?? signal?.skill ?? signal?.artifactPath ?? signal?.subject ?? report.library,
34
+ reasons: signal?.reasons ?? [],
35
+ artifactPath: signal?.artifactPath,
36
+ packageName: signal?.packageName,
37
+ packageRoot: signal?.packageRoot,
38
+ skill: signal?.skill
39
+ });
40
+ }
41
+ }
42
+ return items;
43
+ }
44
+ function createFailedStaleReviewItem(library) {
45
+ return {
46
+ type: "stale-check-failed",
47
+ library,
48
+ subject: "intent stale --json",
49
+ reasons: ["The stale check command failed. Review the workflow logs before updating skills."]
50
+ };
51
+ }
52
+ function buildStaleReviewBody(items) {
53
+ const grouped = /* @__PURE__ */ new Map();
54
+ for (const item of items) grouped.set(item.type, (grouped.get(item.type) ?? 0) + 1);
55
+ const signalRows = [...grouped.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([type, count]) => `| \`${type}\` | ${count} |`);
56
+ const itemRows = items.map((item) => {
57
+ const subject = item.subject ? `\`${item.subject}\`` : "-";
58
+ const reasons = item.reasons?.length ? item.reasons.join("; ") : "-";
59
+ return `| \`${item.type}\` | ${subject} | \`${item.library}\` | ${reasons} |`;
60
+ });
61
+ const prompt = [
62
+ "You are helping maintain Intent skills for this repository.",
63
+ "",
64
+ "Goal:",
65
+ "Resolve the Intent skill review signals below while preserving the existing scope, taxonomy, and maintainer-reviewed artifacts.",
66
+ "",
67
+ "Review signals:",
68
+ JSON.stringify(items, null, 2),
69
+ "",
70
+ "Required workflow:",
71
+ "1. Read the existing `_artifacts/*domain_map.yaml`, `_artifacts/*skill_tree.yaml`, and generated `skills/**/SKILL.md` files.",
72
+ "2. Read each flagged package package.json, public exports, README/docs if present, and source entry points.",
73
+ "3. Compare flagged packages against the existing domains, skills, tasks, packages, covers, sources, tensions, and cross-references in the artifacts.",
74
+ "4. For each signal, decide whether it means existing skill coverage, a missing generated skill, a new skill candidate, out-of-scope coverage, or deferred work.",
75
+ "",
76
+ "Maintainer questions:",
77
+ "Before editing skills or artifacts, ask the maintainer:",
78
+ "1. For each flagged package, is this package user-facing enough to need agent guidance?",
79
+ "2. If yes, should it extend an existing skill or become a new skill?",
80
+ "3. If it extends an existing skill, which current skill should own it?",
81
+ "4. If it is out of scope, what short reason should be recorded in artifact coverage ignores?",
82
+ "5. Are any of these packages experimental or unstable enough to exclude for now?",
83
+ "",
84
+ "Decision rules:",
85
+ "- Do not auto-generate skills.",
86
+ "- Do not create broad new skill areas without maintainer confirmation.",
87
+ "- Prefer adding package coverage to an existing skill when the package is an implementation variant of an existing domain.",
88
+ "- Create a new skill only when the package introduces a distinct developer task or failure mode.",
89
+ "- Preserve current naming, path, and package layout conventions.",
90
+ "- Keep generated skills under the package-local `skills/` directory.",
91
+ "- Keep repo-root `_artifacts` as the reviewed plan.",
92
+ "",
93
+ "If maintainer confirms updates:",
94
+ "1. Update the relevant `_artifacts/*domain_map.yaml` or `_artifacts/*skill_tree.yaml`.",
95
+ "2. Update or create `SKILL.md` files only for confirmed coverage changes.",
96
+ "3. Keep `sources` aligned between artifact skill entries and SKILL frontmatter.",
97
+ "4. Bump `library_version` only for skills whose covered source package version changed.",
98
+ "5. Run `npx @tanstack/intent@latest validate` on touched skill directories.",
99
+ "6. Summarize every package as one of: existing-skill coverage, new skill, ignored, or deferred."
100
+ ].join("\n");
101
+ return [
102
+ "## Intent Skill Review Needed",
103
+ "",
104
+ "Intent found skills, artifact coverage, or workspace package coverage that need maintainer review.",
105
+ "",
106
+ "### Summary",
107
+ "",
108
+ "| Signal | Count |",
109
+ "| --- | ---: |",
110
+ ...signalRows,
111
+ "",
112
+ "### Review Items",
113
+ "",
114
+ "| Signal | Subject | Library | Reason |",
115
+ "| --- | --- | --- | --- |",
116
+ ...itemRows,
117
+ "",
118
+ "### Agent Prompt",
119
+ "",
120
+ "Paste this into your coding agent:",
121
+ "",
122
+ "```text",
123
+ prompt,
124
+ "```",
125
+ "",
126
+ "This PR is a review reminder only. It does not update skills automatically."
127
+ ].join("\n");
128
+ }
129
+
130
+ //#endregion
11
131
  //#region src/feedback.ts
12
132
  const META_FEEDBACK_REPO = "TanStack/intent";
13
133
  const SECRET_PATTERNS = [
@@ -271,4 +391,4 @@ function submitMetaFeedback(payload, opts) {
271
391
  }
272
392
 
273
393
  //#endregion
274
- export { checkStaleness, containsSecrets, findSkillFiles, getDeps, hasGhCli, metaToMarkdown, parseFrontmatter, resolveDepDir, resolveFrequency, runEditPackageJson, runSetupGithubActions, scanForIntents, submitFeedback, submitMetaFeedback, toMarkdown, validateMetaPayload, validatePayload };
394
+ export { ResolveSkillUseError, SkillUseParseError, buildStaleReviewBody, checkStaleness, collectStaleReviewItems, containsSecrets, createFailedStaleReviewItem, findSkillFiles, formatSkillUse, getDeps, hasGhCli, isResolveSkillUseError, isSkillUseParseError, metaToMarkdown, parseFrontmatter, parseSkillUse, readIntentArtifacts, resolveDepDir, resolveFrequency, resolveSkillUse, runEditPackageJson, runSetupGithubActions, scanForIntents, submitFeedback, submitMetaFeedback, toMarkdown, validateMetaPayload, validatePayload };
@@ -0,0 +1,542 @@
1
+ import { i as parseSkillUse, n as formatSkillUse } from "./skill-use-uwGleSOz.mjs";
2
+ import { t as resolveProjectContext } from "./project-context-IDLpJU3S.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
+ const INTENT_CHECK_SKILLS_WORKFLOW_VERSION = 2;
32
+ function getMetaDir() {
33
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "meta");
34
+ }
35
+ function getCheckSkillsWorkflowAdvisories(root) {
36
+ const workflowPath = join(root, ".github", "workflows", "check-skills.yml");
37
+ if (!existsSync(workflowPath)) return [];
38
+ let content;
39
+ try {
40
+ content = readFileSync(workflowPath, "utf8");
41
+ } catch {
42
+ return [];
43
+ }
44
+ const versionMatch = content.match(/intent-workflow-version:\s*(\d+)/);
45
+ if ((versionMatch ? Number(versionMatch[1]) : 0) >= INTENT_CHECK_SKILLS_WORKFLOW_VERSION) return [];
46
+ return [`Intent workflow update available: run \`npx @tanstack/intent@latest setup\` to refresh ${relative(process.cwd(), workflowPath) || workflowPath}.`];
47
+ }
48
+ async function scanIntentsOrFail(options) {
49
+ const { scanForIntents } = await import("./scanner-DlkcbVye.mjs");
50
+ try {
51
+ return scanForIntents(void 0, options);
52
+ } catch (err) {
53
+ fail(err instanceof Error ? err.message : String(err));
54
+ }
55
+ }
56
+ function scanOptionsFromGlobalFlags(options) {
57
+ if (options.global && options.globalOnly) fail("Use either --global or --global-only, not both.");
58
+ if (options.globalOnly) return { scope: "global" };
59
+ if (options.global) return { scope: "local-and-global" };
60
+ return { scope: "local" };
61
+ }
62
+ async function resolveStaleTargets(targetDir) {
63
+ const resolvedRoot = targetDir ? resolve(process.cwd(), targetDir) : process.cwd();
64
+ const context = resolveProjectContext({
65
+ cwd: process.cwd(),
66
+ targetPath: targetDir
67
+ });
68
+ const workflowAdvisories = getCheckSkillsWorkflowAdvisories(context.workspaceRoot ?? context.packageRoot ?? resolvedRoot);
69
+ const { buildWorkspaceCoverageSignals, checkStaleness, readPackageName } = await import("./staleness-DpbmYod4.mjs");
70
+ const isWorkspaceRootTarget = context.workspaceRoot !== null && resolvedRoot === context.workspaceRoot;
71
+ if (context.packageRoot && !isWorkspaceRootTarget && (context.targetSkillsDir !== null || context.workspaceRoot === null)) return {
72
+ reports: [await checkStaleness(context.packageRoot, readPackageName(context.packageRoot), context.workspaceRoot ?? context.packageRoot)],
73
+ workflowAdvisories
74
+ };
75
+ const { findPackagesWithSkills, findWorkspacePackages, findWorkspaceRoot } = await import("./workspace-patterns-x-dLZxx4.mjs");
76
+ const workspaceRoot = findWorkspaceRoot(resolvedRoot);
77
+ if (workspaceRoot) {
78
+ const packageDirsWithSkills = findPackagesWithSkills(workspaceRoot);
79
+ const allPackageDirs = findWorkspacePackages(workspaceRoot);
80
+ const reports = await Promise.all(packageDirsWithSkills.map((packageDir) => checkStaleness(packageDir, readPackageName(packageDir), workspaceRoot)));
81
+ const { readIntentArtifacts } = await import("./artifact-coverage-BAN2W6aH.mjs");
82
+ const coverageSignals = buildWorkspaceCoverageSignals({
83
+ artifactRoot: workspaceRoot,
84
+ artifacts: existsSync(join(workspaceRoot, "_artifacts")) ? readIntentArtifacts(workspaceRoot) : null,
85
+ packageDirs: allPackageDirs
86
+ });
87
+ if (coverageSignals.length > 0) reports.push({
88
+ library: relative(process.cwd(), workspaceRoot) || "workspace",
89
+ currentVersion: null,
90
+ skillVersion: null,
91
+ versionDrift: null,
92
+ skills: [],
93
+ signals: coverageSignals
94
+ });
95
+ if (reports.length > 0) return {
96
+ reports,
97
+ workflowAdvisories
98
+ };
99
+ }
100
+ if (existsSync(join(resolvedRoot, "skills"))) return {
101
+ reports: [await checkStaleness(resolvedRoot, readPackageName(resolvedRoot))],
102
+ workflowAdvisories
103
+ };
104
+ const staleResult = await scanIntentsOrFail();
105
+ return {
106
+ reports: await Promise.all(staleResult.packages.map((pkg) => checkStaleness(pkg.packageRoot, pkg.name))),
107
+ workflowAdvisories
108
+ };
109
+ }
110
+
111
+ //#endregion
112
+ //#region src/commands/install-writer.ts
113
+ const INTENT_SKILLS_START = "<!-- intent-skills:start -->";
114
+ const INTENT_SKILLS_END = "<!-- intent-skills:end -->";
115
+ const SUPPORTED_AGENT_CONFIG_FILES = [
116
+ "AGENTS.md",
117
+ "CLAUDE.md",
118
+ ".cursorrules",
119
+ ".github/copilot-instructions.md"
120
+ ];
121
+ const NON_ACTIONABLE_SKILL_TYPES = new Set([
122
+ "maintainer",
123
+ "maintainer-only",
124
+ "meta",
125
+ "reference"
126
+ ]);
127
+ function normalizeBlock(content) {
128
+ return content.replace(/\r\n/g, "\n").trimEnd();
129
+ }
130
+ function readManagedBlock(content) {
131
+ const start = content.indexOf(INTENT_SKILLS_START);
132
+ const errors = [];
133
+ if (start === -1) errors.push("Missing intent-skills start marker.");
134
+ const endMarkerStart = start === -1 ? content.indexOf(INTENT_SKILLS_END) : content.indexOf(INTENT_SKILLS_END, start);
135
+ if (endMarkerStart === -1) errors.push("Missing intent-skills end marker.");
136
+ const hasMarker = start !== -1 || endMarkerStart !== -1;
137
+ if (errors.length > 0 || start === -1 || endMarkerStart === -1) return {
138
+ errors,
139
+ hasMarker,
140
+ managedBlock: null
141
+ };
142
+ const end = endMarkerStart + 26;
143
+ return {
144
+ errors,
145
+ hasMarker,
146
+ managedBlock: {
147
+ end,
148
+ start,
149
+ text: content.slice(start, end)
150
+ }
151
+ };
152
+ }
153
+ function parseSkillsList(block) {
154
+ const yamlBody = normalizeBlock(block).split("\n").filter((line) => line !== INTENT_SKILLS_START && line !== INTENT_SKILLS_END).join("\n");
155
+ try {
156
+ const parsed = parse(yamlBody);
157
+ if (!parsed || !Array.isArray(parsed.skills)) return {
158
+ errors: ["Managed block must contain a skills list."],
159
+ skills: []
160
+ };
161
+ return {
162
+ errors: [],
163
+ skills: parsed.skills
164
+ };
165
+ } catch (err) {
166
+ return {
167
+ errors: [`Managed block contains invalid YAML: ${err instanceof Error ? err.message : String(err)}`],
168
+ skills: []
169
+ };
170
+ }
171
+ }
172
+ function verifyIntentSkillsBlockFile({ expectedBlock, expectedMappingCount, targetPath }) {
173
+ const errors = [];
174
+ if (!existsSync(targetPath)) return {
175
+ errors: [`Agent config file was not created: ${targetPath}`],
176
+ ok: false
177
+ };
178
+ const { managedBlock, errors: markerErrors } = readManagedBlock(readFileSync(targetPath, "utf8"));
179
+ errors.push(...markerErrors);
180
+ if (!managedBlock) return {
181
+ errors,
182
+ ok: false
183
+ };
184
+ const block = managedBlock.text;
185
+ if (normalizeBlock(block) !== normalizeBlock(expectedBlock)) errors.push("Managed block does not match generated mappings.");
186
+ if (expectedMappingCount === void 0) return {
187
+ errors,
188
+ ok: errors.length === 0
189
+ };
190
+ const { skills, errors: parseErrors } = parseSkillsList(block);
191
+ errors.push(...parseErrors);
192
+ if (skills.length !== expectedMappingCount) errors.push(`Expected ${expectedMappingCount} skill mappings, found ${skills.length}.`);
193
+ for (const skill of skills) {
194
+ if (!skill || typeof skill !== "object") {
195
+ errors.push("Each skill mapping must be an object.");
196
+ continue;
197
+ }
198
+ const mapping = skill;
199
+ if (mapping.load !== void 0) errors.push("Skill mappings must use compact `use` entries, not `load`.");
200
+ if (typeof mapping.when !== "string" || mapping.when.trim() === "") errors.push("Each skill mapping must include a non-empty `when` field.");
201
+ if (typeof mapping.use !== "string") errors.push("Each skill mapping must include a `use` field.");
202
+ else try {
203
+ parseSkillUse(mapping.use);
204
+ } catch (err) {
205
+ errors.push(err instanceof Error ? err.message : String(err));
206
+ }
207
+ }
208
+ return {
209
+ errors,
210
+ ok: errors.length === 0
211
+ };
212
+ }
213
+ function resolveIntentSkillsBlockTargetPath(root, mappingCount) {
214
+ if (mappingCount === 0) return null;
215
+ return findExistingConfigWithManagedBlock(root)?.filePath ?? join(root, "AGENTS.md");
216
+ }
217
+ function compareNames(a, b) {
218
+ return a.name.localeCompare(b.name);
219
+ }
220
+ function quoteYamlString(value) {
221
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
222
+ }
223
+ function isActionableSkill(skill) {
224
+ const type = skill.type?.trim().toLowerCase();
225
+ return !type || !NON_ACTIONABLE_SKILL_TYPES.has(type);
226
+ }
227
+ function formatWhen(packageName, skill) {
228
+ return skill.description.replace(/\s+/g, " ").trim() || `Use ${packageName} ${skill.name}`;
229
+ }
230
+ function buildIntentSkillsBlock(scanResult) {
231
+ const lines = [
232
+ INTENT_SKILLS_START,
233
+ "# Skill mappings - load `use` with `npx @tanstack/intent@latest load <use>`.",
234
+ "skills:"
235
+ ];
236
+ let mappingCount = 0;
237
+ for (const pkg of [...scanResult.packages].sort(compareNames)) for (const skill of [...pkg.skills].sort(compareNames)) {
238
+ if (!isActionableSkill(skill)) continue;
239
+ mappingCount++;
240
+ lines.push(` - when: ${quoteYamlString(formatWhen(pkg.name, skill))}`);
241
+ lines.push(` use: ${quoteYamlString(formatSkillUse(pkg.name, skill.name))}`);
242
+ }
243
+ if (mappingCount === 0) lines[2] = "skills: []";
244
+ lines.push(INTENT_SKILLS_END);
245
+ return {
246
+ block: `${lines.join("\n")}\n`,
247
+ mappingCount
248
+ };
249
+ }
250
+ function buildIntentSkillGuidanceBlock() {
251
+ return {
252
+ block: `${[
253
+ INTENT_SKILLS_START,
254
+ "## Skill Loading",
255
+ "",
256
+ "Before substantial work:",
257
+ "- Skill check: run `npx @tanstack/intent@latest list`, or use skills already listed in context.",
258
+ "- Skill guidance: if one local skill clearly matches the task, run `npx @tanstack/intent@latest load <package>#<skill>` and follow the returned `SKILL.md`.",
259
+ "- Monorepos: when working across packages, run the skill check from the workspace root and prefer the local skill for the package being changed.",
260
+ "- 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.",
261
+ INTENT_SKILLS_END
262
+ ].join("\n")}\n`,
263
+ mappingCount: 0
264
+ };
265
+ }
266
+ function detectNewline(content) {
267
+ return content.includes("\r\n") ? "\r\n" : "\n";
268
+ }
269
+ function withNewlineStyle(content, newline) {
270
+ return newline === "\n" ? content : content.replace(/\n/g, newline);
271
+ }
272
+ function findExistingConfigWithManagedBlock(root) {
273
+ for (const file of SUPPORTED_AGENT_CONFIG_FILES) {
274
+ const filePath = join(root, file);
275
+ if (!existsSync(filePath)) continue;
276
+ const content = readFileSync(filePath, "utf8");
277
+ const { managedBlock, errors, hasMarker } = readManagedBlock(content);
278
+ if (managedBlock) return {
279
+ content,
280
+ filePath,
281
+ managedBlock
282
+ };
283
+ if (hasMarker) throw new Error(`Invalid intent-skills block in ${filePath}: ${errors.join(" ")}`);
284
+ }
285
+ return null;
286
+ }
287
+ function replaceManagedBlock(content, managedBlock, block) {
288
+ const newline = detectNewline(content);
289
+ const styledBlock = withNewlineStyle(block.trimEnd(), newline);
290
+ return `${content.slice(0, managedBlock.start)}${styledBlock}${content.slice(managedBlock.end)}`;
291
+ }
292
+ function writeIntentSkillsBlock({ block, mappingCount, root, skipWhenEmpty = true }) {
293
+ if (mappingCount === 0 && skipWhenEmpty) return {
294
+ mappingCount,
295
+ status: "skipped",
296
+ targetPath: null
297
+ };
298
+ const existingTarget = findExistingConfigWithManagedBlock(root);
299
+ const targetPath = existingTarget?.filePath ?? join(root, "AGENTS.md");
300
+ if (existingTarget) {
301
+ const nextContent = replaceManagedBlock(existingTarget.content, existingTarget.managedBlock, block);
302
+ if (nextContent === existingTarget.content) return {
303
+ mappingCount,
304
+ status: "unchanged",
305
+ targetPath
306
+ };
307
+ writeFileSync(targetPath, nextContent);
308
+ return {
309
+ mappingCount,
310
+ status: "updated",
311
+ targetPath
312
+ };
313
+ }
314
+ if (existsSync(targetPath)) {
315
+ const currentContent = readFileSync(targetPath, "utf8");
316
+ const newline = detectNewline(currentContent);
317
+ const separator = currentContent === "" ? "" : newline;
318
+ writeFileSync(targetPath, `${withNewlineStyle(block, newline)}${separator}${currentContent}`);
319
+ return {
320
+ mappingCount,
321
+ status: "updated",
322
+ targetPath
323
+ };
324
+ }
325
+ mkdirSync(dirname(targetPath), { recursive: true });
326
+ writeFileSync(targetPath, block);
327
+ return {
328
+ mappingCount,
329
+ status: "created",
330
+ targetPath
331
+ };
332
+ }
333
+
334
+ //#endregion
335
+ //#region src/commands/install.ts
336
+ const INSTALL_PROMPT = `You are an AI assistant helping a developer set up skill-to-task mappings for their project.
337
+
338
+ Goal: create or update one agent config file with an intent-skills mapping block.
339
+
340
+ Hard rules:
341
+ - Do not report success until a file was created or updated, or an existing mapping block was confirmed.
342
+ - If skills are discovered and no mapping block exists, create AGENTS.md unless the user asks for another supported config file.
343
+ - If a mapping block already exists in a supported config file, update that file.
344
+ - Preserve all content outside the managed block unchanged.
345
+ - Store compact \`use\` values in the managed block; do not write \`load\` paths.
346
+ - Never write absolute local file paths, node_modules paths, or package-manager-internal paths in the managed block.
347
+ - Verify the target file before your final response.
348
+
349
+ Follow these steps in order:
350
+
351
+ 1. CHECK FOR EXISTING MAPPINGS
352
+ Search the project's agent config files (AGENTS.md, CLAUDE.md, .cursorrules,
353
+ .github/copilot-instructions.md) for a block delimited by:
354
+ <!-- intent-skills:start -->
355
+ <!-- intent-skills:end -->
356
+ - If found: show the user the current mappings, keep that file as the source of truth,
357
+ and ask "What would you like to update?" Then skip to step 4 with their requested changes.
358
+ - If not found: continue to step 2.
359
+
360
+ 2. DISCOVER AVAILABLE SKILLS
361
+ Run: \`npx @tanstack/intent@latest list\`
362
+ This scans project-local node_modules by default and outputs each package and skill's name,
363
+ description, and source.
364
+ If the user explicitly wants globally installed skills included, run:
365
+ \`npx @tanstack/intent@latest list --global\`
366
+ This works best in Node-compatible environments (npm, pnpm, Bun, or Deno npm interop
367
+ with node_modules enabled).
368
+ If no skills are found, do not create a config file. Report: "No intent-enabled skills found."
369
+
370
+ 3. SCAN THE REPOSITORY
371
+ Build a picture of the project's structure and patterns:
372
+ - Read package.json for library dependencies
373
+ - Survey the directory layout (src/, app/, routes/, components/, api/, etc.)
374
+ - Note recurring patterns (routing, data fetching, auth, UI components, etc.)
375
+
376
+ Mapping coverage rule:
377
+ - Create mappings for all discovered actionable skills.
378
+ - Do not omit an actionable skill only because the repo does not currently appear to use it.
379
+ - Do not map reference, meta, maintainer, or maintainer-only skills by default.
380
+ - Include slash-named sub-skills when no parent mapping exists, or when they describe distinct user tasks.
381
+ - If the proposed block would exceed 12 mappings, show the full discovered list and ask which packages
382
+ or skill groups to include before writing.
383
+ - Add one fallback note telling the agent to run \`npx @tanstack/intent@latest list\` for less common local skills.
384
+
385
+ Based on the repository scan and the coverage rule, propose the skill-to-task mappings.
386
+ For each one explain:
387
+ - The task or code area (in plain language the user would recognise)
388
+ - Which skill applies and why
389
+
390
+ Then ask: "What other tasks do you commonly use AI coding agents for?
391
+ I'll create mappings for those too."
392
+ Also ask: "I'll default to AGENTS.md unless you want another supported config file.
393
+ Do you have a preference?"
394
+
395
+ 4. WRITE THE MAPPINGS BLOCK
396
+ Once you have the full set of mappings, write or update the agent config file.
397
+ - If you found an existing intent-skills block, update that file in place.
398
+ - Otherwise prefer AGENTS.md by default, unless the user asked for another supported file.
399
+ - Do not stop after discovery. If skills were found, the task is incomplete until this file exists
400
+ and contains the managed block.
401
+
402
+ Use this exact block:
403
+
404
+ <!-- intent-skills:start -->
405
+ # Skill mappings - load \`use\` with \`npx @tanstack/intent@latest load <use>\`.
406
+ skills:
407
+ - when: "describe the task or code area here"
408
+ use: "@scope/package#skill-name"
409
+ <!-- intent-skills:end -->
410
+
411
+ Rules:
412
+ - Use the user's own words for \`when\` descriptions
413
+ - Use compact \`use\` values in \`<package>#<skill>\` format
414
+ - Do not include \`load\`
415
+ - Do not include machine-specific directories such as \`/Users/...\`, \`/home/...\`, \`/private/...\`,
416
+ drive letters, temp workspace paths, \`.pnpm/\`, \`.bun/\`, or \`.yarn/\`.
417
+ - Agents should load \`use\` at runtime with \`npx @tanstack/intent@latest load <use>\`
418
+ - Keep entries concise - this block is read on every agent task
419
+ - Preserve all content outside the block tags unchanged
420
+ - If the user is on Deno, note that this setup is best-effort today and relies on npm interop
421
+
422
+ 5. VERIFY AND REPORT
423
+ Before reporting completion:
424
+ - Confirm the target file exists
425
+ - Confirm it contains both managed block markers
426
+ - Confirm every mapping has \`when\` and \`use\`
427
+ - Confirm every \`use\` parses as \`<package>#<skill>\`
428
+ - Confirm no mapping includes \`load\`
429
+ - Confirm no path-like machine-specific values are stored in the managed block
430
+ - Confirm every discovered actionable skill is mapped, skipped by rule, or deferred by user choice
431
+
432
+ Final response must include:
433
+ - The target file path
434
+ - Whether it was created, updated, or already contained a valid block
435
+ - The number of mappings
436
+ - The verification result`;
437
+ function formatTargetPath(targetPath) {
438
+ return relative(process.cwd(), targetPath) || targetPath;
439
+ }
440
+ function formatMappingCount(mappingCount) {
441
+ return `${mappingCount} ${mappingCount === 1 ? "mapping" : "mappings"}`;
442
+ }
443
+ function printNoActionableSkills(warnings) {
444
+ console.log("No intent-enabled skills found.");
445
+ printWarnings(warnings);
446
+ }
447
+ function printPlacementTip(targetPath) {
448
+ console.log(`Tip: Keep the intent-skills block near the top of ${formatTargetPath(targetPath)} so agents read it before task-specific instructions.`);
449
+ }
450
+ function printWriteResult({ mappingCount, status, targetPath }) {
451
+ const target = formatTargetPath(targetPath);
452
+ if (mappingCount === 0) {
453
+ switch (status) {
454
+ case "created":
455
+ console.log(`Created ${target} with skill loading guidance.`);
456
+ break;
457
+ case "updated":
458
+ console.log(`Updated ${target} with skill loading guidance.`);
459
+ break;
460
+ case "unchanged":
461
+ console.log(`No changes to ${target}; skill loading guidance already current.`);
462
+ break;
463
+ }
464
+ return;
465
+ }
466
+ switch (status) {
467
+ case "created":
468
+ console.log(`Created ${target} with ${formatMappingCount(mappingCount)}.`);
469
+ break;
470
+ case "updated":
471
+ console.log(`Updated ${target} with ${formatMappingCount(mappingCount)}.`);
472
+ break;
473
+ case "unchanged":
474
+ console.log(`No changes to ${target}; ${formatMappingCount(mappingCount)} already current.`);
475
+ break;
476
+ }
477
+ }
478
+ async function runInstallCommand(options, scanIntentsOrFail$1) {
479
+ if (options.printPrompt) {
480
+ console.log(INSTALL_PROMPT);
481
+ return;
482
+ }
483
+ scanOptionsFromGlobalFlags(options);
484
+ if (!options.map) {
485
+ const generated$1 = buildIntentSkillGuidanceBlock();
486
+ if (options.dryRun) {
487
+ const targetPath = resolveIntentSkillsBlockTargetPath(process.cwd(), 1);
488
+ console.log(`Generated skill loading guidance for ${formatTargetPath(targetPath)}.`);
489
+ console.log(generated$1.block);
490
+ return;
491
+ }
492
+ const result$1 = writeIntentSkillsBlock({
493
+ ...generated$1,
494
+ root: process.cwd(),
495
+ skipWhenEmpty: false
496
+ });
497
+ if (!result$1.targetPath) fail("Install guidance target was not created.");
498
+ const verification$1 = verifyIntentSkillsBlockFile({
499
+ expectedBlock: generated$1.block,
500
+ targetPath: result$1.targetPath
501
+ });
502
+ const target$1 = formatTargetPath(result$1.targetPath);
503
+ if (!verification$1.ok) fail([`Install verification failed for ${target$1}:`, ...verification$1.errors.map((error) => `- ${error}`)].join("\n"));
504
+ printWriteResult(result$1);
505
+ printPlacementTip(result$1.targetPath);
506
+ return;
507
+ }
508
+ const scanResult = await scanIntentsOrFail$1(scanOptionsFromGlobalFlags(options));
509
+ const generated = buildIntentSkillsBlock(scanResult);
510
+ if (options.dryRun) {
511
+ const targetPath = resolveIntentSkillsBlockTargetPath(process.cwd(), generated.mappingCount);
512
+ if (!targetPath) {
513
+ printNoActionableSkills(scanResult.warnings);
514
+ return;
515
+ }
516
+ console.log(`Generated ${formatMappingCount(generated.mappingCount)} for ${formatTargetPath(targetPath)}.`);
517
+ console.log(generated.block);
518
+ printWarnings(scanResult.warnings);
519
+ return;
520
+ }
521
+ const result = writeIntentSkillsBlock({
522
+ ...generated,
523
+ root: process.cwd()
524
+ });
525
+ if (!result.targetPath) {
526
+ printNoActionableSkills(scanResult.warnings);
527
+ return;
528
+ }
529
+ const target = formatTargetPath(result.targetPath);
530
+ const verification = verifyIntentSkillsBlockFile({
531
+ expectedBlock: generated.block,
532
+ expectedMappingCount: generated.mappingCount,
533
+ targetPath: result.targetPath
534
+ });
535
+ if (!verification.ok) fail([`Install verification failed for ${target}:`, ...verification.errors.map((error) => `- ${error}`)].join("\n"));
536
+ printWriteResult(result);
537
+ printPlacementTip(result.targetPath);
538
+ printWarnings(scanResult.warnings);
539
+ }
540
+
541
+ //#endregion
542
+ 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 };