@tanstack/intent 0.0.6 → 0.0.8

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.
@@ -520,10 +520,18 @@ See also: [lib]-core/[other-domain]/SKILL.md § Common Mistakes
520
520
  The cross-reference ensures agents that load one skill are pointed
521
521
  toward the related skill where the other side of the tension lives.
522
522
 
523
+ Also check the domain map's `cross_references` section for non-tension
524
+ relationships between skills. For each cross-reference, add a "See also"
525
+ line at the end of the relevant skill's body:
526
+
527
+ ```markdown
528
+ See also: [other-skill]/SKILL.md — [reason]
529
+ ```
530
+
523
531
  ### Step 6 — Write composition skills (if applicable)
524
532
 
525
533
  Use the `compositions` entries from `domain_map.yaml` (populated during
526
- skill-domain-discovery Phase 2h) to identify which composition skills
534
+ skill-domain-discovery Phase 3h) to identify which composition skills
527
535
  to produce.
528
536
 
529
537
  Composition skills cover how two or more libraries work together. These
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/intent",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Ship compositional knowledge for AI coding agents alongside your npm packages",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,6 +32,7 @@
32
32
  "tsdown": "^0.19.0"
33
33
  },
34
34
  "scripts": {
35
+ "prepack": "pnpm run build",
35
36
  "build": "tsdown src/index.ts src/cli.ts src/setup.ts src/intent-library.ts src/library-scanner.ts --format esm --dts",
36
37
  "test:lib": "vitest run",
37
38
  "test:types": "tsc --noEmit"
@@ -1,299 +0,0 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { execSync } from "node:child_process";
4
-
5
- //#region src/feedback.ts
6
- const META_FEEDBACK_REPO = "TanStack/intent";
7
- const SECRET_PATTERNS = [
8
- /(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}/,
9
- /(?:sk|pk)[-_](?:live|test)[-_][A-Za-z0-9]{24,}/,
10
- /AKIA[0-9A-Z]{16}/,
11
- /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/,
12
- /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/,
13
- /(?:Bearer|token)\s+[A-Za-z0-9_\-.~+/]{20,}/i,
14
- /[A-Za-z0-9]{32,}(?=.*(?:key|secret|token|password))/i
15
- ];
16
- function containsSecrets(text) {
17
- return SECRET_PATTERNS.some((pattern) => pattern.test(text));
18
- }
19
- function hasGhCli() {
20
- try {
21
- execSync("gh --version", { stdio: "ignore" });
22
- return true;
23
- } catch {
24
- return false;
25
- }
26
- }
27
- function getHomeConfigDir() {
28
- return process.env.XDG_CONFIG_HOME ?? join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".config");
29
- }
30
- function resolveFrequency(root) {
31
- const userConfigPath = join(getHomeConfigDir(), "intent", "config.json");
32
- try {
33
- const userCfg = JSON.parse(readFileSync(userConfigPath, "utf8"));
34
- if (userCfg.feedback?.frequency) return userCfg.feedback.frequency;
35
- } catch {}
36
- const projectConfigPath = join(root, "intent.config.json");
37
- try {
38
- const projCfg = JSON.parse(readFileSync(projectConfigPath, "utf8"));
39
- if (projCfg.feedback?.frequency) return projCfg.feedback.frequency;
40
- } catch {}
41
- return "every-5";
42
- }
43
- const REQUIRED_FIELDS = [
44
- "skill",
45
- "package",
46
- "skillVersion",
47
- "task",
48
- "whatWorked",
49
- "whatFailed",
50
- "missing",
51
- "selfCorrections",
52
- "userRating"
53
- ];
54
- function validatePayload(payload) {
55
- const errors = [];
56
- if (!payload || typeof payload !== "object") return {
57
- valid: false,
58
- errors: ["Payload must be a JSON object"]
59
- };
60
- const obj = payload;
61
- for (const field of REQUIRED_FIELDS) if (typeof obj[field] !== "string" || obj[field].trim() === "") errors.push(`Missing or empty required field: ${field}`);
62
- if (obj.userRating && ![
63
- "good",
64
- "mixed",
65
- "bad"
66
- ].includes(obj.userRating)) errors.push("userRating must be one of: good, mixed, bad");
67
- if (containsSecrets(Object.values(obj).filter((v) => typeof v === "string").join("\n"))) errors.push("Payload appears to contain secrets or tokens — submission rejected");
68
- return {
69
- valid: errors.length === 0,
70
- errors
71
- };
72
- }
73
- const META_REQUIRED_FIELDS = [
74
- "metaSkill",
75
- "library",
76
- "agentUsed",
77
- "artifactQuality",
78
- "whatWorked",
79
- "whatFailed",
80
- "suggestions",
81
- "userRating"
82
- ];
83
- const VALID_META_SKILLS = [
84
- "domain-discovery",
85
- "tree-generator",
86
- "generate-skill",
87
- "skill-staleness-check"
88
- ];
89
- const VALID_AGENTS = [
90
- "claude-code",
91
- "cursor",
92
- "copilot",
93
- "codex",
94
- "other"
95
- ];
96
- const VALID_QUALITY_RATINGS = [
97
- "good",
98
- "mixed",
99
- "bad"
100
- ];
101
- function validateMetaPayload(payload) {
102
- const errors = [];
103
- if (!payload || typeof payload !== "object") return {
104
- valid: false,
105
- errors: ["Payload must be a JSON object"]
106
- };
107
- const obj = payload;
108
- for (const field of META_REQUIRED_FIELDS) if (typeof obj[field] !== "string" || obj[field].trim() === "") errors.push(`Missing or empty required field: ${field}`);
109
- if (obj.metaSkill && !VALID_META_SKILLS.includes(obj.metaSkill)) errors.push(`metaSkill must be one of: ${VALID_META_SKILLS.join(", ")}`);
110
- if (obj.agentUsed && !VALID_AGENTS.includes(obj.agentUsed)) errors.push(`agentUsed must be one of: ${VALID_AGENTS.join(", ")}`);
111
- if (obj.artifactQuality && !VALID_QUALITY_RATINGS.includes(obj.artifactQuality)) errors.push("artifactQuality must be one of: good, mixed, bad");
112
- if (obj.userRating && !VALID_QUALITY_RATINGS.includes(obj.userRating)) errors.push("userRating must be one of: good, mixed, bad");
113
- if (containsSecrets(Object.values(obj).filter((v) => typeof v === "string").join("\n"))) errors.push("Payload appears to contain secrets or tokens — submission rejected");
114
- return {
115
- valid: errors.length === 0,
116
- errors
117
- };
118
- }
119
- function metaToMarkdown(payload) {
120
- const lines = [
121
- `# Meta-Skill Feedback: ${payload.metaSkill}`,
122
- "",
123
- `**Library:** ${payload.library}`,
124
- `**Agent:** ${payload.agentUsed}`,
125
- `**Artifact quality:** ${payload.artifactQuality}`,
126
- `**Rating:** ${payload.userRating}`
127
- ];
128
- if (payload.interviewQuality) lines.push(`**Interview quality:** ${payload.interviewQuality}`);
129
- if (payload.failureModeQuality) lines.push(`**Failure mode quality:** ${payload.failureModeQuality}`);
130
- lines.push("", "## What Worked", payload.whatWorked, "", "## What Failed", payload.whatFailed, "", "## Suggestions", payload.suggestions);
131
- return lines.join("\n") + "\n";
132
- }
133
- function toMarkdown(payload) {
134
- const lines = [
135
- `# Skill Feedback: ${payload.skill}`,
136
- "",
137
- `**Package:** ${payload.package}`,
138
- `**Skill version:** ${payload.skillVersion}`,
139
- `**Rating:** ${payload.userRating}`,
140
- "",
141
- "## Task",
142
- payload.task,
143
- "",
144
- "## What Worked",
145
- payload.whatWorked,
146
- "",
147
- "## What Failed",
148
- payload.whatFailed,
149
- "",
150
- "## Missing",
151
- payload.missing,
152
- "",
153
- "## Self-Corrections",
154
- payload.selfCorrections
155
- ];
156
- if (payload.userComments) lines.push("", "## User Comments", payload.userComments);
157
- return lines.join("\n") + "\n";
158
- }
159
- function submitFeedback(payload, repo, opts) {
160
- const md = toMarkdown(payload);
161
- if (opts.ghAvailable) try {
162
- execSync(`gh issue create --repo ${repo} --title "${`Skill Feedback: ${payload.skill} (${payload.userRating})`.replace(/"/g, "\\\"")}" --body -`, {
163
- input: md,
164
- stdio: [
165
- "pipe",
166
- "pipe",
167
- "pipe"
168
- ]
169
- });
170
- return {
171
- method: "gh",
172
- detail: `Submitted issue to ${repo}`
173
- };
174
- } catch {}
175
- if (opts.outputPath) {
176
- writeFileSync(opts.outputPath, md, "utf8");
177
- return {
178
- method: "file",
179
- detail: `Saved to ${opts.outputPath}`
180
- };
181
- }
182
- return {
183
- method: "stdout",
184
- detail: md
185
- };
186
- }
187
- function submitMetaFeedback(payload, opts) {
188
- const md = metaToMarkdown(payload);
189
- if (opts.ghAvailable) try {
190
- execSync(`gh issue create --repo ${META_FEEDBACK_REPO} --title "${`Meta-Skill Feedback: ${payload.metaSkill} (${payload.userRating})`.replace(/"/g, "\\\"")}" --label "feedback:${payload.metaSkill}" --body -`, {
191
- input: md,
192
- stdio: [
193
- "pipe",
194
- "pipe",
195
- "pipe"
196
- ]
197
- });
198
- return {
199
- method: "gh",
200
- detail: `Submitted issue to ${META_FEEDBACK_REPO}`
201
- };
202
- } catch {}
203
- if (opts.outputPath) {
204
- writeFileSync(opts.outputPath, md, "utf8");
205
- return {
206
- method: "file",
207
- detail: `Saved to ${opts.outputPath}`
208
- };
209
- }
210
- return {
211
- method: "stdout",
212
- detail: md
213
- };
214
- }
215
- function runFeedback(args) {
216
- const isMeta = args.includes("--meta");
217
- const submitFlag = args.includes("--submit");
218
- const fileIdx = args.indexOf("--file");
219
- const filePath = fileIdx !== -1 ? args[fileIdx + 1] : void 0;
220
- if (!submitFlag || !filePath) {
221
- if (isMeta) console.error("Usage: intent feedback --meta --submit --file <path>");
222
- else console.error("Usage: intent feedback --submit --file <path>");
223
- process.exit(1);
224
- }
225
- if (!existsSync(filePath)) {
226
- console.error(`File not found: ${filePath}`);
227
- process.exit(1);
228
- }
229
- let raw;
230
- try {
231
- raw = JSON.parse(readFileSync(filePath, "utf8"));
232
- } catch {
233
- console.error("Invalid JSON in feedback file");
234
- process.exit(1);
235
- }
236
- const ghAvailable = hasGhCli();
237
- if (resolveFrequency(process.cwd()) === "never") {
238
- console.log("Feedback is disabled (frequency: never)");
239
- return;
240
- }
241
- const dateSuffix = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
242
- if (isMeta) {
243
- const validation$1 = validateMetaPayload(raw);
244
- if (!validation$1.valid) {
245
- console.error("Meta-feedback validation failed:");
246
- for (const err of validation$1.errors) console.error(` - ${err}`);
247
- process.exit(1);
248
- }
249
- const payload$1 = raw;
250
- const fallbackPath$1 = `intent-meta-feedback-${dateSuffix}.md`;
251
- const result$1 = submitMetaFeedback(payload$1, {
252
- ghAvailable,
253
- outputPath: ghAvailable ? void 0 : fallbackPath$1
254
- });
255
- switch (result$1.method) {
256
- case "gh":
257
- console.log(`✓ ${result$1.detail}`);
258
- break;
259
- case "file":
260
- console.log(`✓ ${result$1.detail}`);
261
- console.log(`You can manually open an issue at https://github.com/${META_FEEDBACK_REPO}/issues with this content.`);
262
- break;
263
- case "stdout":
264
- console.log("--- Meta-feedback markdown (copy/paste to issue) ---");
265
- console.log(result$1.detail);
266
- break;
267
- }
268
- return;
269
- }
270
- const validation = validatePayload(raw);
271
- if (!validation.valid) {
272
- console.error("Feedback validation failed:");
273
- for (const err of validation.errors) console.error(` - ${err}`);
274
- process.exit(1);
275
- }
276
- const payload = raw;
277
- const repo = payload.package.replace(/^@/, "").replace(/\//, "/");
278
- const fallbackPath = `intent-feedback-${dateSuffix}.md`;
279
- const result = submitFeedback(payload, repo, {
280
- ghAvailable,
281
- outputPath: ghAvailable ? void 0 : fallbackPath
282
- });
283
- switch (result.method) {
284
- case "gh":
285
- console.log(`✓ ${result.detail}`);
286
- break;
287
- case "file":
288
- console.log(`✓ ${result.detail}`);
289
- console.log("You can manually open an issue with this content.");
290
- break;
291
- case "stdout":
292
- console.log("--- Feedback markdown (copy/paste to issue) ---");
293
- console.log(result.detail);
294
- break;
295
- }
296
- }
297
-
298
- //#endregion
299
- export { runFeedback as a, toMarkdown as c, resolveFrequency as i, validateMetaPayload as l, hasGhCli as n, submitFeedback as o, metaToMarkdown as r, submitMetaFeedback as s, containsSecrets as t, validatePayload as u };
@@ -1,3 +0,0 @@
1
- import { a as runFeedback, c as toMarkdown, i as resolveFrequency, l as validateMetaPayload, n as hasGhCli, o as submitFeedback, r as metaToMarkdown, s as submitMetaFeedback, t as containsSecrets, u as validatePayload } from "./feedback-BTSaBAS_.mjs";
2
-
3
- export { runFeedback };
@@ -1,3 +0,0 @@
1
- import { a as runInit, i as readProjectConfig, n as hasIntentBlock, o as writeProjectConfig, r as injectIntentBlock, t as detectAgentConfigs } from "./init-DUiDMMrN.mjs";
2
-
3
- export { detectAgentConfigs, runInit };
@@ -1,72 +0,0 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
-
4
- //#region src/init.ts
5
- const AGENT_CONFIG_FILES = [
6
- "AGENTS.md",
7
- "CLAUDE.md",
8
- ".cursorrules",
9
- ".github/copilot-instructions.md"
10
- ];
11
- const INTENT_BLOCK_MARKER = "## Intent Skills";
12
- const INTENT_BLOCK = `## Intent Skills
13
-
14
- This project uses TanStack Intent. Run \`npx intent list\` to discover
15
- available AI coding skills. Before working with a library that has skills,
16
- read the relevant SKILL.md file at the path shown in the list output.
17
- After completing a task that used skills, collect and submit feedback
18
- following the instructions in the list output.
19
- `;
20
- const DEFAULT_CONFIG = { feedback: { frequency: "every-5" } };
21
- function detectAgentConfigs(root) {
22
- return AGENT_CONFIG_FILES.map((f) => join(root, f)).filter((f) => existsSync(f));
23
- }
24
- function hasIntentBlock(filePath) {
25
- try {
26
- return readFileSync(filePath, "utf8").includes(INTENT_BLOCK_MARKER);
27
- } catch {
28
- return false;
29
- }
30
- }
31
- function injectIntentBlock(filePath) {
32
- if (hasIntentBlock(filePath)) return false;
33
- let content;
34
- try {
35
- content = readFileSync(filePath, "utf8");
36
- } catch {
37
- content = "";
38
- }
39
- const separator = content.length > 0 && !content.endsWith("\n\n") ? "\n\n" : "";
40
- writeFileSync(filePath, content + separator + INTENT_BLOCK);
41
- return true;
42
- }
43
- function writeProjectConfig(root) {
44
- const configPath = join(root, "intent.config.json");
45
- if (!existsSync(configPath)) writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n");
46
- return configPath;
47
- }
48
- function readProjectConfig(root) {
49
- const configPath = join(root, "intent.config.json");
50
- try {
51
- return JSON.parse(readFileSync(configPath, "utf8"));
52
- } catch {
53
- return null;
54
- }
55
- }
56
- function runInit(root) {
57
- const detected = detectAgentConfigs(root);
58
- const injected = [];
59
- const skipped = [];
60
- const created = [];
61
- for (const filePath of detected) if (injectIntentBlock(filePath)) injected.push(filePath);
62
- else skipped.push(filePath);
63
- return {
64
- injected,
65
- skipped,
66
- created,
67
- configPath: writeProjectConfig(root)
68
- };
69
- }
70
-
71
- //#endregion
72
- export { runInit as a, readProjectConfig as i, hasIntentBlock as n, writeProjectConfig as o, injectIntentBlock as r, detectAgentConfigs as t };