threadlines 0.1.4 → 0.1.6
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 +79 -1
- package/dist/commands/check.js +32 -2
- package/dist/commands/init.js +9 -0
- package/dist/index.js +45 -0
- package/dist/utils/config.js +63 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
Threadline CLI - AI-powered linter based on your natural language documentation.
|
|
4
4
|
|
|
5
|
+
## Why Threadline?
|
|
6
|
+
|
|
7
|
+
Getting teams to follow consistent quality standards is **hard**. Really hard.
|
|
8
|
+
|
|
9
|
+
- **Documentation** → Nobody reads it. Or it's outdated before you finish writing it.
|
|
10
|
+
- **Linting** → Catches syntax errors, but misses nuanced stuff.
|
|
11
|
+
- **AI Code Reviewers** → Powerful, but you can't trust them. Did they actually check what you care about? Can you customize them with your team's specific rules?
|
|
12
|
+
|
|
13
|
+
**Threadline solves this** by running **separate, parallel, highly focused AI-powered reviews** - each focused on a single, specific concern. Your coding standards live in your repository as markdown files, version-controlled and always in sync with your codebase. Each threadline gets its own dedicated AI check, ensuring focused attention on what matters to your team.
|
|
14
|
+
|
|
15
|
+
### What Makes Threadline Different?
|
|
16
|
+
|
|
17
|
+
- **🎯 Focused Reviews** - Instead of one AI trying to check everything, Threadline runs multiple specialized AI reviewers in parallel. Each threadline focuses on one thing and does it well.
|
|
18
|
+
|
|
19
|
+
- **📝 Documentation That Lives With Your Code** - Your coding standards live in your repo, in a `/threadlines` folder. They're version-controlled, reviewable, and always in sync with your codebase.
|
|
20
|
+
|
|
21
|
+
- **🔍 Fully Auditable** - Every AI review decision is logged and traceable. You can see exactly what was checked, why it passed or failed, and have confidence in the results.
|
|
22
|
+
|
|
23
|
+
- **⚡ Fast & Parallel** - Multiple threadlines run simultaneously, so you get comprehensive feedback in seconds, not minutes.
|
|
24
|
+
|
|
5
25
|
## Installation
|
|
6
26
|
|
|
7
27
|
```bash
|
|
@@ -14,14 +34,72 @@ Or use with npx:
|
|
|
14
34
|
npx threadlines check
|
|
15
35
|
```
|
|
16
36
|
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### 1. Initialize Your First Threadline
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx threadlines init
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This command:
|
|
46
|
+
- Creates a `/threadlines` directory in your project root
|
|
47
|
+
- Generates `threadlines/example.md` with a template threadline
|
|
48
|
+
- Provides instructions for setting up your API key
|
|
49
|
+
|
|
50
|
+
### 2. Configure API Key
|
|
51
|
+
|
|
52
|
+
Create a `.env.local` file in your project root:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
THREADLINE_API_KEY=your-api-key-here
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Important:** Make sure `.env.local` is in your `.gitignore` file!
|
|
59
|
+
|
|
60
|
+
For CI/CD environments, set `THREADLINE_API_KEY` as an environment variable in your platform settings.
|
|
61
|
+
|
|
62
|
+
### 3. Edit Your Threadline
|
|
63
|
+
|
|
64
|
+
Edit `threadlines/example.md` with your coding standards, then rename it to something descriptive (e.g., `error-handling.md`).
|
|
65
|
+
|
|
66
|
+
### 4. Run Checks
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx threadlines check
|
|
70
|
+
```
|
|
71
|
+
|
|
17
72
|
## Usage
|
|
18
73
|
|
|
74
|
+
### Initialize Threadline Template
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
threadlines init
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Creates a template threadline file to get you started. The command will:
|
|
81
|
+
- Create the `/threadlines` directory if it doesn't exist
|
|
82
|
+
- Generate `threadlines/example.md` with boilerplate content
|
|
83
|
+
- Display instructions for API key configuration
|
|
84
|
+
|
|
85
|
+
### Check Code Against Threadlines
|
|
86
|
+
|
|
19
87
|
```bash
|
|
20
88
|
threadlines check
|
|
21
89
|
```
|
|
22
90
|
|
|
91
|
+
Analyzes your git changes against all threadlines in the `/threadlines` directory.
|
|
92
|
+
|
|
93
|
+
**Options:**
|
|
94
|
+
- `--api-url <url>` - Override the server URL (default: http://localhost:3000)
|
|
95
|
+
|
|
23
96
|
## Configuration
|
|
24
|
-
|
|
97
|
+
|
|
98
|
+
### Environment Variables
|
|
99
|
+
|
|
100
|
+
- `THREADLINE_API_KEY` - **Required.** Your Threadline API key for authentication
|
|
101
|
+
- Can be set in `.env.local` file (recommended for local development)
|
|
102
|
+
- Or as an environment variable (required for CI/CD)
|
|
25
103
|
- `THREADLINE_API_URL` - Server URL (default: http://localhost:3000)
|
|
26
104
|
- Can also be set with `--api-url` flag: `npx threadlines check --api-url http://your-server.com`
|
|
27
105
|
|
package/dist/commands/check.js
CHANGED
|
@@ -40,12 +40,26 @@ exports.checkCommand = checkCommand;
|
|
|
40
40
|
const experts_1 = require("../validators/experts");
|
|
41
41
|
const diff_1 = require("../git/diff");
|
|
42
42
|
const client_1 = require("../api/client");
|
|
43
|
+
const config_1 = require("../utils/config");
|
|
43
44
|
const fs = __importStar(require("fs"));
|
|
44
45
|
const path = __importStar(require("path"));
|
|
45
46
|
const chalk_1 = __importDefault(require("chalk"));
|
|
46
47
|
async function checkCommand(options) {
|
|
47
48
|
const repoRoot = process.cwd();
|
|
48
49
|
console.log(chalk_1.default.blue('🔍 Threadline: Checking code against your threadlines...\n'));
|
|
50
|
+
// Get and validate API key
|
|
51
|
+
const apiKey = (0, config_1.getThreadlineApiKey)();
|
|
52
|
+
if (!apiKey) {
|
|
53
|
+
console.error(chalk_1.default.red('❌ Error: THREADLINE_API_KEY is required'));
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(chalk_1.default.yellow('To fix this:'));
|
|
56
|
+
console.log(chalk_1.default.white(' 1. Create a .env.local file in your project root'));
|
|
57
|
+
console.log(chalk_1.default.gray(' 2. Add: THREADLINE_API_KEY=your-api-key-here'));
|
|
58
|
+
console.log(chalk_1.default.gray(' 3. Make sure .env.local is in your .gitignore'));
|
|
59
|
+
console.log('');
|
|
60
|
+
console.log(chalk_1.default.gray('For CI/CD: Set THREADLINE_API_KEY as an environment variable in your platform settings.'));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
49
63
|
try {
|
|
50
64
|
// 1. Find and validate threadlines
|
|
51
65
|
console.log(chalk_1.default.gray('📋 Finding threadlines...'));
|
|
@@ -63,6 +77,17 @@ async function checkCommand(options) {
|
|
|
63
77
|
console.log(chalk_1.default.yellow('⚠️ No changes detected. Make some code changes and try again.'));
|
|
64
78
|
process.exit(0);
|
|
65
79
|
}
|
|
80
|
+
// Check for zero diff (files changed but no actual code changes)
|
|
81
|
+
if (!gitDiff.diff || gitDiff.diff.trim() === '') {
|
|
82
|
+
console.log(chalk_1.default.blue('ℹ️ No code changes detected. Diff contains zero lines added or removed.'));
|
|
83
|
+
console.log(chalk_1.default.gray(` ${gitDiff.changedFiles.length} file(s) changed but no content modifications detected.`));
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log(chalk_1.default.bold('Results:\n'));
|
|
86
|
+
console.log(chalk_1.default.gray(`${threadlines.length} threadlines checked`));
|
|
87
|
+
console.log(chalk_1.default.gray(` ${threadlines.length} not relevant`));
|
|
88
|
+
console.log('');
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
66
91
|
console.log(chalk_1.default.green(`✓ Found ${gitDiff.changedFiles.length} changed file(s)\n`));
|
|
67
92
|
// 3. Read context files for each threadline
|
|
68
93
|
const threadlinesWithContext = threadlines.map(threadline => {
|
|
@@ -92,7 +117,8 @@ async function checkCommand(options) {
|
|
|
92
117
|
const response = await client.review({
|
|
93
118
|
threadlines: threadlinesWithContext,
|
|
94
119
|
diff: gitDiff.diff,
|
|
95
|
-
files: gitDiff.changedFiles
|
|
120
|
+
files: gitDiff.changedFiles,
|
|
121
|
+
apiKey
|
|
96
122
|
});
|
|
97
123
|
// 6. Display results
|
|
98
124
|
displayResults(response);
|
|
@@ -106,7 +132,11 @@ async function checkCommand(options) {
|
|
|
106
132
|
}
|
|
107
133
|
}
|
|
108
134
|
function displayResults(response) {
|
|
109
|
-
const { results, metadata } = response;
|
|
135
|
+
const { results, metadata, message } = response;
|
|
136
|
+
// Display informational message if present (e.g., zero diffs)
|
|
137
|
+
if (message) {
|
|
138
|
+
console.log('\n' + chalk_1.default.blue('ℹ️ ' + message));
|
|
139
|
+
}
|
|
110
140
|
console.log('\n' + chalk_1.default.bold('Results:\n'));
|
|
111
141
|
console.log(chalk_1.default.gray(`${metadata.totalThreadlines} threadlines checked`));
|
|
112
142
|
const notRelevant = results.filter((r) => r.status === 'not_relevant').length;
|
package/dist/commands/init.js
CHANGED
|
@@ -94,6 +94,15 @@ async function initCommand() {
|
|
|
94
94
|
console.log(chalk_1.default.blue('Next steps:'));
|
|
95
95
|
console.log(chalk_1.default.gray(' 1. Edit threadlines/example.md with your coding standards'));
|
|
96
96
|
console.log(chalk_1.default.gray(' 2. Rename it to something descriptive (e.g., error-handling.md)'));
|
|
97
|
+
console.log('');
|
|
98
|
+
console.log(chalk_1.default.yellow('⚠️ IMPORTANT: API Key Setup Required'));
|
|
99
|
+
console.log(chalk_1.default.white(' To use threadlines check, you need a THREADLINE_API_KEY.'));
|
|
100
|
+
console.log('');
|
|
101
|
+
console.log(chalk_1.default.white(' Create a .env.local file in your project root with:'));
|
|
102
|
+
console.log(chalk_1.default.gray(' THREADLINE_API_KEY=your-api-key-here'));
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log(chalk_1.default.white(' Make sure .env.local is in your .gitignore file!'));
|
|
105
|
+
console.log('');
|
|
97
106
|
console.log(chalk_1.default.gray(' 3. Run: npx threadlines check'));
|
|
98
107
|
}
|
|
99
108
|
catch (error) {
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
3
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
// Load .env.local from project root before anything else
|
|
41
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const projectRoot = process.cwd();
|
|
45
|
+
const envLocalPath = path.join(projectRoot, '.env.local');
|
|
46
|
+
if (fs.existsSync(envLocalPath)) {
|
|
47
|
+
dotenv_1.default.config({ path: envLocalPath });
|
|
48
|
+
}
|
|
4
49
|
const commander_1 = require("commander");
|
|
5
50
|
const check_1 = require("./commands/check");
|
|
6
51
|
const init_1 = require("./commands/init");
|
|
@@ -0,0 +1,63 @@
|
|
|
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.getThreadlineApiKey = getThreadlineApiKey;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
43
|
+
/**
|
|
44
|
+
* Loads environment variables from .env.local file in the project root
|
|
45
|
+
* (where the user runs the command, not the CLI package directory)
|
|
46
|
+
*/
|
|
47
|
+
function loadEnvLocal() {
|
|
48
|
+
const projectRoot = process.cwd();
|
|
49
|
+
const envLocalPath = path.join(projectRoot, '.env.local');
|
|
50
|
+
if (fs.existsSync(envLocalPath)) {
|
|
51
|
+
dotenv_1.default.config({ path: envLocalPath });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Gets THREADLINE_API_KEY from environment.
|
|
56
|
+
* Priority: process.env.THREADLINE_API_KEY → .env.local file
|
|
57
|
+
*/
|
|
58
|
+
function getThreadlineApiKey() {
|
|
59
|
+
// Load .env.local if it exists (doesn't override existing env vars)
|
|
60
|
+
loadEnvLocal();
|
|
61
|
+
// Check environment variable (from shell or CI/CD)
|
|
62
|
+
return process.env.THREADLINE_API_KEY;
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "threadlines",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Threadline CLI - AI-powered linter based on your natural language documentation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|
|
26
|
-
"url": "https://github.com/ngrootscholten/threadline.git",
|
|
26
|
+
"url": "git+https://github.com/ngrootscholten/threadline.git",
|
|
27
27
|
"directory": "packages/cli"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"commander": "^12.1.0",
|
|
37
|
+
"dotenv": "^16.4.7",
|
|
37
38
|
"simple-git": "^3.27.0",
|
|
38
39
|
"axios": "^1.7.9",
|
|
39
40
|
"chalk": "^4.1.2",
|