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.
@@ -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 url = typeof item.url === 'string' ? item.url.trim() : '';
411
- return enabled && Boolean(url);
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 "url"');
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) => item && typeof item === 'object' && item.enabled !== false && typeof item.url === 'string' && item.url.trim()).length
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: audit.passed === true,
17
- reason: audit.reason || (audit.passed === true ? 'passed' : 'violations'),
35
+ passed,
36
+ reason: passed ? (audit.reason || 'passed') : 'violations',
18
37
  summary: audit.summary || {},
19
- warnings: Array.isArray(audit.warnings) ? audit.warnings : [],
20
- violations: Array.isArray(audit.violations) ? audit.violations : [],
38
+ warnings,
39
+ violations,
40
+ project_problem_projection_sync: projectProblemProjection,
21
41
  audit
22
42
  };
23
43
  }