@tanstack/intent 0.0.9 → 0.0.12

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
@@ -2,15 +2,21 @@
2
2
 
3
3
  Ship compositional knowledge for AI coding agents alongside your npm packages.
4
4
 
5
- Skills are npm packages of knowledge — encoding how tools work together, what patterns apply for which goals, and what to avoid. Skills travel with the tool via `npm update`, not the model's training cutoff.
5
+ ## The problem
6
6
 
7
- `@tanstack/intent` is the toolkit for generating, discovering, and maintaining skills for your library.
7
+ Your docs are good. Your types are solid. Your agent still gets it wrong.
8
8
 
9
- ## Install
9
+ Docs target humans who browse. Types check individual API calls but can't encode intent. Training data snapshots the ecosystem as it _was_, mixing versions without flagging which applies. Once a breaking change ships, models contain _both_ versions forever with no way to disambiguate.
10
10
 
11
- ```bash
12
- pnpm add -D @tanstack/intent
13
- ```
11
+ The ecosystem already moves toward agent-readable knowledge — Cursor rules, CLAUDE.md files, skills directories. But delivery is stuck in copy-paste: hunt for a community-maintained rules file, paste it into your config, repeat for every tool. No versioning, no update path, no staleness signal.
12
+
13
+ ## Skills: the fourth artifact
14
+
15
+ You ship code, docs, and types. Skills are the fourth artifact — knowledge encoded for the thing writing most of your code.
16
+
17
+ Skills are npm packages of knowledge — encoding how tools compose, which patterns fit which goals, and what to avoid. When a library ships skills using `@tanstack/intent`, that knowledge travels with the tool via `npm update` — not the model's training cutoff. Versioned knowledge the maintainer owns, updated when the package updates.
18
+
19
+ Each skill declares its source docs. When those docs change, the CLI flags the skill for review. One source of truth, one derived artifact that stays in sync.
14
20
 
15
21
  ## Quick Start
16
22
 
@@ -22,6 +28,8 @@ Set up skill-to-task mappings in your project's agent config files (CLAUDE.md, .
22
28
  npx @tanstack/intent install
23
29
  ```
24
30
 
31
+ No per-library setup. No hunting for rules files. Install the package, run `intent install`, and the agent understands the tool. Update the package, and skills update too.
32
+
25
33
  List available skills from installed packages:
26
34
 
27
35
  ```bash
@@ -44,23 +52,36 @@ Validate your skill files:
44
52
  npx @tanstack/intent validate
45
53
  ```
46
54
 
47
- Copy CI workflow templates into your repo:
55
+ Check for skills that have fallen behind their sources:
56
+
57
+ ```bash
58
+ npx @tanstack/intent stale
59
+ ```
60
+
61
+ Copy CI workflow templates into your repo so validation and staleness checks run on every push:
48
62
 
49
63
  ```bash
50
64
  npx @tanstack/intent setup
51
65
  ```
52
66
 
67
+ ## Keeping skills current
68
+
69
+ The real risk with any derived artifact is staleness. `npx @tanstack/intent stale` flags skills whose source docs have changed, and CI templates catch drift before it ships.
70
+
71
+ The feedback loop runs both directions. `npx @tanstack/intent 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 `npm update`.
72
+
53
73
  ## CLI Commands
54
74
 
55
- | Command | Description |
56
- | ----------------------- | --------------------------------------------------- |
57
- | `intent install` | Set up skill-to-task mappings in agent config files |
58
- | `intent list [--json]` | Discover intent-enabled packages |
59
- | `intent meta` | List meta-skills for library maintainers |
60
- | `intent scaffold` | Print the guided skill generation prompt |
61
- | `intent validate [dir]` | Validate SKILL.md files |
62
- | `intent setup` | Copy CI templates, generate shim, create labels |
63
- | `intent stale [--json]` | Check skills for version drift |
75
+ | Command | Description |
76
+ | ------------------------------------- | --------------------------------------------------- |
77
+ | `npx @tanstack/intent install` | Set up skill-to-task mappings in agent config files |
78
+ | `npx @tanstack/intent list [--json]` | Discover intent-enabled packages |
79
+ | `npx @tanstack/intent meta` | List meta-skills for library maintainers |
80
+ | `npx @tanstack/intent scaffold` | Print the guided skill generation prompt |
81
+ | `npx @tanstack/intent validate [dir]` | Validate SKILL.md files |
82
+ | `npx @tanstack/intent setup` | Copy CI templates, generate shim, create labels |
83
+ | `npx @tanstack/intent stale [--json]` | Check skills for version drift |
84
+ | `npx @tanstack/intent feedback` | Submit skill feedback |
64
85
 
65
86
  ## License
66
87
 
package/dist/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as parseFrontmatter, t as findSkillFiles } from "./utils-DH3jY3CI.mjs";
3
- import { t as scanForIntents } from "./scanner-OmHt14bs.mjs";
3
+ import { t as scanForIntents } from "./scanner-dIYdkHQ1.mjs";
4
4
  import { n as printSkillTree, r as printTable, t as computeSkillNameWidth } from "./display-D_XzuGnu.mjs";
5
5
  import { existsSync, readFileSync, readdirSync } from "node:fs";
6
6
  import { dirname, join, relative, sep } from "node:path";
@@ -101,7 +101,7 @@ function collectPackagingWarnings(root) {
101
101
  if (!pkgJson.bin?.intent) warnings.push("Missing \"bin\": { \"intent\": ... } entry in package.json");
102
102
  const shimJs = join(root, "bin", "intent.js");
103
103
  const shimMjs = join(root, "bin", "intent.mjs");
104
- if (!existsSync(shimJs) && !existsSync(shimMjs)) warnings.push("No bin/intent.js or bin/intent.mjs shim found (run: npx @tanstack/intent setup --shim)");
104
+ if (!existsSync(shimJs) && !existsSync(shimMjs)) warnings.push("No bin/intent.js or bin/intent.mjs shim found (run: npx @tanstack/intent add-library-bin)");
105
105
  const files = pkgJson.files;
106
106
  if (Array.isArray(files)) {
107
107
  if (!files.includes("skills")) warnings.push("\"skills\" is not in the \"files\" array — skills won't be published");
@@ -287,12 +287,11 @@ This produces: individual SKILL.md files.
287
287
 
288
288
  1. Run \`npx @tanstack/intent validate\` in each package directory
289
289
  2. Commit skills/ and artifacts
290
- 3. For each publishable package, run: \`npx @tanstack/intent setup --shim\`
291
- 4. Ensure each package has \`@tanstack/intent\` as a devDependency
292
- 5. Add \`"skills"\`, \`"bin"\` to the \`"files"\` array in each package.json
293
- 6. Add \`"!skills/_artifacts"\` to exclude artifacts from publishing
294
- 7. Create a \`feedback:<skill-name>\` label on the GitHub repo for each skill (use \`gh label create\`)
295
- 8. Add a README note: "If you use an AI agent, run \`npx @tanstack/intent install\`"
290
+ 3. For each publishable package, run: \`npx @tanstack/intent add-library-bin\`
291
+ 4. For each publishable package, run: \`npx @tanstack/intent edit-package-json\`
292
+ 5. Ensure each package has \`@tanstack/intent\` as a devDependency
293
+ 6. Create a \`feedback:<skill-name>\` label on the GitHub repo for each skill (use \`gh label create\`)
294
+ 7. Add a README note: "If you use an AI agent, run \`npx @tanstack/intent install\`"
296
295
  `;
297
296
  console.log(prompt);
298
297
  }
@@ -304,7 +303,9 @@ Usage:
304
303
  intent validate [<dir>] Validate skill files (default: skills/)
305
304
  intent install Print a skill that guides your coding agent to set up skill-to-task mappings
306
305
  intent scaffold Print maintainer scaffold prompt
307
- intent setup [--workflows] [--shim] [--all] Copy CI templates and generate shim
306
+ intent add-library-bin Generate bin/intent.{js,mjs} bridge file
307
+ intent edit-package-json Wire package.json (files, bin) for skill publishing
308
+ intent setup-github-actions Copy CI workflow templates to .github/workflows/
308
309
  intent stale Check skills for staleness`;
309
310
  const command = process.argv[2];
310
311
  const commandArgs = process.argv.slice(3);
@@ -371,7 +372,7 @@ skills:
371
372
  break;
372
373
  case "stale": {
373
374
  const { checkStaleness } = await import("./staleness-B5gUj7FR.mjs");
374
- const { scanForIntents: scanStale } = await import("./scanner-DkShtCWX.mjs");
375
+ const { scanForIntents: scanStale } = await import("./scanner-DQeiZRzp.mjs");
375
376
  let staleResult;
376
377
  try {
377
378
  staleResult = await scanStale();
@@ -402,9 +403,19 @@ skills:
402
403
  }
403
404
  break;
404
405
  }
405
- case "setup": {
406
- const { runSetup } = await import("./setup.mjs");
407
- runSetup(process.cwd(), getMetaDir(), commandArgs);
406
+ case "add-library-bin": {
407
+ const { runAddLibraryBin } = await import("./setup.mjs");
408
+ runAddLibraryBin(process.cwd());
409
+ break;
410
+ }
411
+ case "edit-package-json": {
412
+ const { runEditPackageJson } = await import("./setup.mjs");
413
+ runEditPackageJson(process.cwd());
414
+ break;
415
+ }
416
+ case "setup-github-actions": {
417
+ const { runSetupGithubActions } = await import("./setup.mjs");
418
+ runSetupGithubActions(process.cwd(), getMetaDir());
408
419
  break;
409
420
  }
410
421
  default:
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as IntentProjectConfig, c as ScanResult, d as StalenessReport, i as IntentPackage, l as SkillEntry, n as FeedbackPayload, o as MetaFeedbackPayload, r as IntentConfig, s as MetaSkillName, t as AgentName, u as SkillStaleness } from "./types-BmnI8kFB.mjs";
2
- import { n as runSetup } from "./setup-BJ4giTKA.mjs";
2
+ import { a as runEditPackageJson, i as runAddLibraryBin, n as EditPackageJsonResult, o as runSetupGithubActions, r as SetupGithubActionsResult, t as AddLibraryBinResult } from "./setup-CANkTz55.mjs";
3
3
 
4
4
  //#region src/scanner.d.ts
5
5
  declare function scanForIntents(root?: string): Promise<ScanResult>;
@@ -44,4 +44,4 @@ declare function findSkillFiles(dir: string): string[];
44
44
  */
45
45
  declare function parseFrontmatter(filePath: string): Record<string, unknown> | null;
46
46
  //#endregion
47
- export { type AgentName, type FeedbackPayload, type IntentConfig, type IntentPackage, type IntentProjectConfig, type MetaFeedbackPayload, type MetaSkillName, type ScanResult, type SkillEntry, type SkillStaleness, type StalenessReport, checkStaleness, containsSecrets, findSkillFiles, hasGhCli, metaToMarkdown, parseFrontmatter, resolveFrequency, runSetup, scanForIntents, submitFeedback, submitMetaFeedback, toMarkdown, validateMetaPayload, validatePayload };
47
+ export { type AddLibraryBinResult, type AgentName, type EditPackageJsonResult, type FeedbackPayload, type IntentConfig, type IntentPackage, type IntentProjectConfig, type MetaFeedbackPayload, type MetaSkillName, type ScanResult, type SetupGithubActionsResult, type SkillEntry, type SkillStaleness, type StalenessReport, checkStaleness, containsSecrets, findSkillFiles, hasGhCli, metaToMarkdown, parseFrontmatter, resolveFrequency, runAddLibraryBin, runEditPackageJson, runSetupGithubActions, scanForIntents, submitFeedback, submitMetaFeedback, toMarkdown, validateMetaPayload, validatePayload };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { n as parseFrontmatter, t as findSkillFiles } from "./utils-DH3jY3CI.mjs";
2
- import { t as scanForIntents } from "./scanner-OmHt14bs.mjs";
2
+ import { t as scanForIntents } from "./scanner-dIYdkHQ1.mjs";
3
3
  import { t as checkStaleness } from "./staleness-lP6B0O4z.mjs";
4
- import { t as runSetup } from "./setup-DGvdyKEq.mjs";
4
+ import { n as runEditPackageJson, r as runSetupGithubActions, t as runAddLibraryBin } from "./setup-BYOg-Ii-.mjs";
5
5
  import { readFileSync, writeFileSync } from "node:fs";
6
6
  import { join } from "node:path";
7
7
  import { execFileSync, execSync } from "node:child_process";
@@ -246,4 +246,4 @@ function submitMetaFeedback(payload, opts) {
246
246
  }
247
247
 
248
248
  //#endregion
249
- export { checkStaleness, containsSecrets, findSkillFiles, hasGhCli, metaToMarkdown, parseFrontmatter, resolveFrequency, runSetup, scanForIntents, submitFeedback, submitMetaFeedback, toMarkdown, validateMetaPayload, validatePayload };
249
+ export { checkStaleness, containsSecrets, findSkillFiles, hasGhCli, metaToMarkdown, parseFrontmatter, resolveFrequency, runAddLibraryBin, runEditPackageJson, runSetupGithubActions, scanForIntents, submitFeedback, submitMetaFeedback, toMarkdown, validateMetaPayload, validatePayload };
@@ -0,0 +1,4 @@
1
+ import "./utils-DH3jY3CI.mjs";
2
+ import { t as scanForIntents } from "./scanner-dIYdkHQ1.mjs";
3
+
4
+ export { scanForIntents };
@@ -125,7 +125,7 @@ async function scanForIntents(root) {
125
125
  };
126
126
  }
127
127
  for (const entry of topEntries) {
128
- if (!entry.isDirectory()) continue;
128
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
129
129
  const dirPath = join(nodeModulesDir, entry.name);
130
130
  if (entry.name.startsWith("@")) {
131
131
  let scopedEntries;
@@ -138,7 +138,7 @@ async function scanForIntents(root) {
138
138
  continue;
139
139
  }
140
140
  for (const scoped of scopedEntries) {
141
- if (!scoped.isDirectory()) continue;
141
+ if (!scoped.isDirectory() && !scoped.isSymbolicLink()) continue;
142
142
  packageDirs.push({ dirPath: join(dirPath, scoped.name) });
143
143
  }
144
144
  } else if (!entry.name.startsWith(".")) packageDirs.push({ dirPath });
@@ -0,0 +1,197 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ //#region src/setup.ts
5
+ function detectVars(root) {
6
+ const pkgPath = join(root, "package.json");
7
+ let pkgJson = {};
8
+ try {
9
+ pkgJson = JSON.parse(readFileSync(pkgPath, "utf8"));
10
+ } catch (err) {
11
+ if (!(err && typeof err === "object" && "code" in err && err.code === "ENOENT")) console.error(`Warning: could not read ${pkgPath}: ${err instanceof Error ? err.message : err}`);
12
+ }
13
+ const name = typeof pkgJson.name === "string" ? pkgJson.name : "unknown";
14
+ const intent = pkgJson.intent;
15
+ const repo = typeof intent?.repo === "string" ? intent.repo : name.replace(/^@/, "").replace(/\//, "/");
16
+ const docs = typeof intent?.docs === "string" ? intent.docs : "docs/";
17
+ let srcPath = `packages/${name.replace(/^@[^/]+\//, "")}/src/**`;
18
+ if (existsSync(join(root, "src"))) srcPath = "src/**";
19
+ return {
20
+ PACKAGE_NAME: name,
21
+ REPO: repo,
22
+ DOCS_PATH: docs.endsWith("**") ? docs : docs.replace(/\/$/, "") + "/**",
23
+ SRC_PATH: srcPath
24
+ };
25
+ }
26
+ function applyVars(content, vars) {
27
+ return content.replace(/\{\{PACKAGE_NAME\}\}/g, vars.PACKAGE_NAME).replace(/\{\{REPO\}\}/g, vars.REPO).replace(/\{\{DOCS_PATH\}\}/g, vars.DOCS_PATH).replace(/\{\{SRC_PATH\}\}/g, vars.SRC_PATH);
28
+ }
29
+ function copyTemplates(srcDir, destDir, vars) {
30
+ const copied = [];
31
+ const skipped = [];
32
+ if (!existsSync(srcDir)) return {
33
+ copied,
34
+ skipped
35
+ };
36
+ mkdirSync(destDir, { recursive: true });
37
+ for (const entry of readdirSync(srcDir)) {
38
+ const srcPath = join(srcDir, entry);
39
+ const destPath = join(destDir, entry);
40
+ if (existsSync(destPath)) {
41
+ skipped.push(destPath);
42
+ continue;
43
+ }
44
+ writeFileSync(destPath, applyVars(readFileSync(srcPath, "utf8"), vars));
45
+ copied.push(destPath);
46
+ }
47
+ return {
48
+ copied,
49
+ skipped
50
+ };
51
+ }
52
+ function getShimContent(ext) {
53
+ return `#!/usr/bin/env node
54
+ // Auto-generated by @tanstack/intent setup
55
+ // Exposes the intent end-user CLI for consumers of this library.
56
+ // Commit this file, then add to your package.json:
57
+ // "bin": { "intent": "./bin/intent.${ext}" }
58
+ await import('@tanstack/intent/intent-library')
59
+ `;
60
+ }
61
+ function detectShimExtension(root) {
62
+ try {
63
+ if (JSON.parse(readFileSync(join(root, "package.json"), "utf8")).type === "module") return "js";
64
+ } catch (err) {
65
+ if (!(err && typeof err === "object" && "code" in err && err.code === "ENOENT")) console.error(`Warning: could not read package.json: ${err instanceof Error ? err.message : err}`);
66
+ }
67
+ return "mjs";
68
+ }
69
+ function findExistingShim(root) {
70
+ const shimJs = join(root, "bin", "intent.js");
71
+ if (existsSync(shimJs)) return shimJs;
72
+ const shimMjs = join(root, "bin", "intent.mjs");
73
+ if (existsSync(shimMjs)) return shimMjs;
74
+ return null;
75
+ }
76
+ function runAddLibraryBin(root) {
77
+ const result = {
78
+ shim: null,
79
+ skipped: null
80
+ };
81
+ const existingShim = findExistingShim(root);
82
+ if (existingShim) {
83
+ result.skipped = existingShim;
84
+ console.log(` Already exists: ${existingShim}`);
85
+ return result;
86
+ }
87
+ const ext = detectShimExtension(root);
88
+ const shimPath = join(root, "bin", `intent.${ext}`);
89
+ mkdirSync(join(root, "bin"), { recursive: true });
90
+ writeFileSync(shimPath, getShimContent(ext));
91
+ result.shim = shimPath;
92
+ console.log(`✓ Generated intent shim: ${shimPath}`);
93
+ console.log(`\n Run \`npx @tanstack/intent edit-package-json\` to wire package.json.`);
94
+ return result;
95
+ }
96
+ function runEditPackageJson(root) {
97
+ const result = {
98
+ added: [],
99
+ alreadyPresent: []
100
+ };
101
+ const pkgPath = join(root, "package.json");
102
+ if (!existsSync(pkgPath)) {
103
+ console.error("No package.json found in " + root);
104
+ process.exitCode = 1;
105
+ return result;
106
+ }
107
+ const raw = readFileSync(pkgPath, "utf8");
108
+ let pkg;
109
+ try {
110
+ pkg = JSON.parse(raw);
111
+ } catch (err) {
112
+ const detail = err instanceof SyntaxError ? err.message : String(err);
113
+ console.error(`Failed to parse ${pkgPath}: ${detail}`);
114
+ return result;
115
+ }
116
+ const indentMatch = raw.match(/^(\s+)"/m);
117
+ const indentSize = indentMatch?.[1] ? indentMatch[1].length : 2;
118
+ if (!Array.isArray(pkg.files)) pkg.files = [];
119
+ const files = pkg.files;
120
+ const requiredFiles = (() => {
121
+ let dir = join(root, "..");
122
+ for (let i = 0; i < 5; i++) {
123
+ const parentPkg = join(dir, "package.json");
124
+ if (existsSync(parentPkg)) {
125
+ try {
126
+ const parent = JSON.parse(readFileSync(parentPkg, "utf8"));
127
+ if (Array.isArray(parent.workspaces) || parent.workspaces?.packages) return true;
128
+ } catch {}
129
+ return false;
130
+ }
131
+ const next = join(dir, "..");
132
+ if (next === dir) break;
133
+ dir = next;
134
+ }
135
+ return false;
136
+ })() ? ["skills", "bin"] : [
137
+ "skills",
138
+ "bin",
139
+ "!skills/_artifacts"
140
+ ];
141
+ for (const entry of requiredFiles) if (files.includes(entry)) result.alreadyPresent.push(`files: "${entry}"`);
142
+ else {
143
+ files.push(entry);
144
+ result.added.push(`files: "${entry}"`);
145
+ }
146
+ const existingShim = findExistingShim(root);
147
+ let ext;
148
+ if (existingShim) ext = existingShim.endsWith(".mjs") ? "mjs" : "js";
149
+ else ext = pkg.type === "module" ? "js" : "mjs";
150
+ const shimRelative = `./bin/intent.${ext}`;
151
+ if (typeof pkg.bin === "object" && pkg.bin !== null) {
152
+ const binObj = pkg.bin;
153
+ if (binObj.intent) result.alreadyPresent.push(`bin.intent`);
154
+ else {
155
+ binObj.intent = shimRelative;
156
+ result.added.push(`bin.intent: "${shimRelative}"`);
157
+ }
158
+ } else if (!pkg.bin) {
159
+ pkg.bin = { intent: shimRelative };
160
+ result.added.push(`bin.intent: "${shimRelative}"`);
161
+ } else if (typeof pkg.bin === "string") {
162
+ const pkgName = typeof pkg.name === "string" ? pkg.name.replace(/^@[^/]+\//, "") : "unknown";
163
+ pkg.bin = {
164
+ [pkgName]: pkg.bin,
165
+ intent: shimRelative
166
+ };
167
+ result.added.push(`bin.intent: "${shimRelative}" (converted bin from string to object)`);
168
+ }
169
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, indentSize) + "\n");
170
+ for (const a of result.added) console.log(`✓ Added ${a}`);
171
+ for (const a of result.alreadyPresent) console.log(` Already present: ${a}`);
172
+ return result;
173
+ }
174
+ function runSetupGithubActions(root, metaDir) {
175
+ const vars = detectVars(root);
176
+ const result = {
177
+ workflows: [],
178
+ skipped: []
179
+ };
180
+ const { copied, skipped } = copyTemplates(join(metaDir, "templates", "workflows"), join(root, ".github", "workflows"), vars);
181
+ result.workflows = copied;
182
+ result.skipped = skipped;
183
+ for (const f of result.workflows) console.log(`✓ Copied workflow: ${f}`);
184
+ for (const f of result.skipped) console.log(` Already exists: ${f}`);
185
+ if (result.workflows.length === 0 && result.skipped.length === 0) console.log("No templates directory found. Is @tanstack/intent installed?");
186
+ else if (result.workflows.length > 0) {
187
+ console.log(`\nTemplate variables applied:`);
188
+ console.log(` Package: ${vars.PACKAGE_NAME}`);
189
+ console.log(` Repo: ${vars.REPO}`);
190
+ console.log(` Docs: ${vars.DOCS_PATH}`);
191
+ console.log(` Src: ${vars.SRC_PATH}`);
192
+ }
193
+ return result;
194
+ }
195
+
196
+ //#endregion
197
+ export { runEditPackageJson as n, runSetupGithubActions as r, runAddLibraryBin as t };
@@ -0,0 +1,18 @@
1
+ //#region src/setup.d.ts
2
+ interface AddLibraryBinResult {
3
+ shim: string | null;
4
+ skipped: string | null;
5
+ }
6
+ interface EditPackageJsonResult {
7
+ added: string[];
8
+ alreadyPresent: string[];
9
+ }
10
+ interface SetupGithubActionsResult {
11
+ workflows: string[];
12
+ skipped: string[];
13
+ }
14
+ declare function runAddLibraryBin(root: string): AddLibraryBinResult;
15
+ declare function runEditPackageJson(root: string): EditPackageJsonResult;
16
+ declare function runSetupGithubActions(root: string, metaDir: string): SetupGithubActionsResult;
17
+ //#endregion
18
+ export { runEditPackageJson as a, runAddLibraryBin as i, EditPackageJsonResult as n, runSetupGithubActions as o, SetupGithubActionsResult as r, AddLibraryBinResult as t };
package/dist/setup.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as runSetup, t as SetupResult } from "./setup-BJ4giTKA.mjs";
2
- export { SetupResult, runSetup };
1
+ import { a as runEditPackageJson, i as runAddLibraryBin, n as EditPackageJsonResult, o as runSetupGithubActions, r as SetupGithubActionsResult, t as AddLibraryBinResult } from "./setup-CANkTz55.mjs";
2
+ export { AddLibraryBinResult, EditPackageJsonResult, SetupGithubActionsResult, runAddLibraryBin, runEditPackageJson, runSetupGithubActions };
package/dist/setup.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as runSetup } from "./setup-DGvdyKEq.mjs";
1
+ import { n as runEditPackageJson, r as runSetupGithubActions, t as runAddLibraryBin } from "./setup-BYOg-Ii-.mjs";
2
2
 
3
- export { runSetup };
3
+ export { runAddLibraryBin, runEditPackageJson, runSetupGithubActions };
@@ -42,9 +42,12 @@ path applies (see below).
42
42
 
43
43
  ### Lightweight path (small libraries)
44
44
 
45
- After Phase 1, if the library has **fewer than 5 client-facing skill
46
- areas** (e.g. a focused utility library, a single-purpose tool, or a
47
- library with only 2–3 distinct developer tasks), use a compressed flow:
45
+ After Phase 1, decide whether the library warrants the full five-phase
46
+ flow or the compressed flow below. This is a judgment call lean toward
47
+ full discovery unless the library is obviously small (single-purpose
48
+ utility, 2–3 distinct developer tasks max). Use a compressed flow when
49
+ the skill surface is small enough that two interview rounds would be
50
+ redundant:
48
51
 
49
52
  1. **Phase 1** — Quick scan (same as full flow)
50
53
  2. **Phase 2+4 combined** — Single interview round. Combine the
@@ -59,6 +62,42 @@ The lightweight path produces identical output artifacts (domain_map.yaml
59
62
  and skill_spec.md). It just avoids two separate interview rounds when the
60
63
  library is small enough that one round covers everything.
61
64
 
65
+ ### Hard rules — interview phases are mandatory and interactive
66
+
67
+ These rules override any other reasoning. No exceptions.
68
+
69
+ 1. **Phases 2 and 4 are interactive interviews conducted with the
70
+ maintainer.** You must ask the questions specified in each sub-section
71
+ and wait for the maintainer's response before continuing. Documentation,
72
+ source code, and other automated analysis are NOT substitutes for the
73
+ maintainer's answers.
74
+ 2. **Every question in Phases 2 and 4 must be asked as an open-ended
75
+ question and sent as a message to the maintainer.** You must then
76
+ STOP and WAIT for their reply. Do not answer your own questions. Do
77
+ not infer answers from documentation. Do not skip questions because
78
+ you believe you already know the answer.
79
+ 3. **Do not convert open-ended questions into multiple-choice,
80
+ yes/no, or confirmation prompts.** The question templates in each
81
+ sub-section are open-ended by design. Present them as open-ended
82
+ questions. The maintainer's unprompted answers surface knowledge that
83
+ pre-structured options suppress.
84
+ 4. **Minimum question counts are enforced.** Each sub-section specifies
85
+ a question count range (e.g. "2–4 questions"). You must ask at least
86
+ the minimum number. Asking zero questions in any sub-section is a
87
+ protocol violation.
88
+ 5. **STOP gates are mandatory.** At the boundaries marked `── STOP ──`
89
+ below, you must halt execution and wait for the maintainer's response
90
+ or acknowledgment before proceeding. Do not continue past a STOP gate
91
+ in the same message.
92
+ 6. **If the maintainer asks to skip an interview phase**, explain the
93
+ value of the phase and what will be lost. Proceed with skipping only
94
+ if they confirm a second time.
95
+ 7. **Rich documentation makes interviews MORE valuable, not less.**
96
+ When docs are comprehensive, the interview surfaces what docs miss:
97
+ implicit knowledge, AI-specific failure modes, undocumented tradeoffs,
98
+ and the maintainer's prioritization of what matters most. Never
99
+ rationalize skipping interviews because documentation is thorough.
100
+
62
101
  ---
63
102
 
64
103
  ## Phase 1 — Quick scan (autonomous, ~10 minutes)
@@ -105,20 +144,37 @@ Log (but do not group yet):
105
144
  - Which frameworks it supports
106
145
  - Any existing skill files, agent configs, or intents
107
146
  - Whether the library is a monorepo and which packages matter
147
+ - Peer dependency constraints — read `peerDependencies` and
148
+ `peerDependenciesMeta` from each client-facing package.json to
149
+ understand version ranges and optional integrations early
150
+
151
+ Present your initial impressions to the maintainer as a brief summary
152
+ (3–5 bullets). This orients them on what you found and primes them for
153
+ the interview.
154
+
155
+ **── STOP ── Do not proceed to Phase 2 until the maintainer has
156
+ acknowledged your summary or responded.**
108
157
 
109
158
  ---
110
159
 
111
- ## Phase 2 — High-level interview
160
+ ## Phase 2 — High-level interview (interactive — requires maintainer)
112
161
 
113
162
  The maintainer's mental model of developer tasks IS the skill map. Your
114
163
  job in this phase is to extract it — not to propose your own structure.
115
164
 
165
+ You must ask the questions below to the maintainer and wait for their
166
+ responses. Do not infer answers from documentation or source code.
167
+
116
168
  ### Rules for Phase 2
117
169
 
118
170
  1. One topic per message for open-ended questions. You may batch 2–3
119
171
  yes/no or short-confirmation questions together.
120
- 2. Take notes silently. Do not summarize back unless asked.
121
- 3. If the maintainer gives a short answer, probe deeper before moving on.
172
+ 2. Ask each question as written (you may adapt phrasing to context, but
173
+ keep questions open-ended never convert to multiple-choice).
174
+ 3. Wait for the maintainer's response after each question before asking
175
+ the next.
176
+ 4. Take notes silently. Do not summarize back unless asked.
177
+ 5. If the maintainer gives a short answer, probe deeper before moving on.
122
178
 
123
179
  ### 2a — Developer tasks (2–4 questions)
124
180
 
@@ -135,6 +191,13 @@ Follow up to enumerate distinct tasks:
135
191
  > things like 'set up the client', 'implement auth', 'debug sync issues'
136
192
  > — each one a separate moment where they'd want focused guidance."
137
193
 
194
+ For monorepo libraries, also ask about cross-package tasks:
195
+
196
+ > "Are there tasks that touch multiple packages in your monorepo? For
197
+ > example, a getting-started flow that requires imports from both the
198
+ > client and server packages? I want to make sure skills that span
199
+ > package boundaries are captured correctly."
200
+
138
201
  ### 2b — Developer journeys (1–2 questions)
139
202
 
140
203
  Surface lifecycle/journey skills that cross-cut task areas:
@@ -144,13 +207,22 @@ Surface lifecycle/journey skills that cross-cut task areas:
144
207
  > a migrate-from-v4 walkthrough. Which of these exist in your docs
145
208
  > or would be valuable as standalone skills?"
146
209
 
147
- ### 2c — Composition and ecosystem (1–2 questions)
210
+ ### 2c — Composition and ecosystem (1–3 questions)
148
211
 
149
212
  > "Which other libraries does yours compose with most often? Are there
150
213
  > integration patterns important enough to warrant their own skill —
151
214
  > for example, using your library with [framework/ORM/router]?"
152
215
 
153
- ### 2d Confirm initial skill map
216
+ > "Are there tasks that developers might expect your library to handle,
217
+ > but that are actually handled by a companion library? Which tasks
218
+ > should we explicitly exclude from your library's skills?"
219
+
220
+ ### 2d — Exclude experimental features (1 question)
221
+
222
+ > "Are there any features that are experimental, unstable, or not yet
223
+ > ready to document for agents? We'll exclude these from the skill set."
224
+
225
+ ### 2e — Confirm initial skill map
154
226
 
155
227
  Synthesize what you heard into a proposed skill list and present it:
156
228
 
@@ -158,6 +230,9 @@ Synthesize what you heard into a proposed skill list and present it:
158
230
  > [enumerate skills with one-line descriptions]. Does this match how
159
231
  > you think about your library? What would you add, remove, or rename?"
160
232
 
233
+ **── STOP ── Do not proceed to Phase 3 until the maintainer has
234
+ reviewed and confirmed (or corrected) the skill list.**
235
+
161
236
  ---
162
237
 
163
238
  ## Phase 3 — Deep read (autonomous)
@@ -170,7 +245,14 @@ gotchas.
170
245
 
171
246
  Read in this order. Each step builds context for the next.
172
247
 
173
- 1. **Every narrative guide** the how-to content, not API reference tables
248
+ Before starting, list every file in the docs directory (and subdirectories).
249
+ Use this list as a checklist — every narrative file must be read. Do not
250
+ sample a subset and extrapolate.
251
+
252
+ 1. **Narrative guides** — read as many as needed to build confidence in
253
+ your understanding. Prioritize getting-started, migration, and guides
254
+ covering the skill areas from Phase 2. Skip exhaustive reading of large
255
+ online-only doc sets.
174
256
  2. **Migration guides** — highest-yield source for failure modes; every
175
257
  breaking change is exactly what agents trained on older versions produce
176
258
  3. **API reference** — scan for exports, type signatures, option shapes
@@ -417,20 +499,35 @@ Present the draft to the maintainer before starting Phase 4:
417
499
  > [N] skills and [M] failure modes. I've flagged [K] specific gaps where
418
500
  > I need your input."
419
501
 
502
+ Include the full draft domain_map.yaml in your message so the maintainer
503
+ can review it. Also include a checklist of all docs files you read.
504
+
505
+ **── STOP ── Do not proceed to Phase 4 until the maintainer has
506
+ reviewed the draft and responded. Their feedback on the draft informs
507
+ the detail interview questions.**
508
+
420
509
  ---
421
510
 
422
- ## Phase 4 — Detail interview (builds on Phase 1–3)
511
+ ## Phase 4 — Detail interview (interactive requires maintainer)
423
512
 
424
513
  You have the maintainer's task map and a deep read. The interview now
425
514
  fills gaps, validates your understanding, and surfaces implicit knowledge.
426
515
 
516
+ You must ask the questions below to the maintainer and wait for their
517
+ responses. Do not infer answers from documentation or source code —
518
+ even for gaps you think you can answer from your reading.
519
+
427
520
  ### Rules for Phase 4
428
521
 
429
522
  1. One topic per message for open-ended questions. You may batch 2–3
430
523
  yes/no or short-confirmation questions together.
431
- 2. Each question must reference something specific from your reading.
432
- 3. If the maintainer gives a short answer, probe deeper before moving on.
433
- 4. Take notes silently. Do not summarize back unless asked.
524
+ 2. Ask each question as written (you may adapt phrasing to context, but
525
+ keep questions open-ended never convert to multiple-choice).
526
+ 3. Each question must reference something specific from your reading.
527
+ 4. Wait for the maintainer's response after each question before asking
528
+ the next.
529
+ 5. If the maintainer gives a short answer, probe deeper before moving on.
530
+ 6. Take notes silently. Do not summarize back unless asked.
434
531
 
435
532
  ### 4a — Draft review (2–3 questions)
436
533
 
@@ -538,6 +635,10 @@ Merge interview findings into the draft. For each interview answer:
538
635
  4. If it reveals a new skill — add it
539
636
  5. If it fills a gap — remove from gaps section
540
637
 
638
+ Validate the domain_map.yaml by parsing it with a YAML parser. Check for
639
+ duplicate keys, invalid syntax, and structural correctness. Fix any issues
640
+ before presenting the final artifact.
641
+
541
642
  Update `status: draft` to `status: reviewed`.
542
643
 
543
644
  ---
@@ -580,6 +681,9 @@ skills:
580
681
  domain: '[parent domain slug]'
581
682
  description: '[what a developer is doing — matches a specific task/moment]'
582
683
  type: '[core | framework | lifecycle | composition]'
684
+ packages: # required for monorepo; omit for single-package libraries
685
+ - '[primary package name]'
686
+ - '[secondary package name, if skill spans multiple packages]'
583
687
  covers:
584
688
  - '[API/hook/concept 1]'
585
689
  - '[API/hook/concept 2]'
@@ -710,6 +814,11 @@ not promotional.]
710
814
  | ------------------------------------- | -------------------------------------------------------------------------- |
711
815
  | Quick scan before interview | Never interview without at least reading README and package structure |
712
816
  | High-level interview before deep read | The maintainer's task map informs what you read deeply |
817
+ | **Interview phases are interactive** | Phases 2 and 4 require sending questions to the maintainer and waiting |
818
+ | **Docs are not a substitute** | Documentation cannot replace maintainer answers — even comprehensive docs |
819
+ | **Open-ended questions stay open** | Never convert interview questions to multiple-choice or yes/no |
820
+ | **Minimum question counts enforced** | Each sub-section's minimum count must be met; zero questions = violation |
821
+ | **STOP gates are mandatory** | Do not proceed past a STOP gate without maintainer response |
713
822
  | Batch only confirmations | Yes/no questions may batch 2–3; open-ended questions get their own message |
714
823
  | Questions reference findings | No generic questions — cite what you found |
715
824
  | Skills are task-focused | Each skill matches a developer moment, not a conceptual area |
@@ -719,6 +828,7 @@ not promotional.]
719
828
  | No marketing prose | Library description is factual, not promotional |
720
829
  | domain_map.yaml is valid YAML | Parseable by any YAML parser |
721
830
  | Draft before detail interview | Present draft for review before Phase 4 |
831
+ | **Draft reviewed before Phase 4** | Maintainer must acknowledge or respond to draft before detail interview |
722
832
  | Agent-specific failures probed | Always ask AI-agent-specific questions in Phase 4c |
723
833
  | Compositions discovered from code | Scan peer deps and examples before asking composition questions |
724
834
  | Cross-skill failure modes tagged | Failure modes spanning skills list all relevant slugs |
@@ -727,6 +837,7 @@ not promotional.]
727
837
  | Dense surfaces flagged | Topics with >10 patterns noted as reference_candidates |
728
838
  | Lifecycle skills considered | Suggest journey skills when docs have the material |
729
839
  | Cross-references mapped | Skills that illuminate each other get "See also" pointers |
840
+ | **All docs files read** | List docs directory contents and read every narrative file — no sampling |
730
841
 
731
842
  ---
732
843
 
@@ -736,49 +847,16 @@ This skill is designed to produce consistent results across Claude, GPT-4+,
736
847
  Gemini, and open-source models. To achieve this:
737
848
 
738
849
  - All instructions use imperative sentences, not suggestions
850
+ - Interview phases use explicit STOP gates to prevent models from
851
+ continuing autonomously past interactive checkpoints
852
+ - Hard rules at the top override any model tendency to rationalize
853
+ skipping interactive phases when documentation is available
854
+ - Open-ended questions are explicitly protected from conversion to
855
+ multiple-choice or confirmation prompts, which models default to
856
+ when they have enough context to pre-populate answers
739
857
  - Output formats use YAML (universally parsed) and Markdown tables
740
858
  (universally rendered)
741
859
  - Examples use concrete values, not placeholders like "[your value here]"
742
860
  - Section boundaries use Markdown headers (##) for navigation and --- for
743
861
  phase separation
744
862
  - No model-specific features (no XML tags in output, no tool_use assumptions)
745
-
746
- ---
747
-
748
- ## Meta-skill feedback (alpha default)
749
-
750
- After finalizing artifacts, create or update a single Markdown feedback draft
751
- for all three meta skills used in scaffolding. Use this exact template and
752
- fill only the **Domain Discovery** section now. Leave the other sections in
753
- place for later steps to complete.
754
-
755
- ```
756
- # Intent Meta Skill Feedback
757
-
758
- ## Domain Discovery
759
- - What worked well:
760
- - What was confusing or missing:
761
- - Suggestions for improvement:
762
- - Overall rating: good | mixed | bad
763
-
764
- ## Tree Generator
765
- - What worked well:
766
- - What was confusing or missing:
767
- - Suggestions for improvement:
768
- - Overall rating: good | mixed | bad
769
-
770
- ## Generate Skill
771
- - What worked well:
772
- - What was confusing or missing:
773
- - Suggestions for improvement:
774
- - Overall rating: good | mixed | bad
775
-
776
- ## Context (optional)
777
- - Library:
778
- - Repo:
779
- - Docs:
780
- - Notes:
781
- ```
782
-
783
- Do not submit feedback yet. Tell the maintainer to carry this draft forward
784
- to the next meta skill step.
@@ -17,6 +17,12 @@ Run this at the end of any session where you loaded one or more SKILL.md files.
17
17
  The goal is to capture what worked, what didn't, and what was missing — so skill
18
18
  maintainers can improve future versions.
19
19
 
20
+ This skill also covers **meta-skill feedback** — feedback about the scaffolding
21
+ process itself. When invoked after running domain-discovery, tree-generator, and
22
+ generate-skill, treat those three meta skills as the "skills" being evaluated.
23
+ Capture what worked and what didn't in each scaffolding phase so the meta skills
24
+ can be improved.
25
+
20
26
  ---
21
27
 
22
28
  ## Phase 1 — Automated Signal Collection
@@ -145,6 +151,27 @@ Determine the target repo from the skill's package. The repo is typically
145
151
  derivable from the `repository` field in the package's `package.json`, or
146
152
  from the `sources` field in the SKILL.md frontmatter.
147
153
 
154
+ ### Privacy check
155
+
156
+ Before submitting, determine whether the user's project is public or private.
157
+ Check with `gh repo view --json visibility` or look for a `private` field in
158
+ the project's `package.json`. If you can't determine visibility, assume private.
159
+
160
+ **Private repos:** Feedback is submitted to a public issue tracker, so it must
161
+ not contain project-specific details. Before submission:
162
+
163
+ 1. Strip any project-specific code, file paths, internal API names, service
164
+ URLs, or business logic from all fields
165
+ 2. Rewrite the "Task" field to describe the _type_ of task generically
166
+ (e.g. "set up authenticated data fetching" not "set up auth for our
167
+ internal billing API at api.acme.corp/billing")
168
+ 3. Rewrite "What Failed" and "Missing" entries to reference only the
169
+ skill's own APIs and patterns, not the user's code
170
+ 4. Show the sanitized feedback to the user and ask them to confirm it's
171
+ safe to submit before proceeding
172
+
173
+ **Public repos:** No sanitization needed. Proceed directly to submission.
174
+
148
175
  ### If `gh` CLI is available
149
176
 
150
177
  Submit directly as a GitHub issue:
@@ -79,6 +79,15 @@ skill-tree-generator for the full spec of each type.
79
79
 
80
80
  ---
81
81
 
82
+ ### Subagent guidance for batch generation
83
+
84
+ When generating multiple skills, spawn a separate subagent for each skill
85
+ (or per-package group). Each subagent receives the domain_map.yaml,
86
+ skill_tree.yaml, and the source docs relevant to its skill. This prevents
87
+ context bleed between skills and allows parallel generation.
88
+
89
+ ---
90
+
82
91
  ## Step 2 — Extract content from sources
83
92
 
84
93
  **Line budget:** Each SKILL.md must stay under 500 lines. Before writing,
@@ -319,6 +328,10 @@ Run every check before outputting. Fix any failures.
319
328
 
320
329
  ## Step 6 — Output
321
330
 
331
+ Before generating, ask the maintainer: "Would you like to review each skill
332
+ individually before I generate the next one, or should I generate all skills
333
+ and you review them together?" Respect their preference.
334
+
322
335
  Output the complete SKILL.md file content. If reference files are needed,
323
336
  output those as well with their relative paths.
324
337
 
@@ -381,48 +394,9 @@ Output is consumed by all major AI coding agents. To ensure consistency:
381
394
 
382
395
  ---
383
396
 
384
- ## Meta-skill feedback (alpha default)
385
-
386
- After generating the first iteration of skills, complete a single Markdown
387
- feedback draft for all three meta skills used in scaffolding. If a draft
388
- exists from previous steps, keep it and fill the **Generate Skill** section.
389
- If no draft exists, create it using this exact template.
390
-
391
- ```
392
-
393
- # Intent Meta Skill Feedback
394
-
395
- ## Domain Discovery
396
-
397
- - What worked well:
398
- - What was confusing or missing:
399
- - Suggestions for improvement:
400
- - Overall rating: good | mixed | bad
401
-
402
- ## Tree Generator
403
-
404
- - What worked well:
405
- - What was confusing or missing:
406
- - Suggestions for improvement:
407
- - Overall rating: good | mixed | bad
408
-
409
- ## Generate Skill
410
-
411
- - What worked well:
412
- - What was confusing or missing:
413
- - Suggestions for improvement:
414
- - Overall rating: good | mixed | bad
415
-
416
- ## Context (optional)
417
-
418
- - Library:
419
- - Repo:
420
- - Docs:
421
- - Notes:
422
-
423
- ```
397
+ ## Meta-skill feedback
424
398
 
425
- Ask the maintainer if they want to edit the feedback. Then ask if you should
426
- send it as a GitHub issue to TanStack/intent. Use the issue title:
427
- `[meta-feedback] intent meta skill`. Only submit if they confirm.
399
+ After generating all skills, run the `skill-feedback-collection` skill to
400
+ capture feedback about the scaffolding process (domain-discovery,
401
+ tree-generator, and generate-skill).
428
402
  ```
@@ -274,14 +274,9 @@ packages/
274
274
  │ └── package.json # Add "skills" to files array
275
275
  ```
276
276
 
277
- Add `"skills"` to each package's `files` array in `package.json` so
278
- skill files are included in the published npm tarball:
279
-
280
- ```json
281
- {
282
- "files": ["dist", "src", "skills"]
283
- }
284
- ```
277
+ Run `intent edit-package-json` to wire each package's `package.json`
278
+ automatically (adds `"skills"`, `"bin"`, and `"!skills/_artifacts"` to the
279
+ `files` array, and adds the `bin` entry if missing).
285
280
 
286
281
  ### Step 2 — Write the core skill
287
282
 
@@ -852,41 +847,3 @@ When updating:
852
847
  3. CHANGELOG.md entry
853
848
 
854
849
  ---
855
-
856
- ## Meta-skill feedback (alpha default)
857
-
858
- After producing the skill tree artifact, update a single Markdown feedback
859
- draft for all three meta skills used in scaffolding. If a draft exists from
860
- Domain Discovery, keep it and fill only the **Tree Generator** section. If
861
- no draft exists, create it using this exact template.
862
-
863
- ```
864
- # Intent Meta Skill Feedback
865
-
866
- ## Domain Discovery
867
- - What worked well:
868
- - What was confusing or missing:
869
- - Suggestions for improvement:
870
- - Overall rating: good | mixed | bad
871
-
872
- ## Tree Generator
873
- - What worked well:
874
- - What was confusing or missing:
875
- - Suggestions for improvement:
876
- - Overall rating: good | mixed | bad
877
-
878
- ## Generate Skill
879
- - What worked well:
880
- - What was confusing or missing:
881
- - Suggestions for improvement:
882
- - Overall rating: good | mixed | bad
883
-
884
- ## Context (optional)
885
- - Library:
886
- - Repo:
887
- - Docs:
888
- - Notes:
889
- ```
890
-
891
- Do not submit feedback yet. Carry this draft forward to the generate-skill
892
- step.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/intent",
3
- "version": "0.0.9",
3
+ "version": "0.0.12",
4
4
  "description": "Ship compositional knowledge for AI coding agents alongside your npm packages",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,4 +0,0 @@
1
- import "./utils-DH3jY3CI.mjs";
2
- import { t as scanForIntents } from "./scanner-OmHt14bs.mjs";
3
-
4
- export { scanForIntents };
@@ -1,9 +0,0 @@
1
- //#region src/setup.d.ts
2
- interface SetupResult {
3
- workflows: string[];
4
- skipped: string[];
5
- shim: string | null;
6
- }
7
- declare function runSetup(root: string, metaDir: string, args: string[]): SetupResult;
8
- //#endregion
9
- export { runSetup as n, SetupResult as t };
@@ -1,125 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
-
4
- //#region src/setup.ts
5
- function detectVars(root) {
6
- const pkgPath = join(root, "package.json");
7
- let pkgJson = {};
8
- try {
9
- pkgJson = JSON.parse(readFileSync(pkgPath, "utf8"));
10
- } catch {}
11
- const name = typeof pkgJson.name === "string" ? pkgJson.name : "unknown";
12
- const intent = pkgJson.intent;
13
- const repo = typeof intent?.repo === "string" ? intent.repo : name.replace(/^@/, "").replace(/\//, "/");
14
- const docs = typeof intent?.docs === "string" ? intent.docs : "docs/";
15
- let srcPath = `packages/${name.replace(/^@[^/]+\//, "")}/src/**`;
16
- if (existsSync(join(root, "src"))) srcPath = "src/**";
17
- return {
18
- PACKAGE_NAME: name,
19
- REPO: repo,
20
- DOCS_PATH: docs.endsWith("**") ? docs : docs.replace(/\/$/, "") + "/**",
21
- SRC_PATH: srcPath
22
- };
23
- }
24
- function applyVars(content, vars) {
25
- return content.replace(/\{\{PACKAGE_NAME\}\}/g, vars.PACKAGE_NAME).replace(/\{\{REPO\}\}/g, vars.REPO).replace(/\{\{DOCS_PATH\}\}/g, vars.DOCS_PATH).replace(/\{\{SRC_PATH\}\}/g, vars.SRC_PATH);
26
- }
27
- function copyTemplates(srcDir, destDir, vars) {
28
- const copied = [];
29
- const skipped = [];
30
- if (!existsSync(srcDir)) return {
31
- copied,
32
- skipped
33
- };
34
- mkdirSync(destDir, { recursive: true });
35
- for (const entry of readdirSync(srcDir)) {
36
- const srcPath = join(srcDir, entry);
37
- const destPath = join(destDir, entry);
38
- if (existsSync(destPath)) {
39
- skipped.push(destPath);
40
- continue;
41
- }
42
- writeFileSync(destPath, applyVars(readFileSync(srcPath, "utf8"), vars));
43
- copied.push(destPath);
44
- }
45
- return {
46
- copied,
47
- skipped
48
- };
49
- }
50
- function getShimContent(ext) {
51
- return `#!/usr/bin/env node
52
- // Auto-generated by @tanstack/intent setup
53
- // Exposes the intent end-user CLI for consumers of this library.
54
- // Commit this file, then add to your package.json:
55
- // "bin": { "intent": "./bin/intent.${ext}" }
56
- await import('@tanstack/intent/intent-library')
57
- `;
58
- }
59
- function detectShimExtension(root) {
60
- try {
61
- if (JSON.parse(readFileSync(join(root, "package.json"), "utf8")).type === "module") return "js";
62
- } catch {}
63
- return "mjs";
64
- }
65
- function findExistingShim(root) {
66
- const shimJs = join(root, "bin", "intent.js");
67
- if (existsSync(shimJs)) return shimJs;
68
- const shimMjs = join(root, "bin", "intent.mjs");
69
- if (existsSync(shimMjs)) return shimMjs;
70
- return null;
71
- }
72
- function generateShim(root, result) {
73
- const existingShim = findExistingShim(root);
74
- if (existingShim) {
75
- result.skipped.push(existingShim);
76
- return;
77
- }
78
- const ext = detectShimExtension(root);
79
- const shimPath = join(root, "bin", `intent.${ext}`);
80
- mkdirSync(join(root, "bin"), { recursive: true });
81
- writeFileSync(shimPath, getShimContent(ext));
82
- result.shim = shimPath;
83
- }
84
- function runSetup(root, metaDir, args) {
85
- const doAll = args.includes("--all");
86
- const doWorkflows = doAll || args.includes("--workflows");
87
- const doShim = doAll || args.includes("--shim");
88
- const noFlagsGiven = !doWorkflows && !doShim;
89
- const installWorkflows = doWorkflows || noFlagsGiven;
90
- const installShim = doShim || noFlagsGiven;
91
- const vars = detectVars(root);
92
- const result = {
93
- workflows: [],
94
- skipped: [],
95
- shim: null
96
- };
97
- const templatesDir = join(metaDir, "templates");
98
- if (installWorkflows) {
99
- const { copied, skipped } = copyTemplates(join(templatesDir, "workflows"), join(root, ".github", "workflows"), vars);
100
- result.workflows = copied;
101
- result.skipped.push(...skipped);
102
- }
103
- if (installShim) generateShim(root, result);
104
- for (const f of result.workflows) console.log(`✓ Copied workflow: ${f}`);
105
- for (const f of result.skipped) console.log(` Already exists: ${f}`);
106
- if (result.shim) {
107
- const shimRelative = result.shim.replace(root + "/", "./");
108
- console.log(`✓ Generated intent shim: ${result.shim}`);
109
- console.log(`\n Add to your package.json:`);
110
- console.log(` "bin": { "intent": "${shimRelative}" }`);
111
- console.log(`\n Add "bin" to your package.json "files" array.`);
112
- }
113
- if (result.workflows.length === 0 && result.shim === null && result.skipped.length === 0) console.log("No templates directory found. Is @tanstack/intent installed?");
114
- else if (result.workflows.length > 0) {
115
- console.log(`\nTemplate variables applied:`);
116
- console.log(` Package: ${vars.PACKAGE_NAME}`);
117
- console.log(` Repo: ${vars.REPO}`);
118
- console.log(` Docs: ${vars.DOCS_PATH}`);
119
- console.log(` Src: ${vars.SRC_PATH}`);
120
- }
121
- return result;
122
- }
123
-
124
- //#endregion
125
- export { runSetup as t };