@sanqianx/project-knowledge 4.7.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/.agents/plugins/marketplace.json +17 -17
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/CHANGELOG.md +836 -799
  4. package/README.md +58 -41
  5. package/_modules/knowledge-engine/README.md +91 -0
  6. package/_modules/knowledge-engine/package.json +38 -0
  7. package/_modules/knowledge-engine/src/analysis-contract.js +25 -0
  8. package/_modules/knowledge-engine/src/analysis-worker.js +54 -0
  9. package/_modules/knowledge-engine/src/bin.js +52 -0
  10. package/_modules/knowledge-engine/src/bridge-client.js +24 -0
  11. package/_modules/knowledge-engine/src/coordinator.js +263 -0
  12. package/_modules/knowledge-engine/src/headless-analyzer.js +75 -0
  13. package/_modules/knowledge-engine/src/memories.js +85 -0
  14. package/_modules/knowledge-engine/src/retrieval.js +116 -0
  15. package/_modules/knowledge-engine/src/runtime/README.md +7 -0
  16. package/_modules/knowledge-engine/src/runtime/atomic-file.js +204 -0
  17. package/_modules/knowledge-engine/src/runtime/automation-config.js +140 -0
  18. package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +249 -0
  19. package/_modules/knowledge-engine/src/runtime/commit-processing-ledger.js +26 -0
  20. package/_modules/knowledge-engine/src/runtime/commit-prompt.js +202 -0
  21. package/_modules/knowledge-engine/src/runtime/commit-reconciler.js +432 -0
  22. package/_modules/knowledge-engine/src/runtime/content-hash.js +4 -0
  23. package/_modules/knowledge-engine/src/runtime/contracts.js +279 -0
  24. package/_modules/knowledge-engine/src/runtime/conversation-exclusions.js +204 -0
  25. package/_modules/knowledge-engine/src/runtime/conversation-store.js +387 -0
  26. package/_modules/knowledge-engine/src/runtime/evidence-bundle.js +228 -0
  27. package/_modules/knowledge-engine/src/runtime/git-runner.js +62 -0
  28. package/_modules/knowledge-engine/src/runtime/knowledge-promotion.js +444 -0
  29. package/_modules/knowledge-engine/src/runtime/knowledge-retrieval-service.js +402 -0
  30. package/_modules/knowledge-engine/src/runtime/layout.js +44 -0
  31. package/_modules/knowledge-engine/src/runtime/markdown-knowledge-indexer.js +230 -0
  32. package/_modules/knowledge-engine/src/runtime/scanner.js +298 -0
  33. package/_modules/knowledge-engine/src/server.js +154 -0
  34. package/_modules/knowledge-engine/src/store.js +236 -0
  35. package/_modules/knowledge-engine/ui/app.css +172 -0
  36. package/_modules/knowledge-engine/ui/app.js +297 -0
  37. package/_modules/knowledge-engine/ui/index.html +95 -0
  38. package/_site/README.md +30 -30
  39. package/_site/_test/ai-profile-resolver-test.js +137 -137
  40. package/_site/_test/automation-queue-test.js +14 -14
  41. package/_site/_test/automation-ui-test.js +40 -40
  42. package/_site/_test/background-task-registry-test.js +43 -43
  43. package/_site/_test/baseline-schema-test.js +91 -91
  44. package/_site/_test/chat-claudecodeui-match-test.js +92 -92
  45. package/_site/_test/claude-executable-discovery-test.js +62 -62
  46. package/_site/_test/claude-workbench-test.js +170 -170
  47. package/_site/_test/codex-conversation-projection-test.js +2 -1
  48. package/_site/_test/commit-conversation-binding-test.js +95 -95
  49. package/_site/_test/commit-evidence-test.js +110 -110
  50. package/_site/_test/conversation-api-test.js +109 -109
  51. package/_site/_test/conversation-store-test.js +136 -136
  52. package/_site/_test/desktop-browser-compat-test.js +28 -28
  53. package/_site/_test/desktop-hook-runtime-regression-test.js +204 -204
  54. package/_site/_test/explicit-commit-processor-test.js +37 -37
  55. package/_site/_test/fixtures/make-git-repos.js +12 -1
  56. package/_site/_test/folder-picker-output-test.js +19 -19
  57. package/_site/_test/fresh-hook-reimport-test.js +53 -0
  58. package/_site/_test/fresh-product-data-test.js +42 -0
  59. package/_site/_test/full-integration-e2e-test.js +165 -165
  60. package/_site/_test/git-validation-test.js +2 -2
  61. package/_site/_test/hook-runtime-endpoint-test.js +88 -88
  62. package/_site/_test/hook-status-repair-api-test.js +152 -152
  63. package/_site/_test/hook-trigger-test.js +85 -85
  64. package/_site/_test/import-preflight-api-test.js +234 -233
  65. package/_site/_test/index-writer-concurrency-test.js +135 -135
  66. package/_site/_test/integration-adapters-test.js +171 -171
  67. package/_site/_test/integration-surface-coverage-test.js +164 -164
  68. package/_site/_test/knowledge-language-control-test.js +170 -170
  69. package/_site/_test/knowledge-promotion-recovery-test.js +260 -260
  70. package/_site/_test/knowledge-query-test.js +54 -54
  71. package/_site/_test/knowledge-retrieval-service-test.js +77 -77
  72. package/_site/_test/knowledge-storage-startup-test.js +52 -52
  73. package/_site/_test/knowledge-store-logs-supervision-test.js +89 -89
  74. package/_site/_test/logging-api-test.js +89 -89
  75. package/_site/_test/logging-sse-no-gap-test.js +88 -88
  76. package/_site/_test/logging-ui-test.js +106 -104
  77. package/_site/_test/markdown-delta-overlay-test.js +85 -85
  78. package/_site/_test/markdown-maintenance-api-test.js +75 -75
  79. package/_site/_test/mcp-server-test.js +149 -149
  80. package/_site/_test/module-artifact-integrity-test.js +27 -0
  81. package/_site/_test/module-boundary-test.js +30 -0
  82. package/_site/_test/module-bridge-eventbridge-test.js +7 -0
  83. package/_site/_test/module-model-configuration-test.js +44 -0
  84. package/_site/_test/module-process-lifecycle-test.js +24 -0
  85. package/_site/_test/module-stream-proxy-test.js +34 -0
  86. package/_site/_test/non-release-ci-test.js +34 -34
  87. package/_site/_test/offline-boundary-isolation-test.js +32 -32
  88. package/_site/_test/packaged-ui-smoke-test.js +11 -9
  89. package/_site/_test/path-consistency-test.js +151 -151
  90. package/_site/_test/pending-sweep-test.js +9 -9
  91. package/_site/_test/post-commit-automation-test.js +87 -87
  92. package/_site/_test/product-diagnostics-ui-test.js +75 -0
  93. package/_site/_test/product-import-ui-test.js +92 -0
  94. package/_site/_test/project-delete-recovery-test.js +64 -64
  95. package/_site/_test/project-goal-editor-test.js +144 -144
  96. package/_site/_test/project-lifecycle-transaction-test.js +106 -106
  97. package/_site/_test/prompt-settings-test.js +115 -115
  98. package/_site/_test/protected-architecture-gate-test.js +122 -122
  99. package/_site/_test/refactor-characterization-test.js +36 -36
  100. package/_site/_test/release-version-sync-test.js +1 -1
  101. package/_site/_test/requirement-recorder-test.js +173 -173
  102. package/_site/_test/run-all-tests.js +159 -156
  103. package/_site/_test/shared-contracts-test.js +58 -58
  104. package/_site/_test/startup-analysis-disabled-test.js +10 -10
  105. package/_site/_test/storage-foundation-test.js +78 -78
  106. package/_site/_test/structured-logger-test.js +101 -101
  107. package/_site/_test/tracking-start-test.js +109 -108
  108. package/_site/_test/workbench-permission-test.js +117 -117
  109. package/_site/_test/workspace-ui-contract-test.js +28 -43
  110. package/_site/lib/ai-profile-resolver.js +78 -78
  111. package/_site/lib/ai-workspace.js +101 -101
  112. package/_site/lib/automation-config.js +140 -140
  113. package/_site/lib/bridge-adapter.js +1 -0
  114. package/_site/lib/bridge-consumer-service.js +402 -402
  115. package/_site/lib/claude-cli-runner.js +1510 -1510
  116. package/_site/lib/commit-conversation-binder.js +234 -234
  117. package/_site/lib/commit-processing-ledger.js +27 -27
  118. package/_site/lib/commit-prompt.js +202 -202
  119. package/_site/lib/commit-reconciler.js +406 -406
  120. package/_site/lib/contracts.js +284 -279
  121. package/_site/lib/conversation-query-service.js +144 -144
  122. package/_site/lib/conversation-store.js +385 -385
  123. package/_site/lib/data-dir.js +6 -74
  124. package/_site/lib/engine-connection.js +67 -0
  125. package/_site/lib/evidence-bundle.js +228 -228
  126. package/_site/lib/folder-picker-output.js +21 -21
  127. package/_site/lib/git-runner.js +62 -62
  128. package/_site/lib/github-team-store.js +1172 -1172
  129. package/_site/lib/hook-manager.js +203 -202
  130. package/_site/lib/index-service.js +173 -173
  131. package/_site/lib/integration-installer.js +908 -907
  132. package/_site/lib/kb-framework.js +176 -176
  133. package/_site/lib/kb-validator.js +124 -124
  134. package/_site/lib/knowledge-promotion.js +435 -435
  135. package/_site/lib/knowledge-retrieval-service.js +401 -401
  136. package/_site/lib/knowledge-tool-runtime.js +359 -359
  137. package/_site/lib/llm-client.js +161 -161
  138. package/_site/lib/markdown-knowledge-indexer.js +230 -230
  139. package/_site/lib/module-bridge.js +191 -36
  140. package/_site/lib/post-commit-automation.js +87 -87
  141. package/_site/lib/product-data.js +24 -0
  142. package/_site/lib/project-lifecycle-service.js +535 -497
  143. package/_site/lib/project-store.js +268 -262
  144. package/_site/lib/requirement-recorder.js +278 -278
  145. package/_site/lib/runtime-endpoint.js +155 -155
  146. package/_site/lib/scanner.js +298 -298
  147. package/_site/lib/server-app.js +1579 -1520
  148. package/_site/lib/settings-store.js +133 -113
  149. package/_site/lib/storage-layout.js +162 -162
  150. package/_site/lib/structured-logger.js +574 -574
  151. package/_site/scripts/folder-picker.ps1 +156 -156
  152. package/_site/scripts/hook-trigger.js +157 -156
  153. package/_site/scripts/install-module-candidates.js +69 -0
  154. package/_site/scripts/pack-module-candidates.js +72 -0
  155. package/_site/scripts/sync-release-version.js +2 -2
  156. package/_site/scripts/vendor-modules.js +47 -0
  157. package/_site/scripts/verify-module-runtime.js +45 -0
  158. package/_site/scripts/verify-product-runtime.js +34 -0
  159. package/bin/project-knowledge-mcp.js +194 -194
  160. package/docs/README.zh-CN.md +42 -29
  161. package/docs/fresh-v5-start.md +28 -0
  162. package/docs/project-registry-schema.md +22 -22
  163. package/docs/testing-strategy.md +33 -33
  164. package/module-runtime-manifest.json +220 -0
  165. package/package.json +14 -9
  166. package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
  167. package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
  168. package/plugins/project-knowledge/.mcp.json +1 -1
  169. package/plugins/project-knowledge/opencode/project-knowledge.md +3 -3
  170. package/plugins/project-knowledge/skills/project-knowledge/SKILL.md +28 -28
  171. package/templates/project-readme.md +34 -34
  172. package/ui/favicon.svg +38 -38
  173. package/ui/index.html +52 -149
  174. package/ui/product.css +9 -0
  175. package/ui/product.js +412 -0
  176. package/vendor-manifest.json +45 -0
  177. package/_site/_test/data-dir-migration-test.js +0 -77
  178. package/_site/_test/import-ui-flow-test.js +0 -171
  179. package/_site/_test/knowledge-migration-test.js +0 -75
  180. package/_site/_test/legacy-forward-compat-test.js +0 -257
  181. package/_site/_test/legacy-project-upgrade-e2e-test.js +0 -363
  182. package/_site/_test/p0-data-migration-characterization-test.js +0 -37
  183. package/_site/_test/p0-e2e-gate-test.js +0 -347
  184. package/_site/_test/project-control-panel-task14-test.js +0 -63
  185. package/_site/_test/project-layout-v2-migration-test.js +0 -127
  186. package/_site/_test/server-runtime-migration-safety-test.js +0 -27
  187. package/_site/_test/task15-20-ui-flow-test.js +0 -148
  188. package/_site/_test/ui-i18n-toggle-test.js +0 -114
  189. package/_site/_test/ui-smoke-test.js +0 -73
  190. package/_site/_test/v4122-upgrade-data-contract-test.js +0 -56
  191. package/_site/lib/data-state-classifier.js +0 -40
  192. package/_site/lib/legacy-data-manifest.js +0 -26
  193. package/_site/lib/migration-service.js +0 -442
  194. package/ui/app.css +0 -58
  195. package/ui/app.js +0 -607
  196. package/ui/i18n.js +0 -146
@@ -1,137 +1,137 @@
1
- const fs = require('fs');
2
- const { DomainError, validateProjectId } = require('./contracts');
3
- const { ConversationStore, sha256 } = require('./conversation-store');
4
- const { readDevelopmentEvents } = require('./conversation-exclusions');
5
- const { StorageLayout } = require('./storage-layout');
6
-
7
- function projectEvent(event) {
8
- return {
9
- eventId: event.eventId,
10
- sequence: event.sequence,
11
- content: event.content,
12
- contentHash: event.contentHash,
13
- capturedAt: event.capturedAt,
14
- };
15
- }
16
-
17
- class CommitConversationBinder {
18
- constructor(options = {}) {
19
- this.layout = options.layout || new StorageLayout(options);
20
- this.projectStore = options.projectStore;
21
- this.conversationStore = options.conversationStore || new ConversationStore({
22
- layout: this.layout,
23
- projectStore: this.projectStore,
24
- logger: options.logger,
25
- });
26
- this.logger = options.logger || null;
27
- }
28
-
29
- async log(level, event, message, context) {
30
- if (this.logger && typeof this.logger[level] === 'function') await this.logger[level](event, message, context);
31
- }
32
-
33
- repoMatches(left, right) {
34
- if (!left || !right) return false;
35
- // workspaceId is the canonical primary key (I-06). commonDir/repoId are
36
- // read-compat fallbacks for records stored before repo-identity/v1.
37
- if (left.workspaceId && right.workspaceId) return String(left.workspaceId) === String(right.workspaceId);
38
- if (left.commonDir && right.commonDir) return this.layout.pathsEqual(left.commonDir, right.commonDir);
39
- if (left.repoId && right.repoId) return String(left.repoId) === String(right.repoId);
40
- return false;
41
- }
42
-
43
- boundaryOrder(boundary) {
44
- return Number.isInteger(boundary.journalSequence) ? boundary.journalSequence : boundary.bridgeCursorAtCommit;
45
- }
46
-
47
- previousBoundary(projectId, boundary) {
48
- const order = this.boundaryOrder(boundary);
49
- return this.conversationStore.listBoundaries(projectId)
50
- .filter(candidate => candidate.commitSha !== boundary.commitSha
51
- && this.repoMatches(candidate.repoIdentity, boundary.repoIdentity)
52
- && this.boundaryOrder(candidate) < order)
53
- .sort((left, right) => this.boundaryOrder(right) - this.boundaryOrder(left))[0] || null;
54
- }
55
-
56
- snapshotExists(projectId, commitSha) {
57
- return fs.existsSync(this.layout.getProjectCommitConversationPath(projectId, commitSha));
58
- }
59
-
60
- async bind(input = {}) {
61
- const projectId = validateProjectId(input.projectId);
62
- const commitSha = this.layout.validateCommitSha(input.commitSha);
63
- if (this.snapshotExists(projectId, commitSha)) return this.conversationStore.readSnapshot(projectId, commitSha);
64
-
65
- if (this.projectStore) await this.conversationStore.importLegacyRequirements(projectId);
1
+ const fs = require('fs');
2
+ const { DomainError, validateProjectId } = require('./contracts');
3
+ const { ConversationStore, sha256 } = require('./conversation-store');
4
+ const { readDevelopmentEvents } = require('./conversation-exclusions');
5
+ const { StorageLayout } = require('./storage-layout');
6
+
7
+ function projectEvent(event) {
8
+ return {
9
+ eventId: event.eventId,
10
+ sequence: event.sequence,
11
+ content: event.content,
12
+ contentHash: event.contentHash,
13
+ capturedAt: event.capturedAt,
14
+ };
15
+ }
16
+
17
+ class CommitConversationBinder {
18
+ constructor(options = {}) {
19
+ this.layout = options.layout || new StorageLayout(options);
20
+ this.projectStore = options.projectStore;
21
+ this.conversationStore = options.conversationStore || new ConversationStore({
22
+ layout: this.layout,
23
+ projectStore: this.projectStore,
24
+ logger: options.logger,
25
+ });
26
+ this.logger = options.logger || null;
27
+ }
28
+
29
+ async log(level, event, message, context) {
30
+ if (this.logger && typeof this.logger[level] === 'function') await this.logger[level](event, message, context);
31
+ }
32
+
33
+ repoMatches(left, right) {
34
+ if (!left || !right) return false;
35
+ // workspaceId is the canonical primary key (I-06). commonDir/repoId are
36
+ // read-compat fallbacks for records stored before repo-identity/v1.
37
+ if (left.workspaceId && right.workspaceId) return String(left.workspaceId) === String(right.workspaceId);
38
+ if (left.commonDir && right.commonDir) return this.layout.pathsEqual(left.commonDir, right.commonDir);
39
+ if (left.repoId && right.repoId) return String(left.repoId) === String(right.repoId);
40
+ return false;
41
+ }
42
+
43
+ boundaryOrder(boundary) {
44
+ return Number.isInteger(boundary.journalSequence) ? boundary.journalSequence : boundary.bridgeCursorAtCommit;
45
+ }
46
+
47
+ previousBoundary(projectId, boundary) {
48
+ const order = this.boundaryOrder(boundary);
49
+ return this.conversationStore.listBoundaries(projectId)
50
+ .filter(candidate => candidate.commitSha !== boundary.commitSha
51
+ && this.repoMatches(candidate.repoIdentity, boundary.repoIdentity)
52
+ && this.boundaryOrder(candidate) < order)
53
+ .sort((left, right) => this.boundaryOrder(right) - this.boundaryOrder(left))[0] || null;
54
+ }
55
+
56
+ snapshotExists(projectId, commitSha) {
57
+ return fs.existsSync(this.layout.getProjectCommitConversationPath(projectId, commitSha));
58
+ }
59
+
60
+ async bind(input = {}) {
61
+ const projectId = validateProjectId(input.projectId);
62
+ const commitSha = this.layout.validateCommitSha(input.commitSha);
63
+ if (this.snapshotExists(projectId, commitSha)) return this.conversationStore.readSnapshot(projectId, commitSha);
64
+
65
+ if (this.projectStore) await this.conversationStore.importLegacyRequirements(projectId);
66
66
  // Development Conversation reads are centralized: exclusions and Codex
67
67
  // turn repair are identical here and in the query service.
68
- const events = readDevelopmentEvents(this.conversationStore, projectId);
69
- let boundary = null;
70
- try { boundary = this.conversationStore.readBoundary(projectId, commitSha); }
71
- catch (error) {
72
- if (!error || error.code !== 'ENOENT') throw error;
73
- }
74
-
75
- const explicitEvents = events.filter(event => event.role === 'user' && event.explicitCommitSha === commitSha);
76
- if (!boundary) {
77
- const turns = this.buildExplicitTurns(explicitEvents);
78
- return this.finalize(projectId, commitSha, {
79
- repoIdentity: null,
80
- parentSha: null,
81
- boundaryStartCursor: null,
82
- boundaryEndCursor: null,
83
- status: turns.length ? 'available' : 'unavailable',
84
- turns,
85
- excludedFuturePromptCount: 0,
86
- }, { reason: 'missing-commit-boundary' });
87
- }
88
-
89
- const previous = this.previousBoundary(projectId, boundary);
90
- const state = this.projectStore ? this.projectStore.readState(projectId) : { conversationBaselineCursor: null };
91
- const startCursor = previous ? previous.bridgeCursorAtCommit : state.conversationBaselineCursor;
92
- const endCursor = boundary.bridgeCursorAtCommit;
93
- const selected = new Map();
94
- let ambiguousCount = 0;
95
- let excludedFuturePromptCount = 0;
96
-
97
- for (const event of explicitEvents) {
98
- if (event.repoIdentity && boundary.repoIdentity && !this.repoMatches(event.repoIdentity, boundary.repoIdentity)) {
99
- ambiguousCount += 1;
100
- continue;
101
- }
102
- if (!event.turnId) { ambiguousCount += 1; continue; }
103
- selected.set(event.turnId, 'explicit');
104
- }
105
-
106
- if (Number.isInteger(startCursor)) {
107
- for (const event of events) {
108
- if (event.role !== 'user' || event.sequence == null || event.explicitCommitSha) continue;
109
- if (!this.repoMatches(event.repoIdentity, boundary.repoIdentity)) {
110
- if (event.sequence > startCursor && event.sequence <= endCursor && !event.repoIdentity) ambiguousCount += 1;
111
- continue;
112
- }
113
- if (event.sequence > endCursor) {
114
- excludedFuturePromptCount += 1;
115
- continue;
116
- }
117
- if (event.sequence <= startCursor) continue;
118
- // Automatic direct evidence requires exact identity. Legacy stored
119
- // 'high'/'trusted' translate to exact for old known data only;
120
- // 'explicit' is the explicit-fallback tier (I-18: no synthetic).
121
- const rawConfidence = String(event.identityConfidence || '');
122
- const effectiveConfidence = rawConfidence === 'high' || rawConfidence === 'trusted' ? 'exact' : rawConfidence;
123
- if (!event.turnId || !['exact', 'explicit'].includes(effectiveConfidence)) {
124
- ambiguousCount += 1;
125
- continue;
126
- }
127
- selected.set(event.turnId, 'direct');
128
- }
129
-
68
+ const events = readDevelopmentEvents(this.conversationStore, projectId);
69
+ let boundary = null;
70
+ try { boundary = this.conversationStore.readBoundary(projectId, commitSha); }
71
+ catch (error) {
72
+ if (!error || error.code !== 'ENOENT') throw error;
73
+ }
74
+
75
+ const explicitEvents = events.filter(event => event.role === 'user' && event.explicitCommitSha === commitSha);
76
+ if (!boundary) {
77
+ const turns = this.buildExplicitTurns(explicitEvents);
78
+ return this.finalize(projectId, commitSha, {
79
+ repoIdentity: null,
80
+ parentSha: null,
81
+ boundaryStartCursor: null,
82
+ boundaryEndCursor: null,
83
+ status: turns.length ? 'available' : 'unavailable',
84
+ turns,
85
+ excludedFuturePromptCount: 0,
86
+ }, { reason: 'missing-commit-boundary' });
87
+ }
88
+
89
+ const previous = this.previousBoundary(projectId, boundary);
90
+ const state = this.projectStore ? this.projectStore.readState(projectId) : { conversationBaselineCursor: null };
91
+ const startCursor = previous ? previous.bridgeCursorAtCommit : state.conversationBaselineCursor;
92
+ const endCursor = boundary.bridgeCursorAtCommit;
93
+ const selected = new Map();
94
+ let ambiguousCount = 0;
95
+ let excludedFuturePromptCount = 0;
96
+
97
+ for (const event of explicitEvents) {
98
+ if (event.repoIdentity && boundary.repoIdentity && !this.repoMatches(event.repoIdentity, boundary.repoIdentity)) {
99
+ ambiguousCount += 1;
100
+ continue;
101
+ }
102
+ if (!event.turnId) { ambiguousCount += 1; continue; }
103
+ selected.set(event.turnId, 'explicit');
104
+ }
105
+
106
+ if (Number.isInteger(startCursor)) {
107
+ for (const event of events) {
108
+ if (event.role !== 'user' || event.sequence == null || event.explicitCommitSha) continue;
109
+ if (!this.repoMatches(event.repoIdentity, boundary.repoIdentity)) {
110
+ if (event.sequence > startCursor && event.sequence <= endCursor && !event.repoIdentity) ambiguousCount += 1;
111
+ continue;
112
+ }
113
+ if (event.sequence > endCursor) {
114
+ excludedFuturePromptCount += 1;
115
+ continue;
116
+ }
117
+ if (event.sequence <= startCursor) continue;
118
+ // Automatic direct evidence requires exact identity. Legacy stored
119
+ // 'high'/'trusted' translate to exact for old known data only;
120
+ // 'explicit' is the explicit-fallback tier (I-18: no synthetic).
121
+ const rawConfidence = String(event.identityConfidence || '');
122
+ const effectiveConfidence = rawConfidence === 'high' || rawConfidence === 'trusted' ? 'exact' : rawConfidence;
123
+ if (!event.turnId || !['exact', 'explicit'].includes(effectiveConfidence)) {
124
+ ambiguousCount += 1;
125
+ continue;
126
+ }
127
+ selected.set(event.turnId, 'direct');
128
+ }
129
+
130
130
  for (const turnId of boundary.openTurnIdsAtCommit) {
131
131
  if (selected.has(turnId)) continue;
132
132
  const user = events.find(event => event.role === 'user'
133
- && event.turnId === turnId
134
- && event.sequence != null
133
+ && event.turnId === turnId
134
+ && event.sequence != null
135
135
  && event.sequence <= startCursor
136
136
  && this.repoMatches(event.repoIdentity, boundary.repoIdentity));
137
137
  const closedAt = user && user.developmentTurnClosedAtSequence;
@@ -139,108 +139,108 @@ class CommitConversationBinder {
139
139
  selected.set(turnId, 'shared-spanning');
140
140
  }
141
141
  }
142
- }
143
-
144
- const turns = [];
145
- for (const [turnId, bindingKind] of selected) {
146
- const turnEvents = events.filter(event => event.turnId === turnId).sort((left, right) => {
147
- if (left.sequence == null) return 1;
148
- if (right.sequence == null) return -1;
149
- return left.sequence - right.sequence;
150
- });
151
- const userEvents = turnEvents.filter(event => event.role === 'user'
152
- && (bindingKind === 'explicit' || (event.sequence != null && event.sequence <= endCursor)));
153
- if (!userEvents.length) continue;
154
- const first = userEvents[0];
155
- // I-13: no event beyond the commit boundary may enter a frozen
156
- // snapshot — including assistant replies that arrived after the
157
- // boundary but before a delayed binder run.
158
- const assistantEvents = turnEvents.filter(event => event.role === 'assistant'
159
- && (bindingKind === 'explicit' || (event.sequence != null && event.sequence <= endCursor)));
160
- turns.push({
161
- turnId,
162
- source: first.source,
163
- sessionId: first.sessionId,
164
- bindingKind,
165
- userEvents: userEvents.map(projectEvent),
166
- assistantEvents: assistantEvents.map(projectEvent),
167
- });
168
- }
169
- turns.sort((left, right) => {
170
- const a = left.userEvents[0].sequence;
171
- const b = right.userEvents[0].sequence;
172
- if (a == null) return 1;
173
- if (b == null) return -1;
174
- return a - b;
175
- });
176
-
177
- let status = turns.length ? 'available' : 'no-new-user-prompt';
178
- let reason = turns.length ? 'sequence-window' : 'no-new-user-prompt';
179
- if (!Number.isInteger(startCursor)) {
180
- status = explicitEvents.length && turns.length ? 'available' : 'unavailable';
181
- reason = 'conversation-baseline-unavailable';
182
- } else if (ambiguousCount > 0 && !turns.length) {
183
- status = 'ambiguous';
184
- reason = 'turn-or-repo-identity-ambiguous';
185
- }
186
-
187
- return this.finalize(projectId, commitSha, {
188
- repoIdentity: boundary.repoIdentity,
189
- parentSha: boundary.parentShas[0] || null,
190
- boundaryStartCursor: startCursor,
191
- boundaryEndCursor: endCursor,
192
- status,
193
- turns,
194
- excludedFuturePromptCount,
195
- }, { reason, ambiguousCount, previousBoundaryCommit: previous && previous.commitSha || null });
196
- }
197
-
198
- buildExplicitTurns(events) {
199
- return events.filter(event => event.turnId).map(event => ({
200
- turnId: event.turnId,
201
- source: event.source,
202
- sessionId: event.sessionId,
203
- bindingKind: 'explicit',
204
- userEvents: [projectEvent(event)],
205
- assistantEvents: [],
206
- }));
207
- }
208
-
209
- async finalize(projectId, commitSha, snapshotInput, decision) {
210
- const snapshot = this.conversationStore.writeSnapshot(projectId, {
211
- commitSha,
212
- ...snapshotInput,
213
- finalizedAt: new Date().toISOString(),
214
- });
215
- await this.log(snapshot.status === 'ambiguous' || snapshot.status === 'unavailable' ? 'warn' : 'info', 'conversation.commit_snapshot_frozen', 'Commit conversation snapshot was frozen.', {
216
- projectId,
217
- commitSha,
218
- component: 'commit-conversation-binder',
219
- phase: 'snapshot-frozen',
220
- context: {
221
- snapshotHash: snapshot.snapshotHash,
222
- status: snapshot.status,
223
- turnCount: snapshot.turns.length,
224
- userEventCount: snapshot.turns.reduce((total, turn) => total + turn.userEvents.length, 0),
225
- assistantEventCount: snapshot.turns.reduce((total, turn) => total + turn.assistantEvents.length, 0),
226
- decision,
227
- },
228
- });
229
- return snapshot;
230
- }
231
- }
232
-
233
- function snapshotRequirementRecords(snapshot) {
234
- return snapshot.turns.flatMap(turn => turn.userEvents.map(event => ({
235
- id: event.eventId,
236
- client: turn.source,
237
- sessionId: turn.sessionId,
238
- ts: event.capturedAt,
239
- requirement: event.content,
240
- requirementHash: event.contentHash,
241
- turnId: turn.turnId,
242
- bindingKind: turn.bindingKind,
243
- })));
244
- }
245
-
246
- module.exports = { CommitConversationBinder, snapshotRequirementRecords, projectEvent, sha256 };
142
+ }
143
+
144
+ const turns = [];
145
+ for (const [turnId, bindingKind] of selected) {
146
+ const turnEvents = events.filter(event => event.turnId === turnId).sort((left, right) => {
147
+ if (left.sequence == null) return 1;
148
+ if (right.sequence == null) return -1;
149
+ return left.sequence - right.sequence;
150
+ });
151
+ const userEvents = turnEvents.filter(event => event.role === 'user'
152
+ && (bindingKind === 'explicit' || (event.sequence != null && event.sequence <= endCursor)));
153
+ if (!userEvents.length) continue;
154
+ const first = userEvents[0];
155
+ // I-13: no event beyond the commit boundary may enter a frozen
156
+ // snapshot — including assistant replies that arrived after the
157
+ // boundary but before a delayed binder run.
158
+ const assistantEvents = turnEvents.filter(event => event.role === 'assistant'
159
+ && (bindingKind === 'explicit' || (event.sequence != null && event.sequence <= endCursor)));
160
+ turns.push({
161
+ turnId,
162
+ source: first.source,
163
+ sessionId: first.sessionId,
164
+ bindingKind,
165
+ userEvents: userEvents.map(projectEvent),
166
+ assistantEvents: assistantEvents.map(projectEvent),
167
+ });
168
+ }
169
+ turns.sort((left, right) => {
170
+ const a = left.userEvents[0].sequence;
171
+ const b = right.userEvents[0].sequence;
172
+ if (a == null) return 1;
173
+ if (b == null) return -1;
174
+ return a - b;
175
+ });
176
+
177
+ let status = turns.length ? 'available' : 'no-new-user-prompt';
178
+ let reason = turns.length ? 'sequence-window' : 'no-new-user-prompt';
179
+ if (!Number.isInteger(startCursor)) {
180
+ status = explicitEvents.length && turns.length ? 'available' : 'unavailable';
181
+ reason = 'conversation-baseline-unavailable';
182
+ } else if (ambiguousCount > 0 && !turns.length) {
183
+ status = 'ambiguous';
184
+ reason = 'turn-or-repo-identity-ambiguous';
185
+ }
186
+
187
+ return this.finalize(projectId, commitSha, {
188
+ repoIdentity: boundary.repoIdentity,
189
+ parentSha: boundary.parentShas[0] || null,
190
+ boundaryStartCursor: startCursor,
191
+ boundaryEndCursor: endCursor,
192
+ status,
193
+ turns,
194
+ excludedFuturePromptCount,
195
+ }, { reason, ambiguousCount, previousBoundaryCommit: previous && previous.commitSha || null });
196
+ }
197
+
198
+ buildExplicitTurns(events) {
199
+ return events.filter(event => event.turnId).map(event => ({
200
+ turnId: event.turnId,
201
+ source: event.source,
202
+ sessionId: event.sessionId,
203
+ bindingKind: 'explicit',
204
+ userEvents: [projectEvent(event)],
205
+ assistantEvents: [],
206
+ }));
207
+ }
208
+
209
+ async finalize(projectId, commitSha, snapshotInput, decision) {
210
+ const snapshot = this.conversationStore.writeSnapshot(projectId, {
211
+ commitSha,
212
+ ...snapshotInput,
213
+ finalizedAt: new Date().toISOString(),
214
+ });
215
+ await this.log(snapshot.status === 'ambiguous' || snapshot.status === 'unavailable' ? 'warn' : 'info', 'conversation.commit_snapshot_frozen', 'Commit conversation snapshot was frozen.', {
216
+ projectId,
217
+ commitSha,
218
+ component: 'commit-conversation-binder',
219
+ phase: 'snapshot-frozen',
220
+ context: {
221
+ snapshotHash: snapshot.snapshotHash,
222
+ status: snapshot.status,
223
+ turnCount: snapshot.turns.length,
224
+ userEventCount: snapshot.turns.reduce((total, turn) => total + turn.userEvents.length, 0),
225
+ assistantEventCount: snapshot.turns.reduce((total, turn) => total + turn.assistantEvents.length, 0),
226
+ decision,
227
+ },
228
+ });
229
+ return snapshot;
230
+ }
231
+ }
232
+
233
+ function snapshotRequirementRecords(snapshot) {
234
+ return snapshot.turns.flatMap(turn => turn.userEvents.map(event => ({
235
+ id: event.eventId,
236
+ client: turn.source,
237
+ sessionId: turn.sessionId,
238
+ ts: event.capturedAt,
239
+ requirement: event.content,
240
+ requirementHash: event.contentHash,
241
+ turnId: turn.turnId,
242
+ bindingKind: turn.bindingKind,
243
+ })));
244
+ }
245
+
246
+ module.exports = { CommitConversationBinder, snapshotRequirementRecords, projectEvent, sha256 };
@@ -1,27 +1,27 @@
1
- const fs = require('fs');
2
- const { DomainError, validateProjectId } = require('./contracts');
3
- const { validateSha } = require('./scanner');
4
- const { StorageLayout } = require('./storage-layout');
5
- const AtomicFile = require('./atomic-file');
6
-
7
- class CommitProcessingLedger {
8
- constructor(options = {}) { this.layout = options.layout || new StorageLayout(options); this.atomic = options.atomic || AtomicFile; }
9
- filePath(projectId, commitSha) { return this.layout.getRuntimePath('processed-commits', validateProjectId(projectId), `${validateSha(commitSha)}.json`); }
10
- read(projectId, commitSha) {
11
- const file = this.filePath(projectId, commitSha);
12
- if (!fs.existsSync(file)) return null;
13
- return this.atomic.readJsonStrict(file, { category: 'commit-processing-record', validate: value => {
14
- if (!value || value.schema !== 'commit-processing-record/v1' || value.projectId !== projectId || value.commitSha !== commitSha || value.status !== 'completed') {
15
- throw new DomainError('DATA_CORRUPT', 'Commit processing ledger record is corrupt.', { status: 500 });
16
- }
17
- return value;
18
- } });
19
- }
20
- complete(projectId, commitSha, fields = {}) {
21
- const record = { schema: 'commit-processing-record/v1', projectId: validateProjectId(projectId), commitSha: validateSha(commitSha), status: 'completed', ...fields, completedAt: new Date().toISOString() };
22
- this.atomic.writeJsonAtomic(this.filePath(projectId, commitSha), record);
23
- return record;
24
- }
25
- }
26
-
27
- module.exports = { CommitProcessingLedger };
1
+ const fs = require('fs');
2
+ const { DomainError, validateProjectId } = require('./contracts');
3
+ const { validateSha } = require('./scanner');
4
+ const { StorageLayout } = require('./storage-layout');
5
+ const AtomicFile = require('./atomic-file');
6
+
7
+ class CommitProcessingLedger {
8
+ constructor(options = {}) { this.layout = options.layout || new StorageLayout(options); this.atomic = options.atomic || AtomicFile; }
9
+ filePath(projectId, commitSha) { return this.layout.getRuntimePath('processed-commits', validateProjectId(projectId), `${validateSha(commitSha)}.json`); }
10
+ read(projectId, commitSha) {
11
+ const file = this.filePath(projectId, commitSha);
12
+ if (!fs.existsSync(file)) return null;
13
+ return this.atomic.readJsonStrict(file, { category: 'commit-processing-record', validate: value => {
14
+ if (!value || value.schema !== 'commit-processing-record/v1' || value.projectId !== projectId || value.commitSha !== commitSha || value.status !== 'completed') {
15
+ throw new DomainError('DATA_CORRUPT', 'Commit processing ledger record is corrupt.', { status: 500 });
16
+ }
17
+ return value;
18
+ } });
19
+ }
20
+ complete(projectId, commitSha, fields = {}) {
21
+ const record = { schema: 'commit-processing-record/v1', projectId: validateProjectId(projectId), commitSha: validateSha(commitSha), status: 'completed', ...fields, completedAt: new Date().toISOString() };
22
+ this.atomic.writeJsonAtomic(this.filePath(projectId, commitSha), record);
23
+ return record;
24
+ }
25
+ }
26
+
27
+ module.exports = { CommitProcessingLedger };