@planu/cli 1.0.0 → 1.0.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.
- package/dist/config/license-plans.json +201 -196
- package/dist/engine/filesystem-watcher/index.d.ts +31 -0
- package/dist/engine/filesystem-watcher/index.d.ts.map +1 -0
- package/dist/engine/filesystem-watcher/index.js +101 -0
- package/dist/engine/filesystem-watcher/index.js.map +1 -0
- package/dist/engine/living-specs/criteria-matcher.d.ts +12 -0
- package/dist/engine/living-specs/criteria-matcher.d.ts.map +1 -0
- package/dist/engine/living-specs/criteria-matcher.js +105 -0
- package/dist/engine/living-specs/criteria-matcher.js.map +1 -0
- package/dist/engine/living-specs/file-scanner.d.ts +11 -0
- package/dist/engine/living-specs/file-scanner.d.ts.map +1 -0
- package/dist/engine/living-specs/file-scanner.js +56 -0
- package/dist/engine/living-specs/file-scanner.js.map +1 -0
- package/dist/engine/living-specs/index.d.ts +9 -0
- package/dist/engine/living-specs/index.d.ts.map +1 -0
- package/dist/engine/living-specs/index.js +137 -0
- package/dist/engine/living-specs/index.js.map +1 -0
- package/dist/engine/skills-evaluator/eval-store.d.ts +4 -0
- package/dist/engine/skills-evaluator/eval-store.d.ts.map +1 -0
- package/dist/engine/skills-evaluator/eval-store.js +22 -0
- package/dist/engine/skills-evaluator/eval-store.js.map +1 -0
- package/dist/engine/skills-evaluator/index.d.ts +5 -0
- package/dist/engine/skills-evaluator/index.d.ts.map +1 -0
- package/dist/engine/skills-evaluator/index.js +54 -0
- package/dist/engine/skills-evaluator/index.js.map +1 -0
- package/dist/engine/skills-evaluator/scenario-runner.d.ts +7 -0
- package/dist/engine/skills-evaluator/scenario-runner.d.ts.map +1 -0
- package/dist/engine/skills-evaluator/scenario-runner.js +44 -0
- package/dist/engine/skills-evaluator/scenario-runner.js.map +1 -0
- package/dist/engine/skills-evaluator/skill-reader.d.ts +8 -0
- package/dist/engine/skills-evaluator/skill-reader.d.ts.map +1 -0
- package/dist/engine/skills-evaluator/skill-reader.js +152 -0
- package/dist/engine/skills-evaluator/skill-reader.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/eval-skill-v2-handler.d.ts +3 -0
- package/dist/tools/eval-skill-v2-handler.d.ts.map +1 -0
- package/dist/tools/eval-skill-v2-handler.js +53 -0
- package/dist/tools/eval-skill-v2-handler.js.map +1 -0
- package/dist/tools/filesystem-hooks-handler.d.ts +8 -0
- package/dist/tools/filesystem-hooks-handler.d.ts.map +1 -0
- package/dist/tools/filesystem-hooks-handler.js +96 -0
- package/dist/tools/filesystem-hooks-handler.js.map +1 -0
- package/dist/tools/reconcile-spec-living-handler.d.ts +6 -0
- package/dist/tools/reconcile-spec-living-handler.d.ts.map +1 -0
- package/dist/tools/reconcile-spec-living-handler.js +52 -0
- package/dist/tools/reconcile-spec-living-handler.js.map +1 -0
- package/dist/tools/register-filesystem-hooks-tools.d.ts +3 -0
- package/dist/tools/register-filesystem-hooks-tools.d.ts.map +1 -0
- package/dist/tools/register-filesystem-hooks-tools.js +85 -0
- package/dist/tools/register-filesystem-hooks-tools.js.map +1 -0
- package/dist/tools/register-living-specs-tools.d.ts +3 -0
- package/dist/tools/register-living-specs-tools.d.ts.map +1 -0
- package/dist/tools/register-living-specs-tools.js +24 -0
- package/dist/tools/register-living-specs-tools.js.map +1 -0
- package/dist/tools/register-skills-eval-tools.d.ts +3 -0
- package/dist/tools/register-skills-eval-tools.d.ts.map +1 -0
- package/dist/tools/register-skills-eval-tools.js +29 -0
- package/dist/tools/register-skills-eval-tools.js.map +1 -0
- package/dist/tools/register-spec-331-tools.d.ts.map +1 -1
- package/dist/tools/register-spec-331-tools.js +3 -7
- package/dist/tools/register-spec-331-tools.js.map +1 -1
- package/dist/tools/update-status/side-effects.d.ts.map +1 -1
- package/dist/tools/update-status/side-effects.js +11 -0
- package/dist/tools/update-status/side-effects.js.map +1 -1
- package/dist/types/filesystem-hooks.d.ts +30 -0
- package/dist/types/filesystem-hooks.d.ts.map +1 -0
- package/dist/types/filesystem-hooks.js +3 -0
- package/dist/types/filesystem-hooks.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/living-specs.d.ts +35 -0
- package/dist/types/living-specs.d.ts.map +1 -0
- package/dist/types/living-specs.js +3 -0
- package/dist/types/living-specs.js.map +1 -0
- package/dist/types/skills-evaluation.d.ts +38 -0
- package/dist/types/skills-evaluation.d.ts.map +1 -0
- package/dist/types/skills-evaluation.js +3 -0
- package/dist/types/skills-evaluation.js.map +1 -0
- package/package.json +1 -1
- package/src/config/license-plans.json +201 -196
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const CRITERIA_LINE_REGEX = /^- \[[ x]\] (.+)$/;
|
|
2
|
+
const STOP_WORDS = new Set([
|
|
3
|
+
'that',
|
|
4
|
+
'this',
|
|
5
|
+
'with',
|
|
6
|
+
'from',
|
|
7
|
+
'have',
|
|
8
|
+
'been',
|
|
9
|
+
'will',
|
|
10
|
+
'should',
|
|
11
|
+
'must',
|
|
12
|
+
'when',
|
|
13
|
+
'then',
|
|
14
|
+
'each',
|
|
15
|
+
'they',
|
|
16
|
+
'their',
|
|
17
|
+
'there',
|
|
18
|
+
'where',
|
|
19
|
+
'which',
|
|
20
|
+
'while',
|
|
21
|
+
'about',
|
|
22
|
+
'after',
|
|
23
|
+
'before',
|
|
24
|
+
'below',
|
|
25
|
+
'above',
|
|
26
|
+
'given',
|
|
27
|
+
'other',
|
|
28
|
+
'more',
|
|
29
|
+
'into',
|
|
30
|
+
'only',
|
|
31
|
+
'also',
|
|
32
|
+
'both',
|
|
33
|
+
'such',
|
|
34
|
+
'some',
|
|
35
|
+
'than',
|
|
36
|
+
'over',
|
|
37
|
+
'under',
|
|
38
|
+
]);
|
|
39
|
+
/**
|
|
40
|
+
* Extract criteria lines from spec.md content.
|
|
41
|
+
* Returns the text after the `- [ ] ` or `- [x] ` prefix.
|
|
42
|
+
*/
|
|
43
|
+
export function extractCriteriaLines(specMdContent) {
|
|
44
|
+
const lines = specMdContent.split('\n');
|
|
45
|
+
const result = [];
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
const match = CRITERIA_LINE_REGEX.exec(line.trimEnd());
|
|
48
|
+
if (match !== null) {
|
|
49
|
+
const text = match[1];
|
|
50
|
+
if (text !== undefined && text.trim().length > 0) {
|
|
51
|
+
result.push(text.trim());
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Extract meaningful keywords from a criterion string.
|
|
59
|
+
* Words must be longer than 4 chars and not in the stop-words list.
|
|
60
|
+
*/
|
|
61
|
+
function extractKeywords(criterion) {
|
|
62
|
+
return criterion
|
|
63
|
+
.toLowerCase()
|
|
64
|
+
.split(/\W+/)
|
|
65
|
+
.filter((word) => word.length > 4 && !STOP_WORDS.has(word));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if any export name or file path contains a given keyword.
|
|
69
|
+
*/
|
|
70
|
+
function findKeywordInFiles(keyword, fileResults) {
|
|
71
|
+
for (const file of fileResults) {
|
|
72
|
+
if (!file.exists) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (file.path.toLowerCase().includes(keyword)) {
|
|
76
|
+
return file.path;
|
|
77
|
+
}
|
|
78
|
+
for (const exportName of file.exportsFound) {
|
|
79
|
+
if (exportName.toLowerCase().includes(keyword)) {
|
|
80
|
+
return file.path;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Match each criterion against the scanned file results.
|
|
88
|
+
* A criterion is "met" if at least one keyword is found in any file export or path.
|
|
89
|
+
*/
|
|
90
|
+
export function matchCriteria(criteriaLines, fileResults) {
|
|
91
|
+
return criteriaLines.map((criterion) => {
|
|
92
|
+
const keywords = extractKeywords(criterion);
|
|
93
|
+
if (keywords.length === 0) {
|
|
94
|
+
return { criterion, met: false, evidence: 'No match' };
|
|
95
|
+
}
|
|
96
|
+
for (const keyword of keywords) {
|
|
97
|
+
const foundIn = findKeywordInFiles(keyword, fileResults);
|
|
98
|
+
if (foundIn !== null) {
|
|
99
|
+
return { criterion, met: true, evidence: `Found in ${foundIn}` };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { criterion, met: false, evidence: 'No match' };
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=criteria-matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"criteria-matcher.js","sourceRoot":"","sources":["../../../src/engine/living-specs/criteria-matcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LivingSpecFileResult } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Scan a single file: check existence, count lines, extract exports.
|
|
4
|
+
*/
|
|
5
|
+
export declare function scanFile(filePath: string): Promise<LivingSpecFileResult>;
|
|
6
|
+
/**
|
|
7
|
+
* Extract all file paths referenced in technical.md content (src/ and tests/ paths).
|
|
8
|
+
* Deduplicates results.
|
|
9
|
+
*/
|
|
10
|
+
export declare function extractFilePaths(technicalMdContent: string): string[];
|
|
11
|
+
//# sourceMappingURL=file-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-scanner.d.ts","sourceRoot":"","sources":["../../../src/engine/living-specs/file-scanner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAKjE;;GAEG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAe9E;AAkBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,EAAE,CAWrE"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// engine/living-specs/file-scanner.ts — File existence, line count, export extraction (SPEC-330)
|
|
2
|
+
import { readFile, access } from 'node:fs/promises';
|
|
3
|
+
const EXPORT_REGEX = /export\s+(?:async\s+)?(?:function|class|const)\s+(\w+)/g;
|
|
4
|
+
const FILE_PATH_REGEX = /\b(?:src|tests)\/[\w/.-]+\.ts\b/g;
|
|
5
|
+
/**
|
|
6
|
+
* Scan a single file: check existence, count lines, extract exports.
|
|
7
|
+
*/
|
|
8
|
+
export async function scanFile(filePath) {
|
|
9
|
+
try {
|
|
10
|
+
await access(filePath);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return { path: filePath, exists: false, lineCount: 0, exportsFound: [] };
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const content = await readFile(filePath, 'utf-8');
|
|
17
|
+
const lineCount = content.split('\n').length;
|
|
18
|
+
const exportsFound = extractExports(content);
|
|
19
|
+
return { path: filePath, exists: true, lineCount, exportsFound };
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return { path: filePath, exists: true, lineCount: 0, exportsFound: [] };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Extract all exported symbol names from file content.
|
|
27
|
+
*/
|
|
28
|
+
function extractExports(content) {
|
|
29
|
+
const found = [];
|
|
30
|
+
let match;
|
|
31
|
+
const regex = new RegExp(EXPORT_REGEX.source, 'g');
|
|
32
|
+
while ((match = regex.exec(content)) !== null) {
|
|
33
|
+
const name = match[1];
|
|
34
|
+
if (name !== undefined && !found.includes(name)) {
|
|
35
|
+
found.push(name);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return found;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Extract all file paths referenced in technical.md content (src/ and tests/ paths).
|
|
42
|
+
* Deduplicates results.
|
|
43
|
+
*/
|
|
44
|
+
export function extractFilePaths(technicalMdContent) {
|
|
45
|
+
const found = [];
|
|
46
|
+
const regex = new RegExp(FILE_PATH_REGEX.source, 'g');
|
|
47
|
+
let match;
|
|
48
|
+
while ((match = regex.exec(technicalMdContent)) !== null) {
|
|
49
|
+
const p = match[0];
|
|
50
|
+
if (!found.includes(p)) {
|
|
51
|
+
found.push(p);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return found;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=file-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-scanner.js","sourceRoot":"","sources":["../../../src/engine/living-specs/file-scanner.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAGpD,MAAM,YAAY,GAAG,yDAAyD,CAAC;AAC/E,MAAM,eAAe,GAAG,kCAAkC,CAAC;AAE3D;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;IAC3E,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAClD,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QAC7C,MAAM,YAAY,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QAC7C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;IAC1E,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,OAAe;IACrC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAA6B,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnD,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,kBAA0B;IACzD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACtD,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACzD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACnB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LivingSpecReconcileResult } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Auto-reconcile a spec against its actual implementation files.
|
|
4
|
+
* Reads technical.md for file paths, spec.md for criteria,
|
|
5
|
+
* scans files, matches criteria, updates progress.md.
|
|
6
|
+
* Never throws -- returns partial result on error.
|
|
7
|
+
*/
|
|
8
|
+
export declare function reconcileSpec(specId: string, projectPath: string): Promise<LivingSpecReconcileResult>;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/living-specs/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAkFtE;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,yBAAyB,CAAC,CAoEpC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// engine/living-specs/index.ts -- Main auto-reconcile entry point (SPEC-330)
|
|
2
|
+
import { readFile, writeFile, access } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { glob } from 'glob';
|
|
5
|
+
import { scanFile, extractFilePaths } from './file-scanner.js';
|
|
6
|
+
import { extractCriteriaLines, matchCriteria } from './criteria-matcher.js';
|
|
7
|
+
/**
|
|
8
|
+
* Find the spec directory for a given specId within the project.
|
|
9
|
+
* Looks for planu/specs/{specId}-SLUG pattern via glob.
|
|
10
|
+
*/
|
|
11
|
+
async function findSpecDir(specId, projectPath) {
|
|
12
|
+
const suffix = specId + '-*/';
|
|
13
|
+
const pattern = join(projectPath, 'planu', 'specs', suffix);
|
|
14
|
+
const dirs = await glob(pattern, { absolute: true });
|
|
15
|
+
return dirs[0] ?? null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Read a file safely, returning empty string on error.
|
|
19
|
+
*/
|
|
20
|
+
async function readSafe(filePath) {
|
|
21
|
+
try {
|
|
22
|
+
await access(filePath);
|
|
23
|
+
return await readFile(filePath, 'utf-8');
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build the drift flags from missing files.
|
|
31
|
+
*/
|
|
32
|
+
function buildDriftFlags(fileResults) {
|
|
33
|
+
return fileResults.filter((f) => !f.exists).map((f) => 'DRIFT: ' + f.path + ' missing');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Append or update the Auto-Reconcile section in progress.md.
|
|
37
|
+
*/
|
|
38
|
+
async function updateProgressMd(progressPath, result) {
|
|
39
|
+
const existing = await readSafe(progressPath);
|
|
40
|
+
const coveragePct = result.criteriaTotal > 0 ? Math.round((result.criteriaMet / result.criteriaTotal) * 100) : 0;
|
|
41
|
+
const driftSection = result.driftFlags.length > 0
|
|
42
|
+
? '\n### Drift Flags\n\n' + result.driftFlags.map((f) => '- ' + f).join('\n') + '\n'
|
|
43
|
+
: '';
|
|
44
|
+
const section = '\n## Auto-Reconcile\n\n' +
|
|
45
|
+
'> Last reconciled: ' +
|
|
46
|
+
result.reconciledAt +
|
|
47
|
+
'\n\n' +
|
|
48
|
+
'- Files found: ' +
|
|
49
|
+
String(result.filesFound) +
|
|
50
|
+
'\n' +
|
|
51
|
+
'- Files missing: ' +
|
|
52
|
+
String(result.filesMissing) +
|
|
53
|
+
'\n' +
|
|
54
|
+
'- Criteria met: ' +
|
|
55
|
+
String(result.criteriaMet) +
|
|
56
|
+
'/' +
|
|
57
|
+
String(result.criteriaTotal) +
|
|
58
|
+
' (' +
|
|
59
|
+
String(coveragePct) +
|
|
60
|
+
'%)' +
|
|
61
|
+
driftSection +
|
|
62
|
+
'\n';
|
|
63
|
+
const AUTO_RECONCILE_MARKER = '## Auto-Reconcile';
|
|
64
|
+
let newContent;
|
|
65
|
+
if (existing.includes(AUTO_RECONCILE_MARKER)) {
|
|
66
|
+
newContent = existing.replace(/\n## Auto-Reconcile[\s\S]*?(?=\n## |\n$|$)/, section);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
newContent = existing + section;
|
|
70
|
+
}
|
|
71
|
+
await writeFile(progressPath, newContent, 'utf-8');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Auto-reconcile a spec against its actual implementation files.
|
|
75
|
+
* Reads technical.md for file paths, spec.md for criteria,
|
|
76
|
+
* scans files, matches criteria, updates progress.md.
|
|
77
|
+
* Never throws -- returns partial result on error.
|
|
78
|
+
*/
|
|
79
|
+
export async function reconcileSpec(specId, projectPath) {
|
|
80
|
+
const reconciledAt = new Date().toISOString();
|
|
81
|
+
const empty = {
|
|
82
|
+
specId,
|
|
83
|
+
reconciledAt,
|
|
84
|
+
filesFound: 0,
|
|
85
|
+
filesMissing: 0,
|
|
86
|
+
criteriaMet: 0,
|
|
87
|
+
criteriaTotal: 0,
|
|
88
|
+
driftFlags: [],
|
|
89
|
+
fileResults: [],
|
|
90
|
+
criteriaResults: [],
|
|
91
|
+
};
|
|
92
|
+
try {
|
|
93
|
+
const specDir = await findSpecDir(specId, projectPath);
|
|
94
|
+
if (specDir === null) {
|
|
95
|
+
empty.driftFlags.push('DRIFT: spec directory for ' + specId + ' not found');
|
|
96
|
+
return empty;
|
|
97
|
+
}
|
|
98
|
+
const technicalPath = join(specDir, 'technical.md');
|
|
99
|
+
const specMdPath = join(specDir, 'spec.md');
|
|
100
|
+
const progressPath = join(specDir, 'progress.md');
|
|
101
|
+
const [technicalContent, specMdContent] = await Promise.all([
|
|
102
|
+
readSafe(technicalPath),
|
|
103
|
+
readSafe(specMdPath),
|
|
104
|
+
]);
|
|
105
|
+
const relativePaths = extractFilePaths(technicalContent);
|
|
106
|
+
const absolutePaths = relativePaths.map((p) => join(projectPath, p));
|
|
107
|
+
const fileResults = await Promise.all(absolutePaths.map((p) => scanFile(p)));
|
|
108
|
+
const fileResultsWithRelative = fileResults.map((fr, i) => ({
|
|
109
|
+
...fr,
|
|
110
|
+
path: relativePaths[i] ?? fr.path,
|
|
111
|
+
}));
|
|
112
|
+
const filesFound = fileResultsWithRelative.filter((f) => f.exists).length;
|
|
113
|
+
const filesMissing = fileResultsWithRelative.filter((f) => !f.exists).length;
|
|
114
|
+
const criteriaLines = extractCriteriaLines(specMdContent);
|
|
115
|
+
const criteriaResults = matchCriteria(criteriaLines, fileResultsWithRelative);
|
|
116
|
+
const criteriaMet = criteriaResults.filter((c) => c.met).length;
|
|
117
|
+
const driftFlags = buildDriftFlags(fileResultsWithRelative);
|
|
118
|
+
const result = {
|
|
119
|
+
specId,
|
|
120
|
+
reconciledAt,
|
|
121
|
+
filesFound,
|
|
122
|
+
filesMissing,
|
|
123
|
+
criteriaMet,
|
|
124
|
+
criteriaTotal: criteriaLines.length,
|
|
125
|
+
driftFlags,
|
|
126
|
+
fileResults: fileResultsWithRelative,
|
|
127
|
+
criteriaResults,
|
|
128
|
+
};
|
|
129
|
+
await updateProgressMd(progressPath, result);
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
134
|
+
return { ...empty, driftFlags: ['ERROR: ' + message] };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/living-specs/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SkillsEvalResult } from '../../types/index.js';
|
|
2
|
+
export declare function saveEvalResult(skillName: string, projectPath: string, result: SkillsEvalResult): Promise<void>;
|
|
3
|
+
export declare function loadEvalResult(skillName: string, projectPath: string): Promise<SkillsEvalResult | null>;
|
|
4
|
+
//# sourceMappingURL=eval-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval-store.d.ts","sourceRoot":"","sources":["../../../src/engine/skills-evaluator/eval-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAQ7D,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAQlC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// engine/skills-evaluator/eval-store.ts — Persistence for skill eval results (SPEC-332)
|
|
2
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
function evalResultPath(skillName, projectPath) {
|
|
5
|
+
return join(projectPath, '.planu', 'skills', skillName, 'eval-results.json');
|
|
6
|
+
}
|
|
7
|
+
export async function saveEvalResult(skillName, projectPath, result) {
|
|
8
|
+
const filePath = evalResultPath(skillName, projectPath);
|
|
9
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
10
|
+
await writeFile(filePath, JSON.stringify(result, null, 2), 'utf-8');
|
|
11
|
+
}
|
|
12
|
+
export async function loadEvalResult(skillName, projectPath) {
|
|
13
|
+
const filePath = evalResultPath(skillName, projectPath);
|
|
14
|
+
try {
|
|
15
|
+
const content = await readFile(filePath, 'utf-8');
|
|
16
|
+
return JSON.parse(content);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=eval-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval-store.js","sourceRoot":"","sources":["../../../src/engine/skills-evaluator/eval-store.ts"],"names":[],"mappings":"AAAA,wFAAwF;AAGxF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,SAAS,cAAc,CAAC,SAAiB,EAAE,WAAmB;IAC5D,OAAO,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,mBAAmB,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,SAAiB,EACjB,WAAmB,EACnB,MAAwB;IAExB,MAAM,QAAQ,GAAG,cAAc,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACxD,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,SAAiB,EACjB,WAAmB;IAEnB,MAAM,QAAQ,GAAG,cAAc,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAqB,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SkillsEvalResult, SkillsEvalScenario } from '../../types/index.js';
|
|
2
|
+
export declare function evaluateSkill(skillName: string, projectPath: string): Promise<SkillsEvalResult>;
|
|
3
|
+
export declare function getEvalResults(skillName: string, projectPath: string): Promise<SkillsEvalResult | null>;
|
|
4
|
+
export declare function listScenarios(skillName: string, projectPath: string): Promise<SkillsEvalScenario[]>;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/skills-evaluator/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAejF,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,CAAC,CAoC3B;AAED,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAElC;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAG/B"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// engine/skills-evaluator/index.ts — Public API for skills evaluation framework (SPEC-332)
|
|
2
|
+
import { readSkillContent, parseEvalScenarios } from './skill-reader.js';
|
|
3
|
+
import { runScenario } from './scenario-runner.js';
|
|
4
|
+
import { saveEvalResult, loadEvalResult } from './eval-store.js';
|
|
5
|
+
function buildRecommendation(score) {
|
|
6
|
+
if (score >= 0.8) {
|
|
7
|
+
return 'Excellent — skill is well-configured';
|
|
8
|
+
}
|
|
9
|
+
if (score >= 0.5) {
|
|
10
|
+
return 'Good — minor improvements possible';
|
|
11
|
+
}
|
|
12
|
+
return 'Needs review — consider updating eval_scenarios';
|
|
13
|
+
}
|
|
14
|
+
export async function evaluateSkill(skillName, projectPath) {
|
|
15
|
+
const skillContent = await readSkillContent(skillName, projectPath);
|
|
16
|
+
const scenarios = parseEvalScenarios(skillContent);
|
|
17
|
+
if (scenarios.length === 0) {
|
|
18
|
+
const result = {
|
|
19
|
+
skillName,
|
|
20
|
+
evaluatedAt: new Date().toISOString(),
|
|
21
|
+
scenariosRun: 0,
|
|
22
|
+
scenariosPassed: 0,
|
|
23
|
+
overallScore: 0,
|
|
24
|
+
flagged: true,
|
|
25
|
+
runResults: [],
|
|
26
|
+
recommendation: 'No eval_scenarios defined in SKILL.md',
|
|
27
|
+
};
|
|
28
|
+
await saveEvalResult(skillName, projectPath, result);
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
const runResults = scenarios.map((scenario) => runScenario(scenario, skillContent));
|
|
32
|
+
const scenariosPassed = runResults.filter((r) => r.passed).length;
|
|
33
|
+
const overallScore = runResults.reduce((sum, r) => sum + r.qualityScore, 0) / runResults.length;
|
|
34
|
+
const result = {
|
|
35
|
+
skillName,
|
|
36
|
+
evaluatedAt: new Date().toISOString(),
|
|
37
|
+
scenariosRun: scenarios.length,
|
|
38
|
+
scenariosPassed,
|
|
39
|
+
overallScore,
|
|
40
|
+
flagged: overallScore < 0.5,
|
|
41
|
+
runResults,
|
|
42
|
+
recommendation: buildRecommendation(overallScore),
|
|
43
|
+
};
|
|
44
|
+
await saveEvalResult(skillName, projectPath, result);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
export async function getEvalResults(skillName, projectPath) {
|
|
48
|
+
return loadEvalResult(skillName, projectPath);
|
|
49
|
+
}
|
|
50
|
+
export async function listScenarios(skillName, projectPath) {
|
|
51
|
+
const skillContent = await readSkillContent(skillName, projectPath);
|
|
52
|
+
return parseEvalScenarios(skillContent);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/skills-evaluator/index.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAG3F,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjE,SAAS,mBAAmB,CAAC,KAAa;IACxC,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;QACjB,OAAO,sCAAsC,CAAC;IAChD,CAAC;IACD,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;QACjB,OAAO,oCAAoC,CAAC;IAC9C,CAAC;IACD,OAAO,iDAAiD,CAAC;AAC3D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,WAAmB;IAEnB,MAAM,YAAY,GAAG,MAAM,gBAAgB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACpE,MAAM,SAAS,GAAG,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAEnD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAqB;YAC/B,SAAS;YACT,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACrC,YAAY,EAAE,CAAC;YACf,eAAe,EAAE,CAAC;YAClB,YAAY,EAAE,CAAC;YACf,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,EAAE;YACd,cAAc,EAAE,uCAAuC;SACxD,CAAC;QACF,MAAM,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;QACrD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,UAAU,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;IACpF,MAAM,eAAe,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;IAClE,MAAM,YAAY,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC;IAEhG,MAAM,MAAM,GAAqB;QAC/B,SAAS;QACT,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,YAAY,EAAE,SAAS,CAAC,MAAM;QAC9B,eAAe;QACf,YAAY;QACZ,OAAO,EAAE,YAAY,GAAG,GAAG;QAC3B,UAAU;QACV,cAAc,EAAE,mBAAmB,CAAC,YAAY,CAAC;KAClD,CAAC;IAEF,MAAM,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,SAAiB,EACjB,WAAmB;IAEnB,OAAO,cAAc,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,WAAmB;IAEnB,MAAM,YAAY,GAAG,MAAM,gBAAgB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACpE,OAAO,kBAAkB,CAAC,YAAY,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SkillsEvalScenario, SkillsEvalRunResult } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Evaluate a single scenario against the provided skill content.
|
|
4
|
+
* Pure function — no async, no I/O.
|
|
5
|
+
*/
|
|
6
|
+
export declare function runScenario(scenario: SkillsEvalScenario, skillContent: string): SkillsEvalRunResult;
|
|
7
|
+
//# sourceMappingURL=scenario-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario-runner.d.ts","sourceRoot":"","sources":["../../../src/engine/skills-evaluator/scenario-runner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEpF;;;GAGG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,kBAAkB,EAC5B,YAAY,EAAE,MAAM,GACnB,mBAAmB,CAyCrB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// engine/skills-evaluator/scenario-runner.ts — Pure scenario evaluation logic (SPEC-332)
|
|
2
|
+
/**
|
|
3
|
+
* Evaluate a single scenario against the provided skill content.
|
|
4
|
+
* Pure function — no async, no I/O.
|
|
5
|
+
*/
|
|
6
|
+
export function runScenario(scenario, skillContent) {
|
|
7
|
+
const lower = skillContent.toLowerCase();
|
|
8
|
+
const keywordsFound = [];
|
|
9
|
+
const keywordsMissing = [];
|
|
10
|
+
for (const keyword of scenario.expectedKeywords) {
|
|
11
|
+
if (lower.includes(keyword.toLowerCase())) {
|
|
12
|
+
keywordsFound.push(keyword);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
keywordsMissing.push(keyword);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
let patternsMatched = 0;
|
|
19
|
+
const patternsTotal = scenario.expectedPatterns.length;
|
|
20
|
+
for (const pattern of scenario.expectedPatterns) {
|
|
21
|
+
try {
|
|
22
|
+
const regex = new RegExp(pattern, 'i');
|
|
23
|
+
if (regex.test(skillContent)) {
|
|
24
|
+
patternsMatched++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Invalid regex counts as unmatched
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const keywordRatio = keywordsFound.length / Math.max(scenario.expectedKeywords.length, 1);
|
|
32
|
+
const patternRatio = patternsMatched / Math.max(patternsTotal, 1);
|
|
33
|
+
const qualityScore = keywordRatio * 0.6 + patternRatio * 0.4;
|
|
34
|
+
return {
|
|
35
|
+
scenarioName: scenario.name,
|
|
36
|
+
qualityScore,
|
|
37
|
+
keywordsFound,
|
|
38
|
+
keywordsMissing,
|
|
39
|
+
patternsMatched,
|
|
40
|
+
patternsTotal,
|
|
41
|
+
passed: qualityScore >= 0.5,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=scenario-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario-runner.js","sourceRoot":"","sources":["../../../src/engine/skills-evaluator/scenario-runner.ts"],"names":[],"mappings":"AAAA,yFAAyF;AAIzF;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,QAA4B,EAC5B,YAAoB;IAEpB,MAAM,KAAK,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC;IAEzC,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,MAAM,eAAe,GAAa,EAAE,CAAC;IAErC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,gBAAgB,EAAE,CAAC;QAChD,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAC1C,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,MAAM,aAAa,GAAG,QAAQ,CAAC,gBAAgB,CAAC,MAAM,CAAC;IAEvD,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,gBAAgB,EAAE,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACvC,IAAI,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC7B,eAAe,EAAE,CAAC;YACpB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,oCAAoC;QACtC,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAG,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC1F,MAAM,YAAY,GAAG,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IAClE,MAAM,YAAY,GAAG,YAAY,GAAG,GAAG,GAAG,YAAY,GAAG,GAAG,CAAC;IAE7D,OAAO;QACL,YAAY,EAAE,QAAQ,CAAC,IAAI;QAC3B,YAAY;QACZ,aAAa;QACb,eAAe;QACf,eAAe;QACf,aAAa;QACb,MAAM,EAAE,YAAY,IAAI,GAAG;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SkillsEvalScenario } from '../../types/index.js';
|
|
2
|
+
export declare function readSkillContent(skillName: string, projectPath: string): Promise<string>;
|
|
3
|
+
/**
|
|
4
|
+
* Parse eval_scenarios from skill content frontmatter.
|
|
5
|
+
* Supports block and inline YAML array format.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseEvalScenarios(skillContent: string): SkillsEvalScenario[];
|
|
8
|
+
//# sourceMappingURL=skill-reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-reader.d.ts","sourceRoot":"","sources":["../../../src/engine/skills-evaluator/skill-reader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAA6B,MAAM,sBAAsB,CAAC;AAE1F,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO9F;AAkID;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAiB7E"}
|