@tanstack/intent 0.0.14 → 0.0.20

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 (36) hide show
  1. package/README.md +42 -5
  2. package/dist/cli.d.mts +6 -1
  3. package/dist/cli.mjs +253 -170
  4. package/dist/display-CuCDLPP_.mjs +3 -0
  5. package/dist/index.d.mts +10 -11
  6. package/dist/index.mjs +37 -14
  7. package/dist/install-prompt-C0M-U3WZ.mjs +59 -0
  8. package/dist/intent-library.mjs +5 -49
  9. package/dist/{library-scanner-B1tmOzwf.mjs → library-scanner-DBOEhfm8.mjs} +4 -5
  10. package/dist/library-scanner.d.mts +5 -5
  11. package/dist/library-scanner.mjs +2 -2
  12. package/dist/scanner-BHPl60jH.mjs +5 -0
  13. package/dist/scanner-DVepyEwz.mjs +365 -0
  14. package/dist/setup-B-zdCBu4.d.mts +36 -0
  15. package/dist/setup-mGV2dZrq.mjs +367 -0
  16. package/dist/setup.d.mts +2 -2
  17. package/dist/setup.mjs +3 -2
  18. package/dist/{staleness-DJfMKH62.mjs → staleness-DZKvsLVq.mjs} +24 -3
  19. package/dist/staleness-Dr5-5wj5.mjs +4 -0
  20. package/dist/{types-BmnI8kFB.d.mts → types-ddLtccfV.d.mts} +30 -7
  21. package/dist/utils-BfjM1mQe.mjs +152 -0
  22. package/dist/utils-D7OKi0Rn.mjs +3 -0
  23. package/meta/domain-discovery/SKILL.md +95 -20
  24. package/meta/feedback-collection/SKILL.md +20 -1
  25. package/meta/generate-skill/SKILL.md +56 -5
  26. package/meta/templates/workflows/check-skills.yml +4 -4
  27. package/meta/templates/workflows/{notify-playbooks.yml → notify-intent.yml} +4 -4
  28. package/meta/tree-generator/SKILL.md +2 -2
  29. package/package.json +9 -5
  30. package/dist/scanner-CECGXgox.mjs +0 -4
  31. package/dist/scanner-CY40iozO.mjs +0 -218
  32. package/dist/setup-CANkTz55.d.mts +0 -18
  33. package/dist/setup-Nif1-nhS.mjs +0 -211
  34. package/dist/staleness-C1h7RuZ9.mjs +0 -4
  35. package/dist/utils-CDJzAdjD.mjs +0 -79
  36. /package/dist/{display-D_XzuGnu.mjs → display-DhsUxNJW.mjs} +0 -0
@@ -0,0 +1,367 @@
1
+ import { n as findSkillFiles } from "./utils-BfjM1mQe.mjs";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
3
+ import { basename, join, relative } from "node:path";
4
+ import { parse } from "yaml";
5
+
6
+ //#region src/setup.ts
7
+ function readPackageJson(root) {
8
+ const pkgPath = join(root, "package.json");
9
+ try {
10
+ return JSON.parse(readFileSync(pkgPath, "utf8"));
11
+ } catch (err) {
12
+ if (!(err && typeof err === "object" && "code" in err && err.code === "ENOENT")) console.error(`Warning: could not read ${pkgPath}: ${err instanceof Error ? err.message : err}`);
13
+ return {};
14
+ }
15
+ }
16
+ function detectRepo(pkgJson, fallback) {
17
+ const intent = pkgJson.intent;
18
+ if (typeof intent?.repo === "string") return intent.repo;
19
+ if (typeof pkgJson.repository === "string") return pkgJson.repository.replace(/^git\+/, "").replace(/\.git$/, "").replace(/^https?:\/\/github\.com\//, "");
20
+ if (pkgJson.repository && typeof pkgJson.repository === "object" && typeof pkgJson.repository.url === "string") return pkgJson.repository.url.replace(/^git\+/, "").replace(/\.git$/, "").replace(/^https?:\/\/github\.com\//, "");
21
+ return fallback;
22
+ }
23
+ function normalizePattern(pattern) {
24
+ return pattern.endsWith("**") ? pattern : pattern.replace(/\/$/, "") + "/**";
25
+ }
26
+ function buildWatchPaths(root, packageDirs) {
27
+ const paths = /* @__PURE__ */ new Set();
28
+ if (existsSync(join(root, "docs"))) paths.add("docs/**");
29
+ for (const packageDir of packageDirs) {
30
+ const relDir = relative(root, packageDir).split("\\").join("/");
31
+ if (existsSync(join(packageDir, "src"))) paths.add(`${relDir}/src/**`);
32
+ const intent = readPackageJson(packageDir).intent;
33
+ const docs = typeof intent?.docs === "string" ? intent.docs : "docs/";
34
+ if (!docs.startsWith("http://") && !docs.startsWith("https://")) paths.add(normalizePattern(join(relDir, docs).split("\\").join("/")));
35
+ }
36
+ if (paths.size === 0) {
37
+ paths.add("packages/*/src/**");
38
+ paths.add("packages/*/docs/**");
39
+ }
40
+ return [...paths].sort().map((path) => ` - '${path}'`).join("\n");
41
+ }
42
+ function detectVars(root, packageDirs) {
43
+ const pkgJson = readPackageJson(root);
44
+ const name = typeof pkgJson.name === "string" ? pkgJson.name : "unknown";
45
+ const docs = typeof pkgJson.intent?.docs === "string" ? pkgJson.intent.docs : "docs/";
46
+ const repo = detectRepo(pkgJson, name.replace(/^@/, "").replace(/\//, "/"));
47
+ const isMonorepo = packageDirs !== void 0;
48
+ const packageLabel = isMonorepo && name === "unknown" ? `${basename(root)} workspace` : name;
49
+ let srcPath = `packages/${name.replace(/^@[^/]+\//, "")}/src/**`;
50
+ if (existsSync(join(root, "src"))) srcPath = "src/**";
51
+ return {
52
+ PACKAGE_NAME: name,
53
+ PACKAGE_LABEL: packageLabel,
54
+ PAYLOAD_PACKAGE: packageLabel,
55
+ REPO: repo,
56
+ DOCS_PATH: docs.endsWith("**") ? docs : docs.replace(/\/$/, "") + "/**",
57
+ SRC_PATH: srcPath,
58
+ WATCH_PATHS: isMonorepo ? buildWatchPaths(root, packageDirs) : ` - '${docs.endsWith("**") ? docs : docs.replace(/\/$/, "") + "/**"}'\n - '${srcPath}'`
59
+ };
60
+ }
61
+ function applyVars(content, vars) {
62
+ return content.replace(/\{\{PACKAGE_NAME\}\}/g, vars.PACKAGE_NAME).replace(/\{\{PACKAGE_LABEL\}\}/g, vars.PACKAGE_LABEL).replace(/\{\{PAYLOAD_PACKAGE\}\}/g, vars.PAYLOAD_PACKAGE).replace(/\{\{REPO\}\}/g, vars.REPO).replace(/\{\{DOCS_PATH\}\}/g, vars.DOCS_PATH).replace(/\{\{SRC_PATH\}\}/g, vars.SRC_PATH).replace(/\{\{WATCH_PATHS\}\}/g, vars.WATCH_PATHS);
63
+ }
64
+ function copyTemplates(srcDir, destDir, vars) {
65
+ const copied = [];
66
+ const skipped = [];
67
+ if (!existsSync(srcDir)) return {
68
+ copied,
69
+ skipped
70
+ };
71
+ mkdirSync(destDir, { recursive: true });
72
+ for (const entry of readdirSync(srcDir)) {
73
+ const srcPath = join(srcDir, entry);
74
+ const destPath = join(destDir, entry);
75
+ if (existsSync(destPath)) {
76
+ skipped.push(destPath);
77
+ continue;
78
+ }
79
+ let content = readFileSync(srcPath, "utf8");
80
+ if (vars.WATCH_PATHS.includes("\n")) content = content.replace(/\s+- '?\{\{DOCS_PATH\}\}'?\n\s+- '?\{\{SRC_PATH\}\}'?/, vars.WATCH_PATHS);
81
+ writeFileSync(destPath, applyVars(content, vars));
82
+ copied.push(destPath);
83
+ }
84
+ return {
85
+ copied,
86
+ skipped
87
+ };
88
+ }
89
+ function getShimContent(ext) {
90
+ return `#!/usr/bin/env node
91
+ // Auto-generated by @tanstack/intent setup
92
+ // Exposes the intent end-user CLI for consumers of this library.
93
+ // Commit this file, then add to your package.json:
94
+ // "bin": { "intent": "./bin/intent.${ext}" }
95
+ try {
96
+ await import('@tanstack/intent/intent-library')
97
+ } catch (e) {
98
+ if (e?.code === 'ERR_MODULE_NOT_FOUND' || e?.code === 'MODULE_NOT_FOUND') {
99
+ console.error('@tanstack/intent is not installed.')
100
+ console.error('')
101
+ console.error('Install it as a dev dependency:')
102
+ console.error(' npm add -D @tanstack/intent')
103
+ console.error('')
104
+ console.error('Or run directly:')
105
+ console.error(' npx @tanstack/intent@latest list')
106
+ process.exit(1)
107
+ }
108
+ throw e
109
+ }
110
+ `;
111
+ }
112
+ function detectShimExtension(root) {
113
+ try {
114
+ if (JSON.parse(readFileSync(join(root, "package.json"), "utf8")).type === "module") return "js";
115
+ } catch (err) {
116
+ if (!(err && typeof err === "object" && "code" in err && err.code === "ENOENT")) console.error(`Warning: could not read package.json: ${err instanceof Error ? err.message : err}`);
117
+ }
118
+ return "mjs";
119
+ }
120
+ function findExistingShim(root) {
121
+ const shimJs = join(root, "bin", "intent.js");
122
+ if (existsSync(shimJs)) return shimJs;
123
+ const shimMjs = join(root, "bin", "intent.mjs");
124
+ if (existsSync(shimMjs)) return shimMjs;
125
+ return null;
126
+ }
127
+ function runAddLibraryBin(root) {
128
+ const result = {
129
+ shim: null,
130
+ skipped: null
131
+ };
132
+ const existingShim = findExistingShim(root);
133
+ if (existingShim) {
134
+ result.skipped = existingShim;
135
+ console.log(` Already exists: ${existingShim}`);
136
+ return result;
137
+ }
138
+ const ext = detectShimExtension(root);
139
+ const shimPath = join(root, "bin", `intent.${ext}`);
140
+ mkdirSync(join(root, "bin"), { recursive: true });
141
+ writeFileSync(shimPath, getShimContent(ext));
142
+ result.shim = shimPath;
143
+ console.log(`✓ Generated intent shim: ${shimPath}`);
144
+ console.log(`\n Run \`npx @tanstack/intent edit-package-json\` to wire package.json.`);
145
+ return result;
146
+ }
147
+ function runEditPackageJson(root) {
148
+ const result = {
149
+ added: [],
150
+ alreadyPresent: []
151
+ };
152
+ const pkgPath = join(root, "package.json");
153
+ if (!existsSync(pkgPath)) {
154
+ console.error("No package.json found in " + root);
155
+ process.exitCode = 1;
156
+ return result;
157
+ }
158
+ const raw = readFileSync(pkgPath, "utf8");
159
+ let pkg;
160
+ try {
161
+ pkg = JSON.parse(raw);
162
+ } catch (err) {
163
+ const detail = err instanceof SyntaxError ? err.message : String(err);
164
+ console.error(`Failed to parse ${pkgPath}: ${detail}`);
165
+ return result;
166
+ }
167
+ const indentMatch = raw.match(/^(\s+)"/m);
168
+ const indentSize = indentMatch?.[1] ? indentMatch[1].length : 2;
169
+ if (!Array.isArray(pkg.keywords)) pkg.keywords = [];
170
+ const keywords = pkg.keywords;
171
+ for (const kw of ["tanstack-intent"]) if (keywords.includes(kw)) result.alreadyPresent.push(`keywords: "${kw}"`);
172
+ else {
173
+ keywords.push(kw);
174
+ result.added.push(`keywords: "${kw}"`);
175
+ }
176
+ if (!Array.isArray(pkg.files)) pkg.files = [];
177
+ const files = pkg.files;
178
+ const requiredFiles = (() => {
179
+ let dir = join(root, "..");
180
+ for (let i = 0; i < 5; i++) {
181
+ const parentPkg = join(dir, "package.json");
182
+ if (existsSync(parentPkg)) {
183
+ try {
184
+ const parent = JSON.parse(readFileSync(parentPkg, "utf8"));
185
+ if (Array.isArray(parent.workspaces) || parent.workspaces?.packages) return true;
186
+ } catch {}
187
+ return false;
188
+ }
189
+ const next = join(dir, "..");
190
+ if (next === dir) break;
191
+ dir = next;
192
+ }
193
+ return false;
194
+ })() ? ["skills", "bin"] : [
195
+ "skills",
196
+ "bin",
197
+ "!skills/_artifacts"
198
+ ];
199
+ for (const entry of requiredFiles) if (files.includes(entry)) result.alreadyPresent.push(`files: "${entry}"`);
200
+ else {
201
+ files.push(entry);
202
+ result.added.push(`files: "${entry}"`);
203
+ }
204
+ const existingShim = findExistingShim(root);
205
+ let ext;
206
+ if (existingShim) ext = existingShim.endsWith(".mjs") ? "mjs" : "js";
207
+ else ext = pkg.type === "module" ? "js" : "mjs";
208
+ const shimRelative = `./bin/intent.${ext}`;
209
+ if (typeof pkg.bin === "object" && pkg.bin !== null) {
210
+ const binObj = pkg.bin;
211
+ if (binObj.intent) result.alreadyPresent.push(`bin.intent`);
212
+ else {
213
+ binObj.intent = shimRelative;
214
+ result.added.push(`bin.intent: "${shimRelative}"`);
215
+ }
216
+ } else if (!pkg.bin) {
217
+ pkg.bin = { intent: shimRelative };
218
+ result.added.push(`bin.intent: "${shimRelative}"`);
219
+ } else if (typeof pkg.bin === "string") {
220
+ const pkgName = typeof pkg.name === "string" ? pkg.name.replace(/^@[^/]+\//, "") : "unknown";
221
+ pkg.bin = {
222
+ [pkgName]: pkg.bin,
223
+ intent: shimRelative
224
+ };
225
+ result.added.push(`bin.intent: "${shimRelative}" (converted bin from string to object)`);
226
+ }
227
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, indentSize) + "\n");
228
+ for (const a of result.added) console.log(`✓ Added ${a}`);
229
+ for (const a of result.alreadyPresent) console.log(` Already present: ${a}`);
230
+ return result;
231
+ }
232
+ function readWorkspacePatterns(root) {
233
+ const pnpmWs = join(root, "pnpm-workspace.yaml");
234
+ if (existsSync(pnpmWs)) try {
235
+ const config = parse(readFileSync(pnpmWs, "utf8"));
236
+ if (Array.isArray(config.packages)) return config.packages;
237
+ } catch (err) {
238
+ console.error(`Warning: failed to parse ${pnpmWs}: ${err instanceof Error ? err.message : err}`);
239
+ }
240
+ const pkgPath = join(root, "package.json");
241
+ if (existsSync(pkgPath)) try {
242
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
243
+ if (Array.isArray(pkg.workspaces)) return pkg.workspaces;
244
+ if (Array.isArray(pkg.workspaces?.packages)) return pkg.workspaces.packages;
245
+ } catch (err) {
246
+ console.error(`Warning: failed to parse ${pkgPath}: ${err instanceof Error ? err.message : err}`);
247
+ }
248
+ return null;
249
+ }
250
+ /**
251
+ * Resolve workspace glob patterns to actual package directories.
252
+ * Handles simple patterns like "packages/*" and "packages/**".
253
+ * Each resolved directory must contain a package.json.
254
+ */
255
+ function resolveWorkspacePackages(root, patterns) {
256
+ const dirs = [];
257
+ for (const pattern of patterns) {
258
+ const baseDir = join(root, pattern.replace(/\/\*\*?(\/\*)?$/, ""));
259
+ if (!existsSync(baseDir)) continue;
260
+ if (pattern.includes("**")) collectPackageDirs(baseDir, dirs);
261
+ else if (pattern.endsWith("/*")) {
262
+ let entries;
263
+ try {
264
+ entries = readdirSync(baseDir, { withFileTypes: true });
265
+ } catch {
266
+ continue;
267
+ }
268
+ for (const entry of entries) {
269
+ if (!entry.isDirectory()) continue;
270
+ const dir = join(baseDir, entry.name);
271
+ if (existsSync(join(dir, "package.json"))) dirs.push(dir);
272
+ }
273
+ } else {
274
+ const dir = join(root, pattern);
275
+ if (existsSync(join(dir, "package.json"))) dirs.push(dir);
276
+ }
277
+ }
278
+ return dirs;
279
+ }
280
+ function collectPackageDirs(dir, result) {
281
+ if (existsSync(join(dir, "package.json"))) result.push(dir);
282
+ let entries;
283
+ try {
284
+ entries = readdirSync(dir, { withFileTypes: true });
285
+ } catch (err) {
286
+ console.error(`Warning: could not read directory ${dir}: ${err instanceof Error ? err.message : err}`);
287
+ return;
288
+ }
289
+ for (const entry of entries) {
290
+ if (!entry.isDirectory() || entry.name === "node_modules" || entry.name.startsWith(".")) continue;
291
+ collectPackageDirs(join(dir, entry.name), result);
292
+ }
293
+ }
294
+ function findWorkspaceRoot(start) {
295
+ let dir = start;
296
+ while (true) {
297
+ if (readWorkspacePatterns(dir)) return dir;
298
+ const next = join(dir, "..");
299
+ if (next === dir) return null;
300
+ dir = next;
301
+ }
302
+ }
303
+ /**
304
+ * Find workspace packages that contain at least one SKILL.md file.
305
+ */
306
+ function findPackagesWithSkills(root) {
307
+ const patterns = readWorkspacePatterns(root);
308
+ if (!patterns) return [];
309
+ return resolveWorkspacePackages(root, patterns).filter((dir) => {
310
+ const skillsDir = join(dir, "skills");
311
+ return existsSync(skillsDir) && findSkillFiles(skillsDir).length > 0;
312
+ });
313
+ }
314
+ /**
315
+ * When run from a monorepo root, finds all workspace packages with SKILL.md
316
+ * files and runs the given command on each. Falls back to single-package
317
+ * behavior only when no workspace config is detected. If workspace config
318
+ * exists but no packages have skills, warns and returns empty.
319
+ */
320
+ function runForEachPackage(root, runOne) {
321
+ const isMonorepo = readWorkspacePatterns(root) !== null;
322
+ const pkgsWithSkills = isMonorepo ? findPackagesWithSkills(root) : [];
323
+ if (!isMonorepo) return runOne(root);
324
+ if (pkgsWithSkills.length === 0) {
325
+ console.log("No workspace packages with skills found.");
326
+ return [];
327
+ }
328
+ return pkgsWithSkills.map((pkgDir) => {
329
+ const rel = relative(root, pkgDir) || ".";
330
+ console.log(`\n── ${rel} ──`);
331
+ return {
332
+ package: rel,
333
+ result: runOne(pkgDir)
334
+ };
335
+ });
336
+ }
337
+ function runEditPackageJsonAll(root) {
338
+ return runForEachPackage(root, runEditPackageJson);
339
+ }
340
+ function runAddLibraryBinAll(root) {
341
+ return runForEachPackage(root, runAddLibraryBin);
342
+ }
343
+ function runSetupGithubActions(root, metaDir) {
344
+ const workspaceRoot = findWorkspaceRoot(root) ?? root;
345
+ const packageDirs = findPackagesWithSkills(workspaceRoot);
346
+ const vars = detectVars(workspaceRoot, packageDirs.length > 0 ? packageDirs : void 0);
347
+ const result = {
348
+ workflows: [],
349
+ skipped: []
350
+ };
351
+ const { copied, skipped } = copyTemplates(join(metaDir, "templates", "workflows"), join(workspaceRoot, ".github", "workflows"), vars);
352
+ result.workflows = copied;
353
+ result.skipped = skipped;
354
+ for (const f of result.workflows) console.log(`✓ Copied workflow: ${f}`);
355
+ for (const f of result.skipped) console.log(` Already exists: ${f}`);
356
+ if (result.workflows.length === 0 && result.skipped.length === 0) console.log("No templates directory found. Is @tanstack/intent installed?");
357
+ else if (result.workflows.length > 0) {
358
+ console.log(`\nTemplate variables applied:`);
359
+ console.log(` Package: ${vars.PACKAGE_LABEL}`);
360
+ console.log(` Repo: ${vars.REPO}`);
361
+ console.log(` Mode: ${packageDirs.length > 0 ? `monorepo (${packageDirs.length} packages with skills)` : "single package"}`);
362
+ }
363
+ return result;
364
+ }
365
+
366
+ //#endregion
367
+ export { runAddLibraryBin as a, runEditPackageJsonAll as c, resolveWorkspacePackages as i, runSetupGithubActions as l, findWorkspaceRoot as n, runAddLibraryBinAll as o, readWorkspacePatterns as r, runEditPackageJson as s, findPackagesWithSkills as t };
package/dist/setup.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as runEditPackageJson, i as runAddLibraryBin, n as EditPackageJsonResult, o as runSetupGithubActions, r as SetupGithubActionsResult, t as AddLibraryBinResult } from "./setup-CANkTz55.mjs";
2
- export { AddLibraryBinResult, EditPackageJsonResult, SetupGithubActionsResult, runAddLibraryBin, runEditPackageJson, runSetupGithubActions };
1
+ import { a as findPackagesWithSkills, c as resolveWorkspacePackages, d as runEditPackageJson, f as runEditPackageJsonAll, i as SetupGithubActionsResult, l as runAddLibraryBin, n as EditPackageJsonResult, o as findWorkspaceRoot, p as runSetupGithubActions, r as MonorepoResult, s as readWorkspacePatterns, t as AddLibraryBinResult, u as runAddLibraryBinAll } from "./setup-B-zdCBu4.mjs";
2
+ export { AddLibraryBinResult, EditPackageJsonResult, MonorepoResult, SetupGithubActionsResult, findPackagesWithSkills, findWorkspaceRoot, readWorkspacePatterns, resolveWorkspacePackages, runAddLibraryBin, runAddLibraryBinAll, runEditPackageJson, runEditPackageJsonAll, runSetupGithubActions };
package/dist/setup.mjs CHANGED
@@ -1,3 +1,4 @@
1
- import { n as runEditPackageJson, r as runSetupGithubActions, t as runAddLibraryBin } from "./setup-Nif1-nhS.mjs";
1
+ import "./utils-BfjM1mQe.mjs";
2
+ import { a as runAddLibraryBin, c as runEditPackageJsonAll, i as resolveWorkspacePackages, l as runSetupGithubActions, n as findWorkspaceRoot, o as runAddLibraryBinAll, r as readWorkspacePatterns, s as runEditPackageJson, t as findPackagesWithSkills } from "./setup-mGV2dZrq.mjs";
2
3
 
3
- export { runAddLibraryBin, runEditPackageJson, runSetupGithubActions };
4
+ export { findPackagesWithSkills, findWorkspaceRoot, readWorkspacePatterns, resolveWorkspacePackages, runAddLibraryBin, runAddLibraryBinAll, runEditPackageJson, runEditPackageJsonAll, runSetupGithubActions };
@@ -1,4 +1,4 @@
1
- import { r as parseFrontmatter, t as findSkillFiles } from "./utils-CDJzAdjD.mjs";
1
+ import { a as parseFrontmatter, n as findSkillFiles } from "./utils-BfjM1mQe.mjs";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { join, relative, sep } from "node:path";
4
4
 
@@ -22,10 +22,31 @@ async function fetchNpmVersion(packageName) {
22
22
  return null;
23
23
  }
24
24
  }
25
+ function isStringRecord(value) {
26
+ return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
27
+ }
28
+ function parseSyncState(value) {
29
+ if (!value || typeof value !== "object") return null;
30
+ const raw = value;
31
+ const parsed = {};
32
+ if (typeof raw.library_version === "string") parsed.library_version = raw.library_version;
33
+ if (raw.skills && typeof raw.skills === "object") {
34
+ const skills = {};
35
+ for (const [skillName, skillValue] of Object.entries(raw.skills)) {
36
+ if (!skillValue || typeof skillValue !== "object") continue;
37
+ const sourcesSha = skillValue.sources_sha;
38
+ if (sourcesSha !== void 0 && !isStringRecord(sourcesSha)) continue;
39
+ skills[skillName] = {};
40
+ if (sourcesSha) skills[skillName].sources_sha = sourcesSha;
41
+ }
42
+ parsed.skills = skills;
43
+ }
44
+ return parsed;
45
+ }
25
46
  function readSyncState(packageDir) {
26
47
  const statePath = join(packageDir, "skills", "sync-state.json");
27
48
  try {
28
- return JSON.parse(readFileSync(statePath, "utf8"));
49
+ return parseSyncState(JSON.parse(readFileSync(statePath, "utf8")));
29
50
  } catch {
30
51
  return null;
31
52
  }
@@ -37,7 +58,7 @@ async function checkStaleness(packageDir, packageName) {
37
58
  const fm = parseFrontmatter(filePath);
38
59
  const relName = relative(skillsDir, filePath).replace(/[/\\]SKILL\.md$/, "").split(sep).join("/");
39
60
  return {
40
- name: fm?.name ?? relName,
61
+ name: typeof fm?.name === "string" ? fm.name : relName,
41
62
  filePath,
42
63
  libraryVersion: fm?.library_version,
43
64
  sources: Array.isArray(fm?.sources) ? fm.sources : void 0
@@ -0,0 +1,4 @@
1
+ import "./utils-BfjM1mQe.mjs";
2
+ import { t as checkStaleness } from "./staleness-DZKvsLVq.mjs";
3
+
4
+ export { checkStaleness };
@@ -3,18 +3,40 @@ interface IntentConfig {
3
3
  version: number;
4
4
  repo: string;
5
5
  docs: string;
6
- requires?: string[];
6
+ requires?: Array<string>;
7
7
  }
8
8
  interface ScanResult {
9
9
  packageManager: 'npm' | 'pnpm' | 'yarn' | 'bun' | 'unknown';
10
- packages: IntentPackage[];
11
- warnings: string[];
10
+ packages: Array<IntentPackage>;
11
+ warnings: Array<string>;
12
+ conflicts: Array<VersionConflict>;
13
+ nodeModules: {
14
+ local: NodeModulesScanTarget;
15
+ global: NodeModulesScanTarget;
16
+ };
17
+ }
18
+ interface NodeModulesScanTarget {
19
+ path: string | null;
20
+ detected: boolean;
21
+ exists: boolean;
22
+ scanned: boolean;
23
+ source?: string;
12
24
  }
13
25
  interface IntentPackage {
14
26
  name: string;
15
27
  version: string;
16
28
  intent: IntentConfig;
17
- skills: SkillEntry[];
29
+ skills: Array<SkillEntry>;
30
+ packageRoot: string;
31
+ }
32
+ interface InstalledVariant {
33
+ version: string;
34
+ packageRoot: string;
35
+ }
36
+ interface VersionConflict {
37
+ packageName: string;
38
+ chosen: InstalledVariant;
39
+ variants: Array<InstalledVariant>;
18
40
  }
19
41
  interface SkillEntry {
20
42
  name: string;
@@ -28,11 +50,11 @@ interface StalenessReport {
28
50
  currentVersion: string | null;
29
51
  skillVersion: string | null;
30
52
  versionDrift: 'major' | 'minor' | 'patch' | null;
31
- skills: SkillStaleness[];
53
+ skills: Array<SkillStaleness>;
32
54
  }
33
55
  interface SkillStaleness {
34
56
  name: string;
35
- reasons: string[];
57
+ reasons: Array<string>;
36
58
  needsReview: boolean;
37
59
  }
38
60
  interface FeedbackPayload {
@@ -61,9 +83,10 @@ interface MetaFeedbackPayload {
61
83
  suggestions: string;
62
84
  userRating: 'good' | 'mixed' | 'bad';
63
85
  }
86
+ type FeedbackFrequency = 'always' | 'never' | `every-${number}`;
64
87
  interface IntentProjectConfig {
65
88
  feedback: {
66
- frequency: string;
89
+ frequency: FeedbackFrequency;
67
90
  };
68
91
  }
69
92
  //#endregion
@@ -0,0 +1,152 @@
1
+ import { createRequire } from "node:module";
2
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { execFileSync } from "node:child_process";
5
+ import { parse } from "yaml";
6
+
7
+ //#region src/utils.ts
8
+ /**
9
+ * Recursively find all SKILL.md files under a directory.
10
+ */
11
+ function findSkillFiles(dir) {
12
+ const files = [];
13
+ if (!existsSync(dir)) return files;
14
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
15
+ const fullPath = join(dir, entry.name);
16
+ if (entry.isDirectory()) files.push(...findSkillFiles(fullPath));
17
+ else if (entry.name === "SKILL.md") files.push(fullPath);
18
+ }
19
+ return files;
20
+ }
21
+ /**
22
+ * Read dependencies and peerDependencies (and optionally devDependencies) from
23
+ * a parsed package.json object.
24
+ */
25
+ function getDeps(pkgJson, includeDevDeps = false) {
26
+ const deps = /* @__PURE__ */ new Set();
27
+ const fields = includeDevDeps ? [
28
+ "dependencies",
29
+ "devDependencies",
30
+ "peerDependencies"
31
+ ] : ["dependencies", "peerDependencies"];
32
+ for (const field of fields) {
33
+ const d = pkgJson[field];
34
+ if (d && typeof d === "object") for (const name of Object.keys(d)) deps.add(name);
35
+ }
36
+ return [...deps];
37
+ }
38
+ function listNodeModulesPackageDirs(nodeModulesDir) {
39
+ if (!existsSync(nodeModulesDir)) return [];
40
+ let topEntries;
41
+ try {
42
+ topEntries = readdirSync(nodeModulesDir, {
43
+ withFileTypes: true,
44
+ encoding: "utf8"
45
+ });
46
+ } catch {
47
+ return [];
48
+ }
49
+ const packageDirs = [];
50
+ for (const entry of topEntries) {
51
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
52
+ const dirPath = join(nodeModulesDir, entry.name);
53
+ if (entry.name.startsWith("@")) {
54
+ let scopedEntries;
55
+ try {
56
+ scopedEntries = readdirSync(dirPath, {
57
+ withFileTypes: true,
58
+ encoding: "utf8"
59
+ });
60
+ } catch {
61
+ continue;
62
+ }
63
+ for (const scoped of scopedEntries) {
64
+ if (!scoped.isDirectory() && !scoped.isSymbolicLink()) continue;
65
+ packageDirs.push(join(dirPath, scoped.name));
66
+ }
67
+ } else if (!entry.name.startsWith(".")) packageDirs.push(dirPath);
68
+ }
69
+ return packageDirs;
70
+ }
71
+ function detectGlobalNodeModules(packageManager) {
72
+ const envPath = process.env.INTENT_GLOBAL_NODE_MODULES?.trim();
73
+ if (envPath) return {
74
+ path: envPath,
75
+ source: "INTENT_GLOBAL_NODE_MODULES"
76
+ };
77
+ const commands = [];
78
+ if (packageManager === "pnpm") commands.push({
79
+ command: "pnpm",
80
+ args: ["root", "-g"]
81
+ });
82
+ if (packageManager === "yarn") commands.push({
83
+ command: "yarn",
84
+ args: ["global", "dir"],
85
+ transform: (output) => join(output, "node_modules")
86
+ });
87
+ commands.push({
88
+ command: "npm",
89
+ args: ["root", "-g"]
90
+ });
91
+ for (const candidate of commands) try {
92
+ const output = execFileSync(candidate.command, candidate.args, {
93
+ encoding: "utf8",
94
+ stdio: [
95
+ "ignore",
96
+ "pipe",
97
+ "ignore"
98
+ ]
99
+ }).trim();
100
+ if (!output) continue;
101
+ return {
102
+ path: candidate.transform ? candidate.transform(output) : output,
103
+ source: `${candidate.command} ${candidate.args.join(" ")}`
104
+ };
105
+ } catch {
106
+ continue;
107
+ }
108
+ return { path: null };
109
+ }
110
+ /**
111
+ * Resolve the directory of a dependency by name. Tries createRequire first
112
+ * (handles pnpm symlinks), then falls back to walking up node_modules
113
+ * directories (handles packages with export maps that block ./package.json).
114
+ */
115
+ function resolveDepDir(depName, parentDir) {
116
+ try {
117
+ return dirname(createRequire(join(parentDir, "package.json")).resolve(join(depName, "package.json")));
118
+ } catch (err) {
119
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
120
+ if (code && code !== "MODULE_NOT_FOUND" && code !== "ERR_PACKAGE_PATH_NOT_EXPORTED") console.warn(`Warning: could not resolve ${depName} from ${parentDir}: ${err instanceof Error ? err.message : String(err)}`);
121
+ }
122
+ let dir = parentDir;
123
+ while (true) {
124
+ const candidate = join(dir, "node_modules", depName);
125
+ if (existsSync(join(candidate, "package.json"))) return candidate;
126
+ const parent = dirname(dir);
127
+ if (parent === dir) break;
128
+ dir = parent;
129
+ }
130
+ return null;
131
+ }
132
+ /**
133
+ * Parse YAML frontmatter from a file. Returns null if no frontmatter or on error.
134
+ */
135
+ function parseFrontmatter(filePath) {
136
+ let content;
137
+ try {
138
+ content = readFileSync(filePath, "utf8");
139
+ } catch {
140
+ return null;
141
+ }
142
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
143
+ if (!match?.[1]) return null;
144
+ try {
145
+ return parse(match[1]);
146
+ } catch {
147
+ return null;
148
+ }
149
+ }
150
+
151
+ //#endregion
152
+ export { parseFrontmatter as a, listNodeModulesPackageDirs as i, findSkillFiles as n, resolveDepDir as o, getDeps as r, detectGlobalNodeModules as t };
@@ -0,0 +1,3 @@
1
+ import { a as parseFrontmatter, i as listNodeModulesPackageDirs, n as findSkillFiles, o as resolveDepDir, r as getDeps, t as detectGlobalNodeModules } from "./utils-BfjM1mQe.mjs";
2
+
3
+ export { detectGlobalNodeModules, findSkillFiles, getDeps, listNodeModulesPackageDirs, parseFrontmatter, resolveDepDir };