frontend-project-context 1.6.0 → 1.7.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 (52) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +92 -46
  3. package/UPGRADING.md +22 -1
  4. package/docs/05-ACCEPTANCE-CONTRACT.md +20 -1
  5. package/docs/08-INSTALLATION-AND-DISTRIBUTION.md +44 -21
  6. package/docs/14-FORMAL-RELEASE-READINESS.md +9 -5
  7. package/docs/19-POST-1.3.1-AI-TAKEOVER-EVIDENCE-AND-UPGRADE-PLAN.md +5 -5
  8. package/docs/20-PHASE-A-AI-TAKEOVER-AND-HEALTH-CLOSURE-DESIGN.md +11 -11
  9. package/docs/22-PHASE-C-TARGET-UPGRADE-PROTOCOL-DESIGN.md +4 -4
  10. package/docs/23-ADAPTIVE-BOUNDED-TASK-CONTEXT-DESIGN.md +432 -0
  11. package/docs/24-A130-REAL-HOST-TARGET-PROJECT-COMPARISON.md +210 -0
  12. package/docs/25-REAL-PROJECT-SOURCE-OF-TRUTH-MAINTENANCE-DESIGN.md +409 -0
  13. package/docs/26-A130-QUALITY-CLOSURE-AND-ADAPTIVE-DELIVERY-REPAIR-DESIGN.md +609 -0
  14. package/docs/README.md +22 -6
  15. package/docs/USER-AND-AI-OPERATION-MANUAL.md +73 -30
  16. package/examples/README.md +4 -4
  17. package/examples/package.json +1 -1
  18. package/migration-manifest.json +30 -8
  19. package/package.json +2 -2
  20. package/schemas/adaptive-context-bundle.schema.json +70 -0
  21. package/schemas/capabilities.schema.json +20 -6
  22. package/schemas/context-query.schema.json +69 -0
  23. package/schemas/coverage-audit.schema.json +32 -0
  24. package/schemas/evidence-bundle.schema.json +2 -2
  25. package/schemas/host-promotion-evidence.schema.json +33 -0
  26. package/schemas/migration-manifest.schema.json +3 -3
  27. package/schemas/migration-plan.schema.json +2 -2
  28. package/schemas/projection-lock.schema.json +1 -1
  29. package/schemas/routing-index.schema.json +58 -0
  30. package/schemas/truth-reconciliation-input.schema.json +60 -0
  31. package/schemas/truth-reconciliation-review-bundle.schema.json +155 -0
  32. package/schemas/upgrade-assessment.schema.json +2 -2
  33. package/schemas/upgrade-result-bundle.schema.json +1 -1
  34. package/src/project-context/a130-evaluation.mjs +91 -0
  35. package/src/project-context/adaptive-context-schema.mjs +392 -0
  36. package/src/project-context/adaptive-context.mjs +547 -0
  37. package/src/project-context/ai-entry.mjs +9 -9
  38. package/src/project-context/assist.mjs +4 -2
  39. package/src/project-context/capabilities.mjs +18 -0
  40. package/src/project-context/checker.mjs +4 -3
  41. package/src/project-context/cli.mjs +40 -5
  42. package/src/project-context/contract-schema.mjs +1 -1
  43. package/src/project-context/discovery.mjs +7 -7
  44. package/src/project-context/exchange-schema.mjs +6 -5
  45. package/src/project-context/maintenance.mjs +2 -2
  46. package/src/project-context/migration-manifest.mjs +7 -5
  47. package/src/project-context/renderer.mjs +75 -1
  48. package/src/project-context/source-reader.mjs +63 -30
  49. package/src/project-context/task-context.mjs +14 -2
  50. package/src/project-context/truth-reconciliation-schema.mjs +488 -0
  51. package/src/project-context/truth-reconciliation.mjs +543 -0
  52. package/src/project-context/upgrade-schema.mjs +5 -1
@@ -26,17 +26,32 @@ import {
26
26
  UPGRADE_ASSESSMENT_SCHEMA_VERSION,
27
27
  UPGRADE_RESULT_BUNDLE_SCHEMA_VERSION,
28
28
  } from "./upgrade-schema.mjs";
29
+ import {
30
+ ADAPTIVE_CONTEXT_BUNDLE_SCHEMA_VERSION,
31
+ CONTEXT_QUERY_SCHEMA_VERSION,
32
+ COVERAGE_AUDIT_SCHEMA_VERSION,
33
+ ROUTING_INDEX_SCHEMA_VERSION,
34
+ } from "./adaptive-context-schema.mjs";
35
+ import {
36
+ HOST_PROMOTION_EVIDENCE_SCHEMA_VERSION,
37
+ TRUTH_RECONCILIATION_INPUT_SCHEMA_VERSION,
38
+ TRUTH_RECONCILIATION_REVIEW_BUNDLE_SCHEMA_VERSION,
39
+ } from "./truth-reconciliation-schema.mjs";
29
40
 
30
41
  function schemas(projectionLockWritten = 1) {
31
42
  return {
32
43
  actionPlan: ACTION_PLAN_SCHEMA_VERSION,
44
+ adaptiveContextBundle: ADAPTIVE_CONTEXT_BUNDLE_SCHEMA_VERSION,
33
45
  assistBundle: ASSIST_BUNDLE_SCHEMA_VERSION,
34
46
  capabilities: CAPABILITIES_SCHEMA_VERSION,
35
47
  contract: 2,
48
+ contextQuery: CONTEXT_QUERY_SCHEMA_VERSION,
49
+ coverageAudit: COVERAGE_AUDIT_SCHEMA_VERSION,
36
50
  dashboardViewModel: DASHBOARD_SCHEMA_VERSION,
37
51
  evidenceBundle: EVIDENCE_BUNDLE_SCHEMA_VERSION,
38
52
  evidenceInput: EVIDENCE_INPUT_SCHEMA_VERSION,
39
53
  integrationReviewBundle: INTEGRATION_REVIEW_BUNDLE_SCHEMA_VERSION,
54
+ hostPromotionEvidence: HOST_PROMOTION_EVIDENCE_SCHEMA_VERSION,
40
55
  projectionLock: 2,
41
56
  projectionLockReadable: [1, 2],
42
57
  projectionLockWritten,
@@ -47,10 +62,13 @@ function schemas(projectionLockWritten = 1) {
47
62
  migrationManifest: 2,
48
63
  migrationPlan: MIGRATION_PLAN_SCHEMA_VERSION,
49
64
  reviewBundle: REVIEW_BUNDLE_SCHEMA_VERSION,
65
+ routingIndex: ROUTING_INDEX_SCHEMA_VERSION,
50
66
  sourceLock: 1,
51
67
  stageContextBundle: STAGE_CONTEXT_BUNDLE_SCHEMA_VERSION,
52
68
  stageReceipt: STAGE_RECEIPT_SCHEMA_VERSION,
53
69
  taskContextPlan: TASK_CONTEXT_PLAN_SCHEMA_VERSION,
70
+ truthReconciliationInput: TRUTH_RECONCILIATION_INPUT_SCHEMA_VERSION,
71
+ truthReconciliationReviewBundle: TRUTH_RECONCILIATION_REVIEW_BUNDLE_SCHEMA_VERSION,
54
72
  upgradeAssessment: UPGRADE_ASSESSMENT_SCHEMA_VERSION,
55
73
  upgradeResultBundle: UPGRADE_RESULT_BUNDLE_SCHEMA_VERSION,
56
74
  };
@@ -8,7 +8,8 @@ import { findConflicts, validateOverrides } from "./scope-compiler.mjs";
8
8
  import { parseProjectionMarker, RENDERER_VERSION, renderProjection } from "./renderer.mjs";
9
9
  import { resolveWritableInside } from "./path-policy.mjs";
10
10
 
11
- export async function checkProject(root, project) {
11
+ export async function checkProject(root, project, options = {}) {
12
+ const sourceReadContext = options.sourceReadContext;
12
13
  const findings = [];
13
14
  const contractSourceIds = new Set(project.contract.sources.map((source) => source.id));
14
15
  const sourceLock = new Map(project.sourcesLock.sources.map((entry) => [entry.id, entry.digest]));
@@ -25,7 +26,7 @@ export async function checkProject(root, project) {
25
26
  continue;
26
27
  }
27
28
  try {
28
- const actual = await readSourceDigest(root, source);
29
+ const actual = await readSourceDigest(root, source, sourceReadContext);
29
30
  if (actual !== locked) findings.push({ code: "source-changed", source: source.id, path: source.path, expected: locked, actual });
30
31
  } catch (error) {
31
32
  const code = error.code === "source-missing"
@@ -51,7 +52,7 @@ export async function checkProject(root, project) {
51
52
  const sourceMap = new Map(project.contract.sources.map((source) => [source.id, source]));
52
53
  for (const item of project.contract.items.filter((candidate) => candidate.status === "approved")) {
53
54
  try {
54
- const verification = await verifyItem(root, item, sourceMap);
55
+ const verification = await verifyItem(root, item, sourceMap, sourceReadContext);
55
56
  if (verification) findings.push(verification);
56
57
  } catch (error) {
57
58
  findings.push({
@@ -1,4 +1,5 @@
1
1
  import { buildSetupAssistBundle, buildSyncAssistBundle } from "./assist.mjs";
2
+ import { buildAdaptiveContextBundleFiles, buildAdaptiveContextDeliveryFiles, buildCoverageAuditFiles, indexContextFiles } from "./adaptive-context.mjs";
2
3
  import { approvePendingItems, approveProposal } from "./approver.mjs";
3
4
  import { buildItemProposal, registerSource } from "./authoring.mjs";
4
5
  import { blockingContextFindings, checkExitCode, checkProject } from "./checker.mjs";
@@ -22,6 +23,7 @@ import { buildProjectStatus } from "./project-status.mjs";
22
23
  import { renderContextBundle } from "./renderer.mjs";
23
24
  import { buildIntegrationReviewBundleFiles, buildStageContextBundleFiles } from "./task-context.mjs";
24
25
  import { applyMigrationPlanFile, buildMigrationPlanFile, buildUpgradeAssessment } from "./upgrade.mjs";
26
+ import { buildTruthReconciliationReviewFiles } from "./truth-reconciliation.mjs";
25
27
 
26
28
  const HELP = `project-context — model-neutral project contract compiler
27
29
 
@@ -44,6 +46,9 @@ Usage:
44
46
  project-context discover --project PATH [--output FILE --write] [--json]
45
47
  project-context approve --project PATH (--proposal FILE | --pending) --ids ID... --by NAME [--rationale TEXT] [--write] [--json | --full-json]
46
48
  project-context context --project PATH --path RELATIVE_PATH... [--task TEXT] [--locale zh-CN|en|all] [--json]
49
+ project-context context-query --project PATH --input FILE [--previous FILE] [--json | --prompt]
50
+ project-context coverage-audit --project PATH [--changed-path RELATIVE_PATH...] [--json]
51
+ project-context index-context --project PATH [--write] [--json]
47
52
  project-context publish --project PATH --target agents|ruler --output FILE [--path RELATIVE_PATH...] [--write] [--json]
48
53
  project-context publish-entry --project PATH --output AGENTS.md [--write] [--json]
49
54
  project-context remove-entry --project PATH --output AGENTS.md [--write] [--json]
@@ -53,19 +58,20 @@ Usage:
53
58
  project-context preflight --project PATH --plan FILE [--json]
54
59
  project-context stage-context --project PATH --plan FILE --stage STAGE_ID [--receipt FILE...] [--receipt-bundle FILE...] [--changed-path RELATIVE_PATH...] [--json]
55
60
  project-context integration-review --project PATH --plan FILE [--receipt FILE...] [--receipt-bundle FILE...] [--main-changed-path RELATIVE_PATH...] [--branch-changed-path RELATIVE_PATH...] [--json]
61
+ project-context reconcile-truth --project PATH --input FILE... [--previous-review FILE] [--json]
56
62
 
57
63
  All commands are read-only unless their own --write flag is present.
58
64
  `;
59
65
  const VALUE_FLAGS = new Set([
60
- "project", "id", "name", "input", "output", "proposal", "by", "task", "target", "rationale",
66
+ "project", "id", "name", "output", "proposal", "by", "task", "target", "rationale",
61
67
  "kind", "pointer", "reference", "subject", "value", "value-json", "statement", "scope", "scope-path",
62
68
  "verification", "verification-source", "verification-expected-json",
63
- "expected-digest", "expected-item-digest", "expected-source-digest", "locale", "plan", "stage", "from-version", "assessment",
69
+ "expected-digest", "expected-item-digest", "expected-source-digest", "locale", "plan", "stage", "from-version", "assessment", "previous", "previous-review",
64
70
  ]);
65
71
  const LIST_FLAGS = new Set([
66
- "ids", "path", "changed-path", "sources", "overrides", "affected-items", "receipt", "receipt-bundle", "main-changed-path", "branch-changed-path",
72
+ "ids", "path", "input", "changed-path", "sources", "overrides", "affected-items", "receipt", "receipt-bundle", "main-changed-path", "branch-changed-path",
67
73
  ]);
68
- const BOOLEAN_FLAGS = new Set(["write", "json", "full-json", "help", "pending"]);
74
+ const BOOLEAN_FLAGS = new Set(["write", "json", "full-json", "help", "pending", "prompt"]);
69
75
  const COMMAND_OPTIONS = new Map([
70
76
  ["init", new Set(["project", "id", "name", "write", "json", "help"])],
71
77
  ["capabilities", new Set(["project", "json", "help"])],
@@ -98,6 +104,9 @@ const COMMAND_OPTIONS = new Map([
98
104
  ["discover", new Set(["project", "output", "write", "json", "help"])],
99
105
  ["approve", new Set(["project", "proposal", "pending", "ids", "by", "rationale", "write", "json", "full-json", "help"])],
100
106
  ["context", new Set(["project", "path", "task", "locale", "json", "help"])],
107
+ ["context-query", new Set(["project", "input", "previous", "json", "prompt", "help"])],
108
+ ["coverage-audit", new Set(["project", "changed-path", "json", "help"])],
109
+ ["index-context", new Set(["project", "write", "json", "help"])],
101
110
  ["publish", new Set(["project", "target", "output", "path", "write", "json", "help"])],
102
111
  ["publish-entry", new Set(["project", "output", "write", "json", "help"])],
103
112
  ["remove-entry", new Set(["project", "output", "write", "json", "help"])],
@@ -107,6 +116,7 @@ const COMMAND_OPTIONS = new Map([
107
116
  ["preflight", new Set(["project", "plan", "json", "help"])],
108
117
  ["stage-context", new Set(["project", "plan", "stage", "receipt", "receipt-bundle", "changed-path", "json", "help"])],
109
118
  ["integration-review", new Set(["project", "plan", "receipt", "receipt-bundle", "main-changed-path", "branch-changed-path", "json", "help"])],
119
+ ["reconcile-truth", new Set(["project", "input", "previous-review", "json", "help"])],
110
120
  ]);
111
121
 
112
122
  export function parseArgs(argv) {
@@ -348,7 +358,7 @@ async function runCommand(command, options) {
348
358
  return { exitCode: result.exitCode, stdout: jsonOrText(options, result.status, summary), stderr: "" };
349
359
  }
350
360
  if (command === "evidence") {
351
- const bundle = await buildEvidenceBundleFile(root, required(options, "input"));
361
+ const bundle = await buildEvidenceBundleFile(root, oneListValue(options, "input"));
352
362
  const summary = [
353
363
  `Evidence ${bundle.result}; project health ${bundle.projectContext.health}.`,
354
364
  `Finding codes: ${bundle.projectContext.findingCodes.join(", ") || "none"}.`,
@@ -411,6 +421,26 @@ async function runCommand(command, options) {
411
421
  const summary = `Stage context ${bundle.status}: ${bundle.stage.id}; ${bundle.contractItems.length} contract item(s), ${bundle.readTargets.length} read target(s), ${bundle.budget.usedUtf8Bytes}/${bundle.budget.maxUtf8Bytes} UTF-8 bytes.\n`;
412
422
  return { exitCode: bundle.status === "ready" ? 0 : 1, stdout: jsonOrText(options, bundle, summary), stderr: "" };
413
423
  }
424
+ if (command === "context-query") {
425
+ if (options.json && options.prompt) fail("argument-conflict", "context-query accepts only one of --json or --prompt");
426
+ if (options.prompt) {
427
+ const result = await buildAdaptiveContextDeliveryFiles(root, oneListValue(options, "input"), { previousPath: options.previous });
428
+ return { exitCode: result.bundle.taskHealth === "ready" ? 0 : 1, stdout: result.content, stderr: "" };
429
+ }
430
+ const bundle = await buildAdaptiveContextBundleFiles(root, oneListValue(options, "input"), { previousPath: options.previous });
431
+ const summary = `Adaptive context ${bundle.taskHealth}: ${bundle.hydratedItems.length} hydrated, ${bundle.retainedItemIds.length} retained, ${bundle.deferredItems.length} deferred item(s); audit ${bundle.budget.audit.usedUtf8Bytes}/${bundle.budget.audit.maxUtf8Bytes}, delivery ${bundle.budget.delivery.usedUtf8Bytes}/${bundle.budget.delivery.maxUtf8Bytes} UTF-8 bytes.\n`;
432
+ return { exitCode: bundle.taskHealth === "ready" ? 0 : 1, stdout: jsonOrText(options, bundle, summary), stderr: "" };
433
+ }
434
+ if (command === "coverage-audit") {
435
+ const audit = await buildCoverageAuditFiles(root, options["changed-path"] ?? []);
436
+ const summary = `Registration coverage ${audit.registrationCoverage}: ${audit.categories["review-required"].length} review-required candidate(s).\n`;
437
+ return { exitCode: audit.registrationCoverage === "review-required" ? 1 : 0, stdout: jsonOrText(options, audit, summary), stderr: "" };
438
+ }
439
+ if (command === "index-context") {
440
+ const index = await indexContextFiles(root, { write: options.write });
441
+ const summary = `Routing index ${options.write ? "written" : "preview"}: ${index.items.length} item(s), ${index.sources.length} source(s); ${index.indexDigest}.\n`;
442
+ return { exitCode: 0, stdout: jsonOrText(options, index, summary), stderr: "" };
443
+ }
414
444
  if (command === "integration-review") {
415
445
  const bundle = await buildIntegrationReviewBundleFiles(root, required(options, "plan"), {
416
446
  receiptPaths: options.receipt ?? [],
@@ -421,6 +451,11 @@ async function runCommand(command, options) {
421
451
  const summary = `Integration review ${bundle.status}: ${bundle.findings.length} finding(s), ${bundle.contractOverlapItemIds.length} contract overlap(s), ${bundle.decisionCandidates.length} decision candidate(s).\n`;
422
452
  return { exitCode: bundle.status === "reviewable" ? 0 : 1, stdout: jsonOrText(options, bundle, summary), stderr: "" };
423
453
  }
454
+ if (command === "reconcile-truth") {
455
+ const bundle = await buildTruthReconciliationReviewFiles(root, required(options, "input"), { previousReviewPath: options["previous-review"] });
456
+ const summary = `Truth reconciliation ${bundle.summary.taskHealth}: ${bundle.summary.collisionGroups} collision group(s), ${bundle.summary.findings} finding(s); existing delivery ${bundle.summary.existingDelivery}.\n`;
457
+ return { exitCode: bundle.summary.taskHealth === "ready" ? 0 : 1, stdout: jsonOrText(options, bundle, summary), stderr: "" };
458
+ }
424
459
  const project = await loadProject(root);
425
460
  if (command === "register") {
426
461
  const result = await registerSource(root, project, {
@@ -308,7 +308,7 @@ export function validateProjectionLock(lock) {
308
308
  if (entry.regionId !== "project-context-ai-entry") fail("schema-invalid-enum", "AI Entry regionId is invalid");
309
309
  string(entry.regionDigest, `${label}.regionDigest`);
310
310
  if (!SHA256.test(entry.regionDigest)) fail("schema-invalid", "AI Entry regionDigest must be sha256");
311
- if (entry.rendererVersion !== 1) fail("schema-version-unsupported", "AI Entry rendererVersion must be 1");
311
+ if (![1, 2, 3].includes(entry.rendererVersion)) fail("schema-version-unsupported", "AI Entry rendererVersion must be 1, 2, or 3");
312
312
  if (typeof entry.createdFile !== "boolean") fail("schema-invalid", `${label}.createdFile must be boolean`);
313
313
  } else {
314
314
  if (entry.target !== "agents" && entry.target !== "ruler") fail("schema-invalid-enum", "projection target is invalid");
@@ -145,7 +145,7 @@ export async function discoverProject(root, contract) {
145
145
  id: "fact-package-manager",
146
146
  subject: "project.package-manager",
147
147
  value: packageJson.packageManager,
148
- statement: `The project declares package manager ${packageJson.packageManager}.`,
148
+ statement: `项目声明使用包管理器 ${packageJson.packageManager}。`,
149
149
  source,
150
150
  }));
151
151
  }
@@ -155,7 +155,7 @@ export async function discoverProject(root, contract) {
155
155
  id: `fact-${scriptId}`,
156
156
  subject: `project.${scriptId.replaceAll("-", ".")}`,
157
157
  value: packageJson.scripts[name],
158
- statement: `The project declares the ${name} script.`,
158
+ statement: `项目声明了 ${name} 脚本。`,
159
159
  source,
160
160
  }));
161
161
  }
@@ -167,7 +167,7 @@ export async function discoverProject(root, contract) {
167
167
  id: `fact-${dependencyId}`,
168
168
  subject: `project.${dependencyId.replaceAll("-", ".")}`,
169
169
  value: dependencies[name],
170
- statement: `The project declares ${name} as a dependency.`,
170
+ statement: `项目声明 ${name} 为依赖。`,
171
171
  source,
172
172
  }));
173
173
  }
@@ -181,7 +181,7 @@ export async function discoverProject(root, contract) {
181
181
  id: `fact-${configId}`,
182
182
  subject: `project.${configId.replaceAll("-", ".")}`,
183
183
  value: true,
184
- statement: `The project contains ${relative}.`,
184
+ statement: `项目包含 ${relative}。`,
185
185
  source,
186
186
  }));
187
187
  }
@@ -191,7 +191,7 @@ export async function discoverProject(root, contract) {
191
191
  id: `fact-${generatedId("top-level-directory", relative)}`,
192
192
  subject: "project.structure.top-level-directory",
193
193
  value: relative,
194
- statement: `The project contains the top-level directory ${relative}.`,
194
+ statement: `项目包含顶层目录 ${relative}。`,
195
195
  source,
196
196
  scope: { kind: "path-prefix", path: relative },
197
197
  }));
@@ -221,7 +221,7 @@ export async function discoverProject(root, contract) {
221
221
  kind: "reference",
222
222
  subject: `agent-rules.${slug(relative)}`,
223
223
  value: relative,
224
- statement: `Review the existing Agent instruction source at ${relative}.`,
224
+ statement: `阅读现有 AI Agent 指令源 ${relative}。`,
225
225
  source,
226
226
  scope: relative.includes("/") && !relative.startsWith(".")
227
227
  ? { kind: "path-prefix", path: path.posix.dirname(relative) }
@@ -243,7 +243,7 @@ export async function discoverProject(root, contract) {
243
243
  kind: "reference",
244
244
  subject: `registered-source.${source.id.replaceAll("-", ".")}`,
245
245
  value: source.path ?? source.reference,
246
- statement: `Review the explicitly registered project source ${source.id}.`,
246
+ statement: `阅读显式登记的项目源 ${source.id}。`,
247
247
  source: source.id,
248
248
  }));
249
249
  } catch {
@@ -2,11 +2,11 @@ import { canonicalValue, digestJson, validateJsonValue } from "./canonical-json.
2
2
  import { fail } from "./errors.mjs";
3
3
  import { normalizeRelativePath } from "./path-policy.mjs";
4
4
 
5
- export const PACKAGE_VERSION = "1.6.0";
6
- export const EXCHANGE_PROTOCOL_VERSION = 4;
5
+ export const PACKAGE_VERSION = "1.7.0";
6
+ export const EXCHANGE_PROTOCOL_VERSION = 7;
7
7
  export const ACTION_PLAN_SCHEMA_VERSION = 2;
8
8
  export const REVIEW_BUNDLE_SCHEMA_VERSION = 2;
9
- export const CAPABILITIES_SCHEMA_VERSION = 4;
9
+ export const CAPABILITIES_SCHEMA_VERSION = 7;
10
10
 
11
11
  export const ACTION_KINDS = Object.freeze([
12
12
  "accept-source-change",
@@ -22,10 +22,11 @@ export const ACTION_KINDS = Object.freeze([
22
22
  ]);
23
23
 
24
24
  export const COMMANDS = Object.freeze([
25
- "accept-source-change", "approve", "capabilities", "check", "context", "dashboard", "deprecate",
25
+ "accept-source-change", "approve", "capabilities", "check", "context", "context-query", "coverage-audit", "dashboard", "deprecate",
26
26
  "deprecate-source", "discover", "evidence", "init", "integration-review", "preflight", "propose", "publish", "register",
27
- "publish-entry", "remove-entry", "review-source", "revise", "setup", "stage-context", "status", "sync",
27
+ "index-context", "publish-entry", "remove-entry", "review-source", "revise", "setup", "stage-context", "status", "sync",
28
28
  "upgrade-apply", "upgrade-check", "upgrade-plan",
29
+ "reconcile-truth",
29
30
  ]);
30
31
 
31
32
  const ACTION_KIND_SET = new Set(ACTION_KINDS);
@@ -79,7 +79,7 @@ export function sourceImpact(project, sourceId) {
79
79
  return { ...relations, directProjectionPaths, staleProjectionPaths };
80
80
  }
81
81
 
82
- export async function reviewSource(root, project, sourceId) {
82
+ export async function reviewSource(root, project, sourceId, options = {}) {
83
83
  const source = project.contract.sources.find((entry) => entry.id === sourceId);
84
84
  if (!source) fail("source-not-found", `source is not registered: ${sourceId}`, { details: { source: sourceId } });
85
85
  const sourceObjectDigest = digestJson(source);
@@ -104,7 +104,7 @@ export async function reviewSource(root, project, sourceId) {
104
104
  let status;
105
105
  let reason;
106
106
  try {
107
- currentDigest = await readSourceDigest(root, source);
107
+ currentDigest = await readSourceDigest(root, source, options.sourceReadContext);
108
108
  if (lockedDigest === null || lockedDigest !== source.digest) {
109
109
  status = "unreadable";
110
110
  reason = lockedDigest === null ? "source-lock-missing" : "source-lock-mismatch";
@@ -22,8 +22,9 @@ const MIGRATION_KINDS = new Set([
22
22
  ]);
23
23
  const ROLLBACK_CLASSES = new Set(["package-only", "reversible-data", "forward-only"]);
24
24
  const CONSUMER_CHANGE_KEYS = new Set([
25
- "actionPlan", "capabilities", "evidenceBundle", "evidenceInput", "exchange",
26
- "integrationReviewBundle", "reviewBundle", "stageContextBundle", "stageReceipt", "taskContextPlan",
25
+ "actionPlan", "adaptiveContextBundle", "capabilities", "contextQuery", "coverageAudit", "evidenceBundle", "evidenceInput", "exchange",
26
+ "hostPromotionEvidence", "integrationReviewBundle", "reviewBundle", "routingIndex", "stageContextBundle", "stageReceipt", "taskContextPlan",
27
+ "truthReconciliationInput", "truthReconciliationReviewBundle",
27
28
  ]);
28
29
 
29
30
  function invalid(message, details) {
@@ -102,7 +103,7 @@ export function validateMigrationManifest(input) {
102
103
  ]), "migration manifest");
103
104
  if (manifest.schemaVersion !== MIGRATION_MANIFEST_SCHEMA_VERSION) invalid("migration manifest schemaVersion must be 2");
104
105
  exactKeys(manifest.package, new Set(["name", "version"]), "migration manifest package");
105
- if (manifest.package.name !== "frontend-project-context" || manifest.package.version !== "1.6.0") invalid("migration manifest package does not match this runtime");
106
+ if (manifest.package.name !== "frontend-project-context" || manifest.package.version !== "1.7.0") invalid("migration manifest package does not match this runtime");
106
107
  versions(manifest.upgradeFrom, "upgradeFrom");
107
108
  if (manifest.upgradeFrom.length === 0) invalid("upgradeFrom must not be empty");
108
109
  exactKeys(manifest.stores, new Set(["contract", "projectionLock", "proposal", "sourceLock"]), "stores");
@@ -110,8 +111,9 @@ export function validateMigrationManifest(input) {
110
111
  exactKeys(manifest.renderers, new Set(["aiEntry", "projection"]), "renderers");
111
112
  for (const name of Object.keys(manifest.renderers)) versionMatrix(manifest.renderers[name], `renderers.${name}`);
112
113
  exactKeys(manifest.protocols, new Set([
113
- "exchange", "actionPlan", "reviewBundle", "evidenceInput", "evidenceBundle", "taskContextPlan",
114
- "stageReceipt", "stageContextBundle", "integrationReviewBundle",
114
+ "exchange", "actionPlan", "adaptiveContextBundle", "contextQuery", "coverageAudit", "reviewBundle", "evidenceInput", "evidenceBundle", "taskContextPlan",
115
+ "routingIndex", "stageReceipt", "stageContextBundle", "integrationReviewBundle", "hostPromotionEvidence",
116
+ "truthReconciliationInput", "truthReconciliationReviewBundle",
115
117
  ]), "protocols");
116
118
  for (const name of Object.keys(manifest.protocols)) versionMatrix(manifest.protocols[name], `protocols.${name}`);
117
119
  if (!Array.isArray(manifest.builtInMigrations)) invalid("builtInMigrations must be an array");
@@ -1,5 +1,5 @@
1
1
  import { canonicalJson, digestJson, sha256 } from "./canonical-json.mjs";
2
- import { describeScope, effectiveItems } from "./scope-compiler.mjs";
2
+ import { describeScope, effectiveItems, scopeApplies } from "./scope-compiler.mjs";
3
3
  import { normalizeRelativePath } from "./path-policy.mjs";
4
4
 
5
5
  const KIND_TITLES = new Map([
@@ -107,6 +107,80 @@ export function renderContextBundle(contract, paths, task, options = {}) {
107
107
  return renderCollectedBundle(collectBundle(contract, paths), contract.sources, task, options);
108
108
  }
109
109
 
110
+ export function renderSelectedContextBundle(contract, paths, itemIds, task, options = {}) {
111
+ const selected = new Set(itemIds);
112
+ const normalizedPaths = paths.map((value) => normalizeRelativePath(value, { allowRoot: true, label: "target path" }));
113
+ const selectedItems = contract.items.filter((item) => item.status === "approved" && selected.has(item.id));
114
+ const sections = normalizedPaths.map((targetPath) => ({
115
+ path: targetPath,
116
+ items: selectedItems.filter((item) => scopeApplies(item.scope, targetPath)).sort((left, right) => left.id.localeCompare(right.id)),
117
+ }));
118
+ const bundle = {
119
+ project: contract.project,
120
+ contractDigest: digestJson(contract),
121
+ paths: normalizedPaths,
122
+ sections,
123
+ itemIds: [...selected].sort(),
124
+ };
125
+ return renderCollectedBundle(bundle, contract.sources, task, options);
126
+ }
127
+
128
+ // Task delivery deliberately has its own renderer. Managed AGENTS/Ruler projections
129
+ // keep using renderCollectedBundle and RENDERER_VERSION 3 byte-for-byte.
130
+ function renderTaskItem(item, locale) {
131
+ const sources = item.sources.map((source) => `\`${source}\``).join(", ");
132
+ const value = canonicalJson(item.value);
133
+ const fence = fenceFor(value);
134
+ const subject = item.subject === item.id ? "" : `Subject: \`${item.subject}\`; `;
135
+ return `- **${item.id}** — ${localizedStatement(item.statement, locale)}\n - ${subject}Scope: \`${describeScope(item.scope)}\`; source IDs: ${sources}\n - Value (canonical JSON):\n\n ${fence}json\n ${value}\n ${fence}`;
136
+ }
137
+
138
+ export function renderTaskContextBundle(contract, paths, itemIds, task, options = {}) {
139
+ const locale = options.locale ?? "zh-CN";
140
+ const selected = new Set(itemIds);
141
+ const normalizedPaths = paths.map((value) => normalizeRelativePath(value, { allowRoot: true, label: "target path" }));
142
+ const effectiveByPath = normalizedPaths.map((targetPath) => ({
143
+ path: targetPath,
144
+ items: effectiveItems(contract.items, targetPath)
145
+ .filter((item) => selected.has(item.id))
146
+ .sort((left, right) => left.id.localeCompare(right.id)),
147
+ }));
148
+ const commonIds = effectiveByPath.length === 0 ? new Set() : new Set(effectiveByPath[0].items.map((item) => item.id));
149
+ for (const section of effectiveByPath.slice(1)) {
150
+ const ids = new Set(section.items.map((item) => item.id));
151
+ for (const id of [...commonIds]) if (!ids.has(id)) commonIds.delete(id);
152
+ }
153
+ const common = effectiveByPath[0]?.items.filter((item) => commonIds.has(item.id)) ?? [];
154
+ const lines = [
155
+ "# Project Context Task Delivery",
156
+ "",
157
+ `Project: **${contract.project.name}** (\`${contract.project.id}\`)`,
158
+ `Contract digest: \`${digestJson(contract)}\``,
159
+ ];
160
+ const fence = fenceFor(task);
161
+ lines.push("", "## Task constraint", "", fence, String(task), fence);
162
+ const renderKinds = (items) => {
163
+ for (const [kind, title] of KIND_TITLES) {
164
+ const entries = items.filter((item) => item.kind === kind);
165
+ if (entries.length > 0) lines.push("", `### ${title}`, "", ...entries.map((item) => renderTaskItem(item, locale)));
166
+ }
167
+ };
168
+ if (common.length > 0) {
169
+ lines.push("", "## Applies to all targets");
170
+ renderKinds(common);
171
+ }
172
+ for (const section of effectiveByPath) {
173
+ const delta = section.items.filter((item) => !commonIds.has(item.id));
174
+ lines.push("", `## Target delta: \`${section.path}\``);
175
+ if (delta.length === 0) lines.push("", "No additional approved contract items apply.");
176
+ else renderKinds(delta);
177
+ }
178
+ lines.push("", "## Source IDs", "");
179
+ const sourceIds = [...new Set(effectiveByPath.flatMap((section) => section.items.flatMap((item) => item.sources)))].sort();
180
+ lines.push(sourceIds.length === 0 ? "No sources selected." : sourceIds.map((id) => `- \`${id}\``).join("\n"));
181
+ return `${lines.join("\n")}\n`;
182
+ }
183
+
110
184
  export function renderProjection(contract, paths, target) {
111
185
  const bundle = collectBundle(contract, paths);
112
186
  const body = renderCollectedBundle(bundle, contract.sources);
@@ -33,7 +33,20 @@ export function readJsonPointer(value, pointer) {
33
33
  return current;
34
34
  }
35
35
 
36
- async function digestDirectory(absolute) {
36
+ export function createSourceReadContext() {
37
+ return {
38
+ cache: new Map(),
39
+ metrics: { sourceDigestReads: 0, sourceBodyReads: 0, sourceIdentityReads: 0 },
40
+ };
41
+ }
42
+
43
+ async function cached(context, key, read) {
44
+ if (!context?.cache) return read();
45
+ if (!context.cache.has(key)) context.cache.set(key, Promise.resolve().then(read));
46
+ return context.cache.get(key);
47
+ }
48
+
49
+ async function digestDirectory(absolute, context) {
37
50
  const entries = [];
38
51
  async function visit(directory, prefix) {
39
52
  const children = (await readdir(directory, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
@@ -46,7 +59,11 @@ async function digestDirectory(absolute) {
46
59
  } else if (child.isDirectory()) {
47
60
  await visit(full, relative);
48
61
  } else if (child.isFile()) {
49
- entries.push([relative, sha256(await readFile(full))]);
62
+ const body = await cached(context, `body:${full}`, async () => {
63
+ if (context?.metrics) context.metrics.sourceBodyReads += 1;
64
+ return readFile(full);
65
+ });
66
+ entries.push([relative, sha256(body)]);
50
67
  }
51
68
  }
52
69
  }
@@ -54,42 +71,58 @@ async function digestDirectory(absolute) {
54
71
  return sha256(canonicalJson(entries));
55
72
  }
56
73
 
57
- export async function digestPath(projectRoot, relativePath) {
58
- const { absolute } = await resolveExistingInside(projectRoot, relativePath);
59
- const info = await stat(absolute);
60
- if (info.isDirectory()) return digestDirectory(absolute);
61
- if (!info.isFile()) fail("source-unsupported", `source is not a regular file or directory: ${relativePath}`);
62
- return sha256(await readFile(absolute));
74
+ export async function digestPath(projectRoot, relativePath, context) {
75
+ return cached(context, `digest:${relativePath}`, async () => {
76
+ if (context?.metrics) context.metrics.sourceDigestReads += 1;
77
+ const { absolute } = await resolveExistingInside(projectRoot, relativePath);
78
+ const info = await stat(absolute);
79
+ if (info.isDirectory()) return digestDirectory(absolute, context);
80
+ if (!info.isFile()) fail("source-unsupported", `source is not a regular file or directory: ${relativePath}`);
81
+ const body = await cached(context, `body:${absolute}`, async () => {
82
+ if (context?.metrics) context.metrics.sourceBodyReads += 1;
83
+ return readFile(absolute);
84
+ });
85
+ return sha256(body);
86
+ });
63
87
  }
64
88
 
65
- export async function digestPathIdentity(projectRoot, relativePath) {
66
- const { absolute } = await resolveExistingInside(projectRoot, relativePath);
67
- const info = await stat(absolute);
68
- const kind = info.isDirectory() ? "directory" : info.isFile() ? "file" : null;
69
- if (!kind) fail("source-unsupported", `source is not a regular file or directory: ${relativePath}`);
70
- return sha256(canonicalJson({ kind }));
89
+ export async function digestPathIdentity(projectRoot, relativePath, context) {
90
+ return cached(context, `identity:${relativePath}`, async () => {
91
+ if (context?.metrics) context.metrics.sourceIdentityReads += 1;
92
+ const { absolute } = await resolveExistingInside(projectRoot, relativePath);
93
+ const info = await stat(absolute);
94
+ const kind = info.isDirectory() ? "directory" : info.isFile() ? "file" : null;
95
+ if (!kind) fail("source-unsupported", `source is not a regular file or directory: ${relativePath}`);
96
+ return sha256(canonicalJson({ kind }));
97
+ });
71
98
  }
72
99
 
73
- export async function readSourceDigest(projectRoot, source) {
100
+ export async function readSourceDigest(projectRoot, source, context) {
74
101
  if (sourceStatus(source) === "deprecated") return null;
75
102
  if (source.kind === "human-decision" || source.kind === "external-reference") return null;
76
- if (source.kind === "path") return digestPathIdentity(projectRoot, source.path);
77
- if (source.kind === "file") return digestPath(projectRoot, source.path);
78
- return sha256(canonicalJson(await readJsonSourceValue(projectRoot, source)));
103
+ if (source.kind === "path") return digestPathIdentity(projectRoot, source.path, context);
104
+ if (source.kind === "file") return digestPath(projectRoot, source.path, context);
105
+ return sha256(canonicalJson(await readJsonSourceValue(projectRoot, source, context)));
79
106
  }
80
107
 
81
- async function readJsonSourceValue(projectRoot, source) {
82
- const { absolute } = await resolveExistingInside(projectRoot, source.path);
83
- let parsed;
84
- try {
85
- parsed = JSON.parse(await readFile(absolute, "utf8"));
86
- } catch (error) {
87
- fail("source-json-invalid", `cannot parse JSON source: ${source.path}`, { cause: error });
88
- }
89
- return readJsonPointer(parsed, source.pointer);
108
+ async function readJsonSourceValue(projectRoot, source, context) {
109
+ return cached(context, `json:${source.path}:${source.pointer}`, async () => {
110
+ const { absolute } = await resolveExistingInside(projectRoot, source.path);
111
+ let parsed;
112
+ try {
113
+ const body = await cached(context, `body:${absolute}`, async () => {
114
+ if (context?.metrics) context.metrics.sourceBodyReads += 1;
115
+ return readFile(absolute, "utf8");
116
+ });
117
+ parsed = JSON.parse(body);
118
+ } catch (error) {
119
+ fail("source-json-invalid", `cannot parse JSON source: ${source.path}`, { cause: error });
120
+ }
121
+ return readJsonPointer(parsed, source.pointer);
122
+ });
90
123
  }
91
124
 
92
- export async function verifyItem(projectRoot, item, sourceMap) {
125
+ export async function verifyItem(projectRoot, item, sourceMap, context) {
93
126
  const verification = item.verification;
94
127
  if (!verification || verification.kind === "none") return null;
95
128
  const source = sourceMap.get(verification.source);
@@ -106,7 +139,7 @@ export async function verifyItem(projectRoot, item, sourceMap) {
106
139
  if (source.kind !== "json-pointer") {
107
140
  return { code: "verification-source-incompatible", item: item.id, source: source.id };
108
141
  }
109
- const actualValue = await readJsonSourceValue(projectRoot, source);
142
+ const actualValue = await readJsonSourceValue(projectRoot, source, context);
110
143
  if (canonicalJson(actualValue) !== canonicalJson(verification.expected)) {
111
144
  return { code: "verification-failed", item: item.id, source: source.id, expected: verification.expected, actual: actualValue };
112
145
  }
@@ -115,7 +148,7 @@ export async function verifyItem(projectRoot, item, sourceMap) {
115
148
  if (verification.kind !== "path-digest") {
116
149
  return { code: "verification-kind-unsupported", item: item.id, source: source.id };
117
150
  }
118
- const actual = await digestPath(projectRoot, source.path);
151
+ const actual = await digestPath(projectRoot, source.path, context);
119
152
  const expected = verification.expected ?? source.digest;
120
153
  if (actual !== expected) return { code: "verification-failed", item: item.id, source: source.id, expected, actual };
121
154
  return null;
@@ -1,4 +1,5 @@
1
1
  import { canonicalJson, digestJson } from "./canonical-json.mjs";
2
+ import { selectAdaptiveItems } from "./adaptive-context.mjs";
2
3
  import { blockingContextFindings, checkProject } from "./checker.mjs";
3
4
  import { ProjectContextError, fail } from "./errors.mjs";
4
5
  import { readJsonFile } from "./io.mjs";
@@ -91,8 +92,18 @@ function normalizedReceipts(receiptInputs, plan) {
91
92
  return byStage;
92
93
  }
93
94
 
94
- function selectedItems(contract, targetPaths, findings) {
95
+ function selectedItems(contract, targetPaths, findings, adaptiveText) {
95
96
  const byId = new Map();
97
+ if (adaptiveText !== undefined) {
98
+ const adaptiveFindings = [];
99
+ const selection = selectAdaptiveItems(contract, null, {
100
+ task: { text: adaptiveText, topics: [], paths: targetPaths, changedPaths: [], itemIds: [] },
101
+ level: "initial",
102
+ }, { findings: adaptiveFindings });
103
+ findings.push(...adaptiveFindings);
104
+ for (const item of selection.selected) byId.set(item.id, item);
105
+ }
106
+ if (adaptiveText === undefined) {
96
107
  for (const targetPath of targetPaths) {
97
108
  try {
98
109
  for (const item of effectiveItems(contract.items, targetPath)) byId.set(item.id, item);
@@ -102,6 +113,7 @@ function selectedItems(contract, targetPaths, findings) {
102
113
  for (const item of contract.items.filter((entry) => entry.status === "approved" && scopeApplies(entry.scope, targetPath))) byId.set(item.id, item);
103
114
  }
104
115
  }
116
+ }
105
117
  return [...byId.values()].sort((left, right) => left.id.localeCompare(right.id)).map((item) => ({
106
118
  id: item.id,
107
119
  kind: item.kind,
@@ -205,7 +217,7 @@ async function buildStageContextBundleCore(root, project, plan, options, receipt
205
217
  if (receipt.status !== "completed") findings.push(finding("stage-dependency-blocked", "blocked", { stageId: stage.id, dependencyStageId: dependencyId }));
206
218
  }
207
219
  const targetPaths = uniqueSorted([...stage.paths, ...changedPaths]);
208
- const contractItems = selectedItems(project.contract, targetPaths, findings);
220
+ const contractItems = selectedItems(project.contract, targetPaths, findings, `${plan.task.goal}\n${stage.objective}`);
209
221
  const readTargets = mergeReadTargets([
210
222
  ...stage.paths.map((entry) => ({ path: entry, reason: "stage-scope" })),
211
223
  ...changedPaths.map((entry) => ({ path: entry, reason: "host-changed-path-signal" })),