attacca-forge 0.5.0 → 0.5.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.
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
3
+ "name": "attacca-forge",
4
+ "description": "Open-source AI agent development toolkit: spec writing, evaluation, intent engineering, and build orchestration for production-grade agents",
5
+ "owner": {
6
+ "name": "Attacca"
7
+ },
8
+ "plugins": [
9
+ {
10
+ "name": "attacca-forge",
11
+ "description": "AI agent development methodology — design, evaluate, and align autonomous agents with production-grade specifications, factorial stress testing, and intent engineering",
12
+ "version": "0.5.2",
13
+ "author": {
14
+ "name": "Attacca"
15
+ },
16
+ "source": "./plugins/attacca-forge",
17
+ "category": "development"
18
+ }
19
+ ]
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "attacca-forge",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Spec-driven AI development toolkit — design, evaluate, stress-test, and certify autonomous agents from your terminal",
5
5
  "keywords": [
6
6
  "ai",
@@ -33,6 +33,7 @@
33
33
  "bin/",
34
34
  "src/",
35
35
  "plugins/",
36
+ ".claude-plugin/",
36
37
  "docs/",
37
38
  "examples/",
38
39
  "LICENSE",
@@ -2,10 +2,86 @@
2
2
  // attacca-forge install — Install skills into Claude Code plugin directory
3
3
  // =============================================================================
4
4
 
5
- import { cpSync, existsSync, mkdirSync } from 'node:fs';
5
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
  import { getClaudeDir, getPluginDir, isClaudeInstalled } from '../utils/detect-claude.js';
8
8
 
9
+ const MARKETPLACE_NAME = 'attacca-forge';
10
+ const PLUGIN_NAME = 'attacca-forge';
11
+
12
+ function readJsonFile(path) {
13
+ try {
14
+ return JSON.parse(readFileSync(path, 'utf-8'));
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ function writeJsonFile(path, data) {
21
+ writeFileSync(path, JSON.stringify(data, null, 2) + '\n', 'utf-8');
22
+ }
23
+
24
+ function registerMarketplace(claudeDir, targetDir) {
25
+ const marketplacesPath = join(claudeDir, 'plugins', 'known_marketplaces.json');
26
+ const marketplaces = readJsonFile(marketplacesPath) || {};
27
+
28
+ if (marketplaces[MARKETPLACE_NAME]) {
29
+ marketplaces[MARKETPLACE_NAME].lastUpdated = new Date().toISOString();
30
+ } else {
31
+ marketplaces[MARKETPLACE_NAME] = {
32
+ source: {
33
+ source: 'directory',
34
+ path: targetDir,
35
+ },
36
+ installLocation: targetDir,
37
+ lastUpdated: new Date().toISOString(),
38
+ };
39
+ }
40
+
41
+ writeJsonFile(marketplacesPath, marketplaces);
42
+ return true;
43
+ }
44
+
45
+ function registerPlugin(claudeDir, pluginDir, version) {
46
+ const installedPath = join(claudeDir, 'plugins', 'installed_plugins.json');
47
+ const installed = readJsonFile(installedPath) || { version: 2, plugins: {} };
48
+
49
+ if (!installed.version) installed.version = 2;
50
+ if (!installed.plugins) installed.plugins = {};
51
+
52
+ const pluginKey = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
53
+ const now = new Date().toISOString();
54
+
55
+ // Set up cache directory and copy skills there
56
+ const cachePath = join(claudeDir, 'plugins', 'cache', MARKETPLACE_NAME, PLUGIN_NAME, version);
57
+ if (!existsSync(cachePath)) mkdirSync(cachePath, { recursive: true });
58
+
59
+ const sourceSkills = join(pluginDir, 'skills');
60
+ const cacheSkills = join(cachePath, 'skills');
61
+ if (existsSync(sourceSkills)) {
62
+ cpSync(sourceSkills, cacheSkills, { recursive: true, force: true });
63
+ }
64
+
65
+ if (installed.plugins[pluginKey]) {
66
+ installed.plugins[pluginKey][0].version = version;
67
+ installed.plugins[pluginKey][0].installPath = cachePath;
68
+ installed.plugins[pluginKey][0].lastUpdated = now;
69
+ } else {
70
+ installed.plugins[pluginKey] = [
71
+ {
72
+ scope: 'user',
73
+ installPath: cachePath,
74
+ version,
75
+ installedAt: now,
76
+ lastUpdated: now,
77
+ },
78
+ ];
79
+ }
80
+
81
+ writeJsonFile(installedPath, installed);
82
+ return true;
83
+ }
84
+
9
85
  export default async function install({ args, cwd, rootDir }) {
10
86
  console.log('\n Attacca Forge — Install Skills');
11
87
  console.log(' ===============================\n');
@@ -22,44 +98,68 @@ export default async function install({ args, cwd, rootDir }) {
22
98
 
23
99
  console.log(' ✓ Claude Code detected');
24
100
 
25
- // Source plugin directory (shipped with this package)
101
+ // Source directories (shipped with this package)
102
+ // Repo layout:
103
+ // .claude-plugin/marketplace.json ← marketplace descriptor
104
+ // plugins/attacca-forge/ ← plugin with .claude-plugin/plugin.json + skills/
105
+ const sourceMarketplace = join(rootDir, '.claude-plugin', 'marketplace.json');
26
106
  const sourcePlugin = join(rootDir, 'plugins', 'attacca-forge');
107
+
27
108
  if (!existsSync(sourcePlugin)) {
28
109
  console.error(' ✗ Plugin source not found. Package may be corrupted.');
29
110
  process.exit(1);
30
111
  }
31
112
 
32
- // Target plugin directory
33
- const targetDir = getPluginDir();
34
- const localDir = join(getClaudeDir(), 'plugins', 'local');
113
+ // Read version from package.json
114
+ const pkg = readJsonFile(join(rootDir, 'package.json'));
115
+ const version = pkg?.version || '0.5.2';
35
116
 
36
- // Create directories
37
- if (!existsSync(localDir)) mkdirSync(localDir, { recursive: true });
117
+ // Target: ~/.claude/plugins/local/attacca-forge/
118
+ // Installed layout (mirrors nirbound-marketplace):
119
+ // .claude-plugin/marketplace.json
120
+ // plugins/attacca-forge/.claude-plugin/plugin.json
121
+ // plugins/attacca-forge/skills/
122
+ const claudeDir = getClaudeDir();
123
+ const targetDir = getPluginDir();
38
124
 
39
- // Copy plugin files
40
125
  const existed = existsSync(targetDir);
41
- cpSync(sourcePlugin, targetDir, { recursive: true, force: true });
42
-
43
- // Also copy root marketplace plugin.json
44
- const rootPluginJson = join(rootDir, '.claude-plugin');
45
- const targetRootPlugin = join(getClaudeDir(), 'plugins', 'local', 'attacca-forge-root', '.claude-plugin');
46
- if (existsSync(rootPluginJson)) {
47
- mkdirSync(join(getClaudeDir(), 'plugins', 'local', 'attacca-forge-root'), { recursive: true });
48
- cpSync(rootPluginJson, targetRootPlugin, { recursive: true, force: true });
126
+ if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
127
+
128
+ // 1. Copy marketplace descriptor to target root
129
+ const targetMarketplaceDir = join(targetDir, '.claude-plugin');
130
+ if (!existsSync(targetMarketplaceDir)) mkdirSync(targetMarketplaceDir, { recursive: true });
131
+ if (existsSync(sourceMarketplace)) {
132
+ cpSync(sourceMarketplace, join(targetMarketplaceDir, 'marketplace.json'), { force: true });
49
133
  }
50
134
 
135
+ // 2. Copy plugin (with .claude-plugin/plugin.json + skills/) into nested directory
136
+ const targetPlugin = join(targetDir, 'plugins', 'attacca-forge');
137
+ if (!existsSync(targetPlugin)) mkdirSync(targetPlugin, { recursive: true });
138
+ cpSync(sourcePlugin, targetPlugin, { recursive: true, force: true });
139
+
51
140
  // Count skills
52
- const skillsDir = join(targetDir, 'skills');
141
+ const targetSkills = join(targetPlugin, 'skills');
53
142
  let skillCount = 0;
54
- if (existsSync(skillsDir)) {
55
- const { readdirSync } = await import('node:fs');
56
- skillCount = readdirSync(skillsDir).filter((f) => {
57
- return existsSync(join(skillsDir, f, 'SKILL.md'));
143
+ if (existsSync(targetSkills)) {
144
+ skillCount = readdirSync(targetSkills).filter((f) => {
145
+ return existsSync(join(targetSkills, f, 'SKILL.md'));
58
146
  }).length;
59
147
  }
60
148
 
61
149
  console.log(` ✓ ${existed ? 'Updated' : 'Installed'} ${skillCount} skills to Claude Code`);
62
150
  console.log(` Location: ${targetDir}`);
151
+
152
+ // 3. Register marketplace in known_marketplaces.json
153
+ const pluginsDir = join(claudeDir, 'plugins');
154
+ if (!existsSync(pluginsDir)) mkdirSync(pluginsDir, { recursive: true });
155
+
156
+ registerMarketplace(claudeDir, targetDir);
157
+ console.log(' ✓ Marketplace registered');
158
+
159
+ // 4. Register plugin in installed_plugins.json + populate cache
160
+ registerPlugin(claudeDir, targetPlugin, version);
161
+ console.log(' ✓ Plugin registered');
162
+
63
163
  console.log('');
64
164
  console.log(' Restart Claude Code to load the new skills.');
65
165
  console.log('');