scene-capability-engine 3.6.52 → 3.6.54
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 +18 -0
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/docs/command-reference.md +9 -2
- package/docs/errorbook-registry.md +24 -5
- package/docs/releases/v3.6.53.md +19 -0
- package/docs/releases/v3.6.54.md +19 -0
- package/docs/zh/releases/v3.6.53.md +19 -0
- package/docs/zh/releases/v3.6.54.md +19 -0
- package/lib/commands/errorbook.js +104 -7
- package/lib/problem/project-problem-projection.js +239 -0
- package/lib/workspace/collab-governance-audit.js +206 -6
- package/lib/workspace/collab-governance-gate.js +24 -4
- package/lib/workspace/takeover-baseline.js +166 -3
- package/package.json +1 -1
- package/template/.sce/README.md +1 -1
- package/template/.sce/config/errorbook-registry.json +14 -0
- package/template/.sce/config/problem-closure-policy.json +5 -0
- package/template/.sce/knowledge/errorbook/project-shared-registry.json +15 -0
- package/template/.sce/knowledge/problem/project-shared-problems.json +16 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
loadStudioIntakePolicy,
|
|
8
|
+
scanSpecPortfolio
|
|
9
|
+
} = require('../studio/spec-intake-governor');
|
|
10
|
+
|
|
11
|
+
const PROJECT_SHARED_PROBLEM_PROJECTION_API_VERSION = 'sce.project-problem-projection/v0.1';
|
|
12
|
+
const DEFAULT_PROBLEM_CLOSURE_POLICY_PATH = '.sce/config/problem-closure-policy.json';
|
|
13
|
+
const DEFAULT_PROJECT_SHARED_PROBLEM_FILE = '.sce/knowledge/problem/project-shared-problems.json';
|
|
14
|
+
const DEFAULT_PROJECT_SHARED_PROBLEM_SCOPE = 'non_completed';
|
|
15
|
+
|
|
16
|
+
function normalizeText(value) {
|
|
17
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeProblemProjectionScope(value, fallback = DEFAULT_PROJECT_SHARED_PROBLEM_SCOPE) {
|
|
21
|
+
const normalized = normalizeText(`${value || ''}`).toLowerCase();
|
|
22
|
+
if (!normalized) {
|
|
23
|
+
return fallback;
|
|
24
|
+
}
|
|
25
|
+
if (normalized === 'all' || normalized === 'non_completed' || normalized === 'active_only') {
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
throw new Error('project_shared_projection.scope must be one of: all, non_completed, active_only');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeProblemProjectionConfig(payload = {}) {
|
|
32
|
+
const candidate = payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
33
|
+
? payload
|
|
34
|
+
: {};
|
|
35
|
+
return {
|
|
36
|
+
enabled: candidate.enabled !== false,
|
|
37
|
+
file: normalizeText(candidate.file || DEFAULT_PROJECT_SHARED_PROBLEM_FILE) || DEFAULT_PROJECT_SHARED_PROBLEM_FILE,
|
|
38
|
+
scope: normalizeProblemProjectionScope(candidate.scope, DEFAULT_PROJECT_SHARED_PROBLEM_SCOPE)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function readProblemClosurePolicy(projectPath = process.cwd(), fileSystem = fs, configPath = DEFAULT_PROBLEM_CLOSURE_POLICY_PATH) {
|
|
43
|
+
const absolutePath = path.isAbsolute(configPath)
|
|
44
|
+
? configPath
|
|
45
|
+
: path.join(projectPath, configPath);
|
|
46
|
+
if (!await fileSystem.pathExists(absolutePath)) {
|
|
47
|
+
return {
|
|
48
|
+
exists: false,
|
|
49
|
+
path: absolutePath,
|
|
50
|
+
payload: null,
|
|
51
|
+
project_shared_projection: normalizeProblemProjectionConfig({})
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const payload = await fileSystem.readJson(absolutePath);
|
|
56
|
+
return {
|
|
57
|
+
exists: true,
|
|
58
|
+
path: absolutePath,
|
|
59
|
+
payload,
|
|
60
|
+
project_shared_projection: normalizeProblemProjectionConfig(payload.project_shared_projection)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function readJsonSafe(filePath, fileSystem = fs) {
|
|
65
|
+
if (!await fileSystem.pathExists(filePath)) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return fileSystem.readJson(filePath).catch(() => null);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function pickScopeRecords(records = [], scope = DEFAULT_PROJECT_SHARED_PROBLEM_SCOPE) {
|
|
72
|
+
if (scope === 'all') {
|
|
73
|
+
return records;
|
|
74
|
+
}
|
|
75
|
+
if (scope === 'active_only') {
|
|
76
|
+
return records.filter((item) => item.lifecycle_state === 'active');
|
|
77
|
+
}
|
|
78
|
+
return records.filter((item) => item.lifecycle_state !== 'completed');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function toRelativePosix(projectPath, absolutePath) {
|
|
82
|
+
return path.relative(projectPath, absolutePath).replace(/\\/g, '/');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function buildProjectSharedProblemProjection(projectPath = process.cwd(), options = {}, dependencies = {}) {
|
|
86
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
87
|
+
const studioIntakePolicy = dependencies.studioIntakePolicy || await loadStudioIntakePolicy(projectPath, fileSystem);
|
|
88
|
+
const staleDays = Number(
|
|
89
|
+
options.staleDays
|
|
90
|
+
|| studioIntakePolicy?.governance?.stale_days
|
|
91
|
+
|| 14
|
|
92
|
+
);
|
|
93
|
+
const closurePolicy = dependencies.problemClosurePolicyBundle
|
|
94
|
+
|| await readProblemClosurePolicy(projectPath, fileSystem, options.policyPath || DEFAULT_PROBLEM_CLOSURE_POLICY_PATH);
|
|
95
|
+
const projectionConfig = normalizeProblemProjectionConfig(
|
|
96
|
+
options.projectSharedProjection || closurePolicy.project_shared_projection
|
|
97
|
+
);
|
|
98
|
+
const scanOptions = {
|
|
99
|
+
staleDays
|
|
100
|
+
};
|
|
101
|
+
const records = dependencies.specPortfolio
|
|
102
|
+
|| await scanSpecPortfolio(projectPath, scanOptions, { fileSystem });
|
|
103
|
+
const scopedRecords = pickScopeRecords(records, projectionConfig.scope);
|
|
104
|
+
const entries = [];
|
|
105
|
+
|
|
106
|
+
for (const record of scopedRecords) {
|
|
107
|
+
const specRoot = path.join(projectPath, '.sce', 'specs', record.spec_id);
|
|
108
|
+
const contractPath = path.join(specRoot, 'custom', 'problem-contract.json');
|
|
109
|
+
const domainChainPath = path.join(specRoot, 'custom', 'problem-domain-chain.json');
|
|
110
|
+
const contract = await readJsonSafe(contractPath, fileSystem);
|
|
111
|
+
const domainChain = await readJsonSafe(domainChainPath, fileSystem);
|
|
112
|
+
const problemStatement = normalizeText(
|
|
113
|
+
record.problem_statement
|
|
114
|
+
|| (contract && contract.issue_statement)
|
|
115
|
+
|| (domainChain && domainChain.problem && domainChain.problem.statement)
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (!problemStatement) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const summary = domainChain && domainChain.summary && typeof domainChain.summary === 'object'
|
|
123
|
+
? domainChain.summary
|
|
124
|
+
: {};
|
|
125
|
+
const ontologyCounts = summary.ontology_counts && typeof summary.ontology_counts === 'object'
|
|
126
|
+
? summary.ontology_counts
|
|
127
|
+
: {};
|
|
128
|
+
|
|
129
|
+
entries.push({
|
|
130
|
+
spec_id: record.spec_id,
|
|
131
|
+
scene_id: record.scene_id || null,
|
|
132
|
+
lifecycle_state: record.lifecycle_state || null,
|
|
133
|
+
updated_at: record.updated_at || null,
|
|
134
|
+
age_days: Number.isFinite(Number(record.age_days)) ? Number(record.age_days) : null,
|
|
135
|
+
tasks_total: Number.isFinite(Number(record.tasks_total)) ? Number(record.tasks_total) : 0,
|
|
136
|
+
tasks_done: Number.isFinite(Number(record.tasks_done)) ? Number(record.tasks_done) : 0,
|
|
137
|
+
tasks_progress: Number.isFinite(Number(record.tasks_progress)) ? Number(record.tasks_progress) : 0,
|
|
138
|
+
problem_statement: problemStatement,
|
|
139
|
+
expected_outcome: normalizeText(contract && contract.expected_outcome),
|
|
140
|
+
impact_scope: normalizeText(contract && contract.impact_scope),
|
|
141
|
+
reproduction_steps: Array.isArray(contract && contract.reproduction_steps) ? contract.reproduction_steps : [],
|
|
142
|
+
forbidden_workarounds: Array.isArray(contract && contract.forbidden_workarounds)
|
|
143
|
+
? contract.forbidden_workarounds
|
|
144
|
+
: [],
|
|
145
|
+
verification_gates: Array.isArray(summary.verification_gates) ? summary.verification_gates : [],
|
|
146
|
+
ontology_counts: {
|
|
147
|
+
entity: Number(ontologyCounts.entity || 0),
|
|
148
|
+
relation: Number(ontologyCounts.relation || 0),
|
|
149
|
+
business_rule: Number(ontologyCounts.business_rule || 0),
|
|
150
|
+
decision_policy: Number(ontologyCounts.decision_policy || 0),
|
|
151
|
+
execution_flow: Number(ontologyCounts.execution_flow || 0)
|
|
152
|
+
},
|
|
153
|
+
evidence_binding_count: Number(summary.evidence_binding_count || 0),
|
|
154
|
+
hypothesis_count: Number(summary.hypothesis_count || 0),
|
|
155
|
+
risk_count: Number(summary.risk_count || 0),
|
|
156
|
+
paths: {
|
|
157
|
+
problem_contract: await fileSystem.pathExists(contractPath) ? toRelativePosix(projectPath, contractPath) : null,
|
|
158
|
+
problem_domain_chain: await fileSystem.pathExists(domainChainPath) ? toRelativePosix(projectPath, domainChainPath) : null
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
entries.sort((left, right) => String(right.updated_at || '').localeCompare(String(left.updated_at || '')));
|
|
164
|
+
const activeCount = entries.filter((item) => item.lifecycle_state === 'active').length;
|
|
165
|
+
const staleCount = entries.filter((item) => item.lifecycle_state === 'stale').length;
|
|
166
|
+
const completedCount = entries.filter((item) => item.lifecycle_state === 'completed').length;
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
api_version: PROJECT_SHARED_PROBLEM_PROJECTION_API_VERSION,
|
|
170
|
+
generated_at: new Date().toISOString(),
|
|
171
|
+
source: {
|
|
172
|
+
project: path.basename(projectPath),
|
|
173
|
+
stale_days: staleDays,
|
|
174
|
+
scope: projectionConfig.scope
|
|
175
|
+
},
|
|
176
|
+
summary: {
|
|
177
|
+
total_entries: entries.length,
|
|
178
|
+
active_entries: activeCount,
|
|
179
|
+
stale_entries: staleCount,
|
|
180
|
+
completed_entries: completedCount
|
|
181
|
+
},
|
|
182
|
+
entries
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function syncProjectSharedProblemProjection(projectPath = process.cwd(), options = {}, dependencies = {}) {
|
|
187
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
188
|
+
const closurePolicy = dependencies.problemClosurePolicyBundle
|
|
189
|
+
|| await readProblemClosurePolicy(projectPath, fileSystem, options.policyPath || DEFAULT_PROBLEM_CLOSURE_POLICY_PATH);
|
|
190
|
+
const projectionConfig = normalizeProblemProjectionConfig(
|
|
191
|
+
options.projectSharedProjection || closurePolicy.project_shared_projection
|
|
192
|
+
);
|
|
193
|
+
const absolutePath = path.isAbsolute(projectionConfig.file)
|
|
194
|
+
? projectionConfig.file
|
|
195
|
+
: path.join(projectPath, projectionConfig.file);
|
|
196
|
+
|
|
197
|
+
if (projectionConfig.enabled !== true) {
|
|
198
|
+
return {
|
|
199
|
+
mode: 'project-problem-projection-sync',
|
|
200
|
+
enabled: false,
|
|
201
|
+
file: absolutePath,
|
|
202
|
+
scope: projectionConfig.scope,
|
|
203
|
+
total_entries: 0,
|
|
204
|
+
refreshed: false
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const payload = await buildProjectSharedProblemProjection(projectPath, {
|
|
209
|
+
...options,
|
|
210
|
+
projectSharedProjection: projectionConfig
|
|
211
|
+
}, {
|
|
212
|
+
...dependencies,
|
|
213
|
+
problemClosurePolicyBundle: closurePolicy
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
await fileSystem.ensureDir(path.dirname(absolutePath));
|
|
217
|
+
await fileSystem.writeJson(absolutePath, payload, { spaces: 2 });
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
mode: 'project-problem-projection-sync',
|
|
221
|
+
enabled: true,
|
|
222
|
+
file: absolutePath,
|
|
223
|
+
scope: projectionConfig.scope,
|
|
224
|
+
total_entries: Number(payload.summary.total_entries || payload.entries.length || 0),
|
|
225
|
+
refreshed: true
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
module.exports = {
|
|
230
|
+
PROJECT_SHARED_PROBLEM_PROJECTION_API_VERSION,
|
|
231
|
+
DEFAULT_PROBLEM_CLOSURE_POLICY_PATH,
|
|
232
|
+
DEFAULT_PROJECT_SHARED_PROBLEM_FILE,
|
|
233
|
+
DEFAULT_PROJECT_SHARED_PROBLEM_SCOPE,
|
|
234
|
+
normalizeProblemProjectionScope,
|
|
235
|
+
normalizeProblemProjectionConfig,
|
|
236
|
+
readProblemClosurePolicy,
|
|
237
|
+
buildProjectSharedProblemProjection,
|
|
238
|
+
syncProjectSharedProblemProjection
|
|
239
|
+
};
|
|
@@ -5,6 +5,11 @@ const path = require('path');
|
|
|
5
5
|
const { minimatch } = require('minimatch');
|
|
6
6
|
const { loadGitSnapshot } = require('./spec-delivery-audit');
|
|
7
7
|
const SteeringComplianceChecker = require('../steering/steering-compliance-checker');
|
|
8
|
+
const {
|
|
9
|
+
normalizeProblemProjectionConfig,
|
|
10
|
+
DEFAULT_PROBLEM_CLOSURE_POLICY_PATH,
|
|
11
|
+
DEFAULT_PROJECT_SHARED_PROBLEM_FILE
|
|
12
|
+
} = require('../problem/project-problem-projection');
|
|
8
13
|
|
|
9
14
|
const MAX_SCAN_BYTES = 256 * 1024;
|
|
10
15
|
const ACTIVE_TEXT_EXTENSIONS = new Set([
|
|
@@ -51,6 +56,10 @@ const ACTIVE_TEXT_SCAN_EXCLUDES = Object.freeze([
|
|
|
51
56
|
const LEGACY_REFERENCE_REGEX = /\.kiro(?:[\\/]|-workspaces\b)/;
|
|
52
57
|
const MULTI_AGENT_CONFIG_REFERENCE = '.sce/config/multi-agent.json';
|
|
53
58
|
const ERRORBOOK_REGISTRY_CONFIG_PATH = '.sce/config/errorbook-registry.json';
|
|
59
|
+
const PROJECT_SHARED_ERRORBOOK_REGISTRY_PATH = '.sce/knowledge/errorbook/project-shared-registry.json';
|
|
60
|
+
const PROJECT_SHARED_ERRORBOOK_SOURCE_NAME = 'project-shared';
|
|
61
|
+
const PROBLEM_CLOSURE_POLICY_PATH = DEFAULT_PROBLEM_CLOSURE_POLICY_PATH;
|
|
62
|
+
const PROJECT_SHARED_PROBLEM_FILE_PATH = DEFAULT_PROJECT_SHARED_PROBLEM_FILE;
|
|
54
63
|
const ADOPTION_CONFIG_PATH = '.sce/adoption-config.json';
|
|
55
64
|
|
|
56
65
|
const REQUIRED_GITIGNORE_RULES = Object.freeze([
|
|
@@ -396,6 +405,24 @@ function validateErrorbookRegistryConfig(payload) {
|
|
|
396
405
|
violations.push('errorbook registry config must declare non-empty field "cache_file"');
|
|
397
406
|
}
|
|
398
407
|
|
|
408
|
+
const projection = payload.project_shared_projection;
|
|
409
|
+
if (!projection || typeof projection !== 'object' || Array.isArray(projection)) {
|
|
410
|
+
violations.push('errorbook registry config must declare object field "project_shared_projection"');
|
|
411
|
+
} else {
|
|
412
|
+
if (projection.enabled !== true) {
|
|
413
|
+
violations.push('errorbook registry config must keep project_shared_projection.enabled=true under co-work baseline');
|
|
414
|
+
}
|
|
415
|
+
if (typeof projection.file !== 'string' || !projection.file.trim()) {
|
|
416
|
+
violations.push('errorbook registry config must declare non-empty project_shared_projection.file');
|
|
417
|
+
}
|
|
418
|
+
if (!Array.isArray(projection.statuses) || projection.statuses.length === 0) {
|
|
419
|
+
violations.push('errorbook registry config must declare non-empty project_shared_projection.statuses');
|
|
420
|
+
}
|
|
421
|
+
if (!Number.isFinite(Number(projection.min_quality))) {
|
|
422
|
+
violations.push('errorbook registry config must declare numeric project_shared_projection.min_quality');
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
399
426
|
const sources = Array.isArray(payload.sources) ? payload.sources : [];
|
|
400
427
|
if (sources.length === 0) {
|
|
401
428
|
violations.push('errorbook registry config must declare at least one registry source');
|
|
@@ -407,18 +434,38 @@ function validateErrorbookRegistryConfig(payload) {
|
|
|
407
434
|
return false;
|
|
408
435
|
}
|
|
409
436
|
const enabled = item.enabled !== false;
|
|
410
|
-
const
|
|
411
|
-
|
|
437
|
+
const source = typeof item.url === 'string' && item.url.trim()
|
|
438
|
+
? item.url.trim()
|
|
439
|
+
: typeof item.file === 'string' && item.file.trim()
|
|
440
|
+
? item.file.trim()
|
|
441
|
+
: typeof item.path === 'string' && item.path.trim()
|
|
442
|
+
? item.path.trim()
|
|
443
|
+
: '';
|
|
444
|
+
return enabled && Boolean(source);
|
|
412
445
|
});
|
|
413
446
|
|
|
414
447
|
if (enabledSources.length === 0) {
|
|
415
|
-
violations.push('errorbook registry config must keep at least one enabled source with a non-empty
|
|
448
|
+
violations.push('errorbook registry config must keep at least one enabled source with a non-empty url/file');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (projection && typeof projection === 'object' && !Array.isArray(projection)) {
|
|
452
|
+
const hasProjectSharedSource = enabledSources.some((item) => {
|
|
453
|
+
const name = typeof item.name === 'string' ? item.name.trim() : '';
|
|
454
|
+
const file = typeof item.file === 'string' ? item.file.trim() : '';
|
|
455
|
+
const sourcePath = typeof item.path === 'string' ? item.path.trim() : '';
|
|
456
|
+
return name === PROJECT_SHARED_ERRORBOOK_SOURCE_NAME
|
|
457
|
+
|| file === projection.file
|
|
458
|
+
|| sourcePath === projection.file;
|
|
459
|
+
});
|
|
460
|
+
if (!hasProjectSharedSource) {
|
|
461
|
+
violations.push('errorbook registry config must keep an enabled project-shared source aligned with project_shared_projection.file');
|
|
462
|
+
}
|
|
416
463
|
}
|
|
417
464
|
|
|
418
465
|
return violations;
|
|
419
466
|
}
|
|
420
467
|
|
|
421
|
-
async function inspectErrorbookRegistry(projectRoot, options = {}, dependencies = {}) {
|
|
468
|
+
async function inspectErrorbookRegistry(projectRoot, gitSnapshot, options = {}, dependencies = {}) {
|
|
422
469
|
const fileSystem = dependencies.fileSystem || fs;
|
|
423
470
|
const configPath = path.join(projectRoot, '.sce', 'config', 'errorbook-registry.json');
|
|
424
471
|
const exists = await fileSystem.pathExists(configPath);
|
|
@@ -428,6 +475,9 @@ async function inspectErrorbookRegistry(projectRoot, options = {}, dependencies
|
|
|
428
475
|
valid: false,
|
|
429
476
|
enabled: null,
|
|
430
477
|
enabled_source_count: 0,
|
|
478
|
+
project_shared_projection_file: PROJECT_SHARED_ERRORBOOK_REGISTRY_PATH,
|
|
479
|
+
project_shared_projection_exists: false,
|
|
480
|
+
project_shared_projection_tracked: null,
|
|
431
481
|
warnings: [],
|
|
432
482
|
violations: [],
|
|
433
483
|
passed: true,
|
|
@@ -454,8 +504,23 @@ async function inspectErrorbookRegistry(projectRoot, options = {}, dependencies
|
|
|
454
504
|
const validationErrors = validateErrorbookRegistryConfig(payload);
|
|
455
505
|
report.valid = validationErrors.length === 0;
|
|
456
506
|
report.enabled = typeof payload.enabled === 'boolean' ? payload.enabled : null;
|
|
507
|
+
report.project_shared_projection_file = payload
|
|
508
|
+
&& payload.project_shared_projection
|
|
509
|
+
&& typeof payload.project_shared_projection.file === 'string'
|
|
510
|
+
&& payload.project_shared_projection.file.trim()
|
|
511
|
+
? normalizeRelativePath(projectRoot, payload.project_shared_projection.file.trim()) || payload.project_shared_projection.file.trim()
|
|
512
|
+
: PROJECT_SHARED_ERRORBOOK_REGISTRY_PATH;
|
|
457
513
|
report.enabled_source_count = Array.isArray(payload.sources)
|
|
458
|
-
? payload.sources.filter((item) =>
|
|
514
|
+
? payload.sources.filter((item) => {
|
|
515
|
+
if (!item || typeof item !== 'object' || Array.isArray(item) || item.enabled === false) {
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
return (
|
|
519
|
+
(typeof item.url === 'string' && item.url.trim())
|
|
520
|
+
|| (typeof item.file === 'string' && item.file.trim())
|
|
521
|
+
|| (typeof item.path === 'string' && item.path.trim())
|
|
522
|
+
);
|
|
523
|
+
}).length
|
|
459
524
|
: 0;
|
|
460
525
|
|
|
461
526
|
if (validationErrors.length > 0) {
|
|
@@ -465,6 +530,41 @@ async function inspectErrorbookRegistry(projectRoot, options = {}, dependencies
|
|
|
465
530
|
return report;
|
|
466
531
|
}
|
|
467
532
|
|
|
533
|
+
const projectionFile = report.project_shared_projection_file;
|
|
534
|
+
const projectionAbsolutePath = path.isAbsolute(projectionFile)
|
|
535
|
+
? projectionFile
|
|
536
|
+
: path.join(projectRoot, projectionFile);
|
|
537
|
+
report.project_shared_projection_exists = await fileSystem.pathExists(projectionAbsolutePath);
|
|
538
|
+
if (!report.project_shared_projection_exists) {
|
|
539
|
+
report.passed = false;
|
|
540
|
+
report.reason = 'missing-project-shared-projection';
|
|
541
|
+
report.violations.push(`shared project errorbook projection file is missing: ${projectionFile}`);
|
|
542
|
+
return report;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
try {
|
|
546
|
+
const projectionPayload = await fileSystem.readJson(projectionAbsolutePath);
|
|
547
|
+
if (!projectionPayload || typeof projectionPayload !== 'object' || Array.isArray(projectionPayload)) {
|
|
548
|
+
report.violations.push(`shared project errorbook projection must be a JSON object: ${projectionFile}`);
|
|
549
|
+
} else if (!Array.isArray(projectionPayload.entries)) {
|
|
550
|
+
report.violations.push(`shared project errorbook projection must declare entries[]: ${projectionFile}`);
|
|
551
|
+
}
|
|
552
|
+
} catch (error) {
|
|
553
|
+
report.violations.push(`invalid shared project errorbook projection: ${error.message}`);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (gitSnapshot && gitSnapshot.available === true) {
|
|
557
|
+
report.project_shared_projection_tracked = gitSnapshot.tracked_files.has(projectionFile);
|
|
558
|
+
if (report.project_shared_projection_tracked !== true) {
|
|
559
|
+
report.violations.push(`shared project errorbook projection must be tracked by git: ${projectionFile}`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (report.violations.length > 0) {
|
|
564
|
+
report.passed = false;
|
|
565
|
+
report.reason = 'invalid-config';
|
|
566
|
+
}
|
|
567
|
+
|
|
468
568
|
return report;
|
|
469
569
|
}
|
|
470
570
|
|
|
@@ -532,6 +632,101 @@ async function inspectErrorbookConvergence(projectRoot, options = {}, dependenci
|
|
|
532
632
|
return report;
|
|
533
633
|
}
|
|
534
634
|
|
|
635
|
+
async function inspectProjectProblemProjection(projectRoot, gitSnapshot, options = {}, dependencies = {}) {
|
|
636
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
637
|
+
const policyPath = path.join(projectRoot, PROBLEM_CLOSURE_POLICY_PATH);
|
|
638
|
+
const exists = await fileSystem.pathExists(policyPath);
|
|
639
|
+
const report = {
|
|
640
|
+
file: PROBLEM_CLOSURE_POLICY_PATH,
|
|
641
|
+
exists,
|
|
642
|
+
valid: false,
|
|
643
|
+
project_shared_projection_file: PROJECT_SHARED_PROBLEM_FILE_PATH,
|
|
644
|
+
project_shared_projection_exists: false,
|
|
645
|
+
project_shared_projection_tracked: null,
|
|
646
|
+
warnings: [],
|
|
647
|
+
violations: [],
|
|
648
|
+
passed: true,
|
|
649
|
+
reason: 'passed'
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
if (!exists) {
|
|
653
|
+
report.passed = false;
|
|
654
|
+
report.reason = 'missing-config';
|
|
655
|
+
report.violations.push('problem closure policy is missing');
|
|
656
|
+
return report;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
let payload;
|
|
660
|
+
try {
|
|
661
|
+
payload = await fileSystem.readJson(policyPath);
|
|
662
|
+
} catch (error) {
|
|
663
|
+
report.passed = false;
|
|
664
|
+
report.reason = 'invalid-json';
|
|
665
|
+
report.violations.push(`invalid problem closure policy: ${error.message}`);
|
|
666
|
+
return report;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
670
|
+
report.passed = false;
|
|
671
|
+
report.reason = 'invalid-config';
|
|
672
|
+
report.violations.push('problem closure policy must be a JSON object');
|
|
673
|
+
return report;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
let projection;
|
|
677
|
+
try {
|
|
678
|
+
projection = normalizeProblemProjectionConfig(payload.project_shared_projection);
|
|
679
|
+
} catch (error) {
|
|
680
|
+
report.passed = false;
|
|
681
|
+
report.reason = 'invalid-config';
|
|
682
|
+
report.violations.push(error.message);
|
|
683
|
+
return report;
|
|
684
|
+
}
|
|
685
|
+
report.valid = true;
|
|
686
|
+
report.project_shared_projection_file = normalizeRelativePath(projectRoot, projection.file) || projection.file;
|
|
687
|
+
|
|
688
|
+
if (projection.enabled !== true) {
|
|
689
|
+
report.violations.push('problem closure policy must keep project_shared_projection.enabled=true under co-work baseline');
|
|
690
|
+
}
|
|
691
|
+
if (!projection.file) {
|
|
692
|
+
report.violations.push('problem closure policy must declare non-empty project_shared_projection.file');
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const projectionFile = report.project_shared_projection_file;
|
|
696
|
+
const projectionAbsolutePath = path.isAbsolute(projectionFile)
|
|
697
|
+
? projectionFile
|
|
698
|
+
: path.join(projectRoot, projectionFile);
|
|
699
|
+
report.project_shared_projection_exists = await fileSystem.pathExists(projectionAbsolutePath);
|
|
700
|
+
if (!report.project_shared_projection_exists) {
|
|
701
|
+
report.violations.push(`shared project problem projection file is missing: ${projectionFile}`);
|
|
702
|
+
} else {
|
|
703
|
+
try {
|
|
704
|
+
const projectionPayload = await fileSystem.readJson(projectionAbsolutePath);
|
|
705
|
+
if (!projectionPayload || typeof projectionPayload !== 'object' || Array.isArray(projectionPayload)) {
|
|
706
|
+
report.violations.push(`shared project problem projection must be a JSON object: ${projectionFile}`);
|
|
707
|
+
} else if (!Array.isArray(projectionPayload.entries)) {
|
|
708
|
+
report.violations.push(`shared project problem projection must declare entries[]: ${projectionFile}`);
|
|
709
|
+
}
|
|
710
|
+
} catch (error) {
|
|
711
|
+
report.violations.push(`invalid shared project problem projection: ${error.message}`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (gitSnapshot && gitSnapshot.available === true) {
|
|
716
|
+
report.project_shared_projection_tracked = gitSnapshot.tracked_files.has(projectionFile);
|
|
717
|
+
if (report.project_shared_projection_tracked !== true) {
|
|
718
|
+
report.violations.push(`shared project problem projection must be tracked by git: ${projectionFile}`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (report.violations.length > 0) {
|
|
723
|
+
report.passed = false;
|
|
724
|
+
report.reason = 'projection-drift';
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return report;
|
|
728
|
+
}
|
|
729
|
+
|
|
535
730
|
async function inspectMultiAgentConfig(projectRoot, scanResult, options = {}, dependencies = {}) {
|
|
536
731
|
const fileSystem = dependencies.fileSystem || fs;
|
|
537
732
|
const configPath = path.join(projectRoot, '.sce', 'config', 'multi-agent.json');
|
|
@@ -652,8 +847,9 @@ async function auditCollabGovernance(projectRoot = process.cwd(), options = {},
|
|
|
652
847
|
const runtimeTracking = inspectRuntimeTracking(gitSnapshot);
|
|
653
848
|
const scanResult = await scanActiveTextReferences(projectRoot, gitSnapshot.tracked_files, options, dependencies);
|
|
654
849
|
const multiAgent = await inspectMultiAgentConfig(projectRoot, scanResult, options, dependencies);
|
|
655
|
-
const errorbookRegistry = await inspectErrorbookRegistry(projectRoot, options, dependencies);
|
|
850
|
+
const errorbookRegistry = await inspectErrorbookRegistry(projectRoot, gitSnapshot, options, dependencies);
|
|
656
851
|
const errorbookConvergence = await inspectErrorbookConvergence(projectRoot, options, dependencies);
|
|
852
|
+
const projectProblemProjection = await inspectProjectProblemProjection(projectRoot, gitSnapshot, options, dependencies);
|
|
657
853
|
const steeringBoundary = inspectSteeringBoundary(projectRoot);
|
|
658
854
|
|
|
659
855
|
const legacyReferences = {
|
|
@@ -680,6 +876,7 @@ async function auditCollabGovernance(projectRoot = process.cwd(), options = {},
|
|
|
680
876
|
multi_agent: multiAgent,
|
|
681
877
|
errorbook_registry: errorbookRegistry,
|
|
682
878
|
errorbook_convergence: errorbookConvergence,
|
|
879
|
+
problem_projection: projectProblemProjection,
|
|
683
880
|
legacy_references: legacyReferences,
|
|
684
881
|
steering_boundary: steeringBoundary,
|
|
685
882
|
summary: {
|
|
@@ -689,6 +886,7 @@ async function auditCollabGovernance(projectRoot = process.cwd(), options = {},
|
|
|
689
886
|
multi_agent_violations: multiAgent.violations.length,
|
|
690
887
|
errorbook_registry_violations: errorbookRegistry.violations.length,
|
|
691
888
|
errorbook_convergence_violations: errorbookConvergence.violations.length,
|
|
889
|
+
problem_projection_violations: projectProblemProjection.violations.length,
|
|
692
890
|
legacy_reference_count: legacyReferences.matches.length,
|
|
693
891
|
steering_boundary_violations: steeringBoundary.violations.length
|
|
694
892
|
},
|
|
@@ -704,6 +902,7 @@ async function auditCollabGovernance(projectRoot = process.cwd(), options = {},
|
|
|
704
902
|
report.warnings.push(...multiAgent.warnings);
|
|
705
903
|
report.warnings.push(...errorbookRegistry.warnings);
|
|
706
904
|
report.warnings.push(...errorbookConvergence.warnings);
|
|
905
|
+
report.warnings.push(...projectProblemProjection.warnings);
|
|
707
906
|
report.warnings.push(...legacyReferences.warnings);
|
|
708
907
|
report.warnings.push(...steeringBoundary.warnings);
|
|
709
908
|
|
|
@@ -712,6 +911,7 @@ async function auditCollabGovernance(projectRoot = process.cwd(), options = {},
|
|
|
712
911
|
report.violations.push(...multiAgent.violations);
|
|
713
912
|
report.violations.push(...errorbookRegistry.violations);
|
|
714
913
|
report.violations.push(...errorbookConvergence.violations);
|
|
914
|
+
report.violations.push(...projectProblemProjection.violations);
|
|
715
915
|
report.violations.push(...legacyReferences.violations);
|
|
716
916
|
report.violations.push(
|
|
717
917
|
...steeringBoundary.violations.map((item) => `steering boundary violation: ${item.path || item.name}`)
|
|
@@ -1,23 +1,43 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { auditCollabGovernance } = require('./collab-governance-audit');
|
|
4
|
+
const { syncProjectSharedProblemProjection } = require('../problem/project-problem-projection');
|
|
4
5
|
|
|
5
6
|
async function evaluateCollabGovernanceGate(options = {}, dependencies = {}) {
|
|
6
7
|
const projectPath = options.projectPath || dependencies.projectPath || process.cwd();
|
|
8
|
+
const warnings = [];
|
|
9
|
+
const preflightViolations = [];
|
|
10
|
+
let projectProblemProjection = null;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
projectProblemProjection = await (
|
|
14
|
+
dependencies.syncProjectSharedProblemProjection || syncProjectSharedProblemProjection
|
|
15
|
+
)(projectPath, options, dependencies);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
preflightViolations.push(`project-shared problem projection sync failed: ${error.message}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
7
20
|
const audit = await (dependencies.auditCollabGovernance || auditCollabGovernance)(
|
|
8
21
|
projectPath,
|
|
9
22
|
options,
|
|
10
23
|
dependencies
|
|
11
24
|
);
|
|
25
|
+
warnings.push(...(Array.isArray(audit.warnings) ? audit.warnings : []));
|
|
26
|
+
const violations = [
|
|
27
|
+
...preflightViolations,
|
|
28
|
+
...(Array.isArray(audit.violations) ? audit.violations : [])
|
|
29
|
+
];
|
|
30
|
+
const passed = audit.passed === true && preflightViolations.length === 0;
|
|
12
31
|
|
|
13
32
|
return {
|
|
14
33
|
mode: 'collab-governance-gate',
|
|
15
34
|
project_path: projectPath,
|
|
16
|
-
passed
|
|
17
|
-
reason:
|
|
35
|
+
passed,
|
|
36
|
+
reason: passed ? (audit.reason || 'passed') : 'violations',
|
|
18
37
|
summary: audit.summary || {},
|
|
19
|
-
warnings
|
|
20
|
-
violations
|
|
38
|
+
warnings,
|
|
39
|
+
violations,
|
|
40
|
+
project_problem_projection_sync: projectProblemProjection,
|
|
21
41
|
audit
|
|
22
42
|
};
|
|
23
43
|
}
|