@nolrm/contextkit 0.12.20 → 0.13.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 +1 -10
- package/bin/contextkit.js +5 -79
- package/lib/commands/install.js +1 -0
- package/lib/commands/update.js +13 -1
- package/lib/utils/migrations.js +50 -0
- package/lib/utils/migrations.md +58 -0
- package/package.json +2 -2
- package/lib/commands/ai.js +0 -147
- package/lib/commands/dashboard.js +0 -383
- package/lib/commands/publish.js +0 -184
- package/lib/commands/pull.js +0 -191
package/README.md
CHANGED
|
@@ -128,11 +128,6 @@ codex "create checkout flow for customer"
|
|
|
128
128
|
opencode "create checkout flow for customer"
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
**CLI** (Chat with AI)
|
|
132
|
-
```bash
|
|
133
|
-
ck ai "create a button"
|
|
134
|
-
```
|
|
135
|
-
|
|
136
131
|
---
|
|
137
132
|
|
|
138
133
|
## Slash Commands
|
|
@@ -275,7 +270,7 @@ When the `commit-msg` hook is enabled, all commits must follow this format:
|
|
|
275
270
|
<type>(<scope>): <description>
|
|
276
271
|
```
|
|
277
272
|
|
|
278
|
-
**Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
|
|
273
|
+
**Types:** `feat`, `fix`, `improve`, `docs`, `style`, `refactor`, `test`, `chore`
|
|
279
274
|
|
|
280
275
|
**Examples:**
|
|
281
276
|
```bash
|
|
@@ -299,7 +294,6 @@ Hooks are optional and can be skipped with `ck install --no-hooks`.
|
|
|
299
294
|
- ⚡ **Zero Config** - Auto-detects project type and package manager
|
|
300
295
|
- ✅ **Policy Enforcement** - Configurable validation with `ck check`
|
|
301
296
|
- 📝 **Corrections Tracking** - Track AI performance issues with corrections log
|
|
302
|
-
- 📊 **Observability Dashboard** - Visual metrics and compliance tracking
|
|
303
297
|
|
|
304
298
|
## Commands
|
|
305
299
|
|
|
@@ -337,9 +331,6 @@ ck note "AI issue" --category "AI Behavior" --priority HIGH
|
|
|
337
331
|
/squad-auto # runs architect → dev → test → review → doc hands-free
|
|
338
332
|
/squad-reset # clear stuck or mixed squad state
|
|
339
333
|
|
|
340
|
-
# Observability
|
|
341
|
-
ck dashboard # start web dashboard
|
|
342
|
-
ck dashboard --no-server # CLI metrics only
|
|
343
334
|
```
|
|
344
335
|
|
|
345
336
|
## Links
|
package/bin/contextkit.js
CHANGED
|
@@ -4,13 +4,10 @@ const { program } = require('commander');
|
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const { install, update, status } = require('../lib');
|
|
6
6
|
const analyze = require('../lib/commands/analyze');
|
|
7
|
-
const ai = require('../lib/commands/ai');
|
|
8
7
|
const check = require('../lib/commands/check');
|
|
9
8
|
const note = require('../lib/commands/note');
|
|
10
9
|
const run = require('../lib/commands/run');
|
|
11
|
-
|
|
12
|
-
const pull = require('../lib/commands/pull');
|
|
13
|
-
const dashboard = require('../lib/commands/dashboard');
|
|
10
|
+
|
|
14
11
|
const packageJson = require('../package.json');
|
|
15
12
|
const { checkForUpdates } = require('../lib/utils/notifier');
|
|
16
13
|
|
|
@@ -81,20 +78,6 @@ program
|
|
|
81
78
|
}
|
|
82
79
|
});
|
|
83
80
|
|
|
84
|
-
// AI command with "ai" keyword
|
|
85
|
-
program
|
|
86
|
-
.command('ai <prompt>')
|
|
87
|
-
.description('Chat with AI using ContextKit context')
|
|
88
|
-
.option('--ai <tool>', 'AI tool to use (aider, claude, gemini)')
|
|
89
|
-
.action(async (prompt, options) => {
|
|
90
|
-
try {
|
|
91
|
-
await ai(prompt, options);
|
|
92
|
-
} catch (error) {
|
|
93
|
-
console.error(chalk.red('AI chat failed:'), error.message);
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
81
|
// Check command
|
|
99
82
|
program
|
|
100
83
|
.command('check')
|
|
@@ -141,51 +124,6 @@ program
|
|
|
141
124
|
}
|
|
142
125
|
});
|
|
143
126
|
|
|
144
|
-
// Publish command
|
|
145
|
-
program
|
|
146
|
-
.command('publish')
|
|
147
|
-
.description('Publish current ContextKit configuration to registry')
|
|
148
|
-
.option('--name <name>', 'Package name (e.g., @company/react-standards)')
|
|
149
|
-
.option('--version <version>', 'Version (e.g., 1.0.0)')
|
|
150
|
-
.action(async (options) => {
|
|
151
|
-
try {
|
|
152
|
-
await publish(options);
|
|
153
|
-
} catch (error) {
|
|
154
|
-
console.error(chalk.red('Publish failed:'), error.message);
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// Pull command
|
|
160
|
-
program
|
|
161
|
-
.command('pull <package>')
|
|
162
|
-
.description('Pull ContextKit configuration from registry')
|
|
163
|
-
.option('--force', 'Force overwrite existing .contextkit')
|
|
164
|
-
.option('--backup', 'Backup existing .contextkit before pulling')
|
|
165
|
-
.action(async (packageSpec, options) => {
|
|
166
|
-
try {
|
|
167
|
-
await pull(packageSpec, options);
|
|
168
|
-
} catch (error) {
|
|
169
|
-
console.error(chalk.red('Pull failed:'), error.message);
|
|
170
|
-
process.exit(1);
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Dashboard command
|
|
175
|
-
program
|
|
176
|
-
.command('dashboard')
|
|
177
|
-
.description('Start observability dashboard')
|
|
178
|
-
.option('--port <port>', 'Port number', '3001')
|
|
179
|
-
.option('--no-server', 'Display metrics only (no web server)')
|
|
180
|
-
.action(async (options) => {
|
|
181
|
-
try {
|
|
182
|
-
await dashboard(options);
|
|
183
|
-
} catch (error) {
|
|
184
|
-
console.error(chalk.red('Dashboard failed:'), error.message);
|
|
185
|
-
process.exit(1);
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
|
|
189
127
|
// Platform-specific install commands
|
|
190
128
|
program
|
|
191
129
|
.command('cursor')
|
|
@@ -317,23 +255,11 @@ program
|
|
|
317
255
|
}
|
|
318
256
|
});
|
|
319
257
|
|
|
320
|
-
// Catch-all for unknown commands
|
|
321
|
-
// This MUST be registered AFTER all commands but BEFORE parse()
|
|
258
|
+
// Catch-all for unknown commands
|
|
322
259
|
program.on('command:*', function(args) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (prompt) {
|
|
327
|
-
try {
|
|
328
|
-
ai(prompt, {}).catch(error => {
|
|
329
|
-
console.error(chalk.red('AI chat failed:'), error.message);
|
|
330
|
-
process.exit(1);
|
|
331
|
-
});
|
|
332
|
-
} catch (error) {
|
|
333
|
-
console.error(chalk.red('AI chat failed:'), error.message);
|
|
334
|
-
process.exit(1);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
260
|
+
console.error(chalk.red(`Unknown command: ${args[0]}`));
|
|
261
|
+
console.log(chalk.yellow('Run `ck --help` to see available commands.'));
|
|
262
|
+
process.exit(1);
|
|
337
263
|
});
|
|
338
264
|
|
|
339
265
|
// Parse command line arguments
|
package/lib/commands/install.js
CHANGED
|
@@ -995,6 +995,7 @@ _source:
|
|
|
995
995
|
version: "${require('../../package.json').version}"
|
|
996
996
|
npm: "https://www.npmjs.com/package/@nolrm/contextkit"
|
|
997
997
|
ck: 1
|
|
998
|
+
format_version: 1
|
|
998
999
|
version: "${config.version}"
|
|
999
1000
|
updated: "${now.split('T')[0]}"
|
|
1000
1001
|
project_name: "${config.project_name}"
|
package/lib/commands/update.js
CHANGED
|
@@ -6,6 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const DownloadManager = require('../utils/download');
|
|
7
7
|
const ProjectDetector = require('../utils/project-detector');
|
|
8
8
|
const GitHooksManager = require('../utils/git-hooks');
|
|
9
|
+
const MigrationRunner = require('../utils/migrations');
|
|
9
10
|
const { printBanner } = require('../utils/banner');
|
|
10
11
|
|
|
11
12
|
class UpdateCommand {
|
|
@@ -44,6 +45,14 @@ class UpdateCommand {
|
|
|
44
45
|
try {
|
|
45
46
|
// Detect current configuration
|
|
46
47
|
const config = await this.parseConfig();
|
|
48
|
+
|
|
49
|
+
// Run any pending format migrations before touching files
|
|
50
|
+
const migrations = new MigrationRunner();
|
|
51
|
+
await migrations.run(config.format_version, '.contextkit/config.yml');
|
|
52
|
+
// Re-read so format_version is current after migration
|
|
53
|
+
const migratedConfig = await this.parseConfig();
|
|
54
|
+
Object.assign(config, migratedConfig);
|
|
55
|
+
|
|
47
56
|
const projectType = this.projectDetector.detectProjectType();
|
|
48
57
|
const packageManager = this.projectDetector.detectPackageManager();
|
|
49
58
|
|
|
@@ -163,7 +172,9 @@ class UpdateCommand {
|
|
|
163
172
|
const lines = configContent.split('\n');
|
|
164
173
|
for (const line of lines) {
|
|
165
174
|
const trimmed = line.trim();
|
|
166
|
-
if (trimmed.startsWith('
|
|
175
|
+
if (trimmed.startsWith('format_version:')) {
|
|
176
|
+
config.format_version = parseInt(trimmed.split('format_version:')[1].trim(), 10);
|
|
177
|
+
} else if (trimmed.startsWith('version:')) {
|
|
167
178
|
config.version = trimmed.split('version:')[1].trim().replace(/"/g, '');
|
|
168
179
|
} else if (trimmed.startsWith('project_name:')) {
|
|
169
180
|
config.project_name = trimmed.split('project_name:')[1].trim().replace(/"/g, '');
|
|
@@ -357,6 +368,7 @@ class UpdateCommand {
|
|
|
357
368
|
async restoreUserConfig(config) {
|
|
358
369
|
// Restore user's project-specific configuration
|
|
359
370
|
const configContent = `# ContextKit Configuration
|
|
371
|
+
format_version: ${config.format_version || 1}
|
|
360
372
|
version: "${config.version}"
|
|
361
373
|
project_name: "${config.project_name}"
|
|
362
374
|
project_type: "${config.project_type}"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
|
|
4
|
+
// Increment this when a new migration step is added.
|
|
5
|
+
const CURRENT_FORMAT_VERSION = 1;
|
|
6
|
+
|
|
7
|
+
class MigrationRunner {
|
|
8
|
+
/**
|
|
9
|
+
* Run all pending migrations from currentVersion up to CURRENT_FORMAT_VERSION.
|
|
10
|
+
* @param {number|undefined} currentVersion - parsed from config.yml format_version field
|
|
11
|
+
* @param {string} configPath - path to config.yml
|
|
12
|
+
*/
|
|
13
|
+
async run(currentVersion, configPath) {
|
|
14
|
+
const from = (currentVersion === undefined || currentVersion === null) ? 0 : currentVersion;
|
|
15
|
+
|
|
16
|
+
if (from > CURRENT_FORMAT_VERSION) {
|
|
17
|
+
console.log(chalk.yellow(
|
|
18
|
+
`⚠️ .contextkit/ format version (${from}) is newer than this version of ContextKit (${CURRENT_FORMAT_VERSION}). Skipping migration.`
|
|
19
|
+
));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (from === CURRENT_FORMAT_VERSION) {
|
|
24
|
+
return; // Nothing to do
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (let v = from; v < CURRENT_FORMAT_VERSION; v++) {
|
|
28
|
+
const next = v + 1;
|
|
29
|
+
console.log(chalk.blue(` Migrating .contextkit/ format from v${v} to v${next}...`));
|
|
30
|
+
|
|
31
|
+
if (v === 0) await this._migrate_0_to_1(configPath);
|
|
32
|
+
|
|
33
|
+
console.log(chalk.green(` ✓ Format migrated to v${next}`));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Migrations ──────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
async _migrate_0_to_1(configPath) {
|
|
40
|
+
const content = await fs.readFile(configPath, 'utf8');
|
|
41
|
+
if (content.includes('format_version:')) {
|
|
42
|
+
// Update existing value to 1
|
|
43
|
+
await fs.writeFile(configPath, content.replace(/^format_version:.*$/m, 'format_version: 1'));
|
|
44
|
+
} else {
|
|
45
|
+
await fs.writeFile(configPath, content.trimEnd() + '\nformat_version: 1\n');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = MigrationRunner;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# migrations.js
|
|
2
|
+
|
|
3
|
+
Format migration runner for the `.contextkit/` directory.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Detects when an installed `.contextkit/` directory uses an older format version and runs the appropriate migration steps to bring it up to date. Called by `ck update` before any files are downloaded or rewritten.
|
|
8
|
+
|
|
9
|
+
## Exports
|
|
10
|
+
|
|
11
|
+
`MigrationRunner` — class
|
|
12
|
+
|
|
13
|
+
## Public API
|
|
14
|
+
|
|
15
|
+
### `new MigrationRunner()`
|
|
16
|
+
|
|
17
|
+
No constructor arguments.
|
|
18
|
+
|
|
19
|
+
### `async run(currentVersion, configPath)`
|
|
20
|
+
|
|
21
|
+
Runs all pending migrations from `currentVersion` up to `CURRENT_FORMAT_VERSION`.
|
|
22
|
+
|
|
23
|
+
| Param | Type | Description |
|
|
24
|
+
|-------|------|-------------|
|
|
25
|
+
| `currentVersion` | `number \| undefined \| null` | Value of `format_version` from config.yml. `undefined`/`null` is treated as `0` (pre-1.0.0 install). |
|
|
26
|
+
| `configPath` | `string` | Absolute or relative path to `.contextkit/config.yml`. |
|
|
27
|
+
|
|
28
|
+
**Behaviour:**
|
|
29
|
+
- If `currentVersion` is missing/null → treated as `0`, runs v0→v1
|
|
30
|
+
- If `currentVersion === CURRENT_FORMAT_VERSION` → no-op
|
|
31
|
+
- If `currentVersion > CURRENT_FORMAT_VERSION` → logs warning, returns without modifying config
|
|
32
|
+
- Prints progress to console for each step
|
|
33
|
+
|
|
34
|
+
## Constants
|
|
35
|
+
|
|
36
|
+
`CURRENT_FORMAT_VERSION = 1` — increment when a new migration step is added.
|
|
37
|
+
|
|
38
|
+
## Adding a Future Migration
|
|
39
|
+
|
|
40
|
+
1. Increment `CURRENT_FORMAT_VERSION`
|
|
41
|
+
2. Add `if (v === N) await this._migrate_N_to_N1(configPath);` inside the loop in `run()`
|
|
42
|
+
3. Implement `_migrate_N_to_N1(configPath)` as a private method
|
|
43
|
+
4. Add test cases in `__tests__/utils/migrations.test.js`
|
|
44
|
+
|
|
45
|
+
## Usage Example
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
const MigrationRunner = require('../utils/migrations');
|
|
49
|
+
|
|
50
|
+
const migrations = new MigrationRunner();
|
|
51
|
+
await migrations.run(config.format_version, '.contextkit/config.yml');
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Edge Cases & Notes
|
|
55
|
+
|
|
56
|
+
- Migrations are idempotent — safe to run twice
|
|
57
|
+
- The `ck update` backup is created before migrations run, so any failure can be rolled back
|
|
58
|
+
- `format_version: 0` (explicit) and missing `format_version` are both treated as v0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nolrm/contextkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "ContextKit - Context Engineering for AI Development. Provide rich context to AI through structured MD files with standards, code guides, and documentation. Works with Cursor, Claude, Aider, VS Code Copilot, and more.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"url": "https://github.com/nolrm/contextkit/issues"
|
|
61
61
|
},
|
|
62
62
|
"engines": {
|
|
63
|
-
"node": ">=
|
|
63
|
+
"node": ">=18.0.0"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"axios": "^1.6.0",
|
package/lib/commands/ai.js
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
const ora = require('ora');
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const { execSync } = require('child_process');
|
|
5
|
-
|
|
6
|
-
class AICommand {
|
|
7
|
-
async run(prompt, options = {}) {
|
|
8
|
-
if (!prompt) {
|
|
9
|
-
console.log(chalk.red('❌ Please provide a prompt'));
|
|
10
|
-
console.log(chalk.blue(' Usage: contextkit ai "your prompt here"'));
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Check if contextkit is installed
|
|
15
|
-
if (!await fs.pathExists('.contextkit/context.md')) {
|
|
16
|
-
console.log(chalk.red('❌ ContextKit not initialized.'));
|
|
17
|
-
console.log(chalk.yellow(' Run: contextkit install'));
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Get AI tool preference
|
|
22
|
-
const aiTool = options.ai || process.env.AI_TOOL || 'display';
|
|
23
|
-
|
|
24
|
-
const spinner = ora('Loading ContextKit context...').start();
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
// Load context
|
|
28
|
-
const context = await fs.readFile('.contextkit/context.md', 'utf-8');
|
|
29
|
-
const standards = await this.loadStandards();
|
|
30
|
-
|
|
31
|
-
spinner.succeed('Context loaded\n');
|
|
32
|
-
|
|
33
|
-
// Prepare full prompt
|
|
34
|
-
const fullPrompt = `${context}\n\n## Project Standards\n\n${standards}\n\n## User Request\n\n${prompt}`;
|
|
35
|
-
|
|
36
|
-
// Execute with AI tool
|
|
37
|
-
await this.executeWithAI(fullPrompt, aiTool, prompt);
|
|
38
|
-
|
|
39
|
-
} catch (error) {
|
|
40
|
-
spinner.fail('Failed to load context');
|
|
41
|
-
console.log(chalk.red(error.message));
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async loadStandards() {
|
|
46
|
-
const standards = {
|
|
47
|
-
codeStyle: '',
|
|
48
|
-
testing: '',
|
|
49
|
-
architecture: '',
|
|
50
|
-
guidelines: ''
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
if (await fs.pathExists('.contextkit/standards/code-style.md')) {
|
|
55
|
-
standards.codeStyle = await fs.readFile('.contextkit/standards/code-style.md', 'utf-8');
|
|
56
|
-
}
|
|
57
|
-
if (await fs.pathExists('.contextkit/standards/testing.md')) {
|
|
58
|
-
standards.testing = await fs.readFile('.contextkit/standards/testing.md', 'utf-8');
|
|
59
|
-
}
|
|
60
|
-
if (await fs.pathExists('.contextkit/standards/architecture.md')) {
|
|
61
|
-
standards.architecture = await fs.readFile('.contextkit/standards/architecture.md', 'utf-8');
|
|
62
|
-
}
|
|
63
|
-
if (await fs.pathExists('.contextkit/standards/ai-guidelines.md')) {
|
|
64
|
-
standards.guidelines = await fs.readFile('.contextkit/standards/ai-guidelines.md', 'utf-8');
|
|
65
|
-
}
|
|
66
|
-
} catch (error) {
|
|
67
|
-
console.log(chalk.yellow('⚠️ Some standards files not found'));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return `Code Style: ${standards.codeStyle.substring(0, 500)}...\n\nTesting: ${standards.testing.substring(0, 500)}...\n\nArchitecture: ${standards.architecture.substring(0, 500)}...\n\nGuidelines: ${standards.guidelines.substring(0, 500)}...`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async executeWithAI(fullPrompt, aiTool, userPrompt) {
|
|
74
|
-
console.log(chalk.blue(`🤖 Using AI tool: ${aiTool}\n`));
|
|
75
|
-
|
|
76
|
-
switch(aiTool) {
|
|
77
|
-
case 'aider':
|
|
78
|
-
await this.executeWithAider(userPrompt);
|
|
79
|
-
break;
|
|
80
|
-
case 'claude':
|
|
81
|
-
await this.executeWithClaude(userPrompt, fullPrompt);
|
|
82
|
-
break;
|
|
83
|
-
case 'gemini':
|
|
84
|
-
await this.executeWithGemini(userPrompt, fullPrompt);
|
|
85
|
-
break;
|
|
86
|
-
case 'display':
|
|
87
|
-
default:
|
|
88
|
-
this.displayPrompt(fullPrompt, aiTool);
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async executeWithAider(prompt) {
|
|
94
|
-
console.log(chalk.green('🚀 Starting Aider...\n'));
|
|
95
|
-
console.log(chalk.blue('💡 Aider will use .aider/rules.md for context\n'));
|
|
96
|
-
console.log(chalk.yellow(`Prompt: ${prompt}\n`));
|
|
97
|
-
console.log('─'.repeat(60));
|
|
98
|
-
try {
|
|
99
|
-
execSync(`echo "${prompt}" | aider`, { stdio: 'inherit' });
|
|
100
|
-
} catch (error) {
|
|
101
|
-
console.log(chalk.red(`Aider error: ${error.message}`));
|
|
102
|
-
console.log(chalk.yellow('💡 Make sure Aider is installed and in your PATH'));
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async executeWithClaude(prompt, context) {
|
|
107
|
-
console.log(chalk.green('🚀 Starting Claude CLI...\n'));
|
|
108
|
-
console.log('─'.repeat(60));
|
|
109
|
-
try {
|
|
110
|
-
execSync(`echo "${context}\n\nUser: ${prompt}" | claude`, { stdio: 'inherit' });
|
|
111
|
-
} catch (error) {
|
|
112
|
-
console.log(chalk.red(`Claude error: ${error.message}`));
|
|
113
|
-
console.log(chalk.yellow('💡 Make sure Claude CLI is installed'));
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async executeWithGemini(prompt, context) {
|
|
118
|
-
console.log(chalk.green('🚀 Starting Gemini CLI...\n'));
|
|
119
|
-
console.log('─'.repeat(60));
|
|
120
|
-
try {
|
|
121
|
-
execSync(`echo "${context}\n\nUser: ${prompt}" | gemini`, { stdio: 'inherit' });
|
|
122
|
-
} catch (error) {
|
|
123
|
-
console.log(chalk.red(`Gemini error: ${error.message}`));
|
|
124
|
-
console.log(chalk.yellow('💡 Make sure Gemini CLI is installed'));
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
displayPrompt(fullPrompt, aiTool) {
|
|
129
|
-
console.log(chalk.yellow('\n📝 Full Prompt (display mode):\n'));
|
|
130
|
-
console.log('─'.repeat(60));
|
|
131
|
-
console.log(fullPrompt);
|
|
132
|
-
console.log('─'.repeat(60));
|
|
133
|
-
|
|
134
|
-
console.log(chalk.blue('\n💡 To execute with AI:'));
|
|
135
|
-
console.log(' Set AI_TOOL environment variable:');
|
|
136
|
-
console.log(' export AI_TOOL=aider && contextkit ai "your prompt"');
|
|
137
|
-
console.log(' export AI_TOOL=claude && contextkit ai "your prompt"');
|
|
138
|
-
console.log(' export AI_TOOL=gemini && contextkit ai "your prompt"\n');
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async function ai(prompt, options) {
|
|
143
|
-
const cmd = new AICommand();
|
|
144
|
-
await cmd.run(prompt, options);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
module.exports = ai;
|
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
const fs = require('fs-extra');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const yaml = require('js-yaml');
|
|
5
|
-
const http = require('http');
|
|
6
|
-
const { execSync } = require('child_process');
|
|
7
|
-
|
|
8
|
-
class DashboardCommand {
|
|
9
|
-
constructor() {
|
|
10
|
-
this.port = 3001;
|
|
11
|
-
this.metrics = {};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async run(options = {}) {
|
|
15
|
-
console.log(chalk.magenta('📊 Starting ContextKit Dashboard\n'));
|
|
16
|
-
|
|
17
|
-
if (!await fs.pathExists('.contextkit/config.yml')) {
|
|
18
|
-
console.log(chalk.red('❌ ContextKit not installed'));
|
|
19
|
-
console.log(chalk.yellow(' Run: contextkit install'));
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Collect metrics
|
|
24
|
-
await this.collectMetrics();
|
|
25
|
-
|
|
26
|
-
// Start server
|
|
27
|
-
if (options.server !== false) {
|
|
28
|
-
this.startServer(options.port || this.port);
|
|
29
|
-
} else {
|
|
30
|
-
// Just display metrics
|
|
31
|
-
this.displayMetrics();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async collectMetrics() {
|
|
36
|
-
this.metrics = {
|
|
37
|
-
standards: {},
|
|
38
|
-
corrections: {},
|
|
39
|
-
policy: {},
|
|
40
|
-
freshness: {},
|
|
41
|
-
files: {}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// Load config
|
|
45
|
-
const config = await this.loadConfig();
|
|
46
|
-
this.metrics.config = config;
|
|
47
|
-
|
|
48
|
-
// Check standards files
|
|
49
|
-
const standardsFiles = [
|
|
50
|
-
'standards/code-style.md',
|
|
51
|
-
'standards/testing.md',
|
|
52
|
-
'standards/architecture.md',
|
|
53
|
-
'standards/ai-guidelines.md',
|
|
54
|
-
'standards/workflows.md',
|
|
55
|
-
'standards/glossary.md'
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
for (const file of standardsFiles) {
|
|
59
|
-
const filePath = `.contextkit/${file}`;
|
|
60
|
-
if (await fs.pathExists(filePath)) {
|
|
61
|
-
const stats = await fs.stat(filePath);
|
|
62
|
-
const daysSinceUpdate = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
|
|
63
|
-
|
|
64
|
-
this.metrics.standards[file] = {
|
|
65
|
-
exists: true,
|
|
66
|
-
lastModified: stats.mtime.toISOString(),
|
|
67
|
-
daysSinceUpdate: Math.floor(daysSinceUpdate),
|
|
68
|
-
size: stats.size
|
|
69
|
-
};
|
|
70
|
-
} else {
|
|
71
|
-
this.metrics.standards[file] = {
|
|
72
|
-
exists: false
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Parse corrections log
|
|
78
|
-
if (await fs.pathExists('.contextkit/corrections.md')) {
|
|
79
|
-
const correctionsContent = await fs.readFile('.contextkit/corrections.md', 'utf-8');
|
|
80
|
-
|
|
81
|
-
// Count sessions
|
|
82
|
-
const sessionMatches = correctionsContent.matchAll(/### (\d{4}-\d{2}-\d{2})/g);
|
|
83
|
-
const sessions = Array.from(sessionMatches);
|
|
84
|
-
this.metrics.corrections.totalSessions = sessions.length;
|
|
85
|
-
|
|
86
|
-
// Count rule updates
|
|
87
|
-
const ruleUpdateMatches = correctionsContent.matchAll(/#### Rule Updates[\s\S]*?(?=####|###|##|$)/g);
|
|
88
|
-
let ruleUpdates = 0;
|
|
89
|
-
for (const match of ruleUpdateMatches) {
|
|
90
|
-
const updates = match[0].match(/^-/gm);
|
|
91
|
-
if (updates) ruleUpdates += updates.length;
|
|
92
|
-
}
|
|
93
|
-
this.metrics.corrections.ruleUpdates = ruleUpdates;
|
|
94
|
-
|
|
95
|
-
// Count AI behavior issues
|
|
96
|
-
const behaviorMatches = correctionsContent.matchAll(/\[HIGH\]/g);
|
|
97
|
-
const mediumMatches = correctionsContent.matchAll(/\[MEDIUM\]/g);
|
|
98
|
-
const lowMatches = correctionsContent.matchAll(/\[LOW\]/g);
|
|
99
|
-
|
|
100
|
-
this.metrics.corrections.highPriority = Array.from(behaviorMatches).length;
|
|
101
|
-
this.metrics.corrections.mediumPriority = Array.from(mediumMatches).length;
|
|
102
|
-
this.metrics.corrections.lowPriority = Array.from(lowMatches).length;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Load policy
|
|
106
|
-
if (await fs.pathExists('.contextkit/policies/policy.yml')) {
|
|
107
|
-
const policyContent = await fs.readFile('.contextkit/policies/policy.yml', 'utf-8');
|
|
108
|
-
this.metrics.policy = yaml.load(policyContent);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Check product context
|
|
112
|
-
const productFiles = [
|
|
113
|
-
'product/mission.md',
|
|
114
|
-
'product/roadmap.md',
|
|
115
|
-
'product/decisions.md',
|
|
116
|
-
'product/context.md'
|
|
117
|
-
];
|
|
118
|
-
|
|
119
|
-
for (const file of productFiles) {
|
|
120
|
-
const filePath = `.contextkit/${file}`;
|
|
121
|
-
this.metrics.files[file] = await fs.pathExists(filePath);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async loadConfig() {
|
|
126
|
-
try {
|
|
127
|
-
const configContent = await fs.readFile('.contextkit/config.yml', 'utf-8');
|
|
128
|
-
return yaml.load(configContent);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
return {};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
displayMetrics() {
|
|
135
|
-
console.log(chalk.blue('\n📊 ContextKit Metrics\n'));
|
|
136
|
-
console.log('─'.repeat(60));
|
|
137
|
-
|
|
138
|
-
// Standards freshness
|
|
139
|
-
console.log(chalk.bold('\n📚 Standards Freshness:'));
|
|
140
|
-
const standards = Object.entries(this.metrics.standards);
|
|
141
|
-
const existing = standards.filter(([_, data]) => data.exists);
|
|
142
|
-
const outdated = existing.filter(([_, data]) => data.daysSinceUpdate > 90);
|
|
143
|
-
|
|
144
|
-
console.log(chalk.green(` ${existing.length}/${standards.length} standards files exist`));
|
|
145
|
-
if (outdated.length > 0) {
|
|
146
|
-
console.log(chalk.yellow(` ⚠️ ${outdated.length} files outdated (>90 days)`));
|
|
147
|
-
outdated.forEach(([file, data]) => {
|
|
148
|
-
console.log(chalk.dim(` - ${file}: ${data.daysSinceUpdate} days old`));
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Corrections log
|
|
153
|
-
if (this.metrics.corrections.totalSessions > 0) {
|
|
154
|
-
console.log(chalk.bold('\n📝 Corrections Log:'));
|
|
155
|
-
console.log(chalk.cyan(` Total Sessions: ${this.metrics.corrections.totalSessions}`));
|
|
156
|
-
console.log(chalk.cyan(` Rule Updates: ${this.metrics.corrections.ruleUpdates || 0}`));
|
|
157
|
-
console.log(chalk.red(` High Priority Issues: ${this.metrics.corrections.highPriority || 0}`));
|
|
158
|
-
console.log(chalk.yellow(` Medium Priority Issues: ${this.metrics.corrections.mediumPriority || 0}`));
|
|
159
|
-
console.log(chalk.green(` Low Priority Issues: ${this.metrics.corrections.lowPriority || 0}`));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Product context
|
|
163
|
-
console.log(chalk.bold('\n📦 Product Context:'));
|
|
164
|
-
const productFiles = Object.entries(this.metrics.files);
|
|
165
|
-
const existingProduct = productFiles.filter(([_, exists]) => exists);
|
|
166
|
-
console.log(chalk.cyan(` ${existingProduct.length}/${productFiles.length} product files exist`));
|
|
167
|
-
|
|
168
|
-
// Policy
|
|
169
|
-
if (this.metrics.policy.enforcement) {
|
|
170
|
-
console.log(chalk.bold('\n⚖️ Policy Enforcement:'));
|
|
171
|
-
const enforcement = this.metrics.policy.enforcement;
|
|
172
|
-
if (enforcement.testing) {
|
|
173
|
-
console.log(chalk.cyan(` Testing: ${enforcement.testing.numbered_cases || 'not set'}`));
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
console.log('\n' + '─'.repeat(60));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
startServer(port) {
|
|
181
|
-
const server = http.createServer((req, res) => {
|
|
182
|
-
if (req.url === '/' || req.url === '/index.html') {
|
|
183
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
184
|
-
res.end(this.generateDashboardHTML());
|
|
185
|
-
} else if (req.url === '/api/metrics') {
|
|
186
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
187
|
-
res.end(JSON.stringify(this.metrics, null, 2));
|
|
188
|
-
} else {
|
|
189
|
-
res.writeHead(404);
|
|
190
|
-
res.end('Not found');
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
server.listen(port, () => {
|
|
195
|
-
console.log(chalk.green(`\n✅ Dashboard running at http://localhost:${port}`));
|
|
196
|
-
console.log(chalk.blue(' Press Ctrl+C to stop\n'));
|
|
197
|
-
|
|
198
|
-
// Try to open browser
|
|
199
|
-
try {
|
|
200
|
-
const platform = process.platform;
|
|
201
|
-
const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open';
|
|
202
|
-
execSync(`${command} http://localhost:${port}`);
|
|
203
|
-
} catch (error) {
|
|
204
|
-
// Ignore if can't open browser
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
generateDashboardHTML() {
|
|
210
|
-
const metrics = this.metrics;
|
|
211
|
-
const standardsCount = Object.values(metrics.standards).filter(s => s.exists).length;
|
|
212
|
-
const totalStandards = Object.keys(metrics.standards).length;
|
|
213
|
-
const freshnessPercent = Math.round((standardsCount / totalStandards) * 100);
|
|
214
|
-
|
|
215
|
-
return `<!DOCTYPE html>
|
|
216
|
-
<html lang="en">
|
|
217
|
-
<head>
|
|
218
|
-
<meta charset="UTF-8">
|
|
219
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
220
|
-
<title>ContextKit Dashboard</title>
|
|
221
|
-
<style>
|
|
222
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
223
|
-
body {
|
|
224
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
225
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
226
|
-
padding: 20px;
|
|
227
|
-
min-height: 100vh;
|
|
228
|
-
}
|
|
229
|
-
.container {
|
|
230
|
-
max-width: 1200px;
|
|
231
|
-
margin: 0 auto;
|
|
232
|
-
}
|
|
233
|
-
h1 {
|
|
234
|
-
color: white;
|
|
235
|
-
margin-bottom: 30px;
|
|
236
|
-
text-align: center;
|
|
237
|
-
font-size: 2.5em;
|
|
238
|
-
}
|
|
239
|
-
.grid {
|
|
240
|
-
display: grid;
|
|
241
|
-
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
242
|
-
gap: 20px;
|
|
243
|
-
margin-bottom: 20px;
|
|
244
|
-
}
|
|
245
|
-
.card {
|
|
246
|
-
background: white;
|
|
247
|
-
border-radius: 10px;
|
|
248
|
-
padding: 20px;
|
|
249
|
-
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
250
|
-
}
|
|
251
|
-
.card h2 {
|
|
252
|
-
color: #333;
|
|
253
|
-
margin-bottom: 15px;
|
|
254
|
-
font-size: 1.3em;
|
|
255
|
-
}
|
|
256
|
-
.stat {
|
|
257
|
-
font-size: 2.5em;
|
|
258
|
-
font-weight: bold;
|
|
259
|
-
color: #667eea;
|
|
260
|
-
margin: 10px 0;
|
|
261
|
-
}
|
|
262
|
-
.stat-label {
|
|
263
|
-
color: #666;
|
|
264
|
-
font-size: 0.9em;
|
|
265
|
-
}
|
|
266
|
-
.progress-bar {
|
|
267
|
-
width: 100%;
|
|
268
|
-
height: 30px;
|
|
269
|
-
background: #e0e0e0;
|
|
270
|
-
border-radius: 15px;
|
|
271
|
-
overflow: hidden;
|
|
272
|
-
margin: 10px 0;
|
|
273
|
-
}
|
|
274
|
-
.progress-fill {
|
|
275
|
-
height: 100%;
|
|
276
|
-
background: linear-gradient(90deg, #667eea, #764ba2);
|
|
277
|
-
transition: width 0.3s ease;
|
|
278
|
-
display: flex;
|
|
279
|
-
align-items: center;
|
|
280
|
-
justify-content: center;
|
|
281
|
-
color: white;
|
|
282
|
-
font-weight: bold;
|
|
283
|
-
}
|
|
284
|
-
.list {
|
|
285
|
-
list-style: none;
|
|
286
|
-
}
|
|
287
|
-
.list li {
|
|
288
|
-
padding: 8px 0;
|
|
289
|
-
border-bottom: 1px solid #eee;
|
|
290
|
-
}
|
|
291
|
-
.list li:last-child {
|
|
292
|
-
border-bottom: none;
|
|
293
|
-
}
|
|
294
|
-
.badge {
|
|
295
|
-
display: inline-block;
|
|
296
|
-
padding: 4px 8px;
|
|
297
|
-
border-radius: 4px;
|
|
298
|
-
font-size: 0.8em;
|
|
299
|
-
font-weight: bold;
|
|
300
|
-
}
|
|
301
|
-
.badge-success { background: #4caf50; color: white; }
|
|
302
|
-
.badge-warning { background: #ff9800; color: white; }
|
|
303
|
-
.badge-danger { background: #f44336; color: white; }
|
|
304
|
-
.badge-info { background: #2196f3; color: white; }
|
|
305
|
-
</style>
|
|
306
|
-
</head>
|
|
307
|
-
<body>
|
|
308
|
-
<div class="container">
|
|
309
|
-
<h1>🎵 ContextKit Dashboard</h1>
|
|
310
|
-
|
|
311
|
-
<div class="grid">
|
|
312
|
-
<div class="card">
|
|
313
|
-
<h2>📚 Standards Freshness</h2>
|
|
314
|
-
<div class="stat">${freshnessPercent}%</div>
|
|
315
|
-
<div class="stat-label">${standardsCount} of ${totalStandards} files exist</div>
|
|
316
|
-
<div class="progress-bar">
|
|
317
|
-
<div class="progress-fill" style="width: ${freshnessPercent}%">${freshnessPercent}%</div>
|
|
318
|
-
</div>
|
|
319
|
-
</div>
|
|
320
|
-
|
|
321
|
-
<div class="card">
|
|
322
|
-
<h2>📝 Corrections Log</h2>
|
|
323
|
-
<div class="stat">${metrics.corrections.totalSessions || 0}</div>
|
|
324
|
-
<div class="stat-label">Total Sessions</div>
|
|
325
|
-
<ul class="list">
|
|
326
|
-
<li><span class="badge badge-danger">HIGH</span> ${metrics.corrections.highPriority || 0} issues</li>
|
|
327
|
-
<li><span class="badge badge-warning">MEDIUM</span> ${metrics.corrections.mediumPriority || 0} issues</li>
|
|
328
|
-
<li><span class="badge badge-success">LOW</span> ${metrics.corrections.lowPriority || 0} issues</li>
|
|
329
|
-
<li><span class="badge badge-info">Updates</span> ${metrics.corrections.ruleUpdates || 0} rule updates</li>
|
|
330
|
-
</ul>
|
|
331
|
-
</div>
|
|
332
|
-
|
|
333
|
-
<div class="card">
|
|
334
|
-
<h2>📦 Product Context</h2>
|
|
335
|
-
<ul class="list">
|
|
336
|
-
${Object.entries(metrics.files).map(([file, exists]) =>
|
|
337
|
-
`<li>${exists ? '✅' : '❌'} ${file.split('/').pop()}</li>`
|
|
338
|
-
).join('')}
|
|
339
|
-
</ul>
|
|
340
|
-
</div>
|
|
341
|
-
|
|
342
|
-
<div class="card">
|
|
343
|
-
<h2>⚖️ Policy</h2>
|
|
344
|
-
${metrics.policy.enforcement ? `
|
|
345
|
-
<ul class="list">
|
|
346
|
-
${metrics.policy.enforcement.testing ?
|
|
347
|
-
`<li>Testing: <span class="badge badge-info">${metrics.policy.enforcement.testing.numbered_cases || 'not set'}</span></li>` : ''}
|
|
348
|
-
${metrics.policy.enforcement.code_style ?
|
|
349
|
-
`<li>Code Style: <span class="badge badge-info">${metrics.policy.enforcement.code_style.typescript_strict || 'not set'}</span></li>` : ''}
|
|
350
|
-
</ul>
|
|
351
|
-
` : '<p>No policy configured</p>'}
|
|
352
|
-
</div>
|
|
353
|
-
</div>
|
|
354
|
-
|
|
355
|
-
<div class="card">
|
|
356
|
-
<h2>📊 Standards Files</h2>
|
|
357
|
-
<ul class="list">
|
|
358
|
-
${Object.entries(metrics.standards).map(([file, data]) => {
|
|
359
|
-
if (!data.exists) return `<li>❌ ${file}</li>`;
|
|
360
|
-
const days = data.daysSinceUpdate || 0;
|
|
361
|
-
const badge = days > 90 ? 'badge-warning' : days > 30 ? 'badge-info' : 'badge-success';
|
|
362
|
-
return `<li>✅ ${file} <span class="badge ${badge}">${days} days old</span></li>`;
|
|
363
|
-
}).join('')}
|
|
364
|
-
</ul>
|
|
365
|
-
</div>
|
|
366
|
-
</div>
|
|
367
|
-
|
|
368
|
-
<script>
|
|
369
|
-
// Auto-refresh every 30 seconds
|
|
370
|
-
setTimeout(() => location.reload(), 30000);
|
|
371
|
-
</script>
|
|
372
|
-
</body>
|
|
373
|
-
</html>`;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
async function dashboard(options) {
|
|
378
|
-
const cmd = new DashboardCommand();
|
|
379
|
-
await cmd.run(options);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
module.exports = dashboard;
|
|
383
|
-
|
package/lib/commands/publish.js
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
const fs = require('fs-extra');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const yaml = require('js-yaml');
|
|
5
|
-
const { execSync } = require('child_process');
|
|
6
|
-
|
|
7
|
-
class PublishCommand {
|
|
8
|
-
constructor() {
|
|
9
|
-
this.registryPath = path.join(process.env.HOME || process.env.USERPROFILE, '.contextkit-registry');
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async run(options = {}) {
|
|
13
|
-
console.log(chalk.magenta('📦 Publishing ContextKit Configuration\n'));
|
|
14
|
-
|
|
15
|
-
// Check if .contextkit exists
|
|
16
|
-
if (!await fs.pathExists('.contextkit/config.yml')) {
|
|
17
|
-
console.log(chalk.red('❌ ContextKit not installed in this directory'));
|
|
18
|
-
console.log(chalk.yellow(' Run: contextkit install'));
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Get package name and version
|
|
23
|
-
const name = options.name || await this.promptName();
|
|
24
|
-
const version = options.version || await this.promptVersion();
|
|
25
|
-
|
|
26
|
-
if (!name || !version) {
|
|
27
|
-
console.log(chalk.red('❌ Package name and version are required'));
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Validate version format
|
|
32
|
-
if (!/^\d+\.\d+\.\d+$/.test(version)) {
|
|
33
|
-
console.log(chalk.red('❌ Invalid version format. Use semantic versioning (e.g., 1.0.0)'));
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Create registry structure
|
|
38
|
-
await this.ensureRegistry();
|
|
39
|
-
|
|
40
|
-
// Create package directory
|
|
41
|
-
const packageDir = path.join(this.registryPath, name.replace('@', '').replace('/', '-'), version);
|
|
42
|
-
await fs.ensureDir(packageDir);
|
|
43
|
-
|
|
44
|
-
// Copy .contextkit files (exclude some)
|
|
45
|
-
const excludePatterns = [
|
|
46
|
-
'corrections.md',
|
|
47
|
-
'status.json',
|
|
48
|
-
'node_modules',
|
|
49
|
-
'.git'
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
await this.copyContextKitFiles('.contextkit', packageDir, excludePatterns);
|
|
53
|
-
|
|
54
|
-
// Create package.json for the published package
|
|
55
|
-
const config = await this.loadConfig();
|
|
56
|
-
const packageJson = {
|
|
57
|
-
name: name,
|
|
58
|
-
version: version,
|
|
59
|
-
description: `ContextKit configuration for ${config.project_name || 'project'}`,
|
|
60
|
-
contextKit: {
|
|
61
|
-
ck: config.ck || 1,
|
|
62
|
-
profile: config.profile,
|
|
63
|
-
generated_at: new Date().toISOString(),
|
|
64
|
-
published_at: new Date().toISOString()
|
|
65
|
-
},
|
|
66
|
-
files: [
|
|
67
|
-
'**/*.md',
|
|
68
|
-
'**/*.yml',
|
|
69
|
-
'**/*.yaml',
|
|
70
|
-
'**/*.ts',
|
|
71
|
-
'**/*.tsx',
|
|
72
|
-
'**/*.sh'
|
|
73
|
-
]
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
await fs.writeJson(path.join(packageDir, 'package.json'), packageJson, { spaces: 2 });
|
|
77
|
-
|
|
78
|
-
// Create README
|
|
79
|
-
const readme = `# ${name} v${version}
|
|
80
|
-
|
|
81
|
-
ContextKit configuration package.
|
|
82
|
-
|
|
83
|
-
## Installation
|
|
84
|
-
|
|
85
|
-
\`\`\`bash
|
|
86
|
-
contextkit pull ${name}@${version}
|
|
87
|
-
\`\`\`
|
|
88
|
-
|
|
89
|
-
## Contents
|
|
90
|
-
|
|
91
|
-
This package includes:
|
|
92
|
-
- Standards files
|
|
93
|
-
- Templates
|
|
94
|
-
- Commands
|
|
95
|
-
- Product context
|
|
96
|
-
- Policies
|
|
97
|
-
|
|
98
|
-
## Version
|
|
99
|
-
|
|
100
|
-
${version} - Published ${new Date().toISOString().split('T')[0]}
|
|
101
|
-
`;
|
|
102
|
-
|
|
103
|
-
await fs.writeFile(path.join(packageDir, 'README.md'), readme);
|
|
104
|
-
|
|
105
|
-
console.log(chalk.green(`\n✅ Published ${name}@${version}`));
|
|
106
|
-
console.log(chalk.dim(` Location: ${packageDir}`));
|
|
107
|
-
console.log(chalk.blue(`\n💡 To use: contextkit pull ${name}@${version}`));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async promptName() {
|
|
111
|
-
const readline = require('readline').createInterface({
|
|
112
|
-
input: process.stdin,
|
|
113
|
-
output: process.stdout
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
return new Promise((resolve) => {
|
|
117
|
-
readline.question(chalk.yellow('Package name (e.g., @company/react-standards): '), (name) => {
|
|
118
|
-
readline.close();
|
|
119
|
-
resolve(name.trim() || null);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async promptVersion() {
|
|
125
|
-
const readline = require('readline').createInterface({
|
|
126
|
-
input: process.stdin,
|
|
127
|
-
output: process.stdout
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
return new Promise((resolve) => {
|
|
131
|
-
readline.question(chalk.yellow('Version (e.g., 1.0.0): '), (version) => {
|
|
132
|
-
readline.close();
|
|
133
|
-
resolve(version.trim() || null);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async ensureRegistry() {
|
|
139
|
-
await fs.ensureDir(this.registryPath);
|
|
140
|
-
console.log(chalk.dim(`Registry location: ${this.registryPath}`));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async copyContextKitFiles(sourceDir, targetDir, excludePatterns) {
|
|
144
|
-
const files = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
145
|
-
|
|
146
|
-
for (const file of files) {
|
|
147
|
-
const sourcePath = path.join(sourceDir, file.name);
|
|
148
|
-
const targetPath = path.join(targetDir, file.name);
|
|
149
|
-
|
|
150
|
-
// Check if should exclude
|
|
151
|
-
const shouldExclude = excludePatterns.some(pattern =>
|
|
152
|
-
file.name.includes(pattern) || sourcePath.includes(pattern)
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
if (shouldExclude) {
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (file.isDirectory()) {
|
|
160
|
-
await fs.ensureDir(targetPath);
|
|
161
|
-
await this.copyContextKitFiles(sourcePath, targetPath, excludePatterns);
|
|
162
|
-
} else {
|
|
163
|
-
await fs.copy(sourcePath, targetPath);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async loadConfig() {
|
|
169
|
-
try {
|
|
170
|
-
const configContent = await fs.readFile('.contextkit/config.yml', 'utf-8');
|
|
171
|
-
return yaml.load(configContent);
|
|
172
|
-
} catch (error) {
|
|
173
|
-
return {};
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async function publish(options) {
|
|
179
|
-
const cmd = new PublishCommand();
|
|
180
|
-
await cmd.run(options);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
module.exports = publish;
|
|
184
|
-
|
package/lib/commands/pull.js
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
const fs = require('fs-extra');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const yaml = require('js-yaml');
|
|
5
|
-
|
|
6
|
-
class PullCommand {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.registryPath = path.join(process.env.HOME || process.env.USERPROFILE, '.contextkit-registry');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async run(packageSpec, options = {}) {
|
|
12
|
-
console.log(chalk.magenta(`📥 Pulling ContextKit Configuration: ${packageSpec}\n`));
|
|
13
|
-
|
|
14
|
-
// Parse package spec (name@version or just name)
|
|
15
|
-
const [packageName, version] = packageSpec.includes('@')
|
|
16
|
-
? packageSpec.split('@')
|
|
17
|
-
: [packageSpec, 'latest'];
|
|
18
|
-
|
|
19
|
-
// Find package
|
|
20
|
-
const packageDir = await this.findPackage(packageName, version);
|
|
21
|
-
|
|
22
|
-
if (!packageDir) {
|
|
23
|
-
console.log(chalk.red(`❌ Package not found: ${packageSpec}`));
|
|
24
|
-
console.log(chalk.yellow('\n💡 Available packages:'));
|
|
25
|
-
await this.listPackages();
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Check if .contextkit exists
|
|
30
|
-
const backupDir = options.backup ? `.contextkit.backup.${Date.now()}` : null;
|
|
31
|
-
|
|
32
|
-
if (await fs.pathExists('.contextkit') && !options.force) {
|
|
33
|
-
if (backupDir) {
|
|
34
|
-
await fs.copy('.contextkit', backupDir);
|
|
35
|
-
console.log(chalk.green(`✅ Backed up existing .contextkit to ${backupDir}`));
|
|
36
|
-
} else {
|
|
37
|
-
const { confirm } = await this.promptOverwrite();
|
|
38
|
-
if (!confirm) {
|
|
39
|
-
console.log(chalk.yellow('⏭️ Cancelled'));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Copy package files
|
|
46
|
-
await this.copyPackageFiles(packageDir, '.contextkit', options);
|
|
47
|
-
|
|
48
|
-
// Update config.yml with package info
|
|
49
|
-
await this.updateConfig(packageName, version);
|
|
50
|
-
|
|
51
|
-
console.log(chalk.green(`\n✅ Pulled ${packageSpec} successfully!`));
|
|
52
|
-
|
|
53
|
-
if (backupDir) {
|
|
54
|
-
console.log(chalk.dim(` Backup: ${backupDir}`));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async findPackage(packageName, version) {
|
|
59
|
-
const packagePath = path.join(this.registryPath, packageName.replace('@', '').replace('/', '-'));
|
|
60
|
-
|
|
61
|
-
if (!await fs.pathExists(packagePath)) {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (version === 'latest') {
|
|
66
|
-
// Find latest version
|
|
67
|
-
const versions = await fs.readdir(packagePath);
|
|
68
|
-
const sortedVersions = versions
|
|
69
|
-
.filter(v => /^\d+\.\d+\.\d+$/.test(v))
|
|
70
|
-
.sort((a, b) => {
|
|
71
|
-
const aParts = a.split('.').map(Number);
|
|
72
|
-
const bParts = b.split('.').map(Number);
|
|
73
|
-
for (let i = 0; i < 3; i++) {
|
|
74
|
-
if (aParts[i] !== bParts[i]) {
|
|
75
|
-
return bParts[i] - aParts[i];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return 0;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
if (sortedVersions.length > 0) {
|
|
82
|
-
return path.join(packagePath, sortedVersions[0]);
|
|
83
|
-
}
|
|
84
|
-
} else {
|
|
85
|
-
const versionPath = path.join(packagePath, version);
|
|
86
|
-
if (await fs.pathExists(versionPath)) {
|
|
87
|
-
return versionPath;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async listPackages() {
|
|
95
|
-
if (!await fs.pathExists(this.registryPath)) {
|
|
96
|
-
console.log(chalk.dim(' (No packages in registry)'));
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const packages = await fs.readdir(this.registryPath, { withFileTypes: true });
|
|
101
|
-
|
|
102
|
-
for (const pkg of packages.filter(p => p.isDirectory())) {
|
|
103
|
-
const packagePath = path.join(this.registryPath, pkg.name);
|
|
104
|
-
const versions = await fs.readdir(packagePath);
|
|
105
|
-
const validVersions = versions.filter(v => /^\d+\.\d+\.\d+$/.test(v));
|
|
106
|
-
|
|
107
|
-
if (validVersions.length > 0) {
|
|
108
|
-
const latest = validVersions.sort().reverse()[0];
|
|
109
|
-
console.log(chalk.cyan(` ${pkg.name.replace('-', '/')}@${latest}`));
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async promptOverwrite() {
|
|
115
|
-
const readline = require('readline').createInterface({
|
|
116
|
-
input: process.stdin,
|
|
117
|
-
output: process.stdout
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
return new Promise((resolve) => {
|
|
121
|
-
readline.question(chalk.yellow('⚠️ .contextkit already exists. Overwrite? (y/N): '), (answer) => {
|
|
122
|
-
readline.close();
|
|
123
|
-
resolve({ confirm: answer.toLowerCase() === 'y' });
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async copyPackageFiles(sourceDir, targetDir, options) {
|
|
129
|
-
// Read package.json if exists
|
|
130
|
-
const packageJsonPath = path.join(sourceDir, 'package.json');
|
|
131
|
-
let packageJson = {};
|
|
132
|
-
|
|
133
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
134
|
-
packageJson = await fs.readJson(packageJsonPath);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Copy all files from package
|
|
138
|
-
const files = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
139
|
-
|
|
140
|
-
for (const file of files) {
|
|
141
|
-
const sourcePath = path.join(sourceDir, file.name);
|
|
142
|
-
const targetPath = path.join(targetDir, file.name);
|
|
143
|
-
|
|
144
|
-
// Skip package.json and README.md
|
|
145
|
-
if (file.name === 'package.json' || file.name === 'README.md') {
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (file.isDirectory()) {
|
|
150
|
-
await fs.ensureDir(targetPath);
|
|
151
|
-
await this.copyPackageFiles(sourcePath, targetPath, options);
|
|
152
|
-
} else {
|
|
153
|
-
await fs.copy(sourcePath, targetPath);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async updateConfig(packageName, version) {
|
|
159
|
-
const configPath = '.contextkit/config.yml';
|
|
160
|
-
|
|
161
|
-
if (!await fs.pathExists(configPath)) {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
167
|
-
const config = yaml.load(configContent);
|
|
168
|
-
|
|
169
|
-
// Add package info
|
|
170
|
-
if (!config.metadata) {
|
|
171
|
-
config.metadata = {};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
config.metadata.pulled_from = packageName;
|
|
175
|
-
config.metadata.pulled_version = version;
|
|
176
|
-
config.metadata.pulled_at = new Date().toISOString();
|
|
177
|
-
|
|
178
|
-
await fs.writeFile(configPath, yaml.dump(config, { lineWidth: 120 }));
|
|
179
|
-
} catch (error) {
|
|
180
|
-
console.log(chalk.yellow(`⚠️ Could not update config.yml: ${error.message}`));
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async function pull(packageSpec, options) {
|
|
186
|
-
const cmd = new PullCommand();
|
|
187
|
-
await cmd.run(packageSpec, options);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
module.exports = pull;
|
|
191
|
-
|