kiro-spec-engine 1.37.0 → 1.38.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 +15 -0
- package/lib/commands/scene.js +304 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.38.0] - 2026-02-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Scene Registry Statistics**: Dashboard for local scene package registry metrics
|
|
14
|
+
- `kse scene stats` show aggregate statistics (packages, versions, tags, ownership, deprecation, last publish)
|
|
15
|
+
- `--registry <dir>` custom registry directory
|
|
16
|
+
- `--json` structured JSON output
|
|
17
|
+
- **Scene Version Locking**: Protect specific package versions from accidental unpublish
|
|
18
|
+
- `kse scene lock set --name <pkg> --version <ver>` lock a version
|
|
19
|
+
- `kse scene lock rm --name <pkg> --version <ver>` unlock a version
|
|
20
|
+
- `kse scene lock ls --name <pkg>` list locked versions
|
|
21
|
+
- `--registry <dir>` custom registry directory
|
|
22
|
+
- `--json` structured JSON output
|
|
23
|
+
- Lock state stored as `locked: true` on version entries in `registry-index.json`
|
|
24
|
+
|
|
10
25
|
## [1.37.0] - 2026-02-10
|
|
11
26
|
|
|
12
27
|
### Added
|
package/lib/commands/scene.js
CHANGED
|
@@ -681,6 +681,53 @@ function registerSceneCommands(program) {
|
|
|
681
681
|
.action(async (options) => {
|
|
682
682
|
await runSceneTagCommand({ ...options, action: 'ls' });
|
|
683
683
|
});
|
|
684
|
+
|
|
685
|
+
// ── scene stats ──
|
|
686
|
+
sceneCmd
|
|
687
|
+
.command('stats')
|
|
688
|
+
.description('Show aggregate statistics about the local scene package registry')
|
|
689
|
+
.option('-r, --registry <path>', 'Registry root directory', '.kiro/registry')
|
|
690
|
+
.option('--json', 'Print result as JSON')
|
|
691
|
+
.action(async (options) => {
|
|
692
|
+
await runSceneStatsCommand(options);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// ── scene lock ──
|
|
696
|
+
const lockCmd = sceneCmd
|
|
697
|
+
.command('lock')
|
|
698
|
+
.description('Manage version locks on scene packages');
|
|
699
|
+
|
|
700
|
+
lockCmd
|
|
701
|
+
.command('set')
|
|
702
|
+
.description('Lock a specific version of a package')
|
|
703
|
+
.requiredOption('-n, --name <name>', 'Package name')
|
|
704
|
+
.requiredOption('-v, --version <version>', 'Version to lock')
|
|
705
|
+
.option('-r, --registry <path>', 'Registry root directory', '.kiro/registry')
|
|
706
|
+
.option('--json', 'Print result as JSON')
|
|
707
|
+
.action(async (options) => {
|
|
708
|
+
await runSceneLockCommand({ ...options, action: 'set' });
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
lockCmd
|
|
712
|
+
.command('rm')
|
|
713
|
+
.description('Unlock a specific version of a package')
|
|
714
|
+
.requiredOption('-n, --name <name>', 'Package name')
|
|
715
|
+
.requiredOption('-v, --version <version>', 'Version to unlock')
|
|
716
|
+
.option('-r, --registry <path>', 'Registry root directory', '.kiro/registry')
|
|
717
|
+
.option('--json', 'Print result as JSON')
|
|
718
|
+
.action(async (options) => {
|
|
719
|
+
await runSceneLockCommand({ ...options, action: 'rm' });
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
lockCmd
|
|
723
|
+
.command('ls')
|
|
724
|
+
.description('List all locked versions for a package')
|
|
725
|
+
.requiredOption('-n, --name <name>', 'Package name')
|
|
726
|
+
.option('-r, --registry <path>', 'Registry root directory', '.kiro/registry')
|
|
727
|
+
.option('--json', 'Print result as JSON')
|
|
728
|
+
.action(async (options) => {
|
|
729
|
+
await runSceneLockCommand({ ...options, action: 'ls' });
|
|
730
|
+
});
|
|
684
731
|
}
|
|
685
732
|
|
|
686
733
|
function normalizeSourceOptions(options = {}) {
|
|
@@ -11698,6 +11745,254 @@ function printSceneTagSummary(options, payload) {
|
|
|
11698
11745
|
}
|
|
11699
11746
|
}
|
|
11700
11747
|
|
|
11748
|
+
// ── Scene Stats ───────────────────────────────────────────────────────────
|
|
11749
|
+
|
|
11750
|
+
function normalizeSceneStatsOptions(options = {}) {
|
|
11751
|
+
return {
|
|
11752
|
+
registry: options.registry ? String(options.registry).trim() : '.kiro/registry',
|
|
11753
|
+
json: options.json === true
|
|
11754
|
+
};
|
|
11755
|
+
}
|
|
11756
|
+
|
|
11757
|
+
function validateSceneStatsOptions(options) {
|
|
11758
|
+
return null;
|
|
11759
|
+
}
|
|
11760
|
+
|
|
11761
|
+
async function runSceneStatsCommand(rawOptions = {}, dependencies = {}) {
|
|
11762
|
+
const projectRoot = dependencies.projectRoot || process.cwd();
|
|
11763
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
11764
|
+
|
|
11765
|
+
const options = normalizeSceneStatsOptions(rawOptions);
|
|
11766
|
+
const validationError = validateSceneStatsOptions(options);
|
|
11767
|
+
|
|
11768
|
+
if (validationError) {
|
|
11769
|
+
console.error(chalk.red(`Scene stats failed: ${validationError}`));
|
|
11770
|
+
process.exitCode = 1;
|
|
11771
|
+
return null;
|
|
11772
|
+
}
|
|
11773
|
+
|
|
11774
|
+
try {
|
|
11775
|
+
const registryRoot = path.isAbsolute(options.registry)
|
|
11776
|
+
? options.registry
|
|
11777
|
+
: path.join(projectRoot, options.registry);
|
|
11778
|
+
|
|
11779
|
+
const index = await loadRegistryIndex(registryRoot, fileSystem);
|
|
11780
|
+
const packages = index.packages || {};
|
|
11781
|
+
const packageNames = Object.keys(packages);
|
|
11782
|
+
|
|
11783
|
+
let totalVersions = 0;
|
|
11784
|
+
let totalTags = 0;
|
|
11785
|
+
let packagesWithOwner = 0;
|
|
11786
|
+
let deprecatedPackages = 0;
|
|
11787
|
+
let mostRecent = null;
|
|
11788
|
+
|
|
11789
|
+
for (const name of packageNames) {
|
|
11790
|
+
const pkg = packages[name];
|
|
11791
|
+
const versions = pkg.versions || {};
|
|
11792
|
+
const versionKeys = Object.keys(versions);
|
|
11793
|
+
totalVersions += versionKeys.length;
|
|
11794
|
+
totalTags += Object.keys(pkg.tags || {}).length;
|
|
11795
|
+
|
|
11796
|
+
if (pkg.owner && String(pkg.owner).trim() !== '') {
|
|
11797
|
+
packagesWithOwner++;
|
|
11798
|
+
}
|
|
11799
|
+
if (pkg.deprecated) {
|
|
11800
|
+
deprecatedPackages++;
|
|
11801
|
+
}
|
|
11802
|
+
|
|
11803
|
+
for (const ver of versionKeys) {
|
|
11804
|
+
const publishedAt = versions[ver].published_at;
|
|
11805
|
+
if (publishedAt && (!mostRecent || publishedAt > mostRecent.publishedAt)) {
|
|
11806
|
+
mostRecent = { package: name, version: ver, publishedAt };
|
|
11807
|
+
}
|
|
11808
|
+
}
|
|
11809
|
+
}
|
|
11810
|
+
|
|
11811
|
+
const payload = {
|
|
11812
|
+
success: true,
|
|
11813
|
+
totalPackages: packageNames.length,
|
|
11814
|
+
totalVersions,
|
|
11815
|
+
totalTags,
|
|
11816
|
+
packagesWithOwner,
|
|
11817
|
+
packagesWithoutOwner: packageNames.length - packagesWithOwner,
|
|
11818
|
+
deprecatedPackages,
|
|
11819
|
+
mostRecentlyPublished: mostRecent,
|
|
11820
|
+
registry: options.registry
|
|
11821
|
+
};
|
|
11822
|
+
|
|
11823
|
+
printSceneStatsSummary(options, payload);
|
|
11824
|
+
return payload;
|
|
11825
|
+
} catch (error) {
|
|
11826
|
+
console.error(chalk.red('Scene stats failed:'), error.message);
|
|
11827
|
+
process.exitCode = 1;
|
|
11828
|
+
return null;
|
|
11829
|
+
}
|
|
11830
|
+
}
|
|
11831
|
+
|
|
11832
|
+
function printSceneStatsSummary(options, payload) {
|
|
11833
|
+
if (options.json) {
|
|
11834
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
11835
|
+
return;
|
|
11836
|
+
}
|
|
11837
|
+
|
|
11838
|
+
console.log(chalk.bold('Registry Statistics'));
|
|
11839
|
+
console.log(` Packages: ${payload.totalPackages}`);
|
|
11840
|
+
console.log(` Versions: ${payload.totalVersions}`);
|
|
11841
|
+
console.log(` Tags: ${payload.totalTags}`);
|
|
11842
|
+
console.log(` With owner: ${payload.packagesWithOwner}`);
|
|
11843
|
+
console.log(` No owner: ${payload.packagesWithoutOwner}`);
|
|
11844
|
+
console.log(` Deprecated: ${payload.deprecatedPackages}`);
|
|
11845
|
+
|
|
11846
|
+
if (payload.mostRecentlyPublished) {
|
|
11847
|
+
const mr = payload.mostRecentlyPublished;
|
|
11848
|
+
console.log(` Last publish: ${mr.package}@${mr.version} (${mr.publishedAt})`);
|
|
11849
|
+
} else {
|
|
11850
|
+
console.log(' Last publish: (none)');
|
|
11851
|
+
}
|
|
11852
|
+
}
|
|
11853
|
+
|
|
11854
|
+
// ── Scene Lock ────────────────────────────────────────────────────────────
|
|
11855
|
+
|
|
11856
|
+
function normalizeSceneLockOptions(options = {}) {
|
|
11857
|
+
return {
|
|
11858
|
+
action: options.action ? String(options.action).trim() : undefined,
|
|
11859
|
+
name: options.name ? String(options.name).trim() : undefined,
|
|
11860
|
+
version: options.version ? String(options.version).trim() : undefined,
|
|
11861
|
+
registry: options.registry ? String(options.registry).trim() : '.kiro/registry',
|
|
11862
|
+
json: options.json === true
|
|
11863
|
+
};
|
|
11864
|
+
}
|
|
11865
|
+
|
|
11866
|
+
function validateSceneLockOptions(options) {
|
|
11867
|
+
if (!options.action) return '--action is required';
|
|
11868
|
+
const validActions = ['set', 'rm', 'ls'];
|
|
11869
|
+
if (!validActions.includes(options.action)) return `invalid action "${options.action}"`;
|
|
11870
|
+
|
|
11871
|
+
if (options.action === 'set') {
|
|
11872
|
+
if (!options.name) return '--name is required';
|
|
11873
|
+
if (!options.version) return '--version is required';
|
|
11874
|
+
}
|
|
11875
|
+
if (options.action === 'rm') {
|
|
11876
|
+
if (!options.name) return '--name is required';
|
|
11877
|
+
if (!options.version) return '--version is required';
|
|
11878
|
+
}
|
|
11879
|
+
if (options.action === 'ls') {
|
|
11880
|
+
if (!options.name) return '--name is required';
|
|
11881
|
+
}
|
|
11882
|
+
return null;
|
|
11883
|
+
}
|
|
11884
|
+
|
|
11885
|
+
async function runSceneLockCommand(rawOptions = {}, dependencies = {}) {
|
|
11886
|
+
const projectRoot = dependencies.projectRoot || process.cwd();
|
|
11887
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
11888
|
+
|
|
11889
|
+
const options = normalizeSceneLockOptions(rawOptions);
|
|
11890
|
+
const validationError = validateSceneLockOptions(options);
|
|
11891
|
+
|
|
11892
|
+
if (validationError) {
|
|
11893
|
+
console.error(chalk.red(`Scene lock failed: ${validationError}`));
|
|
11894
|
+
process.exitCode = 1;
|
|
11895
|
+
return null;
|
|
11896
|
+
}
|
|
11897
|
+
|
|
11898
|
+
try {
|
|
11899
|
+
const registryRoot = path.isAbsolute(options.registry)
|
|
11900
|
+
? options.registry
|
|
11901
|
+
: path.join(projectRoot, options.registry);
|
|
11902
|
+
|
|
11903
|
+
const index = await loadRegistryIndex(registryRoot, fileSystem);
|
|
11904
|
+
const packages = index.packages || {};
|
|
11905
|
+
let payload;
|
|
11906
|
+
|
|
11907
|
+
if (options.action === 'set') {
|
|
11908
|
+
if (!packages[options.name]) {
|
|
11909
|
+
throw new Error(`package "${options.name}" not found in registry`);
|
|
11910
|
+
}
|
|
11911
|
+
const pkg = packages[options.name];
|
|
11912
|
+
if (!pkg.versions || !pkg.versions[options.version]) {
|
|
11913
|
+
throw new Error(`version "${options.version}" not found for package "${options.name}"`);
|
|
11914
|
+
}
|
|
11915
|
+
const versionEntry = pkg.versions[options.version];
|
|
11916
|
+
if (versionEntry.locked === true) {
|
|
11917
|
+
throw new Error(`version "${options.version}" of package "${options.name}" is already locked`);
|
|
11918
|
+
}
|
|
11919
|
+
versionEntry.locked = true;
|
|
11920
|
+
await saveRegistryIndex(registryRoot, index, fileSystem);
|
|
11921
|
+
payload = {
|
|
11922
|
+
success: true,
|
|
11923
|
+
action: 'set',
|
|
11924
|
+
package: options.name,
|
|
11925
|
+
version: options.version,
|
|
11926
|
+
registry: options.registry
|
|
11927
|
+
};
|
|
11928
|
+
} else if (options.action === 'rm') {
|
|
11929
|
+
if (!packages[options.name]) {
|
|
11930
|
+
throw new Error(`package "${options.name}" not found in registry`);
|
|
11931
|
+
}
|
|
11932
|
+
const pkg = packages[options.name];
|
|
11933
|
+
if (!pkg.versions || !pkg.versions[options.version]) {
|
|
11934
|
+
throw new Error(`version "${options.version}" not found for package "${options.name}"`);
|
|
11935
|
+
}
|
|
11936
|
+
const versionEntry = pkg.versions[options.version];
|
|
11937
|
+
if (!versionEntry.locked) {
|
|
11938
|
+
throw new Error(`version "${options.version}" of package "${options.name}" is not locked`);
|
|
11939
|
+
}
|
|
11940
|
+
delete versionEntry.locked;
|
|
11941
|
+
await saveRegistryIndex(registryRoot, index, fileSystem);
|
|
11942
|
+
payload = {
|
|
11943
|
+
success: true,
|
|
11944
|
+
action: 'rm',
|
|
11945
|
+
package: options.name,
|
|
11946
|
+
version: options.version,
|
|
11947
|
+
registry: options.registry
|
|
11948
|
+
};
|
|
11949
|
+
} else if (options.action === 'ls') {
|
|
11950
|
+
if (!packages[options.name]) {
|
|
11951
|
+
throw new Error(`package "${options.name}" not found in registry`);
|
|
11952
|
+
}
|
|
11953
|
+
const pkg = packages[options.name];
|
|
11954
|
+
const versions = pkg.versions || {};
|
|
11955
|
+
const lockedVersions = Object.keys(versions).filter(v => versions[v].locked === true);
|
|
11956
|
+
payload = {
|
|
11957
|
+
success: true,
|
|
11958
|
+
action: 'ls',
|
|
11959
|
+
package: options.name,
|
|
11960
|
+
lockedVersions,
|
|
11961
|
+
registry: options.registry
|
|
11962
|
+
};
|
|
11963
|
+
}
|
|
11964
|
+
|
|
11965
|
+
printSceneLockSummary(options, payload);
|
|
11966
|
+
return payload;
|
|
11967
|
+
} catch (error) {
|
|
11968
|
+
console.error(chalk.red('Scene lock failed:'), error.message);
|
|
11969
|
+
process.exitCode = 1;
|
|
11970
|
+
return null;
|
|
11971
|
+
}
|
|
11972
|
+
}
|
|
11973
|
+
|
|
11974
|
+
function printSceneLockSummary(options, payload) {
|
|
11975
|
+
if (options.json) {
|
|
11976
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
11977
|
+
return;
|
|
11978
|
+
}
|
|
11979
|
+
|
|
11980
|
+
if (payload.action === 'set') {
|
|
11981
|
+
console.log(chalk.green(`Version "${payload.version}" of package "${payload.package}" is now locked`));
|
|
11982
|
+
} else if (payload.action === 'rm') {
|
|
11983
|
+
console.log(chalk.green(`Version "${payload.version}" of package "${payload.package}" is now unlocked`));
|
|
11984
|
+
} else if (payload.action === 'ls') {
|
|
11985
|
+
if (payload.lockedVersions.length === 0) {
|
|
11986
|
+
console.log(`No locked versions for package "${payload.package}"`);
|
|
11987
|
+
} else {
|
|
11988
|
+
console.log(`Locked versions for package "${payload.package}":`);
|
|
11989
|
+
for (const version of payload.lockedVersions) {
|
|
11990
|
+
console.log(` ${version}`);
|
|
11991
|
+
}
|
|
11992
|
+
}
|
|
11993
|
+
}
|
|
11994
|
+
}
|
|
11995
|
+
|
|
11701
11996
|
module.exports = {
|
|
11702
11997
|
RUN_MODES,
|
|
11703
11998
|
SCAFFOLD_TYPES,
|
|
@@ -11882,5 +12177,13 @@ module.exports = {
|
|
|
11882
12177
|
normalizeSceneTagOptions,
|
|
11883
12178
|
validateSceneTagOptions,
|
|
11884
12179
|
runSceneTagCommand,
|
|
11885
|
-
printSceneTagSummary
|
|
12180
|
+
printSceneTagSummary,
|
|
12181
|
+
normalizeSceneStatsOptions,
|
|
12182
|
+
validateSceneStatsOptions,
|
|
12183
|
+
runSceneStatsCommand,
|
|
12184
|
+
printSceneStatsSummary,
|
|
12185
|
+
normalizeSceneLockOptions,
|
|
12186
|
+
validateSceneLockOptions,
|
|
12187
|
+
runSceneLockCommand,
|
|
12188
|
+
printSceneLockSummary
|
|
11886
12189
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kiro-spec-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.38.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": {
|