@tanstack/intent 0.0.32 → 0.0.34
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 +7 -7
- package/dist/artifact-coverage-BAN2W6aH.mjs +3 -0
- package/dist/artifact-coverage-wLNVX8yC.mjs +128 -0
- package/dist/cli.mjs +248 -99
- package/dist/{display-DCRCp4-C.mjs → display-B3vkG99D.mjs} +1 -1
- package/dist/index.d.mts +22 -4
- package/dist/index.mjs +10 -8
- package/dist/{install-BmVqcbEi.mjs → install-QjryhQtg.mjs} +54 -18
- package/dist/intent-library.mjs +5 -5
- package/dist/library-scanner.d.mts +1 -1
- package/dist/library-scanner.mjs +1 -1
- package/dist/{project-context-CKG-q4rD.mjs → project-context-alYMNoNa.mjs} +1 -1
- package/dist/{resolver-aFigTqXi.mjs → resolver-Whd12ksO.mjs} +1 -1
- package/dist/scanner-BAZxWeUk.mjs +6 -0
- package/dist/{scanner-DKL8v8is.mjs → scanner-Dav1tzQK.mjs} +79 -4
- package/dist/{setup-JJvjiGkM.mjs → setup-Dp-W8y0Y.mjs} +2 -2
- package/dist/setup.d.mts +1 -1
- package/dist/setup.mjs +3 -3
- package/dist/staleness-DpbmYod4.mjs +5 -0
- package/dist/staleness-PdgakrCQ.mjs +243 -0
- package/dist/{types-CsySN6Vw.d.mts → types-DT7Y6TFz.d.mts} +48 -1
- package/dist/workflow-review-CwkPVIQf.mjs +153 -0
- package/dist/workflow-review-Dz_ofcYQ.mjs +3 -0
- package/dist/{workspace-patterns-U35B5AO-.mjs → workspace-patterns-BN2A_60g.mjs} +6 -1
- package/dist/workspace-patterns-x-dLZxx4.mjs +4 -0
- package/meta/templates/workflows/check-skills.yml +58 -96
- package/package.json +1 -1
- package/dist/scanner-CRZITpcY.mjs +0 -6
- package/dist/staleness-DorwfGrf.mjs +0 -104
- package/dist/staleness-NF-lxfXf.mjs +0 -4
- package/dist/workspace-patterns-DbnA0peB.mjs +0 -4
- package/meta/templates/workflows/notify-intent.yml +0 -51
- package/meta/templates/workflows/validate-skills.yml +0 -52
- /package/dist/{display-DUgtRJkt.mjs → display-CAof6doy.mjs} +0 -0
- /package/dist/{library-scanner-DFFreLjW.mjs → library-scanner-fexXlPXb.mjs} +0 -0
- /package/dist/{setup-DDoOLriA.d.mts → setup-t1i2o2-h.d.mts} +0 -0
- /package/dist/{skill-use-CXOnncWK.mjs → skill-use-BzuuvLM7.mjs} +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { appendFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
//#region src/workflow-review.ts
|
|
4
|
+
function collectStaleReviewItems(reports) {
|
|
5
|
+
const items = [];
|
|
6
|
+
for (const report of reports) {
|
|
7
|
+
for (const skill of report.skills ?? []) {
|
|
8
|
+
if (!skill?.needsReview) continue;
|
|
9
|
+
items.push({
|
|
10
|
+
type: "stale-skill",
|
|
11
|
+
library: report.library,
|
|
12
|
+
subject: skill.name,
|
|
13
|
+
reasons: skill.reasons ?? []
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
for (const signal of report.signals ?? []) {
|
|
17
|
+
if (signal?.needsReview === false) continue;
|
|
18
|
+
items.push({
|
|
19
|
+
type: signal?.type ?? "review-signal",
|
|
20
|
+
library: signal?.library ?? report.library,
|
|
21
|
+
subject: signal?.packageName ?? signal?.packageRoot ?? signal?.skill ?? signal?.artifactPath ?? signal?.subject ?? report.library,
|
|
22
|
+
reasons: signal?.reasons ?? [],
|
|
23
|
+
artifactPath: signal?.artifactPath,
|
|
24
|
+
packageName: signal?.packageName,
|
|
25
|
+
packageRoot: signal?.packageRoot,
|
|
26
|
+
skill: signal?.skill
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return items;
|
|
31
|
+
}
|
|
32
|
+
function createFailedStaleReviewItem(library) {
|
|
33
|
+
return {
|
|
34
|
+
type: "stale-check-failed",
|
|
35
|
+
library,
|
|
36
|
+
subject: "intent stale --json",
|
|
37
|
+
reasons: ["The stale check command failed. Review the workflow logs before updating skills."]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function createWorkflowAdvisoryReviewItems(library, advisories) {
|
|
41
|
+
return advisories.map((advisory) => ({
|
|
42
|
+
type: "workflow-advisory",
|
|
43
|
+
library,
|
|
44
|
+
subject: "check-skills.yml",
|
|
45
|
+
reasons: [advisory]
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
function buildStaleReviewBody(items) {
|
|
49
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
50
|
+
for (const item of items) grouped.set(item.type, (grouped.get(item.type) ?? 0) + 1);
|
|
51
|
+
const signalRows = [...grouped.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([type, count]) => `| \`${type}\` | ${count} |`);
|
|
52
|
+
const itemRows = items.map((item) => {
|
|
53
|
+
const subject = item.subject ? `\`${item.subject}\`` : "-";
|
|
54
|
+
const reasons = item.reasons?.length ? item.reasons.join("; ") : "-";
|
|
55
|
+
return `| \`${item.type}\` | ${subject} | \`${item.library}\` | ${reasons} |`;
|
|
56
|
+
});
|
|
57
|
+
const reasonBullets = items.map((item) => {
|
|
58
|
+
const subject = item.subject ? `\`${item.subject}\`` : "`unknown`";
|
|
59
|
+
const reasons = item.reasons?.length ? item.reasons.join("; ") : "Intent did not emit a detailed reason for this signal.";
|
|
60
|
+
return `- \`${item.type}\` for ${subject}: ${reasons}`;
|
|
61
|
+
});
|
|
62
|
+
const prompt = [
|
|
63
|
+
"You are helping maintain Intent skills for this repository.",
|
|
64
|
+
"",
|
|
65
|
+
"Goal:",
|
|
66
|
+
"Resolve the Intent skill review signals below while preserving the existing scope, taxonomy, and maintainer-reviewed artifacts.",
|
|
67
|
+
"",
|
|
68
|
+
"Review signals:",
|
|
69
|
+
JSON.stringify(items, null, 2),
|
|
70
|
+
"",
|
|
71
|
+
"Required workflow:",
|
|
72
|
+
"1. Read the existing `_artifacts/*domain_map.yaml`, `_artifacts/*skill_tree.yaml`, and generated `skills/**/SKILL.md` files.",
|
|
73
|
+
"2. Read each flagged package package.json, public exports, README/docs if present, and source entry points.",
|
|
74
|
+
"3. Compare flagged packages against the existing domains, skills, tasks, packages, covers, sources, tensions, and cross-references in the artifacts.",
|
|
75
|
+
"4. For each signal, decide whether it means existing skill coverage, a missing generated skill, a new skill candidate, out-of-scope coverage, or deferred work.",
|
|
76
|
+
"",
|
|
77
|
+
"Maintainer questions:",
|
|
78
|
+
"Before editing skills or artifacts, ask the maintainer:",
|
|
79
|
+
"1. For each flagged package, is this package user-facing enough to need agent guidance?",
|
|
80
|
+
"2. If yes, should it extend an existing skill or become a new skill?",
|
|
81
|
+
"3. If it extends an existing skill, which current skill should own it?",
|
|
82
|
+
"4. If it is out of scope, what short reason should be recorded in artifact coverage ignores?",
|
|
83
|
+
"5. Are any of these packages experimental or unstable enough to exclude for now?",
|
|
84
|
+
"",
|
|
85
|
+
"Decision rules:",
|
|
86
|
+
"- Do not auto-generate skills.",
|
|
87
|
+
"- Do not create broad new skill areas without maintainer confirmation.",
|
|
88
|
+
"- Prefer adding package coverage to an existing skill when the package is an implementation variant of an existing domain.",
|
|
89
|
+
"- Create a new skill only when the package introduces a distinct developer task or failure mode.",
|
|
90
|
+
"- Preserve current naming, path, and package layout conventions.",
|
|
91
|
+
"- Keep generated skills under the package-local `skills/` directory.",
|
|
92
|
+
"- Keep repo-root `_artifacts` as the reviewed plan.",
|
|
93
|
+
"",
|
|
94
|
+
"If maintainer confirms updates:",
|
|
95
|
+
"1. Update the relevant `_artifacts/*domain_map.yaml` or `_artifacts/*skill_tree.yaml`.",
|
|
96
|
+
"2. Update or create `SKILL.md` files only for confirmed coverage changes.",
|
|
97
|
+
"3. Keep `sources` aligned between artifact skill entries and SKILL frontmatter.",
|
|
98
|
+
"4. Bump `library_version` only for skills whose covered source package version changed.",
|
|
99
|
+
"5. Run `npx @tanstack/intent@latest validate` on touched skill directories.",
|
|
100
|
+
"6. Summarize every package as one of: existing-skill coverage, new skill, ignored, or deferred."
|
|
101
|
+
].join("\n");
|
|
102
|
+
return [
|
|
103
|
+
"## Intent Skill Review Needed",
|
|
104
|
+
"",
|
|
105
|
+
"Intent found skills, artifact coverage, or workspace package coverage that need maintainer review.",
|
|
106
|
+
"",
|
|
107
|
+
"### Summary",
|
|
108
|
+
"",
|
|
109
|
+
"| Signal | Count |",
|
|
110
|
+
"| --- | ---: |",
|
|
111
|
+
...signalRows,
|
|
112
|
+
"",
|
|
113
|
+
"### Why This PR Opened",
|
|
114
|
+
"",
|
|
115
|
+
...reasonBullets,
|
|
116
|
+
"",
|
|
117
|
+
"### Review Items",
|
|
118
|
+
"",
|
|
119
|
+
"| Signal | Subject | Library | Reason |",
|
|
120
|
+
"| --- | --- | --- | --- |",
|
|
121
|
+
...itemRows,
|
|
122
|
+
"",
|
|
123
|
+
"### Agent Prompt",
|
|
124
|
+
"",
|
|
125
|
+
"Paste this into your coding agent:",
|
|
126
|
+
"",
|
|
127
|
+
"```text",
|
|
128
|
+
prompt,
|
|
129
|
+
"```",
|
|
130
|
+
"",
|
|
131
|
+
"This PR is a review reminder only. It does not update skills automatically."
|
|
132
|
+
].join("\n");
|
|
133
|
+
}
|
|
134
|
+
function writeStaleReviewWorkflowFiles(items, options = {}) {
|
|
135
|
+
const outputPath = options.outputPath ?? process.env.GITHUB_OUTPUT;
|
|
136
|
+
const summaryPath = options.summaryPath ?? process.env.GITHUB_STEP_SUMMARY;
|
|
137
|
+
const reviewItemsPath = options.reviewItemsPath ?? "review-items.json";
|
|
138
|
+
const prBodyPath = options.prBodyPath ?? "pr-body.md";
|
|
139
|
+
const hasReview = items.length > 0;
|
|
140
|
+
writeFileSync(reviewItemsPath, JSON.stringify(items, null, 2) + "\n");
|
|
141
|
+
if (outputPath) appendFileSync(outputPath, `has_review=${hasReview ? "true" : "false"}\n`);
|
|
142
|
+
const summary = hasReview ? buildStaleReviewBody(items) + "\n" : [
|
|
143
|
+
"### Intent skill review",
|
|
144
|
+
"",
|
|
145
|
+
"No stale skills or coverage gaps found.",
|
|
146
|
+
""
|
|
147
|
+
].join("\n");
|
|
148
|
+
if (hasReview) writeFileSync(prBodyPath, summary);
|
|
149
|
+
if (summaryPath) appendFileSync(summaryPath, summary);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
//#endregion
|
|
153
|
+
export { writeStaleReviewWorkflowFiles as a, createWorkflowAdvisoryReviewItems as i, collectStaleReviewItems as n, createFailedStaleReviewItem as r, buildStaleReviewBody as t };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as writeStaleReviewWorkflowFiles, i as createWorkflowAdvisoryReviewItems, n as collectStaleReviewItems, r as createFailedStaleReviewItem, t as buildStaleReviewBody } from "./workflow-review-CwkPVIQf.mjs";
|
|
2
|
+
|
|
3
|
+
export { collectStaleReviewItems, createFailedStaleReviewItem, createWorkflowAdvisoryReviewItems, writeStaleReviewWorkflowFiles };
|
|
@@ -162,6 +162,11 @@ function findPackagesWithSkills(root) {
|
|
|
162
162
|
return existsSync(skillsDir) && findSkillFiles(skillsDir).length > 0;
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
|
+
function findWorkspacePackages(root) {
|
|
166
|
+
const patterns = readWorkspacePatterns(root);
|
|
167
|
+
if (!patterns) return [];
|
|
168
|
+
return resolveWorkspacePackages(root, patterns);
|
|
169
|
+
}
|
|
165
170
|
|
|
166
171
|
//#endregion
|
|
167
|
-
export { resolveWorkspacePackages as i,
|
|
172
|
+
export { resolveWorkspacePackages as a, readWorkspacePatterns as i, findWorkspacePackages as n, findWorkspaceRoot as r, findPackagesWithSkills as t };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import "./utils-COlDcU72.mjs";
|
|
2
|
+
import { a as resolveWorkspacePackages, i as readWorkspacePatterns, n as findWorkspacePackages, r as findWorkspaceRoot, t as findPackagesWithSkills } from "./workspace-patterns-BN2A_60g.mjs";
|
|
3
|
+
|
|
4
|
+
export { findPackagesWithSkills, findWorkspacePackages, findWorkspaceRoot };
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# check-skills.yml — Drop this into your library repo's .github/workflows/
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
3
|
+
# Validates intent skills on PRs. After a release or manual run, opens or
|
|
4
|
+
# updates one review PR when existing skills, artifact coverage, or workspace
|
|
5
|
+
# package coverage need review.
|
|
6
6
|
#
|
|
7
|
-
# Triggers: new release published, or
|
|
7
|
+
# Triggers: pull requests touching skills/artifacts, new release published, or
|
|
8
|
+
# manual workflow_dispatch.
|
|
9
|
+
#
|
|
10
|
+
# intent-workflow-version: 3
|
|
8
11
|
#
|
|
9
12
|
# Template variables (replaced by `intent setup`):
|
|
10
13
|
# {{PACKAGE_LABEL}} — e.g. @tanstack/query or my-workspace workspace
|
|
@@ -12,6 +15,12 @@
|
|
|
12
15
|
name: Check Skills
|
|
13
16
|
|
|
14
17
|
on:
|
|
18
|
+
pull_request:
|
|
19
|
+
paths:
|
|
20
|
+
- 'skills/**'
|
|
21
|
+
- '**/skills/**'
|
|
22
|
+
- '_artifacts/**'
|
|
23
|
+
- '**/_artifacts/**'
|
|
15
24
|
release:
|
|
16
25
|
types: [published]
|
|
17
26
|
workflow_dispatch: {}
|
|
@@ -21,14 +30,13 @@ permissions:
|
|
|
21
30
|
pull-requests: write
|
|
22
31
|
|
|
23
32
|
jobs:
|
|
24
|
-
|
|
25
|
-
name:
|
|
33
|
+
validate:
|
|
34
|
+
name: Validate intent skills
|
|
35
|
+
if: github.event_name == 'pull_request'
|
|
26
36
|
runs-on: ubuntu-latest
|
|
27
37
|
steps:
|
|
28
38
|
- name: Checkout
|
|
29
39
|
uses: actions/checkout@v4
|
|
30
|
-
with:
|
|
31
|
-
fetch-depth: 0
|
|
32
40
|
|
|
33
41
|
- name: Setup Node
|
|
34
42
|
uses: actions/setup-node@v4
|
|
@@ -38,106 +46,60 @@ jobs:
|
|
|
38
46
|
- name: Install intent
|
|
39
47
|
run: npm install -g @tanstack/intent
|
|
40
48
|
|
|
41
|
-
- name:
|
|
42
|
-
|
|
43
|
-
run: |
|
|
44
|
-
OUTPUT=$(intent stale --json 2>&1) || true
|
|
45
|
-
echo "$OUTPUT"
|
|
49
|
+
- name: Validate skills
|
|
50
|
+
run: intent validate --github-summary
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
console.log(JSON.stringify(stale));
|
|
57
|
-
}
|
|
58
|
-
} catch {}
|
|
59
|
-
")
|
|
60
|
-
|
|
61
|
-
if [ -z "$NEEDS_REVIEW" ]; then
|
|
62
|
-
echo "has_stale=false" >> "$GITHUB_OUTPUT"
|
|
63
|
-
else
|
|
64
|
-
echo "has_stale=true" >> "$GITHUB_OUTPUT"
|
|
65
|
-
# Escape for multiline GH output
|
|
66
|
-
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
|
|
67
|
-
echo "stale_json<<$EOF" >> "$GITHUB_OUTPUT"
|
|
68
|
-
echo "$NEEDS_REVIEW" >> "$GITHUB_OUTPUT"
|
|
69
|
-
echo "$EOF" >> "$GITHUB_OUTPUT"
|
|
70
|
-
fi
|
|
52
|
+
review:
|
|
53
|
+
name: Check intent skill coverage
|
|
54
|
+
if: github.event_name != 'pull_request'
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
steps:
|
|
57
|
+
- name: Checkout
|
|
58
|
+
uses: actions/checkout@v4
|
|
59
|
+
with:
|
|
60
|
+
fetch-depth: 0
|
|
71
61
|
|
|
72
|
-
- name:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
node -e "
|
|
77
|
-
const stale = JSON.parse(process.env.STALE_JSON);
|
|
78
|
-
const lines = stale.map(s =>
|
|
79
|
-
'- **' + s.skill + '** (' + s.library + '): ' + s.reasons.join(', ')
|
|
80
|
-
);
|
|
81
|
-
const summary = lines.join('\n');
|
|
62
|
+
- name: Setup Node
|
|
63
|
+
uses: actions/setup-node@v4
|
|
64
|
+
with:
|
|
65
|
+
node-version: 20
|
|
82
66
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
'',
|
|
86
|
-
...stale.map(s => '- ' + s.skill + ': ' + s.reasons.join(', ')),
|
|
87
|
-
'',
|
|
88
|
-
'For each stale skill:',
|
|
89
|
-
'1. Read the current SKILL.md file',
|
|
90
|
-
'2. Check what changed in the library since the skill was last updated',
|
|
91
|
-
'3. Update the skill content to reflect current APIs and behavior',
|
|
92
|
-
'4. Run \`npx @tanstack/intent validate\` to verify the updated skill',
|
|
93
|
-
].join('\n');
|
|
67
|
+
- name: Install intent
|
|
68
|
+
run: npm install -g @tanstack/intent
|
|
94
69
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
fs.appendFileSync(process.env.GITHUB_OUTPUT,
|
|
100
|
-
'summary<<' + eof + '\n' + summary + '\n' + eof + '\n' +
|
|
101
|
-
'prompt<<' + eof + '\n' + prompt + '\n' + eof + '\n'
|
|
102
|
-
);
|
|
103
|
-
"
|
|
104
|
-
env:
|
|
105
|
-
STALE_JSON: ${{ steps.stale.outputs.stale_json }}
|
|
70
|
+
- name: Check skills
|
|
71
|
+
id: stale
|
|
72
|
+
run: |
|
|
73
|
+
intent stale --github-review --package-label "{{PACKAGE_LABEL}}"
|
|
106
74
|
|
|
107
|
-
- name: Open review PR
|
|
108
|
-
if: steps.stale.outputs.
|
|
75
|
+
- name: Open or update review PR
|
|
76
|
+
if: steps.stale.outputs.has_review == 'true'
|
|
109
77
|
env:
|
|
110
78
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
111
79
|
run: |
|
|
112
80
|
VERSION="${{ github.event.release.tag_name || 'manual' }}"
|
|
113
81
|
BRANCH="skills/review-${VERSION}"
|
|
82
|
+
BASE_BRANCH="${{ github.event.repository.default_branch }}"
|
|
114
83
|
|
|
115
84
|
git config user.name "github-actions[bot]"
|
|
116
85
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
117
|
-
git checkout -b "$BRANCH"
|
|
118
|
-
git commit --allow-empty -m "chore: review stale skills for ${VERSION}"
|
|
119
|
-
git push origin "$BRANCH"
|
|
120
|
-
|
|
121
|
-
gh pr create \
|
|
122
|
-
--title "Review stale skills (${VERSION})" \
|
|
123
|
-
--body "$(cat <<'PREOF'
|
|
124
|
-
## Stale Skills Detected
|
|
125
|
-
|
|
126
|
-
The following skills may need updates after the latest release:
|
|
127
86
|
|
|
128
|
-
$
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
~~~
|
|
137
|
-
${{ steps.summary.outputs.prompt }}
|
|
138
|
-
~~~
|
|
87
|
+
git fetch origin "$BRANCH" || true
|
|
88
|
+
if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then
|
|
89
|
+
git checkout -B "$BRANCH" "origin/$BRANCH"
|
|
90
|
+
else
|
|
91
|
+
git checkout -b "$BRANCH"
|
|
92
|
+
git commit --allow-empty -m "chore: review intent skills for ${VERSION}"
|
|
93
|
+
git push origin "$BRANCH"
|
|
94
|
+
fi
|
|
139
95
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
96
|
+
PR_URL="$(gh pr list --head "$BRANCH" --json url --jq '.[0].url')"
|
|
97
|
+
if [ -n "$PR_URL" ]; then
|
|
98
|
+
gh pr edit "$PR_URL" --body-file pr-body.md
|
|
99
|
+
else
|
|
100
|
+
gh pr create \
|
|
101
|
+
--title "Review intent skills (${VERSION})" \
|
|
102
|
+
--body-file pr-body.md \
|
|
103
|
+
--head "$BRANCH" \
|
|
104
|
+
--base "$BASE_BRANCH"
|
|
105
|
+
fi
|
package/package.json
CHANGED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { a as parseFrontmatter, n as findSkillFiles } from "./utils-COlDcU72.mjs";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { join, relative, sep } from "node:path";
|
|
4
|
-
|
|
5
|
-
//#region src/staleness.ts
|
|
6
|
-
function classifyVersionDrift(oldVer, newVer) {
|
|
7
|
-
if (oldVer === newVer) return null;
|
|
8
|
-
const oldParts = oldVer.replace(/[^0-9.]/g, "").split(".").map(Number);
|
|
9
|
-
const newParts = newVer.replace(/[^0-9.]/g, "").split(".").map(Number);
|
|
10
|
-
if ((newParts[0] ?? 0) > (oldParts[0] ?? 0)) return "major";
|
|
11
|
-
if ((newParts[1] ?? 0) > (oldParts[1] ?? 0)) return "minor";
|
|
12
|
-
if ((newParts[2] ?? 0) > (oldParts[2] ?? 0)) return "patch";
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
function readLocalVersion(packageDir) {
|
|
16
|
-
try {
|
|
17
|
-
const pkgJson = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
|
|
18
|
-
return typeof pkgJson.version === "string" ? pkgJson.version : null;
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
async function fetchNpmVersion(packageName) {
|
|
24
|
-
try {
|
|
25
|
-
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`);
|
|
26
|
-
if (!res.ok) return null;
|
|
27
|
-
const data = await res.json();
|
|
28
|
-
return typeof data.version === "string" ? data.version : null;
|
|
29
|
-
} catch {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
async function fetchCurrentVersion(packageDir, packageName) {
|
|
34
|
-
return readLocalVersion(packageDir) ?? await fetchNpmVersion(packageName);
|
|
35
|
-
}
|
|
36
|
-
function isStringRecord(value) {
|
|
37
|
-
return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
38
|
-
}
|
|
39
|
-
function parseSyncState(value) {
|
|
40
|
-
if (!value || typeof value !== "object") return null;
|
|
41
|
-
const raw = value;
|
|
42
|
-
const parsed = {};
|
|
43
|
-
if (typeof raw.library_version === "string") parsed.library_version = raw.library_version;
|
|
44
|
-
if (raw.skills && typeof raw.skills === "object") {
|
|
45
|
-
const skills = {};
|
|
46
|
-
for (const [skillName, skillValue] of Object.entries(raw.skills)) {
|
|
47
|
-
if (!skillValue || typeof skillValue !== "object") continue;
|
|
48
|
-
const sourcesSha = skillValue.sources_sha;
|
|
49
|
-
if (sourcesSha !== void 0 && !isStringRecord(sourcesSha)) continue;
|
|
50
|
-
skills[skillName] = {};
|
|
51
|
-
if (sourcesSha) skills[skillName].sources_sha = sourcesSha;
|
|
52
|
-
}
|
|
53
|
-
parsed.skills = skills;
|
|
54
|
-
}
|
|
55
|
-
return parsed;
|
|
56
|
-
}
|
|
57
|
-
function readSyncState(packageDir) {
|
|
58
|
-
const statePath = join(packageDir, "skills", "sync-state.json");
|
|
59
|
-
try {
|
|
60
|
-
return parseSyncState(JSON.parse(readFileSync(statePath, "utf8")));
|
|
61
|
-
} catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
async function checkStaleness(packageDir, packageName) {
|
|
66
|
-
const skillsDir = join(packageDir, "skills");
|
|
67
|
-
const library = packageName ?? "unknown";
|
|
68
|
-
const skillMetas = findSkillFiles(skillsDir).map((filePath) => {
|
|
69
|
-
const fm = parseFrontmatter(filePath);
|
|
70
|
-
const relName = relative(skillsDir, filePath).replace(/[/\\]SKILL\.md$/, "").split(sep).join("/");
|
|
71
|
-
return {
|
|
72
|
-
name: typeof fm?.name === "string" ? fm.name : relName,
|
|
73
|
-
filePath,
|
|
74
|
-
libraryVersion: fm?.library_version,
|
|
75
|
-
sources: Array.isArray(fm?.sources) ? fm.sources : void 0
|
|
76
|
-
};
|
|
77
|
-
});
|
|
78
|
-
const skillVersion = skillMetas.find((s) => s.libraryVersion)?.libraryVersion ?? null;
|
|
79
|
-
const currentVersion = await fetchCurrentVersion(packageDir, library);
|
|
80
|
-
const versionDrift = skillVersion && currentVersion ? classifyVersionDrift(skillVersion, currentVersion) : null;
|
|
81
|
-
const syncState = readSyncState(packageDir);
|
|
82
|
-
return {
|
|
83
|
-
library,
|
|
84
|
-
currentVersion,
|
|
85
|
-
skillVersion,
|
|
86
|
-
versionDrift,
|
|
87
|
-
skills: skillMetas.map((skill) => {
|
|
88
|
-
const reasons = [];
|
|
89
|
-
if (currentVersion && skill.libraryVersion && skill.libraryVersion !== currentVersion) reasons.push(`version drift (${skill.libraryVersion} → ${currentVersion})`);
|
|
90
|
-
const storedShas = syncState?.skills?.[skill.name]?.sources_sha ?? {};
|
|
91
|
-
if (skill.sources && Object.keys(storedShas).length > 0) {
|
|
92
|
-
for (const source of skill.sources) if (!storedShas[source]) reasons.push(`new source (${source})`);
|
|
93
|
-
}
|
|
94
|
-
return {
|
|
95
|
-
name: skill.name,
|
|
96
|
-
reasons,
|
|
97
|
-
needsReview: reasons.length > 0
|
|
98
|
-
};
|
|
99
|
-
})
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
//#endregion
|
|
104
|
-
export { checkStaleness as t };
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# notify-intent.yml — Drop this into your library repo's .github/workflows/
|
|
2
|
-
#
|
|
3
|
-
# Fires a repository_dispatch event whenever docs or source files change
|
|
4
|
-
# on merge to main. This triggers the skill staleness check workflow.
|
|
5
|
-
#
|
|
6
|
-
# Requirements:
|
|
7
|
-
# - A fine-grained PAT with contents:write on this repository stored
|
|
8
|
-
# as the INTENT_NOTIFY_TOKEN repository secret.
|
|
9
|
-
#
|
|
10
|
-
# Template variables (replaced by `intent setup`):
|
|
11
|
-
# {{PAYLOAD_PACKAGE}} — e.g. @tanstack/query or my-workspace workspace
|
|
12
|
-
# {{DOCS_PATH}} — e.g. docs/**
|
|
13
|
-
# {{SRC_PATH}} — e.g. packages/query-core/src/**
|
|
14
|
-
|
|
15
|
-
name: Trigger Skill Review
|
|
16
|
-
|
|
17
|
-
on:
|
|
18
|
-
push:
|
|
19
|
-
branches: [main]
|
|
20
|
-
paths:
|
|
21
|
-
- '{{DOCS_PATH}}'
|
|
22
|
-
- '{{SRC_PATH}}'
|
|
23
|
-
|
|
24
|
-
jobs:
|
|
25
|
-
notify:
|
|
26
|
-
name: Trigger Skill Review
|
|
27
|
-
runs-on: ubuntu-latest
|
|
28
|
-
steps:
|
|
29
|
-
- name: Checkout
|
|
30
|
-
uses: actions/checkout@v4
|
|
31
|
-
with:
|
|
32
|
-
fetch-depth: 2
|
|
33
|
-
|
|
34
|
-
- name: Collect changed files
|
|
35
|
-
id: changes
|
|
36
|
-
run: |
|
|
37
|
-
FILES=$(git diff --name-only HEAD~1 HEAD | jq -R -s -c 'split("\n") | map(select(length > 0))')
|
|
38
|
-
echo "files=$FILES" >> "$GITHUB_OUTPUT"
|
|
39
|
-
|
|
40
|
-
- name: Dispatch to intent repo
|
|
41
|
-
uses: peter-evans/repository-dispatch@v3
|
|
42
|
-
with:
|
|
43
|
-
token: ${{ secrets.INTENT_NOTIFY_TOKEN }}
|
|
44
|
-
repository: ${{ github.repository }}
|
|
45
|
-
event-type: skill-check
|
|
46
|
-
client-payload: |
|
|
47
|
-
{
|
|
48
|
-
"package": "{{PAYLOAD_PACKAGE}}",
|
|
49
|
-
"sha": "${{ github.sha }}",
|
|
50
|
-
"changed_files": ${{ steps.changes.outputs.files }}
|
|
51
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# validate-skills.yml — Drop this into your library repo's .github/workflows/
|
|
2
|
-
#
|
|
3
|
-
# Validates skill files on PRs that touch the skills/ directory.
|
|
4
|
-
# Ensures frontmatter is correct, names match paths, and files stay under
|
|
5
|
-
# the 500-line limit.
|
|
6
|
-
|
|
7
|
-
name: Validate Skills
|
|
8
|
-
|
|
9
|
-
on:
|
|
10
|
-
pull_request:
|
|
11
|
-
paths:
|
|
12
|
-
- 'skills/**'
|
|
13
|
-
- '**/skills/**'
|
|
14
|
-
|
|
15
|
-
jobs:
|
|
16
|
-
validate:
|
|
17
|
-
name: Validate skill files
|
|
18
|
-
runs-on: ubuntu-latest
|
|
19
|
-
steps:
|
|
20
|
-
- name: Checkout
|
|
21
|
-
uses: actions/checkout@v4
|
|
22
|
-
|
|
23
|
-
- name: Setup Node
|
|
24
|
-
uses: actions/setup-node@v4
|
|
25
|
-
with:
|
|
26
|
-
node-version: 20
|
|
27
|
-
|
|
28
|
-
- name: Install intent CLI
|
|
29
|
-
run: npm install -g @tanstack/intent
|
|
30
|
-
|
|
31
|
-
- name: Find and validate skills
|
|
32
|
-
run: |
|
|
33
|
-
# Find all directories containing SKILL.md files
|
|
34
|
-
SKILLS_DIR=""
|
|
35
|
-
if [ -d "skills" ]; then
|
|
36
|
-
SKILLS_DIR="skills"
|
|
37
|
-
elif [ -d "packages" ]; then
|
|
38
|
-
# Monorepo — find skills/ under packages
|
|
39
|
-
for dir in packages/*/skills; do
|
|
40
|
-
if [ -d "$dir" ]; then
|
|
41
|
-
echo "Validating $dir..."
|
|
42
|
-
intent validate "$dir"
|
|
43
|
-
fi
|
|
44
|
-
done
|
|
45
|
-
exit 0
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
if [ -n "$SKILLS_DIR" ]; then
|
|
49
|
-
intent validate "$SKILLS_DIR"
|
|
50
|
-
else
|
|
51
|
-
echo "No skills/ directory found — skipping validation."
|
|
52
|
-
fi
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|