@devflow-tools/mcp-server 0.17.7 → 0.18.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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/command-registry.json +5 -5
  4. package/dist/domain-tool-registry.d.ts +3 -0
  5. package/dist/domain-tool-registry.d.ts.map +1 -0
  6. package/dist/domain-tool-registry.js +24 -0
  7. package/dist/domain-tool-registry.js.map +1 -0
  8. package/dist/enforcer/constitutional.js +1 -1
  9. package/dist/enforcer/constitutional.js.map +1 -1
  10. package/dist/enforcer/index.d.ts +2 -2
  11. package/dist/enforcer/index.d.ts.map +1 -1
  12. package/dist/enforcer/index.js +140 -49
  13. package/dist/enforcer/index.js.map +1 -1
  14. package/dist/enforcer/rule-engine.d.ts +1 -1
  15. package/dist/enforcer/rule-engine.d.ts.map +1 -1
  16. package/dist/enforcer/rule-engine.js +1 -1
  17. package/dist/enforcer/rule-engine.js.map +1 -1
  18. package/dist/index.js +8 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/resources.d.ts +1 -1
  21. package/dist/resources.d.ts.map +1 -1
  22. package/dist/retrieval-composition.d.ts +73 -0
  23. package/dist/retrieval-composition.d.ts.map +1 -0
  24. package/dist/retrieval-composition.js +611 -0
  25. package/dist/retrieval-composition.js.map +1 -0
  26. package/dist/runtime-composition.d.ts +42 -0
  27. package/dist/runtime-composition.d.ts.map +1 -0
  28. package/dist/runtime-composition.js +93 -0
  29. package/dist/runtime-composition.js.map +1 -0
  30. package/dist/sampling-evidence-reranker.d.ts +4 -0
  31. package/dist/sampling-evidence-reranker.d.ts.map +1 -0
  32. package/dist/sampling-evidence-reranker.js +82 -0
  33. package/dist/sampling-evidence-reranker.js.map +1 -0
  34. package/dist/semantic-sampling-resolver.d.ts +7 -6
  35. package/dist/semantic-sampling-resolver.d.ts.map +1 -1
  36. package/dist/semantic-sampling-resolver.js +191 -71
  37. package/dist/semantic-sampling-resolver.js.map +1 -1
  38. package/dist/server.d.ts +2 -1
  39. package/dist/server.d.ts.map +1 -1
  40. package/dist/server.js +15 -6
  41. package/dist/server.js.map +1 -1
  42. package/dist/task-evidence-service.d.ts +24 -0
  43. package/dist/task-evidence-service.d.ts.map +1 -0
  44. package/dist/task-evidence-service.js +202 -0
  45. package/dist/task-evidence-service.js.map +1 -0
  46. package/dist/tool-contracts.d.ts +26 -7
  47. package/dist/tool-contracts.d.ts.map +1 -1
  48. package/dist/tool-contracts.js +27 -6
  49. package/dist/tool-contracts.js.map +1 -1
  50. package/dist/tool-profile.d.ts +1 -0
  51. package/dist/tool-profile.d.ts.map +1 -1
  52. package/dist/tool-profile.js +13 -2
  53. package/dist/tool-profile.js.map +1 -1
  54. package/dist/tools.d.ts +11 -2
  55. package/dist/tools.d.ts.map +1 -1
  56. package/dist/tools.js +295 -335
  57. package/dist/tools.js.map +1 -1
  58. package/package.json +34 -11
package/dist/tools.js CHANGED
@@ -1,13 +1,15 @@
1
- import { buildChannelQueryPlan, buildRetrievalTaskPlan, extractStructuralSemanticEvidence, getKnowledgeResult, getMemoryResult, getProjectContextResult, mergeSemanticAuthority, parseIntent, } from "@devflow-tools/context-engine";
2
- import { assertTaskSemanticFrameV2, projectSemanticFrameToLegacyIntent, resolveSemanticResolutionMode, } from "@devflow-tools/sdk";
1
+ import { loadSemanticExecutionContext, retrieveProjectEvidence, } from './retrieval-composition.js';
2
+ import { projectTaskKnowledgeResult, projectTaskMemoryResult, queryAuthoritativeTaskEvidence, } from './task-evidence-service.js';
3
+ import { resolveTaskSemanticContext, } from '@devflow-tools/semantic-engine';
3
4
  import { resolveCapabilityRollout } from "@devflow-tools/sdk/capability-rollout";
4
5
  import { canonicalizeProjectRoot, wrapHandlerRequireRoot } from "./plugin-guards.js";
5
6
  import { getReportResourceUri, readToolReport } from "./report-store.js";
6
7
  import { normalizeTurnId, openGlobalDevFlowDatabase, } from "@devflow-tools/database";
7
- import { createHash } from "node:crypto";
8
8
  import { isAbsolute, normalize, relative } from "node:path";
9
9
  import { normalizeMemoryCandidates, OBSERVATION_TYPE_SCHEMA, rejectedToolResult, TOOL_RESULT_OUTPUT_SCHEMA, } from "./tool-contracts.js";
10
- import { emitSemanticControlLog } from '@devflow-tools/telemetry';
10
+ import { emitSemanticControlLog, SemanticRuntimeEventProjector } from '@devflow-tools/telemetry';
11
+ import { ActivationSnapshotStore, TaskEventStore, TaskKernel } from '@devflow-tools/task-runtime';
12
+ import { createHash } from 'node:crypto';
11
13
  export const GUARDED_CORE_TOOL_NAMES = new Set([
12
14
  "get_project_context",
13
15
  "search_symbol",
@@ -57,6 +59,7 @@ export function getCoreTools(knowledgeScopes = []) {
57
59
  type: "object",
58
60
  properties: {
59
61
  query: { type: "string", description: "自然语言查询" },
62
+ budgetTokens: { type: "number", description: "Maximum response tokens", default: 2000, minimum: 1 },
60
63
  mode: { type: "string", enum: ["fast", "deep"], default: "fast" },
61
64
  expandedTerms: { type: "string", description: "Agent 预翻译的英文关键词(空格分隔),用于提升中文查询的搜索效果" },
62
65
  retrievalSessionId: { type: "string", description: "首轮上下文返回的检索会话 ID;仅用于最多三轮的定向补检" },
@@ -133,9 +136,6 @@ export function getCoreTools(knowledgeScopes = []) {
133
136
  status: { type: "string", enum: ["done", "failed", "skipped", "waiting_user"] },
134
137
  output: { type: "string", description: "Bounded step result summary" },
135
138
  duration: { type: "number", minimum: 0, description: "Step duration in milliseconds" },
136
- contextReceipt: { type: "string", description: "Context receipt used for the host action" },
137
- executionId: { type: "string", description: "Host execution identity" },
138
- toolUseIds: { type: "array", items: { type: "string" }, maxItems: 100 },
139
139
  changedFiles: { type: "array", items: { type: "string" }, maxItems: 100 },
140
140
  verification: {
141
141
  type: "object",
@@ -179,7 +179,7 @@ export function getCoreTools(knowledgeScopes = []) {
179
179
  },
180
180
  {
181
181
  name: "get_memory",
182
- description: "获取项目记忆:技术栈、约定、偏好、历史决策",
182
+ description: "投影当前 canonical task authoritative receipt 中的 Memory 证据",
183
183
  inputSchema: {
184
184
  type: "object",
185
185
  properties: {
@@ -261,6 +261,16 @@ export function getCoreTools(knowledgeScopes = []) {
261
261
  items: { type: "string", minLength: 1 },
262
262
  description: "Exact, verbatim substrings copied from the canonical user prompt that support this candidate. Required when refining an already committed explicit-memory turn.",
263
263
  },
264
+ operation: {
265
+ type: "string",
266
+ enum: ["add", "reinforce", "supersede", "link", "hold", "retire", "none"],
267
+ description: "Requested lifecycle operation. supersede/retire require referencedFactIds and exact evidenceSpans; omit when uncertain.",
268
+ },
269
+ referencedFactIds: {
270
+ type: "array",
271
+ items: { type: "string", minLength: 1 },
272
+ description: "Exact existing fact IDs affected by reinforce/link/supersede/retire. Entity or alias matches are not fact identity.",
273
+ },
264
274
  },
265
275
  },
266
276
  },
@@ -351,7 +361,7 @@ export function getCoreTools(knowledgeScopes = []) {
351
361
  },
352
362
  {
353
363
  name: "get_knowledge",
354
- description: "查询技术文档知识库",
364
+ description: "投影当前 canonical task authoritative receipt 中的 Knowledge 证据",
355
365
  inputSchema: {
356
366
  type: "object",
357
367
  properties: {
@@ -582,14 +592,9 @@ function resolveBoundContextTask(input) {
582
592
  || record.turnId !== input.turnId || record.requestId !== input.requestId) {
583
593
  throw new Error('TASK_IDENTITY_MISMATCH:intent artifact does not belong to this MCP request');
584
594
  }
585
- const planRecord = database.getChannelQueryPlanForIntent({
586
- projectRoot: record.projectRoot,
587
- sessionId: record.sessionId,
588
- turnId: record.turnId,
589
- sourceIntentHash: record.artifactHash,
590
- });
591
- if (!planRecord)
592
- throw new Error(`QUERY_PLAN_NOT_FOUND:${record.artifactHash}`);
595
+ if (!record.executionId) {
596
+ throw new Error('TASK_IDENTITY_INCOMPLETE:intent artifact execution identity is missing');
597
+ }
593
598
  const identity = {
594
599
  projectRoot: record.projectRoot,
595
600
  projectId: record.projectId,
@@ -597,7 +602,7 @@ function resolveBoundContextTask(input) {
597
602
  sessionId: record.sessionId,
598
603
  turnId: record.turnId,
599
604
  requestId: record.requestId,
600
- ...(record.executionId ? { executionId: record.executionId } : {}),
605
+ executionId: record.executionId,
601
606
  };
602
607
  const artifact = {
603
608
  identity,
@@ -619,75 +624,80 @@ function resolveBoundContextTask(input) {
619
624
  artifactHash: record.artifactHash,
620
625
  createdAt: record.createdAt,
621
626
  };
622
- const queryPlan = {
623
- identity,
624
- sourceIntentHash: planRecord.sourceIntentHash,
625
- planHash: planRecord.planHash,
626
- code: planRecord.code,
627
- memory: planRecord.memory,
628
- knowledge: planRecord.knowledge,
629
- generatedBy: planRecord.generatedBy,
630
- ...(planRecord.degradation ? { degradation: planRecord.degradation } : {}),
631
- createdAt: planRecord.createdAt,
632
- };
633
627
  const query = stripArtifactCommand(artifact.normalizedPrompt, artifact.command);
634
628
  const expandedTerms = [input.agentQuery, input.expandedTerms]
635
629
  .filter((value) => typeof value === 'string' && value.trim().length > 0)
636
630
  .join(' ')
637
631
  .slice(0, 4_000) || undefined;
638
- return { query, expandedTerms, requestId: record.requestId, turnId: record.turnId, intentArtifact: artifact, queryPlan };
632
+ return { query, expandedTerms, requestId: record.requestId, turnId: record.turnId, intentArtifact: artifact };
639
633
  }
640
634
  finally {
641
635
  database.close();
642
636
  }
643
637
  }
644
638
  export async function resolveBoundContextTaskWithSemantics(input, engines, dependencies) {
645
- const legacy = resolveBoundContextTask(input);
646
- const artifact = legacy.intentArtifact;
639
+ const bound = resolveBoundContextTask(input);
640
+ const artifact = bound.intentArtifact;
647
641
  if (!artifact)
648
- return legacy;
649
- const semanticMode = resolveSemanticResolutionMode(input.projectRoot);
650
- if (semanticMode === 'off' || !engines.semanticRouter)
651
- return legacy;
642
+ return bound;
652
643
  const database = openGlobalDevFlowDatabase(undefined, { busyTimeoutMs: 250 });
653
644
  try {
654
- const latest = database.getLatestSemanticResolution(artifact.identity.projectRoot, artifact.identity.sessionId, artifact.identity.turnId);
655
- if (!latest)
656
- return legacy;
657
- if (latest.artifactRevision >= 2) {
658
- const resolvedArtifact = database.getTaskIntentArtifact(latest.artifactHash);
659
- const resolvedPlan = resolvedArtifact && database.getChannelQueryPlanForIntent({
660
- projectRoot: resolvedArtifact.projectRoot,
661
- sessionId: resolvedArtifact.sessionId,
662
- turnId: resolvedArtifact.turnId,
663
- sourceIntentHash: resolvedArtifact.artifactHash,
664
- });
665
- if (resolvedArtifact && resolvedPlan && semanticMode !== 'shadow') {
666
- return boundTaskFromRecords(legacy, resolvedArtifact, resolvedPlan);
645
+ const currentContext = database.getSemanticExecutionContext(artifact.identity.projectRoot, artifact.identity.sessionId, artifact.identity.turnId, artifact.version);
646
+ if (currentContext) {
647
+ const snapshot = database.getTaskAggregateSnapshot(artifact.identity.projectRoot, artifact.identity.sessionId, artifact.identity.turnId, artifact.version);
648
+ const definition = database.getTaskDefinitionV2(currentContext.taskDefinitionHash);
649
+ const activation = database.getRuntimeActivation(artifact.identity.projectRoot, artifact.identity.sessionId, artifact.identity.turnId, artifact.version);
650
+ if (snapshot && definition && activation
651
+ && snapshot.semanticFactsHash === currentContext.facts.factsHash
652
+ && snapshot.semanticContextHash === currentContext.contextHash
653
+ && snapshot.channelPlanHash === currentContext.plan.planHash
654
+ && snapshot.taskDefinitionHash === definition.definitionHash
655
+ && snapshot.activationSnapshotHash === activation.snapshotHash
656
+ && definition.semanticContextHash === currentContext.contextHash) {
657
+ return { ...bound, queryPlan: currentContext.plan };
667
658
  }
668
- return legacy;
669
- }
670
- let provisional;
671
- try {
672
- assertTaskSemanticFrameV2(latest.frame, artifact.normalizedPrompt);
673
- provisional = latest.frame;
674
- }
675
- catch {
676
- return legacy;
677
659
  }
678
- const structural = extractStructuralSemanticEvidence({
679
- prompt: artifact.normalizedPrompt,
680
- command: artifact.command,
660
+ const activation = new ActivationSnapshotStore(database).get(artifact.identity, artifact.version);
661
+ if (!activation)
662
+ throw new Error(`RUNTIME_ACTIVATION_MISSING:${artifact.artifactHash}`);
663
+ const revision = artifact.version;
664
+ const resolvedContext = await resolveTaskSemanticContext({
665
+ normalizedPrompt: artifact.normalizedPrompt,
666
+ sourceHash: artifact.sourceHash,
667
+ identity: artifact.identity,
668
+ revision,
669
+ activation,
670
+ ...(engines.semanticRouter ? { router: engines.semanticRouter } : {}),
671
+ ...(dependencies.semanticSampling ? {
672
+ hostSampling: { sample: dependencies.semanticSampling },
673
+ } : {}),
674
+ ...(dependencies.retrievalQuerySampling ? {
675
+ retrievalQuerySampling: { sample: dependencies.retrievalQuerySampling },
676
+ } : {}),
677
+ ...(artifact.command ? { command: artifact.command } : {}),
681
678
  targetAnchors: artifact.targetAnchors,
679
+ policy: {
680
+ policyHash: `policy:${activation.rolloutPolicyVersion}`,
681
+ activeSkill: artifact.activeSkill,
682
+ budgets: {
683
+ hookDeadlineMs: 3_000,
684
+ contextTokens: 8_000,
685
+ responseReserveTokens: 2_000,
686
+ perChannelMs: { code: 1_500, memory: 800, knowledge: 1_500 },
687
+ },
688
+ sourceEvidenceIds: artifact.sourceEventIds,
689
+ },
682
690
  });
683
- const route = await engines.semanticRouter.route(structural.normalizedPrompt);
691
+ const finalResolution = resolvedContext.resolution;
692
+ const facts = finalResolution.facts;
693
+ const route = finalResolution.route;
684
694
  emitSemanticControlLog({
685
695
  event: 'semantic.route.scored',
686
696
  identity: { ...artifact.identity, intentArtifactHash: artifact.artifactHash },
687
697
  level: route.status === 'unavailable' ? 'warn' : 'info',
688
698
  timestamp: Date.now(),
689
699
  data: {
690
- mode: semanticMode,
700
+ mode: activation.semantic,
691
701
  status: route.status,
692
702
  modelId: route.modelId,
693
703
  modelRevision: route.modelRevision,
@@ -703,228 +713,95 @@ export async function resolveBoundContextTaskWithSemantics(input, engines, depen
703
713
  identity: { ...artifact.identity, intentArtifactHash: artifact.artifactHash },
704
714
  level: 'debug', timestamp: Date.now(),
705
715
  data: {
706
- mode: semanticMode, modelId: route.modelId, modelRevision: route.modelRevision,
707
- thresholdVersion: route.thresholdVersion, catalogVersion: provisional.resolver.routeCatalogVersion,
716
+ mode: activation.semantic, modelId: route.modelId, modelRevision: route.modelRevision,
717
+ thresholdVersion: route.thresholdVersion,
708
718
  status: route.status, latencyMs: route.latencyMs,
709
719
  },
710
720
  });
711
- let sampled;
712
- if ((route.status === 'escalation_required' || hasCompositionalStructure(structural))
713
- && dependencies.semanticSampling) {
721
+ if (finalResolution.sampling.attempted) {
714
722
  emitSemanticControlLog({
715
723
  event: 'semantic.resolution.escalated', identity: artifact.identity, level: 'info', timestamp: Date.now(),
716
- data: { mode: semanticMode, frameHash: provisional.frameHash, reason: route.reason },
717
- });
718
- sampled = await dependencies.semanticSampling({
719
- normalizedPrompt: structural.normalizedPrompt,
720
- sourceHash: artifact.sourceHash,
721
- structural,
722
- route,
723
- provisionalFrame: provisional,
724
- timeoutMs: 3_000,
724
+ data: { mode: activation.semantic, factsHash: facts.factsHash, reason: route.reason,
725
+ providerReceiptId: finalResolution.sampling.receiptId },
725
726
  });
726
727
  }
727
- const resolved = mergeSemanticAuthority({
728
- sourceHash: artifact.sourceHash,
729
- structural,
730
- route,
731
- ...(sampled?.status === 'resolved' && sampled.frame ? { sampledFrame: sampled.frame } : {}),
732
- provisionalFrame: provisional,
728
+ const semanticPlan = resolvedContext.context.plan;
729
+ const kernel = new TaskKernel(new TaskEventStore(database), new SemanticRuntimeEventProjector());
730
+ kernel.dispatch({
731
+ definition: resolvedContext.definition,
732
+ kind: 'activation_bound',
733
+ payload: { activationSnapshotHash: activation.snapshotHash },
734
+ idempotencyKey: `activation:${artifact.artifactHash}:${revision}`,
735
+ producer: 'mcp-semantic-adapter',
733
736
  });
734
- const finalResolution = withSamplingResult(resolved, sampled);
735
- const projection = projectSemanticFrameToLegacyIntent(finalResolution.frame);
736
- const revisedBody = {
737
- ...artifact,
738
- version: 2,
739
- supersedesHash: artifact.artifactHash,
740
- intent: projection.intent,
741
- action: projection.action,
742
- entities: finalResolution.frame.entities,
743
- targetAnchors: finalResolution.frame.targetAnchors,
744
- policyConstraints: finalResolution.frame.constraints,
745
- classificationEvidence: finalResolution.frame.evidence.flatMap((evidence) => {
746
- const field = evidence.field === 'negation' || evidence.field === 'condition'
747
- ? 'constraint'
748
- : evidence.field;
749
- if (field !== 'intent' && field !== 'action' && field !== 'entity'
750
- && field !== 'target' && field !== 'constraint')
751
- return [];
752
- const source = evidence.source === 'structural'
753
- || evidence.source === 'lexical_feature'
754
- ? 'deterministic_parser'
755
- : evidence.source === 'host_sampling'
756
- ? 'host_semantic'
757
- : evidence.source;
758
- return [{
759
- source,
760
- field,
761
- value: evidence.value,
762
- reason: `semantic_frame:${finalResolution.frame.frameHash}`,
763
- confidence: evidence.confidence,
764
- }];
765
- }),
766
- createdAt: finalResolution.frame.createdAt,
767
- };
768
- const { artifactHash: _oldHash, ...unhashedArtifact } = revisedBody;
769
- const revisedArtifact = {
770
- ...unhashedArtifact,
771
- artifactHash: hashCanonical(unhashedArtifact),
772
- };
773
- const taskPlan = buildRetrievalTaskPlan({
774
- query: legacy.query,
775
- parsedIntent: parseIntent(legacy.query),
776
- targetAnchors: revisedArtifact.targetAnchors,
777
- activeSkill: revisedArtifact.activeSkill,
778
- command: revisedArtifact.command,
779
- authoritativeArtifact: revisedArtifact,
780
- semanticFrame: finalResolution.frame,
737
+ kernel.dispatch({
738
+ definition: resolvedContext.definition,
739
+ kind: 'semantic_resolved',
740
+ payload: {
741
+ semanticFactsHash: facts.factsHash,
742
+ semanticContextHash: resolvedContext.context.contextHash,
743
+ },
744
+ idempotencyKey: `semantic:${artifact.artifactHash}:${revision}:${facts.factsHash}`,
745
+ producer: 'mcp-semantic-adapter',
781
746
  });
782
- const revisedPlan = buildChannelQueryPlan({
783
- artifact: revisedArtifact,
784
- taskPlan,
785
- expandedTerms: legacy.expandedTerms,
786
- generatedBy: 'hybrid',
747
+ kernel.dispatch({
748
+ definition: resolvedContext.definition,
749
+ kind: 'plan_compiled',
750
+ payload: { channelPlanHash: semanticPlan.planHash },
751
+ idempotencyKey: `plan:${artifact.artifactHash}:${revision}:${semanticPlan.planHash}`,
752
+ producer: 'mcp-semantic-adapter',
787
753
  });
788
- database.appendTaskIntentArtifact(toTaskIntentRecord(revisedArtifact));
789
- database.appendChannelQueryPlan(toChannelQueryPlanRecord(revisedPlan));
790
- database.appendSemanticResolution({
791
- frameHash: finalResolution.frame.frameHash,
792
- ...artifact.identity,
793
- sourceHash: artifact.sourceHash,
794
- artifactHash: revisedArtifact.artifactHash,
795
- artifactRevision: 2,
796
- supersedesFrameHash: provisional.frameHash,
797
- state: finalResolution.frame.state,
798
- generatedBy: finalResolution.frame.resolver.generatedBy,
799
- modelId: finalResolution.frame.resolver.modelId,
800
- modelRevision: finalResolution.frame.resolver.modelRevision,
801
- routeCatalogVersion: finalResolution.frame.resolver.routeCatalogVersion,
802
- thresholdVersion: finalResolution.frame.resolver.thresholdVersion,
803
- route: finalResolution.route,
804
- sampling: finalResolution.sampling,
805
- conflicts: finalResolution.conflicts,
806
- frame: finalResolution.frame,
807
- durationMs: Math.round(finalResolution.route.latencyMs + (finalResolution.sampling.latencyMs ?? 0)),
808
- createdAt: finalResolution.frame.createdAt,
754
+ database.putCanonicalSemanticTask({
755
+ revision,
756
+ activation,
757
+ context: resolvedContext.context,
758
+ definition: resolvedContext.definition,
809
759
  });
810
- logSemanticResolution(artifact, semanticMode, finalResolution, projection);
811
- return semanticMode === 'shadow'
812
- ? legacy
813
- : { ...legacy, intentArtifact: revisedArtifact, queryPlan: revisedPlan };
760
+ logSemanticResolution(artifact, resolvedContext.context.mode, finalResolution);
761
+ return { ...bound, queryPlan: semanticPlan };
814
762
  }
815
763
  finally {
816
764
  database.close();
817
765
  }
818
766
  }
819
- function boundTaskFromRecords(legacy, artifactRecord, planRecord) {
820
- return {
821
- ...legacy,
822
- intentArtifact: toSdkIntentArtifact(artifactRecord),
823
- queryPlan: toSdkChannelQueryPlan(planRecord),
824
- };
825
- }
826
- function hasCompositionalStructure(structural) {
827
- return structural.conditions.length > 0 || structural.prohibitions.length > 0
828
- || new Set(structural.positiveActionSpans.map(item => item.value)).size > 1;
829
- }
830
- function withSamplingResult(resolution, sampled) {
831
- if (!sampled)
832
- return resolution;
833
- return {
834
- ...resolution,
835
- sampling: {
836
- attempted: true,
837
- status: sampled.status,
838
- ...(sampled.model ? { model: sampled.model } : {}),
839
- latencyMs: sampled.latencyMs,
840
- ...(sampled.responseHash ? { responseHash: sampled.responseHash } : {}),
841
- },
842
- };
767
+ const semanticInitializationLocks = new Map();
768
+ async function ensureBoundContextTaskWithSemantics(input, engines, dependencies) {
769
+ if (!input.intentArtifactHash)
770
+ return resolveBoundContextTaskWithSemantics(input, engines, dependencies);
771
+ const key = `${input.projectRoot}\0${input.intentArtifactHash}`;
772
+ const pending = semanticInitializationLocks.get(key);
773
+ if (pending)
774
+ return pending;
775
+ const current = resolveBoundContextTaskWithSemantics(input, engines, dependencies);
776
+ semanticInitializationLocks.set(key, current);
777
+ try {
778
+ return await current;
779
+ }
780
+ finally {
781
+ if (semanticInitializationLocks.get(key) === current)
782
+ semanticInitializationLocks.delete(key);
783
+ }
843
784
  }
844
- function logSemanticResolution(artifact, mode, resolution, projection) {
845
- const event = resolution.frame.state === 'resolved'
846
- ? 'semantic.resolution.completed'
847
- : 'semantic.resolution.abstained';
785
+ function logSemanticResolution(artifact, mode, resolution) {
786
+ const event = resolution.facts.conflicts.length === 0
787
+ ? 'semantic.resolution.completed' : 'semantic.resolution.abstained';
848
788
  emitSemanticControlLog({
849
789
  event, identity: { ...artifact.identity, intentArtifactHash: artifact.artifactHash },
850
- level: resolution.frame.state === 'resolved' ? 'info' : 'warn', timestamp: Date.now(),
790
+ level: resolution.facts.conflicts.length === 0 ? 'info' : 'warn', timestamp: Date.now(),
851
791
  data: {
852
- mode, frameHash: resolution.frame.frameHash, state: resolution.frame.state,
853
- permission: resolution.frame.mutationPermission, intent: projection.intent,
854
- action: projection.action, samplingStatus: resolution.sampling.status,
855
- conflictCount: resolution.conflicts.length,
792
+ mode, factsHash: resolution.facts.factsHash,
793
+ mutation: resolution.actionAuthority.mutation,
794
+ intents: resolution.facts.intents,
795
+ actions: resolution.facts.actions,
796
+ samplingStatus: resolution.sampling.status,
797
+ conflictCount: resolution.facts.conflicts.length,
856
798
  },
857
799
  });
858
- if (resolution.conflicts.length > 0)
800
+ if (resolution.facts.conflicts.length > 0)
859
801
  emitSemanticControlLog({
860
802
  event: 'semantic.authority.conflicted', identity: artifact.identity, level: 'warn', timestamp: Date.now(),
861
- data: { frameHash: resolution.frame.frameHash, conflicts: resolution.conflicts },
803
+ data: { factsHash: resolution.facts.factsHash, conflicts: resolution.facts.conflicts },
862
804
  });
863
- if (projection.intent !== artifact.intent || projection.action !== artifact.action)
864
- emitSemanticControlLog({
865
- event: 'semantic.regex.shadow_mismatch', identity: artifact.identity, level: 'info', timestamp: Date.now(),
866
- data: { mode, frameHash: resolution.frame.frameHash, legacyIntent: artifact.intent,
867
- legacyAction: artifact.action, semanticIntent: projection.intent, semanticAction: projection.action },
868
- });
869
- }
870
- function toTaskIntentRecord(artifact) {
871
- return {
872
- ...artifact.identity, version: artifact.version, supersedesHash: artifact.supersedesHash,
873
- rawPrompt: artifact.rawPrompt, normalizedPrompt: artifact.normalizedPrompt,
874
- command: artifact.command, slashArgs: artifact.slashArgs, activeSkill: artifact.activeSkill,
875
- intent: artifact.intent, action: artifact.action, entities: artifact.entities,
876
- targetAnchors: artifact.targetAnchors, policyConstraints: artifact.policyConstraints,
877
- classificationEvidence: artifact.classificationEvidence.map(item => ({ ...item })),
878
- sourceEventIds: artifact.sourceEventIds, sourceHash: artifact.sourceHash,
879
- artifactHash: artifact.artifactHash, createdAt: artifact.createdAt,
880
- };
881
- }
882
- function toChannelQueryPlanRecord(plan) {
883
- return {
884
- ...plan.identity, sourceIntentHash: plan.sourceIntentHash, planHash: plan.planHash,
885
- code: plan.code.map(item => ({ ...item })), memory: plan.memory.map(item => ({ ...item })),
886
- knowledge: plan.knowledge.map(item => ({ ...item })), generatedBy: plan.generatedBy,
887
- ...(plan.degradation ? { degradation: { ...plan.degradation } } : {}), createdAt: plan.createdAt,
888
- };
889
- }
890
- function toSdkIntentArtifact(record) {
891
- return {
892
- identity: { projectRoot: record.projectRoot, projectId: record.projectId, hostId: record.hostId,
893
- sessionId: record.sessionId, turnId: record.turnId, requestId: record.requestId,
894
- ...(record.executionId ? { executionId: record.executionId } : {}) },
895
- version: record.version, ...(record.supersedesHash ? { supersedesHash: record.supersedesHash } : {}),
896
- rawPrompt: record.rawPrompt, normalizedPrompt: record.normalizedPrompt,
897
- ...(record.command ? { command: record.command } : {}), slashArgs: record.slashArgs,
898
- ...(record.activeSkill ? { activeSkill: record.activeSkill } : {}), intent: record.intent,
899
- action: record.action, entities: record.entities, targetAnchors: record.targetAnchors,
900
- policyConstraints: record.policyConstraints, classificationEvidence: record.classificationEvidence,
901
- sourceEventIds: record.sourceEventIds, sourceHash: record.sourceHash,
902
- artifactHash: record.artifactHash, createdAt: record.createdAt,
903
- };
904
- }
905
- function toSdkChannelQueryPlan(record) {
906
- return { identity: { projectRoot: record.projectRoot, projectId: record.projectId, hostId: record.hostId,
907
- sessionId: record.sessionId, turnId: record.turnId, requestId: record.requestId,
908
- ...(record.executionId ? { executionId: record.executionId } : {}) },
909
- sourceIntentHash: record.sourceIntentHash, planHash: record.planHash, code: record.code,
910
- memory: record.memory, knowledge: record.knowledge, generatedBy: record.generatedBy,
911
- ...(record.degradation ? { degradation: record.degradation } : {}), createdAt: record.createdAt };
912
- }
913
- function hashCanonical(value) {
914
- return createHash('sha256').update(stableCanonicalJson(value)).digest('hex');
915
- }
916
- function stableCanonicalJson(value) {
917
- if (value === null || typeof value === 'string' || typeof value === 'boolean')
918
- return JSON.stringify(value);
919
- if (typeof value === 'number')
920
- return Number.isFinite(value) ? JSON.stringify(value) : 'null';
921
- if (Array.isArray(value))
922
- return `[${value.map(stableCanonicalJson).join(',')}]`;
923
- if (!value || typeof value !== 'object')
924
- return JSON.stringify(String(value));
925
- const record = value;
926
- return `{${Object.keys(record).sort().filter(key => record[key] !== undefined)
927
- .map(key => `${JSON.stringify(key)}:${stableCanonicalJson(record[key])}`).join(',')}}`;
928
805
  }
929
806
  function stripArtifactCommand(prompt, command) {
930
807
  if (!command)
@@ -935,9 +812,41 @@ function stripArtifactCommand(prompt, command) {
935
812
  export function createToolHandlers(engines, boundProjectRoot, dependencies = {}) {
936
813
  const hasBoundProjectRoot = typeof boundProjectRoot === 'string' && boundProjectRoot.trim().length > 0;
937
814
  const canonicalProjectRoot = canonicalizeProjectRoot(String(boundProjectRoot ?? process.cwd()));
938
- // Resolution is invoked by the rollout-aware context path in the next stage.
939
- // Retaining the injected dependency here keeps handler construction backward compatible.
940
- void dependencies.semanticSampling;
815
+ const taskEvidence = dependencies.taskEvidence ?? {
816
+ load: loadSemanticExecutionContext,
817
+ query: queryAuthoritativeTaskEvidence,
818
+ };
819
+ async function authoritativeEvidence(projection, runtime) {
820
+ const sessionId = typeof runtime.sessionId === 'string' ? runtime.sessionId : undefined;
821
+ const turnId = typeof runtime.turnId === 'string' ? runtime.turnId : undefined;
822
+ if (!sessionId || !turnId) {
823
+ return {
824
+ status: 'unavailable',
825
+ authority: 'authoritative_task',
826
+ reasonCode: 'task_identity_required',
827
+ };
828
+ }
829
+ const loaded = taskEvidence.load({ projectRoot: canonicalProjectRoot, sessionId, turnId });
830
+ if (!loaded) {
831
+ return {
832
+ status: 'unavailable',
833
+ authority: 'authoritative_task',
834
+ reasonCode: 'task_evidence_unavailable',
835
+ };
836
+ }
837
+ const result = await taskEvidence.query({
838
+ engines,
839
+ request: {
840
+ schemaVersion: 'authoritative-task-evidence-request.v1',
841
+ identity: loaded.context.identity,
842
+ taskRevision: loaded.definition.revision,
843
+ semanticContextHash: loaded.context.contextHash,
844
+ requestedProjection: projection,
845
+ },
846
+ ...(dependencies.evidenceReranker ? { reranker: dependencies.evidenceReranker } : {}),
847
+ });
848
+ return result;
849
+ }
941
850
  function getFreshnessMs() {
942
851
  try {
943
852
  const stats = engines.codegraph.getStats();
@@ -948,8 +857,8 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
948
857
  }
949
858
  }
950
859
  const handlers = {
951
- get_project_context: async ({ query, mode, expandedTerms, retrievalSessionId, baselineReceipt, missingContext, _devflow_session_id, _devflow_execution_id, _devflow_request_id, _devflow_turn_id, _devflow_intent_artifact_hash, _devflow_active_skill, activeSkill }) => {
952
- const boundTask = await resolveBoundContextTaskWithSemantics({
860
+ get_project_context: async ({ query, budgetTokens, mode, expandedTerms, retrievalSessionId, baselineReceipt, missingContext, _devflow_session_id, _devflow_execution_id, _devflow_request_id, _devflow_turn_id, _devflow_intent_artifact_hash, _devflow_active_skill, activeSkill }) => {
861
+ const boundTask = await ensureBoundContextTaskWithSemantics({
953
862
  projectRoot: canonicalProjectRoot,
954
863
  sessionId: typeof _devflow_session_id === 'string' ? _devflow_session_id : undefined,
955
864
  requestId: typeof _devflow_request_id === 'string' ? _devflow_request_id : undefined,
@@ -958,26 +867,28 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
958
867
  agentQuery: query,
959
868
  expandedTerms: typeof expandedTerms === 'string' ? expandedTerms : undefined,
960
869
  }, engines, dependencies);
961
- return getProjectContextResult(engines.context, {
962
- query: boundTask.query,
963
- mode: mode ?? "fast",
964
- expandedTerms: boundTask.expandedTerms,
965
- retrievalSessionId: typeof retrievalSessionId === 'string' ? retrievalSessionId : undefined,
966
- baselineReceipt: typeof baselineReceipt === 'string' ? baselineReceipt : undefined,
967
- missingContext: Array.isArray(missingContext) ? missingContext : undefined,
968
- sessionId: typeof _devflow_session_id === 'string' ? _devflow_session_id : process.env.DEVFLOW_SESSION_ID ?? undefined,
969
- executionId: typeof _devflow_execution_id === 'string' ? _devflow_execution_id : undefined,
970
- requestId: boundTask.requestId,
870
+ const sessionId = typeof _devflow_session_id === 'string'
871
+ ? _devflow_session_id : process.env.DEVFLOW_SESSION_ID ?? undefined;
872
+ const semantic = loadSemanticExecutionContext({
873
+ projectRoot: canonicalProjectRoot,
874
+ sessionId,
971
875
  turnId: boundTask.turnId,
972
- intentArtifact: boundTask.intentArtifact,
973
- queryPlan: boundTask.queryPlan,
974
- activeSkill: boundTask.intentArtifact?.activeSkill ?? (typeof _devflow_active_skill === 'string'
975
- ? _devflow_active_skill
976
- : typeof activeSkill === 'string' ? activeSkill : undefined),
977
- projectRoot: boundProjectRoot,
978
- host: 'claude-code',
979
- command: boundTask.intentArtifact?.command ?? 'devflow:context',
980
- }, getFreshnessMs());
876
+ });
877
+ if (semantic) {
878
+ return retrieveProjectEvidence({
879
+ engines,
880
+ context: semantic.context,
881
+ definition: semantic.definition,
882
+ ...(typeof budgetTokens === 'number' ? { tokenBudget: budgetTokens } : {}),
883
+ query: boundTask.query,
884
+ ...(dependencies.evidenceReranker ? { reranker: dependencies.evidenceReranker } : {}),
885
+ });
886
+ }
887
+ return {
888
+ status: 'unavailable',
889
+ authority: 'authoritative_task',
890
+ reasonCode: 'semantic_execution_context_unavailable',
891
+ };
981
892
  },
982
893
  search_symbol: async ({ symbol }) => {
983
894
  const result = await engines.indexer.findSymbol(symbol);
@@ -1018,7 +929,7 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1018
929
  _accuracy: {
1019
930
  data_freshness_ms: getFreshnessMs(),
1020
931
  source_layer: "codegraph",
1021
- degradation: "fallback",
932
+ execution_status: "unavailable",
1022
933
  },
1023
934
  seeds: [t],
1024
935
  depth: d,
@@ -1038,7 +949,7 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1038
949
  _accuracy: {
1039
950
  data_freshness_ms: getFreshnessMs(),
1040
951
  source_layer: "codegraph",
1041
- degradation: result.edges.length === 0 && result.nodes.length > 0 ? "partial" : "none",
952
+ execution_status: result.edges.length === 0 && result.nodes.length > 0 ? "unavailable" : "executed",
1042
953
  },
1043
954
  };
1044
955
  },
@@ -1126,7 +1037,7 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1126
1037
  },
1127
1038
  };
1128
1039
  },
1129
- devflow_report_step: async ({ runId, stepId, status, output, duration, contextReceipt, executionId, toolUseIds, changedFiles, verification, _devflow_session_id, _devflow_execution_id, }) => {
1040
+ devflow_report_step: async ({ runId, stepId, status, output, duration, changedFiles, verification, _devflow_session_id, _devflow_execution_id, _devflow_tool_use_id, }) => {
1130
1041
  const canonicalRunId = String(runId ?? '').trim();
1131
1042
  const canonicalStepId = String(stepId ?? '').trim();
1132
1043
  if (!canonicalRunId || !canonicalStepId) {
@@ -1185,29 +1096,30 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1185
1096
  if (!hostRun || !hostStep) {
1186
1097
  return rejectedToolResult('WORKFLOW_ENGINE_MAPPING_MISSING', 'The workflow engine run mapping is unavailable.', false);
1187
1098
  }
1188
- const canonicalExecutionId = typeof executionId === 'string'
1189
- ? executionId.trim()
1190
- : typeof _devflow_execution_id === 'string' ? _devflow_execution_id.trim() : '';
1191
- const canonicalReceipt = typeof contextReceipt === 'string' ? contextReceipt.trim() : '';
1192
1099
  const sessionId = typeof _devflow_session_id === 'string' ? _devflow_session_id.trim() : '';
1193
- const activeReceipt = sessionId && canonicalExecutionId
1194
- ? database.getActiveContextReceipt(canonicalProjectRoot, sessionId, canonicalExecutionId)
1195
- : null;
1196
- if (!canonicalReceipt || canonicalReceipt !== action.contextReceipt || activeReceipt?.contextHash !== canonicalReceipt) {
1197
- return rejectedToolResult('STALE_CONTEXT_RECEIPT', 'A current context receipt bound to this execution is required.', true);
1100
+ const canonicalExecutionId = typeof _devflow_execution_id === 'string'
1101
+ ? _devflow_execution_id.trim() : '';
1102
+ const canonicalToolUseId = typeof _devflow_tool_use_id === 'string'
1103
+ ? _devflow_tool_use_id.trim() : '';
1104
+ if (!sessionId || !canonicalExecutionId || !canonicalToolUseId) {
1105
+ return rejectedToolResult('WORKFLOW_IDENTITY_MISSING', 'Canonical session, execution, and tool-use identity are required.', false);
1106
+ }
1107
+ if (sessionId !== action.sessionId || canonicalExecutionId !== action.executionId) {
1108
+ return rejectedToolResult('WORKFLOW_ACTION_IDENTITY_MISMATCH', 'The canonical report identity does not match the host action.', false);
1198
1109
  }
1199
- if (!canonicalExecutionId || canonicalExecutionId !== action.executionId) {
1200
- return rejectedToolResult('EXECUTION_ID_MISMATCH', 'The report execution does not match the host action.', false);
1110
+ const activeReceipt = database.getActiveContextReceipt(canonicalProjectRoot, sessionId, canonicalExecutionId);
1111
+ if (!activeReceipt || activeReceipt.contextHash !== action.contextReceipt) {
1112
+ return rejectedToolResult('STALE_CONTEXT_RECEIPT', 'The host action is not bound to the active context receipt.', true);
1201
1113
  }
1202
1114
  const files = sanitizeChangedFiles(canonicalProjectRoot, changedFiles);
1203
- const tools = boundedStringArray(toolUseIds);
1115
+ const tools = [canonicalToolUseId];
1204
1116
  const verificationRecord = verification && typeof verification === 'object' && !Array.isArray(verification)
1205
1117
  ? verification
1206
1118
  : undefined;
1207
1119
  const report = {
1208
1120
  status: requestedStatus,
1209
1121
  summary: typeof output === 'string' ? output.trim().slice(0, 4000) : '',
1210
- contextReceipt: canonicalReceipt,
1122
+ contextReceipt: activeReceipt.contextHash,
1211
1123
  executionId: canonicalExecutionId,
1212
1124
  toolUseIds: tools.sort(),
1213
1125
  changedFiles: files.sort(),
@@ -1223,6 +1135,7 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1223
1135
  };
1224
1136
  const evidenceHash = workflowEvidenceHash(report);
1225
1137
  let terminal;
1138
+ let retrievalProof;
1226
1139
  if (requestedStatus === 'failed') {
1227
1140
  terminal = database.failHostAction({
1228
1141
  actionId: action.actionId,
@@ -1283,6 +1196,17 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1283
1196
  report,
1284
1197
  evidenceHash,
1285
1198
  });
1199
+ if (actionKind === 'verify') {
1200
+ retrievalProof = database.verifyReceiptBoundRetrieval({
1201
+ projectRoot: canonicalProjectRoot,
1202
+ sessionId,
1203
+ executionId: canonicalExecutionId,
1204
+ contextReceipt: activeReceipt.contextHash,
1205
+ toolUseId: canonicalToolUseId,
1206
+ verificationReceipt: evidenceHash,
1207
+ verifiedAt: finishedAt,
1208
+ });
1209
+ }
1286
1210
  }
1287
1211
  let telemetryDegraded = false;
1288
1212
  try {
@@ -1295,7 +1219,7 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1295
1219
  return {
1296
1220
  ok: true,
1297
1221
  acknowledged: true,
1298
- status: telemetryDegraded ? 'degraded' : 'accepted',
1222
+ status: 'accepted',
1299
1223
  runId: canonicalRunId,
1300
1224
  stepId: canonicalStepId,
1301
1225
  receipt: {
@@ -1303,8 +1227,11 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1303
1227
  evidenceHash,
1304
1228
  stepState: terminal.state,
1305
1229
  runState: resumed.metadata?.canonicalStatus,
1230
+ ...(retrievalProof ? { retrievalProof } : {}),
1306
1231
  },
1307
- ...(telemetryDegraded ? { degradation: { reason: 'telemetry_write_failed', fallback: 'durable_host_action' } } : {}),
1232
+ ...(telemetryDegraded ? { warnings: [{
1233
+ code: 'TELEMETRY_WRITE_FAILED', message: 'Host action was accepted but telemetry persistence failed.',
1234
+ }] } : {}),
1308
1235
  };
1309
1236
  }
1310
1237
  catch (error) {
@@ -1329,15 +1256,29 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1329
1256
  steps: run.steps.map(step => ({ ...step, stepId: step.stepId ?? step.name })),
1330
1257
  };
1331
1258
  },
1332
- get_memory: async ({ query, purpose, budgetTokens, scopes, _devflow_session_id, _devflow_execution_id, _devflow_request_id }) => getMemoryResult(engines.memory, {
1333
- query: typeof query === "string" ? query : undefined,
1334
- purpose: typeof purpose === "string" ? purpose : undefined,
1335
- budgetTokens: typeof budgetTokens === "number" ? budgetTokens : undefined,
1336
- scopes: Array.isArray(scopes) ? scopes.filter((scope) => typeof scope === 'string') : undefined,
1337
- sessionId: typeof _devflow_session_id === 'string' ? _devflow_session_id : undefined,
1338
- executionId: typeof _devflow_execution_id === 'string' ? _devflow_execution_id : undefined,
1339
- requestId: typeof _devflow_request_id === 'string' ? _devflow_request_id : undefined,
1340
- }),
1259
+ get_memory: async ({ query, purpose, budgetTokens, scopes, _devflow_session_id, _devflow_execution_id, _devflow_request_id, _devflow_turn_id, _devflow_intent_artifact_hash }) => {
1260
+ await ensureBoundContextTaskWithSemantics({
1261
+ projectRoot: canonicalProjectRoot,
1262
+ sessionId: typeof _devflow_session_id === 'string' ? _devflow_session_id : undefined,
1263
+ requestId: typeof _devflow_request_id === 'string' ? _devflow_request_id : undefined,
1264
+ turnId: typeof _devflow_turn_id === 'string' ? _devflow_turn_id : undefined,
1265
+ intentArtifactHash: typeof _devflow_intent_artifact_hash === 'string' ? _devflow_intent_artifact_hash : undefined,
1266
+ agentQuery: typeof query === 'string' ? query : '',
1267
+ }, engines, dependencies);
1268
+ const authoritative = await authoritativeEvidence('memory', {
1269
+ sessionId: _devflow_session_id,
1270
+ executionId: _devflow_execution_id,
1271
+ requestId: _devflow_request_id,
1272
+ turnId: _devflow_turn_id,
1273
+ });
1274
+ if (authoritative.status === 'unavailable')
1275
+ return authoritative;
1276
+ const [project, user] = await Promise.all([
1277
+ engines.memory.getProjectContext(),
1278
+ engines.memory.getUserPreferences(),
1279
+ ]);
1280
+ return projectTaskMemoryResult(authoritative.receipt, { project, user });
1281
+ },
1341
1282
  memory_request_distill: async ({ sessionId, batchId, page, pageSize }) => {
1342
1283
  const memory = engines.memory;
1343
1284
  const requestedPage = typeof page === "number" && Number.isFinite(page) ? page : 0;
@@ -1402,7 +1343,7 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1402
1343
  const memory = engines.memory;
1403
1344
  if (pending.status === 'pending' && recovery?.operation === 'commit_explicit' && recovery.explicitContent?.trim()) {
1404
1345
  await ensureMemoryTurnEvent(memory, pending, recovery);
1405
- const saved = await memory.saveExplicitMemoryIntent(recovery.explicitContent.trim(), sessionId, pending.eventId);
1346
+ const saved = await memory.saveExplicitMemoryIntent(recovery.explicitContent.trim(), { actorId: 'actor:mcp-host', sessionId }, pending.eventId);
1406
1347
  const recoveredMemoryIds = [...new Set([saved.id, ...(saved.relatedMemoryIds ?? [])])];
1407
1348
  const receipt = commitMemoryReceipt(database, pending, recoveredMemoryIds, 'explicit_intent', 'explicit_user_request_mcp_recovery');
1408
1349
  if (memory.finalizeTurnDecision && receipt.receiptId) {
@@ -1467,13 +1408,13 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1467
1408
  if (explicitRefinement) {
1468
1409
  return {
1469
1410
  ...replayMemoryDecision(pending),
1470
- status: 'degraded',
1411
+ status: 'accepted',
1471
1412
  replayed: false,
1472
1413
  refined: false,
1473
- degradation: {
1474
- reason: 'invalid_host_refinement_candidates',
1475
- fallback: 'deterministic_explicit_memory',
1476
- },
1414
+ warnings: [{
1415
+ code: 'INVALID_HOST_REFINEMENT_CANDIDATES',
1416
+ message: 'The explicit memory remains committed; host refinement candidates were rejected.',
1417
+ }],
1477
1418
  rejectedCandidates: validation.rejected,
1478
1419
  };
1479
1420
  }
@@ -1500,18 +1441,20 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1500
1441
  sourceEventIds: candidate.sourceEventIds,
1501
1442
  evidenceSpans: candidate.evidenceSpans,
1502
1443
  disposition: candidate.disposition,
1444
+ operation: candidate.operation,
1445
+ referencedFactIds: candidate.referencedFactIds,
1503
1446
  }));
1504
1447
  await ensureMemoryTurnEvent(memory, pending, recovery);
1505
1448
  if (explicitRefinement) {
1506
1449
  if (!memory.refineExplicitTurnObservations) {
1507
1450
  return {
1508
1451
  ...replayMemoryDecision(pending),
1509
- status: 'degraded',
1452
+ status: 'accepted',
1510
1453
  replayed: false,
1511
- degradation: {
1512
- reason: 'explicit_refinement_unavailable',
1513
- fallback: 'deterministic_explicit_memory',
1514
- },
1454
+ warnings: [{
1455
+ code: 'EXPLICIT_REFINEMENT_UNAVAILABLE',
1456
+ message: 'The explicit memory remains committed; host refinement is unavailable.',
1457
+ }],
1515
1458
  };
1516
1459
  }
1517
1460
  try {
@@ -1570,7 +1513,7 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1570
1513
  refinedMemoryIds,
1571
1514
  retiredMemoryIds: refined.retiredMemoryIds,
1572
1515
  operations: refined.operations,
1573
- fallbackReceiptPreserved: true,
1516
+ explicitReceiptPreserved: true,
1574
1517
  },
1575
1518
  };
1576
1519
  }
@@ -1583,22 +1526,22 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1583
1526
  metadata: {
1584
1527
  projectRoot: String(projectRoot), sessionId, turnId: pending.turnId,
1585
1528
  receiptId: pending.receiptId, reason,
1586
- fallback: 'deterministic_explicit_memory',
1529
+ warningCode: 'EXPLICIT_REFINEMENT_FAILED',
1587
1530
  },
1588
1531
  });
1589
1532
  emitSemanticControlLog({
1590
- event: 'memory.write.degraded',
1533
+ event: 'memory.write.completed',
1591
1534
  identity: { projectRoot: String(projectRoot), sessionId, turnId: pending.turnId },
1592
1535
  level: 'warn',
1593
1536
  timestamp: Date.now(),
1594
- data: { stage: 'explicit_host_refinement', reason, fallback: 'deterministic_explicit_memory' },
1537
+ data: { stage: 'explicit_host_refinement', reason, warningCode: 'EXPLICIT_REFINEMENT_FAILED' },
1595
1538
  });
1596
1539
  return {
1597
1540
  ...replayMemoryDecision(pending),
1598
- status: 'degraded',
1541
+ status: 'accepted',
1599
1542
  replayed: false,
1600
1543
  refined: false,
1601
- degradation: { reason, fallback: 'deterministic_explicit_memory' },
1544
+ warnings: [{ code: 'EXPLICIT_REFINEMENT_FAILED', message: reason }],
1602
1545
  };
1603
1546
  }
1604
1547
  }
@@ -1806,10 +1749,13 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1806
1749
  }
1807
1750
  catch (error) {
1808
1751
  emitSemanticControlLog({
1809
- event: 'memory.write.degraded',
1752
+ event: 'memory.write.completed',
1810
1753
  identity: { projectRoot: canonicalProjectRoot, sessionId: typeof _devflow_session_id === 'string' ? _devflow_session_id : undefined },
1811
1754
  level: 'warn', timestamp: Date.now(),
1812
- data: { stage: 'distill_vector_enqueue', batchId, reason: error.message, fallback: 'lexical_memory_ready' },
1755
+ data: {
1756
+ stage: 'distill_vector_enqueue', batchId, reason: error.message,
1757
+ warningCode: 'VECTOR_BACKFILL_ENQUEUE_FAILED',
1758
+ },
1813
1759
  });
1814
1760
  }
1815
1761
  finally {
@@ -1832,11 +1778,25 @@ export function createToolHandlers(engines, boundProjectRoot, dependencies = {})
1832
1778
  },
1833
1779
  };
1834
1780
  },
1835
- get_knowledge: async ({ query, scope, budgetTokens }) => getKnowledgeResult(engines.knowledge, {
1836
- query: query,
1837
- scope: typeof scope === 'string' ? scope : undefined,
1838
- budgetTokens: typeof budgetTokens === 'number' ? budgetTokens : undefined,
1839
- }),
1781
+ get_knowledge: async ({ query, scope, budgetTokens, _devflow_session_id, _devflow_execution_id, _devflow_request_id, _devflow_turn_id, _devflow_intent_artifact_hash }) => {
1782
+ await ensureBoundContextTaskWithSemantics({
1783
+ projectRoot: canonicalProjectRoot,
1784
+ sessionId: typeof _devflow_session_id === 'string' ? _devflow_session_id : undefined,
1785
+ requestId: typeof _devflow_request_id === 'string' ? _devflow_request_id : undefined,
1786
+ turnId: typeof _devflow_turn_id === 'string' ? _devflow_turn_id : undefined,
1787
+ intentArtifactHash: typeof _devflow_intent_artifact_hash === 'string' ? _devflow_intent_artifact_hash : undefined,
1788
+ agentQuery: typeof query === 'string' ? query : '',
1789
+ }, engines, dependencies);
1790
+ const authoritative = await authoritativeEvidence('knowledge', {
1791
+ sessionId: _devflow_session_id,
1792
+ executionId: _devflow_execution_id,
1793
+ requestId: _devflow_request_id,
1794
+ turnId: _devflow_turn_id,
1795
+ });
1796
+ if (authoritative.status === 'unavailable')
1797
+ return authoritative;
1798
+ return projectTaskKnowledgeResult(authoritative.receipt);
1799
+ },
1840
1800
  devflow_report_event: async (args) => {
1841
1801
  const truncate = (s, max) => {
1842
1802
  if (!s || s.length <= max)