@itz4blitz/agentful 1.8.1 → 1.8.2

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
@@ -70,9 +70,6 @@ npx @itz4blitz/agentful init --preset=minimal
70
70
 
71
71
  # Custom: Choose components
72
72
  npx @itz4blitz/agentful init --agents=orchestrator,backend --skills=validation
73
-
74
- # Web configurator
75
- https://agentful.app/configure
76
73
  ```
77
74
 
78
75
  ## Commands
package/bin/cli.js CHANGED
@@ -20,6 +20,7 @@
20
20
 
21
21
  import fs from 'fs';
22
22
  import path from 'path';
23
+ import { spawnSync } from 'child_process';
23
24
  import { fileURLToPath } from 'url';
24
25
  import { initProject, isInitialized } from '../lib/init.js';
25
26
  import {
@@ -82,11 +83,11 @@ function showHelp() {
82
83
  console.log('');
83
84
  console.log('INIT OPTIONS (optional):');
84
85
  console.log(` ${colors.yellow}--preset=minimal${colors.reset} Minimal setup (orchestrator + backend only)`);
85
- console.log(` ${colors.yellow}--config=<url|id>${colors.reset} Use a shareable configuration`);
86
86
  console.log(` ${colors.yellow}--agents=<list>${colors.reset} Custom agents (comma-separated)`);
87
87
  console.log(` ${colors.yellow}--skills=<list>${colors.reset} Custom skills (comma-separated)`);
88
88
  console.log(` ${colors.yellow}--hooks=<list>${colors.reset} Custom hooks (comma-separated)`);
89
89
  console.log(` ${colors.yellow}--gates=<list>${colors.reset} Custom quality gates (comma-separated)`);
90
+ console.log(` ${colors.yellow}--skip-mcp${colors.reset} Skip automatic MCP server setup`);
90
91
  console.log('');
91
92
  console.log('EXAMPLES:');
92
93
  console.log(` ${colors.dim}# Install agentful (all components - recommended)${colors.reset}`);
@@ -183,50 +184,78 @@ function checkGitignore() {
183
184
  }
184
185
  }
185
186
 
186
- /**
187
- * Fetch configuration from shareable URL or ID
188
- * @param {string} configParam - URL or ID
189
- * @returns {Promise<Object|null>}
190
- */
191
- async function fetchShareableConfig(configParam) {
192
- try {
193
- // Determine if it's a full URL or just an ID
194
- let apiUrl;
195
- if (configParam.startsWith('http://') || configParam.startsWith('https://')) {
196
- // Extract ID from URL
197
- const match = configParam.match(/\/c\/([a-f0-9]{8})$/i);
198
- if (!match) {
199
- throw new Error('Invalid config URL format. Expected: https://agentful.app/c/{id}');
200
- }
201
- const id = match[1];
202
- apiUrl = `https://agentful.app/api/get-config/${id}`;
203
- } else if (/^[a-f0-9]{8}$/i.test(configParam)) {
204
- // It's just the ID
205
- apiUrl = `https://agentful.app/api/get-config/${configParam}`;
206
- } else {
207
- throw new Error('Invalid config parameter. Provide either a full URL or an 8-character ID.');
208
- }
187
+ const MCP_SERVER_NAME = 'agentful';
188
+ const MCP_SERVER_PACKAGE = '@itz4blitz/agentful-mcp-server';
189
+ const MCP_SETUP_COMMAND = `claude mcp add ${MCP_SERVER_NAME} -- npx -y ${MCP_SERVER_PACKAGE}`;
209
190
 
210
- log(colors.dim, `Fetching configuration from ${apiUrl}...`);
191
+ function parseMcpInstallState() {
192
+ const result = spawnSync('claude', ['mcp', 'list'], {
193
+ encoding: 'utf8'
194
+ });
211
195
 
212
- const response = await fetch(apiUrl);
196
+ if (result.error) {
197
+ return {
198
+ ok: false,
199
+ reason: result.error.code === 'ENOENT' ? 'claude-not-found' : result.error.message
200
+ };
201
+ }
213
202
 
214
- if (!response.ok) {
215
- if (response.status === 404) {
216
- throw new Error('Configuration not found. The config may have expired or the ID is invalid.');
217
- }
218
- if (response.status === 410) {
219
- throw new Error('Configuration has expired (1 year TTL).');
220
- }
221
- throw new Error(`Failed to fetch config: HTTP ${response.status}`);
222
- }
203
+ if (result.status !== 0) {
204
+ return {
205
+ ok: false,
206
+ reason: (result.stderr || result.stdout || '').trim() || 'claude mcp list failed'
207
+ };
208
+ }
223
209
 
224
- const data = await response.json();
225
- return data.config;
226
- } catch (error) {
227
- log(colors.red, `Error fetching shareable config: ${error.message}`);
228
- return null;
210
+ const output = `${result.stdout || ''}\n${result.stderr || ''}`;
211
+ const installed = new RegExp(`^\\s*${MCP_SERVER_NAME}(\\s|$)`, 'm').test(output);
212
+
213
+ return {
214
+ ok: true,
215
+ installed
216
+ };
217
+ }
218
+
219
+ function ensureAgentfulMcpInstalled() {
220
+ const state = parseMcpInstallState();
221
+ if (!state.ok) {
222
+ return {
223
+ status: 'unavailable',
224
+ reason: state.reason
225
+ };
229
226
  }
227
+
228
+ if (state.installed) {
229
+ return { status: 'already-installed' };
230
+ }
231
+
232
+ const addResult = spawnSync('claude', ['mcp', 'add', MCP_SERVER_NAME, '--', 'npx', '-y', MCP_SERVER_PACKAGE], {
233
+ encoding: 'utf8'
234
+ });
235
+
236
+ if (addResult.error) {
237
+ return {
238
+ status: 'failed',
239
+ reason: addResult.error.message
240
+ };
241
+ }
242
+
243
+ if (addResult.status !== 0) {
244
+ return {
245
+ status: 'failed',
246
+ reason: (addResult.stderr || addResult.stdout || '').trim() || 'claude mcp add failed'
247
+ };
248
+ }
249
+
250
+ const verify = parseMcpInstallState();
251
+ if (!verify.ok || !verify.installed) {
252
+ return {
253
+ status: 'failed',
254
+ reason: !verify.ok ? verify.reason : 'MCP server not visible after install'
255
+ };
256
+ }
257
+
258
+ return { status: 'installed' };
230
259
  }
231
260
 
232
261
  async function init(args) {
@@ -241,77 +270,72 @@ async function init(args) {
241
270
  // Build configuration from preset and/or flags
242
271
  let config = null;
243
272
 
244
- // Check for shareable config first
245
273
  if (flags.config) {
246
- config = await fetchShareableConfig(flags.config);
247
- if (!config) {
248
- log(colors.red, 'Failed to load shareable configuration.');
249
- process.exit(1);
250
- }
251
- log(colors.green, 'Loaded shareable configuration successfully!');
252
- console.log('');
253
- } else {
254
- // Default to "default" preset if no flags provided
255
- let presetConfig = null;
256
- const hasCustomFlags = flags.agents || flags.skills || flags.hooks || flags.gates;
257
-
258
- if (flags.preset) {
259
- // User explicitly specified a preset
260
- presetConfig = getPreset(flags.preset);
261
- if (!presetConfig) {
262
- log(colors.red, `Unknown preset: ${flags.preset}`);
263
- console.log('');
264
- log(colors.dim, 'Available presets:');
265
- listPresets().forEach(p => log(colors.dim, ` - ${p.name}`));
266
- console.log('');
267
- log(colors.dim, 'Run: agentful presets');
268
- process.exit(1);
269
- }
270
- log(colors.dim, `Using preset: ${flags.preset}`);
271
- } else if (!hasCustomFlags) {
272
- // No preset and no custom flags = use default preset
273
- presetConfig = getPreset('default');
274
- log(colors.dim, 'Installing agentful (all components)');
275
- }
276
-
277
- // Parse individual flags
278
- const flagConfig = {
279
- agents: flags.agents ? parseArrayFlag(flags.agents) : null,
280
- skills: flags.skills ? parseArrayFlag(flags.skills) : null,
281
- hooks: flags.hooks ? parseArrayFlag(flags.hooks) : null,
282
- gates: flags.gates ? parseArrayFlag(flags.gates) : null
283
- };
274
+ log(colors.red, '--config is no longer supported.');
275
+ log(colors.dim, 'Use local CLI flags instead: --preset, --agents, --skills, --hooks, --gates');
276
+ process.exit(1);
277
+ }
284
278
 
285
- // Merge preset with flags (flags override preset)
286
- if (presetConfig) {
287
- config = mergePresetWithFlags(presetConfig, flagConfig);
288
- } else {
289
- // Custom configuration with no preset
290
- config = {
291
- agents: flagConfig.agents || ['orchestrator'],
292
- skills: flagConfig.skills || [],
293
- hooks: flagConfig.hooks || [],
294
- gates: flagConfig.gates || []
295
- };
296
- }
279
+ // Default to "default" preset if no flags provided
280
+ let presetConfig = null;
281
+ const hasCustomFlags = flags.agents || flags.skills || flags.hooks || flags.gates;
297
282
 
298
- // Validate configuration
299
- const validation = validateConfiguration(config);
300
- if (!validation.valid) {
301
- log(colors.yellow, 'Configuration warnings:');
302
- validation.errors.forEach(err => log(colors.yellow, ` - ${err}`));
283
+ if (flags.preset) {
284
+ // User explicitly specified a preset
285
+ presetConfig = getPreset(flags.preset);
286
+ if (!presetConfig) {
287
+ log(colors.red, `Unknown preset: ${flags.preset}`);
288
+ console.log('');
289
+ log(colors.dim, 'Available presets:');
290
+ listPresets().forEach(p => log(colors.dim, ` - ${p.name}`));
303
291
  console.log('');
292
+ log(colors.dim, 'Run: agentful presets');
293
+ process.exit(1);
304
294
  }
295
+ log(colors.dim, `Using preset: ${flags.preset}`);
296
+ } else if (!hasCustomFlags) {
297
+ // No preset and no custom flags = use default preset
298
+ presetConfig = getPreset('default');
299
+ log(colors.dim, 'Installing agentful (all components)');
300
+ }
305
301
 
306
- // Show what will be installed
307
- log(colors.dim, 'Configuration:');
308
- log(colors.dim, ` Agents: ${config.agents.join(', ')}`);
309
- log(colors.dim, ` Skills: ${config.skills.join(', ') || 'none'}`);
310
- log(colors.dim, ` Hooks: ${config.hooks.join(', ') || 'none'}`);
311
- log(colors.dim, ` Gates: ${config.gates.join(', ') || 'none'}`);
302
+ // Parse individual flags
303
+ const flagConfig = {
304
+ agents: flags.agents ? parseArrayFlag(flags.agents) : null,
305
+ skills: flags.skills ? parseArrayFlag(flags.skills) : null,
306
+ hooks: flags.hooks ? parseArrayFlag(flags.hooks) : null,
307
+ gates: flags.gates ? parseArrayFlag(flags.gates) : null
308
+ };
309
+
310
+ // Merge preset with flags (flags override preset)
311
+ if (presetConfig) {
312
+ config = mergePresetWithFlags(presetConfig, flagConfig);
313
+ } else {
314
+ // Custom configuration with no preset
315
+ config = {
316
+ agents: flagConfig.agents || ['orchestrator'],
317
+ skills: flagConfig.skills || [],
318
+ hooks: flagConfig.hooks || [],
319
+ gates: flagConfig.gates || []
320
+ };
321
+ }
322
+
323
+ // Validate configuration
324
+ const validation = validateConfiguration(config);
325
+ if (!validation.valid) {
326
+ log(colors.yellow, 'Configuration warnings:');
327
+ validation.errors.forEach(err => log(colors.yellow, ` - ${err}`));
312
328
  console.log('');
313
329
  }
314
330
 
331
+ // Show what will be installed
332
+ log(colors.dim, 'Configuration:');
333
+ log(colors.dim, ` Agents: ${config.agents.join(', ')}`);
334
+ log(colors.dim, ` Skills: ${config.skills.join(', ') || 'none'}`);
335
+ log(colors.dim, ` Hooks: ${config.hooks.join(', ') || 'none'}`);
336
+ log(colors.dim, ` Gates: ${config.gates.join(', ') || 'none'}`);
337
+ console.log('');
338
+
315
339
  // Check if already initialized
316
340
  if (await isInitialized(targetDir)) {
317
341
  log(colors.yellow, 'agentful is already initialized in this directory!');
@@ -391,6 +415,28 @@ async function init(args) {
391
415
  // Update .gitignore
392
416
  checkGitignore();
393
417
 
418
+ // Configure Agentful MCP server by default (non-fatal)
419
+ const skipMcpSetup = Boolean(flags['skip-mcp']);
420
+ const mcpSetupResult = skipMcpSetup ? { status: 'skipped' } : ensureAgentfulMcpInstalled();
421
+
422
+ if (skipMcpSetup) {
423
+ log(colors.dim, 'Skipped MCP setup (--skip-mcp)');
424
+ console.log('');
425
+ } else if (mcpSetupResult.status === 'installed') {
426
+ log(colors.green, '✓ Agentful MCP server configured');
427
+ console.log('');
428
+ } else if (mcpSetupResult.status === 'already-installed') {
429
+ log(colors.green, '✓ Agentful MCP server already configured');
430
+ console.log('');
431
+ } else {
432
+ log(colors.yellow, '⚠ Could not auto-configure Agentful MCP server');
433
+ if (mcpSetupResult.reason) {
434
+ log(colors.dim, ` Reason: ${mcpSetupResult.reason}`);
435
+ }
436
+ log(colors.dim, ` Run manually: ${MCP_SETUP_COMMAND}`);
437
+ console.log('');
438
+ }
439
+
394
440
  // Show next steps
395
441
  console.log('');
396
442
  log(colors.bright, 'Next Steps:');
@@ -411,13 +457,23 @@ async function init(args) {
411
457
  }
412
458
  log(colors.dim, 'Optional: Edit CLAUDE.md and .claude/product/index.md first to customize.');
413
459
  console.log('');
414
- log(colors.bright, 'Recommended: Enable Pattern Learning');
415
- console.log('');
416
- log(colors.dim, ' Agents get smarter when they can store and reuse patterns.');
417
- log(colors.dim, ' Run this once to enable:');
418
- console.log('');
419
- log(colors.cyan, ' claude mcp add agentful -- npx -y @itz4blitz/agentful-mcp-server');
420
- console.log('');
460
+ if (skipMcpSetup) {
461
+ log(colors.bright, 'Pattern Learning (Skipped)');
462
+ console.log('');
463
+ log(colors.dim, ' You skipped automatic MCP setup with --skip-mcp.');
464
+ log(colors.dim, ' Enable it later with:');
465
+ console.log('');
466
+ log(colors.cyan, ` ${MCP_SETUP_COMMAND}`);
467
+ console.log('');
468
+ } else if (mcpSetupResult.status !== 'installed' && mcpSetupResult.status !== 'already-installed') {
469
+ log(colors.bright, 'Pattern Learning (Manual Step Required)');
470
+ console.log('');
471
+ log(colors.dim, ' Agents get smarter when they can store and reuse patterns.');
472
+ log(colors.dim, ' Run this once to enable:');
473
+ console.log('');
474
+ log(colors.cyan, ` ${MCP_SETUP_COMMAND}`);
475
+ console.log('');
476
+ }
421
477
  }
422
478
 
423
479
  function showStatus() {
@@ -239,4 +239,6 @@ function markForReanalysis(arch, reasons) {
239
239
 
240
240
  // Run detection
241
241
  const driftDetected = detectArchitectDrift();
242
- process.exit(driftDetected ? 1 : 0);
242
+ // Always exit 0 - drift is informational, not an error
243
+ // Non-zero exit codes are interpreted as hook errors by Claude Code
244
+ process.exit(0);
@@ -12,7 +12,6 @@
12
12
  * - .claude/skills/*\/SKILL.md (skill documentation)
13
13
  * - .claude/product/**\/*.md (product specifications)
14
14
  * - template/**\/*.md (template files)
15
- * - examples/**\/*.md (example documentation)
16
15
  *
17
16
  * Blocked:
18
17
  * - Random *.md files in project root
@@ -42,7 +41,6 @@ const ALLOWED_PATTERNS = [
42
41
  /^\.claude\/skills\/[^\/]+\/SKILL\.md$/, // Skill docs
43
42
  /^\.claude\/product\/.*\.md$/, // Product specs
44
43
  /^template\/.*\.md$/, // Template files
45
- /^examples\/.*\.md$/, // Examples
46
44
  /^\.agentful\/.*\.md$/ // Internal agentful state docs (rare)
47
45
  ];
48
46
 
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Health Check Hook
4
+ *
5
+ * Verifies the "agentful" MCP server is configured in Claude Code.
6
+ * Non-blocking: warns only, never exits with failure.
7
+ */
8
+
9
+ import { spawnSync } from 'child_process';
10
+
11
+ const MCP_NAME = 'agentful';
12
+ const SETUP_COMMAND = 'claude mcp add agentful -- npx -y @itz4blitz/agentful-mcp-server';
13
+
14
+ function runClaudeMcpList() {
15
+ return spawnSync('claude', ['mcp', 'list'], {
16
+ encoding: 'utf8'
17
+ });
18
+ }
19
+
20
+ function hasAgentfulMcp(text) {
21
+ return new RegExp(`^\\s*${MCP_NAME}(\\s|$)`, 'm').test(text);
22
+ }
23
+
24
+ function printMissingMcpWarning() {
25
+ console.log('⚠️ Agentful MCP server is not configured.');
26
+ console.log(` Run: ${SETUP_COMMAND}`);
27
+ }
28
+
29
+ (() => {
30
+ const result = runClaudeMcpList();
31
+
32
+ if (result.error) {
33
+ if (result.error.code === 'ENOENT') {
34
+ console.log('⚠️ Claude CLI not found; MCP setup check skipped.');
35
+ return;
36
+ }
37
+
38
+ if (process.env.VERBOSE) {
39
+ console.log(`⚠️ MCP setup check failed: ${result.error.message}`);
40
+ }
41
+ return;
42
+ }
43
+
44
+ if (result.status !== 0) {
45
+ if (process.env.VERBOSE) {
46
+ const reason = (result.stderr || result.stdout || '').trim();
47
+ console.log(`⚠️ MCP setup check failed${reason ? `: ${reason}` : ''}`);
48
+ }
49
+ return;
50
+ }
51
+
52
+ const output = `${result.stdout || ''}\n${result.stderr || ''}`;
53
+ if (!hasAgentfulMcp(output)) {
54
+ printMissingMcpWarning();
55
+ }
56
+ })();
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Package Metadata Guard Hook
5
+ *
6
+ * Protects package.json metadata from accidental ownership corruption.
7
+ *
8
+ * Catches:
9
+ * - repository.type being changed away from "git"
10
+ * - repository.url being changed to github.com/itz4blitz/* in repos owned by someone else
11
+ *
12
+ * Run: PostToolUse (Write|Edit)
13
+ * Action: Exit non-zero so the agent corrects the change immediately.
14
+ */
15
+
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ import { execSync } from 'child_process';
19
+
20
+ const AGENTFUL_OWNER = 'itz4blitz';
21
+
22
+ function getOriginOwner() {
23
+ try {
24
+ const remote = execSync('git config --get remote.origin.url', {
25
+ encoding: 'utf8',
26
+ stdio: ['ignore', 'pipe', 'ignore']
27
+ }).trim();
28
+
29
+ if (!remote) return null;
30
+
31
+ const httpsMatch = remote.match(/github\.com[:/]+([^/]+)\/[^/]+(?:\.git)?$/i);
32
+ if (httpsMatch?.[1]) {
33
+ return httpsMatch[1].toLowerCase();
34
+ }
35
+
36
+ return null;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ function resolveFilePath(filePath) {
43
+ if (!filePath) return null;
44
+ return path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath);
45
+ }
46
+
47
+ function isPackageJsonTarget(filePath) {
48
+ if (!filePath) return false;
49
+ const normalized = filePath.replace(/\\/g, '/');
50
+ return path.basename(normalized) === 'package.json' && !normalized.includes('/node_modules/');
51
+ }
52
+
53
+ function inspectPackageMetadata(packageJsonPath, originOwner) {
54
+ const issues = [];
55
+
56
+ let pkg;
57
+ try {
58
+ pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
59
+ } catch {
60
+ return issues;
61
+ }
62
+
63
+ if (!pkg.repository || typeof pkg.repository !== 'object') {
64
+ return issues;
65
+ }
66
+
67
+ const repoType = pkg.repository.type;
68
+ if (typeof repoType === 'string' && repoType.trim().toLowerCase() !== 'git') {
69
+ issues.push(`repository.type is "${repoType}" (expected "git")`);
70
+ }
71
+
72
+ const repoUrl = pkg.repository.url;
73
+ if (typeof repoUrl === 'string') {
74
+ const ownerInUrl = repoUrl.match(/github\.com[:/]+([^/]+)\//i)?.[1]?.toLowerCase();
75
+ if (ownerInUrl === AGENTFUL_OWNER && originOwner && originOwner !== AGENTFUL_OWNER) {
76
+ issues.push(`repository.url points to "${AGENTFUL_OWNER}" but git remote owner is "${originOwner}"`);
77
+ }
78
+ }
79
+
80
+ return issues;
81
+ }
82
+
83
+ function main() {
84
+ const filePath = process.env.FILE || '';
85
+ const toolName = process.env.TOOL_NAME || '';
86
+
87
+ if (toolName !== 'Write' && toolName !== 'Edit') {
88
+ process.exit(0);
89
+ }
90
+
91
+ if (!isPackageJsonTarget(filePath)) {
92
+ process.exit(0);
93
+ }
94
+
95
+ const absolutePath = resolveFilePath(filePath);
96
+ if (!absolutePath || !fs.existsSync(absolutePath)) {
97
+ process.exit(0);
98
+ }
99
+
100
+ const originOwner = getOriginOwner();
101
+ const issues = inspectPackageMetadata(absolutePath, originOwner);
102
+
103
+ if (issues.length === 0) {
104
+ process.exit(0);
105
+ }
106
+
107
+ console.error('\n❌ BLOCKED: Suspicious package.json metadata change\n');
108
+ console.error(`File: ${filePath}`);
109
+ for (const issue of issues) {
110
+ console.error(`- ${issue}`);
111
+ }
112
+ console.error('\nExpected behavior: preserve project ownership metadata and keep repository.type as "git".');
113
+ console.error('Action: restore the package.json metadata to match the current project repository.\n');
114
+
115
+ process.exit(1);
116
+ }
117
+
118
+ main();
@@ -29,15 +29,42 @@ try {
29
29
 
30
30
  /**
31
31
  * Detect if TeammateTool (parallel execution) is enabled
32
+ * Supports Windows, macOS, and Linux
32
33
  */
33
34
  function detectParallelExecution() {
35
+ // Check environment variable first (user can set AGENTFUL_PARALLEL=true)
36
+ if (process.env.AGENTFUL_PARALLEL === 'true') {
37
+ return { enabled: true, method: 'env_var' };
38
+ }
39
+
34
40
  try {
35
- // Find Claude Code binary
36
- const npmRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
37
- const cliPath = path.join(npmRoot, '@anthropic-ai', 'claude-code', 'cli.js');
41
+ // Find Claude Code binary - try multiple paths for Windows/Unix
42
+ let cliPath = null;
43
+ const possiblePaths = [
44
+ // Unix npm global
45
+ '/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js',
46
+ // Homebrew on macOS
47
+ '/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js',
48
+ ];
49
+
50
+ // npm root -g can throw on Windows if npm isn't in PATH
51
+ try {
52
+ const npmRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
53
+ possiblePaths.unshift(path.join(npmRoot, '@anthropic-ai', 'claude-code', 'cli.js'));
54
+ } catch {
55
+ // npm not available - continue with static paths
56
+ }
57
+
58
+ for (const p of possiblePaths) {
59
+ if (fs.existsSync(p)) {
60
+ cliPath = p;
61
+ break;
62
+ }
63
+ }
38
64
 
39
- if (!fs.existsSync(cliPath)) {
40
- return { enabled: false, reason: 'Claude Code binary not found' };
65
+ if (!cliPath) {
66
+ // Assume enabled if we can't find CLI (newer versions have it by default)
67
+ return { enabled: true, method: 'assumed' };
41
68
  }
42
69
 
43
70
  // Check for TeammateTool pattern
package/lib/presets.js CHANGED
@@ -19,7 +19,7 @@ export const presets = {
19
19
  description: 'Complete agentful installation (recommended)',
20
20
  agents: ['orchestrator', 'architect', 'backend', 'frontend', 'tester', 'reviewer', 'fixer', 'product-analyzer'],
21
21
  skills: ['product-tracking', 'validation', 'testing', 'conversation', 'product-planning', 'deployment', 'research'],
22
- hooks: ['session-start', 'health-check', 'block-random-docs', 'block-file-creation', 'product-spec-watcher', 'architect-drift-detector', 'analyze-trigger'],
22
+ hooks: ['session-start', 'health-check', 'mcp-health-check', 'block-random-docs', 'block-file-creation', 'product-spec-watcher', 'architect-drift-detector', 'analyze-trigger', 'package-metadata-guard'],
23
23
  gates: ['types', 'tests', 'coverage', 'lint', 'security', 'dead-code']
24
24
  },
25
25
 
@@ -47,6 +47,16 @@ export const hookConfigurations = {
47
47
  }
48
48
  },
49
49
 
50
+ 'mcp-health-check': {
51
+ event: 'SessionStart',
52
+ config: {
53
+ type: 'command',
54
+ command: 'node bin/hooks/mcp-health-check.js',
55
+ timeout: 3,
56
+ description: 'Verify agentful MCP server is configured'
57
+ }
58
+ },
59
+
50
60
  'block-random-docs': {
51
61
  event: 'PreToolUse',
52
62
  matcher: 'Write',
@@ -151,6 +161,17 @@ export const hookConfigurations = {
151
161
  timeout: 3,
152
162
  description: 'Watch for product spec changes and auto-trigger generation'
153
163
  }
164
+ },
165
+
166
+ 'package-metadata-guard': {
167
+ event: 'PostToolUse',
168
+ matcher: 'Write|Edit',
169
+ config: {
170
+ type: 'command',
171
+ command: 'node bin/hooks/package-metadata-guard.js',
172
+ timeout: 3,
173
+ description: 'Protect package.json ownership metadata from accidental corruption'
174
+ }
154
175
  }
155
176
  };
156
177
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itz4blitz/agentful",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "Pre-configured AI toolkit with self-hosted execution - works with any LLM, any tech stack, any platform.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,16 +16,14 @@
16
16
  "lint": "eslint .",
17
17
  "release": "semantic-release",
18
18
  "release:dry-run": "semantic-release --dry-run",
19
- "docs:dev": "vocs dev",
19
+ "docs:dev": "vocs dev --host 127.0.0.1 --port 8080",
20
20
  "docs:build": "vocs build",
21
21
  "docs:preview": "vocs preview",
22
- "build:all": "npm run build:cli && npm run build:studio && npm run build:mcp",
23
- "build:cli": "cd packages/cli && npm run build",
24
- "build:studio": "cd packages/studio && npm run build",
22
+ "build:all": "npm run build:mcp",
25
23
  "build:mcp": "cd packages/mcp-server && npm run build",
26
- "dev:studio": "cd packages/studio && npm run dev",
27
- "watch:studio": "cd packages/studio && npm run watch",
28
- "install:all": "npm install && cd packages/cli && npm install && cd ../studio && npm install && cd ../mcp-server && npm install && cd ../shared && npm install"
24
+ "test:mcp": "cd packages/mcp-server && npm run test",
25
+ "dev:mcp": "cd packages/mcp-server && npm run dev",
26
+ "install:all": "npm install && cd packages/mcp-server && npm install"
29
27
  },
30
28
  "exports": {
31
29
  ".": {
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "PreToolUse": [
27
27
  {
28
- "tools": ["Write", "Edit"],
28
+ "matcher": "Write|Edit|NotebookEdit",
29
29
  "hooks": [
30
30
  {
31
31
  "type": "command",
@@ -36,7 +36,7 @@
36
36
  ]
37
37
  },
38
38
  {
39
- "tools": ["Write", "Edit"],
39
+ "matcher": "Write|Edit|NotebookEdit",
40
40
  "hooks": [
41
41
  {
42
42
  "type": "command",
@@ -47,7 +47,7 @@
47
47
  ]
48
48
  },
49
49
  {
50
- "tools": ["Write"],
50
+ "matcher": "Write",
51
51
  "hooks": [
52
52
  {
53
53
  "type": "command",
@@ -60,7 +60,7 @@
60
60
  ],
61
61
  "PostToolUse": [
62
62
  {
63
- "matcher": "Write|Edit",
63
+ "matcher": "Write|Edit|NotebookEdit",
64
64
  "hooks": [
65
65
  {
66
66
  "type": "command",
@@ -71,7 +71,7 @@
71
71
  ]
72
72
  },
73
73
  {
74
- "matcher": "Write|Edit",
74
+ "matcher": "Write|Edit|NotebookEdit",
75
75
  "hooks": [
76
76
  {
77
77
  "type": "command",
@@ -82,7 +82,7 @@
82
82
  ]
83
83
  },
84
84
  {
85
- "matcher": "Write|Edit",
85
+ "matcher": "Write|Edit|NotebookEdit",
86
86
  "hooks": [
87
87
  {
88
88
  "type": "command",
@@ -93,7 +93,18 @@
93
93
  ]
94
94
  },
95
95
  {
96
- "matcher": "Write|Edit",
96
+ "matcher": "Write|Edit|NotebookEdit",
97
+ "hooks": [
98
+ {
99
+ "type": "command",
100
+ "command": "node bin/hooks/package-metadata-guard.js",
101
+ "timeout": 3,
102
+ "description": "Protect package.json ownership metadata from accidental corruption"
103
+ }
104
+ ]
105
+ },
106
+ {
107
+ "matcher": "Write|Edit|NotebookEdit",
97
108
  "hooks": [
98
109
  {
99
110
  "type": "command",
@@ -102,7 +113,7 @@
102
113
  ]
103
114
  },
104
115
  {
105
- "matcher": "Write|Edit",
116
+ "matcher": "Write|Edit|NotebookEdit",
106
117
  "hooks": [
107
118
  {
108
119
  "type": "command",
@@ -132,4 +143,4 @@
132
143
  "Bash(mkfs:*)"
133
144
  ]
134
145
  }
135
- }
146
+ }
@@ -20,7 +20,7 @@ npx @itz4blitz/agentful init --agents=orchestrator,backend --skills=validation
20
20
  npx @itz4blitz/agentful presets
21
21
  ```
22
22
 
23
- **[agentful.app/configure](https://agentful.app/configure)** - Interactive web configurator for shareable configurations
23
+ Use `npx @itz4blitz/agentful presets` to see local installation options.
24
24
 
25
25
  ## Quick Start
26
26
 
@@ -78,6 +78,15 @@ Without MCP: agents start from scratch every session. With MCP: agents compound
78
78
 
79
79
  See [Hooks System](#hooks-system) below for manual setup or other editors.
80
80
 
81
+ ## Metadata Safety (Critical)
82
+
83
+ When editing `package.json` (or other repo metadata files), preserve project ownership:
84
+
85
+ - Never change `repository.url`, `homepage`, `bugs.url`, `name` scope, or `author` to agentful maintainer values unless the project explicitly asks.
86
+ - Keep `repository.type` as `"git"` when a `repository` object exists.
87
+ - Do not infer repo owner from agentful package names/docs (`@itz4blitz/agentful`); derive owner from the current repository remote instead.
88
+ - Prefer structured JSON edits (JSON path / `jq` / `npm pkg`) instead of broad text replace on `"type"` or `"url"`.
89
+
81
90
  ## Commands
82
91
 
83
92
  | Command | Description | When to Use |
@@ -286,7 +295,6 @@ Agentful uses Claude Code hooks for automation, protection, and intelligent cont
286
295
  - ✅ `.claude/skills/*/SKILL.md` - Skill documentation
287
296
  - ✅ `.claude/product/**/*.md` - Product specifications
288
297
  - ✅ `template/**/*.md` - Template files
289
- - ✅ `examples/**/*.md` - Example documentation
290
298
  - **Allowed if parent directory exists**:
291
299
  - 📁 `docs/*.md`, `docs/pages/*.mdx` - Requires `docs/` directory
292
300
  - 📁 `documentation/*.md` - Requires `documentation/` directory
@@ -16,7 +16,6 @@
16
16
  * - .claude/skills/*\/SKILL.md (skill documentation)
17
17
  * - .claude/product/**\/*.md (product specifications)
18
18
  * - template/**\/*.md (template files)
19
- * - examples/**\/*.md (example documentation)
20
19
  *
21
20
  * Blocked:
22
21
  * - Random *.md files in project root
package/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.8.0"
2
+ "version": "1.8.1"
3
3
  }