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.
Files changed (180) hide show
  1. package/dist/audit/doc-parser.d.ts +5 -0
  2. package/dist/audit/doc-parser.js +106 -0
  3. package/dist/audit/index.d.ts +4 -0
  4. package/dist/audit/index.js +4 -0
  5. package/dist/audit/matcher.d.ts +6 -0
  6. package/dist/audit/matcher.js +94 -0
  7. package/dist/audit/reporter.d.ts +9 -0
  8. package/dist/audit/reporter.js +106 -0
  9. package/dist/audit/types.d.ts +37 -0
  10. package/dist/auth/index.js +6 -4
  11. package/dist/cli.js +12 -2
  12. package/dist/commands/audit.d.ts +2 -0
  13. package/dist/commands/audit.js +59 -0
  14. package/dist/commands/config.d.ts +2 -0
  15. package/dist/commands/config.js +73 -0
  16. package/dist/commands/{generate.d.ts → generate/index.d.ts} +1 -0
  17. package/dist/commands/generate/index.js +393 -0
  18. package/dist/commands/generate/scan.d.ts +41 -0
  19. package/dist/commands/generate/scan.js +256 -0
  20. package/dist/commands/generate/verify.d.ts +14 -0
  21. package/dist/commands/generate/verify.js +122 -0
  22. package/dist/commands/generate/write.d.ts +25 -0
  23. package/dist/commands/generate/write.js +120 -0
  24. package/dist/commands/import.js +4 -1
  25. package/dist/commands/llms-txt.js +6 -4
  26. package/dist/commands/refresh.d.ts +2 -0
  27. package/dist/commands/refresh.js +158 -0
  28. package/dist/commands/review.d.ts +2 -0
  29. package/dist/commands/review.js +110 -0
  30. package/dist/commands/test.js +177 -236
  31. package/dist/commands/watch.js +29 -20
  32. package/dist/config/loader.d.ts +6 -2
  33. package/dist/config/loader.js +39 -3
  34. package/dist/config/types.d.ts +7 -0
  35. package/dist/generator/agents-md.d.ts +25 -0
  36. package/dist/generator/agents-md.js +122 -0
  37. package/dist/generator/generator.js +2 -1
  38. package/dist/generator/index.d.ts +2 -0
  39. package/dist/generator/index.js +2 -0
  40. package/dist/generator/mdx-serializer.d.ts +11 -0
  41. package/dist/generator/mdx-serializer.js +135 -0
  42. package/dist/generator/organizer.d.ts +1 -16
  43. package/dist/generator/organizer.js +0 -38
  44. package/dist/generator/types.d.ts +3 -0
  45. package/dist/generator/writer.js +65 -32
  46. package/dist/github/org-discovery.d.ts +17 -0
  47. package/dist/github/org-discovery.js +93 -0
  48. package/dist/llm/index.d.ts +2 -0
  49. package/dist/llm/index.js +8 -2
  50. package/dist/llm/proxy-client.d.ts +32 -0
  51. package/dist/llm/proxy-client.js +103 -0
  52. package/dist/next-actions/actions.d.ts +2 -0
  53. package/dist/next-actions/actions.js +190 -0
  54. package/dist/next-actions/index.d.ts +6 -0
  55. package/dist/next-actions/index.js +39 -0
  56. package/dist/next-actions/setup.d.ts +2 -0
  57. package/dist/next-actions/setup.js +72 -0
  58. package/dist/next-actions/state.d.ts +7 -0
  59. package/dist/next-actions/state.js +68 -0
  60. package/dist/next-actions/suggest.d.ts +3 -0
  61. package/dist/next-actions/suggest.js +47 -0
  62. package/dist/next-actions/types.d.ts +26 -0
  63. package/dist/refresh/differ.d.ts +9 -0
  64. package/dist/refresh/differ.js +67 -0
  65. package/dist/refresh/index.d.ts +4 -0
  66. package/dist/refresh/index.js +4 -0
  67. package/dist/refresh/manifest.d.ts +18 -0
  68. package/dist/refresh/manifest.js +71 -0
  69. package/dist/refresh/splicer.d.ts +9 -0
  70. package/dist/refresh/splicer.js +50 -0
  71. package/dist/refresh/types.d.ts +37 -0
  72. package/dist/review/index.d.ts +8 -0
  73. package/dist/review/index.js +94 -0
  74. package/dist/review/parser.d.ts +16 -0
  75. package/dist/review/parser.js +95 -0
  76. package/dist/review/types.d.ts +18 -0
  77. package/dist/scanner/csharp.d.ts +0 -4
  78. package/dist/scanner/csharp.js +9 -49
  79. package/dist/scanner/go.d.ts +0 -3
  80. package/dist/scanner/go.js +8 -35
  81. package/dist/scanner/java.d.ts +0 -4
  82. package/dist/scanner/java.js +9 -49
  83. package/dist/scanner/kotlin.d.ts +0 -3
  84. package/dist/scanner/kotlin.js +6 -33
  85. package/dist/scanner/php.d.ts +0 -10
  86. package/dist/scanner/php.js +11 -55
  87. package/dist/scanner/ruby.d.ts +0 -3
  88. package/dist/scanner/ruby.js +8 -38
  89. package/dist/scanner/rust.d.ts +0 -3
  90. package/dist/scanner/rust.js +10 -37
  91. package/dist/scanner/swift.d.ts +0 -3
  92. package/dist/scanner/swift.js +8 -35
  93. package/dist/scanner/types.d.ts +2 -0
  94. package/dist/scanner/utils.d.ts +41 -0
  95. package/dist/scanner/utils.js +97 -0
  96. package/dist/structure/index.d.ts +19 -0
  97. package/dist/structure/index.js +92 -0
  98. package/dist/structure/planner.d.ts +8 -0
  99. package/dist/structure/planner.js +180 -0
  100. package/dist/structure/topology.d.ts +16 -0
  101. package/dist/structure/topology.js +49 -0
  102. package/dist/structure/types.d.ts +26 -0
  103. package/dist/template/docs.json +5 -2
  104. package/dist/template/next.config.mjs +31 -0
  105. package/dist/template/package.json +5 -3
  106. package/dist/template/src/app/layout.tsx +13 -13
  107. package/dist/template/src/app/llms-full.md/route.ts +29 -0
  108. package/dist/template/src/app/llms.txt/route.ts +29 -0
  109. package/dist/template/src/app/md/[...slug]/route.ts +174 -0
  110. package/dist/template/src/app/reference/route.ts +22 -18
  111. package/dist/template/src/app/sitemap.ts +1 -1
  112. package/dist/template/src/components/ai-chat-impl.tsx +206 -0
  113. package/dist/template/src/components/ai-chat.tsx +20 -193
  114. package/dist/template/src/components/mdx/index.tsx +27 -4
  115. package/dist/template/src/lib/fonts.ts +135 -0
  116. package/dist/template/src/middleware.ts +101 -0
  117. package/dist/template/src/styles/globals.css +28 -20
  118. package/dist/testing/comparator.d.ts +7 -0
  119. package/dist/testing/comparator.js +77 -0
  120. package/dist/testing/docker.d.ts +21 -0
  121. package/dist/testing/docker.js +234 -0
  122. package/dist/testing/env.d.ts +16 -0
  123. package/dist/testing/env.js +58 -0
  124. package/dist/testing/extractor.d.ts +9 -0
  125. package/dist/testing/extractor.js +195 -0
  126. package/dist/testing/index.d.ts +6 -0
  127. package/dist/testing/index.js +6 -0
  128. package/dist/testing/runner.d.ts +5 -0
  129. package/dist/testing/runner.js +225 -0
  130. package/dist/testing/types.d.ts +58 -0
  131. package/dist/utils/files.d.ts +0 -8
  132. package/dist/utils/files.js +0 -33
  133. package/package.json +1 -1
  134. package/dist/autofix/autofix.test.js +0 -487
  135. package/dist/commands/generate.js +0 -445
  136. package/dist/generator/generator.test.js +0 -259
  137. package/dist/generator/writer.test.js +0 -411
  138. package/dist/llm/llm.manual-test.js +0 -112
  139. package/dist/llm/llm.mock-test.d.ts +0 -4
  140. package/dist/llm/llm.mock-test.js +0 -79
  141. package/dist/plugins/index.d.ts +0 -47
  142. package/dist/plugins/index.js +0 -181
  143. package/dist/scanner/content-type.test.js +0 -231
  144. package/dist/scanner/integration.test.d.ts +0 -4
  145. package/dist/scanner/integration.test.js +0 -180
  146. package/dist/scanner/scanner.test.js +0 -210
  147. package/dist/scanner/typescript.manual-test.d.ts +0 -1
  148. package/dist/scanner/typescript.manual-test.js +0 -112
  149. package/dist/template/src/app/docs/auth/page.mdx +0 -589
  150. package/dist/template/src/app/docs/autofix/page.mdx +0 -624
  151. package/dist/template/src/app/docs/cli/page.mdx +0 -217
  152. package/dist/template/src/app/docs/config/page.mdx +0 -428
  153. package/dist/template/src/app/docs/configuration/page.mdx +0 -86
  154. package/dist/template/src/app/docs/deployment/page.mdx +0 -112
  155. package/dist/template/src/app/docs/generator/generator.md +0 -504
  156. package/dist/template/src/app/docs/generator/organizer.md +0 -779
  157. package/dist/template/src/app/docs/generator/page.mdx +0 -613
  158. package/dist/template/src/app/docs/github/page.mdx +0 -502
  159. package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
  160. package/dist/template/src/app/docs/llm/index.md +0 -471
  161. package/dist/template/src/app/docs/llm/page.mdx +0 -428
  162. package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
  163. package/dist/template/src/app/docs/pro/page.mdx +0 -121
  164. package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
  165. package/dist/template/src/app/docs/scanner/content-type.md +0 -599
  166. package/dist/template/src/app/docs/scanner/index.md +0 -212
  167. package/dist/template/src/app/docs/scanner/page.mdx +0 -307
  168. package/dist/template/src/app/docs/scanner/python.md +0 -469
  169. package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
  170. package/dist/template/src/app/docs/scanner/rust.md +0 -325
  171. package/dist/template/src/app/docs/scanner/typescript.md +0 -201
  172. package/dist/template/src/app/icon.tsx +0 -29
  173. package/dist/utils/validation.d.ts +0 -1
  174. package/dist/utils/validation.js +0 -12
  175. /package/dist/{autofix/autofix.test.d.ts → audit/types.js} +0 -0
  176. /package/dist/{generator/generator.test.d.ts → next-actions/types.js} +0 -0
  177. /package/dist/{generator/writer.test.d.ts → refresh/types.js} +0 -0
  178. /package/dist/{llm/llm.manual-test.d.ts → review/types.js} +0 -0
  179. /package/dist/{scanner/content-type.test.d.ts → structure/types.js} +0 -0
  180. /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,4 @@
1
+ export * from './types.js';
2
+ export * from './manifest.js';
3
+ export * from './differ.js';
4
+ export * from './splicer.js';
@@ -0,0 +1,4 @@
1
+ export * from './types.js';
2
+ export * from './manifest.js';
3
+ export * from './differ.js';
4
+ export * from './splicer.js';
@@ -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
+ }
@@ -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
  }