skrypt-ai 0.6.1 → 0.8.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/dist/audit/doc-parser.d.ts +5 -0
- package/dist/audit/doc-parser.js +106 -0
- package/dist/audit/index.d.ts +4 -0
- package/dist/audit/index.js +4 -0
- package/dist/audit/matcher.d.ts +6 -0
- package/dist/audit/matcher.js +94 -0
- package/dist/audit/reporter.d.ts +9 -0
- package/dist/audit/reporter.js +106 -0
- package/dist/audit/types.d.ts +37 -0
- package/dist/auth/index.js +6 -4
- package/dist/cli.js +12 -2
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.js +59 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +73 -0
- package/dist/commands/{generate.d.ts → generate/index.d.ts} +1 -0
- package/dist/commands/generate/index.js +393 -0
- package/dist/commands/generate/scan.d.ts +41 -0
- package/dist/commands/generate/scan.js +256 -0
- package/dist/commands/generate/verify.d.ts +14 -0
- package/dist/commands/generate/verify.js +122 -0
- package/dist/commands/generate/write.d.ts +25 -0
- package/dist/commands/generate/write.js +120 -0
- package/dist/commands/import.js +4 -1
- package/dist/commands/llms-txt.js +6 -4
- package/dist/commands/refresh.d.ts +2 -0
- package/dist/commands/refresh.js +158 -0
- package/dist/commands/review.d.ts +2 -0
- package/dist/commands/review.js +110 -0
- package/dist/commands/test.js +177 -236
- package/dist/commands/watch.js +29 -20
- package/dist/config/loader.d.ts +6 -2
- package/dist/config/loader.js +39 -3
- package/dist/config/types.d.ts +7 -0
- package/dist/generator/agents-md.d.ts +25 -0
- package/dist/generator/agents-md.js +122 -0
- package/dist/generator/generator.js +2 -1
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +2 -0
- package/dist/generator/mdx-serializer.d.ts +11 -0
- package/dist/generator/mdx-serializer.js +135 -0
- package/dist/generator/organizer.d.ts +1 -16
- package/dist/generator/organizer.js +0 -38
- package/dist/generator/types.d.ts +3 -0
- package/dist/generator/writer.js +65 -32
- package/dist/github/org-discovery.d.ts +17 -0
- package/dist/github/org-discovery.js +93 -0
- package/dist/llm/index.d.ts +2 -0
- package/dist/llm/index.js +8 -2
- package/dist/llm/proxy-client.d.ts +32 -0
- package/dist/llm/proxy-client.js +103 -0
- package/dist/next-actions/actions.d.ts +2 -0
- package/dist/next-actions/actions.js +190 -0
- package/dist/next-actions/index.d.ts +6 -0
- package/dist/next-actions/index.js +39 -0
- package/dist/next-actions/setup.d.ts +2 -0
- package/dist/next-actions/setup.js +72 -0
- package/dist/next-actions/state.d.ts +7 -0
- package/dist/next-actions/state.js +68 -0
- package/dist/next-actions/suggest.d.ts +3 -0
- package/dist/next-actions/suggest.js +47 -0
- package/dist/next-actions/types.d.ts +26 -0
- package/dist/refresh/differ.d.ts +9 -0
- package/dist/refresh/differ.js +67 -0
- package/dist/refresh/index.d.ts +4 -0
- package/dist/refresh/index.js +4 -0
- package/dist/refresh/manifest.d.ts +18 -0
- package/dist/refresh/manifest.js +71 -0
- package/dist/refresh/splicer.d.ts +9 -0
- package/dist/refresh/splicer.js +50 -0
- package/dist/refresh/types.d.ts +37 -0
- package/dist/review/index.d.ts +8 -0
- package/dist/review/index.js +94 -0
- package/dist/review/parser.d.ts +16 -0
- package/dist/review/parser.js +95 -0
- package/dist/review/types.d.ts +18 -0
- package/dist/scanner/csharp.d.ts +0 -4
- package/dist/scanner/csharp.js +9 -49
- package/dist/scanner/go.d.ts +0 -3
- package/dist/scanner/go.js +8 -35
- package/dist/scanner/java.d.ts +0 -4
- package/dist/scanner/java.js +9 -49
- package/dist/scanner/kotlin.d.ts +0 -3
- package/dist/scanner/kotlin.js +6 -33
- package/dist/scanner/php.d.ts +0 -10
- package/dist/scanner/php.js +11 -55
- package/dist/scanner/ruby.d.ts +0 -3
- package/dist/scanner/ruby.js +8 -38
- package/dist/scanner/rust.d.ts +0 -3
- package/dist/scanner/rust.js +10 -37
- package/dist/scanner/swift.d.ts +0 -3
- package/dist/scanner/swift.js +8 -35
- package/dist/scanner/types.d.ts +2 -0
- package/dist/scanner/utils.d.ts +41 -0
- package/dist/scanner/utils.js +97 -0
- package/dist/structure/index.d.ts +19 -0
- package/dist/structure/index.js +92 -0
- package/dist/structure/planner.d.ts +8 -0
- package/dist/structure/planner.js +180 -0
- package/dist/structure/topology.d.ts +16 -0
- package/dist/structure/topology.js +49 -0
- package/dist/structure/types.d.ts +26 -0
- package/dist/template/docs.json +5 -2
- package/dist/template/next.config.mjs +31 -0
- package/dist/template/package.json +5 -3
- package/dist/template/src/app/layout.tsx +13 -13
- package/dist/template/src/app/llms-full.md/route.ts +29 -0
- package/dist/template/src/app/llms.txt/route.ts +29 -0
- package/dist/template/src/app/md/[...slug]/route.ts +174 -0
- package/dist/template/src/app/reference/route.ts +22 -18
- package/dist/template/src/app/sitemap.ts +1 -1
- package/dist/template/src/components/ai-chat-impl.tsx +206 -0
- package/dist/template/src/components/ai-chat.tsx +20 -193
- package/dist/template/src/components/mdx/index.tsx +27 -4
- package/dist/template/src/lib/fonts.ts +135 -0
- package/dist/template/src/middleware.ts +101 -0
- package/dist/template/src/styles/globals.css +28 -20
- package/dist/testing/comparator.d.ts +7 -0
- package/dist/testing/comparator.js +77 -0
- package/dist/testing/docker.d.ts +21 -0
- package/dist/testing/docker.js +234 -0
- package/dist/testing/env.d.ts +16 -0
- package/dist/testing/env.js +58 -0
- package/dist/testing/extractor.d.ts +9 -0
- package/dist/testing/extractor.js +195 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.js +6 -0
- package/dist/testing/runner.d.ts +5 -0
- package/dist/testing/runner.js +225 -0
- package/dist/testing/types.d.ts +58 -0
- package/dist/utils/files.d.ts +0 -8
- package/dist/utils/files.js +0 -33
- package/package.json +1 -1
- package/dist/autofix/autofix.test.js +0 -487
- package/dist/commands/generate.js +0 -445
- package/dist/generator/generator.test.js +0 -259
- package/dist/generator/writer.test.js +0 -411
- package/dist/llm/llm.manual-test.js +0 -112
- package/dist/llm/llm.mock-test.d.ts +0 -4
- package/dist/llm/llm.mock-test.js +0 -79
- package/dist/plugins/index.d.ts +0 -47
- package/dist/plugins/index.js +0 -181
- package/dist/scanner/content-type.test.js +0 -231
- package/dist/scanner/integration.test.d.ts +0 -4
- package/dist/scanner/integration.test.js +0 -180
- package/dist/scanner/scanner.test.js +0 -210
- package/dist/scanner/typescript.manual-test.d.ts +0 -1
- package/dist/scanner/typescript.manual-test.js +0 -112
- package/dist/template/src/app/docs/auth/page.mdx +0 -589
- package/dist/template/src/app/docs/autofix/page.mdx +0 -624
- package/dist/template/src/app/docs/cli/page.mdx +0 -217
- package/dist/template/src/app/docs/config/page.mdx +0 -428
- package/dist/template/src/app/docs/configuration/page.mdx +0 -86
- package/dist/template/src/app/docs/deployment/page.mdx +0 -112
- package/dist/template/src/app/docs/generator/generator.md +0 -504
- package/dist/template/src/app/docs/generator/organizer.md +0 -779
- package/dist/template/src/app/docs/generator/page.mdx +0 -613
- package/dist/template/src/app/docs/github/page.mdx +0 -502
- package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
- package/dist/template/src/app/docs/llm/index.md +0 -471
- package/dist/template/src/app/docs/llm/page.mdx +0 -428
- package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
- package/dist/template/src/app/docs/pro/page.mdx +0 -121
- package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
- package/dist/template/src/app/docs/scanner/content-type.md +0 -599
- package/dist/template/src/app/docs/scanner/index.md +0 -212
- package/dist/template/src/app/docs/scanner/page.mdx +0 -307
- package/dist/template/src/app/docs/scanner/python.md +0 -469
- package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
- package/dist/template/src/app/docs/scanner/rust.md +0 -325
- package/dist/template/src/app/docs/scanner/typescript.md +0 -201
- package/dist/template/src/app/icon.tsx +0 -29
- package/dist/utils/validation.d.ts +0 -1
- package/dist/utils/validation.js +0 -12
- /package/dist/{autofix/autofix.test.d.ts → audit/types.js} +0 -0
- /package/dist/{generator/generator.test.d.ts → next-actions/types.js} +0 -0
- /package/dist/{generator/writer.test.d.ts → refresh/types.js} +0 -0
- /package/dist/{llm/llm.manual-test.d.ts → review/types.js} +0 -0
- /package/dist/{scanner/content-type.test.d.ts → structure/types.js} +0 -0
- /package/dist/{scanner/scanner.test.d.ts → testing/types.js} +0 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { NextActionPreferences, ProjectActionState, Suggestion } from './types.js';
|
|
2
|
+
export declare function getSuggestions(commandName: string, preferences: NextActionPreferences, _state?: ProjectActionState): Suggestion[];
|
|
3
|
+
export declare function printSuggestions(suggestions: Suggestion[]): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ACTION_DEFINITIONS } from './actions.js';
|
|
2
|
+
const MAX_SUGGESTIONS = 2;
|
|
3
|
+
export function getSuggestions(commandName, preferences, _state) {
|
|
4
|
+
if (!preferences.enabled)
|
|
5
|
+
return [];
|
|
6
|
+
const candidates = ACTION_DEFINITIONS
|
|
7
|
+
.filter(action => {
|
|
8
|
+
if (!action.afterCommands.includes(commandName))
|
|
9
|
+
return false;
|
|
10
|
+
if (!preferences.categories[action.category])
|
|
11
|
+
return false;
|
|
12
|
+
if (action.condition && !action.condition())
|
|
13
|
+
return false;
|
|
14
|
+
// Don't suggest the same command that just ran
|
|
15
|
+
if (action.command.startsWith('skrypt ')) {
|
|
16
|
+
const suggestedCmd = action.command.split(' ')[1];
|
|
17
|
+
if (suggestedCmd === commandName)
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
})
|
|
22
|
+
.sort((a, b) => b.priority - a.priority)
|
|
23
|
+
.slice(0, MAX_SUGGESTIONS);
|
|
24
|
+
// Deduplicate by command string
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
const unique = [];
|
|
27
|
+
for (const c of candidates) {
|
|
28
|
+
if (!seen.has(c.command)) {
|
|
29
|
+
seen.add(c.command);
|
|
30
|
+
unique.push({ command: c.command, message: c.message });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return unique;
|
|
34
|
+
}
|
|
35
|
+
export function printSuggestions(suggestions) {
|
|
36
|
+
if (suggestions.length === 0)
|
|
37
|
+
return;
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log(' \x1b[36m───\x1b[0m');
|
|
40
|
+
console.log('');
|
|
41
|
+
for (const s of suggestions) {
|
|
42
|
+
console.log(` \x1b[36m\u2192\x1b[0m \x1b[1m${s.command}\x1b[0m`);
|
|
43
|
+
console.log(` ${s.message}`);
|
|
44
|
+
}
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log(' \x1b[90mManage: skrypt config --suggestions\x1b[0m');
|
|
47
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface NextActionPreferences {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
categories: {
|
|
4
|
+
workflow: boolean;
|
|
5
|
+
quality: boolean;
|
|
6
|
+
cicd: boolean;
|
|
7
|
+
advanced: boolean;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface ProjectActionState {
|
|
11
|
+
completedCommands: string[];
|
|
12
|
+
lastUpdated: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ActionDefinition {
|
|
15
|
+
id: string;
|
|
16
|
+
afterCommands: string[];
|
|
17
|
+
category: keyof NextActionPreferences['categories'];
|
|
18
|
+
message: string;
|
|
19
|
+
command: string;
|
|
20
|
+
priority: number;
|
|
21
|
+
condition?: () => boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface Suggestion {
|
|
24
|
+
command: string;
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DocManifest, StaleElement } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Get list of changed files since a given ref using git diff
|
|
4
|
+
*/
|
|
5
|
+
export declare function getChangedFiles(since: string, cwd?: string): string[];
|
|
6
|
+
/**
|
|
7
|
+
* Find elements whose signatures have changed since the manifest was written
|
|
8
|
+
*/
|
|
9
|
+
export declare function findStaleElements(manifest: DocManifest, changedFiles: string[]): Promise<StaleElement[]>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import { scanDirectory } from '../scanner/index.js';
|
|
3
|
+
import { hashSignature } from './manifest.js';
|
|
4
|
+
/**
|
|
5
|
+
* Get list of changed files since a given ref using git diff
|
|
6
|
+
*/
|
|
7
|
+
export function getChangedFiles(since, cwd) {
|
|
8
|
+
const result = spawnSync('git', ['diff', '--name-only', `${since}..HEAD`], {
|
|
9
|
+
stdio: 'pipe',
|
|
10
|
+
cwd,
|
|
11
|
+
timeout: 10000,
|
|
12
|
+
});
|
|
13
|
+
if (result.status !== 0) {
|
|
14
|
+
const stderr = result.stderr?.toString() || '';
|
|
15
|
+
throw new Error(`git diff failed: ${stderr}`);
|
|
16
|
+
}
|
|
17
|
+
return result.stdout
|
|
18
|
+
.toString()
|
|
19
|
+
.trim()
|
|
20
|
+
.split('\n')
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Find elements whose signatures have changed since the manifest was written
|
|
25
|
+
*/
|
|
26
|
+
export async function findStaleElements(manifest, changedFiles) {
|
|
27
|
+
const stale = [];
|
|
28
|
+
// Find manifest entries whose source files have changed
|
|
29
|
+
const affectedEntries = manifest.elements.filter(entry => changedFiles.some(f => entry.sourceFile === f ||
|
|
30
|
+
entry.sourceFile.endsWith('/' + f) ||
|
|
31
|
+
f.endsWith('/' + entry.sourceFile)));
|
|
32
|
+
if (affectedEntries.length === 0) {
|
|
33
|
+
return stale;
|
|
34
|
+
}
|
|
35
|
+
// Re-scan the changed source files to get current signatures
|
|
36
|
+
const filesToScan = [...new Set(affectedEntries.map(e => e.sourceFile))];
|
|
37
|
+
for (const file of filesToScan) {
|
|
38
|
+
try {
|
|
39
|
+
const scanResult = await scanDirectory(file);
|
|
40
|
+
const currentElements = scanResult.files.flatMap(f => f.elements);
|
|
41
|
+
for (const entry of affectedEntries.filter(e => e.sourceFile === file)) {
|
|
42
|
+
const current = currentElements.find(el => el.name === entry.name && el.kind === entry.kind);
|
|
43
|
+
if (!current) {
|
|
44
|
+
// Element was removed from code
|
|
45
|
+
stale.push({
|
|
46
|
+
entry,
|
|
47
|
+
newSignature: '[REMOVED]',
|
|
48
|
+
newSignatureHash: '',
|
|
49
|
+
});
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const currentHash = hashSignature(current.signature);
|
|
53
|
+
if (currentHash !== entry.signatureHash) {
|
|
54
|
+
stale.push({
|
|
55
|
+
entry,
|
|
56
|
+
newSignature: current.signature,
|
|
57
|
+
newSignatureHash: currentHash,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
console.warn(` Warning: Could not scan ${file}: ${err instanceof Error ? err.message : err}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return stale;
|
|
67
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { APIElement } from '../scanner/types.js';
|
|
2
|
+
import { DocManifest, ManifestEntry } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Hash a signature string for change detection
|
|
5
|
+
*/
|
|
6
|
+
export declare function hashSignature(signature: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Build manifest entries from scanned elements and output path
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildManifestEntries(elements: APIElement[], outputPath: string): ManifestEntry[];
|
|
11
|
+
/**
|
|
12
|
+
* Write manifest to .skrypt/manifest.json relative to output dir
|
|
13
|
+
*/
|
|
14
|
+
export declare function writeManifest(outputPath: string, entries: ManifestEntry[]): void;
|
|
15
|
+
/**
|
|
16
|
+
* Read existing manifest
|
|
17
|
+
*/
|
|
18
|
+
export declare function readManifest(outputPath: string): DocManifest | null;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import { spawnSync } from 'child_process';
|
|
5
|
+
const MANIFEST_DIR = '.skrypt';
|
|
6
|
+
const MANIFEST_FILE = 'manifest.json';
|
|
7
|
+
/**
|
|
8
|
+
* Hash a signature string for change detection
|
|
9
|
+
*/
|
|
10
|
+
export function hashSignature(signature) {
|
|
11
|
+
return createHash('sha256').update(signature).digest('hex').slice(0, 16);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get current git commit SHA (short)
|
|
15
|
+
*/
|
|
16
|
+
function getCommitSha() {
|
|
17
|
+
try {
|
|
18
|
+
const result = spawnSync('git', ['rev-parse', '--short', 'HEAD'], {
|
|
19
|
+
stdio: 'pipe',
|
|
20
|
+
timeout: 3000,
|
|
21
|
+
});
|
|
22
|
+
if (result.status === 0) {
|
|
23
|
+
return result.stdout.toString().trim();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Not a git repo or git not available
|
|
28
|
+
}
|
|
29
|
+
return 'unknown';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build manifest entries from scanned elements and output path
|
|
33
|
+
*/
|
|
34
|
+
export function buildManifestEntries(elements, outputPath) {
|
|
35
|
+
return elements.map(el => ({
|
|
36
|
+
name: el.name,
|
|
37
|
+
kind: el.kind,
|
|
38
|
+
sourceFile: el.filePath,
|
|
39
|
+
signatureHash: hashSignature(el.signature),
|
|
40
|
+
docFile: outputPath,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Write manifest to .skrypt/manifest.json relative to output dir
|
|
45
|
+
*/
|
|
46
|
+
export function writeManifest(outputPath, entries) {
|
|
47
|
+
const manifestDir = join(outputPath, MANIFEST_DIR);
|
|
48
|
+
mkdirSync(manifestDir, { recursive: true });
|
|
49
|
+
const manifest = {
|
|
50
|
+
generatedAt: new Date().toISOString(),
|
|
51
|
+
commitSha: getCommitSha(),
|
|
52
|
+
elements: entries,
|
|
53
|
+
};
|
|
54
|
+
writeFileSync(join(manifestDir, MANIFEST_FILE), JSON.stringify(manifest, null, 2));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Read existing manifest
|
|
58
|
+
*/
|
|
59
|
+
export function readManifest(outputPath) {
|
|
60
|
+
const manifestPath = join(outputPath, MANIFEST_DIR, MANIFEST_FILE);
|
|
61
|
+
if (!existsSync(manifestPath)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const content = readFileSync(manifestPath, 'utf-8');
|
|
66
|
+
return JSON.parse(content);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replace a section in a markdown file that documents a specific element.
|
|
3
|
+
* Finds the heading for the element and replaces content until the next heading of same/higher level.
|
|
4
|
+
*/
|
|
5
|
+
export declare function spliceDocSection(docFilePath: string, elementName: string, newContent: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Remove a section from a doc file for a removed element
|
|
8
|
+
*/
|
|
9
|
+
export declare function removeDocSection(docFilePath: string, elementName: string): boolean;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
2
|
+
/**
|
|
3
|
+
* Replace a section in a markdown file that documents a specific element.
|
|
4
|
+
* Finds the heading for the element and replaces content until the next heading of same/higher level.
|
|
5
|
+
*/
|
|
6
|
+
export function spliceDocSection(docFilePath, elementName, newContent) {
|
|
7
|
+
if (!existsSync(docFilePath)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const content = readFileSync(docFilePath, 'utf-8');
|
|
11
|
+
const lines = content.split('\n');
|
|
12
|
+
// Find the heading for this element
|
|
13
|
+
const headingPattern = new RegExp(`^(#{2,4})\\s+\`${escapeRegex(elementName)}\``);
|
|
14
|
+
let startIdx = -1;
|
|
15
|
+
let headingLevel = 0;
|
|
16
|
+
for (let i = 0; i < lines.length; i++) {
|
|
17
|
+
const match = lines[i].match(headingPattern);
|
|
18
|
+
if (match) {
|
|
19
|
+
startIdx = i;
|
|
20
|
+
headingLevel = match[1].length;
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (startIdx === -1) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
// Find the end of this section (next heading of same or higher level)
|
|
28
|
+
let endIdx = lines.length;
|
|
29
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
30
|
+
const nextHeading = lines[i].match(/^(#{2,4})\s/);
|
|
31
|
+
if (nextHeading && nextHeading[1].length <= headingLevel) {
|
|
32
|
+
endIdx = i;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Splice in new content
|
|
37
|
+
const newLines = newContent.split('\n');
|
|
38
|
+
lines.splice(startIdx, endIdx - startIdx, ...newLines);
|
|
39
|
+
writeFileSync(docFilePath, lines.join('\n'));
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Remove a section from a doc file for a removed element
|
|
44
|
+
*/
|
|
45
|
+
export function removeDocSection(docFilePath, elementName) {
|
|
46
|
+
return spliceDocSection(docFilePath, elementName, '');
|
|
47
|
+
}
|
|
48
|
+
function escapeRegex(str) {
|
|
49
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
50
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry in the doc manifest mapping code elements to docs
|
|
3
|
+
*/
|
|
4
|
+
export interface ManifestEntry {
|
|
5
|
+
name: string;
|
|
6
|
+
kind: 'function' | 'class' | 'method';
|
|
7
|
+
sourceFile: string;
|
|
8
|
+
signatureHash: string;
|
|
9
|
+
docFile: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The full manifest written to .skrypt/manifest.json
|
|
13
|
+
*/
|
|
14
|
+
export interface DocManifest {
|
|
15
|
+
generatedAt: string;
|
|
16
|
+
commitSha: string;
|
|
17
|
+
elements: ManifestEntry[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* An element whose signature has changed since last generation
|
|
21
|
+
*/
|
|
22
|
+
export interface StaleElement {
|
|
23
|
+
entry: ManifestEntry;
|
|
24
|
+
newSignature: string;
|
|
25
|
+
newSignatureHash: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Report for a refresh operation
|
|
29
|
+
*/
|
|
30
|
+
export interface RefreshReport {
|
|
31
|
+
regenerated: number;
|
|
32
|
+
removed: number;
|
|
33
|
+
unchanged: number;
|
|
34
|
+
total: number;
|
|
35
|
+
changedFiles: string[];
|
|
36
|
+
staleElements: StaleElement[];
|
|
37
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { LLMClient } from '../llm/types.js';
|
|
2
|
+
import { ReviewFeedback, ReviewResult } from './types.js';
|
|
3
|
+
export * from './types.js';
|
|
4
|
+
export * from './parser.js';
|
|
5
|
+
/**
|
|
6
|
+
* Apply a single feedback item by re-generating the relevant section
|
|
7
|
+
*/
|
|
8
|
+
export declare function applyFeedback(feedback: ReviewFeedback, client: LLMClient): Promise<ReviewResult>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
export * from './types.js';
|
|
3
|
+
export * from './parser.js';
|
|
4
|
+
/**
|
|
5
|
+
* Apply a single feedback item by re-generating the relevant section
|
|
6
|
+
*/
|
|
7
|
+
export async function applyFeedback(feedback, client) {
|
|
8
|
+
try {
|
|
9
|
+
const content = readFileSync(feedback.filePath, 'utf-8');
|
|
10
|
+
const lines = content.split('\n');
|
|
11
|
+
// Determine the section to rewrite
|
|
12
|
+
let sectionStart = 0;
|
|
13
|
+
let sectionEnd = lines.length;
|
|
14
|
+
if (feedback.lineNumber) {
|
|
15
|
+
// Find the heading before this line
|
|
16
|
+
for (let i = feedback.lineNumber - 1; i >= 0; i--) {
|
|
17
|
+
if (lines[i].match(/^#{2,4}\s/)) {
|
|
18
|
+
sectionStart = i;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Find next heading of same/higher level
|
|
23
|
+
const headingMatch = lines[sectionStart]?.match(/^(#{2,4})\s/);
|
|
24
|
+
const level = headingMatch ? headingMatch[1].length : 2;
|
|
25
|
+
for (let i = sectionStart + 1; i < lines.length; i++) {
|
|
26
|
+
const nextHeading = lines[i].match(/^(#{2,4})\s/);
|
|
27
|
+
if (nextHeading && nextHeading[1].length <= level) {
|
|
28
|
+
sectionEnd = i;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (feedback.elementName) {
|
|
34
|
+
// Find the element heading
|
|
35
|
+
const pattern = new RegExp(`^#{2,4}\\s+\`${escapeRegex(feedback.elementName)}\``);
|
|
36
|
+
for (let i = 0; i < lines.length; i++) {
|
|
37
|
+
if (pattern.test(lines[i])) {
|
|
38
|
+
sectionStart = i;
|
|
39
|
+
const headingMatch = lines[i].match(/^(#{2,4})/);
|
|
40
|
+
const level = headingMatch ? headingMatch[1].length : 2;
|
|
41
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
42
|
+
const next = lines[j].match(/^(#{2,4})\s/);
|
|
43
|
+
if (next && next[1].length <= level) {
|
|
44
|
+
sectionEnd = j;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const section = lines.slice(sectionStart, sectionEnd).join('\n');
|
|
53
|
+
// Ask LLM to rewrite incorporating feedback
|
|
54
|
+
const response = await client.complete({
|
|
55
|
+
messages: [
|
|
56
|
+
{
|
|
57
|
+
role: 'system',
|
|
58
|
+
content: 'You are a technical documentation writer. Rewrite the given documentation section incorporating the provided feedback. Maintain the same heading level and markdown structure. Return ONLY the rewritten section, no explanation.'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
role: 'user',
|
|
62
|
+
content: `## Current documentation section:\n\n${section}\n\n## Feedback to incorporate:\n\n${feedback.feedback}\n\n## Rewrite the section above incorporating this feedback:`
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
temperature: 0,
|
|
66
|
+
maxTokens: 2048,
|
|
67
|
+
});
|
|
68
|
+
const newSection = response.content.trim();
|
|
69
|
+
if (!newSection) {
|
|
70
|
+
return { feedback, applied: false, error: 'LLM returned empty response' };
|
|
71
|
+
}
|
|
72
|
+
lines.splice(sectionStart, sectionEnd - sectionStart, ...newSection.split('\n'));
|
|
73
|
+
// Remove the inline comment if it was an inline feedback
|
|
74
|
+
if (feedback.source === 'inline') {
|
|
75
|
+
const updatedContent = lines.join('\n');
|
|
76
|
+
const cleaned = updatedContent.replace(/<!--\s*(?:FIXME|TODO)\s*:[\s\S]*?-->\s*\n?/g, '');
|
|
77
|
+
writeFileSync(feedback.filePath, cleaned);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
writeFileSync(feedback.filePath, lines.join('\n'));
|
|
81
|
+
}
|
|
82
|
+
return { feedback, applied: true };
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return {
|
|
86
|
+
feedback,
|
|
87
|
+
applied: false,
|
|
88
|
+
error: err instanceof Error ? err.message : String(err),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function escapeRegex(str) {
|
|
93
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
94
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ReviewFeedback } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a feedback markdown file.
|
|
4
|
+
*
|
|
5
|
+
* Format:
|
|
6
|
+
* ## docs/api/users.md
|
|
7
|
+
* - Line 42: Use the SDK client.get() method, not raw fetch()
|
|
8
|
+
* - General: Too formal, make it conversational
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseFeedbackFile(feedbackPath: string): ReviewFeedback[];
|
|
11
|
+
/**
|
|
12
|
+
* Scan markdown files for inline FIXME/TODO comments.
|
|
13
|
+
*
|
|
14
|
+
* Looks for: <!-- FIXME: ... --> or <!-- TODO: ... -->
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseInlineComments(docsDir: string): ReviewFeedback[];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Parse a feedback markdown file.
|
|
5
|
+
*
|
|
6
|
+
* Format:
|
|
7
|
+
* ## docs/api/users.md
|
|
8
|
+
* - Line 42: Use the SDK client.get() method, not raw fetch()
|
|
9
|
+
* - General: Too formal, make it conversational
|
|
10
|
+
*/
|
|
11
|
+
export function parseFeedbackFile(feedbackPath) {
|
|
12
|
+
const content = readFileSync(feedbackPath, 'utf-8');
|
|
13
|
+
const lines = content.split('\n');
|
|
14
|
+
const feedback = [];
|
|
15
|
+
let currentFile = null;
|
|
16
|
+
for (const line of lines) {
|
|
17
|
+
// ## path/to/file.md
|
|
18
|
+
const fileMatch = line.match(/^##\s+(.+\.(?:md|mdx))\s*$/);
|
|
19
|
+
if (fileMatch) {
|
|
20
|
+
currentFile = fileMatch[1].trim();
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (!currentFile)
|
|
24
|
+
continue;
|
|
25
|
+
// - Line 42: feedback text
|
|
26
|
+
const lineMatch = line.match(/^-\s+Line\s+(\d+)\s*:\s*(.+)$/);
|
|
27
|
+
if (lineMatch) {
|
|
28
|
+
feedback.push({
|
|
29
|
+
filePath: currentFile,
|
|
30
|
+
lineNumber: parseInt(lineMatch[1], 10),
|
|
31
|
+
feedback: lineMatch[2].trim(),
|
|
32
|
+
source: 'file',
|
|
33
|
+
});
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
// - "elementName": feedback text
|
|
37
|
+
const elementMatch = line.match(/^-\s+"([^"]+)"\s*:\s*(.+)$/);
|
|
38
|
+
if (elementMatch) {
|
|
39
|
+
feedback.push({
|
|
40
|
+
filePath: currentFile,
|
|
41
|
+
elementName: elementMatch[1].trim(),
|
|
42
|
+
feedback: elementMatch[2].trim(),
|
|
43
|
+
source: 'file',
|
|
44
|
+
});
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// - General: feedback text or - feedback text
|
|
48
|
+
const generalMatch = line.match(/^-\s+(?:General\s*:\s*)?(.+)$/);
|
|
49
|
+
if (generalMatch) {
|
|
50
|
+
feedback.push({
|
|
51
|
+
filePath: currentFile,
|
|
52
|
+
feedback: generalMatch[1].trim(),
|
|
53
|
+
source: 'file',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return feedback;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Scan markdown files for inline FIXME/TODO comments.
|
|
61
|
+
*
|
|
62
|
+
* Looks for: <!-- FIXME: ... --> or <!-- TODO: ... -->
|
|
63
|
+
*/
|
|
64
|
+
export function parseInlineComments(docsDir) {
|
|
65
|
+
const feedback = [];
|
|
66
|
+
function walk(dir) {
|
|
67
|
+
const entries = readdirSync(dir);
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
const fullPath = join(dir, entry);
|
|
70
|
+
const stat = statSync(fullPath);
|
|
71
|
+
if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
|
|
72
|
+
walk(fullPath);
|
|
73
|
+
}
|
|
74
|
+
else if (stat.isFile() && (extname(entry) === '.md' || extname(entry) === '.mdx')) {
|
|
75
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
76
|
+
const lines = content.split('\n');
|
|
77
|
+
for (let i = 0; i < lines.length; i++) {
|
|
78
|
+
const line = lines[i];
|
|
79
|
+
// <!-- FIXME: ... --> or <!-- TODO: ... -->
|
|
80
|
+
const commentMatch = line.match(/<!--\s*(?:FIXME|TODO)\s*:\s*(.*?)\s*-->/);
|
|
81
|
+
if (commentMatch) {
|
|
82
|
+
feedback.push({
|
|
83
|
+
filePath: fullPath,
|
|
84
|
+
lineNumber: i + 1,
|
|
85
|
+
feedback: commentMatch[1].trim(),
|
|
86
|
+
source: 'inline',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
walk(docsDir);
|
|
94
|
+
return feedback;
|
|
95
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A piece of feedback targeting a specific doc file or element
|
|
3
|
+
*/
|
|
4
|
+
export interface ReviewFeedback {
|
|
5
|
+
filePath: string;
|
|
6
|
+
lineNumber?: number;
|
|
7
|
+
elementName?: string;
|
|
8
|
+
feedback: string;
|
|
9
|
+
source: 'file' | 'inline';
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Result of processing a single feedback item
|
|
13
|
+
*/
|
|
14
|
+
export interface ReviewResult {
|
|
15
|
+
feedback: ReviewFeedback;
|
|
16
|
+
applied: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
package/dist/scanner/csharp.d.ts
CHANGED
|
@@ -12,12 +12,8 @@ export declare class CSharpScanner implements Scanner {
|
|
|
12
12
|
private extractMethods;
|
|
13
13
|
private extractInterfaceMethods;
|
|
14
14
|
private findClassRanges;
|
|
15
|
-
private findMatchingBrace;
|
|
16
15
|
private findParentClass;
|
|
17
16
|
private parseCSharpParams;
|
|
18
|
-
private splitParams;
|
|
19
|
-
private getLineNumber;
|
|
20
17
|
private getDocComment;
|
|
21
18
|
private extractGenerics;
|
|
22
|
-
private getSourceContext;
|
|
23
19
|
}
|