kiro-spec-engine 1.30.0 → 1.32.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/CHANGELOG.md +29 -0
- package/lib/commands/scene.js +302 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.32.0] - 2026-02-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Scene Package Info**: Display detailed package information from local registry
|
|
14
|
+
- `kse scene info --name <packageName>` show package details
|
|
15
|
+
- `--registry <dir>` custom registry directory
|
|
16
|
+
- `--json` structured JSON output
|
|
17
|
+
- `--versions-only` show only version list
|
|
18
|
+
- Displays package metadata, description, group, all published versions
|
|
19
|
+
- Shows latest version, total version count, publish dates
|
|
20
|
+
- Sorted version list (newest first) using `semver.rcompare`
|
|
21
|
+
- Follows normalize → validate → run → print pattern
|
|
22
|
+
- Implements Spec 82-00-scene-info
|
|
23
|
+
|
|
24
|
+
## [1.31.0] - 2026-02-10
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **Scene Package Diff**: Compare two versions of a scene package in the local registry
|
|
28
|
+
- `kse scene diff --name <pkg> --from <v1> --to <v2>` compare package versions
|
|
29
|
+
- `--registry <dir>` custom registry directory
|
|
30
|
+
- `--json` structured JSON output
|
|
31
|
+
- `--stat` show only file change summary
|
|
32
|
+
- Extracts and decompresses tarballs from registry
|
|
33
|
+
- Categorizes files as added, removed, modified, or unchanged
|
|
34
|
+
- Shows changed line counts for modified text files
|
|
35
|
+
- Shared helper: `buildPackageDiff`
|
|
36
|
+
- Follows normalize → validate → run → print pattern
|
|
37
|
+
- Implements Spec 81-00-scene-diff
|
|
38
|
+
|
|
10
39
|
## [1.30.0] - 2026-02-10
|
|
11
40
|
|
|
12
41
|
### Added
|
package/lib/commands/scene.js
CHANGED
|
@@ -547,6 +547,30 @@ function registerSceneCommands(program) {
|
|
|
547
547
|
.action(async (options) => {
|
|
548
548
|
await runSceneVersionCommand(options);
|
|
549
549
|
});
|
|
550
|
+
|
|
551
|
+
sceneCmd
|
|
552
|
+
.command('diff')
|
|
553
|
+
.description('Compare two versions of a scene package in the local registry')
|
|
554
|
+
.requiredOption('-n, --name <name>', 'Package name')
|
|
555
|
+
.requiredOption('-f, --from <version>', 'Source version')
|
|
556
|
+
.requiredOption('-t, --to <version>', 'Target version')
|
|
557
|
+
.option('-r, --registry <path>', 'Registry root directory', '.kiro/registry')
|
|
558
|
+
.option('--json', 'Print result as JSON')
|
|
559
|
+
.option('--stat', 'Show only file change summary')
|
|
560
|
+
.action(async (options) => {
|
|
561
|
+
await runSceneDiffCommand(options);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
sceneCmd
|
|
565
|
+
.command('info')
|
|
566
|
+
.description('Display detailed information about a scene package in the local registry')
|
|
567
|
+
.requiredOption('-n, --name <name>', 'Package name')
|
|
568
|
+
.option('-r, --registry <path>', 'Registry root directory', '.kiro/registry')
|
|
569
|
+
.option('--json', 'Print result as JSON')
|
|
570
|
+
.option('--versions-only', 'Show only version list')
|
|
571
|
+
.action(async (options) => {
|
|
572
|
+
await runSceneInfoCommand(options);
|
|
573
|
+
});
|
|
550
574
|
}
|
|
551
575
|
|
|
552
576
|
function normalizeSourceOptions(options = {}) {
|
|
@@ -10435,6 +10459,274 @@ async function runSceneVersionCommand(rawOptions = {}, dependencies = {}) {
|
|
|
10435
10459
|
}
|
|
10436
10460
|
}
|
|
10437
10461
|
|
|
10462
|
+
function buildPackageDiff(fromFiles, toFiles) {
|
|
10463
|
+
const fromMap = new Map();
|
|
10464
|
+
for (const f of (fromFiles || [])) {
|
|
10465
|
+
fromMap.set(f.relativePath, f.content);
|
|
10466
|
+
}
|
|
10467
|
+
const toMap = new Map();
|
|
10468
|
+
for (const f of (toFiles || [])) {
|
|
10469
|
+
toMap.set(f.relativePath, f.content);
|
|
10470
|
+
}
|
|
10471
|
+
|
|
10472
|
+
const added = [];
|
|
10473
|
+
const removed = [];
|
|
10474
|
+
const modified = [];
|
|
10475
|
+
const unchanged = [];
|
|
10476
|
+
|
|
10477
|
+
for (const [filePath, content] of fromMap) {
|
|
10478
|
+
if (!toMap.has(filePath)) {
|
|
10479
|
+
removed.push(filePath);
|
|
10480
|
+
} else {
|
|
10481
|
+
const toContent = toMap.get(filePath);
|
|
10482
|
+
if (Buffer.compare(content, toContent) === 0) {
|
|
10483
|
+
unchanged.push(filePath);
|
|
10484
|
+
} else {
|
|
10485
|
+
let changedLines = 0;
|
|
10486
|
+
try {
|
|
10487
|
+
const oldLines = content.toString('utf8').split('\n');
|
|
10488
|
+
const newLines = toContent.toString('utf8').split('\n');
|
|
10489
|
+
const maxLen = Math.max(oldLines.length, newLines.length);
|
|
10490
|
+
for (let i = 0; i < maxLen; i++) {
|
|
10491
|
+
if ((oldLines[i] || '') !== (newLines[i] || '')) {
|
|
10492
|
+
changedLines++;
|
|
10493
|
+
}
|
|
10494
|
+
}
|
|
10495
|
+
} catch (_e) {
|
|
10496
|
+
changedLines = -1;
|
|
10497
|
+
}
|
|
10498
|
+
modified.push({ path: filePath, changedLines });
|
|
10499
|
+
}
|
|
10500
|
+
}
|
|
10501
|
+
}
|
|
10502
|
+
|
|
10503
|
+
for (const filePath of toMap.keys()) {
|
|
10504
|
+
if (!fromMap.has(filePath)) {
|
|
10505
|
+
added.push(filePath);
|
|
10506
|
+
}
|
|
10507
|
+
}
|
|
10508
|
+
|
|
10509
|
+
return {
|
|
10510
|
+
added: added.sort(),
|
|
10511
|
+
removed: removed.sort(),
|
|
10512
|
+
modified: modified.sort((a, b) => a.path.localeCompare(b.path)),
|
|
10513
|
+
unchanged: unchanged.sort()
|
|
10514
|
+
};
|
|
10515
|
+
}
|
|
10516
|
+
|
|
10517
|
+
function normalizeSceneDiffOptions(options = {}) {
|
|
10518
|
+
return {
|
|
10519
|
+
name: options.name ? String(options.name).trim() : undefined,
|
|
10520
|
+
from: options.from ? String(options.from).trim() : undefined,
|
|
10521
|
+
to: options.to ? String(options.to).trim() : undefined,
|
|
10522
|
+
registry: options.registry ? String(options.registry).trim() : '.kiro/registry',
|
|
10523
|
+
json: options.json === true,
|
|
10524
|
+
stat: options.stat === true
|
|
10525
|
+
};
|
|
10526
|
+
}
|
|
10527
|
+
|
|
10528
|
+
function validateSceneDiffOptions(options) {
|
|
10529
|
+
if (!options.name) return '--name is required';
|
|
10530
|
+
if (!options.from) return '--from is required';
|
|
10531
|
+
if (!options.to) return '--to is required';
|
|
10532
|
+
if (options.from === options.to) return '--from and --to must be different versions';
|
|
10533
|
+
return null;
|
|
10534
|
+
}
|
|
10535
|
+
|
|
10536
|
+
function printSceneDiffSummary(options, payload, projectRoot = process.cwd()) {
|
|
10537
|
+
if (options.json) {
|
|
10538
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
10539
|
+
return;
|
|
10540
|
+
}
|
|
10541
|
+
|
|
10542
|
+
console.log(chalk.blue(`Scene Package Diff: ${payload.name} ${payload.fromVersion} → ${payload.toVersion}`));
|
|
10543
|
+
console.log(` Added: ${payload.summary.added} file(s)`);
|
|
10544
|
+
console.log(` Removed: ${payload.summary.removed} file(s)`);
|
|
10545
|
+
console.log(` Modified: ${payload.summary.modified} file(s)`);
|
|
10546
|
+
console.log(` Unchanged: ${payload.summary.unchanged} file(s)`);
|
|
10547
|
+
|
|
10548
|
+
if (payload.files.added.length > 0 || payload.files.removed.length > 0 || payload.files.modified.length > 0) {
|
|
10549
|
+
console.log('');
|
|
10550
|
+
for (const f of payload.files.added) {
|
|
10551
|
+
console.log(chalk.green(` + ${f}`));
|
|
10552
|
+
}
|
|
10553
|
+
for (const f of payload.files.removed) {
|
|
10554
|
+
console.log(chalk.red(` - ${f}`));
|
|
10555
|
+
}
|
|
10556
|
+
for (const f of payload.files.modified) {
|
|
10557
|
+
const detail = f.changedLines >= 0 ? ` (${f.changedLines} lines changed)` : ' (binary content differs)';
|
|
10558
|
+
console.log(chalk.yellow(` ~ ${f.path}${detail}`));
|
|
10559
|
+
}
|
|
10560
|
+
}
|
|
10561
|
+
}
|
|
10562
|
+
|
|
10563
|
+
async function runSceneDiffCommand(rawOptions = {}, dependencies = {}) {
|
|
10564
|
+
const projectRoot = dependencies.projectRoot || process.cwd();
|
|
10565
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
10566
|
+
|
|
10567
|
+
const options = normalizeSceneDiffOptions(rawOptions);
|
|
10568
|
+
const validationError = validateSceneDiffOptions(options);
|
|
10569
|
+
if (validationError) {
|
|
10570
|
+
console.error(chalk.red(`Scene diff failed: ${validationError}`));
|
|
10571
|
+
process.exitCode = 1;
|
|
10572
|
+
return null;
|
|
10573
|
+
}
|
|
10574
|
+
|
|
10575
|
+
try {
|
|
10576
|
+
const registryRoot = path.isAbsolute(options.registry)
|
|
10577
|
+
? options.registry
|
|
10578
|
+
: path.join(projectRoot, options.registry);
|
|
10579
|
+
|
|
10580
|
+
const index = await loadRegistryIndex(registryRoot, fileSystem);
|
|
10581
|
+
|
|
10582
|
+
if (!index.packages || !index.packages[options.name]) {
|
|
10583
|
+
throw new Error(`package "${options.name}" not found in registry`);
|
|
10584
|
+
}
|
|
10585
|
+
|
|
10586
|
+
const pkg = index.packages[options.name];
|
|
10587
|
+
if (!pkg.versions || !pkg.versions[options.from]) {
|
|
10588
|
+
throw new Error(`version "${options.from}" not found for package "${options.name}"`);
|
|
10589
|
+
}
|
|
10590
|
+
if (!pkg.versions[options.to]) {
|
|
10591
|
+
throw new Error(`version "${options.to}" not found for package "${options.name}"`);
|
|
10592
|
+
}
|
|
10593
|
+
|
|
10594
|
+
const readFile = typeof fileSystem.readFile === 'function'
|
|
10595
|
+
? fileSystem.readFile.bind(fileSystem) : fs.readFile.bind(fs);
|
|
10596
|
+
|
|
10597
|
+
const fromTarballPath = path.join(registryRoot, pkg.versions[options.from].tarball);
|
|
10598
|
+
const toTarballPath = path.join(registryRoot, pkg.versions[options.to].tarball);
|
|
10599
|
+
|
|
10600
|
+
const fromGz = await readFile(fromTarballPath);
|
|
10601
|
+
const toGz = await readFile(toTarballPath);
|
|
10602
|
+
|
|
10603
|
+
const fromTar = zlib.gunzipSync(fromGz);
|
|
10604
|
+
const toTar = zlib.gunzipSync(toGz);
|
|
10605
|
+
|
|
10606
|
+
const fromFiles = extractTarBuffer(fromTar);
|
|
10607
|
+
const toFiles = extractTarBuffer(toTar);
|
|
10608
|
+
|
|
10609
|
+
const diff = buildPackageDiff(fromFiles, toFiles);
|
|
10610
|
+
|
|
10611
|
+
const payload = {
|
|
10612
|
+
success: true,
|
|
10613
|
+
name: options.name,
|
|
10614
|
+
fromVersion: options.from,
|
|
10615
|
+
toVersion: options.to,
|
|
10616
|
+
summary: {
|
|
10617
|
+
added: diff.added.length,
|
|
10618
|
+
removed: diff.removed.length,
|
|
10619
|
+
modified: diff.modified.length,
|
|
10620
|
+
unchanged: diff.unchanged.length
|
|
10621
|
+
},
|
|
10622
|
+
files: {
|
|
10623
|
+
added: diff.added,
|
|
10624
|
+
removed: diff.removed,
|
|
10625
|
+
modified: diff.modified,
|
|
10626
|
+
unchanged: options.stat ? diff.unchanged : diff.unchanged
|
|
10627
|
+
}
|
|
10628
|
+
};
|
|
10629
|
+
|
|
10630
|
+
printSceneDiffSummary(options, payload, projectRoot);
|
|
10631
|
+
return payload;
|
|
10632
|
+
} catch (error) {
|
|
10633
|
+
console.error(chalk.red('Scene diff failed:'), error.message);
|
|
10634
|
+
process.exitCode = 1;
|
|
10635
|
+
return null;
|
|
10636
|
+
}
|
|
10637
|
+
}
|
|
10638
|
+
|
|
10639
|
+
function normalizeSceneInfoOptions(options = {}) {
|
|
10640
|
+
return {
|
|
10641
|
+
name: options.name ? String(options.name).trim() : undefined,
|
|
10642
|
+
registry: options.registry ? String(options.registry).trim() : '.kiro/registry',
|
|
10643
|
+
json: options.json === true,
|
|
10644
|
+
versionsOnly: options.versionsOnly === true
|
|
10645
|
+
};
|
|
10646
|
+
}
|
|
10647
|
+
|
|
10648
|
+
function validateSceneInfoOptions(options) {
|
|
10649
|
+
if (!options.name) return '--name is required';
|
|
10650
|
+
return null;
|
|
10651
|
+
}
|
|
10652
|
+
|
|
10653
|
+
function printSceneInfoSummary(options, payload, projectRoot = process.cwd()) {
|
|
10654
|
+
if (options.json) {
|
|
10655
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
10656
|
+
return;
|
|
10657
|
+
}
|
|
10658
|
+
|
|
10659
|
+
if (options.versionsOnly) {
|
|
10660
|
+
for (const v of payload.versions) {
|
|
10661
|
+
console.log(`${v.version} ${v.publishedAt}`);
|
|
10662
|
+
}
|
|
10663
|
+
return;
|
|
10664
|
+
}
|
|
10665
|
+
|
|
10666
|
+
console.log(chalk.blue(`Scene Package: ${payload.name}`));
|
|
10667
|
+
console.log(` Group: ${payload.group || '(none)'}`);
|
|
10668
|
+
console.log(` Description: ${payload.description || '(none)'}`);
|
|
10669
|
+
console.log(` Latest: ${payload.latest}`);
|
|
10670
|
+
console.log(` Versions: ${payload.versionCount}`);
|
|
10671
|
+
console.log('');
|
|
10672
|
+
console.log(' ' + 'VERSION'.padEnd(14) + 'PUBLISHED'.padEnd(26) + 'INTEGRITY');
|
|
10673
|
+
for (const v of payload.versions) {
|
|
10674
|
+
console.log(' ' + v.version.padEnd(14) + (v.publishedAt || '').padEnd(26) + (v.integrity || ''));
|
|
10675
|
+
}
|
|
10676
|
+
}
|
|
10677
|
+
|
|
10678
|
+
async function runSceneInfoCommand(rawOptions = {}, dependencies = {}) {
|
|
10679
|
+
const projectRoot = dependencies.projectRoot || process.cwd();
|
|
10680
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
10681
|
+
|
|
10682
|
+
const options = normalizeSceneInfoOptions(rawOptions);
|
|
10683
|
+
const validationError = validateSceneInfoOptions(options);
|
|
10684
|
+
if (validationError) {
|
|
10685
|
+
console.error(chalk.red(`Scene info failed: ${validationError}`));
|
|
10686
|
+
process.exitCode = 1;
|
|
10687
|
+
return null;
|
|
10688
|
+
}
|
|
10689
|
+
|
|
10690
|
+
try {
|
|
10691
|
+
const registryRoot = path.isAbsolute(options.registry)
|
|
10692
|
+
? options.registry
|
|
10693
|
+
: path.join(projectRoot, options.registry);
|
|
10694
|
+
|
|
10695
|
+
const index = await loadRegistryIndex(registryRoot, fileSystem);
|
|
10696
|
+
|
|
10697
|
+
if (!index.packages || !index.packages[options.name]) {
|
|
10698
|
+
throw new Error(`package "${options.name}" not found in registry`);
|
|
10699
|
+
}
|
|
10700
|
+
|
|
10701
|
+
const pkg = index.packages[options.name];
|
|
10702
|
+
const versionKeys = Object.keys(pkg.versions || {});
|
|
10703
|
+
const sortedVersions = versionKeys.slice().sort(semver.rcompare);
|
|
10704
|
+
|
|
10705
|
+
const versions = sortedVersions.map(v => ({
|
|
10706
|
+
version: v,
|
|
10707
|
+
publishedAt: (pkg.versions[v] && pkg.versions[v].published_at) || '',
|
|
10708
|
+
integrity: (pkg.versions[v] && pkg.versions[v].integrity) || ''
|
|
10709
|
+
}));
|
|
10710
|
+
|
|
10711
|
+
const payload = {
|
|
10712
|
+
success: true,
|
|
10713
|
+
name: pkg.name || options.name,
|
|
10714
|
+
group: pkg.group || '',
|
|
10715
|
+
description: pkg.description || '',
|
|
10716
|
+
latest: pkg.latest || resolveLatestVersion(pkg.versions) || '',
|
|
10717
|
+
versionCount: versionKeys.length,
|
|
10718
|
+
versions
|
|
10719
|
+
};
|
|
10720
|
+
|
|
10721
|
+
printSceneInfoSummary(options, payload, projectRoot);
|
|
10722
|
+
return payload;
|
|
10723
|
+
} catch (error) {
|
|
10724
|
+
console.error(chalk.red('Scene info failed:'), error.message);
|
|
10725
|
+
process.exitCode = 1;
|
|
10726
|
+
return null;
|
|
10727
|
+
}
|
|
10728
|
+
}
|
|
10729
|
+
|
|
10438
10730
|
module.exports = {
|
|
10439
10731
|
RUN_MODES,
|
|
10440
10732
|
SCAFFOLD_TYPES,
|
|
@@ -10591,5 +10883,14 @@ module.exports = {
|
|
|
10591
10883
|
normalizeSceneVersionOptions,
|
|
10592
10884
|
validateSceneVersionOptions,
|
|
10593
10885
|
runSceneVersionCommand,
|
|
10594
|
-
printSceneVersionSummary
|
|
10886
|
+
printSceneVersionSummary,
|
|
10887
|
+
buildPackageDiff,
|
|
10888
|
+
normalizeSceneDiffOptions,
|
|
10889
|
+
validateSceneDiffOptions,
|
|
10890
|
+
runSceneDiffCommand,
|
|
10891
|
+
printSceneDiffSummary,
|
|
10892
|
+
normalizeSceneInfoOptions,
|
|
10893
|
+
validateSceneInfoOptions,
|
|
10894
|
+
runSceneInfoCommand,
|
|
10895
|
+
printSceneInfoSummary
|
|
10595
10896
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kiro-spec-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.32.0",
|
|
4
4
|
"description": "kiro-spec-engine (kse) - A CLI tool and npm package for spec-driven development with AI coding assistants. NOT the Kiro IDE desktop application.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|