@tanstack/intent 0.0.32 → 0.0.34

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 (37) hide show
  1. package/README.md +7 -7
  2. package/dist/artifact-coverage-BAN2W6aH.mjs +3 -0
  3. package/dist/artifact-coverage-wLNVX8yC.mjs +128 -0
  4. package/dist/cli.mjs +248 -99
  5. package/dist/{display-DCRCp4-C.mjs → display-B3vkG99D.mjs} +1 -1
  6. package/dist/index.d.mts +22 -4
  7. package/dist/index.mjs +10 -8
  8. package/dist/{install-BmVqcbEi.mjs → install-QjryhQtg.mjs} +54 -18
  9. package/dist/intent-library.mjs +5 -5
  10. package/dist/library-scanner.d.mts +1 -1
  11. package/dist/library-scanner.mjs +1 -1
  12. package/dist/{project-context-CKG-q4rD.mjs → project-context-alYMNoNa.mjs} +1 -1
  13. package/dist/{resolver-aFigTqXi.mjs → resolver-Whd12ksO.mjs} +1 -1
  14. package/dist/scanner-BAZxWeUk.mjs +6 -0
  15. package/dist/{scanner-DKL8v8is.mjs → scanner-Dav1tzQK.mjs} +79 -4
  16. package/dist/{setup-JJvjiGkM.mjs → setup-Dp-W8y0Y.mjs} +2 -2
  17. package/dist/setup.d.mts +1 -1
  18. package/dist/setup.mjs +3 -3
  19. package/dist/staleness-DpbmYod4.mjs +5 -0
  20. package/dist/staleness-PdgakrCQ.mjs +243 -0
  21. package/dist/{types-CsySN6Vw.d.mts → types-DT7Y6TFz.d.mts} +48 -1
  22. package/dist/workflow-review-CwkPVIQf.mjs +153 -0
  23. package/dist/workflow-review-Dz_ofcYQ.mjs +3 -0
  24. package/dist/{workspace-patterns-U35B5AO-.mjs → workspace-patterns-BN2A_60g.mjs} +6 -1
  25. package/dist/workspace-patterns-x-dLZxx4.mjs +4 -0
  26. package/meta/templates/workflows/check-skills.yml +58 -96
  27. package/package.json +1 -1
  28. package/dist/scanner-CRZITpcY.mjs +0 -6
  29. package/dist/staleness-DorwfGrf.mjs +0 -104
  30. package/dist/staleness-NF-lxfXf.mjs +0 -4
  31. package/dist/workspace-patterns-DbnA0peB.mjs +0 -4
  32. package/meta/templates/workflows/notify-intent.yml +0 -51
  33. package/meta/templates/workflows/validate-skills.yml +0 -52
  34. /package/dist/{display-DUgtRJkt.mjs → display-CAof6doy.mjs} +0 -0
  35. /package/dist/{library-scanner-DFFreLjW.mjs → library-scanner-fexXlPXb.mjs} +0 -0
  36. /package/dist/{setup-DDoOLriA.d.mts → setup-t1i2o2-h.d.mts} +0 -0
  37. /package/dist/{skill-use-CXOnncWK.mjs → skill-use-BzuuvLM7.mjs} +0 -0
package/README.md CHANGED
@@ -90,10 +90,10 @@ npx @tanstack/intent@latest stale
90
90
 
91
91
  From a monorepo root, `intent stale` checks every workspace package that ships skills. To scope it to one package, pass a directory like `intent stale packages/router`.
92
92
 
93
- Copy CI workflow templates into your repo so validation and staleness checks run on every push:
93
+ Copy CI workflow templates into your repo so validation and staleness checks can run in GitHub Actions:
94
94
 
95
95
  ```bash
96
- npx @tanstack/intent@latest setup-github-actions
96
+ npx @tanstack/intent@latest setup
97
97
  ```
98
98
 
99
99
  ## Compatibility
@@ -108,14 +108,14 @@ npx @tanstack/intent@latest setup-github-actions
108
108
 
109
109
  ## Monorepos
110
110
 
111
- - Run `npx @tanstack/intent@latest setup-github-actions` from either the repo root or a package directory. Intent detects the workspace root and writes workflows to the repo-level `.github/workflows/` directory.
112
- - Generated workflows are monorepo-aware: validation loops over workspace packages with skills, staleness checks run from the workspace root, and notify workflows watch package `src/` and docs paths.
111
+ - Run `npx @tanstack/intent@latest setup` from either the repo root or a package directory. Intent detects the workspace root and writes workflows to the repo-level `.github/workflows/` directory.
112
+ - Generated workflows are monorepo-aware: validation loops over workspace packages with skills, and staleness checks run from the workspace root.
113
113
  - Run `npx @tanstack/intent@latest validate packages/<pkg>/skills` from the repo root to validate one package without root-level packaging warnings.
114
- - Run `npx @tanstack/intent@latest stale` from the repo root to check all workspace packages with skills, or `intent stale packages/<pkg>` to check one package.
114
+ - Run `npx @tanstack/intent@latest stale` from the repo root to check workspace packages with skills and public workspace packages missing skill or `_artifacts` coverage, or `intent stale packages/<pkg>` to check one package.
115
115
 
116
116
  ## Keeping skills current
117
117
 
118
- The real risk with any derived artifact is staleness. `npx @tanstack/intent@latest stale` flags skills whose source docs have changed, and CI templates catch drift before it ships.
118
+ The real risk with any derived artifact is staleness. `npx @tanstack/intent@latest stale` flags skills whose source docs have changed, generated skills that drift from `_artifacts`, and public workspace packages missing coverage. CI templates catch drift before it ships.
119
119
 
120
120
  The feedback loop runs both directions. `npx @tanstack/intent@latest feedback` lets users submit structured reports when a skill produces wrong output — which skill, which version, what broke. That context flows back to the maintainer, and the fix ships to everyone on the next package update. Every support interaction produces an artifact that prevents the same class of problem for all future users — not just the one who reported it.
121
121
 
@@ -129,7 +129,7 @@ The feedback loop runs both directions. `npx @tanstack/intent@latest feedback` l
129
129
  | `npx @tanstack/intent@latest meta` | List meta-skills for library maintainers |
130
130
  | `npx @tanstack/intent@latest scaffold` | Print the guided skill generation prompt |
131
131
  | `npx @tanstack/intent@latest validate [dir]` | Validate SKILL.md files |
132
- | `npx @tanstack/intent@latest setup-github-actions` | Copy CI templates into your repo |
132
+ | `npx @tanstack/intent@latest setup` | Copy CI templates into your repo |
133
133
  | `npx @tanstack/intent@latest stale [dir] [--json]` | Check skills for version drift |
134
134
  | `npx @tanstack/intent@latest feedback` | Submit skill feedback |
135
135
 
@@ -0,0 +1,3 @@
1
+ import { t as readIntentArtifacts } from "./artifact-coverage-wLNVX8yC.mjs";
2
+
3
+ export { readIntentArtifacts };
@@ -0,0 +1,128 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parse } from "yaml";
4
+
5
+ //#region src/artifact-coverage.ts
6
+ function isRecord(value) {
7
+ return !!value && typeof value === "object" && !Array.isArray(value);
8
+ }
9
+ function stringValue(value) {
10
+ return typeof value === "string" && value.trim() ? value : void 0;
11
+ }
12
+ function stringArray(value) {
13
+ if (!Array.isArray(value)) return [];
14
+ return value.filter((entry) => typeof entry === "string");
15
+ }
16
+ function detectArtifactKind(fileName) {
17
+ if (fileName.endsWith("skill_tree.yaml")) return "skill-tree";
18
+ if (fileName.endsWith("domain_map.yaml")) return "domain-map";
19
+ return null;
20
+ }
21
+ function readArtifactYaml(artifactPath, warnings) {
22
+ let parsed;
23
+ try {
24
+ parsed = parse(readFileSync(artifactPath, "utf8"));
25
+ } catch (err) {
26
+ warnings.push({
27
+ artifactPath,
28
+ message: `Invalid YAML: ${err instanceof Error ? err.message : String(err)}`
29
+ });
30
+ return null;
31
+ }
32
+ if (!isRecord(parsed)) {
33
+ warnings.push({
34
+ artifactPath,
35
+ message: "Artifact YAML must contain an object at the top level"
36
+ });
37
+ return null;
38
+ }
39
+ return parsed;
40
+ }
41
+ function parseArtifactFile(artifactPath, kind, parsed) {
42
+ const library = isRecord(parsed.library) ? parsed.library : {};
43
+ return {
44
+ path: artifactPath,
45
+ kind,
46
+ libraryName: stringValue(library.name),
47
+ libraryVersion: stringValue(library.version)
48
+ };
49
+ }
50
+ function parseSkills(artifactPath, kind, parsed) {
51
+ if (!Array.isArray(parsed.skills)) return [];
52
+ const skills = [];
53
+ for (const skill of parsed.skills) {
54
+ if (!isRecord(skill)) continue;
55
+ const packagePath = stringValue(skill.package);
56
+ const packages = stringArray(skill.packages);
57
+ if (packagePath) packages.push(packagePath);
58
+ skills.push({
59
+ artifactPath,
60
+ artifactKind: kind,
61
+ name: stringValue(skill.name),
62
+ slug: stringValue(skill.slug),
63
+ path: stringValue(skill.path),
64
+ package: packagePath,
65
+ packages: [...new Set(packages)].sort((a, b) => a.localeCompare(b)),
66
+ sources: stringArray(skill.sources),
67
+ covers: stringArray(skill.covers)
68
+ });
69
+ }
70
+ return skills;
71
+ }
72
+ function parseCoverageIgnores(artifactPath, parsed) {
73
+ const ignored = (isRecord(parsed.coverage) ? parsed.coverage : null)?.ignored_packages;
74
+ if (!Array.isArray(ignored)) return [];
75
+ const ignores = [];
76
+ for (const entry of ignored) {
77
+ if (typeof entry === "string") {
78
+ if (entry.trim()) ignores.push({
79
+ packageName: entry,
80
+ artifactPath
81
+ });
82
+ continue;
83
+ }
84
+ if (!isRecord(entry)) continue;
85
+ const packageName = stringValue(entry.name);
86
+ if (!packageName) continue;
87
+ ignores.push({
88
+ packageName,
89
+ reason: stringValue(entry.reason),
90
+ artifactPath
91
+ });
92
+ }
93
+ return ignores;
94
+ }
95
+ function readIntentArtifacts(root) {
96
+ const artifactsDir = join(root, "_artifacts");
97
+ if (!existsSync(artifactsDir)) return null;
98
+ const warnings = [];
99
+ const skillTrees = [];
100
+ const domainMaps = [];
101
+ const skills = [];
102
+ const ignoredPackages = [];
103
+ for (const entry of readdirSync(artifactsDir, { withFileTypes: true })) {
104
+ if (!entry.isFile()) continue;
105
+ const kind = detectArtifactKind(entry.name);
106
+ if (!kind) continue;
107
+ const artifactPath = join(artifactsDir, entry.name);
108
+ const parsed = readArtifactYaml(artifactPath, warnings);
109
+ if (!parsed) continue;
110
+ const artifactFile = parseArtifactFile(artifactPath, kind, parsed);
111
+ if (kind === "skill-tree") skillTrees.push(artifactFile);
112
+ else domainMaps.push(artifactFile);
113
+ skills.push(...parseSkills(artifactPath, kind, parsed));
114
+ ignoredPackages.push(...parseCoverageIgnores(artifactPath, parsed));
115
+ }
116
+ return {
117
+ root,
118
+ artifactsDir,
119
+ skillTrees: skillTrees.sort((a, b) => a.path.localeCompare(b.path)),
120
+ domainMaps: domainMaps.sort((a, b) => a.path.localeCompare(b.path)),
121
+ skills: skills.sort((a, b) => `${a.artifactPath}:${a.slug ?? a.name ?? ""}`.localeCompare(`${b.artifactPath}:${b.slug ?? b.name ?? ""}`)),
122
+ ignoredPackages: ignoredPackages.sort((a, b) => a.packageName.localeCompare(b.packageName)),
123
+ warnings
124
+ };
125
+ }
126
+
127
+ //#endregion
128
+ export { readIntentArtifacts as t };
package/dist/cli.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import "./utils-COlDcU72.mjs";
3
- import "./workspace-patterns-U35B5AO-.mjs";
4
- import { i as parseSkillUse } from "./skill-use-CXOnncWK.mjs";
5
- import { r as resolveSkillUse } from "./resolver-aFigTqXi.mjs";
6
- import { t as resolveProjectContext } from "./project-context-CKG-q4rD.mjs";
7
- import { a as scanIntentsOrFail, c as fail, i as resolveStaleTargets, l as isCliFailure, n as runInstallCommand, o as scanOptionsFromGlobalFlags, r as getMetaDir, s as printWarnings } from "./install-BmVqcbEi.mjs";
8
- import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
9
- import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ import { n as findWorkspacePackages } from "./workspace-patterns-BN2A_60g.mjs";
4
+ import { i as parseSkillUse } from "./skill-use-BzuuvLM7.mjs";
5
+ import { r as resolveSkillUse } from "./resolver-Whd12ksO.mjs";
6
+ import { t as resolveProjectContext } from "./project-context-alYMNoNa.mjs";
7
+ import { a as scanIntentsOrFail, c as fail, i as resolveStaleTargets, l as isCliFailure, n as runInstallCommand, o as scanOptionsFromGlobalFlags, r as getMetaDir, s as printWarnings } from "./install-QjryhQtg.mjs";
8
+ import { appendFileSync, existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
9
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { cac } from "cac";
12
12
 
@@ -43,7 +43,7 @@ async function runListCommand(options, scanIntentsOrFail$1) {
43
43
  console.log(JSON.stringify(result, null, 2));
44
44
  return;
45
45
  }
46
- const { computeSkillNameWidth, printSkillTree, printTable } = await import("./display-DCRCp4-C.mjs");
46
+ const { computeSkillNameWidth, printSkillTree, printTable } = await import("./display-B3vkG99D.mjs");
47
47
  const scanCoverage = formatScanCoverage(result);
48
48
  if (result.packages.length === 0) {
49
49
  console.log("No intent-enabled packages found.");
@@ -245,11 +245,17 @@ async function runSetupGithubActionsCommand(root, metaDir) {
245
245
  //#endregion
246
246
  //#region src/commands/stale.ts
247
247
  async function runStaleCommand(targetDir, options, resolveStaleTargets$1) {
248
- const { reports } = await resolveStaleTargets$1(targetDir);
248
+ if (options.githubReview) {
249
+ await runGithubReview(targetDir, options, resolveStaleTargets$1);
250
+ return;
251
+ }
252
+ const { reports, workflowAdvisories = [] } = await resolveStaleTargets$1(targetDir);
249
253
  if (options.json) {
250
254
  console.log(JSON.stringify(reports, null, 2));
251
255
  return;
252
256
  }
257
+ for (const advisory of workflowAdvisories) console.log(advisory);
258
+ if (workflowAdvisories.length > 0) console.log();
253
259
  if (reports.length === 0) {
254
260
  console.log("No intent-enabled packages found.");
255
261
  return;
@@ -259,14 +265,38 @@ async function runStaleCommand(targetDir, options, resolveStaleTargets$1) {
259
265
  const vLabel = report.skillVersion && report.currentVersion ? ` (${report.skillVersion} → ${report.currentVersion})` : "";
260
266
  console.log(`${report.library}${vLabel}${driftLabel}`);
261
267
  const stale = report.skills.filter((skill) => skill.needsReview);
262
- if (stale.length === 0) console.log(" All skills up-to-date");
263
- else for (const skill of stale) console.log(` ${skill.name}: ${skill.reasons.join(", ")}`);
268
+ const signals = report.signals.filter((signal) => signal.needsReview);
269
+ if (stale.length === 0 && signals.length === 0) console.log(" All skills up-to-date");
270
+ else {
271
+ for (const skill of stale) console.log(` ⚠ ${skill.name}: ${skill.reasons.join(", ")}`);
272
+ for (const signal of signals) {
273
+ const subject = signal.packageName ?? signal.packageRoot ?? signal.skill ?? signal.artifactPath ?? signal.subject ?? report.library;
274
+ console.log(` ⚠ ${subject}: ${signal.reasons.join(", ")}`);
275
+ }
276
+ }
264
277
  console.log();
265
278
  }
266
279
  }
280
+ async function runGithubReview(targetDir, options, resolveStaleTargets$1) {
281
+ const { collectStaleReviewItems, createFailedStaleReviewItem, createWorkflowAdvisoryReviewItems, writeStaleReviewWorkflowFiles } = await import("./workflow-review-Dz_ofcYQ.mjs");
282
+ const packageLabel = options.packageLabel ?? "workspace";
283
+ try {
284
+ const { reports, workflowAdvisories = [] } = await resolveStaleTargets$1(targetDir);
285
+ const items = [...collectStaleReviewItems(reports), ...createWorkflowAdvisoryReviewItems(packageLabel, workflowAdvisories)];
286
+ writeStaleReviewWorkflowFiles(items);
287
+ if (items.length === 0) console.log("No stale skills or coverage gaps found.");
288
+ else console.log(`Wrote ${items.length} intent skill review item(s).`);
289
+ } catch (err) {
290
+ writeStaleReviewWorkflowFiles([createFailedStaleReviewItem(packageLabel)]);
291
+ const message = err instanceof Error ? err.message : String(err);
292
+ console.log(`Intent stale check failed: ${message}`);
293
+ console.log("Wrote a review PR body so maintainers can inspect the logs.");
294
+ }
295
+ }
267
296
 
268
297
  //#endregion
269
298
  //#region src/commands/validate.ts
299
+ const agentSkillNamePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
270
300
  function buildValidationFailure(errors, warnings) {
271
301
  const lines = [
272
302
  "",
@@ -301,114 +331,230 @@ function collectPackagingWarnings(context) {
301
331
  }
302
332
  return warnings;
303
333
  }
304
- async function runValidateCommand(dir) {
305
- const [{ parse: parseYaml }, { findSkillFiles }] = await Promise.all([import("yaml"), import("./utils-dkVvY7D7.mjs")]);
306
- const targetDir = dir ?? "skills";
307
- const context = resolveProjectContext({
308
- cwd: process.cwd(),
309
- targetPath: targetDir
310
- });
311
- const skillsDir = context.targetSkillsDir ?? resolve(process.cwd(), targetDir);
312
- if (!existsSync(skillsDir)) fail(`Skills directory not found: ${skillsDir}`);
313
- const errors = [];
314
- const skillFiles = findSkillFiles(skillsDir);
315
- if (skillFiles.length === 0) fail("No SKILL.md files found");
316
- for (const filePath of skillFiles) {
317
- const rel = relative(process.cwd(), filePath);
318
- const content = readFileSync(filePath, "utf8");
319
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)/);
320
- if (!match) {
321
- errors.push({
322
- file: rel,
323
- message: "Missing or invalid frontmatter"
324
- });
325
- continue;
326
- }
327
- if (!match[1]) {
328
- errors.push({
329
- file: rel,
330
- message: "Missing YAML frontmatter"
331
- });
332
- continue;
333
- }
334
- let fm;
335
- try {
336
- fm = parseYaml(match[1]);
337
- } catch (err) {
338
- const detail = err instanceof Error ? err.message : String(err);
339
- errors.push({
334
+ function formatWarning({ file, message }) {
335
+ return `${file}: ${message}`;
336
+ }
337
+ function isRecord(value) {
338
+ return !!value && typeof value === "object" && !Array.isArray(value);
339
+ }
340
+ function collectAgentSkillSpecWarnings({ filePath, fm, rel }) {
341
+ const warnings = [];
342
+ if (typeof fm.name === "string") {
343
+ if (fm.name.length > 64) warnings.push({
344
+ file: rel,
345
+ message: `Agent Skills spec warning: name exceeds 64 characters (${fm.name.length} chars)`
346
+ });
347
+ for (const segment of fm.name.split("/")) if (!agentSkillNamePattern.test(segment)) {
348
+ warnings.push({
340
349
  file: rel,
341
- message: `Invalid YAML frontmatter: ${detail}`
350
+ message: "Agent Skills spec warning: each name segment should use lowercase letters, numbers, and single hyphens only"
342
351
  });
343
- continue;
352
+ break;
344
353
  }
345
- if (!fm.name) errors.push({
354
+ const parentDir = basename(dirname(filePath));
355
+ if (!fm.name.includes("/") && fm.name !== parentDir) warnings.push({
346
356
  file: rel,
347
- message: "Missing required field: name"
357
+ message: "Agent Skills spec warning: name should match the parent directory name"
348
358
  });
349
- if (!fm.description) errors.push({
359
+ }
360
+ if (fm.license !== void 0 && (typeof fm.license !== "string" || fm.license.trim().length === 0)) warnings.push({
361
+ file: rel,
362
+ message: "Agent Skills spec warning: license should be a non-empty string"
363
+ });
364
+ if (fm.compatibility !== void 0) {
365
+ if (typeof fm.compatibility !== "string" || fm.compatibility.trim().length === 0) warnings.push({
350
366
  file: rel,
351
- message: "Missing required field: description"
367
+ message: "Agent Skills spec warning: compatibility should be a non-empty string"
352
368
  });
353
- if (typeof fm.name === "string") {
354
- const expectedPath = relative(skillsDir, filePath).replace(/[/\\]SKILL\.md$/, "").split(sep).join("/");
355
- if (fm.name !== expectedPath) errors.push({
356
- file: rel,
357
- message: `name "${fm.name}" does not match directory path "${expectedPath}"`
358
- });
359
- }
360
- if (typeof fm.description === "string" && fm.description.length > 1024) errors.push({
369
+ else if (fm.compatibility.length > 500) warnings.push({
361
370
  file: rel,
362
- message: `Description exceeds 1024 character limit (${fm.description.length} chars)`
371
+ message: `Agent Skills spec warning: compatibility exceeds 500 characters (${fm.compatibility.length} chars)`
363
372
  });
364
- if (fm.type === "framework" && !Array.isArray(fm.requires)) errors.push({
373
+ }
374
+ if (fm.metadata !== void 0) {
375
+ if (!isRecord(fm.metadata)) warnings.push({
365
376
  file: rel,
366
- message: "Framework skills must have a \"requires\" field"
377
+ message: "Agent Skills spec warning: metadata should be a mapping"
367
378
  });
368
- const lineCount = content.split(/\r?\n/).length;
369
- if (lineCount > 500) errors.push({
379
+ else if (Object.values(fm.metadata).some((value) => typeof value !== "string")) warnings.push({
370
380
  file: rel,
371
- message: `Exceeds 500 line limit (${lineCount} lines). Rewrite for conciseness: move API tables to references/, trim verbose examples, and remove content an agent already knows. Do not simply raise the limit.`
381
+ message: "Agent Skills spec warning: metadata values should be strings"
382
+ });
383
+ }
384
+ if (fm["allowed-tools"] !== void 0 && typeof fm["allowed-tools"] !== "string") warnings.push({
385
+ file: rel,
386
+ message: "Agent Skills spec warning: allowed-tools should be a space-separated string"
387
+ });
388
+ return warnings;
389
+ }
390
+ async function runValidateCommand(dir, options = {}) {
391
+ if (!options.githubSummary) {
392
+ await runValidateCommandInternal(dir);
393
+ return;
394
+ }
395
+ try {
396
+ await runValidateCommandInternal(dir);
397
+ writeGithubValidationSummary({ ok: true });
398
+ } catch (err) {
399
+ writeGithubValidationSummary({
400
+ ok: false,
401
+ message: validationErrorMessage(err)
372
402
  });
403
+ throw err;
373
404
  }
374
- const artifactsDir = join(skillsDir, "_artifacts");
375
- if (!context.isMonorepo && existsSync(artifactsDir)) for (const fileName of [
376
- "domain_map.yaml",
377
- "skill_spec.md",
378
- "skill_tree.yaml"
379
- ]) {
380
- const artifactPath = join(artifactsDir, fileName);
381
- if (!existsSync(artifactPath)) {
382
- errors.push({
383
- file: relative(process.cwd(), artifactPath),
384
- message: "Missing required artifact"
405
+ }
406
+ async function runValidateCommandInternal(dir) {
407
+ const [{ parse: parseYaml }, { findSkillFiles }] = await Promise.all([import("yaml"), import("./utils-dkVvY7D7.mjs")]);
408
+ const context = resolveProjectContext({
409
+ cwd: process.cwd(),
410
+ targetPath: dir
411
+ });
412
+ const explicitDir = dir !== void 0;
413
+ const skillsDirs = explicitDir ? [context.targetSkillsDir ?? resolve(process.cwd(), dir)] : collectDefaultSkillsDirs(context, findSkillFiles);
414
+ if (explicitDir && !existsSync(skillsDirs[0])) fail(`Skills directory not found: ${skillsDirs[0]}`);
415
+ const errors = [];
416
+ const warnings = [];
417
+ let validatedCount = 0;
418
+ if (explicitDir && findSkillFiles(skillsDirs[0]).length === 0) fail("No SKILL.md files found");
419
+ if (skillsDirs.length === 0) {
420
+ console.log("No skills/ directory found — skipping validation.");
421
+ return;
422
+ }
423
+ for (const skillsDir of skillsDirs) {
424
+ const skillFiles = findSkillFiles(skillsDir);
425
+ const validateContext = resolveProjectContext({
426
+ cwd: process.cwd(),
427
+ targetPath: skillsDir
428
+ });
429
+ for (const filePath of skillFiles) {
430
+ const rel = relative(process.cwd(), filePath);
431
+ const content = readFileSync(filePath, "utf8");
432
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)/);
433
+ if (!match) {
434
+ errors.push({
435
+ file: rel,
436
+ message: "Missing or invalid frontmatter"
437
+ });
438
+ continue;
439
+ }
440
+ if (!match[1]) {
441
+ errors.push({
442
+ file: rel,
443
+ message: "Missing YAML frontmatter"
444
+ });
445
+ continue;
446
+ }
447
+ let fm;
448
+ try {
449
+ fm = parseYaml(match[1]);
450
+ } catch (err) {
451
+ const detail = err instanceof Error ? err.message : String(err);
452
+ errors.push({
453
+ file: rel,
454
+ message: `Invalid YAML frontmatter: ${detail}`
455
+ });
456
+ continue;
457
+ }
458
+ if (!fm.name) errors.push({
459
+ file: rel,
460
+ message: "Missing required field: name"
385
461
  });
386
- continue;
387
- }
388
- const content = readFileSync(artifactPath, "utf8");
389
- if (content.trim().length === 0) {
390
- errors.push({
391
- file: relative(process.cwd(), artifactPath),
392
- message: "Artifact file is empty"
462
+ if (!fm.description) errors.push({
463
+ file: rel,
464
+ message: "Missing required field: description"
393
465
  });
394
- continue;
395
- }
396
- if (fileName.endsWith(".yaml")) try {
397
- parseYaml(content);
398
- } catch (err) {
399
- const detail = err instanceof Error ? err.message : String(err);
400
- errors.push({
401
- file: relative(process.cwd(), artifactPath),
402
- message: `Invalid YAML in artifact file: ${detail}`
466
+ if (typeof fm.name === "string") {
467
+ const expectedPath = relative(skillsDir, filePath).replace(/[/\\]SKILL\.md$/, "").split(sep).join("/");
468
+ if (fm.name !== expectedPath) errors.push({
469
+ file: rel,
470
+ message: `name "${fm.name}" does not match directory path "${expectedPath}"`
471
+ });
472
+ }
473
+ if (typeof fm.description === "string" && fm.description.length > 1024) errors.push({
474
+ file: rel,
475
+ message: `Description exceeds 1024 character limit (${fm.description.length} chars)`
476
+ });
477
+ if (fm.type === "framework" && !Array.isArray(fm.requires)) errors.push({
478
+ file: rel,
479
+ message: "Framework skills must have a \"requires\" field"
403
480
  });
481
+ warnings.push(...collectAgentSkillSpecWarnings({
482
+ filePath,
483
+ fm,
484
+ rel
485
+ }).map(formatWarning));
486
+ const lineCount = content.split(/\r?\n/).length;
487
+ if (lineCount > 500) errors.push({
488
+ file: rel,
489
+ message: `Exceeds 500 line limit (${lineCount} lines). Rewrite for conciseness: move API tables to references/, trim verbose examples, and remove content an agent already knows. Do not simply raise the limit.`
490
+ });
491
+ }
492
+ const artifactsDir = join(skillsDir, "_artifacts");
493
+ if (!validateContext.isMonorepo && existsSync(artifactsDir)) for (const fileName of [
494
+ "domain_map.yaml",
495
+ "skill_spec.md",
496
+ "skill_tree.yaml"
497
+ ]) {
498
+ const artifactPath = join(artifactsDir, fileName);
499
+ if (!existsSync(artifactPath)) {
500
+ errors.push({
501
+ file: relative(process.cwd(), artifactPath),
502
+ message: "Missing required artifact"
503
+ });
504
+ continue;
505
+ }
506
+ const content = readFileSync(artifactPath, "utf8");
507
+ if (content.trim().length === 0) {
508
+ errors.push({
509
+ file: relative(process.cwd(), artifactPath),
510
+ message: "Artifact file is empty"
511
+ });
512
+ continue;
513
+ }
514
+ if (fileName.endsWith(".yaml")) try {
515
+ parseYaml(content);
516
+ } catch (err) {
517
+ const detail = err instanceof Error ? err.message : String(err);
518
+ errors.push({
519
+ file: relative(process.cwd(), artifactPath),
520
+ message: `Invalid YAML in artifact file: ${detail}`
521
+ });
522
+ }
404
523
  }
524
+ validatedCount += skillFiles.length;
525
+ warnings.push(...collectPackagingWarnings(validateContext));
405
526
  }
406
- const warnings = collectPackagingWarnings(context);
407
527
  if (errors.length > 0) fail(buildValidationFailure(errors, warnings));
408
- console.log(`✅ Validated ${skillFiles.length} skill files — all passed`);
528
+ console.log(`✅ Validated ${validatedCount} skill files — all passed`);
409
529
  if (warnings.length > 0) console.log();
410
530
  printWarnings(warnings);
411
531
  }
532
+ function validationErrorMessage(err) {
533
+ if (isCliFailure(err)) return err.message;
534
+ if (err instanceof Error) return err.message;
535
+ return String(err);
536
+ }
537
+ function writeGithubValidationSummary({ message, ok }) {
538
+ const summaryPath = process.env.GITHUB_STEP_SUMMARY;
539
+ if (!summaryPath) return;
540
+ const lines = ["### Intent skill validation", ""];
541
+ if (ok) lines.push("Skill validation passed.", "");
542
+ else lines.push("Skill validation failed.", "", "Why this failed:", "", "Intent validates SKILL.md frontmatter, skill names, required fields, size limits, framework requirements, and artifact files.", "The command output below contains the exact file-level reasons to fix.", "", "Run locally:", "", "```bash", "npx @tanstack/intent@latest validate", "```", "", "Command output:", "", "```text", message ?? "Unknown validation error.", "```", "");
543
+ appendFileSync(summaryPath, lines.join("\n"));
544
+ }
545
+ function collectDefaultSkillsDirs(context, findSkillFiles) {
546
+ const skillsDirs = [];
547
+ const addSkillsDir = (skillsDir) => {
548
+ if (existsSync(skillsDir) && findSkillFiles(skillsDir).length > 0) skillsDirs.push(skillsDir);
549
+ };
550
+ if (context.workspaceRoot && context.cwd === context.workspaceRoot) {
551
+ addSkillsDir(join(context.workspaceRoot, "skills"));
552
+ for (const packageDir of findWorkspacePackages(context.workspaceRoot)) addSkillsDir(join(packageDir, "skills"));
553
+ return [...new Set(skillsDirs)].sort((a, b) => a.localeCompare(b));
554
+ }
555
+ addSkillsDir(context.targetSkillsDir ?? (context.packageRoot ? join(context.packageRoot, "skills") : resolve(context.cwd, "skills")));
556
+ return skillsDirs;
557
+ }
412
558
 
413
559
  //#endregion
414
560
  //#region src/cli.ts
@@ -424,8 +570,8 @@ function createCli() {
424
570
  cli.command("meta [name]", "List meta-skills, or print one by name").usage("meta [name]").example("meta").example("meta domain-discovery").action(async (name) => {
425
571
  await runMetaCommand(name, getMetaDir());
426
572
  });
427
- cli.command("validate [dir]", "Validate skill files").usage("validate [dir]").example("validate").example("validate packages/query/skills").action(async (dir) => {
428
- await runValidateCommand(dir);
573
+ cli.command("validate [dir]", "Validate skill files").usage("validate [dir] [--github-summary]").option("--github-summary", "Write a GitHub Actions step summary").example("validate").example("validate packages/query/skills").action(async (dir, options) => {
574
+ await runValidateCommand(dir, options);
429
575
  });
430
576
  cli.command("install", "Create or update skill loading guidance in an agent config file").usage("install [--map] [--dry-run] [--print-prompt] [--global] [--global-only]").option("--map", "Write explicit skill-to-task mappings").option("--dry-run", "Print the generated block without writing").option("--print-prompt", "Print the legacy agent setup prompt instead of writing").option("--global", "Include global packages after project packages").option("--global-only", "Install mappings from global packages only").example("install").example("install --map").example("install --dry-run").example("install --print-prompt").example("install --global").action(async (options) => {
431
577
  await runInstallCommand(options, scanIntentsOrFail);
@@ -433,12 +579,15 @@ function createCli() {
433
579
  cli.command("scaffold", "Print maintainer scaffold prompt").usage("scaffold").action(() => {
434
580
  runScaffoldCommand(getMetaDir());
435
581
  });
436
- cli.command("stale [dir]", "Check skills for staleness in the current package or workspace").usage("stale [dir] [--json]").option("--json", "Output JSON").example("stale").example("stale packages/query").example("stale --json").action(async (targetDir, options) => {
582
+ cli.command("stale [dir]", "Check skills for staleness in the current package or workspace").usage("stale [dir] [--json] [--github-review]").option("--json", "Output JSON").option("--github-review", "Write GitHub Actions review PR files").option("--package-label <label>", "Fallback package label for review PRs").example("stale").example("stale packages/query").example("stale --json").action(async (targetDir, options) => {
437
583
  await runStaleCommand(targetDir, options, resolveStaleTargets);
438
584
  });
439
585
  cli.command("edit-package-json", "Update package.json files so skills are published").usage("edit-package-json").action(async () => {
440
586
  await runEditPackageJsonCommand(process.cwd());
441
587
  });
588
+ cli.command("setup", "Copy Intent CI workflow templates into .github/workflows/").usage("setup").action(async () => {
589
+ await runSetupGithubActionsCommand(process.cwd(), getMetaDir());
590
+ });
442
591
  cli.command("setup-github-actions", "Copy Intent CI workflow templates into .github/workflows/").usage("setup-github-actions").action(async () => {
443
592
  await runSetupGithubActionsCommand(process.cwd(), getMetaDir());
444
593
  });
@@ -1,5 +1,5 @@
1
1
  import "./utils-COlDcU72.mjs";
2
2
  import "./skill-paths-8k9K9y26.mjs";
3
- import { n as printSkillTree, r as printTable, t as computeSkillNameWidth } from "./display-DUgtRJkt.mjs";
3
+ import { n as printSkillTree, r as printTable, t as computeSkillNameWidth } from "./display-CAof6doy.mjs";
4
4
 
5
5
  export { computeSkillNameWidth, printSkillTree, printTable };
package/dist/index.d.mts CHANGED
@@ -1,11 +1,29 @@
1
- import { a as IntentProjectConfig, c as ScanOptions, d as SkillStaleness, f as StalenessReport, i as IntentPackage, l as ScanResult, n as FeedbackPayload, o as MetaFeedbackPayload, p as VersionConflict, r as IntentConfig, s as MetaSkillName, t as AgentName, u as SkillEntry } from "./types-CsySN6Vw.mjs";
2
- import { i as runEditPackageJson, o as runSetupGithubActions, r as SetupGithubActionsResult, t as EditPackageJsonResult } from "./setup-DDoOLriA.mjs";
1
+ import { _ as StalenessReport, a as IntentArtifactSet, c as IntentConfig, d as MetaFeedbackPayload, f as MetaSkillName, g as SkillStaleness, h as SkillEntry, i as IntentArtifactFile, l as IntentPackage, m as ScanResult, n as FeedbackPayload, o as IntentArtifactSkill, p as ScanOptions, r as IntentArtifactCoverageIgnore, s as IntentArtifactWarning, t as AgentName, u as IntentProjectConfig, v as StalenessSignal, y as VersionConflict } from "./types-DT7Y6TFz.mjs";
2
+ import { i as runEditPackageJson, o as runSetupGithubActions, r as SetupGithubActionsResult, t as EditPackageJsonResult } from "./setup-t1i2o2-h.mjs";
3
3
 
4
4
  //#region src/scanner.d.ts
5
5
  declare function scanForIntents(root?: string, options?: ScanOptions): ScanResult;
6
6
  //#endregion
7
7
  //#region src/staleness.d.ts
8
- declare function checkStaleness(packageDir: string, packageName?: string): Promise<StalenessReport>;
8
+ declare function checkStaleness(packageDir: string, packageName?: string, artifactRoot?: string): Promise<StalenessReport>;
9
+ //#endregion
10
+ //#region src/artifact-coverage.d.ts
11
+ declare function readIntentArtifacts(root: string): IntentArtifactSet | null;
12
+ //#endregion
13
+ //#region src/workflow-review.d.ts
14
+ interface StaleReviewItem {
15
+ type: string;
16
+ library: string;
17
+ subject: string;
18
+ reasons: Array<string>;
19
+ artifactPath?: string;
20
+ packageName?: string;
21
+ packageRoot?: string;
22
+ skill?: string;
23
+ }
24
+ declare function collectStaleReviewItems(reports: Array<StalenessReport>): Array<StaleReviewItem>;
25
+ declare function createFailedStaleReviewItem(library: string): StaleReviewItem;
26
+ declare function buildStaleReviewBody(items: Array<StaleReviewItem>): string;
9
27
  //#endregion
10
28
  //#region src/feedback.d.ts
11
29
  declare function containsSecrets(text: string): boolean;
@@ -108,4 +126,4 @@ declare class ResolveSkillUseError extends Error {
108
126
  declare function isResolveSkillUseError(error: unknown): error is ResolveSkillUseError;
109
127
  declare function resolveSkillUse(use: string, scanResult: ScanResult): ResolveSkillResult;
110
128
  //#endregion
111
- export { type AgentName, type EditPackageJsonResult, type FeedbackPayload, type IntentConfig, type IntentPackage, type IntentProjectConfig, type MetaFeedbackPayload, type MetaSkillName, type ResolveSkillResult, ResolveSkillUseError, type ResolveSkillUseErrorCode, type ScanOptions, type ScanResult, type SetupGithubActionsResult, type SkillEntry, type SkillStaleness, type SkillUse, SkillUseParseError, type SkillUseParseErrorCode, type StalenessReport, checkStaleness, containsSecrets, findSkillFiles, formatSkillUse, getDeps, hasGhCli, isResolveSkillUseError, isSkillUseParseError, metaToMarkdown, parseFrontmatter, parseSkillUse, resolveDepDir, resolveFrequency, resolveSkillUse, runEditPackageJson, runSetupGithubActions, scanForIntents, submitFeedback, submitMetaFeedback, toMarkdown, validateMetaPayload, validatePayload };
129
+ export { type AgentName, type EditPackageJsonResult, type FeedbackPayload, type IntentArtifactCoverageIgnore, type IntentArtifactFile, type IntentArtifactSet, type IntentArtifactSkill, type IntentArtifactWarning, type IntentConfig, type IntentPackage, type IntentProjectConfig, type MetaFeedbackPayload, type MetaSkillName, type ResolveSkillResult, ResolveSkillUseError, type ResolveSkillUseErrorCode, type ScanOptions, type ScanResult, type SetupGithubActionsResult, type SkillEntry, type SkillStaleness, type SkillUse, SkillUseParseError, type SkillUseParseErrorCode, type StaleReviewItem, type StalenessReport, type StalenessSignal, 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 };