kiro-spec-engine 1.45.13 → 1.46.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +15 -4
  3. package/README.zh.md +15 -3
  4. package/bin/kiro-spec-engine.js +84 -0
  5. package/docs/adoption-guide.md +3 -2
  6. package/docs/command-reference.md +30 -12
  7. package/docs/cross-tool-guide.md +2 -1
  8. package/docs/document-governance.md +2 -1
  9. package/docs/faq.md +14 -13
  10. package/docs/manual-workflows-guide.md +2 -1
  11. package/docs/quick-start-with-ai-tools.md +4 -3
  12. package/docs/quick-start.md +21 -7
  13. package/docs/spec-workflow.md +3 -2
  14. package/docs/tools/claude-guide.md +3 -2
  15. package/docs/tools/cursor-guide.md +3 -2
  16. package/docs/tools/generic-guide.md +3 -2
  17. package/docs/tools/vscode-guide.md +3 -2
  18. package/docs/tools/windsurf-guide.md +3 -2
  19. package/docs/troubleshooting.md +10 -9
  20. package/docs/zh/quick-start.md +30 -14
  21. package/docs/zh/tools/claude-guide.md +2 -1
  22. package/docs/zh/tools/cursor-guide.md +2 -1
  23. package/docs/zh/tools/generic-guide.md +8 -7
  24. package/docs/zh/tools/vscode-guide.md +2 -1
  25. package/docs/zh/tools/windsurf-guide.md +2 -1
  26. package/lib/commands/orchestrate.js +123 -95
  27. package/lib/commands/spec-bootstrap.js +147 -0
  28. package/lib/commands/spec-gate.js +157 -0
  29. package/lib/commands/spec-pipeline.js +205 -0
  30. package/lib/spec/bootstrap/context-collector.js +48 -0
  31. package/lib/spec/bootstrap/draft-generator.js +158 -0
  32. package/lib/spec/bootstrap/questionnaire-engine.js +70 -0
  33. package/lib/spec/bootstrap/trace-emitter.js +59 -0
  34. package/lib/spec/multi-spec-orchestrate.js +93 -0
  35. package/lib/spec/pipeline/constants.js +6 -0
  36. package/lib/spec/pipeline/stage-adapters.js +118 -0
  37. package/lib/spec/pipeline/stage-runner.js +146 -0
  38. package/lib/spec/pipeline/state-store.js +119 -0
  39. package/lib/spec-gate/engine/gate-engine.js +165 -0
  40. package/lib/spec-gate/policy/default-policy.js +22 -0
  41. package/lib/spec-gate/policy/policy-loader.js +103 -0
  42. package/lib/spec-gate/result-emitter.js +81 -0
  43. package/lib/spec-gate/rules/default-rules.js +156 -0
  44. package/lib/spec-gate/rules/rule-registry.js +51 -0
  45. package/package.json +2 -1
@@ -0,0 +1,103 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const { DEFAULT_GATE_POLICY } = require('./default-policy');
4
+
5
+ class PolicyLoader {
6
+ constructor(projectPath = process.cwd()) {
7
+ this.projectPath = projectPath;
8
+ }
9
+
10
+ async load(options = {}) {
11
+ const policyPath = this._resolvePolicyPath(options.policy);
12
+ if (!policyPath) {
13
+ return this._applyStrictMode(this._clone(DEFAULT_GATE_POLICY), options.strict);
14
+ }
15
+
16
+ const userPolicy = await fs.readJson(policyPath);
17
+ this._validatePolicy(userPolicy, policyPath);
18
+
19
+ const merged = this._deepMerge(this._clone(DEFAULT_GATE_POLICY), userPolicy);
20
+ return this._applyStrictMode(merged, options.strict);
21
+ }
22
+
23
+ getTemplate() {
24
+ return this._clone(DEFAULT_GATE_POLICY);
25
+ }
26
+
27
+ _resolvePolicyPath(explicitPath) {
28
+ if (explicitPath) {
29
+ return path.isAbsolute(explicitPath)
30
+ ? explicitPath
31
+ : path.join(this.projectPath, explicitPath);
32
+ }
33
+
34
+ const projectPolicy = path.join(this.projectPath, '.kiro', 'config', 'spec-gate-policy.json');
35
+ if (fs.existsSync(projectPolicy)) {
36
+ return projectPolicy;
37
+ }
38
+
39
+ return null;
40
+ }
41
+
42
+ _applyStrictMode(policy, strict) {
43
+ if (!strict) {
44
+ return policy;
45
+ }
46
+
47
+ return {
48
+ ...policy,
49
+ strict_mode: {
50
+ ...policy.strict_mode,
51
+ warning_as_failure: true
52
+ }
53
+ };
54
+ }
55
+
56
+ _validatePolicy(policy, sourcePath) {
57
+ if (!policy || typeof policy !== 'object') {
58
+ throw new Error(`Invalid gate policy at ${sourcePath}: expected JSON object`);
59
+ }
60
+
61
+ if (!policy.rules || typeof policy.rules !== 'object') {
62
+ throw new Error(`Invalid gate policy at ${sourcePath}: missing rules object`);
63
+ }
64
+
65
+ if (policy.thresholds) {
66
+ const { go, conditional_go: conditionalGo } = policy.thresholds;
67
+ if ((go !== undefined && typeof go !== 'number') ||
68
+ (conditionalGo !== undefined && typeof conditionalGo !== 'number')) {
69
+ throw new Error(`Invalid gate policy at ${sourcePath}: thresholds must be numeric`);
70
+ }
71
+ }
72
+ }
73
+
74
+ _clone(value) {
75
+ return JSON.parse(JSON.stringify(value));
76
+ }
77
+
78
+ _deepMerge(base, override) {
79
+ const result = { ...base };
80
+
81
+ Object.keys(override || {}).forEach(key => {
82
+ const baseValue = result[key];
83
+ const overrideValue = override[key];
84
+
85
+ if (this._isPlainObject(baseValue) && this._isPlainObject(overrideValue)) {
86
+ result[key] = this._deepMerge(baseValue, overrideValue);
87
+ } else {
88
+ result[key] = overrideValue;
89
+ }
90
+ });
91
+
92
+ return result;
93
+ }
94
+
95
+ _isPlainObject(value) {
96
+ return value && typeof value === 'object' && !Array.isArray(value);
97
+ }
98
+ }
99
+
100
+ module.exports = {
101
+ PolicyLoader
102
+ };
103
+
@@ -0,0 +1,81 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+
5
+ class ResultEmitter {
6
+ constructor(projectPath = process.cwd()) {
7
+ this.projectPath = projectPath;
8
+ }
9
+
10
+ async emit(result, options = {}) {
11
+ if (!options.silent) {
12
+ if (options.json) {
13
+ console.log(JSON.stringify(result, null, 2));
14
+ } else {
15
+ this._printHumanReadable(result);
16
+ }
17
+ }
18
+
19
+ const outputPath = options.out
20
+ ? this._resolvePath(options.out)
21
+ : null;
22
+
23
+ if (outputPath) {
24
+ await fs.ensureDir(path.dirname(outputPath));
25
+ await fs.writeJson(outputPath, result, { spaces: 2 });
26
+ }
27
+
28
+ return {
29
+ outputPath
30
+ };
31
+ }
32
+
33
+ _printHumanReadable(result) {
34
+ const decisionColor = result.decision === 'go'
35
+ ? chalk.green
36
+ : result.decision === 'conditional-go'
37
+ ? chalk.yellow
38
+ : chalk.red;
39
+
40
+ console.log(chalk.red('🔥') + ' Spec Gate');
41
+ console.log();
42
+ console.log(`${chalk.gray('Spec:')} ${result.spec_id}`);
43
+ console.log(`${chalk.gray('Run:')} ${result.run_id}`);
44
+ console.log(`${chalk.gray('Decision:')} ${decisionColor(result.decision)}`);
45
+ console.log(`${chalk.gray('Score:')} ${result.score}`);
46
+ console.log();
47
+
48
+ console.log(chalk.bold('Rule Results'));
49
+ result.rules.forEach(rule => {
50
+ const icon = rule.passed ? chalk.green('✓') : chalk.red('✗');
51
+ console.log(` ${icon} ${rule.id} (${rule.score}/${rule.max_score})`);
52
+ });
53
+
54
+ if (result.failed_checks.length > 0) {
55
+ console.log();
56
+ console.log(chalk.bold('Failed Checks'));
57
+ result.failed_checks.forEach(check => {
58
+ console.log(` - ${check.id} (hard_fail: ${check.hard_fail})`);
59
+ });
60
+ }
61
+
62
+ if (result.next_actions.length > 0) {
63
+ console.log();
64
+ console.log(chalk.bold('Next Actions'));
65
+ result.next_actions.forEach(action => console.log(` - ${action}`));
66
+ }
67
+ }
68
+
69
+ _resolvePath(filePath) {
70
+ if (path.isAbsolute(filePath)) {
71
+ return filePath;
72
+ }
73
+
74
+ return path.join(this.projectPath, filePath);
75
+ }
76
+ }
77
+
78
+ module.exports = {
79
+ ResultEmitter
80
+ };
81
+
@@ -0,0 +1,156 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const docsCommand = require('../../commands/docs');
4
+
5
+ function createDefaultRules(projectPath = process.cwd()) {
6
+ return [
7
+ {
8
+ id: 'mandatory',
9
+ description: 'Verify mandatory Spec files exist',
10
+ async execute(context) {
11
+ const specPath = path.join(projectPath, '.kiro', 'specs', context.specId);
12
+ const requiredFiles = ['requirements.md', 'design.md', 'tasks.md'];
13
+ const checks = await Promise.all(requiredFiles.map(async fileName => {
14
+ const filePath = path.join(specPath, fileName);
15
+ const exists = await fs.pathExists(filePath);
16
+ return { fileName, exists };
17
+ }));
18
+
19
+ const missing = checks.filter(item => !item.exists).map(item => item.fileName);
20
+ const passRatio = checks.filter(item => item.exists).length / requiredFiles.length;
21
+
22
+ return {
23
+ passed: missing.length === 0,
24
+ ratio: passRatio,
25
+ details: {
26
+ requiredFiles,
27
+ missing
28
+ },
29
+ warnings: missing.length > 0 ? [`Missing files: ${missing.join(', ')}`] : []
30
+ };
31
+ }
32
+ },
33
+ {
34
+ id: 'tests',
35
+ description: 'Verify tasks include explicit validation intent',
36
+ async execute(context) {
37
+ const tasksPath = path.join(projectPath, '.kiro', 'specs', context.specId, 'tasks.md');
38
+ if (!await fs.pathExists(tasksPath)) {
39
+ return {
40
+ passed: false,
41
+ ratio: 0,
42
+ details: { reason: 'tasks.md missing' },
43
+ warnings: ['tasks.md missing']
44
+ };
45
+ }
46
+
47
+ const content = await fs.readFile(tasksPath, 'utf8');
48
+ const hasValidationHints = /验证|Validation|Acceptance/i.test(content);
49
+
50
+ return {
51
+ passed: hasValidationHints,
52
+ ratio: hasValidationHints ? 1 : 0,
53
+ details: {
54
+ hasValidationHints
55
+ },
56
+ warnings: hasValidationHints ? [] : ['No validation markers found in tasks.md']
57
+ };
58
+ }
59
+ },
60
+ {
61
+ id: 'docs',
62
+ description: 'Validate document structure compatibility',
63
+ async execute(context) {
64
+ const exitCode = await docsCommand('validate', {
65
+ spec: context.specId
66
+ });
67
+
68
+ return {
69
+ passed: exitCode === 0,
70
+ ratio: exitCode === 0 ? 1 : 0,
71
+ details: {
72
+ exitCode
73
+ },
74
+ warnings: exitCode === 0 ? [] : [`docs validate returned exit code ${exitCode}`]
75
+ };
76
+ }
77
+ },
78
+ {
79
+ id: 'config_consistency',
80
+ description: 'Verify project-level KSE config baseline exists',
81
+ async execute() {
82
+ const kiroDir = path.join(projectPath, '.kiro');
83
+ const configDir = path.join(kiroDir, 'config');
84
+ const hasKiro = await fs.pathExists(kiroDir);
85
+ const hasConfig = await fs.pathExists(configDir);
86
+
87
+ const ratio = hasKiro && hasConfig ? 1 : hasKiro ? 0.5 : 0;
88
+ const warnings = [];
89
+ if (!hasKiro) {
90
+ warnings.push('.kiro directory missing');
91
+ }
92
+ if (hasKiro && !hasConfig) {
93
+ warnings.push('.kiro/config directory missing');
94
+ }
95
+
96
+ return {
97
+ passed: hasKiro,
98
+ ratio,
99
+ details: {
100
+ hasKiro,
101
+ hasConfig
102
+ },
103
+ warnings
104
+ };
105
+ }
106
+ },
107
+ {
108
+ id: 'traceability',
109
+ description: 'Verify requirement-design-task traceability hints',
110
+ async execute(context) {
111
+ const specPath = path.join(projectPath, '.kiro', 'specs', context.specId);
112
+ const designPath = path.join(specPath, 'design.md');
113
+ const tasksPath = path.join(specPath, 'tasks.md');
114
+
115
+ const hasDesign = await fs.pathExists(designPath);
116
+ const hasTasks = await fs.pathExists(tasksPath);
117
+
118
+ if (!hasDesign || !hasTasks) {
119
+ return {
120
+ passed: false,
121
+ ratio: 0,
122
+ details: {
123
+ hasDesign,
124
+ hasTasks
125
+ },
126
+ warnings: ['design.md or tasks.md missing']
127
+ };
128
+ }
129
+
130
+ const [designContent, tasksContent] = await Promise.all([
131
+ fs.readFile(designPath, 'utf8'),
132
+ fs.readFile(tasksPath, 'utf8')
133
+ ]);
134
+
135
+ const hasMapping = /Mapping|映射|Requirement/i.test(designContent);
136
+ const hasTaskReferences = /Requirement|Design|需求|设计/i.test(tasksContent);
137
+ const scoreParts = [hasMapping, hasTaskReferences].filter(Boolean).length;
138
+
139
+ return {
140
+ passed: hasMapping && hasTaskReferences,
141
+ ratio: scoreParts / 2,
142
+ details: {
143
+ hasMapping,
144
+ hasTaskReferences
145
+ },
146
+ warnings: hasMapping && hasTaskReferences ? [] : ['Traceability links are incomplete']
147
+ };
148
+ }
149
+ }
150
+ ];
151
+ }
152
+
153
+ module.exports = {
154
+ createDefaultRules
155
+ };
156
+
@@ -0,0 +1,51 @@
1
+ class RuleRegistry {
2
+ constructor(initialRules = []) {
3
+ this.rules = new Map();
4
+ initialRules.forEach(rule => this.register(rule));
5
+ }
6
+
7
+ register(rule) {
8
+ if (!rule || !rule.id || typeof rule.execute !== 'function') {
9
+ throw new Error('Invalid gate rule: expected id and execute()');
10
+ }
11
+
12
+ this.rules.set(rule.id, rule);
13
+ }
14
+
15
+ setEnabled(ruleId, enabled) {
16
+ const rule = this.rules.get(ruleId);
17
+ if (!rule) {
18
+ throw new Error(`Rule not found: ${ruleId}`);
19
+ }
20
+
21
+ rule.enabled = !!enabled;
22
+ }
23
+
24
+ get(ruleId) {
25
+ return this.rules.get(ruleId);
26
+ }
27
+
28
+ list() {
29
+ return Array.from(this.rules.values());
30
+ }
31
+
32
+ listEnabled(policyRules = {}) {
33
+ return this.list().filter(rule => {
34
+ const policy = policyRules[rule.id];
35
+ if (!policy) {
36
+ return false;
37
+ }
38
+
39
+ if (typeof rule.enabled === 'boolean') {
40
+ return rule.enabled;
41
+ }
42
+
43
+ return policy.enabled !== false;
44
+ });
45
+ }
46
+ }
47
+
48
+ module.exports = {
49
+ RuleRegistry
50
+ };
51
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kiro-spec-engine",
3
- "version": "1.45.13",
3
+ "version": "1.46.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": {
@@ -84,3 +84,4 @@
84
84
  "jest": "^27.5.1"
85
85
  }
86
86
  }
87
+