kiro-spec-engine 1.32.0 → 1.33.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 +17 -0
- package/lib/commands/scene.js +168 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.33.0] - 2026-02-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Scene Package Directory Validation**: Comprehensive validation for scene package directories
|
|
14
|
+
- `kse scene package-validate --package <dir>` now supports directory-level validation
|
|
15
|
+
- `--strict` treat warnings as errors (exit code 1)
|
|
16
|
+
- `--json` structured JSON output
|
|
17
|
+
- Validates `scene-package.json` existence and required fields
|
|
18
|
+
- Contract-level validation via `validateScenePackageContract`
|
|
19
|
+
- Semver validation for `metadata.version` using `semver.valid`
|
|
20
|
+
- File existence checks for `artifacts.entry_scene` and `artifacts.generates`
|
|
21
|
+
- Template variable schema validation if `variables` present
|
|
22
|
+
- Collects all errors/warnings (no early exit)
|
|
23
|
+
- New `validateScenePackageDirectory` helper for programmatic use
|
|
24
|
+
- Follows normalize → validate → run → print pattern
|
|
25
|
+
- Implements Spec 83-00-scene-validate
|
|
26
|
+
|
|
10
27
|
## [1.32.0] - 2026-02-10
|
|
11
28
|
|
|
12
29
|
### Added
|
package/lib/commands/scene.js
CHANGED
|
@@ -367,6 +367,7 @@ function registerSceneCommands(program) {
|
|
|
367
367
|
.option('-p, --package <path>', 'Path to scene package contract JSON')
|
|
368
368
|
.option('--spec-package <relative-path>', 'Package path inside spec folder', 'custom/scene-package.json')
|
|
369
369
|
.option('--json', 'Print validation payload as JSON')
|
|
370
|
+
.option('--strict', 'Treat warnings as errors')
|
|
370
371
|
.action(async (options) => {
|
|
371
372
|
await runScenePackageValidateCommand(options);
|
|
372
373
|
});
|
|
@@ -801,7 +802,8 @@ function normalizeScenePackageValidateOptions(options = {}) {
|
|
|
801
802
|
spec: options.spec ? String(options.spec).trim() : undefined,
|
|
802
803
|
packagePath: options.package ? String(options.package).trim() : undefined,
|
|
803
804
|
specPackage: normalizeRelativePath(options.specPackage || 'custom/scene-package.json') || 'custom/scene-package.json',
|
|
804
|
-
json: options.json === true
|
|
805
|
+
json: options.json === true,
|
|
806
|
+
strict: options.strict === true
|
|
805
807
|
};
|
|
806
808
|
}
|
|
807
809
|
|
|
@@ -5890,6 +5892,12 @@ function printScenePackageValidateSummary(options, payload, projectRoot = proces
|
|
|
5890
5892
|
const statusLabel = payload.valid ? chalk.green('valid') : chalk.red('invalid');
|
|
5891
5893
|
console.log(chalk.blue('Scene Package Validate'));
|
|
5892
5894
|
console.log(` Status: ${statusLabel}`);
|
|
5895
|
+
if (payload.strict) {
|
|
5896
|
+
console.log(` Mode: ${chalk.yellow('strict')}`);
|
|
5897
|
+
}
|
|
5898
|
+
if (payload.input && payload.input.mode) {
|
|
5899
|
+
console.log(` Validation: ${payload.input.mode}`);
|
|
5900
|
+
}
|
|
5893
5901
|
console.log(` Source: ${chalk.gray(resolvePath(projectRoot, payload.input.path))}`);
|
|
5894
5902
|
|
|
5895
5903
|
if (payload.summary.coordinate) {
|
|
@@ -5899,6 +5907,10 @@ function printScenePackageValidateSummary(options, payload, projectRoot = proces
|
|
|
5899
5907
|
console.log(` Kind: ${payload.summary.kind || 'unknown'}`);
|
|
5900
5908
|
console.log(` Parameters: ${payload.summary.parameter_count}`);
|
|
5901
5909
|
|
|
5910
|
+
if (typeof payload.summary.files_checked === 'number') {
|
|
5911
|
+
console.log(` Files checked: ${payload.summary.files_checked}, missing: ${payload.summary.files_missing}`);
|
|
5912
|
+
}
|
|
5913
|
+
|
|
5902
5914
|
if (Array.isArray(payload.errors) && payload.errors.length > 0) {
|
|
5903
5915
|
console.log(chalk.red(' Errors:'));
|
|
5904
5916
|
for (const error of payload.errors) {
|
|
@@ -6831,6 +6843,105 @@ async function runScenePackageTemplateCommand(rawOptions = {}, dependencies = {}
|
|
|
6831
6843
|
}
|
|
6832
6844
|
}
|
|
6833
6845
|
|
|
6846
|
+
async function validateScenePackageDirectory(packageDir, fileSystem) {
|
|
6847
|
+
const _fs = fileSystem || fs;
|
|
6848
|
+
const readJson = typeof _fs.readJson === 'function' ? _fs.readJson.bind(_fs) : fs.readJson.bind(fs);
|
|
6849
|
+
const errors = [];
|
|
6850
|
+
const warnings = [];
|
|
6851
|
+
const fileChecks = { checked: 0, missing: 0 };
|
|
6852
|
+
|
|
6853
|
+
// Read scene-package.json
|
|
6854
|
+
const manifestPath = path.join(packageDir, 'scene-package.json');
|
|
6855
|
+
let contract;
|
|
6856
|
+
try {
|
|
6857
|
+
contract = await readJson(manifestPath);
|
|
6858
|
+
} catch (err) {
|
|
6859
|
+
return {
|
|
6860
|
+
valid: false,
|
|
6861
|
+
contract: null,
|
|
6862
|
+
errors: [`scene-package.json not found or not valid JSON in ${packageDir}: ${err.message}`],
|
|
6863
|
+
warnings: [],
|
|
6864
|
+
fileChecks: { checked: 0, missing: 0 },
|
|
6865
|
+
summary: { coordinate: null, kind: null, parameter_count: 0, provides_count: 0, requires_count: 0, file_count: 0, files_checked: 0, files_missing: 0 }
|
|
6866
|
+
};
|
|
6867
|
+
}
|
|
6868
|
+
|
|
6869
|
+
// Contract-level validation
|
|
6870
|
+
const contractResult = validateScenePackageContract(contract);
|
|
6871
|
+
errors.push(...contractResult.errors);
|
|
6872
|
+
warnings.push(...contractResult.warnings);
|
|
6873
|
+
|
|
6874
|
+
// Semver validation (more precise than contract regex)
|
|
6875
|
+
const metadata = isPlainObject(contract.metadata) ? contract.metadata : {};
|
|
6876
|
+
const version = String(metadata.version || '').trim();
|
|
6877
|
+
if (version && !semver.valid(version)) {
|
|
6878
|
+
const hasSemverError = errors.some(e => e.includes('metadata.version'));
|
|
6879
|
+
if (!hasSemverError) {
|
|
6880
|
+
errors.push(`metadata.version "${version}" is not valid semver`);
|
|
6881
|
+
}
|
|
6882
|
+
}
|
|
6883
|
+
|
|
6884
|
+
// File existence checks
|
|
6885
|
+
const artifacts = isPlainObject(contract.artifacts) ? contract.artifacts : {};
|
|
6886
|
+
const filesToCheck = [];
|
|
6887
|
+
if (typeof artifacts.entry_scene === 'string' && artifacts.entry_scene.trim()) {
|
|
6888
|
+
filesToCheck.push(artifacts.entry_scene.trim());
|
|
6889
|
+
}
|
|
6890
|
+
if (Array.isArray(artifacts.generates)) {
|
|
6891
|
+
for (const g of artifacts.generates) {
|
|
6892
|
+
if (typeof g === 'string' && g.trim()) {
|
|
6893
|
+
filesToCheck.push(g.trim());
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6896
|
+
}
|
|
6897
|
+
|
|
6898
|
+
for (const filePath of filesToCheck) {
|
|
6899
|
+
fileChecks.checked++;
|
|
6900
|
+
const fullPath = path.join(packageDir, filePath);
|
|
6901
|
+
try {
|
|
6902
|
+
const stat = typeof _fs.stat === 'function' ? _fs.stat.bind(_fs) : fs.stat.bind(fs);
|
|
6903
|
+
await stat(fullPath);
|
|
6904
|
+
} catch (_e) {
|
|
6905
|
+
fileChecks.missing++;
|
|
6906
|
+
errors.push(`referenced file not found: ${filePath}`);
|
|
6907
|
+
}
|
|
6908
|
+
}
|
|
6909
|
+
|
|
6910
|
+
// Template variable schema validation
|
|
6911
|
+
if (Array.isArray(contract.variables) && contract.variables.length > 0) {
|
|
6912
|
+
const varResult = validateTemplateVariableSchema(contract.variables);
|
|
6913
|
+
errors.push(...varResult.errors);
|
|
6914
|
+
warnings.push(...varResult.warnings);
|
|
6915
|
+
}
|
|
6916
|
+
if (Array.isArray(contract.parameters)) {
|
|
6917
|
+
for (const [index, param] of contract.parameters.entries()) {
|
|
6918
|
+
if (!isPlainObject(param)) continue;
|
|
6919
|
+
if (!String(param.id || '').trim()) {
|
|
6920
|
+
const hasIdError = errors.some(e => e.includes(`parameters[${index}].id`));
|
|
6921
|
+
if (!hasIdError) errors.push(`parameters[${index}].id is required`);
|
|
6922
|
+
}
|
|
6923
|
+
if (!String(param.type || '').trim()) {
|
|
6924
|
+
const hasTypeError = errors.some(e => e.includes(`parameters[${index}].type`));
|
|
6925
|
+
if (!hasTypeError) errors.push(`parameters[${index}].type is required`);
|
|
6926
|
+
}
|
|
6927
|
+
}
|
|
6928
|
+
}
|
|
6929
|
+
|
|
6930
|
+
return {
|
|
6931
|
+
valid: errors.length === 0,
|
|
6932
|
+
contract,
|
|
6933
|
+
errors,
|
|
6934
|
+
warnings,
|
|
6935
|
+
fileChecks,
|
|
6936
|
+
summary: {
|
|
6937
|
+
...contractResult.summary,
|
|
6938
|
+
file_count: filesToCheck.length,
|
|
6939
|
+
files_checked: fileChecks.checked,
|
|
6940
|
+
files_missing: fileChecks.missing
|
|
6941
|
+
}
|
|
6942
|
+
};
|
|
6943
|
+
}
|
|
6944
|
+
|
|
6834
6945
|
async function runScenePackageValidateCommand(rawOptions = {}, dependencies = {}) {
|
|
6835
6946
|
const projectRoot = dependencies.projectRoot || process.cwd();
|
|
6836
6947
|
const fileSystem = dependencies.fileSystem || fs;
|
|
@@ -6850,6 +6961,50 @@ async function runScenePackageValidateCommand(rawOptions = {}, dependencies = {}
|
|
|
6850
6961
|
|
|
6851
6962
|
try {
|
|
6852
6963
|
const inputPath = resolveScenePackageValidateInputPath(options, projectRoot);
|
|
6964
|
+
|
|
6965
|
+
// Detect if input is a directory for comprehensive validation
|
|
6966
|
+
let isDirectory = false;
|
|
6967
|
+
try {
|
|
6968
|
+
const statFn = typeof fileSystem.stat === 'function' ? fileSystem.stat.bind(fileSystem) : fs.stat.bind(fs);
|
|
6969
|
+
const stat = await statFn(inputPath);
|
|
6970
|
+
isDirectory = stat.isDirectory();
|
|
6971
|
+
} catch (_e) {
|
|
6972
|
+
// Not a directory or doesn't exist — fall through to file-based validation
|
|
6973
|
+
}
|
|
6974
|
+
|
|
6975
|
+
if (isDirectory) {
|
|
6976
|
+
const dirResult = await validateScenePackageDirectory(inputPath, fileSystem);
|
|
6977
|
+
|
|
6978
|
+
// Strict mode: promote warnings to errors
|
|
6979
|
+
if (options.strict && dirResult.warnings.length > 0) {
|
|
6980
|
+
dirResult.errors.push(...dirResult.warnings);
|
|
6981
|
+
dirResult.warnings = [];
|
|
6982
|
+
dirResult.valid = dirResult.errors.length === 0;
|
|
6983
|
+
}
|
|
6984
|
+
|
|
6985
|
+
const payload = {
|
|
6986
|
+
valid: dirResult.valid,
|
|
6987
|
+
input: {
|
|
6988
|
+
spec: options.spec || null,
|
|
6989
|
+
path: inputPath,
|
|
6990
|
+
spec_package: null,
|
|
6991
|
+
mode: 'directory'
|
|
6992
|
+
},
|
|
6993
|
+
summary: dirResult.summary,
|
|
6994
|
+
errors: dirResult.errors,
|
|
6995
|
+
warnings: dirResult.warnings,
|
|
6996
|
+
strict: options.strict
|
|
6997
|
+
};
|
|
6998
|
+
|
|
6999
|
+
printScenePackageValidateSummary(options, payload, projectRoot);
|
|
7000
|
+
|
|
7001
|
+
if (!dirResult.valid) {
|
|
7002
|
+
process.exitCode = 1;
|
|
7003
|
+
}
|
|
7004
|
+
|
|
7005
|
+
return payload;
|
|
7006
|
+
}
|
|
7007
|
+
|
|
6853
7008
|
const packageContract = await readJson(inputPath);
|
|
6854
7009
|
const validation = validateScenePackageContract(packageContract);
|
|
6855
7010
|
|
|
@@ -6858,16 +7013,24 @@ async function runScenePackageValidateCommand(rawOptions = {}, dependencies = {}
|
|
|
6858
7013
|
input: {
|
|
6859
7014
|
spec: options.spec || null,
|
|
6860
7015
|
path: inputPath,
|
|
6861
|
-
spec_package: options.spec ? options.specPackage : null
|
|
7016
|
+
spec_package: options.spec ? options.specPackage : null,
|
|
7017
|
+
mode: 'file'
|
|
6862
7018
|
},
|
|
6863
7019
|
summary: validation.summary,
|
|
6864
7020
|
errors: validation.errors,
|
|
6865
7021
|
warnings: validation.warnings
|
|
6866
7022
|
};
|
|
6867
7023
|
|
|
7024
|
+
// Strict mode for file-based validation too
|
|
7025
|
+
if (options.strict && payload.warnings.length > 0) {
|
|
7026
|
+
payload.errors.push(...payload.warnings);
|
|
7027
|
+
payload.warnings = [];
|
|
7028
|
+
payload.valid = payload.errors.length === 0;
|
|
7029
|
+
}
|
|
7030
|
+
|
|
6868
7031
|
printScenePackageValidateSummary(options, payload, projectRoot);
|
|
6869
7032
|
|
|
6870
|
-
if (!
|
|
7033
|
+
if (!payload.valid) {
|
|
6871
7034
|
process.exitCode = 1;
|
|
6872
7035
|
}
|
|
6873
7036
|
|
|
@@ -10892,5 +11055,6 @@ module.exports = {
|
|
|
10892
11055
|
normalizeSceneInfoOptions,
|
|
10893
11056
|
validateSceneInfoOptions,
|
|
10894
11057
|
runSceneInfoCommand,
|
|
10895
|
-
printSceneInfoSummary
|
|
11058
|
+
printSceneInfoSummary,
|
|
11059
|
+
validateScenePackageDirectory
|
|
10896
11060
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kiro-spec-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.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": {
|