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.
- package/CHANGELOG.md +22 -0
- package/README.md +92 -46
- package/UPGRADING.md +22 -1
- package/docs/05-ACCEPTANCE-CONTRACT.md +20 -1
- package/docs/08-INSTALLATION-AND-DISTRIBUTION.md +44 -21
- package/docs/14-FORMAL-RELEASE-READINESS.md +9 -5
- package/docs/19-POST-1.3.1-AI-TAKEOVER-EVIDENCE-AND-UPGRADE-PLAN.md +5 -5
- package/docs/20-PHASE-A-AI-TAKEOVER-AND-HEALTH-CLOSURE-DESIGN.md +11 -11
- package/docs/22-PHASE-C-TARGET-UPGRADE-PROTOCOL-DESIGN.md +4 -4
- package/docs/23-ADAPTIVE-BOUNDED-TASK-CONTEXT-DESIGN.md +432 -0
- package/docs/24-A130-REAL-HOST-TARGET-PROJECT-COMPARISON.md +210 -0
- package/docs/25-REAL-PROJECT-SOURCE-OF-TRUTH-MAINTENANCE-DESIGN.md +409 -0
- package/docs/26-A130-QUALITY-CLOSURE-AND-ADAPTIVE-DELIVERY-REPAIR-DESIGN.md +609 -0
- package/docs/README.md +22 -6
- package/docs/USER-AND-AI-OPERATION-MANUAL.md +73 -30
- package/examples/README.md +4 -4
- package/examples/package.json +1 -1
- package/migration-manifest.json +30 -8
- package/package.json +2 -2
- package/schemas/adaptive-context-bundle.schema.json +70 -0
- package/schemas/capabilities.schema.json +20 -6
- package/schemas/context-query.schema.json +69 -0
- package/schemas/coverage-audit.schema.json +32 -0
- package/schemas/evidence-bundle.schema.json +2 -2
- package/schemas/host-promotion-evidence.schema.json +33 -0
- package/schemas/migration-manifest.schema.json +3 -3
- package/schemas/migration-plan.schema.json +2 -2
- package/schemas/projection-lock.schema.json +1 -1
- package/schemas/routing-index.schema.json +58 -0
- package/schemas/truth-reconciliation-input.schema.json +60 -0
- package/schemas/truth-reconciliation-review-bundle.schema.json +155 -0
- package/schemas/upgrade-assessment.schema.json +2 -2
- package/schemas/upgrade-result-bundle.schema.json +1 -1
- package/src/project-context/a130-evaluation.mjs +91 -0
- package/src/project-context/adaptive-context-schema.mjs +392 -0
- package/src/project-context/adaptive-context.mjs +547 -0
- package/src/project-context/ai-entry.mjs +9 -9
- package/src/project-context/assist.mjs +4 -2
- package/src/project-context/capabilities.mjs +18 -0
- package/src/project-context/checker.mjs +4 -3
- package/src/project-context/cli.mjs +40 -5
- package/src/project-context/contract-schema.mjs +1 -1
- package/src/project-context/discovery.mjs +7 -7
- package/src/project-context/exchange-schema.mjs +6 -5
- package/src/project-context/maintenance.mjs +2 -2
- package/src/project-context/migration-manifest.mjs +7 -5
- package/src/project-context/renderer.mjs +75 -1
- package/src/project-context/source-reader.mjs +63 -30
- package/src/project-context/task-context.mjs +14 -2
- package/src/project-context/truth-reconciliation-schema.mjs +488 -0
- package/src/project-context/truth-reconciliation.mjs +543 -0
- 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", "
|
|
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,
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
export const EXCHANGE_PROTOCOL_VERSION =
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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" })),
|