@tanstack/intent 0.0.33 → 0.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -104,7 +104,7 @@ npx @tanstack/intent@latest setup
104
104
  | Node.js + pnpm | Supported | Use `pnpm dlx @tanstack/intent@latest <command>` |
105
105
  | Node.js + Bun | Supported | Use `bunx @tanstack/intent@latest <command>` |
106
106
  | Deno | Best-effort | Requires `npm:` interop and `node_modules` support |
107
- | Yarn PnP | Unsupported | `@tanstack/intent` scans `node_modules` |
107
+ | Yarn PnP | Supported | Uses Yarn's PnP API when `node_modules` is absent |
108
108
 
109
109
  ## Monorepos
110
110
 
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-BN2A_60g.mjs";
4
- import { i as parseSkillUse } from "./skill-use-uwGleSOz.mjs";
5
- import { r as resolveSkillUse } from "./resolver-D2CgIYGg.mjs";
6
- import { t as resolveProjectContext } from "./project-context-IDLpJU3S.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-PUnIfBNC.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-2_wkomiT.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-DvLbcWzq.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,6 +245,10 @@ 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
+ if (options.githubReview) {
249
+ await runGithubReview(targetDir, options, resolveStaleTargets$1);
250
+ return;
251
+ }
248
252
  const { reports, workflowAdvisories = [] } = await resolveStaleTargets$1(targetDir);
249
253
  if (options.json) {
250
254
  console.log(JSON.stringify(reports, null, 2));
@@ -273,9 +277,26 @@ async function runStaleCommand(targetDir, options, resolveStaleTargets$1) {
273
277
  console.log();
274
278
  }
275
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
+ }
276
296
 
277
297
  //#endregion
278
298
  //#region src/commands/validate.ts
299
+ const agentSkillNamePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
279
300
  function buildValidationFailure(errors, warnings) {
280
301
  const lines = [
281
302
  "",
@@ -310,114 +331,230 @@ function collectPackagingWarnings(context) {
310
331
  }
311
332
  return warnings;
312
333
  }
313
- async function runValidateCommand(dir) {
314
- const [{ parse: parseYaml }, { findSkillFiles }] = await Promise.all([import("yaml"), import("./utils-dkVvY7D7.mjs")]);
315
- const targetDir = dir ?? "skills";
316
- const context = resolveProjectContext({
317
- cwd: process.cwd(),
318
- targetPath: targetDir
319
- });
320
- const skillsDir = context.targetSkillsDir ?? resolve(process.cwd(), targetDir);
321
- if (!existsSync(skillsDir)) fail(`Skills directory not found: ${skillsDir}`);
322
- const errors = [];
323
- const skillFiles = findSkillFiles(skillsDir);
324
- if (skillFiles.length === 0) fail("No SKILL.md files found");
325
- for (const filePath of skillFiles) {
326
- const rel = relative(process.cwd(), filePath);
327
- const content = readFileSync(filePath, "utf8");
328
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)/);
329
- if (!match) {
330
- errors.push({
331
- file: rel,
332
- message: "Missing or invalid frontmatter"
333
- });
334
- continue;
335
- }
336
- if (!match[1]) {
337
- errors.push({
338
- file: rel,
339
- message: "Missing YAML frontmatter"
340
- });
341
- continue;
342
- }
343
- let fm;
344
- try {
345
- fm = parseYaml(match[1]);
346
- } catch (err) {
347
- const detail = err instanceof Error ? err.message : String(err);
348
- 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({
349
349
  file: rel,
350
- message: `Invalid YAML frontmatter: ${detail}`
350
+ message: "Agent Skills spec warning: each name segment should use lowercase letters, numbers, and single hyphens only"
351
351
  });
352
- continue;
352
+ break;
353
353
  }
354
- if (!fm.name) errors.push({
354
+ const parentDir = basename(dirname(filePath));
355
+ if (!fm.name.includes("/") && fm.name !== parentDir) warnings.push({
355
356
  file: rel,
356
- message: "Missing required field: name"
357
+ message: "Agent Skills spec warning: name should match the parent directory name"
357
358
  });
358
- 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({
359
366
  file: rel,
360
- message: "Missing required field: description"
367
+ message: "Agent Skills spec warning: compatibility should be a non-empty string"
361
368
  });
362
- if (typeof fm.name === "string") {
363
- const expectedPath = relative(skillsDir, filePath).replace(/[/\\]SKILL\.md$/, "").split(sep).join("/");
364
- if (fm.name !== expectedPath) errors.push({
365
- file: rel,
366
- message: `name "${fm.name}" does not match directory path "${expectedPath}"`
367
- });
368
- }
369
- if (typeof fm.description === "string" && fm.description.length > 1024) errors.push({
369
+ else if (fm.compatibility.length > 500) warnings.push({
370
370
  file: rel,
371
- message: `Description exceeds 1024 character limit (${fm.description.length} chars)`
371
+ message: `Agent Skills spec warning: compatibility exceeds 500 characters (${fm.compatibility.length} chars)`
372
372
  });
373
- if (fm.type === "framework" && !Array.isArray(fm.requires)) errors.push({
373
+ }
374
+ if (fm.metadata !== void 0) {
375
+ if (!isRecord(fm.metadata)) warnings.push({
374
376
  file: rel,
375
- message: "Framework skills must have a \"requires\" field"
377
+ message: "Agent Skills spec warning: metadata should be a mapping"
376
378
  });
377
- const lineCount = content.split(/\r?\n/).length;
378
- if (lineCount > 500) errors.push({
379
+ else if (Object.values(fm.metadata).some((value) => typeof value !== "string")) warnings.push({
379
380
  file: rel,
380
- 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)
381
402
  });
403
+ throw err;
404
+ }
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;
382
422
  }
383
- const artifactsDir = join(skillsDir, "_artifacts");
384
- if (!context.isMonorepo && existsSync(artifactsDir)) for (const fileName of [
385
- "domain_map.yaml",
386
- "skill_spec.md",
387
- "skill_tree.yaml"
388
- ]) {
389
- const artifactPath = join(artifactsDir, fileName);
390
- if (!existsSync(artifactPath)) {
391
- errors.push({
392
- file: relative(process.cwd(), artifactPath),
393
- message: "Missing required artifact"
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"
394
461
  });
395
- continue;
396
- }
397
- const content = readFileSync(artifactPath, "utf8");
398
- if (content.trim().length === 0) {
399
- errors.push({
400
- file: relative(process.cwd(), artifactPath),
401
- message: "Artifact file is empty"
462
+ if (!fm.description) errors.push({
463
+ file: rel,
464
+ message: "Missing required field: description"
402
465
  });
403
- continue;
404
- }
405
- if (fileName.endsWith(".yaml")) try {
406
- parseYaml(content);
407
- } catch (err) {
408
- const detail = err instanceof Error ? err.message : String(err);
409
- errors.push({
410
- file: relative(process.cwd(), artifactPath),
411
- 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"
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.`
412
490
  });
413
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
+ }
523
+ }
524
+ validatedCount += skillFiles.length;
525
+ warnings.push(...collectPackagingWarnings(validateContext));
414
526
  }
415
- const warnings = collectPackagingWarnings(context);
416
527
  if (errors.length > 0) fail(buildValidationFailure(errors, warnings));
417
- console.log(`✅ Validated ${skillFiles.length} skill files — all passed`);
528
+ console.log(`✅ Validated ${validatedCount} skill files — all passed`);
418
529
  if (warnings.length > 0) console.log();
419
530
  printWarnings(warnings);
420
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
+ }
421
558
 
422
559
  //#endregion
423
560
  //#region src/cli.ts
@@ -433,8 +570,8 @@ function createCli() {
433
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) => {
434
571
  await runMetaCommand(name, getMetaDir());
435
572
  });
436
- cli.command("validate [dir]", "Validate skill files").usage("validate [dir]").example("validate").example("validate packages/query/skills").action(async (dir) => {
437
- 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);
438
575
  });
439
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) => {
440
577
  await runInstallCommand(options, scanIntentsOrFail);
@@ -442,7 +579,7 @@ function createCli() {
442
579
  cli.command("scaffold", "Print maintainer scaffold prompt").usage("scaffold").action(() => {
443
580
  runScaffoldCommand(getMetaDir());
444
581
  });
445
- 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) => {
446
583
  await runStaleCommand(targetDir, options, resolveStaleTargets);
447
584
  });
448
585
  cli.command("edit-package-json", "Update package.json files so skills are published").usage("edit-package-json").action(async () => {
@@ -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-BTZWCjzT.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,5 +1,5 @@
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-_y9b00bI.mjs";
2
- import { i as runEditPackageJson, o as runSetupGithubActions, r as SetupGithubActionsResult, t as EditPackageJsonResult } from "./setup-D2CGdTsx.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;
package/dist/index.mjs CHANGED
@@ -1,133 +1,18 @@
1
1
  import { a as parseFrontmatter, n as findSkillFiles, o as resolveDepDir, r as getDeps } from "./utils-COlDcU72.mjs";
2
2
  import "./skill-paths-8k9K9y26.mjs";
3
- import { t as scanForIntents } from "./scanner-CW59cxE_.mjs";
3
+ import { t as scanForIntents } from "./scanner-B1j-wDhj.mjs";
4
4
  import "./workspace-patterns-BN2A_60g.mjs";
5
5
  import { t as readIntentArtifacts } from "./artifact-coverage-wLNVX8yC.mjs";
6
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
+ import { n as collectStaleReviewItems, r as createFailedStaleReviewItem, t as buildStaleReviewBody } from "./workflow-review-CwkPVIQf.mjs";
8
+ import { i as parseSkillUse, n as formatSkillUse, r as isSkillUseParseError, t as SkillUseParseError } from "./skill-use-BzuuvLM7.mjs";
9
+ import { n as isResolveSkillUseError, r as resolveSkillUse, t as ResolveSkillUseError } from "./resolver-Whd12ksO.mjs";
10
+ import "./project-context-alYMNoNa.mjs";
11
+ import { r as runSetupGithubActions, t as runEditPackageJson } from "./setup-Dp-W8y0Y.mjs";
11
12
  import { readFileSync, writeFileSync } from "node:fs";
12
13
  import { join } from "node:path";
13
14
  import { execFileSync, execSync } from "node:child_process";
14
15
 
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
131
16
  //#region src/feedback.ts
132
17
  const META_FEEDBACK_REPO = "TanStack/intent";
133
18
  const SECRET_PATTERNS = [
@@ -1,5 +1,5 @@
1
- import { i as parseSkillUse, n as formatSkillUse } from "./skill-use-uwGleSOz.mjs";
2
- import { t as resolveProjectContext } from "./project-context-IDLpJU3S.mjs";
1
+ import { i as parseSkillUse, n as formatSkillUse } from "./skill-use-BzuuvLM7.mjs";
2
+ import { t as resolveProjectContext } from "./project-context-alYMNoNa.mjs";
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { dirname, join, relative, resolve } from "node:path";
5
5
  import { parse } from "yaml";
@@ -28,7 +28,7 @@ function printWarnings(warnings) {
28
28
 
29
29
  //#endregion
30
30
  //#region src/cli-support.ts
31
- const INTENT_CHECK_SKILLS_WORKFLOW_VERSION = 2;
31
+ const INTENT_CHECK_SKILLS_WORKFLOW_VERSION = 3;
32
32
  function getMetaDir() {
33
33
  return join(dirname(fileURLToPath(import.meta.url)), "..", "meta");
34
34
  }
@@ -46,7 +46,7 @@ function getCheckSkillsWorkflowAdvisories(root) {
46
46
  return [`Intent workflow update available: run \`npx @tanstack/intent@latest setup\` to refresh ${relative(process.cwd(), workflowPath) || workflowPath}.`];
47
47
  }
48
48
  async function scanIntentsOrFail(options) {
49
- const { scanForIntents } = await import("./scanner-DlkcbVye.mjs");
49
+ const { scanForIntents } = await import("./scanner-C2YjF4w_.mjs");
50
50
  try {
51
51
  return scanForIntents(void 0, options);
52
52
  } catch (err) {
@@ -2,10 +2,10 @@
2
2
  import "./utils-COlDcU72.mjs";
3
3
  import "./skill-paths-8k9K9y26.mjs";
4
4
  import "./workspace-patterns-BN2A_60g.mjs";
5
- import "./project-context-IDLpJU3S.mjs";
6
- import { t as INSTALL_PROMPT } from "./install-PUnIfBNC.mjs";
7
- import { n as printSkillTree, r as printTable, t as computeSkillNameWidth } from "./display-BTZWCjzT.mjs";
8
- import { t as scanLibrary } from "./library-scanner-Cl-XPEMf.mjs";
5
+ import "./project-context-alYMNoNa.mjs";
6
+ import { t as INSTALL_PROMPT } from "./install-2_wkomiT.mjs";
7
+ import { n as printSkillTree, r as printTable, t as computeSkillNameWidth } from "./display-CAof6doy.mjs";
8
+ import { t as scanLibrary } from "./library-scanner-fexXlPXb.mjs";
9
9
 
10
10
  //#region src/intent-library.ts
11
11
  function cmdList() {
@@ -1,4 +1,4 @@
1
- import { h as SkillEntry } from "./types-_y9b00bI.mjs";
1
+ import { h as SkillEntry } from "./types-DT7Y6TFz.mjs";
2
2
 
3
3
  //#region src/library-scanner.d.ts
4
4
  interface LibraryPackage {
@@ -1,5 +1,5 @@
1
1
  import "./utils-COlDcU72.mjs";
2
2
  import "./skill-paths-8k9K9y26.mjs";
3
- import { t as scanLibrary } from "./library-scanner-Cl-XPEMf.mjs";
3
+ import { t as scanLibrary } from "./library-scanner-fexXlPXb.mjs";
4
4
 
5
5
  export { scanLibrary };
@@ -1,4 +1,4 @@
1
- import { i as parseSkillUse } from "./skill-use-uwGleSOz.mjs";
1
+ import { i as parseSkillUse } from "./skill-use-BzuuvLM7.mjs";
2
2
 
3
3
  //#region src/resolver.ts
4
4
  var ResolveSkillUseError = class extends Error {
@@ -1,8 +1,9 @@
1
1
  import { a as parseFrontmatter, i as listNodeModulesPackageDirs, o as resolveDepDir, r as getDeps, s as toPosixPath, t as detectGlobalNodeModules } from "./utils-COlDcU72.mjs";
2
2
  import { r as rewriteSkillLoadPaths } from "./skill-paths-8k9K9y26.mjs";
3
3
  import { a as resolveWorkspacePackages, i as readWorkspacePatterns, r as findWorkspaceRoot } from "./workspace-patterns-BN2A_60g.mjs";
4
+ import { createRequire } from "node:module";
4
5
  import { existsSync, readFileSync, readdirSync } from "node:fs";
5
- import { join, relative, sep } from "node:path";
6
+ import { dirname, join, relative, resolve, sep } from "node:path";
6
7
 
7
8
  //#region src/discovery/register.ts
8
9
  function isLocalToProject(dirPath, projectRoot) {
@@ -132,13 +133,31 @@ function createDependencyWalker(opts) {
132
133
 
133
134
  //#endregion
134
135
  //#region src/scanner.ts
135
- function detectPackageManager(root) {
136
- if (existsSync(join(root, ".pnp.cjs")) || existsSync(join(root, ".pnp.js"))) throw new Error("Yarn PnP is not yet supported. Add `nodeLinker: node-modules` to your .yarnrc.yml to use intent.");
136
+ const requireFromHere = createRequire(import.meta.url);
137
+ function findPnpFile(start) {
138
+ let dir = resolve(start);
139
+ while (true) {
140
+ for (const fileName of [".pnp.cjs", ".pnp.js"]) {
141
+ const pnpPath = join(dir, fileName);
142
+ if (existsSync(pnpPath)) return pnpPath;
143
+ }
144
+ const next = dirname(dir);
145
+ if (next === dir) return null;
146
+ dir = next;
147
+ }
148
+ }
149
+ function isYarnPnpProject(root) {
150
+ return findPnpFile(root) !== null;
151
+ }
152
+ function assertLocalNodeModulesSupported(root) {
137
153
  if (existsSync(join(root, "deno.json")) && !existsSync(join(root, "node_modules"))) throw new Error("Deno without node_modules is not yet supported. Add `\"nodeModulesDir\": \"auto\"` to your deno.json to use intent.");
154
+ }
155
+ function detectPackageManager(root) {
138
156
  const dirsToCheck = [root];
139
157
  const wsRoot = findWorkspaceRoot(root);
140
158
  if (wsRoot && wsRoot !== root) dirsToCheck.push(wsRoot);
141
159
  for (const dir of dirsToCheck) {
160
+ if (isYarnPnpProject(dir)) return "yarn";
142
161
  if (existsSync(join(dir, "pnpm-lock.yaml"))) return "pnpm";
143
162
  if (existsSync(join(dir, "bun.lockb")) || existsSync(join(dir, "bun.lock"))) return "bun";
144
163
  if (existsSync(join(dir, "yarn.lock"))) return "yarn";
@@ -146,6 +165,35 @@ function detectPackageManager(root) {
146
165
  }
147
166
  return "unknown";
148
167
  }
168
+ function loadPnpApi(root) {
169
+ const pnpPath = findPnpFile(root);
170
+ if (!pnpPath) return null;
171
+ try {
172
+ const foundApi = requireFromHere("node:module").findPnpApi?.(root);
173
+ if (foundApi) return foundApi;
174
+ const pnpModule = requireFromHere(pnpPath);
175
+ if (typeof pnpModule.setup === "function") pnpModule.setup();
176
+ if (typeof pnpModule.getDependencyTreeRoots === "function" && typeof pnpModule.getPackageInformation === "function") return pnpModule;
177
+ return createRequire(join(dirname(pnpPath), "package.json"))("pnpapi");
178
+ } catch (err) {
179
+ const msg = err instanceof Error ? err.message : String(err);
180
+ throw new Error(`Yarn PnP project detected, but Intent could not load Yarn's PnP API from ${pnpPath}: ${msg}`);
181
+ }
182
+ }
183
+ function getPnpLocatorKey(locator) {
184
+ return `${locator.name ?? "<top>"}@${locator.reference ?? "<top>"}`;
185
+ }
186
+ function getPnpDependencyLocator(dependencyName, target) {
187
+ if (target === null) return null;
188
+ if (Array.isArray(target)) return {
189
+ name: target[0],
190
+ reference: target[1]
191
+ };
192
+ return {
193
+ name: dependencyName,
194
+ reference: target
195
+ };
196
+ }
149
197
  function validateIntentField(_pkgName, intent) {
150
198
  if (!intent || typeof intent !== "object") return null;
151
199
  const pb = intent;
@@ -327,6 +375,12 @@ function scanForIntents(root, options = {}) {
327
375
  const packageIndexes = /* @__PURE__ */ new Map();
328
376
  const packageJsonCache = /* @__PURE__ */ new Map();
329
377
  const packageVariants = /* @__PURE__ */ new Map();
378
+ let pnpApi;
379
+ function getPnpApi() {
380
+ if (scanScope === "global") return null;
381
+ if (pnpApi === void 0) pnpApi = loadPnpApi(projectRoot);
382
+ return pnpApi;
383
+ }
330
384
  function rememberVariant(pkg) {
331
385
  let variants = packageVariants.get(pkg.name);
332
386
  if (!variants) {
@@ -378,7 +432,34 @@ function scanForIntents(root, options = {}) {
378
432
  tryRegister,
379
433
  warnings
380
434
  });
435
+ function scanPnpPackages(api) {
436
+ const visited = /* @__PURE__ */ new Set();
437
+ const workspaceRoot = findWorkspaceRoot(projectRoot);
438
+ const projectLocator = api.findPackageLocator?.(projectRoot.endsWith(sep) ? projectRoot : `${projectRoot}${sep}`);
439
+ const roots = workspaceRoot && workspaceRoot !== projectRoot && projectLocator ? [projectLocator] : api.getDependencyTreeRoots?.() ?? (api.topLevel ? [api.topLevel] : []);
440
+ function visit(locator) {
441
+ const key = getPnpLocatorKey(locator);
442
+ if (visited.has(key)) return;
443
+ visited.add(key);
444
+ const info = api.getPackageInformation(locator);
445
+ if (!info) return;
446
+ tryRegister(info.packageLocation.replace(/[\\/]$/, ""), locator.name ?? "unknown");
447
+ for (const [dependencyName, target] of info.packageDependencies) {
448
+ const dependencyLocator = getPnpDependencyLocator(dependencyName, target);
449
+ if (dependencyLocator) visit(dependencyLocator);
450
+ }
451
+ }
452
+ for (const locator of roots) visit(locator);
453
+ }
381
454
  function scanLocalPackages() {
455
+ if (!nodeModules.local.exists) {
456
+ const api = getPnpApi();
457
+ if (api) {
458
+ scanPnpPackages(api);
459
+ return;
460
+ }
461
+ }
462
+ assertLocalNodeModulesSupported(projectRoot);
382
463
  scanTarget(nodeModules.local);
383
464
  walkWorkspacePackages();
384
465
  walkKnownPackages();
@@ -1,6 +1,6 @@
1
1
  import "./utils-COlDcU72.mjs";
2
2
  import "./skill-paths-8k9K9y26.mjs";
3
- import { t as scanForIntents } from "./scanner-CW59cxE_.mjs";
3
+ import { t as scanForIntents } from "./scanner-B1j-wDhj.mjs";
4
4
  import "./workspace-patterns-BN2A_60g.mjs";
5
5
 
6
6
  export { scanForIntents };
@@ -1,5 +1,5 @@
1
1
  import { i as readWorkspacePatterns, r as findWorkspaceRoot, t as findPackagesWithSkills } from "./workspace-patterns-BN2A_60g.mjs";
2
- import { t as resolveProjectContext } from "./project-context-IDLpJU3S.mjs";
2
+ import { t as resolveProjectContext } from "./project-context-alYMNoNa.mjs";
3
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
4
4
  import { basename, join, relative } from "node:path";
5
5
 
package/dist/setup.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as runEditPackageJsonAll, c as findWorkspaceRoot, i as runEditPackageJson, l as readWorkspacePatterns, n as MonorepoResult, o as runSetupGithubActions, r as SetupGithubActionsResult, s as findPackagesWithSkills, t as EditPackageJsonResult, u as resolveWorkspacePackages } from "./setup-D2CGdTsx.mjs";
1
+ import { a as runEditPackageJsonAll, c as findWorkspaceRoot, i as runEditPackageJson, l as readWorkspacePatterns, n as MonorepoResult, o as runSetupGithubActions, r as SetupGithubActionsResult, s as findPackagesWithSkills, t as EditPackageJsonResult, u as resolveWorkspacePackages } from "./setup-t1i2o2-h.mjs";
2
2
  export { EditPackageJsonResult, MonorepoResult, SetupGithubActionsResult, findPackagesWithSkills, findWorkspaceRoot, readWorkspacePatterns, resolveWorkspacePackages, runEditPackageJson, runEditPackageJsonAll, runSetupGithubActions };
package/dist/setup.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./utils-COlDcU72.mjs";
2
2
  import { a as resolveWorkspacePackages, i as readWorkspacePatterns, r as findWorkspaceRoot, t as findPackagesWithSkills } from "./workspace-patterns-BN2A_60g.mjs";
3
- import "./project-context-IDLpJU3S.mjs";
4
- import { n as runEditPackageJsonAll, r as runSetupGithubActions, t as runEditPackageJson } from "./setup-DfLsziXU.mjs";
3
+ import "./project-context-alYMNoNa.mjs";
4
+ import { n as runEditPackageJsonAll, r as runSetupGithubActions, t as runEditPackageJson } from "./setup-Dp-W8y0Y.mjs";
5
5
 
6
6
  export { findPackagesWithSkills, findWorkspaceRoot, readWorkspacePatterns, resolveWorkspacePackages, runEditPackageJson, runEditPackageJsonAll, runSetupGithubActions };
@@ -0,0 +1,153 @@
1
+ import { appendFileSync, writeFileSync } from "node:fs";
2
+
3
+ //#region src/workflow-review.ts
4
+ function collectStaleReviewItems(reports) {
5
+ const items = [];
6
+ for (const report of reports) {
7
+ for (const skill of report.skills ?? []) {
8
+ if (!skill?.needsReview) continue;
9
+ items.push({
10
+ type: "stale-skill",
11
+ library: report.library,
12
+ subject: skill.name,
13
+ reasons: skill.reasons ?? []
14
+ });
15
+ }
16
+ for (const signal of report.signals ?? []) {
17
+ if (signal?.needsReview === false) continue;
18
+ items.push({
19
+ type: signal?.type ?? "review-signal",
20
+ library: signal?.library ?? report.library,
21
+ subject: signal?.packageName ?? signal?.packageRoot ?? signal?.skill ?? signal?.artifactPath ?? signal?.subject ?? report.library,
22
+ reasons: signal?.reasons ?? [],
23
+ artifactPath: signal?.artifactPath,
24
+ packageName: signal?.packageName,
25
+ packageRoot: signal?.packageRoot,
26
+ skill: signal?.skill
27
+ });
28
+ }
29
+ }
30
+ return items;
31
+ }
32
+ function createFailedStaleReviewItem(library) {
33
+ return {
34
+ type: "stale-check-failed",
35
+ library,
36
+ subject: "intent stale --json",
37
+ reasons: ["The stale check command failed. Review the workflow logs before updating skills."]
38
+ };
39
+ }
40
+ function createWorkflowAdvisoryReviewItems(library, advisories) {
41
+ return advisories.map((advisory) => ({
42
+ type: "workflow-advisory",
43
+ library,
44
+ subject: "check-skills.yml",
45
+ reasons: [advisory]
46
+ }));
47
+ }
48
+ function buildStaleReviewBody(items) {
49
+ const grouped = /* @__PURE__ */ new Map();
50
+ for (const item of items) grouped.set(item.type, (grouped.get(item.type) ?? 0) + 1);
51
+ const signalRows = [...grouped.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([type, count]) => `| \`${type}\` | ${count} |`);
52
+ const itemRows = items.map((item) => {
53
+ const subject = item.subject ? `\`${item.subject}\`` : "-";
54
+ const reasons = item.reasons?.length ? item.reasons.join("; ") : "-";
55
+ return `| \`${item.type}\` | ${subject} | \`${item.library}\` | ${reasons} |`;
56
+ });
57
+ const reasonBullets = items.map((item) => {
58
+ const subject = item.subject ? `\`${item.subject}\`` : "`unknown`";
59
+ const reasons = item.reasons?.length ? item.reasons.join("; ") : "Intent did not emit a detailed reason for this signal.";
60
+ return `- \`${item.type}\` for ${subject}: ${reasons}`;
61
+ });
62
+ const prompt = [
63
+ "You are helping maintain Intent skills for this repository.",
64
+ "",
65
+ "Goal:",
66
+ "Resolve the Intent skill review signals below while preserving the existing scope, taxonomy, and maintainer-reviewed artifacts.",
67
+ "",
68
+ "Review signals:",
69
+ JSON.stringify(items, null, 2),
70
+ "",
71
+ "Required workflow:",
72
+ "1. Read the existing `_artifacts/*domain_map.yaml`, `_artifacts/*skill_tree.yaml`, and generated `skills/**/SKILL.md` files.",
73
+ "2. Read each flagged package package.json, public exports, README/docs if present, and source entry points.",
74
+ "3. Compare flagged packages against the existing domains, skills, tasks, packages, covers, sources, tensions, and cross-references in the artifacts.",
75
+ "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.",
76
+ "",
77
+ "Maintainer questions:",
78
+ "Before editing skills or artifacts, ask the maintainer:",
79
+ "1. For each flagged package, is this package user-facing enough to need agent guidance?",
80
+ "2. If yes, should it extend an existing skill or become a new skill?",
81
+ "3. If it extends an existing skill, which current skill should own it?",
82
+ "4. If it is out of scope, what short reason should be recorded in artifact coverage ignores?",
83
+ "5. Are any of these packages experimental or unstable enough to exclude for now?",
84
+ "",
85
+ "Decision rules:",
86
+ "- Do not auto-generate skills.",
87
+ "- Do not create broad new skill areas without maintainer confirmation.",
88
+ "- Prefer adding package coverage to an existing skill when the package is an implementation variant of an existing domain.",
89
+ "- Create a new skill only when the package introduces a distinct developer task or failure mode.",
90
+ "- Preserve current naming, path, and package layout conventions.",
91
+ "- Keep generated skills under the package-local `skills/` directory.",
92
+ "- Keep repo-root `_artifacts` as the reviewed plan.",
93
+ "",
94
+ "If maintainer confirms updates:",
95
+ "1. Update the relevant `_artifacts/*domain_map.yaml` or `_artifacts/*skill_tree.yaml`.",
96
+ "2. Update or create `SKILL.md` files only for confirmed coverage changes.",
97
+ "3. Keep `sources` aligned between artifact skill entries and SKILL frontmatter.",
98
+ "4. Bump `library_version` only for skills whose covered source package version changed.",
99
+ "5. Run `npx @tanstack/intent@latest validate` on touched skill directories.",
100
+ "6. Summarize every package as one of: existing-skill coverage, new skill, ignored, or deferred."
101
+ ].join("\n");
102
+ return [
103
+ "## Intent Skill Review Needed",
104
+ "",
105
+ "Intent found skills, artifact coverage, or workspace package coverage that need maintainer review.",
106
+ "",
107
+ "### Summary",
108
+ "",
109
+ "| Signal | Count |",
110
+ "| --- | ---: |",
111
+ ...signalRows,
112
+ "",
113
+ "### Why This PR Opened",
114
+ "",
115
+ ...reasonBullets,
116
+ "",
117
+ "### Review Items",
118
+ "",
119
+ "| Signal | Subject | Library | Reason |",
120
+ "| --- | --- | --- | --- |",
121
+ ...itemRows,
122
+ "",
123
+ "### Agent Prompt",
124
+ "",
125
+ "Paste this into your coding agent:",
126
+ "",
127
+ "```text",
128
+ prompt,
129
+ "```",
130
+ "",
131
+ "This PR is a review reminder only. It does not update skills automatically."
132
+ ].join("\n");
133
+ }
134
+ function writeStaleReviewWorkflowFiles(items, options = {}) {
135
+ const outputPath = options.outputPath ?? process.env.GITHUB_OUTPUT;
136
+ const summaryPath = options.summaryPath ?? process.env.GITHUB_STEP_SUMMARY;
137
+ const reviewItemsPath = options.reviewItemsPath ?? "review-items.json";
138
+ const prBodyPath = options.prBodyPath ?? "pr-body.md";
139
+ const hasReview = items.length > 0;
140
+ writeFileSync(reviewItemsPath, JSON.stringify(items, null, 2) + "\n");
141
+ if (outputPath) appendFileSync(outputPath, `has_review=${hasReview ? "true" : "false"}\n`);
142
+ const summary = hasReview ? buildStaleReviewBody(items) + "\n" : [
143
+ "### Intent skill review",
144
+ "",
145
+ "No stale skills or coverage gaps found.",
146
+ ""
147
+ ].join("\n");
148
+ if (hasReview) writeFileSync(prBodyPath, summary);
149
+ if (summaryPath) appendFileSync(summaryPath, summary);
150
+ }
151
+
152
+ //#endregion
153
+ export { writeStaleReviewWorkflowFiles as a, createWorkflowAdvisoryReviewItems as i, collectStaleReviewItems as n, createFailedStaleReviewItem as r, buildStaleReviewBody as t };
@@ -0,0 +1,3 @@
1
+ import { a as writeStaleReviewWorkflowFiles, i as createWorkflowAdvisoryReviewItems, n as collectStaleReviewItems, r as createFailedStaleReviewItem, t as buildStaleReviewBody } from "./workflow-review-CwkPVIQf.mjs";
2
+
3
+ export { collectStaleReviewItems, createFailedStaleReviewItem, createWorkflowAdvisoryReviewItems, writeStaleReviewWorkflowFiles };
@@ -1,11 +1,13 @@
1
1
  # check-skills.yml — Drop this into your library repo's .github/workflows/
2
2
  #
3
- # Checks intent skills after a release and opens or updates one review PR when
4
- # existing skills, artifact coverage, or workspace package coverage need review.
3
+ # Validates intent skills on PRs. After a release or manual run, opens or
4
+ # updates one review PR when existing skills, artifact coverage, or workspace
5
+ # package coverage need review.
5
6
  #
6
- # Triggers: new release published, or manual workflow_dispatch.
7
+ # Triggers: pull requests touching skills/artifacts, new release published, or
8
+ # manual workflow_dispatch.
7
9
  #
8
- # intent-workflow-version: 2
10
+ # intent-workflow-version: 3
9
11
  #
10
12
  # Template variables (replaced by `intent setup`):
11
13
  # {{PACKAGE_LABEL}} — e.g. @tanstack/query or my-workspace workspace
@@ -13,6 +15,12 @@
13
15
  name: Check Skills
14
16
 
15
17
  on:
18
+ pull_request:
19
+ paths:
20
+ - 'skills/**'
21
+ - '**/skills/**'
22
+ - '_artifacts/**'
23
+ - '**/_artifacts/**'
16
24
  release:
17
25
  types: [published]
18
26
  workflow_dispatch: {}
@@ -22,8 +30,28 @@ permissions:
22
30
  pull-requests: write
23
31
 
24
32
  jobs:
25
- check:
33
+ validate:
34
+ name: Validate intent skills
35
+ if: github.event_name == 'pull_request'
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - name: Checkout
39
+ uses: actions/checkout@v4
40
+
41
+ - name: Setup Node
42
+ uses: actions/setup-node@v4
43
+ with:
44
+ node-version: 20
45
+
46
+ - name: Install intent
47
+ run: npm install -g @tanstack/intent
48
+
49
+ - name: Validate skills
50
+ run: intent validate --github-summary
51
+
52
+ review:
26
53
  name: Check intent skill coverage
54
+ if: github.event_name != 'pull_request'
27
55
  runs-on: ubuntu-latest
28
56
  steps:
29
57
  - name: Checkout
@@ -42,181 +70,7 @@ jobs:
42
70
  - name: Check skills
43
71
  id: stale
44
72
  run: |
45
- set +e
46
- intent stale --json > stale.json
47
- STATUS=$?
48
- set -e
49
-
50
- cat stale.json
51
-
52
- if [ "$STATUS" -ne 0 ]; then
53
- echo "has_review=true" >> "$GITHUB_OUTPUT"
54
- echo "check_failed=true" >> "$GITHUB_OUTPUT"
55
- cat > review-items.json <<'JSON'
56
- [
57
- {
58
- "type": "stale-check-failed",
59
- "library": "{{PACKAGE_LABEL}}",
60
- "subject": "intent stale --json",
61
- "reasons": ["The stale check command failed. Review the workflow logs before updating skills."]
62
- }
63
- ]
64
- JSON
65
- else
66
- node <<'NODE'
67
- const fs = require('fs')
68
- const reports = JSON.parse(fs.readFileSync('stale.json', 'utf8'))
69
- const items = []
70
-
71
- for (const report of reports) {
72
- for (const skill of report.skills ?? []) {
73
- if (!skill?.needsReview) continue
74
- items.push({
75
- type: 'stale-skill',
76
- library: report.library,
77
- subject: skill.name,
78
- reasons: skill.reasons ?? [],
79
- })
80
- }
81
-
82
- for (const signal of report.signals ?? []) {
83
- if (signal?.needsReview === false) continue
84
- items.push({
85
- type: signal?.type ?? 'review-signal',
86
- library: signal?.library ?? report.library,
87
- subject:
88
- signal?.packageName ??
89
- signal?.packageRoot ??
90
- signal?.skill ??
91
- signal?.artifactPath ??
92
- signal?.subject ??
93
- report.library,
94
- reasons: signal?.reasons ?? [],
95
- artifactPath: signal?.artifactPath,
96
- packageName: signal?.packageName,
97
- packageRoot: signal?.packageRoot,
98
- skill: signal?.skill,
99
- })
100
- }
101
- }
102
-
103
- fs.writeFileSync('review-items.json', JSON.stringify(items, null, 2) + '\n')
104
- fs.appendFileSync(
105
- process.env.GITHUB_OUTPUT,
106
- `has_review=${items.length > 0 ? 'true' : 'false'}\n`,
107
- )
108
- NODE
109
- fi
110
-
111
- {
112
- echo "review_items<<EOF"
113
- cat review-items.json
114
- echo "EOF"
115
- } >> "$GITHUB_OUTPUT"
116
-
117
- - name: Write clean summary
118
- if: steps.stale.outputs.has_review == 'false'
119
- run: |
120
- {
121
- echo "### Intent skill review"
122
- echo ""
123
- echo "No stale skills or coverage gaps found."
124
- } >> "$GITHUB_STEP_SUMMARY"
125
-
126
- - name: Build review PR body
127
- if: steps.stale.outputs.has_review == 'true'
128
- run: |
129
- node <<'NODE'
130
- const fs = require('fs')
131
- const items = JSON.parse(fs.readFileSync('review-items.json', 'utf8'))
132
- const grouped = new Map()
133
-
134
- for (const item of items) {
135
- grouped.set(item.type, (grouped.get(item.type) ?? 0) + 1)
136
- }
137
-
138
- const signalRows = [...grouped.entries()]
139
- .sort(([a], [b]) => a.localeCompare(b))
140
- .map(([type, count]) => `| \`${type}\` | ${count} |`)
141
-
142
- const itemRows = items.map((item) => {
143
- const subject = item.subject ? `\`${item.subject}\`` : '-'
144
- const reasons = item.reasons?.length ? item.reasons.join('; ') : '-'
145
- return `| \`${item.type}\` | ${subject} | \`${item.library}\` | ${reasons} |`
146
- })
147
-
148
- const prompt = [
149
- 'You are helping maintain Intent skills for this repository.',
150
- '',
151
- 'Goal:',
152
- 'Resolve the Intent skill review signals below while preserving the existing scope, taxonomy, and maintainer-reviewed artifacts.',
153
- '',
154
- 'Review signals:',
155
- JSON.stringify(items, null, 2),
156
- '',
157
- 'Required workflow:',
158
- '1. Read the existing `_artifacts/*domain_map.yaml`, `_artifacts/*skill_tree.yaml`, and generated `skills/**/SKILL.md` files.',
159
- '2. Read each flagged package package.json, public exports, README/docs if present, and source entry points.',
160
- '3. Compare flagged packages against the existing domains, skills, tasks, packages, covers, sources, tensions, and cross-references in the artifacts.',
161
- '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.',
162
- '',
163
- 'Maintainer questions:',
164
- 'Before editing skills or artifacts, ask the maintainer:',
165
- '1. For each flagged package, is this package user-facing enough to need agent guidance?',
166
- '2. If yes, should it extend an existing skill or become a new skill?',
167
- '3. If it extends an existing skill, which current skill should own it?',
168
- '4. If it is out of scope, what short reason should be recorded in artifact coverage ignores?',
169
- '5. Are any of these packages experimental or unstable enough to exclude for now?',
170
- '',
171
- 'Decision rules:',
172
- '- Do not auto-generate skills.',
173
- '- Do not create broad new skill areas without maintainer confirmation.',
174
- '- Prefer adding package coverage to an existing skill when the package is an implementation variant of an existing domain.',
175
- '- Create a new skill only when the package introduces a distinct developer task or failure mode.',
176
- '- Preserve current naming, path, and package layout conventions.',
177
- '- Keep generated skills under the package-local `skills/` directory.',
178
- '- Keep repo-root `_artifacts` as the reviewed plan.',
179
- '',
180
- 'If maintainer confirms updates:',
181
- '1. Update the relevant `_artifacts/*domain_map.yaml` or `_artifacts/*skill_tree.yaml`.',
182
- '2. Update or create `SKILL.md` files only for confirmed coverage changes.',
183
- '3. Keep `sources` aligned between artifact skill entries and SKILL frontmatter.',
184
- '4. Bump `library_version` only for skills whose covered source package version changed.',
185
- '5. Run `npx @tanstack/intent@latest validate` on touched skill directories.',
186
- '6. Summarize every package as one of: existing-skill coverage, new skill, ignored, or deferred.',
187
- ].join('\n')
188
-
189
- const body = [
190
- '## Intent Skill Review Needed',
191
- '',
192
- 'Intent found skills, artifact coverage, or workspace package coverage that need maintainer review.',
193
- '',
194
- '### Summary',
195
- '',
196
- '| Signal | Count |',
197
- '| --- | ---: |',
198
- ...signalRows,
199
- '',
200
- '### Review Items',
201
- '',
202
- '| Signal | Subject | Library | Reason |',
203
- '| --- | --- | --- | --- |',
204
- ...itemRows,
205
- '',
206
- '### Agent Prompt',
207
- '',
208
- 'Paste this into your coding agent:',
209
- '',
210
- '```text',
211
- prompt,
212
- '```',
213
- '',
214
- 'This PR is a review reminder only. It does not update skills automatically.',
215
- ].join('\n')
216
-
217
- fs.writeFileSync('pr-body.md', body + '\n')
218
- fs.writeFileSync(process.env.GITHUB_STEP_SUMMARY, body + '\n')
219
- NODE
73
+ intent stale --github-review --package-label "{{PACKAGE_LABEL}}"
220
74
 
221
75
  - name: Open or update review PR
222
76
  if: steps.stale.outputs.has_review == 'true'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/intent",
3
- "version": "0.0.33",
3
+ "version": "0.0.35",
4
4
  "description": "Ship compositional knowledge for AI coding agents alongside your npm packages",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,52 +0,0 @@
1
- # validate-skills.yml — Drop this into your library repo's .github/workflows/
2
- #
3
- # Validates skill files on PRs that touch the skills/ directory.
4
- # Ensures frontmatter is correct, names match paths, and files stay under
5
- # the 500-line limit.
6
-
7
- name: Validate Skills
8
-
9
- on:
10
- pull_request:
11
- paths:
12
- - 'skills/**'
13
- - '**/skills/**'
14
-
15
- jobs:
16
- validate:
17
- name: Validate skill files
18
- runs-on: ubuntu-latest
19
- steps:
20
- - name: Checkout
21
- uses: actions/checkout@v4
22
-
23
- - name: Setup Node
24
- uses: actions/setup-node@v4
25
- with:
26
- node-version: 20
27
-
28
- - name: Install intent CLI
29
- run: npm install -g @tanstack/intent
30
-
31
- - name: Find and validate skills
32
- run: |
33
- # Find all directories containing SKILL.md files
34
- SKILLS_DIR=""
35
- if [ -d "skills" ]; then
36
- SKILLS_DIR="skills"
37
- elif [ -d "packages" ]; then
38
- # Monorepo — find skills/ under packages
39
- for dir in packages/*/skills; do
40
- if [ -d "$dir" ]; then
41
- echo "Validating $dir..."
42
- intent validate "$dir"
43
- fi
44
- done
45
- exit 0
46
- fi
47
-
48
- if [ -n "$SKILLS_DIR" ]; then
49
- intent validate "$SKILLS_DIR"
50
- else
51
- echo "No skills/ directory found — skipping validation."
52
- fi