@givolt/i3-composer-agent 0.6.12 → 1.0.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.
Files changed (37) hide show
  1. package/.agents/plugins/marketplace.json +3 -2
  2. package/.claude-plugin/marketplace.json +2 -1
  3. package/.claude-plugin/plugin.json +3 -1
  4. package/.codex-plugin/plugin.json +3 -1
  5. package/CHANGELOG.md +23 -0
  6. package/README.md +77 -137
  7. package/bin/i3-composer-agent.cjs +6 -3
  8. package/docs/antigravity-setup.md +1 -1
  9. package/docs/comparison.md +1 -1
  10. package/hooks/sdd-cache-post.sh +0 -0
  11. package/hooks/sdd-cache-pre.sh +0 -0
  12. package/package.json +75 -17
  13. package/plugin.json +4 -2
  14. package/rebrand/MAINTENANCE.md +92 -31
  15. package/rebrand/NOTICE.md +9 -0
  16. package/rebrand/PR-WORKFLOW.md +34 -0
  17. package/rebrand/UNIFICATION-SPEC.md +33 -0
  18. package/rebrand/VALIDATION.md +28 -0
  19. package/rebrand/VERSIONING.md +32 -0
  20. package/rebrand/apply.cjs +80 -7
  21. package/rebrand/brand.json +6 -2
  22. package/rebrand/catalog.cjs +56 -0
  23. package/rebrand/fork-patches.json +12 -0
  24. package/rebrand/overlay/.github/workflows/test-plugin-install.yml +177 -0
  25. package/rebrand/overlay/CHANGELOG.md +23 -0
  26. package/rebrand/overlay/bin/i3-composer-agent.cjs +122 -0
  27. package/rebrand/overlay/scripts/validate-brand.cjs +44 -0
  28. package/rebrand/overlay/tests/bundle.test.cjs +85 -0
  29. package/rebrand/package.template.json +82 -0
  30. package/rebrand/rebrand.sh +4 -0
  31. package/rebrand/record-upstream.cjs +37 -0
  32. package/rebrand/templates/README.md +44 -136
  33. package/rebrand/tests/pipeline.test.cjs +185 -0
  34. package/rebrand/upstream.json +171 -0
  35. package/scripts/validate-brand.cjs +11 -3
  36. package/skills/idea-refine/scripts/idea-refine.sh +0 -0
  37. package/tests/bundle.test.cjs +11 -5
@@ -0,0 +1,177 @@
1
+ name: Test Plugin Installation
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ validate-skills:
10
+ name: Validate skill content
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+ with:
15
+ fetch-depth: 0
16
+
17
+ - name: Set up Node.js
18
+ uses: actions/setup-node@v6
19
+ with:
20
+ node-version: '24'
21
+
22
+ - name: Validate all skills
23
+ run: node scripts/validate-skills.js
24
+
25
+ - name: Test skill lint rules
26
+ run: node --test scripts/lib/skill-lint-test.js
27
+
28
+ - name: Validate manifest versions
29
+ run: node scripts/validate-versions.js
30
+
31
+ - name: Validate i3 Composer Agent identity and replay
32
+ run: |
33
+ node scripts/validate-brand.cjs
34
+ node rebrand/apply.cjs --check
35
+
36
+ - name: Test npm bundle CLI
37
+ run: node --test tests/*.test.cjs rebrand/tests/*.test.cjs
38
+
39
+ - name: Test manifest version validator
40
+ run: node --test scripts/validate-versions-test.js
41
+
42
+ - name: Test skill eval runner
43
+ run: node --test scripts/run-evals-test.js
44
+
45
+ - name: Run skill evals (trigger + routing)
46
+ run: node scripts/run-evals.js --min-rank1 95
47
+
48
+ - name: Validate references/ links in skills
49
+ run: node scripts/validate-reference-links.js
50
+
51
+ - name: Test reference-link validator
52
+ run: node --test scripts/validate-reference-links-test.js
53
+
54
+ - name: Test floor-guard reference
55
+ run: node --test scripts/floor-guard-reference-test.js
56
+
57
+ - name: Test session-start hook payload
58
+ run: bash hooks/session-start-test.sh
59
+
60
+ validate-commands:
61
+ name: Validate command parity and description sync
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - uses: actions/checkout@v6
65
+
66
+ - name: Set up Node.js
67
+ uses: actions/setup-node@v6
68
+ with:
69
+ node-version: '24'
70
+
71
+ - name: Validate commands across all tool directories
72
+ run: node scripts/validate-commands.js
73
+
74
+ - name: Test command validator
75
+ run: node --test scripts/validate-commands-test.js
76
+
77
+ - name: Validate spec/plan/todo artifact paths
78
+ run: node scripts/validate-artifact-paths.js
79
+
80
+ - name: Test artifact-path validator
81
+ run: node --test scripts/validate-artifact-paths-test.js
82
+
83
+ # Ubuntu coverage stays in the existing jobs above, preserving their check names.
84
+ validate-platforms:
85
+ name: Validate tooling (${{ matrix.os }})
86
+ runs-on: ${{ matrix.os }}
87
+ strategy:
88
+ fail-fast: false
89
+ matrix:
90
+ os: [macos-latest, windows-latest]
91
+ steps:
92
+ - uses: actions/checkout@v6
93
+ with:
94
+ fetch-depth: 0
95
+
96
+ - name: Set up Node.js
97
+ uses: actions/setup-node@v6
98
+ with:
99
+ node-version: '24'
100
+
101
+ - name: Test validators and eval runner
102
+ run: >-
103
+ node --test
104
+ scripts/lib/skill-lint-test.js
105
+ scripts/validate-versions-test.js
106
+ scripts/run-evals-test.js
107
+ scripts/validate-reference-links-test.js
108
+ scripts/validate-commands-test.js
109
+ scripts/validate-artifact-paths-test.js
110
+ scripts/floor-guard-reference-test.js
111
+ tests/*.test.cjs
112
+ rebrand/tests/*.test.cjs
113
+
114
+ - name: Validate all skills
115
+ run: node scripts/validate-skills.js
116
+
117
+ - name: Validate manifest versions
118
+ run: node scripts/validate-versions.js
119
+
120
+ - name: Validate references/ links in skills
121
+ run: node scripts/validate-reference-links.js
122
+
123
+ - name: Validate commands across all tool directories
124
+ run: node scripts/validate-commands.js
125
+
126
+ - name: Validate spec/plan/todo artifact paths
127
+ run: node scripts/validate-artifact-paths.js
128
+
129
+ - name: Run skill evals (trigger + routing)
130
+ run: node scripts/run-evals.js --min-rank1 95
131
+
132
+ test-hooks:
133
+ name: Hook regression tests
134
+ runs-on: ubuntu-latest
135
+ steps:
136
+ - uses: actions/checkout@v6
137
+
138
+ - name: Test sdd-cache hooks (stubbed curl, no network)
139
+ run: bash hooks/sdd-cache-test.sh
140
+
141
+ - name: Test simplify-ignore hook
142
+ run: bash hooks/simplify-ignore-test.sh
143
+
144
+ validate:
145
+ name: Validate plugin structure
146
+ needs: [validate-skills, validate-commands, validate-platforms]
147
+ runs-on: ubuntu-latest
148
+ steps:
149
+ - uses: actions/checkout@v6
150
+
151
+ - name: Install Claude Code
152
+ run: npm install -g @anthropic-ai/claude-code
153
+
154
+ - name: Validate marketplace and plugin manifests
155
+ run: claude plugin validate .
156
+
157
+ test-install:
158
+ name: Test plugin installation
159
+ runs-on: ubuntu-latest
160
+ needs: validate
161
+ steps:
162
+ - uses: actions/checkout@v6
163
+
164
+ - name: Install Claude Code
165
+ run: npm install -g @anthropic-ai/claude-code
166
+
167
+ - name: Configure git to use HTTPS
168
+ run: git config --global url."https://github.com/".insteadOf "git@github.com:"
169
+
170
+ - name: Add marketplace
171
+ run: claude plugin marketplace add ./
172
+
173
+ - name: List marketplaces
174
+ run: claude plugin marketplace list
175
+
176
+ - name: Install plugin
177
+ run: claude plugin install i3-composer-agent@i3-composer-agent --scope user
@@ -0,0 +1,23 @@
1
+ # i3 Composer Agent changes
2
+
3
+ ## 1.0.0 — Unreleased
4
+
5
+ Upstream baseline: agent-skills 0.6.12 at commit 1401c8b8030e023baeebb31781a6653fe8e93026.
6
+
7
+ ### Changed
8
+
9
+ - Start independent i3 release numbering. The upstream baseline remains 0.6.12; this replaces the unpublished 0.6.13 candidate.
10
+ - Display the i3 release and upstream baseline together in the README, CLI help, and bundle preparation output. Keep --version machine-readable.
11
+ - Include the upstream version, commit, and repository in package metadata.
12
+ - Maintain source changes on agent/source, generate and validate a branded candidate, then update main in this independent repository.
13
+
14
+ ### Fixed
15
+
16
+ - Preserve complete reviewed runtime assets when replaying branding, including newly approved upstream skills and commands.
17
+ - Keep replay checks repeatable in npm bundles without repository CI files.
18
+ - Preserve branded ignore rules and exclude Finder metadata from runtime preservation checks.
19
+
20
+ ## 0.6.12
21
+
22
+ - Publish the first i3 Composer Agent npm distribution based on agent-skills 0.6.12.
23
+ - Retain upstream skill IDs, command workflows, specialist personas, shared references, and MIT attribution.
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const crypto = require('node:crypto');
7
+ const root = path.resolve(__dirname, '..');
8
+ const pkg = require('../package.json');
9
+ const name = 'i3-composer-agent';
10
+ const args = process.argv.slice(2);
11
+ const identity = `i3 Composer Agent v${pkg.version}\nBased on agent-skills v${pkg.i3ComposerAgent.upstreamVersion}`;
12
+ const help = `${identity}
13
+ ${pkg.name}
14
+
15
+ Prepare the complete i3 Composer Agent plugin, including shared references.
16
+
17
+ Usage:
18
+ npx ${pkg.name} --codebuddy [--dir <directory>]
19
+ npx ${pkg.name} --codex [--dir <directory>]
20
+ npx ${pkg.name} --claude [--dir <directory>]
21
+ npx ${pkg.name} --extract --dir <directory>
22
+
23
+ Options:
24
+ --dir <directory> Output directory (default: ./i3-composer-agent)
25
+ --dry-run Show the planned bundle without writing files
26
+ --force Replace an existing i3 bundle, preserving a backup
27
+ --version Print the package version
28
+ --help Show this help
29
+
30
+ Host flags prepare the bundle and print native registration commands.
31
+ Execute those commands in the intended host to enable the plugin.
32
+ `;
33
+
34
+ function quote(value) { return "'" + value.replaceAll("'", "'\\''") + "'"; }
35
+ function realDestination(target) {
36
+ let existing = target;
37
+ const suffix = [];
38
+ while (!fs.existsSync(existing)) {
39
+ suffix.unshift(path.basename(existing));
40
+ const parent = path.dirname(existing);
41
+ if (parent === existing) throw new Error(`Cannot resolve destination: ${target}`);
42
+ existing = parent;
43
+ }
44
+ return path.join(fs.realpathSync(existing), ...suffix);
45
+ }
46
+ function main() {
47
+ if (!args.length || args.includes('--help')) { console.log(help); return; }
48
+ if (args.length === 1 && args[0] === '--version') { console.log(pkg.version); return; }
49
+ let host;
50
+ let directory = path.join(process.cwd(), name);
51
+ let dryRun = false;
52
+ let force = false;
53
+ for (let i = 0; i < args.length; i++) {
54
+ const arg = args[i];
55
+ if (['--codebuddy', '--codex', '--claude', '--extract'].includes(arg)) {
56
+ if (host) throw new Error('Choose exactly one host flag or --extract.');
57
+ host = arg.slice(2);
58
+ } else if (arg === '--dir') {
59
+ if (!args[i + 1] || args[i + 1].startsWith('--')) throw new Error('--dir requires a directory.');
60
+ directory = path.resolve(args[++i]);
61
+ } else if (arg === '--dry-run') dryRun = true;
62
+ else if (arg === '--force') force = true;
63
+ else throw new Error(`Unknown option: ${arg}`);
64
+ }
65
+ if (!host) throw new Error('Choose --codebuddy, --codex, --claude, or --extract.');
66
+ directory = realDestination(directory);
67
+ const sourceRoot = fs.realpathSync(root);
68
+ if (directory === sourceRoot || directory.startsWith(sourceRoot + path.sep) || sourceRoot.startsWith(directory + path.sep)) {
69
+ throw new Error('The output directory must be outside this package directory.');
70
+ }
71
+ let exists = false;
72
+ try { fs.lstatSync(directory); exists = true; } catch (error) { if (error.code !== 'ENOENT') throw error; }
73
+ if (exists) {
74
+ if (!force) throw new Error(`Output already exists: ${directory}. Choose a new directory or use --force for an i3 bundle.`);
75
+ const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'plugin.json'), 'utf8'));
76
+ if (manifest.name !== name) throw new Error('Refusing to replace a directory belonging to another plugin.');
77
+ }
78
+ if (dryRun) {
79
+ console.log(JSON.stringify({ name, version: pkg.version, upstreamVersion: pkg.i3ComposerAgent.upstreamVersion, upstreamCommit: pkg.i3ComposerAgent.upstreamCommit, directory, host, files: pkg.i3ComposerAgent.pluginFiles, replacesExisting: exists }, null, 2));
80
+ return;
81
+ }
82
+ fs.mkdirSync(path.dirname(directory), { recursive: true });
83
+ const staging = fs.mkdtempSync(path.join(path.dirname(directory), `.${name}-`));
84
+ let backup;
85
+ try {
86
+ for (const relative of pkg.i3ComposerAgent.pluginFiles) {
87
+ const source = path.join(root, relative);
88
+ const destination = path.join(staging, relative);
89
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
90
+ fs.cpSync(source, destination, {
91
+ recursive: true,
92
+ filter: file => {
93
+ if (path.basename(file) === '.DS_Store') return false;
94
+ if (fs.lstatSync(file).isSymbolicLink()) throw new Error(`Bundle contains a symlink: ${file}`);
95
+ return !/(?:^|[\\/])(?:results|node_modules|\.git)(?:[\\/]|$)/.test(path.relative(root, file));
96
+ },
97
+ });
98
+ }
99
+ if (exists) {
100
+ backup = `${directory}.backup-${crypto.randomUUID()}`;
101
+ fs.renameSync(directory, backup);
102
+ }
103
+ try { fs.renameSync(staging, directory); }
104
+ catch (error) { if (backup) fs.renameSync(backup, directory); throw error; }
105
+ } finally { if (fs.existsSync(staging)) fs.rmSync(staging, { recursive: true, force: true }); }
106
+ console.log(identity);
107
+ console.log(`Prepared ${name} at ${directory}`);
108
+ if (backup) console.log(`Previous bundle preserved at ${backup}`);
109
+ if (host === 'extract') return;
110
+ console.log('\nRegister and enable the plugin with these native commands:');
111
+ const selector = `${name}@${name}`;
112
+ if (host === 'codex') {
113
+ console.log(`codex plugin marketplace add ${quote(directory)}`);
114
+ console.log(`codex plugin add ${selector}`);
115
+ console.log('Start a new Codex session and invoke the existing skill names. Lifecycle slash-command adaptation is separate.');
116
+ } else {
117
+ console.log(`/plugin marketplace add ${quote(directory)}`);
118
+ console.log(`/plugin install ${selector}`);
119
+ console.log(`Use /${name}:spec, /${name}:plan, /${name}:build, and /${name}:ship in ${host === 'claude' ? 'Claude Code' : 'CodeBuddy'}.`);
120
+ }
121
+ }
122
+ try { main(); } catch (error) { console.error(`${name}: ${error.message}`); process.exitCode = 1; }
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const root = path.resolve(__dirname, '..');
8
+ const brand = require('../rebrand/brand.json');
9
+ const pkg = require('../package.json');
10
+ const upstream = require('../rebrand/upstream.json');
11
+ const { validateCatalog } = require('../rebrand/catalog.cjs');
12
+ validateCatalog(root, upstream);
13
+ function json(relative) { return JSON.parse(fs.readFileSync(path.join(root, relative), 'utf8')); }
14
+ assert.equal(pkg.name, brand.npmName);
15
+ assert.equal(pkg.version, brand.releaseVersion);
16
+ assert.equal(brand.upstreamCommit, upstream.upstreamCommit);
17
+ assert.equal(pkg.i3ComposerAgent.upstreamVersion, upstream.upstreamVersion);
18
+ assert.equal(pkg.i3ComposerAgent.upstreamCommit, upstream.upstreamCommit);
19
+ assert.equal(pkg.i3ComposerAgent.upstreamRepository, brand.upstream);
20
+ assert.equal(Object.keys(pkg.bin).length, 1, 'npx must resolve one executable');
21
+ assert.ok(fs.existsSync(path.join(root, pkg.bin[brand.name])));
22
+ for (const relative of ['plugin.json', '.claude-plugin/plugin.json', '.codex-plugin/plugin.json']) {
23
+ assert.equal(json(relative).name, brand.name, `${relative}: inconsistent plugin identity`);
24
+ }
25
+ for (const relative of ['.claude-plugin/marketplace.json', '.agents/plugins/marketplace.json']) {
26
+ const marketplace = json(relative);
27
+ assert.equal(marketplace.name, brand.name);
28
+ assert.equal(marketplace.plugins[0].name, brand.name);
29
+ assert.ok(marketplace.plugins[0].source === './' || marketplace.plugins[0].source.path === './', 'marketplace must load this distribution');
30
+ }
31
+ assert.ok(json('.codex-plugin/plugin.json').interface.shortDescription.length <= 30);
32
+ const skills = fs.readdirSync(path.join(root, 'skills')).filter(name => fs.existsSync(path.join(root, 'skills', name, 'SKILL.md')));
33
+ assert.equal(skills.length, upstream.skills.length, 'brand conversion must preserve the reviewed upstream skills');
34
+ assert.ok(skills.includes('using-agent-skills'), 'meta-skill ID must remain stable');
35
+ const commands = fs.readdirSync(path.join(root, '.claude/commands')).filter(name => name.endsWith('.md'));
36
+ assert.equal(commands.length, upstream.commands.length, 'brand conversion must preserve the reviewed upstream commands');
37
+ for (const command of commands) {
38
+ const body = fs.readFileSync(path.join(root, '.claude/commands', command), 'utf8');
39
+ assert.ok(!body.includes('agent-skills:'), `${command}: stale plugin namespace`);
40
+ for (const match of body.matchAll(/i3-composer-agent:([a-z-]+)/g)) assert.ok(skills.includes(match[1]), `${command}: unknown skill ${match[1]}`);
41
+ }
42
+ for (const relative of pkg.i3ComposerAgent.pluginFiles) assert.ok(fs.existsSync(path.join(root, relative)), `bundle is missing ${relative}`);
43
+ assert.match(fs.readFileSync(path.join(root, 'LICENSE'), 'utf8'), /Copyright \(c\) 2025 Addy Osmani/);
44
+ console.log('Brand identity, package entrypoint, reviewed upstream catalog, and attribution are consistent.');
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ const test = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const { spawnSync } = require('node:child_process');
9
+ const root = path.resolve(__dirname, '..');
10
+ const upstream = require('../rebrand/upstream.json');
11
+ const cli = path.join(root, 'bin/i3-composer-agent.cjs');
12
+ function run(args) { return spawnSync(process.execPath, [cli, ...args], { encoding: 'utf8' }); }
13
+ function workspace(t) {
14
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'i3-agent-bundle-'));
15
+ t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
16
+ return directory;
17
+ }
18
+ test('help and version identify the package without preparing files', () => {
19
+ const help = run(['--help']).stdout;
20
+ assert.match(help, /@givolt\/i3-composer-agent/);
21
+ assert.ok(help.includes(`i3 Composer Agent v${require('../package.json').version}`));
22
+ assert.ok(help.includes(`Based on agent-skills v${upstream.upstreamVersion}`));
23
+ assert.equal(run(['--version']).stdout.trim(), require('../package.json').version);
24
+ });
25
+ test('dry run and invalid arguments leave the target untouched', t => {
26
+ const target = path.join(workspace(t), 'plugin');
27
+ assert.equal(run(['--codex', '--dir', target, '--dry-run']).status, 0);
28
+ assert.equal(fs.existsSync(target), false);
29
+ assert.equal(run(['--codebuddy', '--codex', '--dir', target]).status, 1);
30
+ assert.equal(run(['--extract', '--dir']).status, 1);
31
+ assert.equal(fs.existsSync(target), false);
32
+ });
33
+ test('complete bundle preserves reference links and all host assets at a path with spaces', t => {
34
+ const target = path.join(workspace(t), 'plugin with spaces');
35
+ const result = run(['--codebuddy', '--dir', target]);
36
+ assert.equal(result.status, 0, result.stderr);
37
+ assert.ok(result.stdout.includes(`i3 Composer Agent v${require('../package.json').version}`));
38
+ assert.ok(result.stdout.includes(`Based on agent-skills v${upstream.upstreamVersion}`));
39
+ assert.match(result.stdout, /\/plugin install i3-composer-agent@i3-composer-agent/);
40
+ assert.equal(fs.readdirSync(path.join(target, 'skills')).length, upstream.skills.length);
41
+ assert.equal(fs.readdirSync(path.join(target, '.claude/commands')).length, upstream.commands.length);
42
+ assert.equal(fs.readdirSync(path.join(target, 'agents')).filter(name => name.endsWith('.md')).length, upstream.personas.length);
43
+ assert.equal(JSON.parse(fs.readFileSync(path.join(target, '.codex-plugin/plugin.json'), 'utf8')).name, 'i3-composer-agent');
44
+ const check = spawnSync(process.execPath, [path.join(target, 'scripts/validate-reference-links.js')], { cwd: target, encoding: 'utf8' });
45
+ assert.equal(check.status, 0, check.stdout + check.stderr);
46
+ assert.equal(fs.existsSync(path.join(target, 'AGENTS.md')), false);
47
+ assert.equal(fs.existsSync(path.join(target, 'CLAUDE.md')), false);
48
+ });
49
+ test('existing unrelated files cannot be replaced even with force', t => {
50
+ const target = path.join(workspace(t), 'existing');
51
+ fs.mkdirSync(target);
52
+ fs.writeFileSync(path.join(target, 'plugin.json'), '{"name":"another-plugin"}\n');
53
+ fs.writeFileSync(path.join(target, 'keep.txt'), 'business data');
54
+ assert.equal(run(['--extract', '--dir', target]).status, 1);
55
+ assert.equal(run(['--extract', '--dir', target, '--force']).status, 1);
56
+ assert.equal(fs.readFileSync(path.join(target, 'keep.txt'), 'utf8'), 'business data');
57
+ });
58
+ test('installed packages beneath node_modules remain fully extractable', t => {
59
+ const parent = workspace(t);
60
+ const installed = path.join(parent, 'node_modules', '@givolt', 'i3-composer-agent');
61
+ assert.equal(run(['--extract', '--dir', installed]).status, 0);
62
+ const target = path.join(parent, 'plugin');
63
+ const result = spawnSync(process.execPath, [path.join(installed, 'bin/i3-composer-agent.cjs'), '--extract', '--dir', target], { encoding: 'utf8' });
64
+ assert.equal(result.status, 0, result.stderr);
65
+ assert.equal(JSON.parse(fs.readFileSync(path.join(target, 'package.json'), 'utf8')).name, '@givolt/i3-composer-agent');
66
+ assert.equal(fs.readdirSync(path.join(target, 'skills')).length, upstream.skills.length);
67
+ });
68
+ test('replacement preserves the complete previous bundle in a backup', t => {
69
+ const parent = workspace(t);
70
+ const target = path.join(parent, 'plugin');
71
+ assert.equal(run(['--extract', '--dir', target]).status, 0);
72
+ fs.writeFileSync(path.join(target, 'custom-note.txt'), 'preserve me');
73
+ const result = run(['--extract', '--dir', target, '--force']);
74
+ assert.equal(result.status, 0, result.stderr);
75
+ const backups = fs.readdirSync(parent).filter(name => name.startsWith('plugin.backup-'));
76
+ assert.equal(backups.length, 1);
77
+ assert.equal(fs.readFileSync(path.join(parent, backups[0], 'custom-note.txt'), 'utf8'), 'preserve me');
78
+ assert.equal(fs.existsSync(path.join(target, 'custom-note.txt')), false);
79
+ });
80
+ test('source directories and aliases cannot be overwritten', t => {
81
+ assert.equal(run(['--extract', '--dir', root, '--force']).status, 1);
82
+ const alias = path.join(workspace(t), 'source-alias');
83
+ fs.symlinkSync(root, alias, 'dir');
84
+ assert.equal(run(['--extract', '--dir', alias, '--force']).status, 1);
85
+ });
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@givolt/i3-composer-agent",
3
+ "description": "i3 Composer Agent: production-grade engineering skills and lifecycle commands for AI coding agents.",
4
+ "license": "MIT",
5
+ "author": "Givolt",
6
+ "contributors": [
7
+ {
8
+ "name": "Addy Osmani"
9
+ }
10
+ ],
11
+ "bin": {
12
+ "i3-composer-agent": "bin/i3-composer-agent.cjs"
13
+ },
14
+ "engines": {
15
+ "node": ">=20"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "registry": "https://registry.npmjs.org/"
20
+ },
21
+ "files": [
22
+ "bin/",
23
+ "skills/",
24
+ "references/",
25
+ "agents/",
26
+ "commands/",
27
+ ".claude/commands/",
28
+ ".gemini/commands/",
29
+ ".claude-plugin/",
30
+ ".codex-plugin/",
31
+ ".agents/plugins/",
32
+ "hooks/",
33
+ "docs/",
34
+ "scripts/",
35
+ "evals/",
36
+ "rebrand/",
37
+ "tests/",
38
+ "plugin.json",
39
+ "NOTICE.md",
40
+ "CHANGELOG.md"
41
+ ],
42
+ "scripts": {
43
+ "rebrand": "node rebrand/apply.cjs --apply",
44
+ "rebrand:check": "node rebrand/apply.cjs --check",
45
+ "validate": "node scripts/validate-brand.cjs && node scripts/validate-skills.js && node scripts/validate-versions.js && node scripts/validate-commands.js && node scripts/validate-reference-links.js && node scripts/validate-artifact-paths.js",
46
+ "test": "node --test scripts/*-test.js scripts/lib/*-test.js tests/*.test.cjs rebrand/tests/*.test.cjs",
47
+ "prepack": "npm run validate"
48
+ },
49
+ "keywords": [
50
+ "i3-composer",
51
+ "agent-skills",
52
+ "codebuddy",
53
+ "codex",
54
+ "claude-code"
55
+ ],
56
+ "i3ComposerAgent": {
57
+ "pluginFiles": [
58
+ "bin",
59
+ "skills",
60
+ "references",
61
+ "agents",
62
+ "commands",
63
+ ".claude/commands",
64
+ ".gemini/commands",
65
+ ".claude-plugin",
66
+ ".codex-plugin",
67
+ ".agents/plugins",
68
+ "hooks",
69
+ "docs",
70
+ "scripts",
71
+ "evals",
72
+ "rebrand",
73
+ "tests",
74
+ "plugin.json",
75
+ "package.json",
76
+ "README.md",
77
+ "LICENSE",
78
+ "NOTICE.md",
79
+ "CHANGELOG.md"
80
+ ]
81
+ }
82
+ }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
4
+ exec node "$ROOT/rebrand/apply.cjs" "${1:---check}"
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { execFileSync } = require('node:child_process');
7
+ const { describe, runtimeRoots, runtimeFiles } = require('./catalog.cjs');
8
+ const root = path.resolve(__dirname, '..');
9
+ const ref = process.argv[2];
10
+ if (!ref || ref.startsWith('-') || process.argv.length !== 3) {
11
+ console.error('Usage: node rebrand/record-upstream.cjs <reviewed upstream ref>');
12
+ process.exit(1);
13
+ }
14
+ try {
15
+ const git = args => execFileSync('git', args, { cwd: root, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
16
+ const commit = git(['rev-parse', '--verify', '--end-of-options', `${ref}^{commit}`]).trim();
17
+ const entries = git(['ls-tree', '-rz', commit, '--', ...runtimeRoots, ...runtimeFiles]).split('\0').filter(Boolean);
18
+ const blobs = new Map(entries.filter(line => /^100\d+ blob /.test(line)).map(line => {
19
+ const [metadata, file] = line.split('\t');
20
+ return [file, metadata.split(' ')[2]];
21
+ }));
22
+ const read = file => git(['cat-file', 'blob', blobs.get(file)]);
23
+ const snapshot = {
24
+ upstreamCommit: commit,
25
+ upstreamVersion: JSON.parse(read('plugin.json')).version,
26
+ ...describe([...blobs.keys()], read),
27
+ };
28
+ fs.writeFileSync(path.join(__dirname, 'upstream.json'), JSON.stringify(snapshot, null, 2) + '\n');
29
+ const brandFile = path.join(__dirname, 'brand.json');
30
+ const brand = JSON.parse(fs.readFileSync(brandFile, 'utf8'));
31
+ brand.upstreamCommit = commit;
32
+ fs.writeFileSync(brandFile, JSON.stringify(brand, null, 2) + '\n');
33
+ console.log(JSON.stringify({ commit, version: snapshot.upstreamVersion, skills: snapshot.skills.length, commands: snapshot.commands.length }));
34
+ } catch (error) {
35
+ console.error(error.message);
36
+ process.exitCode = 1;
37
+ }