locus-product-planning 1.1.0 → 1.2.1

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 (74) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/LICENSE +21 -21
  4. package/README.md +11 -7
  5. package/agents/engineering/architect-reviewer.md +122 -122
  6. package/agents/engineering/engineering-manager.md +101 -101
  7. package/agents/engineering/principal-engineer.md +98 -98
  8. package/agents/engineering/staff-engineer.md +86 -86
  9. package/agents/engineering/tech-lead.md +114 -114
  10. package/agents/executive/ceo-strategist.md +81 -81
  11. package/agents/executive/cfo-analyst.md +97 -97
  12. package/agents/executive/coo-operations.md +100 -100
  13. package/agents/executive/cpo-product.md +104 -104
  14. package/agents/executive/cto-architect.md +90 -90
  15. package/agents/product/product-manager.md +70 -70
  16. package/agents/product/project-manager.md +95 -95
  17. package/agents/product/qa-strategist.md +132 -132
  18. package/agents/product/scrum-master.md +70 -70
  19. package/dist/index.cjs +13012 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/{lib/skills-core.d.ts → index.d.cts} +46 -12
  22. package/dist/index.d.ts +113 -5
  23. package/dist/index.js +12963 -237
  24. package/dist/index.js.map +1 -0
  25. package/package.json +88 -82
  26. package/skills/01-executive-suite/ceo-strategist/SKILL.md +132 -132
  27. package/skills/01-executive-suite/cfo-analyst/SKILL.md +187 -187
  28. package/skills/01-executive-suite/coo-operations/SKILL.md +211 -211
  29. package/skills/01-executive-suite/cpo-product/SKILL.md +231 -231
  30. package/skills/01-executive-suite/cto-architect/SKILL.md +173 -173
  31. package/skills/02-product-management/estimation-expert/SKILL.md +139 -139
  32. package/skills/02-product-management/product-manager/SKILL.md +265 -265
  33. package/skills/02-product-management/program-manager/SKILL.md +178 -178
  34. package/skills/02-product-management/project-manager/SKILL.md +221 -221
  35. package/skills/02-product-management/roadmap-strategist/SKILL.md +186 -186
  36. package/skills/02-product-management/scrum-master/SKILL.md +212 -212
  37. package/skills/03-engineering-leadership/architect-reviewer/SKILL.md +249 -249
  38. package/skills/03-engineering-leadership/engineering-manager/SKILL.md +207 -207
  39. package/skills/03-engineering-leadership/principal-engineer/SKILL.md +206 -206
  40. package/skills/03-engineering-leadership/staff-engineer/SKILL.md +237 -237
  41. package/skills/03-engineering-leadership/tech-lead/SKILL.md +296 -296
  42. package/skills/04-developer-specializations/core/api-designer/SKILL.md +579 -0
  43. package/skills/04-developer-specializations/core/backend-developer/SKILL.md +205 -205
  44. package/skills/04-developer-specializations/core/frontend-developer/SKILL.md +233 -233
  45. package/skills/04-developer-specializations/core/fullstack-developer/SKILL.md +202 -202
  46. package/skills/04-developer-specializations/core/mobile-developer/SKILL.md +220 -220
  47. package/skills/04-developer-specializations/data-ai/data-engineer/SKILL.md +316 -316
  48. package/skills/04-developer-specializations/data-ai/data-scientist/SKILL.md +338 -338
  49. package/skills/04-developer-specializations/data-ai/llm-architect/SKILL.md +390 -390
  50. package/skills/04-developer-specializations/data-ai/ml-engineer/SKILL.md +349 -349
  51. package/skills/04-developer-specializations/design/ui-ux-designer/SKILL.md +337 -0
  52. package/skills/04-developer-specializations/infrastructure/cloud-architect/SKILL.md +354 -354
  53. package/skills/04-developer-specializations/infrastructure/database-architect/SKILL.md +430 -0
  54. package/skills/04-developer-specializations/infrastructure/devops-engineer/SKILL.md +306 -306
  55. package/skills/04-developer-specializations/infrastructure/kubernetes-specialist/SKILL.md +419 -419
  56. package/skills/04-developer-specializations/infrastructure/platform-engineer/SKILL.md +289 -289
  57. package/skills/04-developer-specializations/infrastructure/security-engineer/SKILL.md +336 -336
  58. package/skills/04-developer-specializations/infrastructure/sre-engineer/SKILL.md +425 -425
  59. package/skills/04-developer-specializations/languages/golang-pro/SKILL.md +366 -366
  60. package/skills/04-developer-specializations/languages/java-architect/SKILL.md +296 -296
  61. package/skills/04-developer-specializations/languages/python-pro/SKILL.md +317 -317
  62. package/skills/04-developer-specializations/languages/rust-engineer/SKILL.md +309 -309
  63. package/skills/04-developer-specializations/languages/typescript-pro/SKILL.md +251 -251
  64. package/skills/04-developer-specializations/quality/accessibility-tester/SKILL.md +338 -338
  65. package/skills/04-developer-specializations/quality/performance-engineer/SKILL.md +384 -384
  66. package/skills/04-developer-specializations/quality/qa-expert/SKILL.md +413 -413
  67. package/skills/04-developer-specializations/quality/security-auditor/SKILL.md +359 -359
  68. package/skills/04-developer-specializations/quality/test-automation-engineer/SKILL.md +711 -0
  69. package/skills/05-specialists/compliance-specialist/SKILL.md +171 -171
  70. package/skills/05-specialists/technical-writer/SKILL.md +576 -0
  71. package/skills/using-locus/SKILL.md +5 -3
  72. package/dist/index.d.ts.map +0 -1
  73. package/dist/lib/skills-core.d.ts.map +0 -1
  74. package/dist/lib/skills-core.js +0 -361
@@ -1,361 +0,0 @@
1
- /**
2
- * Skills Core Library
3
- * Shared utilities for skill discovery, loading, and management
4
- */
5
- import { readFileSync, existsSync, readdirSync } from 'fs';
6
- import { join, basename } from 'path';
7
- /**
8
- * Extract YAML frontmatter from a skill or agent file.
9
- *
10
- * Supports:
11
- * - Simple key: value pairs
12
- * - Multiline values with |
13
- * - Nested objects (one level deep, e.g., metadata:)
14
- *
15
- * Format:
16
- * ---
17
- * name: skill-name
18
- * description: Use when [condition] - [what it does]
19
- * metadata:
20
- * version: "1.0.0"
21
- * tier: developer
22
- * ---
23
- */
24
- export function extractFrontmatter(filePath) {
25
- try {
26
- const content = readFileSync(filePath, 'utf-8');
27
- // Normalize line endings (handle Windows \r\n)
28
- const lines = content.replace(/\r\n/g, '\n').split('\n');
29
- let inFrontmatter = false;
30
- const metadata = { name: '', description: '' };
31
- let currentKey = '';
32
- let multilineValue = '';
33
- let inNestedObject = false;
34
- let nestedKey = '';
35
- const nestedObject = {};
36
- for (const line of lines) {
37
- const trimmedLine = line.trim();
38
- if (trimmedLine === '---') {
39
- if (inFrontmatter)
40
- break;
41
- inFrontmatter = true;
42
- continue;
43
- }
44
- if (inFrontmatter) {
45
- // Check for nested object entry (starts with 2 spaces)
46
- if (inNestedObject && line.match(/^ {2}\w+:/)) {
47
- const nestedMatch = line.match(/^ {2}(\w+):\s*(.*)$/);
48
- if (nestedMatch) {
49
- const [, key, value] = nestedMatch;
50
- nestedObject[key] = value.trim().replace(/^["']|["']$/g, ''); // Strip quotes
51
- }
52
- continue;
53
- }
54
- // Check for top-level key
55
- const match = line.match(/^(\w+):\s*(.*)$/);
56
- if (match) {
57
- // Save previous nested object if any
58
- if (inNestedObject && nestedKey) {
59
- metadata[nestedKey] = { ...nestedObject };
60
- // Also flatten common nested fields to top level for convenience
61
- if (nestedKey === 'metadata') {
62
- for (const [k, v] of Object.entries(nestedObject)) {
63
- if (v && !metadata[k]) {
64
- metadata[k] = v;
65
- }
66
- }
67
- }
68
- inNestedObject = false;
69
- nestedKey = '';
70
- // Clear nested object for next use
71
- for (const k of Object.keys(nestedObject)) {
72
- delete nestedObject[k];
73
- }
74
- }
75
- // Save previous multiline value if any
76
- if (currentKey && multilineValue) {
77
- metadata[currentKey] = multilineValue.trim();
78
- }
79
- const [, key, value] = match;
80
- currentKey = key;
81
- // Handle nested object (value is empty, next lines are indented)
82
- if (value.trim() === '') {
83
- inNestedObject = true;
84
- nestedKey = key;
85
- currentKey = '';
86
- multilineValue = '';
87
- }
88
- // Handle multiline values starting with |
89
- else if (value.trim() === '|') {
90
- multilineValue = '';
91
- }
92
- else {
93
- metadata[key] = value.trim();
94
- currentKey = '';
95
- multilineValue = '';
96
- }
97
- }
98
- else if (currentKey && line.startsWith(' ')) {
99
- // Continuation of multiline value
100
- multilineValue += line.trim() + ' ';
101
- }
102
- }
103
- }
104
- // Save final nested object
105
- if (inNestedObject && nestedKey) {
106
- metadata[nestedKey] = { ...nestedObject };
107
- if (nestedKey === 'metadata') {
108
- for (const [k, v] of Object.entries(nestedObject)) {
109
- if (v && !metadata[k]) {
110
- metadata[k] = v;
111
- }
112
- }
113
- }
114
- }
115
- // Save final multiline value
116
- if (currentKey && multilineValue) {
117
- metadata[currentKey] = multilineValue.trim();
118
- }
119
- return metadata;
120
- }
121
- catch {
122
- return { name: '', description: '' };
123
- }
124
- }
125
- /**
126
- * Strip YAML frontmatter from content, returning just the body.
127
- */
128
- export function stripFrontmatter(content) {
129
- const lines = content.split('\n');
130
- let inFrontmatter = false;
131
- let frontmatterEnded = false;
132
- const contentLines = [];
133
- for (const line of lines) {
134
- if (line.trim() === '---') {
135
- if (inFrontmatter) {
136
- frontmatterEnded = true;
137
- continue;
138
- }
139
- inFrontmatter = true;
140
- continue;
141
- }
142
- if (frontmatterEnded || !inFrontmatter) {
143
- contentLines.push(line);
144
- }
145
- }
146
- return contentLines.join('\n').trim();
147
- }
148
- /**
149
- * Find all SKILL.md files in a directory recursively.
150
- */
151
- export function findSkillsInDir(dir, sourceType, maxDepth = 4) {
152
- const skills = [];
153
- if (!existsSync(dir))
154
- return skills;
155
- function recurse(currentDir, depth) {
156
- if (depth > maxDepth)
157
- return;
158
- let entries;
159
- try {
160
- entries = readdirSync(currentDir, { withFileTypes: true });
161
- }
162
- catch {
163
- return;
164
- }
165
- for (const entry of entries) {
166
- if (entry.name.startsWith('.'))
167
- continue;
168
- const fullPath = join(currentDir, entry.name);
169
- if (entry.isDirectory()) {
170
- // Check for SKILL.md in this directory
171
- const skillFile = join(fullPath, 'SKILL.md');
172
- if (existsSync(skillFile)) {
173
- const meta = extractFrontmatter(skillFile);
174
- skills.push({
175
- path: fullPath,
176
- skillFile,
177
- name: meta.name || entry.name,
178
- description: meta.description || '',
179
- sourceType,
180
- tier: typeof meta.tier === 'string' ? meta.tier : undefined,
181
- category: typeof meta.category === 'string' ? meta.category : undefined,
182
- council: typeof meta.council === 'string' ? meta.council : undefined,
183
- version: typeof meta.version === 'string' ? meta.version : undefined,
184
- });
185
- }
186
- // Recurse into subdirectories
187
- recurse(fullPath, depth + 1);
188
- }
189
- }
190
- }
191
- recurse(dir, 0);
192
- return skills;
193
- }
194
- /**
195
- * Find all agent definition files in a directory.
196
- */
197
- export function findAgentsInDir(dir, maxDepth = 3) {
198
- const agents = [];
199
- if (!existsSync(dir))
200
- return agents;
201
- function recurse(currentDir, depth, category) {
202
- if (depth > maxDepth)
203
- return;
204
- let entries;
205
- try {
206
- entries = readdirSync(currentDir, { withFileTypes: true });
207
- }
208
- catch {
209
- return;
210
- }
211
- for (const entry of entries) {
212
- if (entry.name.startsWith('.'))
213
- continue;
214
- const fullPath = join(currentDir, entry.name);
215
- if (entry.isDirectory()) {
216
- // Use directory name as category
217
- recurse(fullPath, depth + 1, entry.name);
218
- }
219
- else if (entry.name.endsWith('.md')) {
220
- const { name, description } = extractFrontmatter(fullPath);
221
- const agentName = name || basename(entry.name, '.md');
222
- agents.push({
223
- path: fullPath,
224
- name: agentName,
225
- description: description || '',
226
- category: category || 'general',
227
- });
228
- }
229
- }
230
- }
231
- recurse(dir, 0, '');
232
- return agents;
233
- }
234
- /**
235
- * Resolve a skill name to its file path, handling namespacing and shadowing.
236
- * Priority: project > personal > locus
237
- *
238
- * Skill names can be:
239
- * - "skill-name" - searches all locations
240
- * - "project:skill-name" - forces project skills only
241
- * - "locus:skill-name" - forces locus skills only
242
- */
243
- export function resolveSkillPath(skillName, locusSkillsDir, personalSkillsDir, projectSkillsDir) {
244
- // Parse namespace prefix
245
- const forceProject = skillName.startsWith('project:');
246
- const forceLocus = skillName.startsWith('locus:');
247
- const actualSkillName = skillName
248
- .replace(/^project:/, '')
249
- .replace(/^locus:/, '');
250
- // Try project skills first (unless forcing locus)
251
- if (!forceLocus && projectSkillsDir) {
252
- const resolved = findSkillByName(projectSkillsDir, actualSkillName, 'project');
253
- if (resolved)
254
- return resolved;
255
- }
256
- // If forcing project and not found, return null
257
- if (forceProject)
258
- return null;
259
- // Try personal skills (unless forcing locus)
260
- if (!forceLocus && personalSkillsDir) {
261
- const resolved = findSkillByName(personalSkillsDir, actualSkillName, 'personal');
262
- if (resolved)
263
- return resolved;
264
- }
265
- // Try locus skills
266
- const resolved = findSkillByName(locusSkillsDir, actualSkillName, 'locus');
267
- if (resolved)
268
- return resolved;
269
- return null;
270
- }
271
- /**
272
- * Find a skill by name in a directory (searches recursively).
273
- */
274
- function findSkillByName(baseDir, skillName, sourceType) {
275
- if (!existsSync(baseDir))
276
- return null;
277
- // Direct path check
278
- const directPath = join(baseDir, skillName);
279
- const directSkillFile = join(directPath, 'SKILL.md');
280
- if (existsSync(directSkillFile)) {
281
- return {
282
- skillFile: directSkillFile,
283
- sourceType,
284
- skillPath: skillName,
285
- };
286
- }
287
- // Search in subdirectories (for categorized skills like 01-executive-suite/ceo-strategist)
288
- const skills = findSkillsInDir(baseDir, sourceType);
289
- for (const skill of skills) {
290
- if (skill.name === skillName || basename(skill.path) === skillName) {
291
- return {
292
- skillFile: skill.skillFile,
293
- sourceType,
294
- skillPath: skill.path.replace(baseDir + '/', '').replace(baseDir + '\\', ''),
295
- };
296
- }
297
- }
298
- return null;
299
- }
300
- /**
301
- * Get the using-locus bootstrap skill content.
302
- */
303
- export function getBootstrapContent(locusSkillsDir, compact = false) {
304
- // Try to find the main locus skill
305
- const usingLocusPath = join(locusSkillsDir, 'using-locus', 'SKILL.md');
306
- const legacyLocusPath = join(locusSkillsDir, 'locus', 'SKILL.md');
307
- let skillFile = null;
308
- if (existsSync(usingLocusPath)) {
309
- skillFile = usingLocusPath;
310
- }
311
- else if (existsSync(legacyLocusPath)) {
312
- skillFile = legacyLocusPath;
313
- }
314
- if (!skillFile)
315
- return null;
316
- const fullContent = readFileSync(skillFile, 'utf-8');
317
- const content = stripFrontmatter(fullContent);
318
- const toolMapping = compact
319
- ? `**Tool Mapping:** TodoWrite->update_plan, Task->@mention, Skill->use_skill
320
-
321
- **Skills naming (priority order):** project: > personal: > locus:`
322
- : `**Tool Mapping for OpenCode:**
323
- When skills reference tools you don't have, substitute OpenCode equivalents:
324
- - \`TodoWrite\` -> \`update_plan\`
325
- - \`Task\` tool with subagents -> Use OpenCode's subagent system (@mention)
326
- - \`Skill\` tool -> \`use_skill\` custom tool
327
- - \`Read\`, \`Write\`, \`Edit\`, \`Bash\` -> Your native tools
328
-
329
- **Skills naming (priority order):**
330
- - Project skills: \`project:skill-name\` (in .opencode/skills/ or project skills dir)
331
- - Personal skills: \`skill-name\` (in ~/.config/opencode/skills/)
332
- - Locus skills: \`locus:skill-name\`
333
- - Project skills override personal, which override locus when names match`;
334
- return `<EXTREMELY_IMPORTANT>
335
- You have access to Locus skills.
336
-
337
- **IMPORTANT: The using-locus skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the use_skill tool to load "using-locus" - that would be redundant. Use use_skill only for OTHER skills.**
338
-
339
- ${content}
340
-
341
- ${toolMapping}
342
- </EXTREMELY_IMPORTANT>`;
343
- }
344
- /**
345
- * Normalize a path: trim whitespace, expand ~, resolve to absolute.
346
- */
347
- export function normalizePath(p, homeDir) {
348
- if (!p || typeof p !== 'string')
349
- return null;
350
- let normalized = p.trim();
351
- if (!normalized)
352
- return null;
353
- // Expand ~ to home directory
354
- if (normalized.startsWith('~/')) {
355
- normalized = join(homeDir, normalized.slice(2));
356
- }
357
- else if (normalized === '~') {
358
- normalized = homeDir;
359
- }
360
- return normalized;
361
- }