@zhuan-ai/zhuanspec 1.3.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/LICENSE +22 -0
- package/README.md +461 -0
- package/README.zh.md +434 -0
- package/bin/zhuanspec.js +3 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +356 -0
- package/dist/commands/artifact-workflow.d.ts +13 -0
- package/dist/commands/artifact-workflow.js +916 -0
- package/dist/commands/change.d.ts +35 -0
- package/dist/commands/change.js +277 -0
- package/dist/commands/completion.d.ts +72 -0
- package/dist/commands/completion.js +221 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +198 -0
- package/dist/commands/show.d.ts +14 -0
- package/dist/commands/show.js +132 -0
- package/dist/commands/spec.d.ts +15 -0
- package/dist/commands/spec.js +225 -0
- package/dist/commands/validate.d.ts +24 -0
- package/dist/commands/validate.js +294 -0
- package/dist/core/archive.d.ts +30 -0
- package/dist/core/archive.js +438 -0
- package/dist/core/artifact-graph/graph.d.ts +56 -0
- package/dist/core/artifact-graph/graph.js +141 -0
- package/dist/core/artifact-graph/index.d.ts +7 -0
- package/dist/core/artifact-graph/index.js +13 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +134 -0
- package/dist/core/artifact-graph/instruction-loader.js +180 -0
- package/dist/core/artifact-graph/resolver.d.ts +61 -0
- package/dist/core/artifact-graph/resolver.js +187 -0
- package/dist/core/artifact-graph/schema.d.ts +13 -0
- package/dist/core/artifact-graph/schema.js +108 -0
- package/dist/core/artifact-graph/state.d.ts +12 -0
- package/dist/core/artifact-graph/state.js +54 -0
- package/dist/core/artifact-graph/types.d.ts +45 -0
- package/dist/core/artifact-graph/types.js +43 -0
- package/dist/core/completions/command-registry.d.ts +7 -0
- package/dist/core/completions/command-registry.js +362 -0
- package/dist/core/completions/completion-provider.d.ts +60 -0
- package/dist/core/completions/completion-provider.js +102 -0
- package/dist/core/completions/factory.d.ts +51 -0
- package/dist/core/completions/factory.js +57 -0
- package/dist/core/completions/generators/zsh-generator.d.ts +58 -0
- package/dist/core/completions/generators/zsh-generator.js +319 -0
- package/dist/core/completions/installers/zsh-installer.d.ts +136 -0
- package/dist/core/completions/installers/zsh-installer.js +449 -0
- package/dist/core/completions/types.d.ts +78 -0
- package/dist/core/completions/types.js +2 -0
- package/dist/core/config-schema.d.ts +76 -0
- package/dist/core/config-schema.js +200 -0
- package/dist/core/config.d.ts +16 -0
- package/dist/core/config.js +29 -0
- package/dist/core/configurators/agents.d.ts +8 -0
- package/dist/core/configurators/agents.js +15 -0
- package/dist/core/configurators/base.d.ts +7 -0
- package/dist/core/configurators/base.js +2 -0
- package/dist/core/configurators/claude.d.ts +8 -0
- package/dist/core/configurators/claude.js +15 -0
- package/dist/core/configurators/cline.d.ts +8 -0
- package/dist/core/configurators/cline.js +15 -0
- package/dist/core/configurators/codebuddy.d.ts +8 -0
- package/dist/core/configurators/codebuddy.js +15 -0
- package/dist/core/configurators/costrict.d.ts +8 -0
- package/dist/core/configurators/costrict.js +15 -0
- package/dist/core/configurators/iflow.d.ts +8 -0
- package/dist/core/configurators/iflow.js +15 -0
- package/dist/core/configurators/qoder.d.ts +30 -0
- package/dist/core/configurators/qoder.js +42 -0
- package/dist/core/configurators/qwen.d.ts +24 -0
- package/dist/core/configurators/qwen.js +37 -0
- package/dist/core/configurators/registry.d.ts +9 -0
- package/dist/core/configurators/registry.js +43 -0
- package/dist/core/configurators/slash/amazon-q.d.ts +9 -0
- package/dist/core/configurators/slash/amazon-q.js +46 -0
- package/dist/core/configurators/slash/antigravity.d.ts +9 -0
- package/dist/core/configurators/slash/antigravity.js +23 -0
- package/dist/core/configurators/slash/auggie.d.ts +9 -0
- package/dist/core/configurators/slash/auggie.js +31 -0
- package/dist/core/configurators/slash/base.d.ts +19 -0
- package/dist/core/configurators/slash/base.js +69 -0
- package/dist/core/configurators/slash/claude.d.ts +9 -0
- package/dist/core/configurators/slash/claude.js +37 -0
- package/dist/core/configurators/slash/cline.d.ts +9 -0
- package/dist/core/configurators/slash/cline.js +23 -0
- package/dist/core/configurators/slash/codebuddy.d.ts +9 -0
- package/dist/core/configurators/slash/codebuddy.js +37 -0
- package/dist/core/configurators/slash/codex.d.ts +14 -0
- package/dist/core/configurators/slash/codex.js +109 -0
- package/dist/core/configurators/slash/costrict.d.ts +9 -0
- package/dist/core/configurators/slash/costrict.js +31 -0
- package/dist/core/configurators/slash/crush.d.ts +9 -0
- package/dist/core/configurators/slash/crush.js +37 -0
- package/dist/core/configurators/slash/cursor.d.ts +9 -0
- package/dist/core/configurators/slash/cursor.js +37 -0
- package/dist/core/configurators/slash/factory.d.ts +10 -0
- package/dist/core/configurators/slash/factory.js +35 -0
- package/dist/core/configurators/slash/gemini.d.ts +9 -0
- package/dist/core/configurators/slash/gemini.js +22 -0
- package/dist/core/configurators/slash/github-copilot.d.ts +9 -0
- package/dist/core/configurators/slash/github-copilot.js +34 -0
- package/dist/core/configurators/slash/iflow.d.ts +9 -0
- package/dist/core/configurators/slash/iflow.js +37 -0
- package/dist/core/configurators/slash/kilocode.d.ts +9 -0
- package/dist/core/configurators/slash/kilocode.js +17 -0
- package/dist/core/configurators/slash/opencode.d.ts +12 -0
- package/dist/core/configurators/slash/opencode.js +72 -0
- package/dist/core/configurators/slash/qoder.d.ts +35 -0
- package/dist/core/configurators/slash/qoder.js +76 -0
- package/dist/core/configurators/slash/qwen.d.ts +32 -0
- package/dist/core/configurators/slash/qwen.js +49 -0
- package/dist/core/configurators/slash/registry.d.ts +8 -0
- package/dist/core/configurators/slash/registry.js +75 -0
- package/dist/core/configurators/slash/roocode.d.ts +9 -0
- package/dist/core/configurators/slash/roocode.js +23 -0
- package/dist/core/configurators/slash/toml-base.d.ts +10 -0
- package/dist/core/configurators/slash/toml-base.js +53 -0
- package/dist/core/configurators/slash/windsurf.d.ts +9 -0
- package/dist/core/configurators/slash/windsurf.js +23 -0
- package/dist/core/converters/json-converter.d.ts +6 -0
- package/dist/core/converters/json-converter.js +51 -0
- package/dist/core/global-config.d.ts +39 -0
- package/dist/core/global-config.js +115 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +3 -0
- package/dist/core/init.d.ts +60 -0
- package/dist/core/init.js +861 -0
- package/dist/core/list.d.ts +9 -0
- package/dist/core/list.js +171 -0
- package/dist/core/parsers/change-parser.d.ts +13 -0
- package/dist/core/parsers/change-parser.js +193 -0
- package/dist/core/parsers/markdown-parser.d.ts +22 -0
- package/dist/core/parsers/markdown-parser.js +187 -0
- package/dist/core/parsers/requirement-blocks.d.ts +37 -0
- package/dist/core/parsers/requirement-blocks.js +201 -0
- package/dist/core/project-config.d.ts +34 -0
- package/dist/core/project-config.js +79 -0
- package/dist/core/schemas/base.schema.d.ts +13 -0
- package/dist/core/schemas/base.schema.js +13 -0
- package/dist/core/schemas/change.schema.d.ts +73 -0
- package/dist/core/schemas/change.schema.js +31 -0
- package/dist/core/schemas/index.d.ts +4 -0
- package/dist/core/schemas/index.js +4 -0
- package/dist/core/schemas/spec.schema.d.ts +18 -0
- package/dist/core/schemas/spec.schema.js +15 -0
- package/dist/core/skill-discovery.d.ts +24 -0
- package/dist/core/skill-discovery.js +153 -0
- package/dist/core/specs-apply.d.ts +73 -0
- package/dist/core/specs-apply.js +384 -0
- package/dist/core/styles/palette.d.ts +7 -0
- package/dist/core/styles/palette.js +8 -0
- package/dist/core/templates/agents-root-stub.d.ts +2 -0
- package/dist/core/templates/agents-root-stub.js +17 -0
- package/dist/core/templates/agents-template.d.ts +2 -0
- package/dist/core/templates/agents-template.js +706 -0
- package/dist/core/templates/claude-template.d.ts +2 -0
- package/dist/core/templates/claude-template.js +2 -0
- package/dist/core/templates/cline-template.d.ts +2 -0
- package/dist/core/templates/cline-template.js +2 -0
- package/dist/core/templates/costrict-template.d.ts +2 -0
- package/dist/core/templates/costrict-template.js +2 -0
- package/dist/core/templates/index.d.ts +17 -0
- package/dist/core/templates/index.js +37 -0
- package/dist/core/templates/project-template.d.ts +8 -0
- package/dist/core/templates/project-template.js +32 -0
- package/dist/core/templates/skill-templates.d.ts +103 -0
- package/dist/core/templates/skill-templates.js +2131 -0
- package/dist/core/templates/slash-command-templates.d.ts +4 -0
- package/dist/core/templates/slash-command-templates.js +81 -0
- package/dist/core/update.d.ts +4 -0
- package/dist/core/update.js +88 -0
- package/dist/core/validation/constants.d.ts +34 -0
- package/dist/core/validation/constants.js +40 -0
- package/dist/core/validation/types.d.ts +18 -0
- package/dist/core/validation/types.js +2 -0
- package/dist/core/validation/validator.d.ts +33 -0
- package/dist/core/validation/validator.js +409 -0
- package/dist/core/view.d.ts +8 -0
- package/dist/core/view.js +168 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/utils/change-metadata.d.ts +47 -0
- package/dist/utils/change-metadata.js +130 -0
- package/dist/utils/change-utils.d.ts +51 -0
- package/dist/utils/change-utils.js +100 -0
- package/dist/utils/file-system.d.ts +19 -0
- package/dist/utils/file-system.js +177 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/interactive.d.ts +18 -0
- package/dist/utils/interactive.js +21 -0
- package/dist/utils/item-discovery.d.ts +4 -0
- package/dist/utils/item-discovery.js +72 -0
- package/dist/utils/match.d.ts +3 -0
- package/dist/utils/match.js +22 -0
- package/dist/utils/shell-detection.d.ts +20 -0
- package/dist/utils/shell-detection.js +41 -0
- package/dist/utils/task-progress.d.ts +8 -0
- package/dist/utils/task-progress.js +36 -0
- package/package.json +81 -0
- package/schemas/spec-driven/schema.yaml +205 -0
- package/schemas/spec-driven/templates/design.md +19 -0
- package/schemas/spec-driven/templates/proposal.md +43 -0
- package/schemas/spec-driven/templates/spec.md +8 -0
- package/schemas/spec-driven/templates/tasks.md +25 -0
- package/schemas/tdd/schema.yaml +213 -0
- package/schemas/tdd/templates/docs.md +0 -0
- package/schemas/tdd/templates/implementation.md +11 -0
- package/schemas/tdd/templates/spec.md +11 -0
- package/schemas/tdd/templates/test.md +11 -0
- package/scripts/postinstall.js +147 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
export function normalizeRequirementName(name) {
|
|
2
|
+
return name.trim();
|
|
3
|
+
}
|
|
4
|
+
const REQUIREMENT_HEADER_REGEX = /^###\s*Requirement:\s*(.+)\s*$/;
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the Requirements section from a spec file and parses requirement blocks.
|
|
7
|
+
*/
|
|
8
|
+
export function extractRequirementsSection(content) {
|
|
9
|
+
const normalized = normalizeLineEndings(content);
|
|
10
|
+
const lines = normalized.split('\n');
|
|
11
|
+
const reqHeaderIndex = lines.findIndex(l => /^##\s+Requirements\s*$/i.test(l));
|
|
12
|
+
if (reqHeaderIndex === -1) {
|
|
13
|
+
// No requirements section; create an empty one at the end
|
|
14
|
+
const before = content.trimEnd();
|
|
15
|
+
const headerLine = '## Requirements';
|
|
16
|
+
return {
|
|
17
|
+
before: before ? before + '\n\n' : '',
|
|
18
|
+
headerLine,
|
|
19
|
+
preamble: '',
|
|
20
|
+
bodyBlocks: [],
|
|
21
|
+
after: '\n',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Find end of this section: next line that starts with '## ' at same or higher level
|
|
25
|
+
let endIndex = lines.length;
|
|
26
|
+
for (let i = reqHeaderIndex + 1; i < lines.length; i++) {
|
|
27
|
+
if (/^##\s+/.test(lines[i])) {
|
|
28
|
+
endIndex = i;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const before = lines.slice(0, reqHeaderIndex).join('\n');
|
|
33
|
+
const headerLine = lines[reqHeaderIndex];
|
|
34
|
+
const sectionBodyLines = lines.slice(reqHeaderIndex + 1, endIndex);
|
|
35
|
+
// Parse requirement blocks within section body
|
|
36
|
+
const blocks = [];
|
|
37
|
+
let cursor = 0;
|
|
38
|
+
let preambleLines = [];
|
|
39
|
+
// Collect preamble lines until first requirement header
|
|
40
|
+
while (cursor < sectionBodyLines.length && !/^###\s+Requirement:/.test(sectionBodyLines[cursor])) {
|
|
41
|
+
preambleLines.push(sectionBodyLines[cursor]);
|
|
42
|
+
cursor++;
|
|
43
|
+
}
|
|
44
|
+
while (cursor < sectionBodyLines.length) {
|
|
45
|
+
const headerStart = cursor;
|
|
46
|
+
const headerLineCandidate = sectionBodyLines[cursor];
|
|
47
|
+
const headerMatch = headerLineCandidate.match(REQUIREMENT_HEADER_REGEX);
|
|
48
|
+
if (!headerMatch) {
|
|
49
|
+
// Not a requirement header; skip line defensively
|
|
50
|
+
cursor++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const name = normalizeRequirementName(headerMatch[1]);
|
|
54
|
+
cursor++;
|
|
55
|
+
// Gather lines until next requirement header or end of section
|
|
56
|
+
const bodyLines = [headerLineCandidate];
|
|
57
|
+
while (cursor < sectionBodyLines.length && !/^###\s+Requirement:/.test(sectionBodyLines[cursor]) && !/^##\s+/.test(sectionBodyLines[cursor])) {
|
|
58
|
+
bodyLines.push(sectionBodyLines[cursor]);
|
|
59
|
+
cursor++;
|
|
60
|
+
}
|
|
61
|
+
const raw = bodyLines.join('\n').trimEnd();
|
|
62
|
+
blocks.push({ headerLine: headerLineCandidate, name, raw });
|
|
63
|
+
}
|
|
64
|
+
const after = lines.slice(endIndex).join('\n');
|
|
65
|
+
const preamble = preambleLines.join('\n').trimEnd();
|
|
66
|
+
return {
|
|
67
|
+
before: before.trimEnd() ? before + '\n' : before,
|
|
68
|
+
headerLine,
|
|
69
|
+
preamble,
|
|
70
|
+
bodyBlocks: blocks,
|
|
71
|
+
after: after.startsWith('\n') ? after : '\n' + after,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function normalizeLineEndings(content) {
|
|
75
|
+
return content.replace(/\r\n?/g, '\n');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse a delta-formatted spec change file content into a DeltaPlan with raw blocks.
|
|
79
|
+
*/
|
|
80
|
+
export function parseDeltaSpec(content) {
|
|
81
|
+
const normalized = normalizeLineEndings(content);
|
|
82
|
+
const sections = splitTopLevelSections(normalized);
|
|
83
|
+
const addedLookup = getSectionCaseInsensitive(sections, 'ADDED Requirements');
|
|
84
|
+
const modifiedLookup = getSectionCaseInsensitive(sections, 'MODIFIED Requirements');
|
|
85
|
+
const removedLookup = getSectionCaseInsensitive(sections, 'REMOVED Requirements');
|
|
86
|
+
const renamedLookup = getSectionCaseInsensitive(sections, 'RENAMED Requirements');
|
|
87
|
+
const added = parseRequirementBlocksFromSection(addedLookup.body);
|
|
88
|
+
const modified = parseRequirementBlocksFromSection(modifiedLookup.body);
|
|
89
|
+
const removedNames = parseRemovedNames(removedLookup.body);
|
|
90
|
+
const renamedPairs = parseRenamedPairs(renamedLookup.body);
|
|
91
|
+
return {
|
|
92
|
+
added,
|
|
93
|
+
modified,
|
|
94
|
+
removed: removedNames,
|
|
95
|
+
renamed: renamedPairs,
|
|
96
|
+
sectionPresence: {
|
|
97
|
+
added: addedLookup.found,
|
|
98
|
+
modified: modifiedLookup.found,
|
|
99
|
+
removed: removedLookup.found,
|
|
100
|
+
renamed: renamedLookup.found,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function splitTopLevelSections(content) {
|
|
105
|
+
const lines = content.split('\n');
|
|
106
|
+
const result = {};
|
|
107
|
+
const indices = [];
|
|
108
|
+
for (let i = 0; i < lines.length; i++) {
|
|
109
|
+
const m = lines[i].match(/^(##)\s+(.+)$/);
|
|
110
|
+
if (m) {
|
|
111
|
+
const level = m[1].length; // only care for '##'
|
|
112
|
+
indices.push({ title: m[2].trim(), index: i, level });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (let i = 0; i < indices.length; i++) {
|
|
116
|
+
const current = indices[i];
|
|
117
|
+
const next = indices[i + 1];
|
|
118
|
+
const body = lines.slice(current.index + 1, next ? next.index : lines.length).join('\n');
|
|
119
|
+
result[current.title] = body;
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
function getSectionCaseInsensitive(sections, desired) {
|
|
124
|
+
const target = desired.toLowerCase();
|
|
125
|
+
for (const [title, body] of Object.entries(sections)) {
|
|
126
|
+
if (title.toLowerCase() === target)
|
|
127
|
+
return { body, found: true };
|
|
128
|
+
}
|
|
129
|
+
return { body: '', found: false };
|
|
130
|
+
}
|
|
131
|
+
function parseRequirementBlocksFromSection(sectionBody) {
|
|
132
|
+
if (!sectionBody)
|
|
133
|
+
return [];
|
|
134
|
+
const lines = normalizeLineEndings(sectionBody).split('\n');
|
|
135
|
+
const blocks = [];
|
|
136
|
+
let i = 0;
|
|
137
|
+
while (i < lines.length) {
|
|
138
|
+
// Seek next requirement header
|
|
139
|
+
while (i < lines.length && !/^###\s+Requirement:/.test(lines[i]))
|
|
140
|
+
i++;
|
|
141
|
+
if (i >= lines.length)
|
|
142
|
+
break;
|
|
143
|
+
const headerLine = lines[i];
|
|
144
|
+
const m = headerLine.match(REQUIREMENT_HEADER_REGEX);
|
|
145
|
+
if (!m) {
|
|
146
|
+
i++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const name = normalizeRequirementName(m[1]);
|
|
150
|
+
const buf = [headerLine];
|
|
151
|
+
i++;
|
|
152
|
+
while (i < lines.length && !/^###\s+Requirement:/.test(lines[i]) && !/^##\s+/.test(lines[i])) {
|
|
153
|
+
buf.push(lines[i]);
|
|
154
|
+
i++;
|
|
155
|
+
}
|
|
156
|
+
blocks.push({ headerLine, name, raw: buf.join('\n').trimEnd() });
|
|
157
|
+
}
|
|
158
|
+
return blocks;
|
|
159
|
+
}
|
|
160
|
+
function parseRemovedNames(sectionBody) {
|
|
161
|
+
if (!sectionBody)
|
|
162
|
+
return [];
|
|
163
|
+
const names = [];
|
|
164
|
+
const lines = normalizeLineEndings(sectionBody).split('\n');
|
|
165
|
+
for (const line of lines) {
|
|
166
|
+
const m = line.match(REQUIREMENT_HEADER_REGEX);
|
|
167
|
+
if (m) {
|
|
168
|
+
names.push(normalizeRequirementName(m[1]));
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
// Also support bullet list of headers
|
|
172
|
+
const bullet = line.match(/^\s*-\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
|
|
173
|
+
if (bullet) {
|
|
174
|
+
names.push(normalizeRequirementName(bullet[1]));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return names;
|
|
178
|
+
}
|
|
179
|
+
function parseRenamedPairs(sectionBody) {
|
|
180
|
+
if (!sectionBody)
|
|
181
|
+
return [];
|
|
182
|
+
const pairs = [];
|
|
183
|
+
const lines = normalizeLineEndings(sectionBody).split('\n');
|
|
184
|
+
let current = {};
|
|
185
|
+
for (const line of lines) {
|
|
186
|
+
const fromMatch = line.match(/^\s*-?\s*FROM:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
|
|
187
|
+
const toMatch = line.match(/^\s*-?\s*TO:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
|
|
188
|
+
if (fromMatch) {
|
|
189
|
+
current.from = normalizeRequirementName(fromMatch[1]);
|
|
190
|
+
}
|
|
191
|
+
else if (toMatch) {
|
|
192
|
+
current.to = normalizeRequirementName(toMatch[1]);
|
|
193
|
+
if (current.from && current.to) {
|
|
194
|
+
pairs.push({ from: current.from, to: current.to });
|
|
195
|
+
current = {};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return pairs;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=requirement-blocks.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-level configuration loaded from zhuanspec/config.yaml.
|
|
3
|
+
*
|
|
4
|
+
* Supports three key sections:
|
|
5
|
+
* - context: Project background injected into all artifact instructions
|
|
6
|
+
* - rules: Per-artifact or global rules injected into instructions
|
|
7
|
+
* - defaultSchema: Project-wide default schema name
|
|
8
|
+
*/
|
|
9
|
+
export interface ProjectConfig {
|
|
10
|
+
/** Project context injected into all instructions */
|
|
11
|
+
context?: string;
|
|
12
|
+
/** Global rules applied to all artifacts */
|
|
13
|
+
rules?: string;
|
|
14
|
+
/** Per-artifact rules (key = artifact ID, value = rules text) */
|
|
15
|
+
artifactRules?: Record<string, string>;
|
|
16
|
+
/** Default schema for the project */
|
|
17
|
+
defaultSchema?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Loads project-level configuration from zhuanspec/config.yaml.
|
|
21
|
+
*
|
|
22
|
+
* @param projectRoot - Project root directory
|
|
23
|
+
* @returns Parsed project config, or empty config if file doesn't exist
|
|
24
|
+
*/
|
|
25
|
+
export declare function loadProjectConfig(projectRoot: string): ProjectConfig;
|
|
26
|
+
/**
|
|
27
|
+
* Gets the effective rules for a specific artifact, combining global and per-artifact rules.
|
|
28
|
+
*
|
|
29
|
+
* @param config - Project configuration
|
|
30
|
+
* @param artifactId - The artifact ID (e.g., "proposal", "specs", "tasks")
|
|
31
|
+
* @returns Combined rules string, or undefined if no rules apply
|
|
32
|
+
*/
|
|
33
|
+
export declare function getEffectiveRules(config: ProjectConfig, artifactId: string): string | undefined;
|
|
34
|
+
//# sourceMappingURL=project-config.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as yaml from 'yaml';
|
|
4
|
+
const PROJECT_CONFIG_FILENAME = 'config.yaml';
|
|
5
|
+
/**
|
|
6
|
+
* Loads project-level configuration from zhuanspec/config.yaml.
|
|
7
|
+
*
|
|
8
|
+
* @param projectRoot - Project root directory
|
|
9
|
+
* @returns Parsed project config, or empty config if file doesn't exist
|
|
10
|
+
*/
|
|
11
|
+
export function loadProjectConfig(projectRoot) {
|
|
12
|
+
const configPath = path.join(projectRoot, 'zhuanspec', PROJECT_CONFIG_FILENAME);
|
|
13
|
+
if (!fs.existsSync(configPath)) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
let content;
|
|
17
|
+
try {
|
|
18
|
+
content = fs.readFileSync(configPath, 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
if (!content.trim()) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
let parsed;
|
|
27
|
+
try {
|
|
28
|
+
parsed = yaml.parse(content);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32
|
+
console.warn(`Warning: Invalid YAML in ${configPath}: ${msg}`);
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
const raw = parsed;
|
|
39
|
+
const config = {};
|
|
40
|
+
if (typeof raw.context === 'string') {
|
|
41
|
+
config.context = raw.context;
|
|
42
|
+
}
|
|
43
|
+
if (typeof raw.rules === 'string') {
|
|
44
|
+
config.rules = raw.rules;
|
|
45
|
+
}
|
|
46
|
+
if (typeof raw.artifactRules === 'object' && raw.artifactRules !== null) {
|
|
47
|
+
const artifactRules = {};
|
|
48
|
+
for (const [key, value] of Object.entries(raw.artifactRules)) {
|
|
49
|
+
if (typeof value === 'string') {
|
|
50
|
+
artifactRules[key] = value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (Object.keys(artifactRules).length > 0) {
|
|
54
|
+
config.artifactRules = artifactRules;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (typeof raw.defaultSchema === 'string') {
|
|
58
|
+
config.defaultSchema = raw.defaultSchema;
|
|
59
|
+
}
|
|
60
|
+
return config;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Gets the effective rules for a specific artifact, combining global and per-artifact rules.
|
|
64
|
+
*
|
|
65
|
+
* @param config - Project configuration
|
|
66
|
+
* @param artifactId - The artifact ID (e.g., "proposal", "specs", "tasks")
|
|
67
|
+
* @returns Combined rules string, or undefined if no rules apply
|
|
68
|
+
*/
|
|
69
|
+
export function getEffectiveRules(config, artifactId) {
|
|
70
|
+
const parts = [];
|
|
71
|
+
if (config.rules) {
|
|
72
|
+
parts.push(config.rules);
|
|
73
|
+
}
|
|
74
|
+
if (config.artifactRules?.[artifactId]) {
|
|
75
|
+
parts.push(config.artifactRules[artifactId]);
|
|
76
|
+
}
|
|
77
|
+
return parts.length > 0 ? parts.join('\n\n') : undefined;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=project-config.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const ScenarioSchema: z.ZodObject<{
|
|
3
|
+
rawText: z.ZodString;
|
|
4
|
+
}, z.core.$strip>;
|
|
5
|
+
export declare const RequirementSchema: z.ZodObject<{
|
|
6
|
+
text: z.ZodString;
|
|
7
|
+
scenarios: z.ZodArray<z.ZodObject<{
|
|
8
|
+
rawText: z.ZodString;
|
|
9
|
+
}, z.core.$strip>>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export type Scenario = z.infer<typeof ScenarioSchema>;
|
|
12
|
+
export type Requirement = z.infer<typeof RequirementSchema>;
|
|
13
|
+
//# sourceMappingURL=base.schema.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { VALIDATION_MESSAGES } from '../validation/constants.js';
|
|
3
|
+
export const ScenarioSchema = z.object({
|
|
4
|
+
rawText: z.string().min(1, VALIDATION_MESSAGES.SCENARIO_EMPTY),
|
|
5
|
+
});
|
|
6
|
+
export const RequirementSchema = z.object({
|
|
7
|
+
text: z.string()
|
|
8
|
+
.min(1, VALIDATION_MESSAGES.REQUIREMENT_EMPTY)
|
|
9
|
+
.refine((text) => text.includes('SHALL') || text.includes('MUST'), VALIDATION_MESSAGES.REQUIREMENT_NO_SHALL),
|
|
10
|
+
scenarios: z.array(ScenarioSchema)
|
|
11
|
+
.min(1, VALIDATION_MESSAGES.REQUIREMENT_NO_SCENARIOS),
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=base.schema.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const DeltaOperationType: z.ZodEnum<{
|
|
3
|
+
ADDED: "ADDED";
|
|
4
|
+
MODIFIED: "MODIFIED";
|
|
5
|
+
REMOVED: "REMOVED";
|
|
6
|
+
RENAMED: "RENAMED";
|
|
7
|
+
}>;
|
|
8
|
+
export declare const DeltaSchema: z.ZodObject<{
|
|
9
|
+
spec: z.ZodString;
|
|
10
|
+
operation: z.ZodEnum<{
|
|
11
|
+
ADDED: "ADDED";
|
|
12
|
+
MODIFIED: "MODIFIED";
|
|
13
|
+
REMOVED: "REMOVED";
|
|
14
|
+
RENAMED: "RENAMED";
|
|
15
|
+
}>;
|
|
16
|
+
description: z.ZodString;
|
|
17
|
+
requirement: z.ZodOptional<z.ZodObject<{
|
|
18
|
+
text: z.ZodString;
|
|
19
|
+
scenarios: z.ZodArray<z.ZodObject<{
|
|
20
|
+
rawText: z.ZodString;
|
|
21
|
+
}, z.core.$strip>>;
|
|
22
|
+
}, z.core.$strip>>;
|
|
23
|
+
requirements: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
24
|
+
text: z.ZodString;
|
|
25
|
+
scenarios: z.ZodArray<z.ZodObject<{
|
|
26
|
+
rawText: z.ZodString;
|
|
27
|
+
}, z.core.$strip>>;
|
|
28
|
+
}, z.core.$strip>>>;
|
|
29
|
+
rename: z.ZodOptional<z.ZodObject<{
|
|
30
|
+
from: z.ZodString;
|
|
31
|
+
to: z.ZodString;
|
|
32
|
+
}, z.core.$strip>>;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
export declare const ChangeSchema: z.ZodObject<{
|
|
35
|
+
name: z.ZodString;
|
|
36
|
+
why: z.ZodString;
|
|
37
|
+
whatChanges: z.ZodString;
|
|
38
|
+
deltas: z.ZodArray<z.ZodObject<{
|
|
39
|
+
spec: z.ZodString;
|
|
40
|
+
operation: z.ZodEnum<{
|
|
41
|
+
ADDED: "ADDED";
|
|
42
|
+
MODIFIED: "MODIFIED";
|
|
43
|
+
REMOVED: "REMOVED";
|
|
44
|
+
RENAMED: "RENAMED";
|
|
45
|
+
}>;
|
|
46
|
+
description: z.ZodString;
|
|
47
|
+
requirement: z.ZodOptional<z.ZodObject<{
|
|
48
|
+
text: z.ZodString;
|
|
49
|
+
scenarios: z.ZodArray<z.ZodObject<{
|
|
50
|
+
rawText: z.ZodString;
|
|
51
|
+
}, z.core.$strip>>;
|
|
52
|
+
}, z.core.$strip>>;
|
|
53
|
+
requirements: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
54
|
+
text: z.ZodString;
|
|
55
|
+
scenarios: z.ZodArray<z.ZodObject<{
|
|
56
|
+
rawText: z.ZodString;
|
|
57
|
+
}, z.core.$strip>>;
|
|
58
|
+
}, z.core.$strip>>>;
|
|
59
|
+
rename: z.ZodOptional<z.ZodObject<{
|
|
60
|
+
from: z.ZodString;
|
|
61
|
+
to: z.ZodString;
|
|
62
|
+
}, z.core.$strip>>;
|
|
63
|
+
}, z.core.$strip>>;
|
|
64
|
+
metadata: z.ZodOptional<z.ZodObject<{
|
|
65
|
+
version: z.ZodDefault<z.ZodString>;
|
|
66
|
+
format: z.ZodLiteral<"zhuanspec-change">;
|
|
67
|
+
sourcePath: z.ZodOptional<z.ZodString>;
|
|
68
|
+
}, z.core.$strip>>;
|
|
69
|
+
}, z.core.$strip>;
|
|
70
|
+
export type DeltaOperation = z.infer<typeof DeltaOperationType>;
|
|
71
|
+
export type Delta = z.infer<typeof DeltaSchema>;
|
|
72
|
+
export type Change = z.infer<typeof ChangeSchema>;
|
|
73
|
+
//# sourceMappingURL=change.schema.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { RequirementSchema } from './base.schema.js';
|
|
3
|
+
import { MIN_WHY_SECTION_LENGTH, MAX_WHY_SECTION_LENGTH, MAX_DELTAS_PER_CHANGE, VALIDATION_MESSAGES } from '../validation/constants.js';
|
|
4
|
+
export const DeltaOperationType = z.enum(['ADDED', 'MODIFIED', 'REMOVED', 'RENAMED']);
|
|
5
|
+
export const DeltaSchema = z.object({
|
|
6
|
+
spec: z.string().min(1, VALIDATION_MESSAGES.DELTA_SPEC_EMPTY),
|
|
7
|
+
operation: DeltaOperationType,
|
|
8
|
+
description: z.string().min(1, VALIDATION_MESSAGES.DELTA_DESCRIPTION_EMPTY),
|
|
9
|
+
requirement: RequirementSchema.optional(),
|
|
10
|
+
requirements: z.array(RequirementSchema).optional(),
|
|
11
|
+
rename: z.object({
|
|
12
|
+
from: z.string(),
|
|
13
|
+
to: z.string(),
|
|
14
|
+
}).optional(),
|
|
15
|
+
});
|
|
16
|
+
export const ChangeSchema = z.object({
|
|
17
|
+
name: z.string().min(1, VALIDATION_MESSAGES.CHANGE_NAME_EMPTY),
|
|
18
|
+
why: z.string()
|
|
19
|
+
.min(MIN_WHY_SECTION_LENGTH, VALIDATION_MESSAGES.CHANGE_WHY_TOO_SHORT)
|
|
20
|
+
.max(MAX_WHY_SECTION_LENGTH, VALIDATION_MESSAGES.CHANGE_WHY_TOO_LONG),
|
|
21
|
+
whatChanges: z.string().min(1, VALIDATION_MESSAGES.CHANGE_WHAT_EMPTY),
|
|
22
|
+
deltas: z.array(DeltaSchema)
|
|
23
|
+
.min(1, VALIDATION_MESSAGES.CHANGE_NO_DELTAS)
|
|
24
|
+
.max(MAX_DELTAS_PER_CHANGE, VALIDATION_MESSAGES.CHANGE_TOO_MANY_DELTAS),
|
|
25
|
+
metadata: z.object({
|
|
26
|
+
version: z.string().default('1.0.0'),
|
|
27
|
+
format: z.literal('zhuanspec-change'),
|
|
28
|
+
sourcePath: z.string().optional(),
|
|
29
|
+
}).optional(),
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=change.schema.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { ScenarioSchema, RequirementSchema, type Scenario, type Requirement, } from './base.schema.js';
|
|
2
|
+
export { SpecSchema, type Spec, } from './spec.schema.js';
|
|
3
|
+
export { DeltaOperationType, DeltaSchema, ChangeSchema, type DeltaOperation, type Delta, type Change, } from './change.schema.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const SpecSchema: z.ZodObject<{
|
|
3
|
+
name: z.ZodString;
|
|
4
|
+
overview: z.ZodString;
|
|
5
|
+
requirements: z.ZodArray<z.ZodObject<{
|
|
6
|
+
text: z.ZodString;
|
|
7
|
+
scenarios: z.ZodArray<z.ZodObject<{
|
|
8
|
+
rawText: z.ZodString;
|
|
9
|
+
}, z.core.$strip>>;
|
|
10
|
+
}, z.core.$strip>>;
|
|
11
|
+
metadata: z.ZodOptional<z.ZodObject<{
|
|
12
|
+
version: z.ZodDefault<z.ZodString>;
|
|
13
|
+
format: z.ZodLiteral<"zhuanspec">;
|
|
14
|
+
sourcePath: z.ZodOptional<z.ZodString>;
|
|
15
|
+
}, z.core.$strip>>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
export type Spec = z.infer<typeof SpecSchema>;
|
|
18
|
+
//# sourceMappingURL=spec.schema.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { RequirementSchema } from './base.schema.js';
|
|
3
|
+
import { VALIDATION_MESSAGES } from '../validation/constants.js';
|
|
4
|
+
export const SpecSchema = z.object({
|
|
5
|
+
name: z.string().min(1, VALIDATION_MESSAGES.SPEC_NAME_EMPTY),
|
|
6
|
+
overview: z.string().min(1, VALIDATION_MESSAGES.SPEC_PURPOSE_EMPTY),
|
|
7
|
+
requirements: z.array(RequirementSchema)
|
|
8
|
+
.min(1, VALIDATION_MESSAGES.SPEC_NO_REQUIREMENTS),
|
|
9
|
+
metadata: z.object({
|
|
10
|
+
version: z.string().default('1.0.0'),
|
|
11
|
+
format: z.literal('zhuanspec'),
|
|
12
|
+
sourcePath: z.string().optional(),
|
|
13
|
+
}).optional(),
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=spec.schema.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a discovered skill from the filesystem.
|
|
3
|
+
*/
|
|
4
|
+
export interface DiscoveredSkill {
|
|
5
|
+
/** Skill name from YAML frontmatter */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Skill description from YAML frontmatter */
|
|
8
|
+
description: string;
|
|
9
|
+
/** Absolute path to the SKILL.md file */
|
|
10
|
+
filePath: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Discover available skills by scanning priority paths.
|
|
14
|
+
* Uses short-circuit strategy: once a non-empty directory is found, stops scanning.
|
|
15
|
+
*
|
|
16
|
+
* @param _projectRoot - Project root path (reserved for future project-level skill scanning)
|
|
17
|
+
* @returns Array of discovered skills
|
|
18
|
+
*/
|
|
19
|
+
export declare function discoverSkills(_projectRoot?: string): DiscoveredSkill[];
|
|
20
|
+
/**
|
|
21
|
+
* Format discovered skills for display.
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatSkillsList(skills: DiscoveredSkill[], json: boolean): string;
|
|
24
|
+
//# sourceMappingURL=skill-discovery.d.ts.map
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
/**
|
|
5
|
+
* Scan priority paths (highest to lowest).
|
|
6
|
+
* Once a non-empty directory is found, scanning stops (short-circuit strategy).
|
|
7
|
+
*/
|
|
8
|
+
const SCAN_PRIORITY = [
|
|
9
|
+
'~/.ai-config-hub/skills',
|
|
10
|
+
// Future fallback paths can be added here
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a path that may contain ~ to an absolute path.
|
|
14
|
+
*/
|
|
15
|
+
function resolvePath(p) {
|
|
16
|
+
if (p.startsWith('~/') || p === '~') {
|
|
17
|
+
return path.join(os.homedir(), p.slice(1));
|
|
18
|
+
}
|
|
19
|
+
return path.resolve(p);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse YAML frontmatter from a SKILL.md file content.
|
|
23
|
+
* Frontmatter is delimited by --- at the start of the file.
|
|
24
|
+
*/
|
|
25
|
+
function parseFrontmatter(content) {
|
|
26
|
+
const trimmed = content.trimStart();
|
|
27
|
+
if (!trimmed.startsWith('---')) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
const endIndex = trimmed.indexOf('---', 3);
|
|
31
|
+
if (endIndex === -1) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
const frontmatterBlock = trimmed.slice(3, endIndex).trim();
|
|
35
|
+
const result = {};
|
|
36
|
+
for (const line of frontmatterBlock.split('\n')) {
|
|
37
|
+
const colonIndex = line.indexOf(':');
|
|
38
|
+
if (colonIndex === -1)
|
|
39
|
+
continue;
|
|
40
|
+
const key = line.slice(0, colonIndex).trim();
|
|
41
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
42
|
+
// Remove quotes if present
|
|
43
|
+
const unquoted = value.replace(/^["']|["']$/g, '');
|
|
44
|
+
if (key === 'name') {
|
|
45
|
+
result.name = unquoted;
|
|
46
|
+
}
|
|
47
|
+
else if (key === 'description') {
|
|
48
|
+
result.description = unquoted;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Recursively find all SKILL.md files under a directory.
|
|
55
|
+
*/
|
|
56
|
+
function findSkillFiles(dir) {
|
|
57
|
+
const results = [];
|
|
58
|
+
let entries;
|
|
59
|
+
try {
|
|
60
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return results;
|
|
64
|
+
}
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const fullPath = path.join(dir, entry.name);
|
|
67
|
+
if (entry.isDirectory()) {
|
|
68
|
+
results.push(...findSkillFiles(fullPath));
|
|
69
|
+
}
|
|
70
|
+
else if (entry.isFile() && entry.name === 'SKILL.md') {
|
|
71
|
+
results.push(fullPath);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return results;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Discover available skills by scanning priority paths.
|
|
78
|
+
* Uses short-circuit strategy: once a non-empty directory is found, stops scanning.
|
|
79
|
+
*
|
|
80
|
+
* @param _projectRoot - Project root path (reserved for future project-level skill scanning)
|
|
81
|
+
* @returns Array of discovered skills
|
|
82
|
+
*/
|
|
83
|
+
export function discoverSkills(_projectRoot) {
|
|
84
|
+
for (const scanPath of SCAN_PRIORITY) {
|
|
85
|
+
const resolved = resolvePath(scanPath);
|
|
86
|
+
// Check if directory exists
|
|
87
|
+
try {
|
|
88
|
+
const stat = fs.statSync(resolved);
|
|
89
|
+
if (!stat.isDirectory())
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// Scan for SKILL.md files
|
|
96
|
+
const skillFiles = findSkillFiles(resolved);
|
|
97
|
+
if (skillFiles.length === 0)
|
|
98
|
+
continue;
|
|
99
|
+
// Parse skills from found files
|
|
100
|
+
const skills = [];
|
|
101
|
+
const seenNames = new Set();
|
|
102
|
+
for (const filePath of skillFiles) {
|
|
103
|
+
try {
|
|
104
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
105
|
+
const fm = parseFrontmatter(content);
|
|
106
|
+
if (!fm.name)
|
|
107
|
+
continue;
|
|
108
|
+
// Filter out zhuanspec-* workflow skills
|
|
109
|
+
if (fm.name.startsWith('zhuanspec-'))
|
|
110
|
+
continue;
|
|
111
|
+
// Deduplicate by name
|
|
112
|
+
if (seenNames.has(fm.name))
|
|
113
|
+
continue;
|
|
114
|
+
seenNames.add(fm.name);
|
|
115
|
+
skills.push({
|
|
116
|
+
name: fm.name,
|
|
117
|
+
description: fm.description || '',
|
|
118
|
+
filePath,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Skip unreadable files
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Short-circuit: found a valid directory with skills, stop scanning
|
|
127
|
+
if (skills.length > 0) {
|
|
128
|
+
return skills;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// No skills found in any priority path
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Format discovered skills for display.
|
|
136
|
+
*/
|
|
137
|
+
export function formatSkillsList(skills, json) {
|
|
138
|
+
if (json) {
|
|
139
|
+
return JSON.stringify(skills.map(s => ({ name: s.name, description: s.description, filePath: s.filePath })), null, 2);
|
|
140
|
+
}
|
|
141
|
+
if (skills.length === 0) {
|
|
142
|
+
return 'No skills discovered.';
|
|
143
|
+
}
|
|
144
|
+
const lines = [`Found ${skills.length} skill(s):\n`];
|
|
145
|
+
for (const skill of skills) {
|
|
146
|
+
lines.push(` ${skill.name}`);
|
|
147
|
+
if (skill.description) {
|
|
148
|
+
lines.push(` ${skill.description}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return lines.join('\n');
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=skill-discovery.js.map
|