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 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
@@ -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.30.0",
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": {