Skip to content

Commit 13acc1b

Browse files
authored
Merge pull request #192 from aicodingstack/codex/catalog-discovery
feat(catalog): improve product discovery data
2 parents be7ced5 + 409f1a4 commit 13acc1b

72 files changed

Lines changed: 1596 additions & 1369 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/manifest-automation/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The merge helper is advisory. Always inspect its proposed result before applying
4242

4343
## GitHub stars
4444

45-
`data/github-stars.json` tracks `cli`, `desktop`, `extension`, and `ide` entries. Every CLI, desktop, extension, and IDE manifest must have a corresponding entry; use `null` when no official repository or trustworthy count is available. Models, providers, and vendors are not tracked.
45+
`data/github-stars.json` is a repository-keyed snapshot containing only the observation date and raw star counts. Product associations come from each CLI, desktop, extension, or IDE manifest's `githubUrl`; use `sourceCode` when the repository is only a partial source tree or serves as feedback or documentation rather than product source. Use `null` when a tracked repository has no trustworthy count. Models, providers, and vendors are not tracked.
4646

4747
## Validation
4848

.agents/skills/manifest-automation/scripts/lib/github-stars-updater.mjs

Lines changed: 81 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/**
44
* GitHub Stars Updater
5-
* Updates github-stars.json with new manifest entries
5+
* Keeps repository keys in github-stars.json aligned with product manifests.
66
*/
77

88
import fs from 'node:fs'
@@ -11,122 +11,101 @@ import { fileURLToPath } from 'node:url'
1111

1212
const __filename = fileURLToPath(import.meta.url)
1313
const __dirname = path.dirname(__filename)
14+
const manifestDirectories = {
15+
cli: 'clis',
16+
desktop: 'desktops',
17+
extension: 'extensions',
18+
ide: 'ides',
19+
}
1420

15-
/**
16-
* Get project root directory
17-
*/
1821
function getProjectRoot() {
1922
return path.resolve(__dirname, '../../../../..')
2023
}
2124

22-
/**
23-
* Get the path to github-stars.json
24-
*/
2525
function getGithubStarsPath() {
2626
return path.join(getProjectRoot(), 'data/github-stars.json')
2727
}
2828

29-
/**
30-
* Load github-stars.json
31-
* @returns {Object} The current github-stars data
32-
*/
33-
export function loadGithubStars() {
34-
const filePath = getGithubStarsPath()
29+
function getManifestPath(type, id) {
30+
const directory = manifestDirectories[type]
31+
return directory ? path.join(getProjectRoot(), 'manifests', directory, `${id}.json`) : null
32+
}
3533

36-
if (!fs.existsSync(filePath)) {
37-
throw new Error(`github-stars.json not found at: ${filePath}`)
38-
}
34+
function repositoryIdFromUrl(url) {
35+
const match = url
36+
?.replace(/\/$/, '')
37+
.replace(/\.git$/, '')
38+
.match(/^https:\/\/github\.com\/(.+\/.+)$/)
39+
return match?.[1] ?? null
40+
}
3941

40-
const content = fs.readFileSync(filePath, 'utf-8')
41-
return JSON.parse(content)
42+
function loadManifestRepository(type, id) {
43+
const manifestPath = getManifestPath(type, id)
44+
if (!manifestPath || !fs.existsSync(manifestPath)) return null
45+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
46+
return repositoryIdFromUrl(manifest.githubUrl)
4247
}
4348

44-
/**
45-
* Save github-stars.json
46-
* @param {Object} data - The github-stars data to save
47-
*/
48-
export function saveGithubStars(data) {
49-
const filePath = getGithubStarsPath()
50-
const content = `${JSON.stringify(data, null, 2)}\n`
51-
fs.writeFileSync(filePath, content, 'utf-8')
49+
function countRepositoryAssociations(repositoryId, excludedType, excludedId) {
50+
let count = 0
51+
for (const [type, directory] of Object.entries(manifestDirectories)) {
52+
const directoryPath = path.join(getProjectRoot(), 'manifests', directory)
53+
for (const file of fs.readdirSync(directoryPath).filter(name => name.endsWith('.json'))) {
54+
const id = file.replace(/\.json$/, '')
55+
if (type === excludedType && id === excludedId) continue
56+
const manifest = JSON.parse(fs.readFileSync(path.join(directoryPath, file), 'utf8'))
57+
if (repositoryIdFromUrl(manifest.githubUrl) === repositoryId) count += 1
58+
}
59+
}
60+
return count
5261
}
5362

54-
/**
55-
* Get the tracked category name from manifest type.
56-
* @param {string} type - Manifest type (cli, extension, ide, model)
57-
* @returns {string} Category name for github-stars.json
58-
*/
59-
function getCategoryName(type) {
60-
const mapping = {
61-
cli: 'clis',
62-
extension: 'extensions',
63-
ide: 'ides',
64-
model: 'models',
63+
export function loadGithubStars() {
64+
const filePath = getGithubStarsPath()
65+
if (!fs.existsSync(filePath)) {
66+
throw new Error(`github-stars.json not found at: ${filePath}`)
6567
}
68+
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
69+
}
6670

67-
return mapping[type] || null
71+
export function saveGithubStars(data) {
72+
const sortedRepositories = Object.fromEntries(
73+
Object.entries(data.repositories).sort(([left], [right]) => left.localeCompare(right))
74+
)
75+
fs.writeFileSync(
76+
getGithubStarsPath(),
77+
`${JSON.stringify({ ...data, repositories: sortedRepositories }, null, 2)}\n`,
78+
'utf8'
79+
)
6880
}
6981

70-
/**
71-
* Update github-stars.json with a new or updated manifest entry
72-
* @param {string} type - Manifest type (cli, extension, ide, etc.)
73-
* @param {string} id - Manifest id
74-
* @param {Object} options - Options
75-
* @param {boolean} options.isNew - Whether this is a new entry (true) or update (false)
76-
* @returns {Object} Result with status and message
77-
*/
7882
export function updateGithubStarsEntry(type, id, options = {}) {
7983
const { isNew = false } = options
8084

8185
try {
82-
// Load current data
83-
const githubStars = loadGithubStars()
84-
const category = getCategoryName(type)
85-
86-
if (!category || !Object.hasOwn(githubStars, category)) {
86+
const repositoryId = loadManifestRepository(type, id)
87+
if (!repositoryId) {
8788
return {
8889
status: 'skipped',
89-
message: `Manifest type "${type}" is not tracked by data/github-stars.json`,
90+
message: `Manifest "${type}:${id}" has no tracked GitHub repository`,
9091
}
9192
}
9293

93-
// Check if entry already exists
94-
const exists = id in githubStars[category]
95-
94+
const githubStars = loadGithubStars()
95+
const exists = Object.hasOwn(githubStars.repositories, repositoryId)
9696
if (isNew && exists) {
9797
return {
9898
status: 'skipped',
99-
message: `Entry "${id}" already exists in github-stars.json under "${category}"`,
100-
}
101-
}
102-
103-
if (!isNew && !exists) {
104-
return {
105-
status: 'skipped',
106-
message: `Entry "${id}" does not exist in github-stars.json under "${category}"; no change made. Verify the official repository, then use the add command.`,
99+
message: `Repository "${repositoryId}" already exists in github-stars.json`,
107100
}
108101
}
109102

110-
// Add or update entry with null (stars will be fetched later)
111-
githubStars[category][id] = null
112-
113-
// Sort entries alphabetically within category
114-
const sortedCategory = Object.keys(githubStars[category])
115-
.sort()
116-
.reduce((acc, key) => {
117-
acc[key] = githubStars[category][key]
118-
return acc
119-
}, {})
120-
121-
githubStars[category] = sortedCategory
122-
123-
// Save updated data
103+
githubStars.repositories[repositoryId] ??= null
124104
saveGithubStars(githubStars)
125-
126105
return {
127106
status: 'success',
128-
message: `Updated github-stars.json: ${category}["${id}"] = null`,
129-
action: exists ? 'updated' : 'added',
107+
message: `Tracked github-stars.json repository "${repositoryId}"`,
108+
action: exists ? 'unchanged' : 'added',
130109
}
131110
} catch (error) {
132111
return {
@@ -137,83 +116,59 @@ export function updateGithubStarsEntry(type, id, options = {}) {
137116
}
138117
}
139118

140-
/**
141-
* Remove an entry from github-stars.json
142-
* @param {string} type - Manifest type
143-
* @param {string} id - Manifest id
144-
* @returns {Object} Result with status and message
145-
*/
146119
export function removeGithubStarsEntry(type, id) {
147120
try {
148-
const githubStars = loadGithubStars()
149-
const category = getCategoryName(type)
121+
const repositoryId = loadManifestRepository(type, id)
122+
if (!repositoryId) {
123+
return {
124+
status: 'skipped',
125+
message: `Manifest "${type}:${id}" has no tracked GitHub repository`,
126+
}
127+
}
150128

151-
if (!category || !Object.hasOwn(githubStars, category)) {
129+
if (countRepositoryAssociations(repositoryId, type, id) > 0) {
152130
return {
153131
status: 'skipped',
154-
message: `Manifest type "${type}" is not tracked by data/github-stars.json`,
132+
message: `Repository "${repositoryId}" is still used by another product surface`,
155133
}
156134
}
157135

158-
if (!githubStars[category] || !(id in githubStars[category])) {
136+
const githubStars = loadGithubStars()
137+
if (!Object.hasOwn(githubStars.repositories, repositoryId)) {
159138
return {
160139
status: 'skipped',
161-
message: `Entry "${id}" not found in github-stars.json under "${category}"`,
140+
message: `Repository "${repositoryId}" is not tracked in github-stars.json`,
162141
}
163142
}
164143

165-
delete githubStars[category][id]
144+
delete githubStars.repositories[repositoryId]
166145
saveGithubStars(githubStars)
167-
168146
return {
169147
status: 'success',
170-
message: `Removed "${id}" from github-stars.json under "${category}"`,
148+
message: `Removed repository "${repositoryId}" from github-stars.json`,
171149
}
172150
} catch (error) {
173151
return {
174152
status: 'error',
175-
message: `Failed to remove entry from github-stars.json: ${error.message}`,
153+
message: `Failed to remove repository from github-stars.json: ${error.message}`,
176154
error,
177155
}
178156
}
179157
}
180158

181-
/**
182-
* CLI entry point for testing
183-
*/
184159
if (import.meta.url === `file://${process.argv[1]}`) {
185160
const [, , command, type, id] = process.argv
186-
187-
if (!command || !['add', 'update', 'remove'].includes(command)) {
161+
if (!command || !['add', 'update', 'remove'].includes(command) || !type || !id) {
188162
console.error('Usage:')
189-
console.error(' node github-stars-updater.mjs add <type> <id>')
190-
console.error(' node github-stars-updater.mjs update <type> <id>')
191-
console.error(' node github-stars-updater.mjs remove <type> <id>')
192-
console.error('')
193-
console.error('Examples:')
194-
console.error(' node github-stars-updater.mjs add cli cursor-cli')
195-
console.error(' node github-stars-updater.mjs update extension claude-code')
196-
console.error(' node github-stars-updater.mjs remove ide windsurf')
197-
process.exit(1)
198-
}
199-
200-
if (!type || !id) {
201-
console.error('Error: type and id are required')
163+
console.error(' node github-stars-updater.mjs <add|update|remove> <type> <id>')
202164
process.exit(1)
203165
}
204166

205-
let result
206-
207-
if (command === 'add' || command === 'update') {
208-
result = updateGithubStarsEntry(type, id, { isNew: command === 'add' })
209-
} else {
210-
result = removeGithubStarsEntry(type, id)
211-
}
212-
167+
const result =
168+
command === 'remove'
169+
? removeGithubStarsEntry(type, id)
170+
: updateGithubStarsEntry(type, id, { isNew: command === 'add' })
213171
console.log(`Status: ${result.status}`)
214172
console.log(`Message: ${result.message}`)
215-
216-
if (result.status === 'error') {
217-
process.exit(1)
218-
}
173+
if (result.status === 'error') process.exit(1)
219174
}

cspell.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"продакшену",
5353
"confiabilidade",
5454
"gözlemlenebilirliğini",
55+
"governança",
5556
"modelnya",
5657
"observabilitas",
5758
"observabilidad",
@@ -76,6 +77,7 @@
7677
"ccstatusline",
7778
"API'lerle",
7879
"acli",
80+
"aaif",
7981
"anomalyco",
8082
"aracidir",
8183
"glab",
@@ -142,6 +144,7 @@
142144
"Junie",
143145
"Kimi",
144146
"Kiro",
147+
"kirodotdev",
145148
"Kode",
146149
"lmstudio",
147150
"multiherramienta",

0 commit comments

Comments
 (0)