Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// @ts-check

// @ts-ignore Needed due to moduleResolution Node vs Bundler
import { tanstackConfig } from '@tanstack/eslint-config'
import unusedImports from 'eslint-plugin-unused-imports'

/** @type {import('eslint').Linter.Config[]} */
export default [
const config = [
...tanstackConfig,
{
name: 'tanstack/temp',
Expand All @@ -21,3 +20,5 @@ export default [
},
},
]

export default config
7 changes: 7 additions & 0 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
"entry": ["scripts/*.ts"]
},
"packages/intent": {
"entry": [
"src/index.ts",
"src/cli.ts",
"src/setup.ts",
"src/intent-library.ts",
"src/library-scanner.ts"
],
"ignore": ["meta/**"]
}
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@changesets/cli": "^2.29.8",
"@faker-js/faker": "^10.2.0",
"@svitejs/changesets-changelog-github-compact": "^1.2.0",
"@tanstack/eslint-config": "0.3.4",
"@tanstack/eslint-config": "0.4.0",
"@types/node": "^25.0.7",
"eslint": "^9.39.2",
"eslint-plugin-unused-imports": "^4.3.0",
Expand Down
20 changes: 10 additions & 10 deletions packages/intent/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#!/usr/bin/env node

import { existsSync, readdirSync, readFileSync } from 'node:fs'
import { existsSync, readFileSync, readdirSync } from 'node:fs'
import { dirname, join, relative, sep } from 'node:path'
import { fileURLToPath } from 'node:url'
import { parse as parseYaml } from 'yaml'
import { computeSkillNameWidth, printSkillTree, printTable } from './display.js'
import { scanForIntents } from './scanner.js'
import type { ScanResult } from './types.js'
import { findSkillFiles, parseFrontmatter } from './utils.js'
import type { ScanResult } from './types.js'

// ---------------------------------------------------------------------------
// Helpers
Expand All @@ -22,7 +22,7 @@ function getMetaDir(): string {
// Commands
// ---------------------------------------------------------------------------

async function cmdList(args: string[]): Promise<void> {
async function cmdList(args: Array<string>): Promise<void> {
const jsonOutput = args.includes('--json')

let result: ScanResult
Expand Down Expand Up @@ -91,7 +91,7 @@ async function cmdList(args: string[]): Promise<void> {
}
}

function cmdMeta(args: string[]): void {
function cmdMeta(args: Array<string>): void {
const metaDir = getMetaDir()

if (!existsSync(metaDir)) {
Expand Down Expand Up @@ -151,7 +151,7 @@ function cmdMeta(args: string[]): void {
console.log(`Path: node_modules/@tanstack/intent/meta/<name>/SKILL.md`)
}

function collectPackagingWarnings(root: string): string[] {
function collectPackagingWarnings(root: string): Array<string> {
const pkgJsonPath = join(root, 'package.json')
if (!existsSync(pkgJsonPath)) return []

Expand All @@ -163,7 +163,7 @@ function collectPackagingWarnings(root: string): string[] {
return [`Could not parse package.json: ${msg}`]
}

const warnings: string[] = []
const warnings: Array<string> = []

const devDeps = pkgJson.devDependencies as Record<string, string> | undefined
if (!devDeps?.['@tanstack/intent']) {
Expand All @@ -183,7 +183,7 @@ function collectPackagingWarnings(root: string): string[] {
)
}

const files = pkgJson.files as string[] | undefined
const files = pkgJson.files as Array<string> | undefined
if (Array.isArray(files)) {
if (!files.includes('skills')) {
warnings.push(
Expand All @@ -205,7 +205,7 @@ function collectPackagingWarnings(root: string): string[] {
return warnings
}

function cmdValidate(args: string[]): void {
function cmdValidate(args: Array<string>): void {
const targetDir = args[0] ?? 'skills'
const skillsDir = join(process.cwd(), targetDir)

Expand All @@ -219,7 +219,7 @@ function cmdValidate(args: string[]): void {
message: string
}

const errors: ValidationError[] = []
const errors: Array<ValidationError> = []
const skillFiles = findSkillFiles(skillsDir)

if (skillFiles.length === 0) {
Expand Down Expand Up @@ -337,7 +337,7 @@ function cmdValidate(args: string[]): void {

const warnings = collectPackagingWarnings(process.cwd())

const printWarnings = (log: (...args: unknown[]) => void): void => {
const printWarnings = (log: (...args: Array<unknown>) => void): void => {
if (warnings.length === 0) return
log(`\n⚠ Packaging warnings:`)
for (const w of warnings) log(` ${w}`)
Expand Down
13 changes: 8 additions & 5 deletions packages/intent/src/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ function padColumn(text: string, width: number): string {
return text.length >= width ? text + ' ' : text.padEnd(width)
}

export function printTable(headers: string[], rows: string[][]): void {
export function printTable(
headers: Array<string>,
rows: Array<Array<string>>,
): void {
const widths = headers.map(
(h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? '').length)) + 2,
)
Expand Down Expand Up @@ -46,11 +49,11 @@ function printSkillLine(
}

export function printSkillTree(
skills: SkillDisplay[],
skills: Array<SkillDisplay>,
opts: { nameWidth: number; showTypes: boolean },
): void {
const roots: string[] = []
const children = new Map<string, SkillDisplay[]>()
const roots: Array<string> = []
const children = new Map<string, Array<SkillDisplay>>()

for (const skill of skills) {
const slashIdx = skill.name.indexOf('/')
Expand Down Expand Up @@ -83,7 +86,7 @@ export function printSkillTree(
}

export function computeSkillNameWidth(
allPackageSkills: SkillDisplay[][],
allPackageSkills: Array<Array<SkillDisplay>>,
): number {
let max = 0
for (const skills of allPackageSkills) {
Expand Down
22 changes: 8 additions & 14 deletions packages/intent/src/feedback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function resolveFrequency(root: string): string {
// Feedback payload validation
// ---------------------------------------------------------------------------

const REQUIRED_FIELDS: (keyof FeedbackPayload)[] = [
const REQUIRED_FIELDS: Array<keyof FeedbackPayload> = [
'skill',
'package',
'skillVersion',
Expand All @@ -96,19 +96,16 @@ const REQUIRED_FIELDS: (keyof FeedbackPayload)[] = [

export function validatePayload(payload: unknown): {
valid: boolean
errors: string[]
errors: Array<string>
} {
const errors: string[] = []
const errors: Array<string> = []
if (!payload || typeof payload !== 'object') {
return { valid: false, errors: ['Payload must be a JSON object'] }
}
const obj = payload as Record<string, unknown>

for (const field of REQUIRED_FIELDS) {
if (
typeof obj[field] !== 'string' ||
(obj[field] as string).trim() === ''
) {
if (typeof obj[field] !== 'string' || obj[field].trim() === '') {
errors.push(`Missing or empty required field: ${field}`)
}
}
Expand Down Expand Up @@ -138,7 +135,7 @@ export function validatePayload(payload: unknown): {
// Meta-feedback payload validation
// ---------------------------------------------------------------------------

const META_REQUIRED_FIELDS: (keyof MetaFeedbackPayload)[] = [
const META_REQUIRED_FIELDS: Array<keyof MetaFeedbackPayload> = [
'metaSkill',
'library',
'agentUsed',
Expand All @@ -162,19 +159,16 @@ const VALID_QUALITY_RATINGS = ['good', 'mixed', 'bad']

export function validateMetaPayload(payload: unknown): {
valid: boolean
errors: string[]
errors: Array<string>
} {
const errors: string[] = []
const errors: Array<string> = []
if (!payload || typeof payload !== 'object') {
return { valid: false, errors: ['Payload must be a JSON object'] }
}
const obj = payload as Record<string, unknown>

for (const field of META_REQUIRED_FIELDS) {
if (
typeof obj[field] !== 'string' ||
(obj[field] as string).trim() === ''
) {
if (typeof obj[field] !== 'string' || obj[field].trim() === '') {
errors.push(`Missing or empty required field: ${field}`)
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/intent/src/intent-library.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env node

import { computeSkillNameWidth, printSkillTree, printTable } from './display.js'
import type { LibraryScanResult } from './library-scanner.js'
import { scanLibrary } from './library-scanner.js'
import type { LibraryScanResult } from './library-scanner.js'

// ---------------------------------------------------------------------------
// Commands
Expand Down
24 changes: 12 additions & 12 deletions packages/intent/src/library-scanner.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Dirent } from 'node:fs'
import { existsSync, readdirSync, readFileSync } from 'node:fs'
import { existsSync, readFileSync, readdirSync } from 'node:fs'
import { dirname, join, relative, sep } from 'node:path'
import type { SkillEntry } from './types.js'
import { getDeps, parseFrontmatter, resolveDepDir } from './utils.js'
import type { SkillEntry } from './types.js'
import type { Dirent } from 'node:fs'

// ---------------------------------------------------------------------------
// Types
Expand All @@ -12,12 +12,12 @@ export interface LibraryPackage {
name: string
version: string
description: string
skills: SkillEntry[]
skills: Array<SkillEntry>
}

export interface LibraryScanResult {
packages: LibraryPackage[]
warnings: string[]
packages: Array<LibraryPackage>
warnings: Array<string>
}

// ---------------------------------------------------------------------------
Expand All @@ -34,7 +34,7 @@ function readPkgJson(dir: string): Record<string, unknown> | null {

function findHomeDir(scriptPath: string): string | null {
let dir = dirname(scriptPath)
while (true) {
for (;;) {
if (existsSync(join(dir, 'package.json'))) return dir
const parent = dirname(dir)
if (parent === dir) return null
Expand All @@ -48,11 +48,11 @@ function hasIntentBin(pkg: Record<string, unknown>): boolean {
return 'intent' in (bin as Record<string, unknown>)
}

function discoverSkills(skillsDir: string): SkillEntry[] {
const skills: SkillEntry[] = []
function discoverSkills(skillsDir: string): Array<SkillEntry> {
const skills: Array<SkillEntry> = []

function walk(dir: string): void {
let entries: Dirent<string>[]
let entries: Array<Dirent<string>>
try {
entries = readdirSync(dir, { withFileTypes: true, encoding: 'utf8' })
} catch {
Expand Down Expand Up @@ -94,8 +94,8 @@ export async function scanLibrary(
projectRoot?: string,
): Promise<LibraryScanResult> {
const nodeModulesDir = join(projectRoot ?? process.cwd(), 'node_modules')
const packages: LibraryPackage[] = []
const warnings: string[] = []
const packages: Array<LibraryPackage> = []
const warnings: Array<string> = []
const visited = new Set<string>()

const homeDir = findHomeDir(scriptPath)
Expand Down
27 changes: 15 additions & 12 deletions packages/intent/src/scanner.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Dirent } from 'node:fs'
import { existsSync, readdirSync, readFileSync } from 'node:fs'
import { existsSync, readFileSync, readdirSync } from 'node:fs'
import { join, relative, sep } from 'node:path'
import { getDeps, parseFrontmatter, resolveDepDir } from './utils.js'
import type {
IntentConfig,
IntentPackage,
ScanResult,
SkillEntry,
} from './types.js'
import { getDeps, parseFrontmatter, resolveDepDir } from './utils.js'
import type { Dirent } from 'node:fs'

// ---------------------------------------------------------------------------
// Package manager detection
Expand Down Expand Up @@ -118,11 +118,14 @@ function deriveIntentConfig(
// Skill discovery within a package
// ---------------------------------------------------------------------------

function discoverSkills(skillsDir: string, _baseName: string): SkillEntry[] {
const skills: SkillEntry[] = []
function discoverSkills(
skillsDir: string,
_baseName: string,
): Array<SkillEntry> {
const skills: Array<SkillEntry> = []

function walk(dir: string): void {
let entries: Dirent<string>[]
let entries: Array<Dirent<string>>
try {
entries = readdirSync(dir, { withFileTypes: true, encoding: 'utf8' })
} catch {
Expand Down Expand Up @@ -161,10 +164,10 @@ function discoverSkills(skillsDir: string, _baseName: string): SkillEntry[] {
// Topological sort on requires
// ---------------------------------------------------------------------------

function topoSort(packages: IntentPackage[]): IntentPackage[] {
function topoSort(packages: Array<IntentPackage>): Array<IntentPackage> {
const byName = new Map(packages.map((p) => [p.name, p]))
const visited = new Set<string>()
const sorted: IntentPackage[] = []
const sorted: Array<IntentPackage> = []

function visit(name: string): void {
if (visited.has(name)) return
Expand Down Expand Up @@ -192,8 +195,8 @@ export async function scanForIntents(root?: string): Promise<ScanResult> {
const packageManager = detectPackageManager(projectRoot)
const nodeModulesDir = join(projectRoot, 'node_modules')

const packages: IntentPackage[] = []
const warnings: string[] = []
const packages: Array<IntentPackage> = []
const warnings: Array<string> = []

if (!existsSync(nodeModulesDir)) {
return { packageManager, packages, warnings }
Expand All @@ -202,7 +205,7 @@ export async function scanForIntents(root?: string): Promise<ScanResult> {
// Collect all package directories to check
const packageDirs: Array<{ dirPath: string }> = []

let topEntries: Dirent<string>[]
let topEntries: Array<Dirent<string>>
try {
topEntries = readdirSync(nodeModulesDir, {
withFileTypes: true,
Expand All @@ -218,7 +221,7 @@ export async function scanForIntents(root?: string): Promise<ScanResult> {

if (entry.name.startsWith('@')) {
// Scoped package — check children
let scopedEntries: Dirent<string>[]
let scopedEntries: Array<Dirent<string>>
try {
scopedEntries = readdirSync(dirPath, {
withFileTypes: true,
Expand Down
Loading
Loading