fuli-context 0.4.0 → 0.7.2

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 (207) hide show
  1. package/README.en.md +5 -0
  2. package/README.md +411 -173
  3. package/README.zh-CN.md +448 -0
  4. package/compose.graphiti.yml +6 -6
  5. package/dist/web/assets/AboutPage-DXh7Fwxt.css +1 -0
  6. package/dist/web/assets/AboutPage-DfnZo7Lo.js +1 -0
  7. package/dist/web/assets/ConnectionsPage-BloaSKmt.js +1 -0
  8. package/dist/web/assets/ConnectionsPage-CBHAdZXT.css +1 -0
  9. package/dist/web/assets/KnowledgeInspector-Bjhdu2mf.css +1 -0
  10. package/dist/web/assets/KnowledgeInspector-QvNpv0_k.js +1 -0
  11. package/dist/web/assets/KnowledgeOrganizerPage-Cx1w1jb2.css +1 -0
  12. package/dist/web/assets/KnowledgeOrganizerPage-DKq1jVD5.js +1 -0
  13. package/dist/web/assets/KnowledgePage-BWL0jT01.js +1 -0
  14. package/dist/web/assets/KnowledgeWorkspace-Czq6aZ7b.js +4 -0
  15. package/dist/web/assets/OverviewPage-5sJoO1Gh.js +1 -0
  16. package/dist/web/assets/{PersonalProfilePage-S8ZcG2LU.css → PersonalProfilePage-CdmEzyvZ.css} +1 -1
  17. package/dist/web/assets/PersonalProfilePage-tVChM9ik.js +1 -0
  18. package/dist/web/assets/PersonalProjectsPage-BPFrup4V.js +1 -0
  19. package/dist/web/assets/PublicProjectsPage-BPJSeREN.js +1 -0
  20. package/dist/web/assets/ReviewPage-B7EN817i.js +1 -0
  21. package/dist/web/assets/SearchableSelect-ClLN0rIb.js +1 -0
  22. package/dist/web/assets/SettingsPage-C6ouU0sR.css +1 -0
  23. package/dist/web/assets/SettingsPage-Chd_B3eH.js +1 -0
  24. package/dist/web/assets/_plugin-vue_export-helper-BDNMzG2s.js +1 -0
  25. package/dist/web/assets/console-CbDsKrsO.js +1 -0
  26. package/dist/web/assets/fuli-logo-Bu9WBuQe.js +1 -0
  27. package/dist/web/assets/fuli-logo-ER1wQ8Kk.png +0 -0
  28. package/dist/web/assets/i18n-BQcns1PH.js +5 -0
  29. package/dist/web/assets/identity-B8K4VYg6.js +1 -0
  30. package/dist/web/assets/index-CB8Xmsc2.js +2 -0
  31. package/dist/web/assets/index-CZeuPh-U.css +1 -0
  32. package/dist/web/assets/model-eF52GgPF.js +1 -0
  33. package/dist/web/assets/paths-hwz-Ac1g.js +1 -0
  34. package/dist/web/assets/useMinimumLoadingDisplay-BpCyBeko.css +1 -0
  35. package/dist/web/assets/useMinimumLoadingDisplay-wCgGDmHg.js +1 -0
  36. package/dist/web/assets/vue-router-BsEp_XwI.js +1 -0
  37. package/dist/web/index.html +9 -6
  38. package/docs/external-knowledge-architecture.md +279 -0
  39. package/examples/external-knowledge/markdown-folder.mjs +235 -0
  40. package/graph-provider/fuli_graph/app.py +57 -1
  41. package/graph-provider/fuli_graph/graph_models.py +3 -0
  42. package/graph-provider/fuli_graph/graph_query.py +62 -16
  43. package/graph-provider/fuli_graph/knowledge_review.py +612 -0
  44. package/graph-provider/fuli_graph/knowledge_review_models.py +127 -0
  45. package/graph-provider/fuli_graph/knowledge_search.py +51 -4
  46. package/graph-provider/fuli_graph/models.py +19 -0
  47. package/graph-provider/fuli_graph/runtime.py +15 -0
  48. package/graph-provider/fuli_graph/store_knowledge.py +2 -1
  49. package/graph-provider/pyproject.toml +1 -1
  50. package/npm-shrinkwrap.json +5 -407
  51. package/package.json +7 -4
  52. package/skills/capturing-session-knowledge/SKILL.md +24 -4
  53. package/skills/flreview/SKILL.md +73 -0
  54. package/skills/flreview/agents/openai.yaml +6 -0
  55. package/src/agent-tools/graph-definitions.js +79 -4
  56. package/src/agent-tools/graph-handlers.js +12 -0
  57. package/src/cli/command-registry.js +3 -3
  58. package/src/cli/local-runtime-command.js +60 -23
  59. package/src/cli/local-runtime-options.js +16 -6
  60. package/src/cli/setup-agent-selection.js +12 -12
  61. package/src/cli/setup-command.js +41 -18
  62. package/src/cli/shell-argument.js +10 -0
  63. package/src/cli/uninstall-command.js +17 -15
  64. package/src/cli/update-command.js +38 -30
  65. package/src/external-knowledge/catalog.js +61 -0
  66. package/src/external-knowledge/conflict-policy.js +58 -0
  67. package/src/external-knowledge/connected-search.js +166 -0
  68. package/src/external-knowledge/connectors/custom.js +133 -0
  69. package/src/external-knowledge/connectors/feishu.js +461 -0
  70. package/src/external-knowledge/connectors/mcp.js +438 -0
  71. package/src/external-knowledge/connectors/notion.js +307 -0
  72. package/src/external-knowledge/connectors/retrieval-api.js +222 -0
  73. package/src/external-knowledge/document-mapping.js +151 -0
  74. package/src/external-knowledge/graph-projection.js +17 -0
  75. package/src/external-knowledge/index.js +8 -0
  76. package/src/external-knowledge/registry.js +91 -0
  77. package/src/external-knowledge/runtime.js +68 -0
  78. package/src/external-knowledge/safe-diagnostic.js +14 -0
  79. package/src/external-knowledge/service.js +811 -0
  80. package/src/graphiti/agent-knowledge-workflows.js +7 -2
  81. package/src/graphiti/federated-application.js +66 -41
  82. package/src/graphiti/knowledge-item-role.js +21 -0
  83. package/src/graphiti/knowledge-review.js +43 -0
  84. package/src/graphiti/project-profile-mapping.js +32 -0
  85. package/src/graphiti/provider-client.js +22 -1
  86. package/src/graphiti/source-marker.js +54 -3
  87. package/src/graphiti/task-knowledge-recall.js +250 -0
  88. package/src/http/api-router.js +23 -108
  89. package/src/http/external-knowledge-api-router.js +104 -0
  90. package/src/http/graph-api-router.js +12 -1
  91. package/src/http/request-policy.js +38 -3
  92. package/src/local-runtime/lifecycle.js +37 -16
  93. package/src/mcp/create-mcp-server.js +1 -0
  94. package/src/mcp/instructions.js +7 -7
  95. package/src/mcp/tool-annotations.js +6 -0
  96. package/src/mcp/tool-result.js +1 -0
  97. package/src/server/application-lifecycle.js +2 -11
  98. package/src/server/blocked-ports.js +11 -0
  99. package/src/server/lan-access.js +52 -0
  100. package/src/server.js +103 -25
  101. package/src/setup/agent-installation-status.js +2 -1
  102. package/src/setup/agents.js +2 -1
  103. package/src/setup/claude-code-config.js +2 -1
  104. package/src/setup/codex-bootstrap.js +12 -7
  105. package/src/setup/container-runtime.js +25 -21
  106. package/src/setup/graph-runtime.js +219 -58
  107. package/src/setup/node-runtime.js +3 -3
  108. package/src/setup/options.js +2 -4
  109. package/src/setup/paths.js +18 -3
  110. package/src/setup/review-skill-adapters.js +49 -0
  111. package/src/setup/setup.js +26 -2
  112. package/src/setup/uninstall.js +4 -2
  113. package/src/system/resource-monitor.js +317 -0
  114. package/src/system/runtime-settings.js +131 -0
  115. package/src/system/system-service.js +119 -0
  116. package/dist/web/assets/ConnectionsPage-CCKWm-6p.js +0 -1
  117. package/dist/web/assets/KnowledgeInspector-BZkB2wJn.js +0 -1
  118. package/dist/web/assets/KnowledgeInspector-CrGYxEob.css +0 -1
  119. package/dist/web/assets/KnowledgeOrganizerPage-CJCNI2Wd.css +0 -1
  120. package/dist/web/assets/KnowledgeOrganizerPage-PXH-TlGt.js +0 -1
  121. package/dist/web/assets/KnowledgePage-CNQXd15y.js +0 -1
  122. package/dist/web/assets/KnowledgeWorkspace-CXPE4GCD.js +0 -4
  123. package/dist/web/assets/OverviewPage-ul7g7HXh.js +0 -1
  124. package/dist/web/assets/PersonalProfilePage-CC40Pw2L.js +0 -1
  125. package/dist/web/assets/PersonalProjectsPage-CPIEcShD.js +0 -1
  126. package/dist/web/assets/PublicProjectsPage-C700sR7z.js +0 -1
  127. package/dist/web/assets/ReviewPage-7MDDUDgA.js +0 -1
  128. package/dist/web/assets/console-B_Ena2DS.js +0 -5
  129. package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
  130. package/dist/web/assets/identity-CjBwYSMX.js +0 -1
  131. package/dist/web/assets/index-BQRkOwPx.js +0 -2
  132. package/dist/web/assets/index-CpLDx3of.css +0 -1
  133. package/dist/web/assets/model-C-5RVIJ2.js +0 -1
  134. package/dist/web/assets/paths-BkULqmGI.js +0 -1
  135. package/dist/web/assets/vue-router-DnD3hUm7.js +0 -1
  136. package/src/agent/candidate-read-service.js +0 -53
  137. package/src/agent/context-pack-service.js +0 -83
  138. package/src/agent/public-read-policy.js +0 -39
  139. package/src/agent/public-read-service.js +0 -69
  140. package/src/agent/read-projection.js +0 -107
  141. package/src/agent/search-service.js +0 -61
  142. package/src/agent/space-boundary.js +0 -33
  143. package/src/app/bootstrap.js +0 -8
  144. package/src/app/create-application.js +0 -143
  145. package/src/app/state-service.js +0 -38
  146. package/src/candidates.js +0 -78
  147. package/src/classifier.js +0 -63
  148. package/src/cli/command-arguments.js +0 -14
  149. package/src/context-pack.js +0 -206
  150. package/src/extractor.js +0 -77
  151. package/src/fact-history.js +0 -39
  152. package/src/facts/fact-service.js +0 -89
  153. package/src/http/bounded-json-array.js +0 -19
  154. package/src/http/lens-route.js +0 -50
  155. package/src/ingestion.js +0 -161
  156. package/src/lens/lens-correction-service.js +0 -81
  157. package/src/lens/lens-fact-writer.js +0 -85
  158. package/src/lens/lens-input.js +0 -62
  159. package/src/lens/lens-query-projection.js +0 -87
  160. package/src/lens/lens-query-ranking.js +0 -62
  161. package/src/lens/lens-query.js +0 -143
  162. package/src/lens/lens-resource-projection.js +0 -93
  163. package/src/lens/lens-resource-selection.js +0 -45
  164. package/src/lens/lens-resource-service.js +0 -103
  165. package/src/lens/lens-search-query.js +0 -122
  166. package/src/lens/lens-service.js +0 -183
  167. package/src/models.js +0 -44
  168. package/src/observer.js +0 -83
  169. package/src/project-rules.js +0 -33
  170. package/src/publication/outbox-service.js +0 -46
  171. package/src/publication/publication-envelope-schema.js +0 -75
  172. package/src/publication/publication-envelope.js +0 -37
  173. package/src/publication/publication-policy.js +0 -82
  174. package/src/publication/publication-service.js +0 -48
  175. package/src/router.js +0 -58
  176. package/src/runtime-options.js +0 -138
  177. package/src/security/episode-visibility.js +0 -14
  178. package/src/security/fact-visibility.js +0 -15
  179. package/src/security/source-metadata-policy.js +0 -8
  180. package/src/storage/episode-evidence-preview.js +0 -100
  181. package/src/storage/fact-search-order.js +0 -18
  182. package/src/storage/file/episode-evidence.js +0 -37
  183. package/src/storage/file/episode-index.js +0 -59
  184. package/src/storage/file/fact-search.js +0 -60
  185. package/src/storage/file-store.js +0 -437
  186. package/src/storage/migrate.js +0 -128
  187. package/src/storage/migrations/001-initial.sql +0 -83
  188. package/src/storage/migrations/002-storage-integrity.sql +0 -113
  189. package/src/storage/record-validation.js +0 -46
  190. package/src/storage/snapshot-defaults.js +0 -54
  191. package/src/storage/sqlite/candidates-repository.js +0 -84
  192. package/src/storage/sqlite/episode-evidence-repository.js +0 -154
  193. package/src/storage/sqlite/episode-privacy-sql.js +0 -33
  194. package/src/storage/sqlite/episodes-repository.js +0 -52
  195. package/src/storage/sqlite/fact-search-repository.js +0 -162
  196. package/src/storage/sqlite/facts-repository.js +0 -146
  197. package/src/storage/sqlite/imports-repository.js +0 -39
  198. package/src/storage/sqlite/mapper.js +0 -84
  199. package/src/storage/sqlite/open-database.js +0 -15
  200. package/src/storage/sqlite/outbox-repository.js +0 -103
  201. package/src/storage/sqlite/snapshot-repository.js +0 -69
  202. package/src/storage/sqlite/spaces-repository.js +0 -52
  203. package/src/storage/sqlite/subscriptions-repository.js +0 -54
  204. package/src/storage/sqlite-store.js +0 -159
  205. package/src/storage/stable-json.js +0 -24
  206. package/src/storage/store-port.js +0 -20
  207. package/src/store.js +0 -2
@@ -2,9 +2,14 @@ import { createHash } from 'node:crypto';
2
2
 
3
3
  import { onlineSourceUri } from './source-uri.js';
4
4
 
5
- export async function beginTaskContext(application, { sessionId, projectPath }) {
5
+ export async function beginTaskContext(application, {
6
+ sessionId,
7
+ projectPath,
8
+ taskPrompt = null
9
+ }) {
6
10
  const preferences = await application.getCollaborationPreferences({
7
11
  projectPath,
12
+ taskPrompt,
8
13
  agentInvocation: true,
9
14
  agentToolName: 'begin_task_context'
10
15
  });
@@ -19,7 +24,7 @@ export async function beginTaskContext(application, { sessionId, projectPath })
19
24
  previous_checkpoint_missing: task.previousCheckpointMissing,
20
25
  ...preferences,
21
26
  task_guidance: {
22
- retrieval: 'Use search_current_project_knowledge for project work; it resolves the current project and inherited parent knowledge without copying IDs.',
27
+ retrieval: 'Inspect task_knowledge_recall before asking for a stable project fact or method again. On a miss, use search_current_project_knowledge with focused action, artifact, target-system, or identifier queries; never use the full conversational request as the only query.',
23
28
  checkpoint: 'Before finishing, call checkpoint_task_knowledge with capture_candidates or retain_nothing.'
24
29
  }
25
30
  };
@@ -1,3 +1,5 @@
1
+ import { dirname } from 'node:path';
2
+
1
3
  import { GraphitiProviderClient } from './provider-client.js';
2
4
  import {
3
5
  canonicalProviderUrl,
@@ -22,8 +24,15 @@ import {
22
24
  createFuliSourceMarker,
23
25
  sourceConsoleUrl
24
26
  } from './source-marker.js';
27
+ import {
28
+ recallTaskKnowledge,
29
+ TASK_KNOWLEDGE_RETRIEVAL_GUIDANCE
30
+ } from './task-knowledge-recall.js';
25
31
  import { resolvePersonalProjectPath } from './project-path-context.js';
32
+ import { mergeExternalKnowledgeProjection } from '../external-knowledge/graph-projection.js';
26
33
  import { TaskContextRegistry } from '../mcp/task-context-registry.js';
34
+ import { attachExternalKnowledgeRuntime } from '../external-knowledge/runtime.js';
35
+ import { resolveSetupPaths } from '../setup/paths.js';
27
36
  import {
28
37
  agentProjectResolution,
29
38
  beginTaskContext as beginTaskContextWorkflow,
@@ -40,19 +49,27 @@ import {
40
49
  providerConfirmationBasis,
41
50
  providerEpisode
42
51
  } from './knowledge-provider-mapping.js';
52
+ import { providerProjectProfile } from './project-profile-mapping.js';
53
+ import {
54
+ finishKnowledgeReview as finishKnowledgeReviewWorkflow,
55
+ listKnowledgeReviewCandidates as listKnowledgeReviewCandidatesWorkflow,
56
+ recordKnowledgeReviewProgress as recordKnowledgeReviewProgressWorkflow,
57
+ startKnowledgeReview as startKnowledgeReviewWorkflow
58
+ } from './knowledge-review.js';
43
59
 
44
60
  export function openFederatedGraphApplication({
45
61
  runtimeConfigPath,
46
62
  config,
47
63
  capturePolicyStore,
48
64
  agentAccessPolicyStore,
49
- fetchImpl = globalThis.fetch
65
+ fetchImpl = globalThis.fetch,
66
+ env = process.env
50
67
  }) {
51
68
  const resolved = config ?? readGraphRuntimeConfig(runtimeConfigPath);
52
69
  const policyStore = capturePolicyStore ?? new CapturePolicyStore(
53
70
  capturePolicyPathForRuntime(runtimeConfigPath)
54
71
  );
55
- return new FederatedGraphApplication(resolved, {
72
+ const app = new FederatedGraphApplication(resolved, {
56
73
  fetchImpl,
57
74
  capturePolicyStore: policyStore,
58
75
  agentAccessPolicyStore: agentAccessPolicyStore ?? new AgentAccessPolicyStore(
@@ -60,6 +77,14 @@ export function openFederatedGraphApplication({
60
77
  ),
61
78
  consoleUrl: sourceConsoleUrl(runtimeConfigPath)
62
79
  });
80
+ if (typeof runtimeConfigPath === 'string' && runtimeConfigPath) {
81
+ attachExternalKnowledgeRuntime(app, {
82
+ paths: resolveSetupPaths({ dataDir: dirname(runtimeConfigPath) }),
83
+ env,
84
+ fetchImpl
85
+ });
86
+ }
87
+ return app;
63
88
  }
64
89
 
65
90
  export class FederatedGraphApplication {
@@ -163,6 +188,7 @@ export class FederatedGraphApplication {
163
188
  async getCollaborationPreferences({
164
189
  personalProjectId = null,
165
190
  projectPath = null,
191
+ taskPrompt = null,
166
192
  limit = 100,
167
193
  agentInvocation = false,
168
194
  agentToolName = 'get_collaboration_preferences'
@@ -188,6 +214,9 @@ export class FederatedGraphApplication {
188
214
  result,
189
215
  queuedConflicts
190
216
  );
217
+ const taskKnowledgeRecall = taskPrompt === null
218
+ ? null
219
+ : await recallTaskKnowledge(this, projectResolution, taskPrompt);
191
220
  if (agentInvocation) {
192
221
  const items = [
193
222
  ...(result.global_preferences ?? []),
@@ -207,14 +236,16 @@ export class FederatedGraphApplication {
207
236
  conflicts: 'Do not apply entries listed in conflicts until the user resolves them.',
208
237
  deferred_conflicts: 'If the current task would use a deferred_conflict, call resolve_deferred_preference_conflict before applying either side. Ignore unrelated deferred conflicts. The resolution must preserve the AI audit marker.',
209
238
  authority: 'Human or authoritative-source confirmed preferences outrank agent-confirmed preferences. Agent-confirmed preferences are usable but lower priority and remain explicitly marked.',
210
- pending: 'Pending preferences are available only through on-demand knowledge search; invalid and unrelated-project preferences are excluded. Automatic preference injection never counts as usage evidence.'
239
+ pending: 'Pending preferences are available only through on-demand knowledge search; invalid and unrelated-project preferences are excluded. Automatic preference injection never counts as usage evidence.',
240
+ knowledge_retrieval: TASK_KNOWLEDGE_RETRIEVAL_GUIDANCE
211
241
  };
212
242
  if (!agentInvocation) {
213
243
  return {
214
244
  ...result,
215
245
  deferred_conflicts: deferredConflicts,
216
246
  application_guidance: applicationGuidance,
217
- project_resolution: agentProjectResolution(projectResolution)
247
+ project_resolution: agentProjectResolution(projectResolution),
248
+ ...(taskKnowledgeRecall ? { task_knowledge_recall: taskKnowledgeRecall } : {})
218
249
  };
219
250
  }
220
251
  return {
@@ -222,6 +253,7 @@ export class FederatedGraphApplication {
222
253
  .map(agentCollaborationPreference),
223
254
  deferred_conflicts: deferredConflicts.map(agentDeferredPreferenceConflict),
224
255
  application_guidance: applicationGuidance,
256
+ ...(taskKnowledgeRecall ? { task_knowledge_recall: taskKnowledgeRecall } : {}),
225
257
  context: {
226
258
  personal_space_id: result.personal_space_id,
227
259
  personal_project_id: result.personal_project_id,
@@ -293,7 +325,9 @@ export class FederatedGraphApplication {
293
325
  if (!['bounded', 'all_local_confirmed'].includes(personalProjectScope)) {
294
326
  throw new TypeError('Unknown personal project search scope');
295
327
  }
296
- const subscriptions = await this.personal.listSubscriptions(personalSpaceId);
328
+ const subscriptions = projectIds.length
329
+ ? await this.personal.listSubscriptions(personalSpaceId)
330
+ : [];
297
331
  const requestedProjects = new Set(projectIds);
298
332
  const selectedSubscriptions = subscriptions.filter(({ project_id: projectId }) =>
299
333
  requestedProjects.has(projectId)
@@ -451,6 +485,7 @@ export class FederatedGraphApplication {
451
485
  providerUrl = null,
452
486
  personalProjectId = null,
453
487
  limit = 500,
488
+ offset = null,
454
489
  agentInvocation = false,
455
490
  agentToolName = 'get_knowledge_graph'
456
491
  }) {
@@ -458,19 +493,42 @@ export class FederatedGraphApplication {
458
493
  if (spaceId !== this.config.personal.spaceId) {
459
494
  throw new TypeError('A configured providerUrl is required for a team-shared project graph');
460
495
  }
461
- const result = await this.personal.graph(spaceId, limit, personalProjectId);
496
+ const result = await this.personal.graph(
497
+ spaceId,
498
+ limit,
499
+ personalProjectId,
500
+ offset
501
+ );
462
502
  if (agentInvocation) {
463
503
  await this.#recordAgentViews([
464
504
  ...result.nodes.map(({ id }) => ({ item_id: id, item_kind: 'entity' })),
465
505
  ...result.edges.map(({ id }) => ({ item_id: id, item_kind: 'relationship' }))
466
506
  ], agentToolName);
467
507
  }
468
- return result;
508
+ return mergeExternalKnowledgeProjection(
509
+ this.externalKnowledge, result, spaceId, personalProjectId
510
+ );
469
511
  }
470
512
  if (personalProjectId) {
471
513
  throw new TypeError('Personal project scope cannot be used with a team-shared provider');
472
514
  }
473
- return this.#workspace(providerUrl).client.graph(spaceId, limit);
515
+ return this.#workspace(providerUrl).client.graph(spaceId, limit, null, offset);
516
+ }
517
+
518
+ async startKnowledgeReview(input) {
519
+ return startKnowledgeReviewWorkflow(this, input);
520
+ }
521
+
522
+ async listKnowledgeReviewCandidates(input) {
523
+ return listKnowledgeReviewCandidatesWorkflow(this, input);
524
+ }
525
+
526
+ async recordKnowledgeReviewProgress(input) {
527
+ return recordKnowledgeReviewProgressWorkflow(this, input);
528
+ }
529
+
530
+ async finishKnowledgeReview(input) {
531
+ return finishKnowledgeReviewWorkflow(this, input);
474
532
  }
475
533
 
476
534
  async recordKnowledgeUsage({
@@ -1203,39 +1261,6 @@ function assertSafeKnowledgeMutation(input) {
1203
1261
  }
1204
1262
  }
1205
1263
 
1206
- function providerProjectProfile(profile) {
1207
- if (!profile || typeof profile !== 'object') throw new TypeError('Project profile is required');
1208
- const assessment = profile.assessment ? sanitizeProjectAssessment(profile.assessment) : null;
1209
- return {
1210
- name: profile.name,
1211
- purpose: profile.purpose ?? null,
1212
- scope: profile.scope ?? null,
1213
- technical_summary: profile.technical_summary ?? profile.technicalSummary ?? null,
1214
- lifecycle: profile.lifecycle ?? 'planned',
1215
- sources: profile.sources ?? [],
1216
- boundaries: profile.boundaries ?? [],
1217
- assessment
1218
- };
1219
- }
1220
-
1221
- function sanitizeProjectAssessment(input) {
1222
- const assessment = {
1223
- score: input.score,
1224
- label: input.label,
1225
- confirmed: input.confirmed ?? [],
1226
- inferred: input.inferred ?? [],
1227
- dimensions: (input.dimensions ?? []).map((dimension) => ({
1228
- key: dimension.key,
1229
- label: dimension.label,
1230
- score: dimension.score,
1231
- state: dimension.state === 'missing' ? 'inferred' : dimension.state,
1232
- evidence: dimension.evidence ?? []
1233
- })),
1234
- analyzed_at: input.analyzed_at ?? input.analyzedAt
1235
- };
1236
- return assessment;
1237
- }
1238
-
1239
1264
  function groupSubscriptions(subscriptions) {
1240
1265
  const grouped = new Map();
1241
1266
  for (const subscription of subscriptions) {
@@ -0,0 +1,21 @@
1
+ const SUPPORTING_ENTITY_TYPES = new Set([
2
+ 'DecisionOption',
3
+ 'DecisionRationale',
4
+ 'ValidationResult'
5
+ ]);
6
+
7
+ const SUPPORTING_RELATIONSHIPS = new Set([
8
+ 'MOTIVATED_BY',
9
+ 'REJECTED_OPTION',
10
+ 'VALIDATED_BY'
11
+ ]);
12
+
13
+ export function knowledgeItemRole(item) {
14
+ if (
15
+ SUPPORTING_ENTITY_TYPES.has(item?.type)
16
+ || SUPPORTING_RELATIONSHIPS.has(item?.relationship)
17
+ ) {
18
+ return 'supporting';
19
+ }
20
+ return 'primary';
21
+ }
@@ -0,0 +1,43 @@
1
+ export function startKnowledgeReview(app, input) {
2
+ assertActivePersonalSpace(app, input.personalSpaceId);
3
+ return app.personal.startKnowledgeReview({
4
+ personal_space_id: input.personalSpaceId,
5
+ scope: input.scope,
6
+ personal_project_id: input.personalProjectId ?? null
7
+ });
8
+ }
9
+
10
+ export function listKnowledgeReviewCandidates(app, input) {
11
+ assertActivePersonalSpace(app, input.personalSpaceId);
12
+ return app.personal.listKnowledgeReviewCandidates({
13
+ personal_space_id: input.personalSpaceId,
14
+ review_id: input.reviewId,
15
+ limit: input.limit
16
+ });
17
+ }
18
+
19
+ export function recordKnowledgeReviewProgress(app, input) {
20
+ assertActivePersonalSpace(app, input.personalSpaceId);
21
+ return app.personal.recordKnowledgeReviewProgress({
22
+ personal_space_id: input.personalSpaceId,
23
+ review_id: input.reviewId,
24
+ candidate_key: input.candidateKey,
25
+ outcome: input.outcome,
26
+ note: input.note ?? null
27
+ });
28
+ }
29
+
30
+ export function finishKnowledgeReview(app, input) {
31
+ assertActivePersonalSpace(app, input.personalSpaceId);
32
+ return app.personal.finishKnowledgeReview({
33
+ personal_space_id: input.personalSpaceId,
34
+ review_id: input.reviewId,
35
+ disposition: input.disposition
36
+ });
37
+ }
38
+
39
+ function assertActivePersonalSpace(app, personalSpaceId) {
40
+ if (personalSpaceId !== app.config.personal.spaceId) {
41
+ throw new TypeError('Knowledge review must use the active personal space');
42
+ }
43
+ }
@@ -0,0 +1,32 @@
1
+ export function providerProjectProfile(profile) {
2
+ if (!profile || typeof profile !== 'object') {
3
+ throw new TypeError('Project profile is required');
4
+ }
5
+ return {
6
+ name: profile.name,
7
+ purpose: profile.purpose ?? null,
8
+ scope: profile.scope ?? null,
9
+ technical_summary: profile.technical_summary ?? profile.technicalSummary ?? null,
10
+ lifecycle: profile.lifecycle ?? 'planned',
11
+ sources: profile.sources ?? [],
12
+ boundaries: profile.boundaries ?? [],
13
+ assessment: profile.assessment ? providerProjectAssessment(profile.assessment) : null
14
+ };
15
+ }
16
+
17
+ function providerProjectAssessment(input) {
18
+ return {
19
+ score: input.score,
20
+ label: input.label,
21
+ confirmed: input.confirmed ?? [],
22
+ inferred: input.inferred ?? [],
23
+ dimensions: (input.dimensions ?? []).map((dimension) => ({
24
+ key: dimension.key,
25
+ label: dimension.label,
26
+ score: dimension.score,
27
+ state: dimension.state === 'missing' ? 'inferred' : dimension.state,
28
+ evidence: dimension.evidence ?? []
29
+ })),
30
+ analyzed_at: input.analyzed_at ?? input.analyzedAt
31
+ };
32
+ }
@@ -118,6 +118,26 @@ export class GraphitiProviderClient {
118
118
  commit(input) {
119
119
  return this.#request('/v1/knowledge/commits', { method: 'POST', body: input });
120
120
  }
121
+ startKnowledgeReview(input) {
122
+ return this.#request('/v1/knowledge/reviews/start', {
123
+ method: 'POST', body: input
124
+ });
125
+ }
126
+ listKnowledgeReviewCandidates(input) {
127
+ return this.#request('/v1/knowledge/reviews/candidates', {
128
+ method: 'POST', body: input
129
+ });
130
+ }
131
+ recordKnowledgeReviewProgress(input) {
132
+ return this.#request('/v1/knowledge/reviews/progress', {
133
+ method: 'POST', body: input
134
+ });
135
+ }
136
+ finishKnowledgeReview(input) {
137
+ return this.#request('/v1/knowledge/reviews/finish', {
138
+ method: 'POST', body: input
139
+ });
140
+ }
121
141
  reviseKnowledgeItem(itemId, input) {
122
142
  return this.#request(`/v1/knowledge/items/${encodeURIComponent(itemId)}`, {
123
143
  method: 'PATCH', body: input
@@ -240,9 +260,10 @@ export class GraphitiProviderClient {
240
260
  if (personalProjectId) query.set('personal_project_id', personalProjectId);
241
261
  return this.#request(`/v1/collaboration-preferences?${query}`);
242
262
  }
243
- graph(spaceId, limit = 500, personalProjectId = null) {
263
+ graph(spaceId, limit = 500, personalProjectId = null, offset = null) {
244
264
  const query = new URLSearchParams({ limit: String(limit) });
245
265
  if (personalProjectId) query.set('personal_project_id', personalProjectId);
266
+ if (offset !== null) query.set('offset', String(offset));
246
267
  return this.#request(`/v1/spaces/${encodeURIComponent(spaceId)}/graph?${query}`);
247
268
  }
248
269
 
@@ -2,8 +2,10 @@ import { readFileSync } from 'node:fs';
2
2
  import { dirname, join, resolve } from 'node:path';
3
3
 
4
4
  import { DEFAULT_FULI_PORT } from '../defaults.js';
5
+ import { knowledgeItemRole } from './knowledge-item-role.js';
5
6
 
6
7
  const MAX_CITATIONS = 1;
8
+ const PROJECT_SCOPE_PRIORITY_BOOST = 1;
7
9
  const DEFAULT_CONSOLE_URL = `http://127.0.0.1:${DEFAULT_FULI_PORT}`;
8
10
 
9
11
  export function sourceConsoleUrl(runtimeConfigPath, {
@@ -22,12 +24,16 @@ export function sourceConsoleUrl(runtimeConfigPath, {
22
24
  export function createFuliSourceMarker({
23
25
  consoleUrl = DEFAULT_CONSOLE_URL,
24
26
  facts = [],
25
- entities = []
27
+ entities = [],
28
+ projectScopePriority = 'balanced'
26
29
  } = {}) {
30
+ const comparePriority = projectScopePriority === 'strict'
31
+ ? compareStrictProjectCitationPriority
32
+ : compareCitationPriority;
27
33
  const citations = uniqueCitations([
28
34
  ...facts.map(factCitation),
29
35
  ...entities.map(entityCitation)
30
- ].filter(Boolean));
36
+ ].filter(Boolean)).sort(comparePriority);
31
37
  const status = citations.length ? 'matched' : 'no_match';
32
38
  return {
33
39
  required: true,
@@ -45,6 +51,10 @@ function factCitation(fact) {
45
51
  spaceId: fact.spaceId ?? fact.space_id,
46
52
  itemKind: 'relationship',
47
53
  itemId: fact.id,
54
+ definedProjectId: fact.defined_project_id,
55
+ scopeDistance: fact.scope_distance,
56
+ score: fact.score,
57
+ role: knowledgeItemRole(fact),
48
58
  title: [fact.source_entity, fact.target_entity].filter(Boolean).join(' → ') ||
49
59
  fact.fact || '关系知识'
50
60
  });
@@ -56,21 +66,62 @@ function entityCitation(entity) {
56
66
  spaceId: entity.spaceId ?? entity.space_id,
57
67
  itemKind: 'entity',
58
68
  itemId: entity.id,
69
+ definedProjectId: entity.defined_project_id,
70
+ scopeDistance: entity.scope_distance,
71
+ score: entity.score,
72
+ role: knowledgeItemRole(entity),
59
73
  title: entity.name || entity.summary || '实体知识'
60
74
  });
61
75
  }
62
76
 
63
- function citation({ scope, spaceId, itemKind, itemId, title }) {
77
+ function citation({
78
+ scope,
79
+ spaceId,
80
+ itemKind,
81
+ itemId,
82
+ definedProjectId,
83
+ scopeDistance,
84
+ score,
85
+ role,
86
+ title
87
+ }) {
64
88
  if (!['personal', 'project'].includes(scope) || !spaceId || !itemId) return null;
65
89
  return {
66
90
  scope,
67
91
  spaceId: String(spaceId),
68
92
  itemKind,
69
93
  itemId: String(itemId),
94
+ projectScoped: Boolean(definedProjectId),
95
+ scopeDistance: Number(scopeDistance ?? 0),
96
+ score: Number(score ?? 0),
97
+ role,
70
98
  title: singleLine(title)
71
99
  };
72
100
  }
73
101
 
102
+ function compareCitationPriority(left, right) {
103
+ return citationPriority(right) - citationPriority(left);
104
+ }
105
+
106
+ function compareStrictProjectCitationPriority(left, right) {
107
+ if (left.projectScoped !== right.projectScoped) {
108
+ return Number(right.projectScoped) - Number(left.projectScoped);
109
+ }
110
+ if (left.projectScoped && left.scopeDistance !== right.scopeDistance) {
111
+ return left.scopeDistance - right.scopeDistance;
112
+ }
113
+ if (left.role !== right.role) {
114
+ return left.role === 'primary' ? -1 : 1;
115
+ }
116
+ return compareCitationPriority(left, right);
117
+ }
118
+
119
+ function citationPriority(value) {
120
+ const projectBoost = value.projectScoped ? PROJECT_SCOPE_PRIORITY_BOOST : 0;
121
+ const distancePenalty = Math.min(Math.max(value.scopeDistance, 0), 8) * 0.15;
122
+ return value.score + projectBoost - distancePenalty;
123
+ }
124
+
74
125
  function uniqueCitations(citations) {
75
126
  const seen = new Set();
76
127
  return citations.filter(({ scope, spaceId, itemKind, itemId }) => {