@tanstack/intent 0.0.14 → 0.0.19
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 +42 -5
- package/dist/cli.d.mts +6 -1
- package/dist/cli.mjs +249 -170
- package/dist/display-CuCDLPP_.mjs +3 -0
- package/dist/index.d.mts +7 -7
- package/dist/index.mjs +37 -14
- package/dist/install-prompt-C0M-U3WZ.mjs +59 -0
- package/dist/intent-library.mjs +5 -49
- package/dist/{library-scanner-B1tmOzwf.mjs → library-scanner-CU0OozQE.mjs} +2 -2
- package/dist/library-scanner.d.mts +4 -4
- package/dist/library-scanner.mjs +2 -2
- package/dist/scanner-B1UvuEBQ.mjs +4 -0
- package/dist/scanner-f82qRq7h.mjs +338 -0
- package/dist/setup-6m3IfxyO.d.mts +30 -0
- package/dist/setup-CncHbQlb.mjs +360 -0
- package/dist/setup.d.mts +2 -2
- package/dist/setup.mjs +3 -2
- package/dist/staleness-CWWuoPop.mjs +4 -0
- package/dist/{staleness-DJfMKH62.mjs → staleness-D_ZiK4Tf.mjs} +24 -3
- package/dist/{types-BmnI8kFB.d.mts → types-ddLtccfV.d.mts} +30 -7
- package/dist/{utils-CDJzAdjD.mjs → utils-DY1eH2E_.mjs} +82 -4
- package/dist/utils-XSyO19J6.mjs +3 -0
- package/meta/domain-discovery/SKILL.md +95 -20
- package/meta/feedback-collection/SKILL.md +20 -1
- package/meta/generate-skill/SKILL.md +56 -5
- package/meta/templates/workflows/check-skills.yml +4 -4
- package/meta/templates/workflows/{notify-playbooks.yml → notify-intent.yml} +4 -4
- package/meta/tree-generator/SKILL.md +2 -2
- package/package.json +4 -3
- package/dist/scanner-CECGXgox.mjs +0 -4
- package/dist/scanner-CY40iozO.mjs +0 -218
- package/dist/setup-CANkTz55.d.mts +0 -18
- package/dist/setup-Nif1-nhS.mjs +0 -211
- package/dist/staleness-C1h7RuZ9.mjs +0 -4
- /package/dist/{display-D_XzuGnu.mjs → display-DhsUxNJW.mjs} +0 -0
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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-
|
|
2
|
-
import {
|
|
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-ddLtccfV.mjs";
|
|
2
|
+
import { c as runAddLibraryBin, f as runSetupGithubActions, i as SetupGithubActionsResult, n as EditPackageJsonResult, t as AddLibraryBinResult, u as runEditPackageJson } from "./setup-6m3IfxyO.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/scanner.d.ts
|
|
5
5
|
declare function scanForIntents(root?: string): Promise<ScanResult>;
|
|
@@ -13,11 +13,11 @@ declare function hasGhCli(): boolean;
|
|
|
13
13
|
declare function resolveFrequency(root: string): string;
|
|
14
14
|
declare function validatePayload(payload: unknown): {
|
|
15
15
|
valid: boolean;
|
|
16
|
-
errors: string
|
|
16
|
+
errors: Array<string>;
|
|
17
17
|
};
|
|
18
18
|
declare function validateMetaPayload(payload: unknown): {
|
|
19
19
|
valid: boolean;
|
|
20
|
-
errors: string
|
|
20
|
+
errors: Array<string>;
|
|
21
21
|
};
|
|
22
22
|
declare function metaToMarkdown(payload: MetaFeedbackPayload): string;
|
|
23
23
|
declare function toMarkdown(payload: FeedbackPayload): string;
|
|
@@ -38,19 +38,19 @@ declare function submitMetaFeedback(payload: MetaFeedbackPayload, opts: {
|
|
|
38
38
|
/**
|
|
39
39
|
* Recursively find all SKILL.md files under a directory.
|
|
40
40
|
*/
|
|
41
|
-
declare function findSkillFiles(dir: string): string
|
|
41
|
+
declare function findSkillFiles(dir: string): Array<string>;
|
|
42
42
|
/**
|
|
43
43
|
* Read dependencies and peerDependencies (and optionally devDependencies) from
|
|
44
44
|
* a parsed package.json object.
|
|
45
45
|
*/
|
|
46
|
-
declare function getDeps(pkgJson: Record<string, unknown>, includeDevDeps?: boolean): string
|
|
46
|
+
declare function getDeps(pkgJson: Record<string, unknown>, includeDevDeps?: boolean): Array<string>;
|
|
47
47
|
/**
|
|
48
48
|
* Resolve the directory of a dependency by name. First checks the top-level
|
|
49
49
|
* node_modules (hoisted layout — npm, yarn, bun), then resolves through the
|
|
50
50
|
* parent package's real path to handle pnpm's virtual store layout where
|
|
51
51
|
* transitive deps are siblings in the .pnpm virtual store node_modules.
|
|
52
52
|
*/
|
|
53
|
-
declare function resolveDepDir(depName: string, parentDir: string, parentName: string,
|
|
53
|
+
declare function resolveDepDir(depName: string, parentDir: string, parentName: string, nodeModulesDirs: string | Array<string>): string | null;
|
|
54
54
|
/**
|
|
55
55
|
* Parse YAML frontmatter from a file. Returns null if no frontmatter or on error.
|
|
56
56
|
*/
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as scanForIntents } from "./scanner-
|
|
3
|
-
import { t as checkStaleness } from "./staleness-
|
|
4
|
-
import {
|
|
1
|
+
import { a as parseFrontmatter, n as findSkillFiles, o as resolveDepDir, r as getDeps } from "./utils-DY1eH2E_.mjs";
|
|
2
|
+
import { t as scanForIntents } from "./scanner-f82qRq7h.mjs";
|
|
3
|
+
import { t as checkStaleness } from "./staleness-D_ZiK4Tf.mjs";
|
|
4
|
+
import { c as runSetupGithubActions, i as runAddLibraryBin, o as runEditPackageJson } from "./setup-CncHbQlb.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";
|
|
@@ -31,17 +31,26 @@ function hasGhCli() {
|
|
|
31
31
|
function getHomeConfigDir() {
|
|
32
32
|
return process.env.XDG_CONFIG_HOME ?? join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".config");
|
|
33
33
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
function parseFrequency(value) {
|
|
35
|
+
if (value === "always" || value === "never") return value;
|
|
36
|
+
if (typeof value !== "string") return null;
|
|
37
|
+
const match = /^every-(\d+)$/.exec(value);
|
|
38
|
+
if (!match) return null;
|
|
39
|
+
const count = Number(match[1]);
|
|
40
|
+
return Number.isInteger(count) && count > 0 ? `every-${count}` : null;
|
|
41
|
+
}
|
|
42
|
+
function readFrequency(filePath) {
|
|
41
43
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
return parseFrequency(JSON.parse(readFileSync(filePath, "utf8")).feedback?.frequency);
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function resolveFrequency(root) {
|
|
50
|
+
const userFrequency = readFrequency(join(getHomeConfigDir(), "intent", "config.json"));
|
|
51
|
+
if (userFrequency) return userFrequency;
|
|
52
|
+
const projectFrequency = readFrequency(join(root, "intent.config.json"));
|
|
53
|
+
if (projectFrequency) return projectFrequency;
|
|
45
54
|
return "every-5";
|
|
46
55
|
}
|
|
47
56
|
const REQUIRED_FIELDS = [
|
|
@@ -102,6 +111,18 @@ const VALID_QUALITY_RATINGS = [
|
|
|
102
111
|
"mixed",
|
|
103
112
|
"bad"
|
|
104
113
|
];
|
|
114
|
+
const VALID_INTERVIEW_QUALITY_RATINGS = [
|
|
115
|
+
"good",
|
|
116
|
+
"mixed",
|
|
117
|
+
"bad",
|
|
118
|
+
"skipped"
|
|
119
|
+
];
|
|
120
|
+
const VALID_FAILURE_MODE_QUALITY_RATINGS = [
|
|
121
|
+
"good",
|
|
122
|
+
"mixed",
|
|
123
|
+
"bad",
|
|
124
|
+
"not-applicable"
|
|
125
|
+
];
|
|
105
126
|
function validateMetaPayload(payload) {
|
|
106
127
|
const errors = [];
|
|
107
128
|
if (!payload || typeof payload !== "object") return {
|
|
@@ -114,6 +135,8 @@ function validateMetaPayload(payload) {
|
|
|
114
135
|
if (obj.agentUsed && !VALID_AGENTS.includes(obj.agentUsed)) errors.push(`agentUsed must be one of: ${VALID_AGENTS.join(", ")}`);
|
|
115
136
|
if (obj.artifactQuality && !VALID_QUALITY_RATINGS.includes(obj.artifactQuality)) errors.push("artifactQuality must be one of: good, mixed, bad");
|
|
116
137
|
if (obj.userRating && !VALID_QUALITY_RATINGS.includes(obj.userRating)) errors.push("userRating must be one of: good, mixed, bad");
|
|
138
|
+
if (obj.interviewQuality && !VALID_INTERVIEW_QUALITY_RATINGS.includes(obj.interviewQuality)) errors.push("interviewQuality must be one of: good, mixed, bad, skipped");
|
|
139
|
+
if (obj.failureModeQuality && !VALID_FAILURE_MODE_QUALITY_RATINGS.includes(obj.failureModeQuality)) errors.push("failureModeQuality must be one of: good, mixed, bad, not-applicable");
|
|
117
140
|
if (containsSecrets(Object.values(obj).filter((v) => typeof v === "string").join("\n"))) errors.push("Payload appears to contain secrets or tokens — submission rejected");
|
|
118
141
|
return {
|
|
119
142
|
valid: errors.length === 0,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region src/install-prompt.ts
|
|
2
|
+
const INSTALL_PROMPT = `You are an AI assistant helping a developer set up skill-to-task mappings for their project.
|
|
3
|
+
|
|
4
|
+
Follow these steps in order:
|
|
5
|
+
|
|
6
|
+
1. CHECK FOR EXISTING MAPPINGS
|
|
7
|
+
Search the project's agent config files (AGENTS.md, CLAUDE.md, .cursorrules,
|
|
8
|
+
.github/copilot-instructions.md) for a block delimited by:
|
|
9
|
+
<!-- intent-skills:start -->
|
|
10
|
+
<!-- intent-skills:end -->
|
|
11
|
+
- If found: show the user the current mappings, keep that file as the source of truth,
|
|
12
|
+
and ask "What would you like to update?" Then skip to step 4 with their requested changes.
|
|
13
|
+
- If not found: continue to step 2.
|
|
14
|
+
|
|
15
|
+
2. DISCOVER AVAILABLE SKILLS
|
|
16
|
+
Run: \`npx @tanstack/intent@latest list\`
|
|
17
|
+
This outputs each skill's name, description, full path, and whether it was found in
|
|
18
|
+
project-local node_modules or accessible global node_modules.
|
|
19
|
+
This works best in Node-compatible environments (npm, pnpm, Bun, or Deno npm interop
|
|
20
|
+
with node_modules enabled).
|
|
21
|
+
|
|
22
|
+
3. SCAN THE REPOSITORY
|
|
23
|
+
Build a picture of the project's structure and patterns:
|
|
24
|
+
- Read package.json for library dependencies
|
|
25
|
+
- Survey the directory layout (src/, app/, routes/, components/, api/, etc.)
|
|
26
|
+
- Note recurring patterns (routing, data fetching, auth, UI components, etc.)
|
|
27
|
+
|
|
28
|
+
Based on this, propose 3-5 skill-to-task mappings. For each one explain:
|
|
29
|
+
- The task or code area (in plain language the user would recognise)
|
|
30
|
+
- Which skill applies and why
|
|
31
|
+
|
|
32
|
+
Then ask: "What other tasks do you commonly use AI coding agents for?
|
|
33
|
+
I'll create mappings for those too."
|
|
34
|
+
Also ask: "I'll default to AGENTS.md unless you want another supported config file.
|
|
35
|
+
Do you have a preference?"
|
|
36
|
+
|
|
37
|
+
4. WRITE THE MAPPINGS BLOCK
|
|
38
|
+
Once you have the full set of mappings, write or update the agent config file.
|
|
39
|
+
- If you found an existing intent-skills block, update that file in place.
|
|
40
|
+
- Otherwise prefer AGENTS.md by default, unless the user asked for another supported file.
|
|
41
|
+
|
|
42
|
+
Use this exact block:
|
|
43
|
+
|
|
44
|
+
<!-- intent-skills:start -->
|
|
45
|
+
# Skill mappings - when working in these areas, load the linked skill file into context.
|
|
46
|
+
skills:
|
|
47
|
+
- task: "describe the task or code area here"
|
|
48
|
+
load: "node_modules/package-name/skills/skill-name/SKILL.md"
|
|
49
|
+
<!-- intent-skills:end -->
|
|
50
|
+
|
|
51
|
+
Rules:
|
|
52
|
+
- Use the user's own words for task descriptions
|
|
53
|
+
- Include the exact path from \`npx @tanstack/intent@latest list\` output so agents can load it directly
|
|
54
|
+
- Keep entries concise - this block is read on every agent task
|
|
55
|
+
- Preserve all content outside the block tags unchanged
|
|
56
|
+
- If the user is on Deno, note that this setup is best-effort today and relies on npm interop`;
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
export { INSTALL_PROMPT as t };
|
package/dist/intent-library.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./utils-
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
2
|
+
import "./utils-DY1eH2E_.mjs";
|
|
3
|
+
import { t as INSTALL_PROMPT } from "./install-prompt-C0M-U3WZ.mjs";
|
|
4
|
+
import { n as printSkillTree, r as printTable, t as computeSkillNameWidth } from "./display-DhsUxNJW.mjs";
|
|
5
|
+
import { t as scanLibrary } from "./library-scanner-CU0OozQE.mjs";
|
|
5
6
|
|
|
6
7
|
//#region src/intent-library.ts
|
|
7
8
|
async function cmdList() {
|
|
@@ -52,52 +53,7 @@ async function cmdList() {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
function cmdInstall() {
|
|
55
|
-
console.log(
|
|
56
|
-
|
|
57
|
-
Follow these steps in order:
|
|
58
|
-
|
|
59
|
-
1. CHECK FOR EXISTING MAPPINGS
|
|
60
|
-
Search the project's agent config files (CLAUDE.md, AGENTS.md, .cursorrules,
|
|
61
|
-
.github/copilot-instructions.md) for a block delimited by:
|
|
62
|
-
<!-- intent-skills:start -->
|
|
63
|
-
<!-- intent-skills:end -->
|
|
64
|
-
- If found: show the user the current mappings and ask "What would you like to update?"
|
|
65
|
-
Then skip to step 4 with their requested changes.
|
|
66
|
-
- If not found: continue to step 2.
|
|
67
|
-
|
|
68
|
-
2. DISCOVER AVAILABLE SKILLS
|
|
69
|
-
Run: intent list
|
|
70
|
-
This outputs each skill's name, description, and full path — grouped by package.
|
|
71
|
-
|
|
72
|
-
3. SCAN THE REPOSITORY
|
|
73
|
-
Build a picture of the project's structure and patterns:
|
|
74
|
-
- Read package.json for library dependencies
|
|
75
|
-
- Survey the directory layout (src/, app/, routes/, components/, api/, etc.)
|
|
76
|
-
- Note recurring patterns (routing, data fetching, auth, UI components, etc.)
|
|
77
|
-
|
|
78
|
-
Based on this, propose 3–5 skill-to-task mappings. For each one explain:
|
|
79
|
-
- The task or code area (in plain language the user would recognise)
|
|
80
|
-
- Which skill applies and why
|
|
81
|
-
|
|
82
|
-
Then ask: "What other tasks do you commonly use AI coding agents for?
|
|
83
|
-
I'll create mappings for those too."
|
|
84
|
-
|
|
85
|
-
4. WRITE THE MAPPINGS BLOCK
|
|
86
|
-
Once you have the full set of mappings, write or update the agent config file
|
|
87
|
-
(prefer CLAUDE.md; create it if none exists) with this exact block:
|
|
88
|
-
|
|
89
|
-
<!-- intent-skills:start -->
|
|
90
|
-
# Skill mappings — when working in these areas, load the linked skill file into context.
|
|
91
|
-
skills:
|
|
92
|
-
- task: "describe the task or code area here"
|
|
93
|
-
load: "node_modules/package-name/skills/skill-name/SKILL.md"
|
|
94
|
-
<!-- intent-skills:end -->
|
|
95
|
-
|
|
96
|
-
Rules:
|
|
97
|
-
- Use the user's own words for task descriptions
|
|
98
|
-
- Include the exact path from \`intent list\` output so agents can load it directly
|
|
99
|
-
- Keep entries concise — this block is read on every agent task
|
|
100
|
-
- Preserve all content outside the block tags unchanged`);
|
|
56
|
+
console.log(INSTALL_PROMPT);
|
|
101
57
|
}
|
|
102
58
|
const USAGE = `TanStack Intent
|
|
103
59
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as parseFrontmatter, o as resolveDepDir, r as getDeps } from "./utils-DY1eH2E_.mjs";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
3
|
import { dirname, join, relative, sep } from "node:path";
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ function readPkgJson(dir) {
|
|
|
12
12
|
}
|
|
13
13
|
function findHomeDir(scriptPath) {
|
|
14
14
|
let dir = dirname(scriptPath);
|
|
15
|
-
|
|
15
|
+
for (;;) {
|
|
16
16
|
if (existsSync(join(dir, "package.json"))) return dir;
|
|
17
17
|
const parent = dirname(dir);
|
|
18
18
|
if (parent === dir) return null;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { l as SkillEntry } from "./types-
|
|
1
|
+
import { l as SkillEntry } from "./types-ddLtccfV.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/library-scanner.d.ts
|
|
4
4
|
interface LibraryPackage {
|
|
5
5
|
name: string;
|
|
6
6
|
version: string;
|
|
7
7
|
description: string;
|
|
8
|
-
skills: SkillEntry
|
|
8
|
+
skills: Array<SkillEntry>;
|
|
9
9
|
}
|
|
10
10
|
interface LibraryScanResult {
|
|
11
|
-
packages: LibraryPackage
|
|
12
|
-
warnings: string
|
|
11
|
+
packages: Array<LibraryPackage>;
|
|
12
|
+
warnings: Array<string>;
|
|
13
13
|
}
|
|
14
14
|
declare function scanLibrary(scriptPath: string, projectRoot?: string): Promise<LibraryScanResult>;
|
|
15
15
|
//#endregion
|
package/dist/library-scanner.mjs
CHANGED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { a as parseFrontmatter, i as listNodeModulesPackageDirs, o as resolveDepDir, r as getDeps, t as detectGlobalNodeModules } from "./utils-DY1eH2E_.mjs";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join, relative, sep } from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region src/scanner.ts
|
|
6
|
+
function detectPackageManager(root) {
|
|
7
|
+
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.");
|
|
8
|
+
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.");
|
|
9
|
+
if (existsSync(join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
10
|
+
if (existsSync(join(root, "bun.lockb")) || existsSync(join(root, "bun.lock"))) return "bun";
|
|
11
|
+
if (existsSync(join(root, "yarn.lock"))) return "yarn";
|
|
12
|
+
if (existsSync(join(root, "package-lock.json"))) return "npm";
|
|
13
|
+
return "unknown";
|
|
14
|
+
}
|
|
15
|
+
function validateIntentField(_pkgName, intent) {
|
|
16
|
+
if (!intent || typeof intent !== "object") return null;
|
|
17
|
+
const pb = intent;
|
|
18
|
+
if (pb.version !== 1) return null;
|
|
19
|
+
if (typeof pb.repo !== "string" || !pb.repo) return null;
|
|
20
|
+
if (typeof pb.docs !== "string" || !pb.docs) return null;
|
|
21
|
+
const requires = Array.isArray(pb.requires) ? pb.requires.filter((r) => typeof r === "string") : void 0;
|
|
22
|
+
return {
|
|
23
|
+
version: 1,
|
|
24
|
+
repo: pb.repo,
|
|
25
|
+
docs: pb.docs,
|
|
26
|
+
requires
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Derive an IntentConfig from standard package.json fields when no explicit
|
|
31
|
+
* `intent` field is present. A package with a `skills/` directory signals
|
|
32
|
+
* intent support; `repo` and `docs` are derived from `repository` and
|
|
33
|
+
* `homepage`.
|
|
34
|
+
*/
|
|
35
|
+
function deriveIntentConfig(pkgJson) {
|
|
36
|
+
let repo = null;
|
|
37
|
+
if (typeof pkgJson.repository === "string") repo = pkgJson.repository;
|
|
38
|
+
else if (pkgJson.repository && typeof pkgJson.repository === "object" && typeof pkgJson.repository.url === "string") {
|
|
39
|
+
repo = pkgJson.repository.url;
|
|
40
|
+
repo = repo.replace(/^git\+/, "").replace(/\.git$/, "").replace(/^https?:\/\/github\.com\//, "");
|
|
41
|
+
}
|
|
42
|
+
const docs = typeof pkgJson.homepage === "string" ? pkgJson.homepage : void 0;
|
|
43
|
+
if (!repo) return null;
|
|
44
|
+
const intentPartial = pkgJson.intent;
|
|
45
|
+
const requires = intentPartial && Array.isArray(intentPartial.requires) ? intentPartial.requires.filter((r) => typeof r === "string") : void 0;
|
|
46
|
+
return {
|
|
47
|
+
version: 1,
|
|
48
|
+
repo,
|
|
49
|
+
docs: docs ?? "",
|
|
50
|
+
requires
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function discoverSkills(skillsDir, _baseName) {
|
|
54
|
+
const skills = [];
|
|
55
|
+
function walk(dir) {
|
|
56
|
+
let entries;
|
|
57
|
+
try {
|
|
58
|
+
entries = readdirSync(dir, {
|
|
59
|
+
withFileTypes: true,
|
|
60
|
+
encoding: "utf8"
|
|
61
|
+
});
|
|
62
|
+
} catch {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
if (!entry.isDirectory()) continue;
|
|
67
|
+
const childDir = join(dir, entry.name);
|
|
68
|
+
const skillFile = join(childDir, "SKILL.md");
|
|
69
|
+
if (existsSync(skillFile)) {
|
|
70
|
+
const fm = parseFrontmatter(skillFile);
|
|
71
|
+
const relName = relative(skillsDir, childDir).split(sep).join("/");
|
|
72
|
+
const desc = typeof fm?.description === "string" ? fm.description.replace(/\s+/g, " ").trim() : "";
|
|
73
|
+
skills.push({
|
|
74
|
+
name: typeof fm?.name === "string" ? fm.name : relName,
|
|
75
|
+
path: skillFile,
|
|
76
|
+
description: desc,
|
|
77
|
+
type: typeof fm?.type === "string" ? fm.type : void 0,
|
|
78
|
+
framework: typeof fm?.framework === "string" ? fm.framework : void 0
|
|
79
|
+
});
|
|
80
|
+
walk(childDir);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
walk(skillsDir);
|
|
85
|
+
return skills;
|
|
86
|
+
}
|
|
87
|
+
function topoSort(packages) {
|
|
88
|
+
const byName = new Map(packages.map((p) => [p.name, p]));
|
|
89
|
+
const visited = /* @__PURE__ */ new Set();
|
|
90
|
+
const sorted = [];
|
|
91
|
+
function visit(name) {
|
|
92
|
+
if (visited.has(name)) return;
|
|
93
|
+
visited.add(name);
|
|
94
|
+
const pkg = byName.get(name);
|
|
95
|
+
if (!pkg) return;
|
|
96
|
+
for (const dep of pkg.intent.requires ?? []) visit(dep);
|
|
97
|
+
sorted.push(pkg);
|
|
98
|
+
}
|
|
99
|
+
for (const pkg of packages) visit(pkg.name);
|
|
100
|
+
return sorted;
|
|
101
|
+
}
|
|
102
|
+
function getPackageDepth(packageRoot, projectRoot) {
|
|
103
|
+
return relative(projectRoot, packageRoot).split(sep).length;
|
|
104
|
+
}
|
|
105
|
+
function parseSemver(version) {
|
|
106
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(version);
|
|
107
|
+
if (!match) return null;
|
|
108
|
+
const prerelease = match[4] ? match[4].split(".").map((identifier) => {
|
|
109
|
+
return /^\d+$/.test(identifier) ? Number(identifier) : identifier;
|
|
110
|
+
}) : [];
|
|
111
|
+
return {
|
|
112
|
+
major: Number(match[1]),
|
|
113
|
+
minor: Number(match[2]),
|
|
114
|
+
patch: Number(match[3]),
|
|
115
|
+
prerelease
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function comparePrereleaseIdentifiers(a, b) {
|
|
119
|
+
if (a === void 0) return b === void 0 ? 0 : 1;
|
|
120
|
+
if (b === void 0) return -1;
|
|
121
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
122
|
+
if (typeof a === "number") return -1;
|
|
123
|
+
if (typeof b === "number") return 1;
|
|
124
|
+
return a.localeCompare(b);
|
|
125
|
+
}
|
|
126
|
+
function comparePackageVersions(a, b) {
|
|
127
|
+
const parsedA = parseSemver(a);
|
|
128
|
+
const parsedB = parseSemver(b);
|
|
129
|
+
if (!parsedA || !parsedB) {
|
|
130
|
+
if (parsedA) return 1;
|
|
131
|
+
if (parsedB) return -1;
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
for (const key of [
|
|
135
|
+
"major",
|
|
136
|
+
"minor",
|
|
137
|
+
"patch"
|
|
138
|
+
]) {
|
|
139
|
+
const diff = parsedA[key] - parsedB[key];
|
|
140
|
+
if (diff !== 0) return diff;
|
|
141
|
+
}
|
|
142
|
+
const length = Math.max(parsedA.prerelease.length, parsedB.prerelease.length);
|
|
143
|
+
for (let i = 0; i < length; i++) {
|
|
144
|
+
const diff = comparePrereleaseIdentifiers(parsedA.prerelease[i], parsedB.prerelease[i]);
|
|
145
|
+
if (diff !== 0) return diff;
|
|
146
|
+
}
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
function formatVariantWarning(name, variants, chosen) {
|
|
150
|
+
const uniqueVersions = new Set(variants.map((variant) => variant.version));
|
|
151
|
+
if (uniqueVersions.size <= 1) return null;
|
|
152
|
+
const details = variants.map((variant) => `${variant.version} at ${variant.packageRoot}`).join(", ");
|
|
153
|
+
return `Found ${variants.length} installed variants of ${name} across ${uniqueVersions.size} versions (${details}). Using ${chosen.version} from ${chosen.packageRoot}.`;
|
|
154
|
+
}
|
|
155
|
+
function toVersionConflict(packageName, variants, chosen) {
|
|
156
|
+
if (new Set(variants.map((variant) => variant.version)).size <= 1) return null;
|
|
157
|
+
return {
|
|
158
|
+
packageName,
|
|
159
|
+
chosen: {
|
|
160
|
+
version: chosen.version,
|
|
161
|
+
packageRoot: chosen.packageRoot
|
|
162
|
+
},
|
|
163
|
+
variants
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async function scanForIntents(root) {
|
|
167
|
+
const projectRoot = root ?? process.cwd();
|
|
168
|
+
const packageManager = detectPackageManager(projectRoot);
|
|
169
|
+
const nodeModulesDir = join(projectRoot, "node_modules");
|
|
170
|
+
const explicitGlobalNodeModules = process.env.INTENT_GLOBAL_NODE_MODULES?.trim() || null;
|
|
171
|
+
const packages = [];
|
|
172
|
+
const warnings = [];
|
|
173
|
+
const conflicts = [];
|
|
174
|
+
const nodeModules = {
|
|
175
|
+
local: {
|
|
176
|
+
path: nodeModulesDir,
|
|
177
|
+
detected: true,
|
|
178
|
+
exists: existsSync(nodeModulesDir),
|
|
179
|
+
scanned: false
|
|
180
|
+
},
|
|
181
|
+
global: {
|
|
182
|
+
path: explicitGlobalNodeModules,
|
|
183
|
+
detected: Boolean(explicitGlobalNodeModules),
|
|
184
|
+
exists: explicitGlobalNodeModules ? existsSync(explicitGlobalNodeModules) : false,
|
|
185
|
+
scanned: false,
|
|
186
|
+
source: explicitGlobalNodeModules ? "INTENT_GLOBAL_NODE_MODULES" : void 0
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const resolutionRoots = [nodeModulesDir];
|
|
190
|
+
const packageIndexes = /* @__PURE__ */ new Map();
|
|
191
|
+
const packageJsonCache = /* @__PURE__ */ new Map();
|
|
192
|
+
const packageVariants = /* @__PURE__ */ new Map();
|
|
193
|
+
function rememberVariant(pkg) {
|
|
194
|
+
let variants = packageVariants.get(pkg.name);
|
|
195
|
+
if (!variants) {
|
|
196
|
+
variants = /* @__PURE__ */ new Map();
|
|
197
|
+
packageVariants.set(pkg.name, variants);
|
|
198
|
+
}
|
|
199
|
+
variants.set(pkg.packageRoot, {
|
|
200
|
+
version: pkg.version,
|
|
201
|
+
packageRoot: pkg.packageRoot
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function ensureGlobalNodeModules() {
|
|
205
|
+
if (!nodeModules.global.path && !explicitGlobalNodeModules) {
|
|
206
|
+
const detected = detectGlobalNodeModules(packageManager);
|
|
207
|
+
nodeModules.global.path = detected.path;
|
|
208
|
+
nodeModules.global.source = detected.source;
|
|
209
|
+
nodeModules.global.detected = Boolean(detected.path);
|
|
210
|
+
nodeModules.global.exists = detected.path ? existsSync(detected.path) : false;
|
|
211
|
+
}
|
|
212
|
+
if (nodeModules.global.exists && nodeModules.global.path && nodeModules.global.path !== nodeModulesDir && !resolutionRoots.includes(nodeModules.global.path)) resolutionRoots.push(nodeModules.global.path);
|
|
213
|
+
}
|
|
214
|
+
function readPkgJson(dirPath) {
|
|
215
|
+
if (packageJsonCache.has(dirPath)) return packageJsonCache.get(dirPath) ?? null;
|
|
216
|
+
try {
|
|
217
|
+
const pkgJson = JSON.parse(readFileSync(join(dirPath, "package.json"), "utf8"));
|
|
218
|
+
packageJsonCache.set(dirPath, pkgJson);
|
|
219
|
+
return pkgJson;
|
|
220
|
+
} catch {
|
|
221
|
+
packageJsonCache.set(dirPath, null);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function scanTarget(target) {
|
|
226
|
+
if (!target.path || !target.exists || target.scanned) return;
|
|
227
|
+
target.scanned = true;
|
|
228
|
+
for (const dirPath of listNodeModulesPackageDirs(target.path)) tryRegister(dirPath, "unknown");
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Try to register a package with a skills/ directory. Reads its
|
|
232
|
+
* package.json, validates intent config, discovers skills, and pushes
|
|
233
|
+
* to `packages`. Returns true if the package was registered.
|
|
234
|
+
*/
|
|
235
|
+
function tryRegister(dirPath, fallbackName) {
|
|
236
|
+
const skillsDir = join(dirPath, "skills");
|
|
237
|
+
if (!existsSync(skillsDir)) return false;
|
|
238
|
+
const pkgJson = readPkgJson(dirPath);
|
|
239
|
+
if (!pkgJson) {
|
|
240
|
+
warnings.push(`Could not read package.json for ${dirPath}`);
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
const name = typeof pkgJson.name === "string" ? pkgJson.name : fallbackName;
|
|
244
|
+
const version = typeof pkgJson.version === "string" ? pkgJson.version : "0.0.0";
|
|
245
|
+
const intent = validateIntentField(name, pkgJson.intent) ?? deriveIntentConfig(pkgJson);
|
|
246
|
+
if (!intent) {
|
|
247
|
+
warnings.push(`${name} has a skills/ directory but could not determine repo/docs from package.json (add a "repository" field or explicit "intent" config)`);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
const candidate = {
|
|
251
|
+
name,
|
|
252
|
+
version,
|
|
253
|
+
intent,
|
|
254
|
+
skills: discoverSkills(skillsDir, name),
|
|
255
|
+
packageRoot: dirPath
|
|
256
|
+
};
|
|
257
|
+
const existingIndex = packageIndexes.get(name);
|
|
258
|
+
if (existingIndex === void 0) {
|
|
259
|
+
rememberVariant(candidate);
|
|
260
|
+
packageIndexes.set(name, packages.push(candidate) - 1);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
const existing = packages[existingIndex];
|
|
264
|
+
if (existing.packageRoot === candidate.packageRoot) return false;
|
|
265
|
+
rememberVariant(existing);
|
|
266
|
+
rememberVariant(candidate);
|
|
267
|
+
const existingDepth = getPackageDepth(existing.packageRoot, projectRoot);
|
|
268
|
+
const candidateDepth = getPackageDepth(candidate.packageRoot, projectRoot);
|
|
269
|
+
if (candidateDepth < existingDepth || candidateDepth === existingDepth && comparePackageVersions(candidate.version, existing.version) > 0) packages[existingIndex] = candidate;
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
scanTarget(nodeModules.local);
|
|
273
|
+
const walkVisited = /* @__PURE__ */ new Set();
|
|
274
|
+
function walkDeps(pkgDir, pkgName) {
|
|
275
|
+
if (walkVisited.has(pkgDir)) return;
|
|
276
|
+
walkVisited.add(pkgDir);
|
|
277
|
+
const pkgJson = readPkgJson(pkgDir);
|
|
278
|
+
if (!pkgJson) {
|
|
279
|
+
warnings.push(`Could not read package.json for ${pkgName} (skipping dependency walk)`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
for (const depName of getDeps(pkgJson)) {
|
|
283
|
+
const depDir = resolveDepDir(depName, pkgDir, pkgName, resolutionRoots);
|
|
284
|
+
if (!depDir || walkVisited.has(depDir)) continue;
|
|
285
|
+
tryRegister(depDir, depName);
|
|
286
|
+
walkDeps(depDir, depName);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function walkKnownPackages() {
|
|
290
|
+
for (const pkg of [...packages]) walkDeps(pkg.packageRoot, pkg.name);
|
|
291
|
+
}
|
|
292
|
+
function walkProjectDeps() {
|
|
293
|
+
let projectPkg = null;
|
|
294
|
+
try {
|
|
295
|
+
projectPkg = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf8"));
|
|
296
|
+
} catch (err) {
|
|
297
|
+
if (!(err && typeof err === "object" && "code" in err && err.code === "ENOENT")) warnings.push(`Could not read project package.json: ${err instanceof Error ? err.message : String(err)}`);
|
|
298
|
+
}
|
|
299
|
+
if (!projectPkg) return;
|
|
300
|
+
for (const depName of getDeps(projectPkg, true)) {
|
|
301
|
+
const depDir = resolveDepDir(depName, projectRoot, depName, resolutionRoots);
|
|
302
|
+
if (depDir && !walkVisited.has(depDir)) walkDeps(depDir, depName);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
walkKnownPackages();
|
|
306
|
+
walkProjectDeps();
|
|
307
|
+
if (explicitGlobalNodeModules || packages.length === 0 || !nodeModules.local.exists) {
|
|
308
|
+
ensureGlobalNodeModules();
|
|
309
|
+
scanTarget(nodeModules.global);
|
|
310
|
+
walkKnownPackages();
|
|
311
|
+
walkProjectDeps();
|
|
312
|
+
}
|
|
313
|
+
if (!nodeModules.local.exists && !nodeModules.global.exists) return {
|
|
314
|
+
packageManager,
|
|
315
|
+
packages,
|
|
316
|
+
warnings,
|
|
317
|
+
conflicts,
|
|
318
|
+
nodeModules
|
|
319
|
+
};
|
|
320
|
+
for (const pkg of packages) {
|
|
321
|
+
const variants = packageVariants.get(pkg.name);
|
|
322
|
+
if (!variants) continue;
|
|
323
|
+
const conflict = toVersionConflict(pkg.name, [...variants.values()], pkg);
|
|
324
|
+
if (conflict) conflicts.push(conflict);
|
|
325
|
+
const warning = formatVariantWarning(pkg.name, [...variants.values()], pkg);
|
|
326
|
+
if (warning) warnings.push(warning);
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
packageManager,
|
|
330
|
+
packages: topoSort(packages),
|
|
331
|
+
warnings,
|
|
332
|
+
conflicts,
|
|
333
|
+
nodeModules
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
//#endregion
|
|
338
|
+
export { scanForIntents as t };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/setup.d.ts
|
|
2
|
+
interface AddLibraryBinResult {
|
|
3
|
+
shim: string | null;
|
|
4
|
+
skipped: string | null;
|
|
5
|
+
}
|
|
6
|
+
interface EditPackageJsonResult {
|
|
7
|
+
added: Array<string>;
|
|
8
|
+
alreadyPresent: Array<string>;
|
|
9
|
+
}
|
|
10
|
+
interface SetupGithubActionsResult {
|
|
11
|
+
workflows: Array<string>;
|
|
12
|
+
skipped: Array<string>;
|
|
13
|
+
}
|
|
14
|
+
interface MonorepoResult<T> {
|
|
15
|
+
package: string;
|
|
16
|
+
result: T;
|
|
17
|
+
}
|
|
18
|
+
declare function runAddLibraryBin(root: string): AddLibraryBinResult;
|
|
19
|
+
declare function runEditPackageJson(root: string): EditPackageJsonResult;
|
|
20
|
+
declare function readWorkspacePatterns(root: string): Array<string> | null;
|
|
21
|
+
declare function findWorkspaceRoot(start: string): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Find workspace packages that contain at least one SKILL.md file.
|
|
24
|
+
*/
|
|
25
|
+
declare function findPackagesWithSkills(root: string): Array<string>;
|
|
26
|
+
declare function runEditPackageJsonAll(root: string): Array<MonorepoResult<EditPackageJsonResult>> | EditPackageJsonResult;
|
|
27
|
+
declare function runAddLibraryBinAll(root: string): Array<MonorepoResult<AddLibraryBinResult>> | AddLibraryBinResult;
|
|
28
|
+
declare function runSetupGithubActions(root: string, metaDir: string): SetupGithubActionsResult;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { findPackagesWithSkills as a, runAddLibraryBin as c, runEditPackageJsonAll as d, runSetupGithubActions as f, SetupGithubActionsResult as i, runAddLibraryBinAll as l, EditPackageJsonResult as n, findWorkspaceRoot as o, MonorepoResult as r, readWorkspacePatterns as s, AddLibraryBinResult as t, runEditPackageJson as u };
|