@nolrm/contextkit 0.12.22 → 0.13.1

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 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
@@ -148,7 +143,7 @@ ContextKit installs reusable slash commands for supported platforms:
148
143
  | `/test` | Generate comprehensive tests |
149
144
  | `/doc` | Add documentation |
150
145
  | `/spec` | Write a component spec (MD-first) before any code is created |
151
- | `/squad` | Kick off a squad task — one task or many (auto-detects batch mode) |
146
+ | `/squad` | Kick off a squad task — one task or many (auto-detects batch mode). Pushes back with clarifying questions if the task is vague. |
152
147
  | `/squad-architect` | Design the technical plan from the PO spec |
153
148
  | `/squad-dev` | Implement code following the architect plan |
154
149
  | `/squad-test` | Write and run tests against acceptance criteria |
@@ -173,7 +168,7 @@ The squad workflow turns a single AI session into a structured multi-role pipeli
173
168
 
174
169
  | Step | Role | Command | What it does |
175
170
  |------|------|---------|-------------|
176
- | 1 | Product Owner | `/squad` | Writes a user story, acceptance criteria, edge cases, and scope. Optionally captures screenshots/images as visual assets. |
171
+ | 1 | Product Owner | `/squad` | Writes a user story, acceptance criteria, edge cases, and scope. If the task is ambiguous, asks up to 5 clarifying questions before writing the spec. Optionally captures screenshots/images as visual assets. |
177
172
  | 2 | Architect | `/squad-architect` | Designs the technical approach, files to change, and implementation steps |
178
173
  | 3 | Developer | `/squad-dev` | Implements the code following the architect's plan |
179
174
  | 4 | Tester | `/squad-test` | Writes and runs tests against the PO's acceptance criteria |
@@ -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
- const publish = require('../lib/commands/publish');
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 (treats them as prompts)
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
- // The command wasn't found, treat it as a prompt
324
- const prompt = args.join(' ');
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
@@ -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}"
@@ -1621,7 +1622,6 @@ enforcement:
1621
1622
  console.log(chalk.bold('📖 Quick Reference'));
1622
1623
  console.log(''.padEnd(48, '─'));
1623
1624
  console.log(`ck status → Check installation & integrations`);
1624
- console.log(`ck ai <cmd> → Use AI with project context`);
1625
1625
  console.log(`ck <platform> → Add platform (claude, cursor, copilot, codex, opencode, gemini, aider, continue, windsurf)`);
1626
1626
  console.log('');
1627
1627
  console.log(`Docs → ${chalk.blue('https://contextkit-docs.vercel.app')}`);
@@ -1667,7 +1667,7 @@ enforcement:
1667
1667
  }
1668
1668
 
1669
1669
  console.log(chalk.bold('In CLI'));
1670
- console.log(`ck ai "create a Button component for customer checkout"`);
1670
+ console.log(chalk.dim('Use your AI tool\'s slash command, e.g. /analyze or @.contextkit/commands/create-component.md'));
1671
1671
  console.log('');
1672
1672
 
1673
1673
  if (platform === 'claude' || platform === 'gemini') {
@@ -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('version:')) {
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
@@ -29,6 +29,7 @@ class ToolDetector {
29
29
  claude_cli: await this.detectCLITool('claude'),
30
30
  gemini_cli: await this.detectCLITool('gemini'),
31
31
  codex_cli: await this.detectCLITool('codex'),
32
+ opencode_cli: await this.detectCLITool('opencode'),
32
33
  };
33
34
 
34
35
  return this.detectedTools;
@@ -120,6 +121,7 @@ class ToolDetector {
120
121
  'claude_cli', // Good CLI
121
122
  'gemini_cli', // Alternative CLI
122
123
  'codex_cli', // OpenAI Codex CLI
124
+ 'opencode_cli', // OpenCode CLI
123
125
  ];
124
126
 
125
127
  return priority.filter(tool => detected[tool]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nolrm/contextkit",
3
- "version": "0.12.22",
3
+ "version": "0.13.1",
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": ">=14.0.0"
63
+ "node": ">=18.0.0"
64
64
  },
65
65
  "dependencies": {
66
66
  "axios": "^1.6.0",
@@ -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
-
@@ -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
-
@@ -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
-