scene-capability-engine 3.6.44 → 3.6.46
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 +25 -0
- package/bin/scene-capability-engine.js +36 -2
- package/docs/command-reference.md +5 -0
- package/docs/releases/README.md +2 -0
- package/docs/releases/v3.6.45.md +18 -0
- package/docs/releases/v3.6.46.md +23 -0
- package/docs/zh/releases/README.md +2 -0
- package/docs/zh/releases/v3.6.45.md +18 -0
- package/docs/zh/releases/v3.6.46.md +23 -0
- package/lib/workspace/collab-governance-audit.js +575 -0
- package/package.json +4 -2
- package/scripts/auto-strategy-router.js +231 -0
- package/scripts/capability-mapping-report.js +339 -0
- package/scripts/check-branding-consistency.js +140 -0
- package/scripts/check-sce-tracking.js +54 -0
- package/scripts/check-skip-allowlist.js +94 -0
- package/scripts/errorbook-registry-health-gate.js +172 -0
- package/scripts/errorbook-release-gate.js +132 -0
- package/scripts/failure-attribution-repair.js +317 -0
- package/scripts/git-managed-gate.js +464 -0
- package/scripts/interactive-approval-event-projection.js +400 -0
- package/scripts/interactive-approval-workflow.js +829 -0
- package/scripts/interactive-authorization-tier-evaluate.js +413 -0
- package/scripts/interactive-change-plan-gate.js +225 -0
- package/scripts/interactive-context-bridge.js +617 -0
- package/scripts/interactive-customization-loop.js +1690 -0
- package/scripts/interactive-dialogue-governance.js +842 -0
- package/scripts/interactive-feedback-log.js +253 -0
- package/scripts/interactive-flow-smoke.js +238 -0
- package/scripts/interactive-flow.js +1059 -0
- package/scripts/interactive-governance-report.js +1112 -0
- package/scripts/interactive-intent-build.js +707 -0
- package/scripts/interactive-loop-smoke.js +215 -0
- package/scripts/interactive-moqui-adapter.js +304 -0
- package/scripts/interactive-plan-build.js +426 -0
- package/scripts/interactive-runtime-policy-evaluate.js +495 -0
- package/scripts/interactive-work-order-build.js +552 -0
- package/scripts/matrix-regression-gate.js +167 -0
- package/scripts/moqui-core-regression-suite.js +397 -0
- package/scripts/moqui-lexicon-audit.js +651 -0
- package/scripts/moqui-matrix-remediation-phased-runner.js +865 -0
- package/scripts/moqui-matrix-remediation-queue.js +852 -0
- package/scripts/moqui-metadata-extract.js +1340 -0
- package/scripts/moqui-rebuild-gate.js +167 -0
- package/scripts/moqui-release-summary.js +729 -0
- package/scripts/moqui-standard-rebuild.js +1370 -0
- package/scripts/moqui-template-baseline-report.js +682 -0
- package/scripts/npm-package-runtime-asset-check.js +221 -0
- package/scripts/problem-closure-gate.js +441 -0
- package/scripts/release-asset-integrity-check.js +216 -0
- package/scripts/release-asset-nonempty-normalize.js +166 -0
- package/scripts/release-drift-evaluate.js +223 -0
- package/scripts/release-drift-signals.js +255 -0
- package/scripts/release-governance-snapshot-export.js +132 -0
- package/scripts/release-ops-weekly-summary.js +934 -0
- package/scripts/release-risk-remediation-bundle.js +315 -0
- package/scripts/release-weekly-ops-gate.js +423 -0
- package/scripts/state-migration-reconciliation-gate.js +110 -0
- package/scripts/state-storage-tiering-audit.js +337 -0
- package/scripts/steering-content-audit.js +393 -0
- package/scripts/symbol-evidence-locate.js +366 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { execSync, spawnSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
function parseArgs(argv = []) {
|
|
9
|
+
const options = {
|
|
10
|
+
projectPath: process.cwd(),
|
|
11
|
+
failOnViolation: false,
|
|
12
|
+
json: false,
|
|
13
|
+
help: false
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
17
|
+
const token = argv[index];
|
|
18
|
+
const next = argv[index + 1];
|
|
19
|
+
|
|
20
|
+
if (token === '--project-path' && next) {
|
|
21
|
+
options.projectPath = path.resolve(next);
|
|
22
|
+
index += 1;
|
|
23
|
+
} else if (token === '--fail-on-violation') {
|
|
24
|
+
options.failOnViolation = true;
|
|
25
|
+
} else if (token === '--json') {
|
|
26
|
+
options.json = true;
|
|
27
|
+
} else if (token === '--help' || token === '-h') {
|
|
28
|
+
options.help = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return options;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function printHelp() {
|
|
36
|
+
const lines = [
|
|
37
|
+
'Usage: node scripts/npm-package-runtime-asset-check.js [options]',
|
|
38
|
+
'',
|
|
39
|
+
'Options:',
|
|
40
|
+
' --project-path <path> Project path (default: cwd)',
|
|
41
|
+
' --fail-on-violation Exit non-zero when runtime assets are missing',
|
|
42
|
+
' --json Print JSON payload',
|
|
43
|
+
' -h, --help Show help'
|
|
44
|
+
];
|
|
45
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveNpmCommand() {
|
|
49
|
+
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function collectRuntimeScriptFiles(projectPath = process.cwd(), dependencies = {}) {
|
|
53
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
54
|
+
const scriptsDir = path.join(projectPath, 'scripts');
|
|
55
|
+
const results = [];
|
|
56
|
+
|
|
57
|
+
function walk(currentDir) {
|
|
58
|
+
let entries = [];
|
|
59
|
+
try {
|
|
60
|
+
entries = fileSystem.readdirSync(currentDir, { withFileTypes: true });
|
|
61
|
+
} catch (_error) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const absolutePath = path.join(currentDir, entry.name);
|
|
67
|
+
if (entry.isDirectory()) {
|
|
68
|
+
walk(absolutePath);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (!entry.isFile()) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const relativePath = path.relative(projectPath, absolutePath).replace(/\\/g, '/');
|
|
75
|
+
if (!relativePath.startsWith('scripts/') || !relativePath.endsWith('.js')) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
results.push(relativePath);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
walk(scriptsDir);
|
|
83
|
+
return results.sort();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function runPackDryRun(projectPath = process.cwd(), dependencies = {}) {
|
|
87
|
+
if (typeof dependencies.packRunner === 'function') {
|
|
88
|
+
return dependencies.packRunner(projectPath);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (process.platform === 'win32') {
|
|
92
|
+
try {
|
|
93
|
+
const stdout = execSync('npm pack --json --dry-run', {
|
|
94
|
+
cwd: projectPath,
|
|
95
|
+
encoding: 'utf8',
|
|
96
|
+
windowsHide: true,
|
|
97
|
+
maxBuffer: 20 * 1024 * 1024
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
status: 0,
|
|
101
|
+
stdout: `${stdout || ''}`,
|
|
102
|
+
stderr: '',
|
|
103
|
+
error: ''
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
return {
|
|
107
|
+
status: Number.isInteger(error.status) ? error.status : 1,
|
|
108
|
+
stdout: `${error.stdout || ''}`,
|
|
109
|
+
stderr: `${error.stderr || ''}`,
|
|
110
|
+
error: `${error.message || ''}`.trim()
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const result = spawnSync(resolveNpmCommand(), ['pack', '--json', '--dry-run'], {
|
|
116
|
+
cwd: projectPath,
|
|
117
|
+
encoding: 'utf8',
|
|
118
|
+
windowsHide: true,
|
|
119
|
+
maxBuffer: 20 * 1024 * 1024
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
status: Number.isInteger(result.status) ? result.status : 1,
|
|
124
|
+
stdout: `${result.stdout || ''}`,
|
|
125
|
+
stderr: `${result.stderr || ''}`,
|
|
126
|
+
error: result.error ? `${result.error.message || ''}`.trim() : ''
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function extractPackedFilePaths(packStdout) {
|
|
131
|
+
const parsed = JSON.parse(`${packStdout || ''}`);
|
|
132
|
+
const payload = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
133
|
+
const files = Array.isArray(payload && payload.files) ? payload.files : [];
|
|
134
|
+
return files
|
|
135
|
+
.map((entry) => entry && typeof entry.path === 'string' ? entry.path.replace(/\\/g, '/') : '')
|
|
136
|
+
.filter(Boolean)
|
|
137
|
+
.sort();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function evaluatePackageRuntimeAssets(projectPath = process.cwd(), options = {}, dependencies = {}) {
|
|
141
|
+
const expectedScripts = Array.isArray(options.expectedScripts)
|
|
142
|
+
? options.expectedScripts.slice().sort()
|
|
143
|
+
: collectRuntimeScriptFiles(projectPath, dependencies);
|
|
144
|
+
const packResult = runPackDryRun(projectPath, dependencies);
|
|
145
|
+
|
|
146
|
+
if (packResult.status !== 0) {
|
|
147
|
+
const packError = `${packResult.stderr || ''}`.trim()
|
|
148
|
+
|| `${packResult.error || ''}`.trim()
|
|
149
|
+
|| 'unknown error';
|
|
150
|
+
const violations = [`npm pack --dry-run failed: ${packError}`];
|
|
151
|
+
return {
|
|
152
|
+
exit_code: options.failOnViolation ? 1 : 0,
|
|
153
|
+
passed: false,
|
|
154
|
+
blocked: options.failOnViolation === true,
|
|
155
|
+
violations,
|
|
156
|
+
payload: {
|
|
157
|
+
mode: 'npm-package-runtime-asset-check',
|
|
158
|
+
project_path: projectPath,
|
|
159
|
+
expected_script_count: expectedScripts.length,
|
|
160
|
+
expected_scripts: expectedScripts,
|
|
161
|
+
packed_script_count: 0,
|
|
162
|
+
packed_scripts: [],
|
|
163
|
+
missing_runtime_scripts: expectedScripts,
|
|
164
|
+
pack_error: packError,
|
|
165
|
+
passed: false
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const packedFiles = extractPackedFilePaths(packResult.stdout);
|
|
171
|
+
const packedScripts = packedFiles.filter((item) => item.startsWith('scripts/'));
|
|
172
|
+
const packedScriptSet = new Set(packedScripts);
|
|
173
|
+
const missingRuntimeScripts = expectedScripts.filter((item) => !packedScriptSet.has(item));
|
|
174
|
+
const violations = missingRuntimeScripts.map((item) => `missing runtime script from npm package: ${item}`);
|
|
175
|
+
const passed = violations.length === 0;
|
|
176
|
+
const blocked = options.failOnViolation === true && !passed;
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
exit_code: blocked ? 1 : 0,
|
|
180
|
+
passed,
|
|
181
|
+
blocked,
|
|
182
|
+
violations,
|
|
183
|
+
payload: {
|
|
184
|
+
mode: 'npm-package-runtime-asset-check',
|
|
185
|
+
project_path: projectPath,
|
|
186
|
+
expected_script_count: expectedScripts.length,
|
|
187
|
+
expected_scripts: expectedScripts,
|
|
188
|
+
packed_script_count: packedScripts.length,
|
|
189
|
+
packed_scripts: packedScripts,
|
|
190
|
+
missing_runtime_scripts: missingRuntimeScripts,
|
|
191
|
+
passed
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (require.main === module) {
|
|
197
|
+
const options = parseArgs(process.argv.slice(2));
|
|
198
|
+
if (options.help) {
|
|
199
|
+
printHelp();
|
|
200
|
+
process.exit(0);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const result = evaluatePackageRuntimeAssets(options.projectPath, options);
|
|
204
|
+
if (options.json) {
|
|
205
|
+
process.stdout.write(`${JSON.stringify(result.payload, null, 2)}\n`);
|
|
206
|
+
} else if (result.passed) {
|
|
207
|
+
process.stdout.write('[npm-package-runtime-assets] passed\n');
|
|
208
|
+
} else {
|
|
209
|
+
process.stdout.write('[npm-package-runtime-assets] blocked\n');
|
|
210
|
+
result.violations.forEach((item) => process.stdout.write(`[npm-package-runtime-assets] violation=${item}\n`));
|
|
211
|
+
}
|
|
212
|
+
process.exit(result.exit_code);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = {
|
|
216
|
+
collectRuntimeScriptFiles,
|
|
217
|
+
evaluatePackageRuntimeAssets,
|
|
218
|
+
extractPackedFilePaths,
|
|
219
|
+
parseArgs,
|
|
220
|
+
runPackDryRun
|
|
221
|
+
};
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const {
|
|
7
|
+
validateSpecDomainArtifacts,
|
|
8
|
+
analyzeSpecDomainCoverage
|
|
9
|
+
} = require('../lib/spec/domain-modeling');
|
|
10
|
+
|
|
11
|
+
const DEFAULT_POLICY_PATH = '.sce/config/problem-closure-policy.json';
|
|
12
|
+
const DEFAULT_GOVERNANCE_REPORT = '.sce/reports/interactive-governance-report.json';
|
|
13
|
+
const DEFAULT_CONTRACT_RELATIVE_PATH = path.join('custom', 'problem-contract.json');
|
|
14
|
+
const DEFAULT_POLICY = Object.freeze({
|
|
15
|
+
schema_version: '1.0',
|
|
16
|
+
enabled: true,
|
|
17
|
+
governance_report_path: DEFAULT_GOVERNANCE_REPORT,
|
|
18
|
+
verify: {
|
|
19
|
+
require_problem_contract: true,
|
|
20
|
+
require_domain_validation: true,
|
|
21
|
+
require_domain_coverage: true
|
|
22
|
+
},
|
|
23
|
+
release: {
|
|
24
|
+
require_problem_contract: true,
|
|
25
|
+
require_domain_validation: true,
|
|
26
|
+
require_domain_coverage: true,
|
|
27
|
+
require_verify_report: true,
|
|
28
|
+
require_governance_report: false,
|
|
29
|
+
block_on_high_governance_alerts: true
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function normalizeText(value) {
|
|
34
|
+
if (typeof value !== 'string') {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
return value.trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeTextList(value = [], limit = 20) {
|
|
41
|
+
if (!Array.isArray(value)) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
return value
|
|
45
|
+
.map((item) => {
|
|
46
|
+
if (typeof item === 'string') {
|
|
47
|
+
return normalizeText(item);
|
|
48
|
+
}
|
|
49
|
+
if (item && typeof item === 'object') {
|
|
50
|
+
return normalizeText(item.step || item.description || item.id || item.name || '');
|
|
51
|
+
}
|
|
52
|
+
return '';
|
|
53
|
+
})
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.slice(0, limit);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseArgs(argv = []) {
|
|
59
|
+
const options = {
|
|
60
|
+
projectPath: process.cwd(),
|
|
61
|
+
stage: 'release',
|
|
62
|
+
spec: '',
|
|
63
|
+
policy: DEFAULT_POLICY_PATH,
|
|
64
|
+
verifyReport: '',
|
|
65
|
+
governanceReport: DEFAULT_GOVERNANCE_REPORT,
|
|
66
|
+
failOnBlock: false,
|
|
67
|
+
json: false,
|
|
68
|
+
help: false
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
72
|
+
const token = argv[index];
|
|
73
|
+
const next = argv[index + 1];
|
|
74
|
+
if (token === '--project-path' && next) {
|
|
75
|
+
options.projectPath = path.resolve(next);
|
|
76
|
+
index += 1;
|
|
77
|
+
} else if (token === '--stage' && next) {
|
|
78
|
+
options.stage = normalizeText(next).toLowerCase();
|
|
79
|
+
index += 1;
|
|
80
|
+
} else if ((token === '--spec' || token === '--spec-id') && next) {
|
|
81
|
+
options.spec = normalizeText(next);
|
|
82
|
+
index += 1;
|
|
83
|
+
} else if (token === '--policy' && next) {
|
|
84
|
+
options.policy = normalizeText(next);
|
|
85
|
+
index += 1;
|
|
86
|
+
} else if (token === '--verify-report' && next) {
|
|
87
|
+
options.verifyReport = normalizeText(next);
|
|
88
|
+
index += 1;
|
|
89
|
+
} else if (token === '--governance-report' && next) {
|
|
90
|
+
options.governanceReport = normalizeText(next);
|
|
91
|
+
index += 1;
|
|
92
|
+
} else if (token === '--fail-on-block') {
|
|
93
|
+
options.failOnBlock = true;
|
|
94
|
+
} else if (token === '--json') {
|
|
95
|
+
options.json = true;
|
|
96
|
+
} else if (token === '--help' || token === '-h') {
|
|
97
|
+
options.help = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return options;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function printHelp() {
|
|
104
|
+
const lines = [
|
|
105
|
+
'Usage: node scripts/problem-closure-gate.js [options]',
|
|
106
|
+
'',
|
|
107
|
+
'Options:',
|
|
108
|
+
' --stage <verify|release> Gate stage (default: release)',
|
|
109
|
+
' --spec <spec-id> Spec id under .sce/specs (required for strict checks)',
|
|
110
|
+
` --policy <path> Policy path (default: ${DEFAULT_POLICY_PATH})`,
|
|
111
|
+
' --verify-report <path> Verify report path (required for release stage convergence)',
|
|
112
|
+
` --governance-report <path> Governance report path (default: ${DEFAULT_GOVERNANCE_REPORT})`,
|
|
113
|
+
' --project-path <path> Project path (default: cwd)',
|
|
114
|
+
' --fail-on-block Exit with code 2 when blocked',
|
|
115
|
+
' --json Print JSON payload',
|
|
116
|
+
' -h, --help Show help'
|
|
117
|
+
];
|
|
118
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function loadPolicy(raw = {}) {
|
|
122
|
+
const payload = raw && typeof raw === 'object' ? raw : {};
|
|
123
|
+
const verify = payload.verify && typeof payload.verify === 'object' ? payload.verify : {};
|
|
124
|
+
const release = payload.release && typeof payload.release === 'object' ? payload.release : {};
|
|
125
|
+
return {
|
|
126
|
+
schema_version: normalizeText(payload.schema_version) || DEFAULT_POLICY.schema_version,
|
|
127
|
+
enabled: payload.enabled !== false,
|
|
128
|
+
governance_report_path: normalizeText(payload.governance_report_path) || DEFAULT_POLICY.governance_report_path,
|
|
129
|
+
verify: {
|
|
130
|
+
require_problem_contract: verify.require_problem_contract !== false,
|
|
131
|
+
require_domain_validation: verify.require_domain_validation !== false,
|
|
132
|
+
require_domain_coverage: verify.require_domain_coverage !== false
|
|
133
|
+
},
|
|
134
|
+
release: {
|
|
135
|
+
require_problem_contract: release.require_problem_contract !== false,
|
|
136
|
+
require_domain_validation: release.require_domain_validation !== false,
|
|
137
|
+
require_domain_coverage: release.require_domain_coverage !== false,
|
|
138
|
+
require_verify_report: release.require_verify_report !== false,
|
|
139
|
+
require_governance_report: release.require_governance_report === true,
|
|
140
|
+
block_on_high_governance_alerts: release.block_on_high_governance_alerts !== false
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function evaluateProblemContract(contract = {}) {
|
|
146
|
+
const source = contract && typeof contract === 'object' ? contract : {};
|
|
147
|
+
const checks = {
|
|
148
|
+
issue_statement: normalizeText(source.issue_statement || source.issue || source.problem_statement).length > 0,
|
|
149
|
+
expected_outcome: normalizeText(source.expected_outcome || source.expected || source.success_criteria).length > 0,
|
|
150
|
+
reproduction_steps: normalizeTextList(source.reproduction_steps || source.repro_steps || source.steps).length > 0,
|
|
151
|
+
impact_scope: normalizeText(source.impact_scope || source.scope).length > 0,
|
|
152
|
+
forbidden_workarounds: normalizeTextList(
|
|
153
|
+
source.forbidden_workarounds || source.prohibited_workarounds || source.disallowed_workarounds
|
|
154
|
+
).length > 0
|
|
155
|
+
};
|
|
156
|
+
const missing = Object.keys(checks).filter((key) => !checks[key]);
|
|
157
|
+
return {
|
|
158
|
+
passed: missing.length === 0,
|
|
159
|
+
checks,
|
|
160
|
+
missing
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function readVerifySignals(projectPath, reportPath, fileSystem = fs) {
|
|
165
|
+
const normalized = normalizeText(reportPath);
|
|
166
|
+
if (!normalized) {
|
|
167
|
+
return {
|
|
168
|
+
available: false,
|
|
169
|
+
report_path: null,
|
|
170
|
+
passed: false,
|
|
171
|
+
failed_step_count: 0
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const absolutePath = path.isAbsolute(normalized)
|
|
175
|
+
? normalized
|
|
176
|
+
: path.join(projectPath, normalized);
|
|
177
|
+
if (!await fileSystem.pathExists(absolutePath)) {
|
|
178
|
+
return {
|
|
179
|
+
available: false,
|
|
180
|
+
report_path: normalized,
|
|
181
|
+
passed: false,
|
|
182
|
+
failed_step_count: 0
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const payload = await fileSystem.readJson(absolutePath).catch(() => null);
|
|
186
|
+
const steps = Array.isArray(payload && payload.steps) ? payload.steps : [];
|
|
187
|
+
const failedStepCount = steps.filter((step) => normalizeText(step && step.status).toLowerCase() === 'failed').length;
|
|
188
|
+
return {
|
|
189
|
+
available: true,
|
|
190
|
+
report_path: path.relative(projectPath, absolutePath).replace(/\\/g, '/'),
|
|
191
|
+
passed: payload && payload.passed === true && failedStepCount === 0,
|
|
192
|
+
failed_step_count: failedStepCount
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function readGovernanceSignals(projectPath, reportPath, fileSystem = fs) {
|
|
197
|
+
const normalized = normalizeText(reportPath) || DEFAULT_GOVERNANCE_REPORT;
|
|
198
|
+
const absolutePath = path.isAbsolute(normalized)
|
|
199
|
+
? normalized
|
|
200
|
+
: path.join(projectPath, normalized);
|
|
201
|
+
if (!await fileSystem.pathExists(absolutePath)) {
|
|
202
|
+
return {
|
|
203
|
+
available: false,
|
|
204
|
+
report_path: normalized,
|
|
205
|
+
high_breach_count: 0,
|
|
206
|
+
medium_breach_count: 0
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const payload = await fileSystem.readJson(absolutePath).catch(() => null);
|
|
210
|
+
const alerts = Array.isArray(payload && payload.alerts) ? payload.alerts : [];
|
|
211
|
+
let high = 0;
|
|
212
|
+
let medium = 0;
|
|
213
|
+
for (const alert of alerts) {
|
|
214
|
+
const status = normalizeText(alert && alert.status).toLowerCase();
|
|
215
|
+
const severity = normalizeText(alert && alert.severity).toLowerCase();
|
|
216
|
+
if (status !== 'breach') {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (severity === 'high') {
|
|
220
|
+
high += 1;
|
|
221
|
+
} else if (severity === 'medium') {
|
|
222
|
+
medium += 1;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
available: true,
|
|
227
|
+
report_path: path.relative(projectPath, absolutePath).replace(/\\/g, '/'),
|
|
228
|
+
high_breach_count: high,
|
|
229
|
+
medium_breach_count: medium
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function evaluateProblemClosureGate(options = {}, dependencies = {}) {
|
|
234
|
+
const projectPath = options.projectPath || dependencies.projectPath || process.cwd();
|
|
235
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
236
|
+
const stage = normalizeText(options.stage || 'release').toLowerCase();
|
|
237
|
+
const specId = normalizeText(options.spec || options.specId);
|
|
238
|
+
const policyPath = normalizeText(options.policy || options.policyPath || DEFAULT_POLICY_PATH);
|
|
239
|
+
const policyAbsolutePath = path.isAbsolute(policyPath)
|
|
240
|
+
? policyPath
|
|
241
|
+
: path.join(projectPath, policyPath);
|
|
242
|
+
const policyPayload = await fileSystem.readJson(policyAbsolutePath).catch(() => ({}));
|
|
243
|
+
const policy = loadPolicy(policyPayload);
|
|
244
|
+
const stagePolicy = stage === 'release' ? policy.release : policy.verify;
|
|
245
|
+
const verifyReport = normalizeText(options.verifyReport || options.verify_report);
|
|
246
|
+
const governanceReport = normalizeText(
|
|
247
|
+
options.governanceReport || options.governance_report || policy.governance_report_path || DEFAULT_GOVERNANCE_REPORT
|
|
248
|
+
);
|
|
249
|
+
if (!['verify', 'release'].includes(stage)) {
|
|
250
|
+
throw new Error(`--stage must be verify|release, received: ${stage || '(empty)'}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const warnings = [];
|
|
254
|
+
const violations = [];
|
|
255
|
+
const checks = {
|
|
256
|
+
spec_available: false,
|
|
257
|
+
problem_contract: {
|
|
258
|
+
available: false,
|
|
259
|
+
passed: false,
|
|
260
|
+
missing: []
|
|
261
|
+
},
|
|
262
|
+
domain_artifacts: {
|
|
263
|
+
passed: false,
|
|
264
|
+
warnings: []
|
|
265
|
+
},
|
|
266
|
+
domain_coverage: {
|
|
267
|
+
passed: false,
|
|
268
|
+
uncovered: []
|
|
269
|
+
},
|
|
270
|
+
convergence: {
|
|
271
|
+
verify_report: {
|
|
272
|
+
available: false,
|
|
273
|
+
passed: false,
|
|
274
|
+
failed_step_count: 0
|
|
275
|
+
},
|
|
276
|
+
governance: {
|
|
277
|
+
available: false,
|
|
278
|
+
high_breach_count: 0,
|
|
279
|
+
medium_breach_count: 0
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
if (policy.enabled !== true) {
|
|
285
|
+
warnings.push('problem closure gate disabled by policy');
|
|
286
|
+
return {
|
|
287
|
+
mode: 'problem-closure-gate',
|
|
288
|
+
stage,
|
|
289
|
+
spec_id: specId || null,
|
|
290
|
+
skipped: true,
|
|
291
|
+
passed: true,
|
|
292
|
+
policy,
|
|
293
|
+
warnings,
|
|
294
|
+
violations: [],
|
|
295
|
+
checks
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!specId) {
|
|
300
|
+
warnings.push('spec id is not provided; problem closure gate skipped');
|
|
301
|
+
return {
|
|
302
|
+
mode: 'problem-closure-gate',
|
|
303
|
+
stage,
|
|
304
|
+
spec_id: null,
|
|
305
|
+
skipped: true,
|
|
306
|
+
passed: true,
|
|
307
|
+
policy,
|
|
308
|
+
warnings,
|
|
309
|
+
violations: [],
|
|
310
|
+
checks
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const specRoot = path.join(projectPath, '.sce', 'specs', specId);
|
|
315
|
+
const specExists = await fileSystem.pathExists(specRoot);
|
|
316
|
+
checks.spec_available = specExists;
|
|
317
|
+
if (!specExists) {
|
|
318
|
+
violations.push(`spec not found: ${specId}`);
|
|
319
|
+
} else {
|
|
320
|
+
const contractPath = path.join(specRoot, DEFAULT_CONTRACT_RELATIVE_PATH);
|
|
321
|
+
const contractExists = await fileSystem.pathExists(contractPath);
|
|
322
|
+
checks.problem_contract.available = contractExists;
|
|
323
|
+
if (!contractExists && stagePolicy.require_problem_contract) {
|
|
324
|
+
violations.push(`missing required artifact: .sce/specs/${specId}/${DEFAULT_CONTRACT_RELATIVE_PATH.replace(/\\/g, '/')}`);
|
|
325
|
+
} else if (contractExists) {
|
|
326
|
+
const contractPayload = await fileSystem.readJson(contractPath).catch(() => null);
|
|
327
|
+
const contractEval = evaluateProblemContract(contractPayload || {});
|
|
328
|
+
checks.problem_contract.passed = contractEval.passed;
|
|
329
|
+
checks.problem_contract.missing = contractEval.missing;
|
|
330
|
+
if (!contractEval.passed && stagePolicy.require_problem_contract) {
|
|
331
|
+
violations.push(`problem contract incomplete: ${contractEval.missing.join(', ')}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const domainValidation = await validateSpecDomainArtifacts(projectPath, specId, fileSystem);
|
|
336
|
+
checks.domain_artifacts.passed = domainValidation.passed;
|
|
337
|
+
checks.domain_artifacts.warnings = Array.isArray(domainValidation.warnings) ? domainValidation.warnings : [];
|
|
338
|
+
if (!domainValidation.passed && stagePolicy.require_domain_validation) {
|
|
339
|
+
violations.push(`spec-domain validation failed: ${checks.domain_artifacts.warnings.join('; ')}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const domainCoverage = await analyzeSpecDomainCoverage(projectPath, specId, fileSystem);
|
|
343
|
+
checks.domain_coverage.passed = domainCoverage.passed;
|
|
344
|
+
checks.domain_coverage.uncovered = Array.isArray(domainCoverage.uncovered) ? domainCoverage.uncovered : [];
|
|
345
|
+
if (!domainCoverage.passed && stagePolicy.require_domain_coverage) {
|
|
346
|
+
violations.push(`spec-domain coverage gaps: ${checks.domain_coverage.uncovered.join(', ')}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (stage === 'release') {
|
|
351
|
+
const verifySignals = await readVerifySignals(projectPath, verifyReport, fileSystem);
|
|
352
|
+
checks.convergence.verify_report = verifySignals;
|
|
353
|
+
if (!verifySignals.available && stagePolicy.require_verify_report) {
|
|
354
|
+
violations.push('verify report is required for release convergence gate');
|
|
355
|
+
} else if (verifySignals.available && !verifySignals.passed && stagePolicy.require_verify_report) {
|
|
356
|
+
violations.push('verify report indicates failed checks or failed steps');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const governanceSignals = await readGovernanceSignals(projectPath, governanceReport, fileSystem);
|
|
360
|
+
checks.convergence.governance = governanceSignals;
|
|
361
|
+
if (!governanceSignals.available && stagePolicy.require_governance_report) {
|
|
362
|
+
violations.push('governance report is required for release convergence gate');
|
|
363
|
+
}
|
|
364
|
+
if (
|
|
365
|
+
governanceSignals.available
|
|
366
|
+
&& governanceSignals.high_breach_count > 0
|
|
367
|
+
&& stagePolicy.block_on_high_governance_alerts
|
|
368
|
+
) {
|
|
369
|
+
violations.push(`interactive governance has high-severity breaches: ${governanceSignals.high_breach_count}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
mode: 'problem-closure-gate',
|
|
375
|
+
stage,
|
|
376
|
+
spec_id: specId,
|
|
377
|
+
skipped: false,
|
|
378
|
+
policy,
|
|
379
|
+
passed: violations.length === 0,
|
|
380
|
+
blocked: violations.length > 0,
|
|
381
|
+
warnings,
|
|
382
|
+
violations,
|
|
383
|
+
checks
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async function runProblemClosureGateScript(options = {}, dependencies = {}) {
|
|
388
|
+
const payload = await evaluateProblemClosureGate(options, dependencies);
|
|
389
|
+
if (options.json) {
|
|
390
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
391
|
+
} else if (payload.passed) {
|
|
392
|
+
process.stdout.write('[problem-closure-gate] passed\n');
|
|
393
|
+
if (payload.skipped) {
|
|
394
|
+
process.stdout.write('[problem-closure-gate] skipped: no bound spec\n');
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
process.stdout.write('[problem-closure-gate] blocked\n');
|
|
398
|
+
payload.violations.forEach((item) => {
|
|
399
|
+
process.stdout.write(`[problem-closure-gate] violation=${item}\n`);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
...payload,
|
|
404
|
+
exit_code: options.failOnBlock && payload.passed !== true ? 2 : 0
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (require.main === module) {
|
|
409
|
+
const options = parseArgs(process.argv.slice(2));
|
|
410
|
+
if (options.help) {
|
|
411
|
+
printHelp();
|
|
412
|
+
process.exit(0);
|
|
413
|
+
}
|
|
414
|
+
runProblemClosureGateScript(options)
|
|
415
|
+
.then((result) => {
|
|
416
|
+
process.exitCode = result.exit_code;
|
|
417
|
+
})
|
|
418
|
+
.catch((error) => {
|
|
419
|
+
if (options.json) {
|
|
420
|
+
process.stdout.write(`${JSON.stringify({
|
|
421
|
+
mode: 'problem-closure-gate',
|
|
422
|
+
passed: false,
|
|
423
|
+
error: error.message
|
|
424
|
+
}, null, 2)}\n`);
|
|
425
|
+
} else {
|
|
426
|
+
process.stderr.write(`[problem-closure-gate] error=${error.message}\n`);
|
|
427
|
+
}
|
|
428
|
+
process.exitCode = 1;
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
module.exports = {
|
|
433
|
+
DEFAULT_POLICY_PATH,
|
|
434
|
+
DEFAULT_POLICY,
|
|
435
|
+
DEFAULT_GOVERNANCE_REPORT,
|
|
436
|
+
parseArgs,
|
|
437
|
+
loadPolicy,
|
|
438
|
+
evaluateProblemContract,
|
|
439
|
+
evaluateProblemClosureGate,
|
|
440
|
+
runProblemClosureGateScript
|
|
441
|
+
};
|