threadlines 0.1.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/README.md +31 -0
- package/bin/threadline +4 -0
- package/dist/api/client.js +36 -0
- package/dist/commands/check.js +157 -0
- package/dist/git/diff.js +41 -0
- package/dist/index.js +17 -0
- package/dist/types/expert.js +2 -0
- package/dist/validators/experts.js +133 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# threadlines
|
|
2
|
+
|
|
3
|
+
Threadline CLI - AI-powered linter based on your natural language documentation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g threadlines
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use with npx:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx threadlines check
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
threadlines check
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
- `THREADLINE_API_URL` - Server URL (default: http://localhost:3000)
|
|
26
|
+
- `OPENAI_API_KEY` - Your OpenAI API key (required)
|
|
27
|
+
|
|
28
|
+
## Expert Files
|
|
29
|
+
|
|
30
|
+
Create a `/threadlines` folder in your repository with markdown files. See [Threadline Format](../../docs/EXPERT_FORMAT.md) for details.
|
|
31
|
+
|
package/bin/threadline
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ReviewAPIClient = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
class ReviewAPIClient {
|
|
9
|
+
constructor(baseURL) {
|
|
10
|
+
this.client = axios_1.default.create({
|
|
11
|
+
baseURL,
|
|
12
|
+
timeout: 60000, // 60s timeout for entire request
|
|
13
|
+
headers: {
|
|
14
|
+
'Content-Type': 'application/json'
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async review(request) {
|
|
19
|
+
try {
|
|
20
|
+
const response = await this.client.post('/api/threadline-check', request);
|
|
21
|
+
return response.data;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error.response) {
|
|
25
|
+
throw new Error(`API error: ${error.response.status} - ${error.response.data?.message || error.message}`);
|
|
26
|
+
}
|
|
27
|
+
else if (error.request) {
|
|
28
|
+
throw new Error(`Network error: Could not reach Threadline server at ${this.client.defaults.baseURL}`);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
throw new Error(`Request error: ${error.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.ReviewAPIClient = ReviewAPIClient;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.checkCommand = checkCommand;
|
|
40
|
+
const experts_1 = require("../validators/experts");
|
|
41
|
+
const diff_1 = require("../git/diff");
|
|
42
|
+
const client_1 = require("../api/client");
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
46
|
+
async function checkCommand(options) {
|
|
47
|
+
const repoRoot = process.cwd();
|
|
48
|
+
console.log(chalk_1.default.blue('🔍 Threadline: Checking code against your threadlines...\n'));
|
|
49
|
+
try {
|
|
50
|
+
// 1. Find and validate threadlines
|
|
51
|
+
console.log(chalk_1.default.gray('📋 Finding threadlines...'));
|
|
52
|
+
const threadlines = await (0, experts_1.findThreadlines)(repoRoot);
|
|
53
|
+
console.log(chalk_1.default.green(`✓ Found ${threadlines.length} threadline(s)\n`));
|
|
54
|
+
if (threadlines.length === 0) {
|
|
55
|
+
console.log(chalk_1.default.yellow('⚠️ No valid threadlines found. Add threadline files to /threadlines folder.'));
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
// 2. Get git diff
|
|
59
|
+
console.log(chalk_1.default.gray('📝 Collecting git changes...'));
|
|
60
|
+
const gitDiff = await (0, diff_1.getGitDiff)(repoRoot);
|
|
61
|
+
if (gitDiff.changedFiles.length === 0) {
|
|
62
|
+
console.log(chalk_1.default.yellow('⚠️ No changes detected. Make some code changes and try again.'));
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
console.log(chalk_1.default.green(`✓ Found ${gitDiff.changedFiles.length} changed file(s)\n`));
|
|
66
|
+
// 3. Read context files for each threadline
|
|
67
|
+
const threadlinesWithContext = threadlines.map(threadline => {
|
|
68
|
+
const contextContent = {};
|
|
69
|
+
if (threadline.contextFiles) {
|
|
70
|
+
for (const contextFile of threadline.contextFiles) {
|
|
71
|
+
const fullPath = path.join(repoRoot, contextFile);
|
|
72
|
+
if (fs.existsSync(fullPath)) {
|
|
73
|
+
contextContent[contextFile] = fs.readFileSync(fullPath, 'utf-8');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
id: threadline.id,
|
|
79
|
+
version: threadline.version,
|
|
80
|
+
patterns: threadline.patterns,
|
|
81
|
+
content: threadline.content,
|
|
82
|
+
contextFiles: threadline.contextFiles,
|
|
83
|
+
contextContent
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
// 4. Get API URL and key
|
|
87
|
+
const apiUrl = options.apiUrl || process.env.THREADLINE_API_URL || 'http://localhost:3000';
|
|
88
|
+
const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
|
|
89
|
+
if (!apiKey) {
|
|
90
|
+
throw new Error('OpenAI API key required. Set OPENAI_API_KEY environment variable.');
|
|
91
|
+
}
|
|
92
|
+
// 5. Call review API
|
|
93
|
+
console.log(chalk_1.default.gray('🤖 Running threadline checks...'));
|
|
94
|
+
const client = new client_1.ReviewAPIClient(apiUrl);
|
|
95
|
+
const response = await client.review({
|
|
96
|
+
threadlines: threadlinesWithContext,
|
|
97
|
+
diff: gitDiff.diff,
|
|
98
|
+
files: gitDiff.changedFiles,
|
|
99
|
+
apiKey
|
|
100
|
+
});
|
|
101
|
+
// 6. Display results
|
|
102
|
+
displayResults(response);
|
|
103
|
+
// Exit with appropriate code
|
|
104
|
+
const hasAttention = response.results.some(r => r.status === 'attention');
|
|
105
|
+
process.exit(hasAttention ? 1 : 0);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.error(chalk_1.default.red(`\n❌ Error: ${error.message}`));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function displayResults(response) {
|
|
113
|
+
const { results, metadata } = response;
|
|
114
|
+
console.log('\n' + chalk_1.default.bold('Results:\n'));
|
|
115
|
+
console.log(chalk_1.default.gray(`${metadata.totalThreadlines} threadlines checked`));
|
|
116
|
+
const notRelevant = results.filter((r) => r.status === 'not_relevant').length;
|
|
117
|
+
const compliant = results.filter((r) => r.status === 'compliant').length;
|
|
118
|
+
const attention = results.filter((r) => r.status === 'attention').length;
|
|
119
|
+
if (notRelevant > 0) {
|
|
120
|
+
console.log(chalk_1.default.gray(` ${notRelevant} not relevant`));
|
|
121
|
+
}
|
|
122
|
+
if (compliant > 0) {
|
|
123
|
+
console.log(chalk_1.default.green(` ${compliant} compliant`));
|
|
124
|
+
}
|
|
125
|
+
if (attention > 0) {
|
|
126
|
+
console.log(chalk_1.default.yellow(` ${attention} attention`));
|
|
127
|
+
}
|
|
128
|
+
if (metadata.timedOut > 0) {
|
|
129
|
+
console.log(chalk_1.default.yellow(` ${metadata.timedOut} timed out`));
|
|
130
|
+
}
|
|
131
|
+
if (metadata.errors > 0) {
|
|
132
|
+
console.log(chalk_1.default.red(` ${metadata.errors} errors`));
|
|
133
|
+
}
|
|
134
|
+
console.log('');
|
|
135
|
+
// Show attention items
|
|
136
|
+
const attentionItems = results.filter((r) => r.status === 'attention');
|
|
137
|
+
if (attentionItems.length > 0) {
|
|
138
|
+
for (const item of attentionItems) {
|
|
139
|
+
console.log(chalk_1.default.yellow(`⚠️ ${item.expertId}`));
|
|
140
|
+
if (item.fileReferences && item.fileReferences.length > 0) {
|
|
141
|
+
for (const fileRef of item.fileReferences) {
|
|
142
|
+
const lineRef = item.lineReferences?.[item.fileReferences.indexOf(fileRef)];
|
|
143
|
+
const lineStr = lineRef ? `:${lineRef}` : '';
|
|
144
|
+
console.log(chalk_1.default.gray(` ${fileRef}${lineStr} - ${item.reasoning || 'Needs attention'}`));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (item.reasoning) {
|
|
148
|
+
console.log(chalk_1.default.gray(` ${item.reasoning}`));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
console.log('');
|
|
152
|
+
}
|
|
153
|
+
// Show compliant items (optional, can be verbose)
|
|
154
|
+
if (attentionItems.length === 0 && compliant > 0) {
|
|
155
|
+
console.log(chalk_1.default.green('✓ All threadlines passed!\n'));
|
|
156
|
+
}
|
|
157
|
+
}
|
package/dist/git/diff.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getGitDiff = getGitDiff;
|
|
7
|
+
const simple_git_1 = __importDefault(require("simple-git"));
|
|
8
|
+
async function getGitDiff(repoRoot) {
|
|
9
|
+
const git = (0, simple_git_1.default)(repoRoot);
|
|
10
|
+
// Check if we're in a git repo
|
|
11
|
+
const isRepo = await git.checkIsRepo();
|
|
12
|
+
if (!isRepo) {
|
|
13
|
+
throw new Error('Not a git repository. Threadline requires a git repository.');
|
|
14
|
+
}
|
|
15
|
+
// Get diff (staged changes, or unstaged if no staged)
|
|
16
|
+
const status = await git.status();
|
|
17
|
+
let diff;
|
|
18
|
+
if (status.staged.length > 0) {
|
|
19
|
+
// Use staged changes
|
|
20
|
+
diff = await git.diff(['--cached']);
|
|
21
|
+
}
|
|
22
|
+
else if (status.files.length > 0) {
|
|
23
|
+
// Use unstaged changes
|
|
24
|
+
diff = await git.diff();
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
// No changes
|
|
28
|
+
return {
|
|
29
|
+
diff: '',
|
|
30
|
+
changedFiles: []
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Get list of changed files
|
|
34
|
+
const changedFiles = status.files
|
|
35
|
+
.filter(f => f.working_dir !== ' ' || f.index !== ' ')
|
|
36
|
+
.map(f => f.path);
|
|
37
|
+
return {
|
|
38
|
+
diff: diff || '',
|
|
39
|
+
changedFiles
|
|
40
|
+
};
|
|
41
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const check_1 = require("./commands/check");
|
|
6
|
+
const program = new commander_1.Command();
|
|
7
|
+
program
|
|
8
|
+
.name('threadline')
|
|
9
|
+
.description('AI-powered linter based on your natural language documentation')
|
|
10
|
+
.version('0.1.0');
|
|
11
|
+
program
|
|
12
|
+
.command('check')
|
|
13
|
+
.description('Check code against your experts')
|
|
14
|
+
.option('--api-url <url>', 'Threadline server URL', process.env.THREADLINE_API_URL || 'http://localhost:3000')
|
|
15
|
+
.option('--api-key <key>', 'OpenAI API key', process.env.OPENAI_API_KEY)
|
|
16
|
+
.action(check_1.checkCommand);
|
|
17
|
+
program.parse();
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.findThreadlines = findThreadlines;
|
|
37
|
+
exports.validateThreadline = validateThreadline;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const yaml = __importStar(require("js-yaml"));
|
|
41
|
+
const REQUIRED_FIELDS = ['id', 'version', 'patterns'];
|
|
42
|
+
async function findThreadlines(repoRoot) {
|
|
43
|
+
const expertsDir = path.join(repoRoot, 'threadlines');
|
|
44
|
+
if (!fs.existsSync(expertsDir)) {
|
|
45
|
+
throw new Error('No /threadlines folder found. Create a /threadlines folder with your threadline markdown files.');
|
|
46
|
+
}
|
|
47
|
+
const files = fs.readdirSync(expertsDir);
|
|
48
|
+
const expertFiles = files.filter(f => f.endsWith('.md'));
|
|
49
|
+
if (expertFiles.length === 0) {
|
|
50
|
+
throw new Error('No threadline files found in /threadlines folder. Add .md files with threadline definitions.');
|
|
51
|
+
}
|
|
52
|
+
const threadlines = [];
|
|
53
|
+
for (const file of expertFiles) {
|
|
54
|
+
const result = await validateThreadline(path.join(expertsDir, file), repoRoot);
|
|
55
|
+
if (result.valid && result.threadline) {
|
|
56
|
+
threadlines.push(result.threadline);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.warn(`⚠️ Skipping ${file}: ${result.errors?.join(', ')}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return threadlines;
|
|
63
|
+
}
|
|
64
|
+
async function validateThreadline(filePath, repoRoot) {
|
|
65
|
+
try {
|
|
66
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
67
|
+
// Parse YAML frontmatter
|
|
68
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
69
|
+
if (!frontmatterMatch) {
|
|
70
|
+
return {
|
|
71
|
+
valid: false,
|
|
72
|
+
errors: ['Missing YAML frontmatter. Threadline files must start with ---']
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const frontmatter = yaml.load(frontmatterMatch[1]);
|
|
76
|
+
const body = frontmatterMatch[2].trim();
|
|
77
|
+
// Validate required fields
|
|
78
|
+
const errors = [];
|
|
79
|
+
for (const field of REQUIRED_FIELDS) {
|
|
80
|
+
if (!frontmatter[field]) {
|
|
81
|
+
errors.push(`Missing required field: ${field}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Validate patterns
|
|
85
|
+
if (frontmatter.patterns && !Array.isArray(frontmatter.patterns)) {
|
|
86
|
+
errors.push('patterns must be an array');
|
|
87
|
+
}
|
|
88
|
+
if (frontmatter.patterns && frontmatter.patterns.length === 0) {
|
|
89
|
+
errors.push('patterns array cannot be empty');
|
|
90
|
+
}
|
|
91
|
+
// Validate context_files if present
|
|
92
|
+
if (frontmatter.context_files) {
|
|
93
|
+
if (!Array.isArray(frontmatter.context_files)) {
|
|
94
|
+
errors.push('context_files must be an array');
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Check if context files exist
|
|
98
|
+
for (const contextFile of frontmatter.context_files) {
|
|
99
|
+
const fullPath = path.join(repoRoot, contextFile);
|
|
100
|
+
if (!fs.existsSync(fullPath)) {
|
|
101
|
+
errors.push(`Context file not found: ${contextFile}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Validate body has content
|
|
107
|
+
if (!body || body.length === 0) {
|
|
108
|
+
errors.push('Threadline body cannot be empty');
|
|
109
|
+
}
|
|
110
|
+
// Validate version format (basic semver check)
|
|
111
|
+
if (frontmatter.version && !/^\d+\.\d+\.\d+/.test(frontmatter.version)) {
|
|
112
|
+
errors.push('version must be in semver format (e.g., 1.0.0)');
|
|
113
|
+
}
|
|
114
|
+
if (errors.length > 0) {
|
|
115
|
+
return { valid: false, errors };
|
|
116
|
+
}
|
|
117
|
+
const threadline = {
|
|
118
|
+
id: frontmatter.id,
|
|
119
|
+
version: frontmatter.version,
|
|
120
|
+
patterns: frontmatter.patterns,
|
|
121
|
+
contextFiles: frontmatter.context_files || [],
|
|
122
|
+
content: body,
|
|
123
|
+
filePath: path.relative(repoRoot, filePath)
|
|
124
|
+
};
|
|
125
|
+
return { valid: true, threadline };
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
return {
|
|
129
|
+
valid: false,
|
|
130
|
+
errors: [`Failed to parse threadline file: ${error.message}`]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "threadlines",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Threadline CLI - AI-powered linter based on your natural language documentation",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"threadline": "./bin/threadline"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"bin",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"code-quality",
|
|
16
|
+
"linter",
|
|
17
|
+
"ai",
|
|
18
|
+
"code-review",
|
|
19
|
+
"threadline",
|
|
20
|
+
"code-standards"
|
|
21
|
+
],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/ngrootscholten/threadline.git",
|
|
27
|
+
"directory": "packages/cli"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"dev": "tsc --watch",
|
|
32
|
+
"start": "node dist/index.js",
|
|
33
|
+
"prepublishOnly": "npm run build"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"commander": "^12.1.0",
|
|
37
|
+
"simple-git": "^3.27.0",
|
|
38
|
+
"axios": "^1.7.9",
|
|
39
|
+
"chalk": "^4.1.2",
|
|
40
|
+
"js-yaml": "^4.1.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.10.2",
|
|
44
|
+
"@types/js-yaml": "^4.0.9",
|
|
45
|
+
"typescript": "^5.7.2"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|