@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.
- package/.agents/plugins/marketplace.json +3 -2
- package/.claude-plugin/marketplace.json +2 -1
- package/.claude-plugin/plugin.json +3 -1
- package/.codex-plugin/plugin.json +3 -1
- package/CHANGELOG.md +23 -0
- package/README.md +77 -137
- package/bin/i3-composer-agent.cjs +6 -3
- package/docs/antigravity-setup.md +1 -1
- package/docs/comparison.md +1 -1
- package/hooks/sdd-cache-post.sh +0 -0
- package/hooks/sdd-cache-pre.sh +0 -0
- package/package.json +75 -17
- package/plugin.json +4 -2
- package/rebrand/MAINTENANCE.md +92 -31
- package/rebrand/NOTICE.md +9 -0
- package/rebrand/PR-WORKFLOW.md +34 -0
- package/rebrand/UNIFICATION-SPEC.md +33 -0
- package/rebrand/VALIDATION.md +28 -0
- package/rebrand/VERSIONING.md +32 -0
- package/rebrand/apply.cjs +80 -7
- package/rebrand/brand.json +6 -2
- package/rebrand/catalog.cjs +56 -0
- package/rebrand/fork-patches.json +12 -0
- package/rebrand/overlay/.github/workflows/test-plugin-install.yml +177 -0
- package/rebrand/overlay/CHANGELOG.md +23 -0
- package/rebrand/overlay/bin/i3-composer-agent.cjs +122 -0
- package/rebrand/overlay/scripts/validate-brand.cjs +44 -0
- package/rebrand/overlay/tests/bundle.test.cjs +85 -0
- package/rebrand/package.template.json +82 -0
- package/rebrand/rebrand.sh +4 -0
- package/rebrand/record-upstream.cjs +37 -0
- package/rebrand/templates/README.md +44 -136
- package/rebrand/tests/pipeline.test.cjs +185 -0
- package/rebrand/upstream.json +171 -0
- package/scripts/validate-brand.cjs +11 -3
- package/skills/idea-refine/scripts/idea-refine.sh +0 -0
- 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,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
|
+
}
|