@hidden-leaf/x-skill 1.0.0
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/.env.example +50 -0
- package/CLAUDE.snippet.md +34 -0
- package/README.md +220 -0
- package/SKILL.md +265 -0
- package/dist/cache/store.d.ts +48 -0
- package/dist/cache/store.d.ts.map +1 -0
- package/dist/cache/store.js +381 -0
- package/dist/cache/store.js.map +1 -0
- package/dist/clients/types.d.ts +217 -0
- package/dist/clients/types.d.ts.map +1 -0
- package/dist/clients/types.js +77 -0
- package/dist/clients/types.js.map +1 -0
- package/dist/clients/x-client.d.ts +111 -0
- package/dist/clients/x-client.d.ts.map +1 -0
- package/dist/clients/x-client.js +421 -0
- package/dist/clients/x-client.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/skills/bookmarks/index.d.ts +79 -0
- package/dist/skills/bookmarks/index.d.ts.map +1 -0
- package/dist/skills/bookmarks/index.js +288 -0
- package/dist/skills/bookmarks/index.js.map +1 -0
- package/dist/skills/bookmarks/synthesize.d.ts +40 -0
- package/dist/skills/bookmarks/synthesize.d.ts.map +1 -0
- package/dist/skills/bookmarks/synthesize.js +164 -0
- package/dist/skills/bookmarks/synthesize.js.map +1 -0
- package/dist/skills/bookmarks/types.d.ts +71 -0
- package/dist/skills/bookmarks/types.d.ts.map +1 -0
- package/dist/skills/bookmarks/types.js +6 -0
- package/dist/skills/bookmarks/types.js.map +1 -0
- package/dist/utils/logger.d.ts +10 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +43 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +68 -0
- package/scripts/cache-report.ts +25 -0
- package/scripts/create-atlsk-ticket.ts +68 -0
- package/scripts/create-deep-dive-ticket.ts +66 -0
- package/scripts/create-jira-project.ts +31 -0
- package/scripts/create-launch-and-roadmap.ts +200 -0
- package/scripts/create-next-session.ts +71 -0
- package/scripts/create-roadmap.ts +150 -0
- package/scripts/debug-api.ts +45 -0
- package/scripts/debug-folder.ts +37 -0
- package/scripts/jira-close-v1-tickets.ts +83 -0
- package/scripts/jira-v1-close-and-post-v1.ts +272 -0
- package/scripts/oauth-flow.ts +216 -0
- package/scripts/postinstall.js +112 -0
- package/scripts/sync-test.ts +36 -0
- package/scripts/test-refresh-forced.ts +29 -0
- package/scripts/test-refresh.ts +25 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as dotenv from 'dotenv';
|
|
2
|
+
dotenv.config();
|
|
3
|
+
import { createJiraClientFromEnv, adf, text } from '@hidden-leaf/atlassian-skill';
|
|
4
|
+
|
|
5
|
+
const tickets = [
|
|
6
|
+
{
|
|
7
|
+
project: 'XSKILL',
|
|
8
|
+
summary: 'Migrate GPT-generated docs (strategy PDF, briefs, deck) into HLN repo',
|
|
9
|
+
type: 'Task',
|
|
10
|
+
description:
|
|
11
|
+
'The Edge Systems Strategy v1.0, Sentinel Product Brief, Forge QC Product Brief, and pitch deck ' +
|
|
12
|
+
'were generated in a ChatGPT session and live in ~/Downloads. These need to be:\n' +
|
|
13
|
+
'1. Pulled into a docs/ directory in the appropriate HLN repo (or new hln-docs repo)\n' +
|
|
14
|
+
'2. Version-controlled with DocForge\n' +
|
|
15
|
+
'3. Converted to markdown source + PDF build pipeline so future edits are tracked\n' +
|
|
16
|
+
'4. Updated to reflect the full x-skill intelligence pipeline integration',
|
|
17
|
+
labels: ['docs', 'next-session'],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
project: 'XSKILL',
|
|
21
|
+
summary: 'Next session plan: token refresh → deep dive briefs → Sentinel repo scaffold',
|
|
22
|
+
type: 'Task',
|
|
23
|
+
description:
|
|
24
|
+
'Pre-planned next session flow:\n\n' +
|
|
25
|
+
'1. XSKILL-9: Implement OAuth auto-refresh (token expires ~2h, will be dead by next session)\n' +
|
|
26
|
+
'2. XSKILL-29: Run first research briefs on priority folders (CHIPS, Robotics, AI HARDWARE, LLMs, Claude)\n' +
|
|
27
|
+
'3. Scaffold hln-sentinel repo (Docker + FastAPI + Jinja2 + YOLO, per GPT dev plan)\n' +
|
|
28
|
+
'4. Wire x-skill brief output → Jira ticket creation for actionable intelligence\n' +
|
|
29
|
+
'5. If time: start Forge QC repo scaffold in parallel\n\n' +
|
|
30
|
+
'Goal: by end of next session, have auto-refreshing tokens, 5 research briefs generated, ' +
|
|
31
|
+
'and the Sentinel repo bootstrapped with Docker + basic detection running.',
|
|
32
|
+
labels: ['planning', 'next-session'],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
project: 'HLN',
|
|
36
|
+
summary: 'Establish DocForge pipeline for HLN versioned documentation',
|
|
37
|
+
type: 'Story',
|
|
38
|
+
description:
|
|
39
|
+
'HLN now has multiple strategy docs, product briefs, and pitch materials being generated ' +
|
|
40
|
+
'across sessions (Claude Code, ChatGPT, manual). Need a unified doc pipeline:\n' +
|
|
41
|
+
'- Markdown source files in repo\n' +
|
|
42
|
+
'- PDF generation (pandoc or similar)\n' +
|
|
43
|
+
'- Version tracking via git\n' +
|
|
44
|
+
'- Confluence sync for living docs\n' +
|
|
45
|
+
'- DocForge integration for Claude Code-assisted writing\n\n' +
|
|
46
|
+
'Docs to migrate: Edge Systems Strategy v1.0, Sentinel Brief, Forge QC Brief, ' +
|
|
47
|
+
'Detroit Semiconductor Hub pitch deck, investor one-pager.',
|
|
48
|
+
labels: ['docs', 'infrastructure', 'docforge'],
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
async function main() {
|
|
53
|
+
const jira = createJiraClientFromEnv();
|
|
54
|
+
|
|
55
|
+
for (const t of tickets) {
|
|
56
|
+
const issue = await jira.createIssue({
|
|
57
|
+
project: t.project,
|
|
58
|
+
issuetype: t.type,
|
|
59
|
+
summary: t.summary,
|
|
60
|
+
description: adf().paragraph(text().text(t.description)).build(),
|
|
61
|
+
labels: t.labels,
|
|
62
|
+
});
|
|
63
|
+
console.log(`${issue.key} — ${t.summary}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
main().catch((err) => {
|
|
68
|
+
console.error('Failed:', err.message);
|
|
69
|
+
if (err.response?.data) console.error(JSON.stringify(err.response.data, null, 2));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create XSKILL 1.0 roadmap tickets in Jira.
|
|
3
|
+
*/
|
|
4
|
+
import * as dotenv from 'dotenv';
|
|
5
|
+
dotenv.config();
|
|
6
|
+
import { createJiraClientFromEnv, adf, text } from '@hidden-leaf/atlassian-skill';
|
|
7
|
+
|
|
8
|
+
interface TicketDef {
|
|
9
|
+
summary: string;
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
labels: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const tickets: TicketDef[] = [
|
|
16
|
+
// ========== DONE (log what we built) ==========
|
|
17
|
+
{
|
|
18
|
+
summary: 'Scaffold x-skill package with TypeScript, ESLint, Jest, CI',
|
|
19
|
+
type: 'Task',
|
|
20
|
+
description: 'Initial project scaffold following atlassian-skill template pattern. package.json, tsconfig (strict, ES2022, NodeNext), eslint, jest, GitHub Actions CI on Node 18+20. DONE.',
|
|
21
|
+
labels: ['done', 'scaffold'],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
summary: 'Implement X API v2 client with OAuth 2.0 User Context',
|
|
25
|
+
type: 'Task',
|
|
26
|
+
description: 'XClient with OAuth 2.0 Bearer auth (User Context, not App-only). Endpoints: GET /bookmarks, GET /bookmarks/folders, GET /bookmarks/folders/:id, GET /tweets (lookup), GET /users/me. Auto-pagination, retry with axios-retry, error normalization (XApiRequestError, XAuthenticationError, XRateLimitError, XNotFoundError). DONE.',
|
|
27
|
+
labels: ['done', 'client'],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
summary: 'Implement SQLite cache layer (sync-then-read architecture)',
|
|
31
|
+
type: 'Task',
|
|
32
|
+
description: 'BookmarkStore using better-sqlite3. Schema: folders, tweets, users, folder_tweets (junction), sync_log. WAL mode, foreign keys. Upsert operations, batch sync transactions, read operations for all skill commands. Cache at ~/.x-skill/bookmarks.db. DONE.',
|
|
33
|
+
labels: ['done', 'cache'],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
summary: 'Implement BookmarksSkill: sync, list, fetch, brief, stats',
|
|
37
|
+
type: 'Task',
|
|
38
|
+
description: 'BookmarksSkill class with sync-then-read pattern. syncAll() and syncFolder() hit X API and populate cache. listFolders(), fetchByFolderName(), fetchByFolderId(), fetchAll(), brief(), stats() all read from cache (free, instant, offline). DONE.',
|
|
39
|
+
labels: ['done', 'skill'],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
summary: 'Implement tweet hydration via GET /2/tweets lookup',
|
|
43
|
+
type: 'Task',
|
|
44
|
+
description: 'Folder endpoint returns only tweet IDs (no text/metrics). Added getTweetsByIds() to hydrate missing tweets in batches of 100 via GET /2/tweets. Sync now cross-references: main bookmarks (paginated, full data) + folder IDs + hydration. Result: 334 tweets, 269 users, 20 folders fully cached. DONE.',
|
|
45
|
+
labels: ['done', 'hydration'],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
summary: 'Implement brief prompt builder for Claude synthesis',
|
|
49
|
+
type: 'Task',
|
|
50
|
+
description: 'buildBriefPrompt() generates structured research analyst prompt from enriched bookmarks. Sections: Key Themes, Notable Voices, Signal vs Noise, Actionable Intelligence, HLN Relevance. extractNotableVoices() ranks by engagement, extractThemes() uses context annotations + hashtags. DONE.',
|
|
51
|
+
labels: ['done', 'brief'],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
summary: 'Write SKILL.md, CLAUDE.md, postinstall, OAuth flow script',
|
|
55
|
+
type: 'Task',
|
|
56
|
+
description: 'SKILL.md (full API reference for Claude Code), CLAUDE.md (dev guide), CLAUDE.snippet.md (auto-injected into consumers), postinstall.js (walks up from node_modules to find project root), oauth-flow.ts (PKCE flow, local callback server, prints token + user ID). DONE.',
|
|
57
|
+
labels: ['done', 'docs'],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
summary: 'Create private GitHub repo and XSKILL Jira project',
|
|
61
|
+
type: 'Task',
|
|
62
|
+
description: 'Hidden-Leaf-Networks/x-skill (private until 1.0). XSKILL Jira project created with Scrum template. DONE.',
|
|
63
|
+
labels: ['done', 'infra'],
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// ========== TODO for 1.0 ==========
|
|
67
|
+
{
|
|
68
|
+
summary: 'Auto-refresh expired OAuth access tokens using refresh token',
|
|
69
|
+
type: 'Task',
|
|
70
|
+
description: 'X OAuth 2.0 access tokens expire after ~2 hours. The refresh token (X_REFRESH_TOKEN) is long-lived. Add auto-refresh logic to XClient: detect 401, exchange refresh token for new access token via POST /2/oauth2/token, update .env or in-memory config, retry the failed request. Without this, users must re-run oauth-flow.ts every 2 hours.',
|
|
71
|
+
labels: ['auth', 'v1', 'must-have'],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
summary: 'Client-side folder inference for unlinked tweets',
|
|
75
|
+
type: 'Task',
|
|
76
|
+
description: 'X API caps folder endpoint at 20 tweet IDs per folder (no pagination). Tweets beyond the cap are in the cache but not linked to a folder. Build a classifier that uses context_annotations, hashtags, entities, and keyword matching to infer folder membership for unlinked tweets. Match against existing folder names. This ensures fetchByFolderName() returns complete results even past the 20-ID API cap.',
|
|
77
|
+
labels: ['intelligence', 'v1', 'must-have'],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
summary: 'Write unit tests for XClient, BookmarkStore, and BookmarksSkill',
|
|
81
|
+
type: 'Task',
|
|
82
|
+
description: 'Jest + ts-jest configured but no tests yet. Need: XClient tests (mock axios, verify endpoint URLs, error handling, pagination), BookmarkStore tests (in-memory SQLite, upsert/read/sync operations), BookmarksSkill tests (mock client + store, verify sync-then-read flow). Target: 80%+ coverage on core modules.',
|
|
83
|
+
labels: ['testing', 'v1', 'must-have'],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
summary: 'Deep pagination on main /bookmarks endpoint',
|
|
87
|
+
type: 'Task',
|
|
88
|
+
description: 'getAllBookmarks() paginates via next_token but should verify it actually exhausts all pages. Add logging to show page count and total during sync. Confirm we get ALL bookmarks, not just the first 100. Add a max_pages safety limit to prevent runaway pagination on large accounts.',
|
|
89
|
+
labels: ['sync', 'v1'],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
summary: 'Polish README for public 1.0 launch',
|
|
93
|
+
type: 'Task',
|
|
94
|
+
description: 'Current README is functional but needs: badges (npm version, CI status, license), GIF/screenshot of sync + brief output, architecture diagram (sync → cache → brief → downstream), contributing section, changelog. Should match atlassian-skill README quality.',
|
|
95
|
+
labels: ['docs', 'v1'],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
summary: 'Add token refresh script for manual re-auth',
|
|
99
|
+
type: 'Task',
|
|
100
|
+
description: 'Companion to auto-refresh. A standalone script (scripts/refresh-token.ts) that reads X_REFRESH_TOKEN from .env, exchanges it for a new access token, and prints the updated values. Useful when auto-refresh fails or for manual recovery.',
|
|
101
|
+
labels: ['auth', 'v1'],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
summary: 'Publish v1.0.0 to npm as @hidden-leaf/x-skill (public)',
|
|
105
|
+
type: 'Task',
|
|
106
|
+
description: 'Final 1.0 checklist: all tests pass, README polished, CHANGELOG written, version bumped to 1.0.0, npm publish --access public. Switch GitHub repo from private to public. Announce on X from @hlntre.',
|
|
107
|
+
labels: ['release', 'v1', 'milestone'],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
summary: 'Brief output: pipe through Claude API for direct synthesis',
|
|
111
|
+
type: 'Story',
|
|
112
|
+
description: 'Currently buildBriefPrompt() generates the prompt and the caller (Claude Code) does the synthesis. For programmatic use outside Claude Code, add an option to call Claude API directly from the brief() method. Requires ANTHROPIC_API_KEY in .env. Should be optional — default behavior stays as prompt-only for Claude Code usage.',
|
|
113
|
+
labels: ['brief', 'v1', 'nice-to-have'],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
summary: 'Add cache expiry and selective re-sync',
|
|
117
|
+
type: 'Story',
|
|
118
|
+
description: 'Add sync staleness tracking: show how old each folder\'s cache is, warn if >24h stale, offer selective re-sync (only folders that changed). Reduces API cost for frequent users. Consider a --force flag to bypass staleness checks.',
|
|
119
|
+
labels: ['cache', 'v1', 'nice-to-have'],
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
async function main() {
|
|
124
|
+
const jira = createJiraClientFromEnv();
|
|
125
|
+
|
|
126
|
+
console.log(`Creating ${tickets.length} XSKILL tickets...\n`);
|
|
127
|
+
|
|
128
|
+
for (const ticket of tickets) {
|
|
129
|
+
const issue = await jira.createIssue({
|
|
130
|
+
project: 'XSKILL',
|
|
131
|
+
issuetype: ticket.type,
|
|
132
|
+
summary: ticket.summary,
|
|
133
|
+
description: adf()
|
|
134
|
+
.paragraph(text().text(ticket.description))
|
|
135
|
+
.build(),
|
|
136
|
+
labels: ticket.labels,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const isDone = ticket.labels.includes('done');
|
|
140
|
+
console.log(` ${issue.key} ${isDone ? '[DONE]' : '[TODO]'} ${ticket.summary}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log('\nDone! All tickets created in XSKILL project.');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
main().catch((err) => {
|
|
147
|
+
console.error('Failed:', err.message);
|
|
148
|
+
if (err.response?.data) console.error(JSON.stringify(err.response.data, null, 2));
|
|
149
|
+
process.exit(1);
|
|
150
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug script — test individual X API endpoints to see what's available.
|
|
3
|
+
*/
|
|
4
|
+
import * as dotenv from 'dotenv';
|
|
5
|
+
dotenv.config();
|
|
6
|
+
|
|
7
|
+
const TOKEN = process.env.X_USER_ACCESS_TOKEN;
|
|
8
|
+
const USER_ID = process.env.X_USER_ID;
|
|
9
|
+
|
|
10
|
+
async function testEndpoint(name: string, url: string) {
|
|
11
|
+
console.log(`\n--- ${name} ---`);
|
|
12
|
+
console.log(`GET ${url}`);
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(url, {
|
|
15
|
+
headers: { Authorization: `Bearer ${TOKEN}` },
|
|
16
|
+
});
|
|
17
|
+
const body = await res.text();
|
|
18
|
+
console.log(`Status: ${res.status}`);
|
|
19
|
+
console.log(`Response: ${body.slice(0, 500)}`);
|
|
20
|
+
} catch (err: unknown) {
|
|
21
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
22
|
+
console.log(`Error: ${msg}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function main() {
|
|
27
|
+
console.log(`User ID: ${USER_ID}`);
|
|
28
|
+
|
|
29
|
+
// 1. Test auth — /2/users/me
|
|
30
|
+
await testEndpoint('GET /2/users/me', 'https://api.x.com/2/users/me');
|
|
31
|
+
|
|
32
|
+
// 2. Test bookmarks — /2/users/:id/bookmarks
|
|
33
|
+
await testEndpoint(
|
|
34
|
+
'GET /2/users/:id/bookmarks',
|
|
35
|
+
`https://api.x.com/2/users/${USER_ID}/bookmarks?max_results=5&tweet.fields=author_id,created_at,text`,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// 3. Test bookmark folders — /2/users/:id/bookmarks/folders
|
|
39
|
+
await testEndpoint(
|
|
40
|
+
'GET /2/users/:id/bookmarks/folders',
|
|
41
|
+
`https://api.x.com/2/users/${USER_ID}/bookmarks/folders`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
main();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as dotenv from 'dotenv';
|
|
2
|
+
dotenv.config();
|
|
3
|
+
|
|
4
|
+
const TOKEN = process.env.X_USER_ACCESS_TOKEN;
|
|
5
|
+
const USER_ID = process.env.X_USER_ID;
|
|
6
|
+
|
|
7
|
+
// Grab first folder ID from the folders response
|
|
8
|
+
async function main() {
|
|
9
|
+
// Get folders
|
|
10
|
+
const fRes = await fetch(`https://api.x.com/2/users/${USER_ID}/bookmarks/folders`, {
|
|
11
|
+
headers: { Authorization: `Bearer ${TOKEN}` },
|
|
12
|
+
});
|
|
13
|
+
const folders = await fRes.json() as { data: Array<{ id: string; name: string }> };
|
|
14
|
+
console.log('Folders:', JSON.stringify(folders.data?.slice(0, 3), null, 2));
|
|
15
|
+
|
|
16
|
+
if (!folders.data?.[0]) return;
|
|
17
|
+
|
|
18
|
+
// Get tweets from first folder
|
|
19
|
+
const tRes = await fetch(
|
|
20
|
+
`https://api.x.com/2/users/${USER_ID}/bookmarks/folders/${folders.data[0].id}`,
|
|
21
|
+
{ headers: { Authorization: `Bearer ${TOKEN}` } },
|
|
22
|
+
);
|
|
23
|
+
const tweets = await tRes.json();
|
|
24
|
+
console.log(`\nFolder "${folders.data[0].name}" response:`);
|
|
25
|
+
console.log(JSON.stringify(tweets, null, 2).slice(0, 2000));
|
|
26
|
+
|
|
27
|
+
// Also check main bookmarks endpoint shape for comparison
|
|
28
|
+
const bRes = await fetch(
|
|
29
|
+
`https://api.x.com/2/users/${USER_ID}/bookmarks?max_results=2&tweet.fields=author_id,created_at,text,public_metrics,entities,context_annotations&user.fields=name,username,description,public_metrics&expansions=author_id`,
|
|
30
|
+
{ headers: { Authorization: `Bearer ${TOKEN}` } },
|
|
31
|
+
);
|
|
32
|
+
const bookmarks = await bRes.json();
|
|
33
|
+
console.log('\nMain bookmarks endpoint (with expansions):');
|
|
34
|
+
console.log(JSON.stringify(bookmarks, null, 2).slice(0, 2000));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
main();
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Close v1.0 tickets that are now complete.
|
|
3
|
+
* Transitions matching open issues to Done (31).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import 'dotenv/config';
|
|
7
|
+
import {
|
|
8
|
+
createJiraClientFromEnv,
|
|
9
|
+
jql,
|
|
10
|
+
textToAdf,
|
|
11
|
+
} from '@hidden-leaf/atlassian-skill';
|
|
12
|
+
|
|
13
|
+
const jira = createJiraClientFromEnv();
|
|
14
|
+
const DONE = '31';
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
console.log('=== Close XSKILL v1 tickets ===\n');
|
|
18
|
+
|
|
19
|
+
const openIssues = await jira.searchIssues({
|
|
20
|
+
jql: jql()
|
|
21
|
+
.equals('project', 'XSKILL')
|
|
22
|
+
.in('status', ['To Do', 'In Progress', 'Backlog', 'Selected for Development'])
|
|
23
|
+
.build(),
|
|
24
|
+
fields: ['summary', 'status'],
|
|
25
|
+
maxResults: 100,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log(`Found ${openIssues.issues.length} open issues\n`);
|
|
29
|
+
|
|
30
|
+
const v1CompletedPatterns = [
|
|
31
|
+
/unit test/i,
|
|
32
|
+
/write.*test/i,
|
|
33
|
+
/readme/i,
|
|
34
|
+
/polish readme/i,
|
|
35
|
+
/publish.*1\.0/i,
|
|
36
|
+
/publish.*npm/i,
|
|
37
|
+
/auto.?refresh.*oauth/i,
|
|
38
|
+
/token refresh/i,
|
|
39
|
+
/refresh.*script/i,
|
|
40
|
+
/launch.*public/i,
|
|
41
|
+
/deep pagination/i,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
for (const issue of openIssues.issues) {
|
|
45
|
+
const summary = issue.fields.summary as string;
|
|
46
|
+
const key = issue.key;
|
|
47
|
+
const isV1Done = v1CompletedPatterns.some((p) => p.test(summary));
|
|
48
|
+
|
|
49
|
+
if (isV1Done) {
|
|
50
|
+
try {
|
|
51
|
+
// Transition without comment first
|
|
52
|
+
await jira.transitionIssue(key, { transitionId: DONE });
|
|
53
|
+
console.log(` ✓ ${key} → Done: "${summary}"`);
|
|
54
|
+
|
|
55
|
+
// Then add comment separately
|
|
56
|
+
await jira.addComment(key, {
|
|
57
|
+
body: textToAdf('Completed in v1.0.0 release (2026-03-31). Commit 72422cd. Repo public at github.com/Hidden-Leaf-Networks/x-skill'),
|
|
58
|
+
});
|
|
59
|
+
} catch (err: any) {
|
|
60
|
+
console.log(` ✗ ${key}: "${summary}" — ${err.message ?? err}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Also undo the accidental Done on the v1.1 epic
|
|
66
|
+
console.log('\nFixing XSKILL-32 (v1.1 epic should not be Done)...');
|
|
67
|
+
try {
|
|
68
|
+
const transitions = await jira.getTransitions('XSKILL-32');
|
|
69
|
+
console.log(' Available transitions:', transitions.map((t: any) => `${t.id}: ${t.name}`).join(', '));
|
|
70
|
+
// Find "To Do" transition
|
|
71
|
+
const toDoTransition = transitions.find((t: any) => t.name === 'To Do' || t.name === 'Backlog');
|
|
72
|
+
if (toDoTransition) {
|
|
73
|
+
await jira.transitionIssue('XSKILL-32', { transitionId: toDoTransition.id });
|
|
74
|
+
console.log(` ✓ XSKILL-32 → ${toDoTransition.name}`);
|
|
75
|
+
}
|
|
76
|
+
} catch (err: any) {
|
|
77
|
+
console.log(` ✗ Could not reopen XSKILL-32: ${err.message}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log('\nDone.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Close v1.0 tickets and create post-v1 work in Jira.
|
|
3
|
+
*
|
|
4
|
+
* - Searches XSKILL for open issues that are done (tests, README, publish, token refresh)
|
|
5
|
+
* - Transitions them to Done
|
|
6
|
+
* - Creates v1.1 improvement epics and tasks
|
|
7
|
+
* - Creates a v1.0.0 release comment on the launch ticket
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import 'dotenv/config';
|
|
11
|
+
import {
|
|
12
|
+
createJiraClientFromEnv,
|
|
13
|
+
jql,
|
|
14
|
+
adf,
|
|
15
|
+
markdownToAdf,
|
|
16
|
+
} from '@hidden-leaf/atlassian-skill';
|
|
17
|
+
|
|
18
|
+
const jira = createJiraClientFromEnv();
|
|
19
|
+
|
|
20
|
+
// Transition IDs (standard Kanban)
|
|
21
|
+
const DONE = '31';
|
|
22
|
+
const IN_PROGRESS = '21';
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
console.log('=== XSKILL Jira v1.0 Close & Post-v1 Setup ===\n');
|
|
26
|
+
|
|
27
|
+
// ========================================================================
|
|
28
|
+
// Step 1: Find all open XSKILL issues
|
|
29
|
+
// ========================================================================
|
|
30
|
+
|
|
31
|
+
console.log('Searching for open XSKILL issues...');
|
|
32
|
+
const openIssues = await jira.searchIssues({
|
|
33
|
+
jql: jql()
|
|
34
|
+
.equals('project', 'XSKILL')
|
|
35
|
+
.in('status', ['To Do', 'In Progress', 'Backlog', 'Selected for Development'])
|
|
36
|
+
.build(),
|
|
37
|
+
fields: ['summary', 'status', 'issuetype'],
|
|
38
|
+
maxResults: 100,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
console.log(`Found ${openIssues.issues.length} open issues\n`);
|
|
42
|
+
|
|
43
|
+
// ========================================================================
|
|
44
|
+
// Step 2: Identify v1 items that are now complete and transition to Done
|
|
45
|
+
// ========================================================================
|
|
46
|
+
|
|
47
|
+
// Keywords that indicate v1 work we just completed
|
|
48
|
+
const v1CompletedPatterns = [
|
|
49
|
+
/unit test/i,
|
|
50
|
+
/write.*test/i,
|
|
51
|
+
/readme/i,
|
|
52
|
+
/polish readme/i,
|
|
53
|
+
/publish.*1\.0/i,
|
|
54
|
+
/publish.*npm/i,
|
|
55
|
+
/auto.?refresh.*oauth/i,
|
|
56
|
+
/token refresh/i,
|
|
57
|
+
/refresh.*script/i,
|
|
58
|
+
/launch.*public/i,
|
|
59
|
+
/deep pagination/i,
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const transitioned: string[] = [];
|
|
63
|
+
|
|
64
|
+
for (const issue of openIssues.issues) {
|
|
65
|
+
const summary = issue.fields.summary as string;
|
|
66
|
+
const key = issue.key;
|
|
67
|
+
const isV1Done = v1CompletedPatterns.some((p) => p.test(summary));
|
|
68
|
+
|
|
69
|
+
if (isV1Done) {
|
|
70
|
+
try {
|
|
71
|
+
await jira.transitionIssue(key, {
|
|
72
|
+
transitionId: DONE,
|
|
73
|
+
comment: markdownToAdf(
|
|
74
|
+
`Completed as part of v1.0.0 release (commit \`72422cd\`).\n\n` +
|
|
75
|
+
`- 93 unit tests across 5 suites\n` +
|
|
76
|
+
`- README rewritten with badges, API reference, architecture diagram\n` +
|
|
77
|
+
`- CHANGELOG.md added\n` +
|
|
78
|
+
`- Version bumped to 1.0.0\n` +
|
|
79
|
+
`- Repo flipped to public: github.com/Hidden-Leaf-Networks/x-skill`
|
|
80
|
+
),
|
|
81
|
+
});
|
|
82
|
+
console.log(` ✓ ${key}: "${summary}" → Done`);
|
|
83
|
+
transitioned.push(key);
|
|
84
|
+
} catch (err: any) {
|
|
85
|
+
console.log(` ✗ ${key}: "${summary}" — ${err.message ?? err}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(`\nTransitioned ${transitioned.length} issues to Done\n`);
|
|
91
|
+
|
|
92
|
+
// ========================================================================
|
|
93
|
+
// Step 3: Create post-v1 work
|
|
94
|
+
// ========================================================================
|
|
95
|
+
|
|
96
|
+
console.log('Creating post-v1 work items...\n');
|
|
97
|
+
|
|
98
|
+
// --- v1.1 Epic ---
|
|
99
|
+
const v11Epic = await jira.createIssue({
|
|
100
|
+
project: 'XSKILL',
|
|
101
|
+
issuetype: 'Epic',
|
|
102
|
+
summary: '[v1.1] Post-launch hardening and DX improvements',
|
|
103
|
+
description: markdownToAdf(
|
|
104
|
+
`## Goal\n\n` +
|
|
105
|
+
`Harden v1.0 based on real-world usage, improve developer experience, ` +
|
|
106
|
+
`and address gaps discovered during launch.\n\n` +
|
|
107
|
+
`## Scope\n\n` +
|
|
108
|
+
`- Error recovery and resilience\n` +
|
|
109
|
+
`- Cache management (TTL, invalidation, selective re-sync)\n` +
|
|
110
|
+
`- OAuth flow UX polish\n` +
|
|
111
|
+
`- TypeDoc API documentation generation\n` +
|
|
112
|
+
`- Integration test suite\n` +
|
|
113
|
+
`- npm publish automation`
|
|
114
|
+
),
|
|
115
|
+
labels: ['v1.1', 'hardening'],
|
|
116
|
+
});
|
|
117
|
+
console.log(` ✓ Created epic: ${v11Epic.key} — [v1.1] Post-launch hardening`);
|
|
118
|
+
|
|
119
|
+
// --- v1.1 Tasks ---
|
|
120
|
+
const v11Tasks = [
|
|
121
|
+
{
|
|
122
|
+
summary: 'Add circuit breaker for repeated API failures',
|
|
123
|
+
description:
|
|
124
|
+
`Implement circuit breaker pattern in XClient to prevent hammering the API ` +
|
|
125
|
+
`when X is down. After N consecutive failures, stop retrying for a cooldown period. ` +
|
|
126
|
+
`Log the circuit state transitions.\n\n` +
|
|
127
|
+
`**Acceptance:** XClient stops retrying after 5 consecutive 5xx errors, ` +
|
|
128
|
+
`reopens after 60s cooldown.`,
|
|
129
|
+
labels: ['v1.1', 'resilience'],
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
summary: 'Add cache TTL and selective re-sync by folder',
|
|
133
|
+
description:
|
|
134
|
+
`Add configurable TTL to cached data. When reading from cache, check if data ` +
|
|
135
|
+
`is stale and optionally trigger a targeted re-sync.\n\n` +
|
|
136
|
+
`- Add \`synced_at\` comparison to read operations\n` +
|
|
137
|
+
`- Add \`maxAge\` option to fetch methods\n` +
|
|
138
|
+
`- Add \`syncIfStale(folderName, maxAgeMs)\` method\n\n` +
|
|
139
|
+
`**Acceptance:** \`fetchByFolderName('AI', { maxAge: '24h' })\` returns cached ` +
|
|
140
|
+
`data if fresh, or triggers sync + returns fresh data if stale.`,
|
|
141
|
+
labels: ['v1.1', 'cache'],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
summary: 'Polish OAuth flow — interactive CLI with prompts and validation',
|
|
145
|
+
description:
|
|
146
|
+
`The current oauth-flow.ts script works but has rough edges:\n\n` +
|
|
147
|
+
`- Add interactive prompts for missing env vars\n` +
|
|
148
|
+
`- Validate token scopes before saving\n` +
|
|
149
|
+
`- Auto-write to .env instead of printing to stdout\n` +
|
|
150
|
+
`- Add --verify flag to test credentials without syncing\n` +
|
|
151
|
+
`- Better error messages for common OAuth failures\n\n` +
|
|
152
|
+
`**Acceptance:** First-time user can run the script with zero env vars ` +
|
|
153
|
+
`and end up with a working .env file.`,
|
|
154
|
+
labels: ['v1.1', 'dx'],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
summary: 'Generate TypeDoc API documentation',
|
|
158
|
+
description:
|
|
159
|
+
`Add TypeDoc to generate HTML API docs from TSDoc comments.\n\n` +
|
|
160
|
+
`- Add typedoc to devDependencies\n` +
|
|
161
|
+
`- Add \`npm run docs\` script\n` +
|
|
162
|
+
`- Configure to output to \`docs/\` directory\n` +
|
|
163
|
+
`- Add to CI (build docs on main push)\n` +
|
|
164
|
+
`- Consider GitHub Pages deployment\n\n` +
|
|
165
|
+
`**Acceptance:** \`npm run docs\` generates browsable API reference.`,
|
|
166
|
+
labels: ['v1.1', 'docs'],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
summary: 'Add integration tests with live API (gated behind env flag)',
|
|
170
|
+
description:
|
|
171
|
+
`Add integration tests that hit the real X API. Gated behind ` +
|
|
172
|
+
`\`X_INTEGRATION_TESTS=true\` env var so they don't run in CI by default.\n\n` +
|
|
173
|
+
`- Test syncAll() with real bookmarks\n` +
|
|
174
|
+
`- Test token refresh flow\n` +
|
|
175
|
+
`- Test rate limit handling\n` +
|
|
176
|
+
`- Test pagination with large bookmark sets\n\n` +
|
|
177
|
+
`**Acceptance:** \`X_INTEGRATION_TESTS=true npm test\` runs live API tests ` +
|
|
178
|
+
`against @hlntre account.`,
|
|
179
|
+
labels: ['v1.1', 'testing'],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
summary: 'Add npm publish CI workflow with GitHub release trigger',
|
|
183
|
+
description:
|
|
184
|
+
`Automate npm publishing via GitHub Actions.\n\n` +
|
|
185
|
+
`- Trigger on GitHub release creation (tag v*)\n` +
|
|
186
|
+
`- Run lint + build + test\n` +
|
|
187
|
+
`- Publish to npm with --access public\n` +
|
|
188
|
+
`- Use NPM_TOKEN secret\n` +
|
|
189
|
+
`- Add provenance flag for supply chain security\n\n` +
|
|
190
|
+
`**Acceptance:** Creating a GitHub release auto-publishes to npm.`,
|
|
191
|
+
labels: ['v1.1', 'ci'],
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
summary: 'Add export commands — JSON, CSV, markdown dump from cache',
|
|
195
|
+
description:
|
|
196
|
+
`Users want to export their cached bookmarks in standard formats.\n\n` +
|
|
197
|
+
`- \`exportToJson(options)\` — full or filtered export\n` +
|
|
198
|
+
`- \`exportToCsv(options)\` — flat CSV with key fields\n` +
|
|
199
|
+
`- \`exportToMarkdown(options)\` — formatted markdown document\n\n` +
|
|
200
|
+
`**Acceptance:** All three export formats work with folder filtering.`,
|
|
201
|
+
labels: ['v1.1', 'feature'],
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
for (const task of v11Tasks) {
|
|
206
|
+
const created = await jira.createIssue({
|
|
207
|
+
project: 'XSKILL',
|
|
208
|
+
issuetype: 'Task',
|
|
209
|
+
summary: task.summary,
|
|
210
|
+
description: markdownToAdf(task.description),
|
|
211
|
+
labels: task.labels,
|
|
212
|
+
parent: v11Epic.key,
|
|
213
|
+
});
|
|
214
|
+
console.log(` ✓ ${created.key}: ${task.summary}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ========================================================================
|
|
218
|
+
// Step 4: Add v1.0 release comment to launch ticket if it exists
|
|
219
|
+
// ========================================================================
|
|
220
|
+
|
|
221
|
+
console.log('\nSearching for launch ticket...');
|
|
222
|
+
const launchSearch = await jira.searchIssues({
|
|
223
|
+
jql: jql()
|
|
224
|
+
.equals('project', 'XSKILL')
|
|
225
|
+
.contains('summary', 'launch')
|
|
226
|
+
.build(),
|
|
227
|
+
fields: ['summary', 'status'],
|
|
228
|
+
maxResults: 5,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (launchSearch.issues.length > 0) {
|
|
232
|
+
const launchKey = launchSearch.issues[0].key;
|
|
233
|
+
await jira.addComment(launchKey, {
|
|
234
|
+
body: markdownToAdf(
|
|
235
|
+
`## v1.0.0 Released — 2026-03-31\n\n` +
|
|
236
|
+
`**Repo:** [github.com/Hidden-Leaf-Networks/x-skill](https://github.com/Hidden-Leaf-Networks/x-skill) (PUBLIC)\n\n` +
|
|
237
|
+
`### What shipped:\n` +
|
|
238
|
+
`- X API v2 client with OAuth 2.0 PKCE + auto-refresh\n` +
|
|
239
|
+
`- SQLite cache layer (sync-then-read)\n` +
|
|
240
|
+
`- BookmarksSkill: syncAll, syncFolder, listFolders, fetchByFolderName, brief, stats\n` +
|
|
241
|
+
`- Research brief synthesis with themes, notable voices, signal scoring\n` +
|
|
242
|
+
`- 93 unit tests across 5 suites (store, client, skill, synthesize, logger)\n` +
|
|
243
|
+
`- README with badges, architecture diagram, full API reference\n` +
|
|
244
|
+
`- CHANGELOG.md (Keep a Changelog format)\n` +
|
|
245
|
+
`- Marketing handoff doc (HANDOFF.md) for GPT promo planning\n` +
|
|
246
|
+
`- GitHub Actions CI (Node 18/20 matrix)\n\n` +
|
|
247
|
+
`### Next: v1.1 hardening epic created. npm publish pending.`
|
|
248
|
+
),
|
|
249
|
+
});
|
|
250
|
+
console.log(` ✓ Added release comment to ${launchKey}`);
|
|
251
|
+
|
|
252
|
+
// Transition launch ticket to Done
|
|
253
|
+
try {
|
|
254
|
+
await jira.transitionIssue(launchKey, { transitionId: DONE });
|
|
255
|
+
console.log(` ✓ ${launchKey} → Done`);
|
|
256
|
+
} catch {
|
|
257
|
+
console.log(` ℹ ${launchKey} may already be Done`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ========================================================================
|
|
262
|
+
// Summary
|
|
263
|
+
// ========================================================================
|
|
264
|
+
|
|
265
|
+
console.log('\n=== Summary ===');
|
|
266
|
+
console.log(` Closed: ${transitioned.length} v1 tickets`);
|
|
267
|
+
console.log(` Created: 1 epic + ${v11Tasks.length} tasks for v1.1`);
|
|
268
|
+
console.log(` Epic: ${v11Epic.key}`);
|
|
269
|
+
console.log('\nDone.');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
main().catch(console.error);
|