@tanstack/intent 0.0.14 → 0.0.20

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.
Files changed (36) hide show
  1. package/README.md +42 -5
  2. package/dist/cli.d.mts +6 -1
  3. package/dist/cli.mjs +253 -170
  4. package/dist/display-CuCDLPP_.mjs +3 -0
  5. package/dist/index.d.mts +10 -11
  6. package/dist/index.mjs +37 -14
  7. package/dist/install-prompt-C0M-U3WZ.mjs +59 -0
  8. package/dist/intent-library.mjs +5 -49
  9. package/dist/{library-scanner-B1tmOzwf.mjs → library-scanner-DBOEhfm8.mjs} +4 -5
  10. package/dist/library-scanner.d.mts +5 -5
  11. package/dist/library-scanner.mjs +2 -2
  12. package/dist/scanner-BHPl60jH.mjs +5 -0
  13. package/dist/scanner-DVepyEwz.mjs +365 -0
  14. package/dist/setup-B-zdCBu4.d.mts +36 -0
  15. package/dist/setup-mGV2dZrq.mjs +367 -0
  16. package/dist/setup.d.mts +2 -2
  17. package/dist/setup.mjs +3 -2
  18. package/dist/{staleness-DJfMKH62.mjs → staleness-DZKvsLVq.mjs} +24 -3
  19. package/dist/staleness-Dr5-5wj5.mjs +4 -0
  20. package/dist/{types-BmnI8kFB.d.mts → types-ddLtccfV.d.mts} +30 -7
  21. package/dist/utils-BfjM1mQe.mjs +152 -0
  22. package/dist/utils-D7OKi0Rn.mjs +3 -0
  23. package/meta/domain-discovery/SKILL.md +95 -20
  24. package/meta/feedback-collection/SKILL.md +20 -1
  25. package/meta/generate-skill/SKILL.md +56 -5
  26. package/meta/templates/workflows/check-skills.yml +4 -4
  27. package/meta/templates/workflows/{notify-playbooks.yml → notify-intent.yml} +4 -4
  28. package/meta/tree-generator/SKILL.md +2 -2
  29. package/package.json +9 -5
  30. package/dist/scanner-CECGXgox.mjs +0 -4
  31. package/dist/scanner-CY40iozO.mjs +0 -218
  32. package/dist/setup-CANkTz55.d.mts +0 -18
  33. package/dist/setup-Nif1-nhS.mjs +0 -211
  34. package/dist/staleness-C1h7RuZ9.mjs +0 -4
  35. package/dist/utils-CDJzAdjD.mjs +0 -79
  36. /package/dist/{display-D_XzuGnu.mjs → display-DhsUxNJW.mjs} +0 -0
@@ -76,23 +76,33 @@ These rules override any other reasoning. No exceptions.
76
76
  STOP and WAIT for their reply. Do not answer your own questions. Do
77
77
  not infer answers from documentation. Do not skip questions because
78
78
  you believe you already know the answer.
79
- 3. **Do not convert open-ended questions into multiple-choice,
79
+ 3. **Never ask factual questions you can answer by searching the
80
+ codebase.** Before asking any question, determine whether the answer
81
+ is a deterministic fact (how many X exist, what versions are
82
+ supported, which files implement Y) or a judgment call (which ones
83
+ matter, what should we prioritize, what do developers struggle with).
84
+ Factual questions must be answered by searching the code — grep,
85
+ glob, read files. Only ask the maintainer for priorities, opinions,
86
+ trade-offs, and implicit knowledge that cannot be found in code or
87
+ docs. Asking the maintainer a question whose answer is sitting in
88
+ the codebase wastes their time and erodes trust in the process.
89
+ 4. **Do not convert open-ended questions into multiple-choice,
80
90
  yes/no, or confirmation prompts.** The question templates in each
81
91
  sub-section are open-ended by design. Present them as open-ended
82
92
  questions. The maintainer's unprompted answers surface knowledge that
83
93
  pre-structured options suppress.
84
- 4. **Minimum question counts are enforced.** Each sub-section specifies
94
+ 5. **Minimum question counts are enforced.** Each sub-section specifies
85
95
  a question count range (e.g. "2–4 questions"). You must ask at least
86
96
  the minimum number. Asking zero questions in any sub-section is a
87
97
  protocol violation.
88
- 5. **STOP gates are mandatory.** At the boundaries marked `── STOP ──`
98
+ 6. **STOP gates are mandatory.** At the boundaries marked `── STOP ──`
89
99
  below, you must halt execution and wait for the maintainer's response
90
100
  or acknowledgment before proceeding. Do not continue past a STOP gate
91
101
  in the same message.
92
- 6. **If the maintainer asks to skip an interview phase**, explain the
102
+ 7. **If the maintainer asks to skip an interview phase**, explain the
93
103
  value of the phase and what will be lost. Proceed with skipping only
94
104
  if they confirm a second time.
95
- 7. **Rich documentation makes interviews MORE valuable, not less.**
105
+ 8. **Rich documentation makes interviews MORE valuable, not less.**
96
106
  When docs are comprehensive, the interview surfaces what docs miss:
97
107
  implicit knowledge, AI-specific failure modes, undocumented tradeoffs,
98
108
  and the maintainer's prioritization of what matters most. Never
@@ -115,6 +125,15 @@ reading exhaustively yet.
115
125
  4. **AGENTS.md or .cursorrules** — if the library already has agent
116
126
  guidance, read it. This is high-signal for what the maintainer
117
127
  considers important
128
+ 5. **All in-repo documentation** — list every `.md` file in the `docs/`
129
+ directory (and any other documentation directories like `guides/`,
130
+ `reference/`, `wiki/`). Read every file. This is NOT the exhaustive
131
+ external doc reading from Phase 3 — this is reading what the
132
+ maintainer committed to the repository, which is fast and
133
+ high-signal. In-repo docs often contain migration guides, backward
134
+ compatibility notes, architecture decisions, and other context that
135
+ prevents you from asking factual questions the docs already answer.
136
+ Do not sample a subset — read them all before the first interview.
118
137
 
119
138
  ### 1b — Read peer dependency constraints
120
139
 
@@ -258,13 +277,57 @@ sample a subset and extrapolate.
258
277
  3. **API reference** — scan for exports, type signatures, option shapes
259
278
  4. **Changelog for major versions** — API renames, removed exports,
260
279
  behavioral changes
261
- 5. **GitHub issues and discussions** — scan for frequently reported
262
- confusion, common misunderstandings, recurring questions. Also look
263
- for what users are implicitly arguing for architecturally — not just
264
- "people are confused about X" but "users keep expecting X to work
265
- like Y, which reveals a tension between [design force] and [design force]."
266
- If no web access, check for FAQ.md, TROUBLESHOOTING.md, or docs/faq
267
- as proxies.
280
+ 5. **GitHub issues and discussions** — this is one of the highest-yield
281
+ sources for failure modes and skill content. Docs describe intended
282
+ behavior; issues reveal actual behavior and real developer confusion.
283
+
284
+ **How to search.** Use `gh search issues` and `gh search prs` (or the
285
+ GitHub web search UI) against the library's repo. Run multiple passes:
286
+ - **High-engagement issues:** sort by reactions or comments to find the
287
+ problems that affect the most developers. These are skill-worthy
288
+ even if already fixed — agents trained on older data still hit them.
289
+ - **Label-based scans:** look for labels like `bug`, `question`,
290
+ `documentation`, `breaking-change`, `good first issue`, `FAQ`,
291
+ `help wanted`. Each label category yields different signal:
292
+ - `bug` + `closed` → failure modes with known fixes (wrong/correct pairs)
293
+ - `question` → developer confusion that skills should preempt
294
+ - `breaking-change` → migration-boundary mistakes
295
+ - **Keyword searches:** search for the skill's primary APIs, hooks,
296
+ and config options by name. E.g. `useQuery stale` or `hydration SSR`.
297
+ - **Recent vs. historical:** scan the last 6–12 months of open issues
298
+ for current pain points. Then scan older closed issues for patterns
299
+ that are now fixed but still appear in agent training data.
300
+
301
+ **GitHub Discussions** are equally important when the repo uses them.
302
+ Discussions surface "how do I..." patterns and architectural questions
303
+ that issues don't capture. Search the Discussions tab (or use
304
+ `gh api` to query discussions) for:
305
+ - Unanswered or long-thread questions (signal: docs are insufficient)
306
+ - Threads marked as "Answered" with a non-obvious solution (skill content)
307
+ - Recurring themes across multiple threads (systemic confusion)
308
+
309
+ **What to extract from issues/discussions:**
310
+ - Frequently reported confusion patterns → candidate failure modes
311
+ - Workarounds that developers use before a fix ships → "wrong pattern"
312
+ examples that agents will reproduce
313
+ - Recurring "how do I X with Y" threads → composition skill candidates
314
+ - Misunderstandings about defaults or config → skill content gaps
315
+ - Feature requests with many upvotes that change API design → signals
316
+ of where the API surface is unintuitive
317
+ - What users are implicitly arguing for architecturally — not just
318
+ "people are confused about X" but "users keep expecting X to work
319
+ like Y, which reveals a tension between [design force] and
320
+ [design force]"
321
+
322
+ **What NOT to extract:** one-off bugs already fixed, feature requests
323
+ unrelated to current API surface, issues about build tooling or CI
324
+ that don't affect library usage patterns.
325
+
326
+ **Fallback.** If no web access is available, check for FAQ.md,
327
+ TROUBLESHOOTING.md, docs/faq, or KNOWN_ISSUES.md as proxies. Also
328
+ scan the repo's `.github/ISSUE_TEMPLATE/` for hints about common
329
+ issue categories.
330
+
268
331
  6. **Source code** — verify ambiguities from docs, check defaults, find
269
332
  assertions and invariant checks. For monorepos, read the 2–3 core
270
333
  packages deeply. For adapter packages, read one representative adapter
@@ -289,6 +352,10 @@ Log every:
289
352
  rejects a subtype of X
290
353
  - Source assertion: any `if (!x) throw`, `invariant()`, or `assert()` with
291
354
  the error message text
355
+ - Issue/discussion pattern: any recurring confusion, workaround, or
356
+ misunderstanding surfaced from GitHub issues or discussions — note the
357
+ issue/discussion URL, the core misunderstanding, and whether it's
358
+ resolved or still active
292
359
 
293
360
  ### What to extract from migration guides specifically
294
361
 
@@ -390,14 +457,16 @@ For each skill, extract failure modes that pass all three tests:
390
457
 
391
458
  **Where to find them:**
392
459
 
393
- | Source | What to extract |
394
- | -------------------- | ------------------------------------------------------------------ |
395
- | Migration guides | Every breaking change → old pattern is the wrong code |
396
- | Doc callouts | Any "note", "warning", "avoid" with surrounding context |
397
- | Source assertions | `throw` and `invariant()` messages describe the failure |
398
- | Default values | Undocumented or surprising defaults that cause wrong behavior |
399
- | Type precision | Source type more restrictive than docs imply |
400
- | Environment branches | `typeof window`, SSR flags, `NODE_ENV` — behavior differs silently |
460
+ | Source | What to extract |
461
+ | -------------------- | -------------------------------------------------------------------- |
462
+ | Migration guides | Every breaking change → old pattern is the wrong code |
463
+ | Doc callouts | Any "note", "warning", "avoid" with surrounding context |
464
+ | Source assertions | `throw` and `invariant()` messages describe the failure |
465
+ | Default values | Undocumented or surprising defaults that cause wrong behavior |
466
+ | Type precision | Source type more restrictive than docs imply |
467
+ | Environment branches | `typeof window`, SSR flags, `NODE_ENV` — behavior differs silently |
468
+ | GitHub issues | Recurring bug reports with workarounds → wrong/correct code pairs |
469
+ | GitHub discussions | "How do I…" threads with non-obvious answers → missing skill content |
401
470
 
402
471
  Target 3 failure modes per skill minimum. Complex skills target 5–6.
403
472
 
@@ -574,6 +643,12 @@ Adapt from this bank of gap-targeted question templates:
574
643
  What should an agent know about using them together?"
575
644
  - "The API reference shows [type signature], but the guide examples use
576
645
  a different shape. Which is accurate?"
646
+ - "I found [N] GitHub issues/discussions where developers struggled with
647
+ [X]. The common workaround seems to be [Y] — is that the recommended
648
+ approach, or is there a better pattern that should be documented?"
649
+ - "GitHub discussions show developers repeatedly asking how to combine
650
+ [feature A] with [feature B]. Is there an intended integration pattern,
651
+ or is this a gap in the current API?"
577
652
 
578
653
  ### 4c — AI-agent-specific failure modes (2–4 questions)
579
654
 
@@ -36,7 +36,7 @@ during the session:
36
36
 
37
37
  - **Loaded and used:** Skills you read and actively followed.
38
38
  - **Available but not loaded:** Skills that were installed (discoverable via
39
- `intent list`) but you never read. This is important — many issues stem from
39
+ `npx @tanstack/intent@latest list`) but you never read. This is important — many issues stem from
40
40
  the agent not loading the right skill, not from the skill itself being wrong.
41
41
 
42
42
  ### 1b: Gap detection
@@ -172,6 +172,25 @@ Determine the target repo from the skill's package. The repo is typically
172
172
  derivable from the `repository` field in the package's `package.json`, or
173
173
  from the `sources` field in the SKILL.md frontmatter.
174
174
 
175
+ ### Link to existing issues/discussions
176
+
177
+ Before creating a new issue, search the target repo for existing issues or
178
+ discussions that match the feedback. Use `gh search issues` or the GitHub
179
+ web search with keywords from the "What Failed" and "Missing" sections.
180
+
181
+ - If an **open issue** already describes the same problem, comment on it
182
+ with the feedback instead of creating a duplicate. Reference the skill
183
+ name and version in your comment.
184
+ - If a **closed issue** describes a problem the skill still gets wrong
185
+ (regression or stale skill content), reference the closed issue in the
186
+ new feedback issue body: `Related to #[number] — this was fixed in the
187
+ library but the skill still describes the old behavior.`
188
+ - If a **discussion thread** covers the same topic, link to it in the
189
+ feedback issue body so maintainers can see the community context.
190
+
191
+ This prevents duplicate issues and gives maintainers richer context for
192
+ improving skills.
193
+
175
194
  ### Privacy check
176
195
 
177
196
  Before submitting, determine whether the user's project is public or private.
@@ -111,6 +111,49 @@ cannot already know:
111
111
  - Framework-specific gotchas (hydration, hook rules, provider ordering)
112
112
  - **Constraints and invariants** — ordering requirements, lifecycle rules,
113
113
  things enforced by runtime assertions
114
+ - **Issue/discussion-sourced patterns** — real developer mistakes and
115
+ confusion surfaced from GitHub issues and discussions (see below)
116
+
117
+ ### 2b — Scan GitHub issues and discussions
118
+
119
+ Before writing the skill body, search the library's GitHub repo for issues
120
+ and discussions relevant to THIS skill's topic. This step is important for
121
+ both initial generation and regeneration — community feedback reveals
122
+ failure modes that docs miss.
123
+
124
+ **Search strategy:**
125
+
126
+ 1. Search issues for the skill's primary APIs, hooks, and config options
127
+ by name (e.g. `useQuery invalidation`, `createRouter middleware`)
128
+ 2. Filter to high-signal threads: sort by reactions/comments, focus on
129
+ closed bugs with workarounds and open questions with long threads
130
+ 3. Search Discussions (if the repo uses them) for "how do I…" threads
131
+ related to the skill's topic
132
+ 4. Check for issues labeled `bug`, `question`, `breaking-change` that
133
+ mention concepts this skill covers
134
+
135
+ **What to incorporate:**
136
+
137
+ - **Recurring bug workarounds** → add as Common Mistakes entries with
138
+ wrong/correct code pairs. Cite the issue URL in the `Source` field.
139
+ - **Frequently asked questions** → if the answer is non-obvious, add it
140
+ to Core Patterns or as a dedicated pattern section
141
+ - **Misunderstandings about defaults** → add to Common Mistakes with the
142
+ incorrect assumption as the "wrong" pattern
143
+ - **Resolved issues that changed behavior** → if the old behavior is
144
+ still in agent training data, add as a migration-boundary mistake
145
+
146
+ **What NOT to incorporate:**
147
+
148
+ - One-off bugs already fixed with no broader pattern
149
+ - Feature requests for APIs that don't exist yet
150
+ - Issues about tooling, CI, or build that don't affect library usage
151
+ - Stale threads (>2 years old) about behavior that has fundamentally changed
152
+
153
+ **Fallback:** If no web access is available, check for FAQ.md,
154
+ TROUBLESHOOTING.md, or docs/faq in the repo. Also check whether the
155
+ domain_map.yaml already contains issue-sourced failure modes from
156
+ domain-discovery — use those directly.
114
157
 
115
158
  ### What NOT to extract
116
159
 
@@ -353,12 +396,20 @@ output in this order:
353
396
  When regenerating a stale skill (triggered by skill-staleness-check):
354
397
 
355
398
  1. Read the existing SKILL.md and the source diff that triggered staleness
356
- 2. Determine which sections are affected by the change
357
- 3. Update only affected sections preserve all other content
358
- 4. If a breaking change occurred, add the old pattern as a new Common
399
+ 2. Scan GitHub issues and discussions opened since the skill was last
400
+ generated (use `library_version` or file timestamps as the baseline).
401
+ Look for new failure modes, resolved confusion, or changed patterns
402
+ related to this skill's topic. Apply the same search strategy from
403
+ Step 2b but scoped to the time window since last generation.
404
+ 3. Determine which sections are affected by the source change AND by
405
+ any new issue/discussion findings
406
+ 4. Update only affected sections — preserve all other content
407
+ 5. If a breaking change occurred, add the old pattern as a new Common
359
408
  Mistake entry (wrong/correct pair)
360
- 5. Bump `library_version` in frontmatter
361
- 6. Validate the complete file against Step 5 checks
409
+ 6. If issues/discussions reveal new failure modes not in the existing
410
+ skill, add them to Common Mistakes with issue URLs as sources
411
+ 7. Bump `library_version` in frontmatter
412
+ 8. Validate the complete file against Step 5 checks
362
413
 
363
414
  Do not rewrite the entire skill for a minor source change. Surgical
364
415
  updates preserve review effort and reduce diff noise.
@@ -7,7 +7,7 @@
7
7
  # Triggers: new release published, or manual workflow_dispatch.
8
8
  #
9
9
  # Template variables (replaced by `intent setup`):
10
- # {{PACKAGE_NAME}} — e.g. @tanstack/query
10
+ # {{PACKAGE_LABEL}} — e.g. @tanstack/query or my-workspace workspace
11
11
 
12
12
  name: Check Skills
13
13
 
@@ -36,12 +36,12 @@ jobs:
36
36
  node-version: 20
37
37
 
38
38
  - name: Install intent
39
- run: npm install {{PACKAGE_NAME}}
39
+ run: npm install -g @tanstack/intent
40
40
 
41
41
  - name: Check staleness
42
42
  id: stale
43
43
  run: |
44
- OUTPUT=$(npx @tanstack/intent stale --json 2>&1) || true
44
+ OUTPUT=$(intent stale --json 2>&1) || true
45
45
  echo "$OUTPUT"
46
46
 
47
47
  # Check if any skills need review
@@ -81,7 +81,7 @@ jobs:
81
81
  const summary = lines.join('\n');
82
82
 
83
83
  const prompt = [
84
- 'Review and update the following stale intent skills for {{PACKAGE_NAME}}:',
84
+ 'Review and update the following stale intent skills for {{PACKAGE_LABEL}}:',
85
85
  '',
86
86
  ...stale.map(s => '- ' + s.skill + ': ' + s.reasons.join(', ')),
87
87
  '',
@@ -9,9 +9,9 @@
9
9
  # as the INTENT_NOTIFY_TOKEN repository secret.
10
10
  #
11
11
  # Template variables (replaced by `intent setup`):
12
- # {{PACKAGE_NAME}} — e.g. @tanstack/query
13
- # {{DOCS_PATH}} — e.g. docs/**
14
- # {{SRC_PATH}} — e.g. packages/query-core/src/**
12
+ # {{PAYLOAD_PACKAGE}} — e.g. @tanstack/query or my-workspace workspace
13
+ # {{DOCS_PATH}} — e.g. docs/**
14
+ # {{SRC_PATH}} — e.g. packages/query-core/src/**
15
15
 
16
16
  name: Notify Intent
17
17
 
@@ -46,7 +46,7 @@ jobs:
46
46
  event-type: skill-check
47
47
  client-payload: |
48
48
  {
49
- "package": "{{PACKAGE_NAME}}",
49
+ "package": "{{PAYLOAD_PACKAGE}}",
50
50
  "sha": "${{ github.sha }}",
51
51
  "changed_files": ${{ steps.changes.outputs.files }}
52
52
  }
@@ -40,7 +40,7 @@ Every skill has a `type` field in its frontmatter. Valid types:
40
40
  | `composition` | Integration between two or more libraries | `electric-drizzle` |
41
41
  | `security` | Audit checklist or security validation | `electric-security-check` |
42
42
 
43
- Agents discover skills via `tanstack intent list` and read them directly
43
+ Agents discover skills via `npx @tanstack/intent list` and read them directly
44
44
  from `node_modules`. Framework skills declare a `requires` dependency on
45
45
  their core skill so agents load them in the right order.
46
46
 
@@ -274,7 +274,7 @@ packages/
274
274
  │ └── package.json # Add "skills" to files array
275
275
  ```
276
276
 
277
- Run `intent edit-package-json` to wire each package's `package.json`
277
+ Run `npx @tanstack/intent@latest edit-package-json` to wire each package's `package.json`
278
278
  automatically (adds `"skills"`, `"bin"`, and `"!skills/_artifacts"` to the
279
279
  `files` array, and adds the `bin` entry if missing).
280
280
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/intent",
3
- "version": "0.0.14",
3
+ "version": "0.0.20",
4
4
  "description": "Ship compositional knowledge for AI coding agents alongside your npm packages",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,7 +14,8 @@
14
14
  "types": "./dist/index.d.mts"
15
15
  },
16
16
  "./intent-library": {
17
- "import": "./dist/intent-library.mjs"
17
+ "import": "./dist/intent-library.mjs",
18
+ "types": "./dist/intent-library.d.mts"
18
19
  }
19
20
  },
20
21
  "bin": {
@@ -29,12 +30,15 @@
29
30
  "yaml": "^2.7.0"
30
31
  },
31
32
  "devDependencies": {
32
- "tsdown": "^0.19.0"
33
+ "@verdaccio/node-api": "6.0.0-6-next.76",
34
+ "tsdown": "^0.19.0",
35
+ "verdaccio": "^6.3.2"
33
36
  },
34
37
  "scripts": {
35
- "prepack": "pnpm run build",
38
+ "prepack": "npm run build",
36
39
  "build": "tsdown src/index.ts src/cli.ts src/setup.ts src/intent-library.ts src/library-scanner.ts --format esm --dts",
37
- "test:lib": "vitest run",
40
+ "test:lib": "vitest run --exclude 'tests/integration/**'",
41
+ "test:integration": "vitest run tests/integration/",
38
42
  "test:types": "tsc --noEmit"
39
43
  }
40
44
  }
@@ -1,4 +0,0 @@
1
- import "./utils-CDJzAdjD.mjs";
2
- import { t as scanForIntents } from "./scanner-CY40iozO.mjs";
3
-
4
- export { scanForIntents };
@@ -1,218 +0,0 @@
1
- import { i as resolveDepDir, n as getDeps, r as parseFrontmatter } from "./utils-CDJzAdjD.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
- async function scanForIntents(root) {
103
- const projectRoot = root ?? process.cwd();
104
- const packageManager = detectPackageManager(projectRoot);
105
- const nodeModulesDir = join(projectRoot, "node_modules");
106
- const packages = [];
107
- const warnings = [];
108
- if (!existsSync(nodeModulesDir)) return {
109
- packageManager,
110
- packages,
111
- warnings
112
- };
113
- const packageDirs = [];
114
- let topEntries;
115
- try {
116
- topEntries = readdirSync(nodeModulesDir, {
117
- withFileTypes: true,
118
- encoding: "utf8"
119
- });
120
- } catch {
121
- return {
122
- packageManager,
123
- packages,
124
- warnings
125
- };
126
- }
127
- for (const entry of topEntries) {
128
- if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
129
- const dirPath = join(nodeModulesDir, entry.name);
130
- if (entry.name.startsWith("@")) {
131
- let scopedEntries;
132
- try {
133
- scopedEntries = readdirSync(dirPath, {
134
- withFileTypes: true,
135
- encoding: "utf8"
136
- });
137
- } catch {
138
- continue;
139
- }
140
- for (const scoped of scopedEntries) {
141
- if (!scoped.isDirectory() && !scoped.isSymbolicLink()) continue;
142
- packageDirs.push({ dirPath: join(dirPath, scoped.name) });
143
- }
144
- } else if (!entry.name.startsWith(".")) packageDirs.push({ dirPath });
145
- }
146
- const foundNames = /* @__PURE__ */ new Set();
147
- /**
148
- * Try to register a package with a skills/ directory. Reads its
149
- * package.json, validates intent config, discovers skills, and pushes
150
- * to `packages`. Returns true if the package was registered.
151
- */
152
- function tryRegister(dirPath, fallbackName) {
153
- const skillsDir = join(dirPath, "skills");
154
- if (!existsSync(skillsDir)) return false;
155
- let pkgJson;
156
- try {
157
- pkgJson = JSON.parse(readFileSync(join(dirPath, "package.json"), "utf8"));
158
- } catch {
159
- warnings.push(`Could not read package.json for ${dirPath}`);
160
- return false;
161
- }
162
- const name = typeof pkgJson.name === "string" ? pkgJson.name : fallbackName;
163
- if (foundNames.has(name)) return false;
164
- const intent = validateIntentField(name, pkgJson.intent) ?? deriveIntentConfig(pkgJson);
165
- if (!intent) {
166
- warnings.push(`${name} has a skills/ directory but could not determine repo/docs from package.json (add a "repository" field or explicit "intent" config)`);
167
- return false;
168
- }
169
- packages.push({
170
- name,
171
- version: typeof pkgJson.version === "string" ? pkgJson.version : "0.0.0",
172
- intent,
173
- skills: discoverSkills(skillsDir, name)
174
- });
175
- foundNames.add(name);
176
- return true;
177
- }
178
- for (const { dirPath } of packageDirs) tryRegister(dirPath, "unknown");
179
- const walkVisited = /* @__PURE__ */ new Set();
180
- function walkDeps(pkgDir, pkgName) {
181
- if (walkVisited.has(pkgName)) return;
182
- walkVisited.add(pkgName);
183
- let pkgJson;
184
- try {
185
- pkgJson = JSON.parse(readFileSync(join(pkgDir, "package.json"), "utf8"));
186
- } catch {
187
- warnings.push(`Could not read package.json for ${pkgName} (skipping dependency walk)`);
188
- return;
189
- }
190
- for (const depName of getDeps(pkgJson)) {
191
- if (foundNames.has(depName) || walkVisited.has(depName)) continue;
192
- const depDir = resolveDepDir(depName, pkgDir, pkgName, nodeModulesDir);
193
- if (!depDir) continue;
194
- tryRegister(depDir, depName);
195
- walkDeps(depDir, depName);
196
- }
197
- }
198
- for (const pkg of [...packages]) walkDeps(join(nodeModulesDir, pkg.name), pkg.name);
199
- let projectPkg = null;
200
- try {
201
- projectPkg = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf8"));
202
- } catch (err) {
203
- 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)}`);
204
- }
205
- if (projectPkg) for (const depName of getDeps(projectPkg, true)) {
206
- if (walkVisited.has(depName)) continue;
207
- const depDir = join(nodeModulesDir, depName);
208
- if (existsSync(join(depDir, "package.json"))) walkDeps(depDir, depName);
209
- }
210
- return {
211
- packageManager,
212
- packages: topoSort(packages),
213
- warnings
214
- };
215
- }
216
-
217
- //#endregion
218
- export { scanForIntents as t };
@@ -1,18 +0,0 @@
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 };