scene-capability-engine 3.6.65 → 3.6.67

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 (121) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +17 -6
  3. package/README.zh.md +18 -6
  4. package/bin/scene-capability-engine.js +4 -0
  5. package/docs/README.md +2 -2
  6. package/docs/command-reference.md +382 -6
  7. package/docs/document-governance.md +3 -2
  8. package/docs/integration-modes.md +62 -478
  9. package/docs/integration-philosophy.md +56 -263
  10. package/docs/magicball-project-portfolio-contract.md +114 -2
  11. package/docs/project-management/README.md +14 -0
  12. package/docs/project-management/assurance/backup.md +3 -0
  13. package/docs/project-management/assurance/config.md +3 -0
  14. package/docs/project-management/assurance/evidence/README.md +3 -0
  15. package/docs/project-management/assurance/incidents/README.md +3 -0
  16. package/docs/project-management/assurance/logs.md +3 -0
  17. package/docs/project-management/assurance/overview.md +3 -0
  18. package/docs/project-management/assurance/recovery/README.md +3 -0
  19. package/docs/project-management/assurance/resource.md +3 -0
  20. package/docs/project-management/assurance/runbooks/README.md +3 -0
  21. package/docs/project-management/delivery/acceptance/README.md +3 -0
  22. package/docs/project-management/delivery/acceptance/evidence/README.md +3 -0
  23. package/docs/project-management/delivery/acceptance/exceptions/README.md +3 -0
  24. package/docs/project-management/delivery/acceptance/reports/README.md +3 -0
  25. package/docs/project-management/delivery/documents/changes.md +3 -0
  26. package/docs/project-management/delivery/documents/issues.md +3 -0
  27. package/docs/project-management/delivery/documents/overview.md +3 -0
  28. package/docs/project-management/delivery/documents/planning.md +3 -0
  29. package/docs/project-management/delivery/documents/requirements.md +3 -0
  30. package/docs/project-management/delivery/documents/tracking.md +3 -0
  31. package/docs/project-management/delivery/handoffs/README.md +3 -0
  32. package/docs/project-management/delivery/handoffs/evidence/README.md +3 -0
  33. package/docs/project-management/delivery/handoffs/records/README.md +3 -0
  34. package/docs/project-management/delivery/overview.md +10 -0
  35. package/docs/project-management/delivery/releases/README.md +3 -0
  36. package/docs/project-management/delivery/releases/baselines/README.md +3 -0
  37. package/docs/project-management/delivery/releases/evidence/README.md +3 -0
  38. package/docs/project-management/delivery/tables/changes.md +3 -0
  39. package/docs/project-management/delivery/tables/issues.md +3 -0
  40. package/docs/project-management/delivery/tables/planning.md +3 -0
  41. package/docs/project-management/delivery/tables/requirements.md +3 -0
  42. package/docs/project-management/delivery/tables/tracking.md +3 -0
  43. package/docs/project-management/environment/agent-discovery.md +3 -0
  44. package/docs/project-management/environment/development.md +3 -0
  45. package/docs/project-management/environment/overview.md +10 -0
  46. package/docs/project-management/environment/testing.md +3 -0
  47. package/docs/project-management/environment/version-alignment.md +3 -0
  48. package/docs/quick-start-with-ai-tools.md +68 -308
  49. package/docs/releases/README.md +2 -0
  50. package/docs/releases/v3.6.66.md +23 -0
  51. package/docs/releases/v3.6.67.md +23 -0
  52. package/docs/steering-governance.md +64 -2
  53. package/docs/zh/README.md +2 -2
  54. package/docs/zh/releases/README.md +2 -0
  55. package/docs/zh/releases/v3.6.66.md +23 -0
  56. package/docs/zh/releases/v3.6.67.md +23 -0
  57. package/lib/commands/adopt.js +24 -0
  58. package/lib/commands/native.js +158 -0
  59. package/lib/commands/project.js +95 -0
  60. package/lib/commands/semantic.js +1459 -0
  61. package/lib/commands/session.js +74 -3
  62. package/lib/commands/spec-bootstrap.js +10 -1
  63. package/lib/commands/spec-gate.js +10 -1
  64. package/lib/commands/spec-pipeline.js +10 -1
  65. package/lib/commands/studio.js +405 -30
  66. package/lib/commands/task.js +141 -7
  67. package/lib/governance/supreme-principles.js +530 -0
  68. package/lib/problem/problem-evaluator.js +4 -0
  69. package/lib/project/candidate-inspection-service.js +24 -1
  70. package/lib/project/portfolio-projection-service.js +315 -5
  71. package/lib/project/project-channel-output.js +94 -0
  72. package/lib/project/project-channel-projection.js +181 -0
  73. package/lib/project/root-onboarding-service.js +60 -8
  74. package/lib/project/semantic-shared-source-projection.js +150 -0
  75. package/lib/project/supervision-action-model.js +277 -0
  76. package/lib/project/supervision-projection-service.js +305 -5
  77. package/lib/project/target-resolution-service.js +70 -5
  78. package/lib/project/visibility-policy.js +93 -0
  79. package/lib/runtime/multi-spec-scene-session.js +8 -1
  80. package/lib/runtime/project-channel-context-store.js +387 -0
  81. package/lib/runtime/project-channel-context.js +406 -0
  82. package/lib/runtime/scene-session-binding.js +46 -0
  83. package/lib/runtime/session-store.js +186 -0
  84. package/lib/runtime/steering-contract.js +7 -1
  85. package/lib/semantic/archive-report.js +283 -0
  86. package/lib/semantic/archive-routing.js +67 -0
  87. package/lib/semantic/backflow-report.js +245 -0
  88. package/lib/semantic/capability-contract.js +30 -0
  89. package/lib/semantic/delta-export.js +145 -0
  90. package/lib/semantic/interaction-observer.js +254 -0
  91. package/lib/semantic/kernel-loader.js +881 -0
  92. package/lib/semantic/native-runtime.js +359 -0
  93. package/lib/semantic/progress-ledger.js +433 -0
  94. package/lib/semantic/replay-evaluator.js +382 -0
  95. package/lib/semantic/shared-publication.js +592 -0
  96. package/lib/semantic/shared-source-config.js +183 -0
  97. package/lib/semantic/shared-source-connect.js +139 -0
  98. package/lib/semantic/shared-source-discovery.js +98 -0
  99. package/lib/semantic/shared-sync-export.js +413 -0
  100. package/lib/semantic/shared-sync-intake.js +592 -0
  101. package/lib/semantic/shared-sync-merge.js +547 -0
  102. package/lib/semantic/shared-sync-release.js +463 -0
  103. package/lib/semantic/supreme-intent-report.js +300 -0
  104. package/lib/state/sce-state-store.js +1360 -0
  105. package/lib/steering/context-sync-manager.js +276 -25
  106. package/lib/studio/spec-intake-governor.js +39 -3
  107. package/lib/studio/task-envelope.js +35 -2
  108. package/lib/workspace/takeover-baseline.js +342 -83
  109. package/package.json +7 -2
  110. package/scripts/agent-governance-baseline-audit.js +395 -0
  111. package/scripts/clarification-first-audit.js +9 -9
  112. package/scripts/deprecated-entry-audit.js +240 -0
  113. package/scripts/release-posture-report.js +262 -0
  114. package/template/.sce/README.md +62 -228
  115. package/template/.sce/config/semantic-shared-sources.json +5 -0
  116. package/template/.sce/config/supreme-principles-policy.json +105 -0
  117. package/template/.sce/config/takeover-baseline.json +7 -0
  118. package/template/.sce/steering/CORE_PRINCIPLES.md +23 -63
  119. package/template/.sce/steering/CURRENT_CONTEXT.md +4 -0
  120. package/template/.sce/steering/RULES_GUIDE.md +17 -9
  121. package/template/README.md +32 -96
@@ -0,0 +1,283 @@
1
+ const { getSceStateStore } = require('../state/sce-state-store');
2
+ const { buildSemanticArchiveDecision } = require('./archive-routing');
3
+
4
+ function normalizeString(value) {
5
+ if (typeof value !== 'string') {
6
+ return '';
7
+ }
8
+ return value.trim();
9
+ }
10
+
11
+ function normalizeInteger(value, fallback = 0) {
12
+ const parsed = Number.parseInt(`${value}`, 10);
13
+ if (!Number.isFinite(parsed) || parsed <= 0) {
14
+ return fallback;
15
+ }
16
+ return parsed;
17
+ }
18
+
19
+ function isObject(value) {
20
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
21
+ }
22
+
23
+ function readLessonPublishState(lesson = {}) {
24
+ const metadata = isObject(lesson.metadata) ? lesson.metadata : {};
25
+ const publish = isObject(metadata.publish) ? metadata.publish : {};
26
+ return normalizeString(publish.publish_state) || normalizeString(lesson.status) || 'none';
27
+ }
28
+
29
+ function readArchiveDecision(lesson = {}, ledger = {}) {
30
+ const lessonMetadata = isObject(lesson.metadata) ? lesson.metadata : {};
31
+ const lessonArchive = isObject(lessonMetadata.archive) ? lessonMetadata.archive : null;
32
+ if (lessonArchive && normalizeString(lessonArchive.target_library)) {
33
+ return lessonArchive;
34
+ }
35
+ const ledgerLineage = isObject(ledger.lineage) ? ledger.lineage : {};
36
+ const lineageArchive = isObject(ledgerLineage.archive) ? ledgerLineage.archive : null;
37
+ if (lineageArchive && normalizeString(lineageArchive.target_library)) {
38
+ return lineageArchive;
39
+ }
40
+ const ledgerPublication = isObject(ledger.publication) ? ledger.publication : {};
41
+ const publicationArchive = isObject(ledgerPublication.archive) ? ledgerPublication.archive : null;
42
+ if (publicationArchive && normalizeString(publicationArchive.target_library)) {
43
+ return publicationArchive;
44
+ }
45
+ return buildSemanticArchiveDecision({
46
+ project_id: lesson.project_id || ledger.project_id,
47
+ scene_id: lesson.scene_id || ledger.scene_id,
48
+ spec_id: lesson.spec_id || ledger.spec_id,
49
+ lesson_type: lesson.lesson_type || null,
50
+ metadata: {
51
+ channel_id: normalizeString(lessonMetadata.channel_id || ledger.channel_id) || null,
52
+ session_id: normalizeString(lessonMetadata.session_id || ledger.session_id) || null
53
+ }
54
+ });
55
+ }
56
+
57
+ function createBucket(targetLibrary, archiveDecision = {}) {
58
+ return {
59
+ target_library: targetLibrary,
60
+ archive_class: normalizeString(archiveDecision.archive_class) || null,
61
+ repository_name: normalizeString(archiveDecision.repository_name) || targetLibrary || null,
62
+ reuse_scope: normalizeString(archiveDecision.reuse_scope) || null,
63
+ lesson_count: 0,
64
+ ledger_count: 0,
65
+ candidate_count: 0,
66
+ publish_pending_count: 0,
67
+ published_shared_count: 0,
68
+ publish_blocked_count: 0,
69
+ deprecated_count: 0,
70
+ active_capability_count: 0,
71
+ quarantined_capability_count: 0,
72
+ deprecated_capability_count: 0
73
+ };
74
+ }
75
+
76
+ function incrementCounter(map, key) {
77
+ const normalizedKey = normalizeString(key) || 'unknown';
78
+ map[normalizedKey] = (map[normalizedKey] || 0) + 1;
79
+ }
80
+
81
+ function sortCounters(map = {}, keyName = 'key') {
82
+ return Object.entries(map)
83
+ .map(([key, count]) => ({ [keyName]: key, count }))
84
+ .sort((left, right) => {
85
+ if (right.count !== left.count) {
86
+ return right.count - left.count;
87
+ }
88
+ return `${left[keyName]}`.localeCompare(`${right[keyName]}`);
89
+ });
90
+ }
91
+
92
+ function buildSemanticArchiveSummary(input = {}) {
93
+ const lessons = Array.isArray(input.lessons) ? input.lessons : [];
94
+ const capabilityLedgers = Array.isArray(input.capabilityLedgers) ? input.capabilityLedgers : [];
95
+ const recentLimit = normalizeInteger(input.recent_limit || input.recentLimit, 10);
96
+ const ledgersByLessonId = {};
97
+ for (const ledger of capabilityLedgers) {
98
+ const lessonId = normalizeString(ledger.lesson_id);
99
+ if (lessonId && !ledgersByLessonId[lessonId]) {
100
+ ledgersByLessonId[lessonId] = ledger;
101
+ }
102
+ }
103
+
104
+ const byLibrary = {};
105
+ const byArchiveClass = {};
106
+ const byReuseScope = {};
107
+ let missingArchiveCount = 0;
108
+ const recentItems = [];
109
+ const seenLessonIds = new Set();
110
+
111
+ for (const lesson of lessons) {
112
+ const lessonId = normalizeString(lesson.lesson_id);
113
+ const ledger = ledgersByLessonId[lessonId] || {};
114
+ const archive = readArchiveDecision(lesson, ledger);
115
+ const targetLibrary = normalizeString(archive.target_library);
116
+ if (!targetLibrary) {
117
+ missingArchiveCount += 1;
118
+ continue;
119
+ }
120
+ if (!byLibrary[targetLibrary]) {
121
+ byLibrary[targetLibrary] = createBucket(targetLibrary, archive);
122
+ }
123
+ const bucket = byLibrary[targetLibrary];
124
+ bucket.lesson_count += 1;
125
+ const lessonStatus = normalizeString(lesson.status);
126
+ const publishState = readLessonPublishState(lesson);
127
+ if (lessonStatus === 'candidate') {
128
+ bucket.candidate_count += 1;
129
+ }
130
+ if (publishState === 'pending' || lessonStatus === 'publish_pending') {
131
+ bucket.publish_pending_count += 1;
132
+ }
133
+ if (publishState === 'published-shared' || lessonStatus === 'published-shared') {
134
+ bucket.published_shared_count += 1;
135
+ }
136
+ if (publishState === 'blocked' || lessonStatus === 'publish_blocked') {
137
+ bucket.publish_blocked_count += 1;
138
+ }
139
+ if (lessonStatus === 'deprecated') {
140
+ bucket.deprecated_count += 1;
141
+ }
142
+ incrementCounter(byArchiveClass, archive.archive_class);
143
+ incrementCounter(byReuseScope, archive.reuse_scope);
144
+ recentItems.push({
145
+ lesson_id: lessonId || null,
146
+ capability_id: normalizeString(ledger.capability_id) || null,
147
+ project_id: normalizeString(lesson.project_id) || normalizeString(ledger.project_id) || null,
148
+ channel_id: normalizeString((lesson.metadata && lesson.metadata.channel_id) || ledger.channel_id) || null,
149
+ session_id: normalizeString((lesson.metadata && lesson.metadata.session_id) || ledger.session_id) || null,
150
+ scene_id: normalizeString(lesson.scene_id) || normalizeString(ledger.scene_id) || null,
151
+ spec_id: normalizeString(lesson.spec_id) || normalizeString(ledger.spec_id) || null,
152
+ target_library: targetLibrary,
153
+ archive_class: normalizeString(archive.archive_class) || null,
154
+ reuse_scope: normalizeString(archive.reuse_scope) || null,
155
+ reason_code: normalizeString(archive.reason_code) || null,
156
+ lesson_status: lessonStatus || null,
157
+ publish_state: publishState || null,
158
+ updated_at: normalizeString(lesson.updated_at) || normalizeString(ledger.updated_at) || null
159
+ });
160
+ if (lessonId) {
161
+ seenLessonIds.add(lessonId);
162
+ }
163
+ }
164
+
165
+ for (const ledger of capabilityLedgers) {
166
+ const archive = readArchiveDecision({}, ledger);
167
+ const targetLibrary = normalizeString(ledger.target_library) || normalizeString(archive.target_library);
168
+ if (!targetLibrary) {
169
+ missingArchiveCount += 1;
170
+ continue;
171
+ }
172
+ if (!byLibrary[targetLibrary]) {
173
+ byLibrary[targetLibrary] = createBucket(targetLibrary, archive);
174
+ }
175
+ const bucket = byLibrary[targetLibrary];
176
+ bucket.ledger_count += 1;
177
+ const capabilityStatus = normalizeString(ledger.capability_status);
178
+ if (capabilityStatus === 'active') {
179
+ bucket.active_capability_count += 1;
180
+ }
181
+ if (capabilityStatus === 'quarantined') {
182
+ bucket.quarantined_capability_count += 1;
183
+ }
184
+ if (capabilityStatus === 'deprecated') {
185
+ bucket.deprecated_capability_count += 1;
186
+ }
187
+
188
+ const lessonId = normalizeString(ledger.lesson_id);
189
+ if (lessonId && !seenLessonIds.has(lessonId)) {
190
+ recentItems.push({
191
+ lesson_id: lessonId,
192
+ capability_id: normalizeString(ledger.capability_id) || null,
193
+ project_id: normalizeString(ledger.project_id) || null,
194
+ channel_id: normalizeString(ledger.channel_id) || null,
195
+ session_id: normalizeString(ledger.session_id) || null,
196
+ scene_id: normalizeString(ledger.scene_id) || null,
197
+ spec_id: normalizeString(ledger.spec_id) || null,
198
+ target_library: targetLibrary,
199
+ archive_class: normalizeString(archive.archive_class) || null,
200
+ reuse_scope: normalizeString(archive.reuse_scope) || null,
201
+ reason_code: normalizeString(archive.reason_code) || null,
202
+ lesson_status: null,
203
+ publish_state: normalizeString(ledger.publish_state) || null,
204
+ updated_at: normalizeString(ledger.updated_at) || null
205
+ });
206
+ }
207
+ }
208
+
209
+ return {
210
+ totals: {
211
+ lesson_total: lessons.length,
212
+ ledger_total: capabilityLedgers.length,
213
+ missing_archive_count: missingArchiveCount,
214
+ library_count: Object.keys(byLibrary).length
215
+ },
216
+ by_library: Object.values(byLibrary).sort((left, right) => `${left.target_library}`.localeCompare(`${right.target_library}`)),
217
+ by_archive_class: sortCounters(byArchiveClass, 'archive_class'),
218
+ by_reuse_scope: sortCounters(byReuseScope, 'reuse_scope'),
219
+ recent_items: recentItems
220
+ .sort((left, right) => `${right.updated_at || ''}`.localeCompare(`${left.updated_at || ''}`))
221
+ .slice(0, recentLimit)
222
+ };
223
+ }
224
+
225
+ async function buildSemanticArchiveReport(options = {}, dependencies = {}) {
226
+ const projectPath = dependencies.projectPath || process.cwd();
227
+ const store = dependencies.stateStore || getSceStateStore(projectPath, {
228
+ fileSystem: dependencies.fileSystem,
229
+ env: dependencies.env,
230
+ sqliteModule: dependencies.sqliteModule,
231
+ noCache: dependencies.noCache === true
232
+ });
233
+ const projectId = normalizeString(options.project_id || options.projectId);
234
+ const specId = normalizeString(options.spec_id || options.specId);
235
+ const sessionId = normalizeString(options.session_id || options.sessionId);
236
+ const limit = normalizeInteger(options.limit, 200);
237
+ const recentLimit = normalizeInteger(options.recent_limit || options.recentLimit, 10);
238
+
239
+ let lessons = await store.listSemanticLessonCandidates({
240
+ project_id: projectId,
241
+ limit
242
+ });
243
+ lessons = Array.isArray(lessons) ? lessons : [];
244
+ if (specId) {
245
+ lessons = lessons.filter((item) => normalizeString(item.spec_id) === specId);
246
+ }
247
+ if (sessionId) {
248
+ lessons = lessons.filter((item) => normalizeString(item.metadata && item.metadata.session_id) === sessionId);
249
+ }
250
+
251
+ let capabilityLedgers = await store.listSemanticCapabilityLedgers({
252
+ project_id: projectId,
253
+ spec_id: specId,
254
+ limit
255
+ });
256
+ capabilityLedgers = Array.isArray(capabilityLedgers) ? capabilityLedgers : [];
257
+ if (sessionId) {
258
+ capabilityLedgers = capabilityLedgers.filter((item) => normalizeString(item.session_id) === sessionId);
259
+ }
260
+
261
+ return {
262
+ mode: 'semantic-archive-report',
263
+ success: true,
264
+ generated_at: new Date().toISOString(),
265
+ filters: {
266
+ project_id: projectId || null,
267
+ spec_id: specId || null,
268
+ session_id: sessionId || null,
269
+ limit,
270
+ recent_limit: recentLimit
271
+ },
272
+ ...buildSemanticArchiveSummary({
273
+ lessons,
274
+ capabilityLedgers,
275
+ recent_limit: recentLimit
276
+ })
277
+ };
278
+ }
279
+
280
+ module.exports = {
281
+ buildSemanticArchiveSummary,
282
+ buildSemanticArchiveReport
283
+ };
@@ -0,0 +1,67 @@
1
+ function normalizeString(value) {
2
+ if (typeof value !== 'string') {
3
+ return '';
4
+ }
5
+ return value.trim();
6
+ }
7
+
8
+ function normalizeMetadata(payload = {}) {
9
+ return payload && typeof payload === 'object' ? payload : {};
10
+ }
11
+
12
+ function buildSemanticArchiveDecision(lesson = {}) {
13
+ const metadata = normalizeMetadata(lesson.metadata);
14
+ const lessonType = normalizeString(lesson.lesson_type);
15
+ const sceneId = normalizeString(lesson.scene_id) || null;
16
+ const specId = normalizeString(lesson.spec_id) || null;
17
+ const projectId = normalizeString(lesson.project_id) || null;
18
+ const channelId = normalizeString(metadata.channel_id || metadata.channelId) || null;
19
+ const sessionId = normalizeString(metadata.session_id || metadata.sessionId) || null;
20
+
21
+ if (lessonType === 'failure-pattern') {
22
+ return {
23
+ target_library: 'experience-library',
24
+ archive_class: 'experience',
25
+ repository_name: 'experience-library',
26
+ reuse_scope: 'cross-project',
27
+ reason_code: 'failure-pattern-routes-to-experience-library',
28
+ project_id: projectId,
29
+ channel_id: channelId,
30
+ session_id: sessionId,
31
+ scene_id: sceneId,
32
+ spec_id: specId
33
+ };
34
+ }
35
+
36
+ if (sceneId === 'scene.sce-semantic-kernel') {
37
+ return {
38
+ target_library: 'ontology-capability-library',
39
+ archive_class: 'ontology-capability',
40
+ repository_name: 'ontology-capability-library',
41
+ reuse_scope: 'cross-project',
42
+ reason_code: 'semantic-kernel-scene-routes-to-ontology-capability-library',
43
+ project_id: projectId,
44
+ channel_id: channelId,
45
+ session_id: sessionId,
46
+ scene_id: sceneId,
47
+ spec_id: specId
48
+ };
49
+ }
50
+
51
+ return {
52
+ target_library: 'application-library',
53
+ archive_class: 'application',
54
+ repository_name: 'application-library',
55
+ reuse_scope: 'project-or-spec',
56
+ reason_code: 'project-scoped-pattern-routes-to-application-library',
57
+ project_id: projectId,
58
+ channel_id: channelId,
59
+ session_id: sessionId,
60
+ scene_id: sceneId,
61
+ spec_id: specId
62
+ };
63
+ }
64
+
65
+ module.exports = {
66
+ buildSemanticArchiveDecision
67
+ };
@@ -0,0 +1,245 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+ const { getSceStateStore } = require('../state/sce-state-store');
4
+
5
+ function normalizeString(value) {
6
+ if (typeof value !== 'string') {
7
+ return '';
8
+ }
9
+ return value.trim();
10
+ }
11
+
12
+ function normalizeInteger(value, fallback = 0) {
13
+ const parsed = Number.parseInt(`${value}`, 10);
14
+ if (!Number.isFinite(parsed) || parsed <= 0) {
15
+ return fallback;
16
+ }
17
+ return parsed;
18
+ }
19
+
20
+ function readLessonPublish(lesson = {}) {
21
+ if (lesson.metadata && typeof lesson.metadata === 'object'
22
+ && lesson.metadata.publish
23
+ && typeof lesson.metadata.publish === 'object') {
24
+ return lesson.metadata.publish;
25
+ }
26
+ return {};
27
+ }
28
+
29
+ async function readJsonIfExists(filePath, fileSystem = fs) {
30
+ if (!await fileSystem.pathExists(filePath)) {
31
+ return null;
32
+ }
33
+ try {
34
+ return await fileSystem.readJson(filePath);
35
+ } catch (_error) {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ function incrementCounter(map, key) {
41
+ const normalizedKey = normalizeString(key) || 'unknown';
42
+ map[normalizedKey] = (map[normalizedKey] || 0) + 1;
43
+ }
44
+
45
+ function sortCounters(map = {}, keyName = 'reason') {
46
+ return Object.entries(map)
47
+ .map(([key, count]) => ({ [keyName]: key, count }))
48
+ .sort((left, right) => {
49
+ if (right.count !== left.count) {
50
+ return right.count - left.count;
51
+ }
52
+ return `${left[keyName]}`.localeCompare(`${right[keyName]}`);
53
+ });
54
+ }
55
+
56
+ function listUniqueSpecIds(lessons = [], explicitSpecId = '') {
57
+ const requested = normalizeString(explicitSpecId);
58
+ if (requested) {
59
+ return [requested];
60
+ }
61
+ return [...new Set(lessons.map((item) => normalizeString(item.spec_id)).filter(Boolean))].sort((a, b) => a.localeCompare(b));
62
+ }
63
+
64
+ function collectBlockedSpecIds(localBlockedLessons = [], mergeReceipts = [], releaseReceipts = []) {
65
+ const specIds = new Set();
66
+ for (const lesson of localBlockedLessons) {
67
+ const specId = normalizeString(lesson && lesson.spec_id);
68
+ if (specId) {
69
+ specIds.add(specId);
70
+ }
71
+ }
72
+ for (const receipt of [...mergeReceipts, ...releaseReceipts]) {
73
+ const specId = normalizeString(receipt && receipt.spec_id);
74
+ const blockedCount = normalizeInteger(receipt && receipt.totals && receipt.totals.blocked, 0);
75
+ if (specId && blockedCount > 0) {
76
+ specIds.add(specId);
77
+ }
78
+ }
79
+ return [...specIds].sort((left, right) => left.localeCompare(right));
80
+ }
81
+
82
+ function buildLocalBlockedItems(lessons = [], ledgersByCapabilityId = {}, limit = 10) {
83
+ const ledgersByLessonId = {};
84
+ for (const ledger of Object.values(ledgersByCapabilityId || {})) {
85
+ const lessonId = normalizeString(ledger && ledger.lesson_id);
86
+ if (lessonId && !ledgersByLessonId[lessonId]) {
87
+ ledgersByLessonId[lessonId] = ledger;
88
+ }
89
+ }
90
+ return lessons
91
+ .map((lesson) => {
92
+ const publish = readLessonPublish(lesson);
93
+ const linkedLedger = ledgersByLessonId[normalizeString(lesson.lesson_id)] || null;
94
+ const capabilityId = normalizeString(linkedLedger && linkedLedger.capability_id);
95
+ const ledger = linkedLedger;
96
+ return {
97
+ lesson_id: lesson.lesson_id,
98
+ capability_id: capabilityId || null,
99
+ project_id: lesson.project_id || null,
100
+ channel_id: normalizeString(lesson.metadata && lesson.metadata.channel_id) || null,
101
+ session_id: normalizeString(lesson.metadata && lesson.metadata.session_id) || null,
102
+ scene_id: lesson.scene_id || null,
103
+ spec_id: lesson.spec_id || null,
104
+ blocked_reason: normalizeString(publish.blocked_reason) || 'unknown',
105
+ blocked_at: normalizeString(publish.blocked_at) || null,
106
+ governance_penalty_score: Number(ledger && ledger.metrics && ledger.metrics.governance_penalty_score) || 0,
107
+ governance_refuse_count: Number(ledger && ledger.metrics && ledger.metrics.governance_refuse_count) || 0,
108
+ governance_execution_blocked_count: Number(ledger && ledger.metrics && ledger.metrics.governance_execution_blocked_count) || 0
109
+ };
110
+ })
111
+ .sort((left, right) => `${right.blocked_at || ''}`.localeCompare(`${left.blocked_at || ''}`))
112
+ .slice(0, limit);
113
+ }
114
+
115
+ async function buildSemanticBackflowReport(options = {}, dependencies = {}) {
116
+ const projectPath = dependencies.projectPath || process.cwd();
117
+ const fileSystem = dependencies.fileSystem || fs;
118
+ const store = dependencies.stateStore || getSceStateStore(projectPath, {
119
+ fileSystem,
120
+ env: dependencies.env,
121
+ sqliteModule: dependencies.sqliteModule,
122
+ noCache: dependencies.noCache === true
123
+ });
124
+
125
+ const projectId = normalizeString(options.project_id || options.projectId);
126
+ const specId = normalizeString(options.spec_id || options.specId);
127
+ const limit = normalizeInteger(options.limit, 200);
128
+ const recentLimit = normalizeInteger(options.recent_limit || options.recentLimit, 10);
129
+
130
+ let lessons = await store.listSemanticLessonCandidates({
131
+ project_id: projectId,
132
+ limit
133
+ });
134
+ lessons = Array.isArray(lessons) ? lessons : [];
135
+ if (specId) {
136
+ lessons = lessons.filter((item) => normalizeString(item.spec_id) === specId);
137
+ }
138
+
139
+ let ledgers = await store.listSemanticCapabilityLedgers({
140
+ project_id: projectId,
141
+ spec_id: specId,
142
+ limit
143
+ });
144
+ ledgers = Array.isArray(ledgers) ? ledgers : [];
145
+ const ledgersByCapabilityId = {};
146
+ for (const ledger of ledgers) {
147
+ const capabilityId = normalizeString(ledger.capability_id);
148
+ if (capabilityId) {
149
+ ledgersByCapabilityId[capabilityId] = ledger;
150
+ }
151
+ }
152
+
153
+ const localBlockedLessons = lessons.filter((item) => {
154
+ const publish = readLessonPublish(item);
155
+ return normalizeString(item.status) === 'publish_blocked'
156
+ || normalizeString(publish.publish_state) === 'blocked';
157
+ });
158
+ const localBlockedByReason = {};
159
+ for (const lesson of localBlockedLessons) {
160
+ incrementCounter(localBlockedByReason, readLessonPublish(lesson).blocked_reason || 'unknown');
161
+ }
162
+
163
+ const targetSpecIds = listUniqueSpecIds(lessons, specId);
164
+ const mergeBlockedByReason = {};
165
+ const releaseBlockedByReason = {};
166
+ const mergeReceipts = [];
167
+ const releaseReceipts = [];
168
+
169
+ for (const currentSpecId of targetSpecIds) {
170
+ const mergeReceiptPath = path.join(projectPath, '.sce', 'specs', currentSpecId, 'registry', 'semantic-shared', 'latest.receipt.json');
171
+ const releaseReceiptPath = path.join(projectPath, '.sce', 'specs', currentSpecId, 'registry', 'semantic-shared', 'published', 'latest.receipt.json');
172
+ const mergeReceipt = await readJsonIfExists(mergeReceiptPath, fileSystem);
173
+ const releaseReceipt = await readJsonIfExists(releaseReceiptPath, fileSystem);
174
+
175
+ if (mergeReceipt) {
176
+ mergeReceipts.push({
177
+ spec_id: currentSpecId,
178
+ success: mergeReceipt.success === true,
179
+ generated_at: normalizeString(mergeReceipt.generated_at) || null,
180
+ totals: mergeReceipt.totals || {},
181
+ blocked: Array.isArray(mergeReceipt.blocked) ? mergeReceipt.blocked : []
182
+ });
183
+ for (const item of Array.isArray(mergeReceipt.blocked) ? mergeReceipt.blocked : []) {
184
+ incrementCounter(mergeBlockedByReason, item.reason || 'unknown');
185
+ }
186
+ }
187
+
188
+ if (releaseReceipt) {
189
+ releaseReceipts.push({
190
+ spec_id: currentSpecId,
191
+ success: releaseReceipt.success === true,
192
+ generated_at: normalizeString(releaseReceipt.generated_at) || null,
193
+ totals: releaseReceipt.totals || {},
194
+ blocked: Array.isArray(releaseReceipt.blocked) ? releaseReceipt.blocked : []
195
+ });
196
+ for (const item of Array.isArray(releaseReceipt.blocked) ? releaseReceipt.blocked : []) {
197
+ incrementCounter(releaseBlockedByReason, item.reason || 'unknown');
198
+ }
199
+ }
200
+ }
201
+
202
+ const blockedSpecIds = collectBlockedSpecIds(localBlockedLessons, mergeReceipts, releaseReceipts);
203
+
204
+ return {
205
+ mode: 'semantic-backflow-report',
206
+ success: true,
207
+ generated_at: new Date().toISOString(),
208
+ filters: {
209
+ project_id: projectId || null,
210
+ spec_id: specId || null,
211
+ limit,
212
+ recent_limit: recentLimit
213
+ },
214
+ totals: {
215
+ lesson_total: lessons.length,
216
+ local_publish_blocked: localBlockedLessons.length,
217
+ central_merge_blocked: mergeReceipts.reduce((sum, item) => sum + normalizeInteger(item.totals && item.totals.blocked, 0), 0),
218
+ central_release_blocked: releaseReceipts.reduce((sum, item) => sum + normalizeInteger(item.totals && item.totals.blocked, 0), 0),
219
+ blocked_spec_count: blockedSpecIds.length,
220
+ merge_receipt_count: mergeReceipts.length,
221
+ release_receipt_count: releaseReceipts.length
222
+ },
223
+ blocked_spec_ids: blockedSpecIds,
224
+ local: {
225
+ blocked_by_reason: sortCounters(localBlockedByReason, 'reason'),
226
+ recent_blocked_items: buildLocalBlockedItems(localBlockedLessons, ledgersByCapabilityId, recentLimit)
227
+ },
228
+ central_merge: {
229
+ blocked_by_reason: sortCounters(mergeBlockedByReason, 'reason'),
230
+ receipts: mergeReceipts
231
+ .sort((left, right) => `${right.generated_at || ''}`.localeCompare(`${left.generated_at || ''}`))
232
+ .slice(0, recentLimit)
233
+ },
234
+ central_release: {
235
+ blocked_by_reason: sortCounters(releaseBlockedByReason, 'reason'),
236
+ receipts: releaseReceipts
237
+ .sort((left, right) => `${right.generated_at || ''}`.localeCompare(`${left.generated_at || ''}`))
238
+ .slice(0, recentLimit)
239
+ }
240
+ };
241
+ }
242
+
243
+ module.exports = {
244
+ buildSemanticBackflowReport
245
+ };
@@ -0,0 +1,30 @@
1
+ const { buildSemanticArchiveDecision } = require('./archive-routing');
2
+
3
+ function normalizeString(value) {
4
+ if (typeof value !== 'string') {
5
+ return '';
6
+ }
7
+ return value.trim();
8
+ }
9
+
10
+ function resolveTargetLibrary(lesson = {}) {
11
+ return normalizeString(buildSemanticArchiveDecision(lesson).target_library) || 'application-library';
12
+ }
13
+
14
+ function buildCapabilityId(lesson = {}) {
15
+ const base = normalizeString(lesson.fingerprint || lesson.lesson_id || 'capability');
16
+ const metadata = lesson.metadata && typeof lesson.metadata === 'object'
17
+ ? lesson.metadata
18
+ : {};
19
+ const channelId = normalizeString(metadata.channel_id || metadata.channelId);
20
+ const normalizedBase = base.replace(/[^a-zA-Z0-9._-]+/g, '-').toLowerCase();
21
+ const normalizedChannel = channelId.replace(/[^a-zA-Z0-9._-]+/g, '-').toLowerCase();
22
+ return normalizedChannel
23
+ ? `cap.${normalizedBase}.ch.${normalizedChannel}`
24
+ : `cap.${normalizedBase}`;
25
+ }
26
+
27
+ module.exports = {
28
+ resolveTargetLibrary,
29
+ buildCapabilityId
30
+ };