frontend-project-context 1.8.0 → 1.9.1

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 (48) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/README.md +7 -7
  3. package/UPGRADING.md +10 -0
  4. package/docs/08-INSTALLATION-AND-DISTRIBUTION.md +9 -3
  5. package/docs/14-FORMAL-RELEASE-READINESS.md +14 -0
  6. package/docs/28-REAL-PROJECT-ONBOARDING-CLOSURE-DESIGN.md +10 -0
  7. package/docs/29-REAL-PROJECT-1.8.0-INITIALIZATION-OBSERVATIONS.md +228 -0
  8. package/docs/30-TASK-CONTEXT-CONSUMPTION-CLOSURE-DESIGN.md +563 -0
  9. package/docs/31-TASK-CONTEXT-INTEGRITY-REPAIR-DESIGN.md +281 -0
  10. package/docs/AI-PROJECT-INITIALIZATION.md +3 -2
  11. package/docs/PRODUCT-SHARING-AND-ADOPTION-GUIDE.md +111 -0
  12. package/docs/README.md +14 -2
  13. package/docs/USER-AND-AI-OPERATION-MANUAL.md +3 -3
  14. package/docs/assets/product-sharing-01-overview.svg +32 -0
  15. package/docs/assets/product-sharing-02-how-it-works.svg +17 -0
  16. package/docs/assets/product-sharing-03-example.svg +19 -0
  17. package/examples/package.json +1 -1
  18. package/migration-manifest.json +30 -12
  19. package/package.json +2 -2
  20. package/schemas/capabilities.schema.json +14 -10
  21. package/schemas/context-bundle.schema.json +32 -0
  22. package/schemas/coverage-audit.schema.json +6 -4
  23. package/schemas/evidence-bundle.schema.json +1 -1
  24. package/schemas/initialization-instruction.schema.json +17 -5
  25. package/schemas/migration-manifest.schema.json +3 -3
  26. package/schemas/migration-plan.schema.json +1 -1
  27. package/schemas/projection-lock.schema.json +1 -1
  28. package/schemas/upgrade-assessment.schema.json +1 -1
  29. package/schemas/upgrade-result-bundle.schema.json +1 -1
  30. package/src/project-context/adaptive-context-schema.mjs +1 -1
  31. package/src/project-context/adaptive-context.mjs +12 -23
  32. package/src/project-context/ai-entry.mjs +22 -9
  33. package/src/project-context/approver.mjs +6 -2
  34. package/src/project-context/authoring.mjs +28 -8
  35. package/src/project-context/capabilities.mjs +4 -1
  36. package/src/project-context/checker.mjs +1 -1
  37. package/src/project-context/cli.mjs +29 -16
  38. package/src/project-context/context-bundle.mjs +378 -0
  39. package/src/project-context/contract-schema.mjs +62 -9
  40. package/src/project-context/coverage-profile.mjs +127 -0
  41. package/src/project-context/exchange-schema.mjs +23 -6
  42. package/src/project-context/exchange.mjs +3 -0
  43. package/src/project-context/initialization-instruction.mjs +20 -2
  44. package/src/project-context/maintenance.mjs +5 -4
  45. package/src/project-context/migration-manifest.mjs +3 -3
  46. package/src/project-context/renderer.mjs +11 -4
  47. package/src/project-context/source-reader.mjs +27 -2
  48. package/src/project-context/upgrade-schema.mjs +1 -1
@@ -23,14 +23,15 @@
23
23
  "consumerChanges": {
24
24
  "actionPlan": {"reason": "Action Plan schema 1 and 2 readers remain compatible; writers remain schema 2.", "state": "preserve"},
25
25
  "adaptiveContextBundle": {"reason": "Schema 1 Adaptive Context Bundles lack task lineage and strict withheld-delivery semantics; regenerate as schema 2.", "state": "regenerate"},
26
- "capabilities": {"reason": "Consumers must branch on capabilities schema 8 and exchange protocol 8.", "state": "regenerate"},
26
+ "capabilities": {"reason": "Consumers must branch on capabilities schema 9 and exchange protocol 9.", "state": "regenerate"},
27
+ "contextBundle": {"reason": "Context Bundle schema 1 is short-lived and must be regenerated from the current Contract and exact task paths.", "state": "regenerate"},
27
28
  "contextQuery": {"reason": "Context Query schema 1 is short-lived and must use current snapshots.", "state": "regenerate"},
28
- "coverageAudit": {"reason": "Coverage Audit schema 1 is derived from the current Contract and filesystem evidence.", "state": "regenerate"},
29
+ "coverageAudit": {"reason": "Coverage Audit schema 2 is derived from the current Contract and declared coverage profile.", "state": "regenerate"},
29
30
  "evidenceBundle": {"reason": "Evidence Bundle schema 1 remains readable and separate from upgrade results.", "state": "preserve"},
30
31
  "evidenceInput": {"reason": "Evidence Input schema 1 remains readable and is never rewritten by upgrade.", "state": "preserve"},
31
- "exchange": {"reason": "Exchange protocol capability metadata changes to version 8.", "state": "regenerate"},
32
+ "exchange": {"reason": "Exchange protocol capability metadata changes to version 9.", "state": "regenerate"},
32
33
  "hostPromotionEvidence": {"reason": "Host Promotion Evidence is short-lived and remains bound to exact code, artifact, and Project Context snapshots.", "state": "regenerate"},
33
- "initializationInstruction": {"reason": "Schema 1 is package-derived from the exact installed instruction and resolved target root.", "state": "regenerate"},
34
+ "initializationInstruction": {"reason": "Schema 2 adds concentrated human entry rule dispositions and remains package-derived.", "state": "regenerate"},
34
35
  "integrationReviewBundle": {"reason": "Short-lived integration review bundles must be regenerated against current baselines.", "state": "regenerate"},
35
36
  "projectStatus": {"reason": "Schema 2 separates governance health from Contract readiness and task-context routing.", "state": "regenerate"},
36
37
  "reviewBundle": {"reason": "Review Bundle schema 2 remains readable but short-lived reviews must be regenerated when their baselines change.", "state": "regenerate"},
@@ -42,8 +43,8 @@
42
43
  ,"truthReconciliationReviewBundle": {"reason": "Schema 1 reviews lack finding digests and machine-verifiable resolution outcomes; recompute as schema 2.", "state": "regenerate"}
43
44
  },
44
45
  "externalEffects": {"automaticUpgrade": false, "businessCode": false, "git": false, "network": false, "packageManager": false, "projectTests": false},
45
- "manifestDigest": "sha256:be29e7ee930d34533380e6d2408f614fc7406ad3491a1657776d904bcc5ebacf",
46
- "package": {"name": "frontend-project-context", "version": "1.8.0"},
46
+ "manifestDigest": "sha256:8b1a6bce6ac179591b122593c062cf6d0f80680da1eca5aba663e3273bbd1e56",
47
+ "package": {"name": "frontend-project-context", "version": "1.9.1"},
47
48
  "paths": [
48
49
  {
49
50
  "acceptance": ["project-context-clean", "target-renderers-current"],
@@ -84,18 +85,35 @@
84
85
  "migrationIds": ["upgrade.republish-ai-entry.v1"],
85
86
  "requiresHumanReview": false,
86
87
  "rollbackClass": "package-only"
88
+ },
89
+ {
90
+ "acceptance": ["project-context-clean", "target-renderers-current"],
91
+ "classification": "package-only",
92
+ "fromVersion": "1.8.0",
93
+ "migrationIds": ["upgrade.republish-ai-entry.v1"],
94
+ "requiresHumanReview": false,
95
+ "rollbackClass": "package-only"
96
+ },
97
+ {
98
+ "acceptance": ["project-context-clean", "target-renderers-current"],
99
+ "classification": "package-only",
100
+ "fromVersion": "1.9.0",
101
+ "migrationIds": [],
102
+ "requiresHumanReview": false,
103
+ "rollbackClass": "package-only"
87
104
  }
88
105
  ],
89
106
  "protocols": {
90
107
  "actionPlan": {"readable": [1, 2], "written": 2},
91
108
  "adaptiveContextBundle": {"readable": [2], "written": 2},
109
+ "contextBundle": {"readable": [1], "written": 1},
92
110
  "contextQuery": {"readable": [2], "written": 2},
93
- "coverageAudit": {"readable": [1], "written": 1},
111
+ "coverageAudit": {"readable": [1, 2], "written": 2},
94
112
  "evidenceBundle": {"readable": [1], "written": 1},
95
113
  "evidenceInput": {"readable": [1], "written": 1},
96
- "exchange": {"readable": [4, 5, 6, 7, 8], "written": 8},
114
+ "exchange": {"readable": [4, 5, 6, 7, 8, 9], "written": 9},
97
115
  "hostPromotionEvidence": {"readable": [1], "written": 1},
98
- "initializationInstruction": {"readable": [1], "written": 1},
116
+ "initializationInstruction": {"readable": [1, 2], "written": 2},
99
117
  "integrationReviewBundle": {"readable": [1], "written": 1},
100
118
  "projectStatus": {"readable": [1, 2], "written": 2},
101
119
  "reviewBundle": {"readable": [1, 2], "written": 2},
@@ -107,16 +125,16 @@
107
125
  "truthReconciliationReviewBundle": {"readable": [2], "written": 2}
108
126
  },
109
127
  "renderers": {
110
- "aiEntry": {"readable": [1, 2, 3, 4], "written": 4},
128
+ "aiEntry": {"readable": [1, 2, 3, 4, 5], "written": 5},
111
129
  "projection": {"readable": [1, 2, 3], "written": 3}
112
130
  },
113
131
  "rollback": {"automatic": false, "defaultClass": "package-only", "externalRestoreRequired": true},
114
132
  "schemaVersion": 2,
115
133
  "stores": {
116
- "contract": {"readable": [1, 2], "written": [1, 2]},
134
+ "contract": {"readable": [1, 2, 3], "written": [1, 2, 3]},
117
135
  "projectionLock": {"readable": [1, 2], "written": [1, 2]},
118
136
  "proposal": {"readable": [1], "written": [1]},
119
137
  "sourceLock": {"readable": [1], "written": [1]}
120
138
  },
121
- "upgradeFrom": ["1.3.1", "1.4.0", "1.5.0", "1.6.0", "1.7.0"]
139
+ "upgradeFrom": ["1.3.1", "1.4.0", "1.5.0", "1.6.0", "1.7.0", "1.8.0", "1.9.0"]
122
140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontend-project-context",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "description": "Govern, compile, and verify project-local context for AI coding tools.",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "scripts": {
29
29
  "check": "node --check bin/project-context.mjs && npm test",
30
- "test": "node --test test/project-context/acceptance.test.mjs test/project-context/adaptive-context.test.mjs test/project-context/adaptive-delivery-v2.test.mjs test/project-context/assist.test.mjs test/project-context/cli.test.mjs test/project-context/dashboard.test.mjs test/project-context/evidence.test.mjs test/project-context/exchange.test.mjs test/project-context/maintenance.test.mjs test/project-context/onboarding-closure.test.mjs test/project-context/source-lifecycle.test.mjs test/project-context/task-context.test.mjs test/project-context/takeover.test.mjs test/project-context/truth-reconciliation.test.mjs test/project-context/upgrade.test.mjs test/release/acceptance.test.mjs",
30
+ "test": "node --test test/project-context/acceptance.test.mjs test/project-context/adaptive-context.test.mjs test/project-context/adaptive-delivery-v2.test.mjs test/project-context/assist.test.mjs test/project-context/cli.test.mjs test/project-context/dashboard.test.mjs test/project-context/evidence.test.mjs test/project-context/exchange.test.mjs test/project-context/maintenance.test.mjs test/project-context/onboarding-closure.test.mjs test/project-context/source-lifecycle.test.mjs test/project-context/task-context.test.mjs test/project-context/task-context-consumption.test.mjs test/project-context/takeover.test.mjs test/project-context/truth-reconciliation.test.mjs test/project-context/upgrade.test.mjs test/release/acceptance.test.mjs",
31
31
  "prepack": "npm run check"
32
32
  },
33
33
  "engines": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "urn:frontend-project-context:schema:capabilities:8",
3
+ "$id": "urn:frontend-project-context:schema:capabilities:9",
4
4
  "title": "Frontend Project Context Capabilities",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
@@ -19,17 +19,17 @@
19
19
  "boundaries"
20
20
  ],
21
21
  "properties": {
22
- "schemaVersion": { "const": 8 },
22
+ "schemaVersion": { "const": 9 },
23
23
  "package": {
24
24
  "type": "object",
25
25
  "additionalProperties": false,
26
26
  "required": ["name", "version"],
27
27
  "properties": {
28
28
  "name": { "const": "frontend-project-context" },
29
- "version": { "const": "1.8.0" }
29
+ "version": { "const": "1.9.1" }
30
30
  }
31
31
  },
32
- "exchangeProtocolVersion": { "const": 8 },
32
+ "exchangeProtocolVersion": { "const": 9 },
33
33
  "schemas": {
34
34
  "type": "object",
35
35
  "additionalProperties": false,
@@ -39,6 +39,8 @@
39
39
  "assistBundle",
40
40
  "capabilities",
41
41
  "contract",
42
+ "contractReadable",
43
+ "contextBundle",
42
44
  "contextQuery",
43
45
  "coverageAudit",
44
46
  "dashboardViewModel",
@@ -71,23 +73,25 @@
71
73
  "actionPlan": { "const": 2 },
72
74
  "adaptiveContextBundle": { "const": 2 },
73
75
  "assistBundle": { "const": 1 },
74
- "capabilities": { "const": 8 },
75
- "contract": { "const": 2 },
76
+ "capabilities": { "const": 9 },
77
+ "contract": { "const": 3 },
78
+ "contractReadable": { "const": [1, 2, 3] },
79
+ "contextBundle": { "const": 1 },
76
80
  "contextQuery": { "const": 2 },
77
- "coverageAudit": { "const": 1 },
81
+ "coverageAudit": { "const": 2 },
78
82
  "dashboardViewModel": { "type": "integer" },
79
83
  "evidenceBundle": { "const": 1 },
80
84
  "evidenceInput": { "const": 1 },
81
85
  "hostPromotionEvidence": { "const": 1 },
82
86
  "integrationReviewBundle": { "const": 1 },
83
- "initializationInstruction": { "const": 1 },
87
+ "initializationInstruction": { "const": 2 },
84
88
  "projectionLock": { "const": 2 },
85
89
  "projectionLockReadable": { "const": [1, 2] },
86
90
  "projectionLockWritten": { "enum": [1, 2] },
87
91
  "projectionRenderer": { "type": "integer" },
88
92
  "proposal": { "const": 1 },
89
93
  "projectStatus": { "const": 2 },
90
- "aiEntryRenderer": { "const": 4 },
94
+ "aiEntryRenderer": { "const": 5 },
91
95
  "migrationManifest": { "const": 2 },
92
96
  "migrationPlan": { "const": 1 },
93
97
  "reviewBundle": { "const": 2 },
@@ -139,7 +143,7 @@
139
143
  "required": ["id", "version", "packagePath", "digest", "command"],
140
144
  "properties": {
141
145
  "id": {"const": "ai-project-initialization"},
142
- "version": {"const": 1},
146
+ "version": {"const": 2},
143
147
  "packagePath": {"const": "docs/AI-PROJECT-INITIALIZATION.md"},
144
148
  "digest": {"type": "string", "pattern": "^sha256:[a-f0-9]{64}$"},
145
149
  "command": {"const": "project-context instructions --project PATH [--json | --prompt]"}
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "urn:frontend-project-context:schema:context-bundle:1",
4
+ "title": "Frontend Project Context Bundle",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "kind", "project", "mode", "task", "snapshots", "semanticCompleteness", "commonItemIds", "targets", "items", "readTargets", "provenanceSources", "coverage", "authority", "gaps", "nextActions", "content", "bundleDigest"],
8
+ "properties": {
9
+ "schemaVersion": {"const": 1},
10
+ "kind": {"const": "context-bundle"},
11
+ "project": {"type": "object", "additionalProperties": false, "required": ["id", "name"], "properties": {"id": {"type": "string", "minLength": 1}, "name": {"type": "string", "minLength": 1}}},
12
+ "mode": {"enum": ["locate", "targeted"]},
13
+ "task": {"type": "object", "additionalProperties": false, "required": ["text", "paths"], "properties": {"text": {"type": "string"}, "paths": {"$ref": "#/$defs/strings"}}},
14
+ "snapshots": {"type": "object", "additionalProperties": false, "required": ["contract", "sourcesLock", "projectionsLock"], "properties": {"contract": {"$ref": "#/$defs/digest"}, "sourcesLock": {"$ref": "#/$defs/digest"}, "projectionsLock": {"$ref": "#/$defs/digest"}}},
15
+ "semanticCompleteness": {"const": "not-claimed"},
16
+ "commonItemIds": {"$ref": "#/$defs/strings"},
17
+ "targets": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["path", "scopeCoverage", "itemIds", "scopedItemIds", "deltaItemIds"], "properties": {"path": {"type": "string"}, "scopeCoverage": {"enum": ["scoped", "project-only", "none"]}, "itemIds": {"$ref": "#/$defs/strings"}, "scopedItemIds": {"$ref": "#/$defs/strings"}, "deltaItemIds": {"$ref": "#/$defs/strings"}}}},
18
+ "items": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["id", "kind", "subject", "scope", "sourceIds", "statement", "value", "overrides"], "properties": {"id": {"type": "string", "minLength": 1}, "kind": {"enum": ["fact", "policy", "reference", "validation-description"]}, "subject": {"type": "string", "minLength": 1}, "scope": {"type": "object", "additionalProperties": false, "required": ["kind"], "properties": {"kind": {"enum": ["project", "path-prefix", "file"]}, "path": {"type": "string"}}}, "sourceIds": {"$ref": "#/$defs/strings"}, "statement": {"type": "string", "minLength": 1}, "value": true, "overrides": {"$ref": "#/$defs/strings"}, "verification": {"type": "object", "additionalProperties": false, "required": ["kind"], "properties": {"kind": {"enum": ["none", "file-exists", "json-value", "path-digest"]}, "source": {"type": "string"}, "expected": true}}, "consumption": {"type": "object", "additionalProperties": false, "required": ["requiredSources", "conditionalSources"], "properties": {"requiredSources": {"$ref": "#/$defs/strings"}, "conditionalSources": {"$ref": "#/$defs/strings"}}}}}},
19
+ "readTargets": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["path", "role", "itemIds", "sourceIds", "reasons"], "properties": {"path": {"type": "string"}, "role": {"enum": ["required", "conditional"]}, "itemIds": {"$ref": "#/$defs/strings"}, "sourceIds": {"$ref": "#/$defs/strings"}, "reasons": {"$ref": "#/$defs/strings"}, "pointers": {"$ref": "#/$defs/strings"}}}},
20
+ "provenanceSources": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["sourceId", "itemIds", "kind", "locator"], "properties": {"sourceId": {"type": "string"}, "itemIds": {"$ref": "#/$defs/strings"}, "kind": {"type": "string"}, "locator": {"type": "object"}}}},
21
+ "coverage": {"type": "object", "additionalProperties": false, "required": ["profileItemId", "profileVersion", "registrationCoverage", "targets", "guarantee"], "properties": {"profileItemId": {"type": ["string", "null"]}, "profileVersion": {"enum": [null, 1, 2]}, "registrationCoverage": {"enum": ["not-declared", "review-required", "unresolved", "closed-for-declared-scope"]}, "targets": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["path", "disposition", "domainId"], "properties": {"path": {"type": "string"}, "disposition": {"enum": ["contract-covered", "dynamic-investigation", "excluded-approved", "unresolved", "outside-declared-coverage"]}, "domainId": {"type": ["string", "null"]}}}}, "guarantee": {"const": "declared-scope-only-never-all-project-truth"}}},
22
+ "authority": {"type": "object", "additionalProperties": false, "required": ["productTaskExecution", "productBusinessCodeWrites", "hostTaskAuthority", "resolveFrom"], "properties": {"productTaskExecution": {"const": false}, "productBusinessCodeWrites": {"const": false}, "hostTaskAuthority": {"const": "external-to-project-context"}, "resolveFrom": {"const": ["current-user-request", "human-authored-project-instructions", "higher-priority-safety-constraints"]}}},
23
+ "gaps": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["code"], "properties": {"code": {"enum": ["target-path-not-yet-known", "project-scope-only", "no-applicable-contract-item", "registration-coverage-not-declared", "registration-coverage-review-required", "required-read-target-unavailable"]}, "path": {"type": "string"}, "sourceIds": {"$ref": "#/$defs/strings"}, "reason": {"type": "string"}, "disposition": {"enum": ["unresolved"]}}}},
24
+ "nextActions": {"$ref": "#/$defs/strings"},
25
+ "content": {"type": "string", "minLength": 1},
26
+ "bundleDigest": {"$ref": "#/$defs/digest"}
27
+ },
28
+ "$defs": {
29
+ "digest": {"type": "string", "pattern": "^sha256:[a-f0-9]{64}$"},
30
+ "strings": {"type": "array", "uniqueItems": true, "items": {"type": "string"}}
31
+ }
32
+ }
@@ -1,25 +1,27 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "urn:frontend-project-context:schema:coverage-audit:1",
3
+ "$id": "urn:frontend-project-context:schema:coverage-audit:2",
4
4
  "title": "Frontend Project Context Coverage Audit",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
7
  "required": ["schemaVersion", "kind", "project", "snapshots", "profile", "registrationCoverage", "categories", "guarantee", "auditDigest"],
8
8
  "properties": {
9
- "schemaVersion": { "const": 1 },
9
+ "schemaVersion": { "const": 2 },
10
10
  "kind": { "const": "coverage-audit" },
11
11
  "project": { "type": "object", "additionalProperties": false, "required": ["id", "name"], "properties": { "id": { "type": "string" }, "name": { "type": "string" } } },
12
12
  "snapshots": { "$ref": "context-query.schema.json#/$defs/snapshots" },
13
13
  "profile": { "type": ["object", "null"] },
14
- "registrationCoverage": { "enum": ["not-declared", "review-required", "closed-for-declared-scope"] },
14
+ "registrationCoverage": { "enum": ["not-declared", "review-required", "unresolved", "closed-for-declared-scope"] },
15
15
  "categories": {
16
16
  "type": "object",
17
17
  "additionalProperties": false,
18
- "required": ["registered", "excluded-approved", "review-required", "outside-declared-coverage"],
18
+ "required": ["registered", "dynamic-investigation", "excluded-approved", "review-required", "unresolved", "outside-declared-coverage"],
19
19
  "properties": {
20
20
  "registered": { "$ref": "#/$defs/candidates" },
21
+ "dynamic-investigation": { "$ref": "#/$defs/candidates" },
21
22
  "excluded-approved": { "$ref": "#/$defs/candidates" },
22
23
  "review-required": { "$ref": "#/$defs/candidates" },
24
+ "unresolved": { "$ref": "#/$defs/candidates" },
23
25
  "outside-declared-coverage": { "$ref": "#/$defs/candidates" }
24
26
  }
25
27
  },
@@ -14,7 +14,7 @@
14
14
  "required": ["name", "version", "exchangeProtocolVersion"],
15
15
  "properties": {
16
16
  "name": { "const": "frontend-project-context" },
17
- "version": { "const": "1.8.0" },
17
+ "version": { "const": "1.9.1" },
18
18
  "exchangeProtocolVersion": { "const": 6 }
19
19
  }
20
20
  },
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "urn:frontend-project-context:schema:initialization-instruction:1",
3
+ "$id": "urn:frontend-project-context:schema:initialization-instruction:2",
4
4
  "title": "Frontend Project Context Initialization Instruction",
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
- "required": ["schemaVersion", "package", "instruction", "targetRoot", "boundaries"],
7
+ "required": ["schemaVersion", "package", "instruction", "targetRoot", "boundaries", "entryRuleReview"],
8
8
  "properties": {
9
- "schemaVersion": {"const": 1},
9
+ "schemaVersion": {"const": 2},
10
10
  "package": {
11
11
  "type": "object",
12
12
  "additionalProperties": false,
13
13
  "required": ["name", "version"],
14
14
  "properties": {
15
15
  "name": {"const": "frontend-project-context"},
16
- "version": {"const": "1.8.0"}
16
+ "version": {"const": "1.9.1"}
17
17
  }
18
18
  },
19
19
  "instruction": {
@@ -22,13 +22,25 @@
22
22
  "required": ["id", "version", "packagePath", "digest", "content"],
23
23
  "properties": {
24
24
  "id": {"const": "ai-project-initialization"},
25
- "version": {"const": 1},
25
+ "version": {"const": 2},
26
26
  "packagePath": {"const": "docs/AI-PROJECT-INITIALIZATION.md"},
27
27
  "digest": {"$ref": "#/$defs/digest"},
28
28
  "content": {"type": "string", "minLength": 1}
29
29
  }
30
30
  },
31
31
  "targetRoot": {"type": "string", "minLength": 1},
32
+ "entryRuleReview": {
33
+ "type": "object",
34
+ "additionalProperties": false,
35
+ "required": ["path", "present", "allowedDispositions", "records", "automaticDeletion"],
36
+ "properties": {
37
+ "path": {"const": "AGENTS.md"},
38
+ "present": {"type": "boolean"},
39
+ "allowedDispositions": {"const": ["migrated-to-contract", "kept-in-entry-intentionally", "excluded-as-stale", "unresolved-blocker"]},
40
+ "records": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["id", "disposition", "rationale"], "properties": {"id": {"type": "string"}, "disposition": {"enum": ["migrated-to-contract", "kept-in-entry-intentionally", "excluded-as-stale", "unresolved-blocker"]}, "rationale": {"type": "string"}}}},
41
+ "automaticDeletion": {"const": false}
42
+ }
43
+ },
32
44
  "boundaries": {
33
45
  "type": "object",
34
46
  "additionalProperties": false,
@@ -7,12 +7,12 @@
7
7
  "required": ["schemaVersion", "package", "upgradeFrom", "stores", "renderers", "protocols", "paths", "builtInMigrations", "consumerChanges", "acceptanceCommands", "rollback", "externalEffects", "manifestDigest"],
8
8
  "properties": {
9
9
  "schemaVersion": {"const": 2},
10
- "package": {"type": "object", "additionalProperties": false, "required": ["name", "version"], "properties": {"name": {"const": "frontend-project-context"}, "version": {"const": "1.8.0"}}},
11
- "upgradeFrom": {"const": ["1.3.1", "1.4.0", "1.5.0", "1.6.0", "1.7.0"]},
10
+ "package": {"type": "object", "additionalProperties": false, "required": ["name", "version"], "properties": {"name": {"const": "frontend-project-context"}, "version": {"const": "1.9.1"}}},
11
+ "upgradeFrom": {"const": ["1.3.1", "1.4.0", "1.5.0", "1.6.0", "1.7.0", "1.8.0"]},
12
12
  "stores": {"type": "object", "additionalProperties": false, "required": ["contract", "projectionLock", "proposal", "sourceLock"], "properties": {"contract": {"$ref": "#/$defs/versionMatrix"}, "projectionLock": {"$ref": "#/$defs/versionMatrix"}, "proposal": {"$ref": "#/$defs/versionMatrix"}, "sourceLock": {"$ref": "#/$defs/versionMatrix"}}},
13
13
  "renderers": {"type": "object", "additionalProperties": false, "required": ["aiEntry", "projection"], "properties": {"aiEntry": {"$ref": "#/$defs/versionTarget"}, "projection": {"$ref": "#/$defs/versionTarget"}}},
14
14
  "protocols": {"type": "object", "additionalProperties": {"$ref": "#/$defs/versionTarget"}},
15
- "paths": {"type": "array", "minItems": 5, "maxItems": 5, "items": {"type": "object", "additionalProperties": false, "required": ["fromVersion", "classification", "migrationIds", "requiresHumanReview", "rollbackClass", "acceptance"], "properties": {"fromVersion": {"enum": ["1.3.1", "1.4.0", "1.5.0", "1.6.0", "1.7.0"]}, "classification": {"enum": ["package-only", "republish-ai-entry", "republish-projection", "built-in-store-migration", "invalidate-ephemeral-protocol"]}, "migrationIds": {"$ref": "#/$defs/strings"}, "requiresHumanReview": {"type": "boolean"}, "rollbackClass": {"$ref": "#/$defs/rollbackClass"}, "acceptance": {"$ref": "#/$defs/strings"}}}},
15
+ "paths": {"type": "array", "minItems": 6, "maxItems": 6, "items": {"type": "object", "additionalProperties": false, "required": ["fromVersion", "classification", "migrationIds", "requiresHumanReview", "rollbackClass", "acceptance"], "properties": {"fromVersion": {"enum": ["1.3.1", "1.4.0", "1.5.0", "1.6.0", "1.7.0", "1.8.0"]}, "classification": {"enum": ["package-only", "republish-ai-entry", "republish-projection", "built-in-store-migration", "invalidate-ephemeral-protocol"]}, "migrationIds": {"$ref": "#/$defs/strings"}, "requiresHumanReview": {"type": "boolean"}, "rollbackClass": {"$ref": "#/$defs/rollbackClass"}, "acceptance": {"$ref": "#/$defs/strings"}}}},
16
16
  "builtInMigrations": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["id", "kind", "input", "targetType", "writes", "rollbackClass", "reverseMigrationId"], "properties": {"id": {"type": "string"}, "kind": {"enum": ["package-only", "republish-ai-entry", "republish-projection", "built-in-store-migration", "invalidate-ephemeral-protocol"]}, "input": {"type": "object", "additionalProperties": false, "required": ["schemaVersions", "beforeDigestRequired"], "properties": {"schemaVersions": {"type": "array", "items": {"type": "integer", "minimum": 1}}, "beforeDigestRequired": {"type": "boolean"}}}, "targetType": {"enum": ["none", "ai-entry", "projection", "store", "ephemeral-protocol"]}, "writes": {"type": "boolean"}, "rollbackClass": {"$ref": "#/$defs/rollbackClass"}, "reverseMigrationId": {"type": ["string", "null"]}}}},
17
17
  "consumerChanges": {"type": "object", "additionalProperties": {"type": "object", "additionalProperties": false, "required": ["state", "reason"], "properties": {"state": {"enum": ["preserve", "regenerate", "invalidate"]}, "reason": {"type": "string"}}}},
18
18
  "acceptanceCommands": {"$ref": "#/$defs/strings"},
@@ -9,7 +9,7 @@
9
9
  "schemaVersion": {"const": 1},
10
10
  "kind": {"const": "target-upgrade-plan"},
11
11
  "fromVersion": {"enum": ["1.3.1", "1.4.0", "1.5.0", "1.6.0", "1.7.0"]},
12
- "targetVersion": {"const": "1.8.0"},
12
+ "targetVersion": {"const": "1.9.1"},
13
13
  "manifestDigest": {"$ref": "#/$defs/digest"},
14
14
  "assessmentDigest": {"$ref": "#/$defs/digest"},
15
15
  "snapshots": {"anyOf": [{"type": "null"}, {"$ref": "#/$defs/snapshots"}]},
@@ -33,7 +33,7 @@
33
33
  "properties": {
34
34
  "path": { "type": "string" }, "target": { "const": "ai-entry" }, "ownership": { "const": "region" },
35
35
  "regionId": { "const": "project-context-ai-entry" }, "regionDigest": { "$ref": "#/$defs/digest" },
36
- "rendererVersion": { "enum": [1, 2, 3] }, "createdFile": { "type": "boolean" }
36
+ "rendererVersion": { "enum": [1, 2, 3, 4, 5] }, "createdFile": { "type": "boolean" }
37
37
  }
38
38
  },
39
39
  "schema1": {
@@ -10,7 +10,7 @@
10
10
  "kind": {"const": "upgrade-assessment"},
11
11
  "product": {"type": "object", "additionalProperties": false, "required": ["name"], "properties": {"name": {"const": "frontend-project-context"}}},
12
12
  "fromVersion": {"enum": ["1.3.1", "1.4.0", "1.5.0", "1.6.0", "1.7.0"]},
13
- "targetVersion": {"const": "1.8.0"},
13
+ "targetVersion": {"const": "1.9.1"},
14
14
  "fromVersionEvidence": {"const": "host-asserted"},
15
15
  "manifestSchemaVersion": {"const": 2},
16
16
  "manifestDigest": {"$ref": "#/$defs/digest"},
@@ -10,7 +10,7 @@
10
10
  "kind": {"const": "upgrade-result-bundle"},
11
11
  "product": {"type": "object", "additionalProperties": false, "required": ["name"], "properties": {"name": {"const": "frontend-project-context"}}},
12
12
  "fromVersion": {"type": "string"},
13
- "targetVersion": {"const": "1.8.0"},
13
+ "targetVersion": {"const": "1.9.1"},
14
14
  "manifestDigest": {"$ref": "#/$defs/digest"},
15
15
  "planDigest": {"$ref": "#/$defs/digest"},
16
16
  "mode": {"enum": ["preview", "write"]},
@@ -5,7 +5,7 @@ import { normalizeRelativePath } from "./path-policy.mjs";
5
5
  export const CONTEXT_QUERY_SCHEMA_VERSION = 2;
6
6
  export const ADAPTIVE_CONTEXT_BUNDLE_SCHEMA_VERSION = 2;
7
7
  export const ROUTING_INDEX_SCHEMA_VERSION = 2;
8
- export const COVERAGE_AUDIT_SCHEMA_VERSION = 1;
8
+ export const COVERAGE_AUDIT_SCHEMA_VERSION = 2;
9
9
  export const ADAPTIVE_SELECTOR_VERSION = 2;
10
10
  export const ROUTING_INDEX_PATH = ".project-context/derived/routing-index.json";
11
11
 
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { canonicalJson, digestJson, prettyCanonicalJson, sha256 } from "./canonical-json.mjs";
4
4
  import { blockingContextFindings, checkProject } from "./checker.mjs";
5
5
  import { sourceStatus } from "./contract-schema.mjs";
6
+ import { coverageClass, coverageProfile } from "./coverage-profile.mjs";
6
7
  import { discoverProject } from "./discovery.mjs";
7
8
  import { ProjectContextError, fail } from "./errors.mjs";
8
9
  import { atomicWriteFile, readJsonFile } from "./io.mjs";
@@ -26,7 +27,6 @@ import {
26
27
  const LOCAL_SOURCE_KINDS = new Set(["file", "path", "json-pointer"]);
27
28
  const MANDATORY_KINDS = new Set(["policy", "validation-description"]);
28
29
  const ROUTED_KINDS = new Set(["fact", "reference"]);
29
- const COVERAGE_SUBJECT = "project.registration-coverage";
30
30
  const RETRIEVAL_SUBJECT = "project.context-retrieval";
31
31
  const EXCLUDED_BODIES = Object.freeze(["chat-history", "git-diffs", "source-bodies", "task-path-bodies", "verification-logs"]);
32
32
  const ROUTING_STOP_TERMS = new Set(["a", "an", "and", "apply", "change", "for", "in", "of", "on", "project", "rule", "the", "to", "update", "use"]);
@@ -145,24 +145,7 @@ export async function indexContext(root, project, options = {}) {
145
145
  return index;
146
146
  }
147
147
 
148
- function pathWithin(target, parent) { return parent === "." || target === parent || target.startsWith(`${parent}/`); }
149
- function coverageProfile(contract) {
150
- const candidates = contract.items.filter((item) => item.status === "approved" && item.kind === "policy" && item.subject === COVERAGE_SUBJECT);
151
- if (candidates.length === 0) return null;
152
- if (candidates.length > 1) fail("coverage-profile-conflict", `multiple approved ${COVERAGE_SUBJECT} items are active`);
153
- const item = candidates[0];
154
- const value = item.value;
155
- if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["excludedPaths", "roots"].includes(key)) || !Array.isArray(value.roots) || !Array.isArray(value.excludedPaths)) fail("coverage-profile-invalid", `${item.id} coverage value is invalid`);
156
- const normalize = (values, label) => uniqueSorted(values.map((entry) => normalizeRelativePath(entry, { allowRoot: true, label })));
157
- return { itemId: item.id, itemDigest: digestJson(item), sourceIds: [...item.sources].sort(), roots: normalize(value.roots, "coverage root"), excludedPaths: normalize(value.excludedPaths, "coverage excluded path") };
158
- }
159
- function coverageClass(profile, registeredPaths, candidatePath) {
160
- if (registeredPaths.has(candidatePath)) return "registered";
161
- if (!profile) return "outside-declared-coverage";
162
- if (profile.excludedPaths.some((entry) => pathWithin(candidatePath, entry))) return "excluded-approved";
163
- if (profile.roots.some((entry) => pathWithin(candidatePath, entry))) return "review-required";
164
- return "outside-declared-coverage";
165
- }
148
+ export { coverageClass, coverageProfile } from "./coverage-profile.mjs";
166
149
  export async function buildCoverageAudit(root, project, changedPaths = []) {
167
150
  const profile = coverageProfile(project.contract);
168
151
  const proposal = await discoverProject(root, project.contract);
@@ -175,10 +158,16 @@ export async function buildCoverageAudit(root, project, changedPaths = []) {
175
158
  if (!byPath.has(normalized)) byPath.set(normalized, { path: normalized, origin: "host-changed-path-signal" });
176
159
  }
177
160
  for (const registeredPath of registered) if (!byPath.has(registeredPath)) byPath.set(registeredPath, { path: registeredPath, origin: "registered-source" });
178
- const categories = { registered: [], "excluded-approved": [], "review-required": [], "outside-declared-coverage": [] };
161
+ const categories = { registered: [], "dynamic-investigation": [], "excluded-approved": [], "review-required": [], unresolved: [], "outside-declared-coverage": [] };
179
162
  for (const candidate of [...byPath.values()].sort((left, right) => left.path.localeCompare(right.path))) categories[coverageClass(profile, registeredPaths, candidate.path)].push(candidate);
180
- const registrationCoverage = !profile ? "not-declared" : categories["review-required"].length === 0 ? "closed-for-declared-scope" : "review-required";
181
- return sealArtifact({ schemaVersion: 1, kind: "coverage-audit", project: { id: project.contract.project.id, name: project.contract.project.name }, snapshots: snapshots(project), profile, registrationCoverage, categories, guarantee: "declared-scope-only-never-all-project-truth" }, "auditDigest");
163
+ const registrationCoverage = !profile
164
+ ? "not-declared"
165
+ : categories.unresolved.length > 0
166
+ ? "unresolved"
167
+ : categories["review-required"].length === 0
168
+ ? "closed-for-declared-scope"
169
+ : "review-required";
170
+ return sealArtifact({ schemaVersion: 2, kind: "coverage-audit", project: { id: project.contract.project.id, name: project.contract.project.name }, snapshots: snapshots(project), profile, registrationCoverage, categories, guarantee: "declared-scope-only-never-all-project-truth" }, "auditDigest");
182
171
  }
183
172
 
184
173
  function effectiveByTarget(contract, paths, findings) {
@@ -331,7 +320,7 @@ async function auditRelevantSources(root, project, items, sourceReadContext) {
331
320
  const base = { id: source.id, kind: source.kind, locator: sourceLocator(source), sourceDigest: digestJson(source) };
332
321
  if (!LOCAL_SOURCE_KINDS.has(source.kind)) { evidence.push({ ...base, freshness: "unverifiable-non-local" }); continue; }
333
322
  try {
334
- const actual = await readSourceDigest(root, source, sourceReadContext);
323
+ const actual = await readSourceDigest(root, source, sourceReadContext, { projectionsLock: project.projectionsLock });
335
324
  const expected = lock.get(source.id) ?? null;
336
325
  const freshness = expected !== null && expected === source.digest && actual === expected ? "current" : "drifted";
337
326
  evidence.push({ ...base, freshness, expectedDigest: expected, actualDigest: actual });
@@ -6,14 +6,14 @@ import { fail } from "./errors.mjs";
6
6
  import { atomicWriteFile, atomicWriteJson, readJsonFile } from "./io.mjs";
7
7
  import { normalizeRelativePath, resolveWritableInside } from "./path-policy.mjs";
8
8
 
9
- export const AI_ENTRY_RENDERER_VERSION = 4;
9
+ export const AI_ENTRY_RENDERER_VERSION = 5;
10
10
  export const AI_ENTRY_REGION_ID = "project-context-ai-entry";
11
11
  export const AI_ENTRY_START = "<!-- project-context:ai-entry:start -->";
12
12
  export const AI_ENTRY_END = "<!-- project-context:ai-entry:end -->";
13
13
 
14
14
  const ENTRY_LINES = [
15
15
  AI_ENTRY_START,
16
- "<!-- project-context:ai-entry; schema-version: 1; renderer-version: 4 -->",
16
+ "<!-- project-context:ai-entry; schema-version: 1; renderer-version: 5 -->",
17
17
  "## Project Context 启动流程",
18
18
  "",
19
19
  "开始项目工作前,使用项目本地安装的 `frontend-project-context` CLI,不要临时下载其他版本。",
@@ -21,10 +21,11 @@ const ENTRY_LINES = [
21
21
  "1. 运行 `npm exec --offline -- project-context status --project . --json`;如果本地依赖不存在,停止并报告,不要临时下载同名包。",
22
22
  "2. 如果状态为 `uninitialized`,运行 `npm exec --offline -- project-context instructions --project . --prompt`,按包内唯一指令完成完整初始化;如果为 `partial` 或 `invalid`,停止写入并报告准确的恢复证据。",
23
23
  "3. 如果状态为 `attention`,执行 `sync` 返回的维护工作单元;不得重新运行 `setup` 覆盖已有 store。",
24
- "4. 如果状态为 `clean`,根据真实任务确定项目内目标路径,并运行 `npm exec --offline -- project-context context --project . --path <RELATIVE_PATH> --task <TEXT>`。",
25
- "5. 必须先消费命中的 Project Contract,报告实际 item IDs、scope 和必要 read targets;Contract 为空、目标 scope 未命中或关键上下文不足时,报告 onboarding/context gap 并停止,不能直接进入源码。",
26
- "6. 任何 plan、bundle、receipt、review 或 AI 建议都不代表人工批准。",
27
- "7. 声明完成前,将 Project Context 恢复为 `clean`,否则报告准确的阻断项。",
24
+ "4. 如果状态为 `clean` 且目标路径未知,先运行 `npm exec --offline -- project-context context --project . --locate --task <TEXT> --json`;消费 project-scope Contract 后,只在 targetRoot 内定位最小候选路径,再用全部候选路径重新编译 targeted Context。",
25
+ "5. 如果目标路径已知,运行 `npm exec --offline -- project-context context --project . --path <RELATIVE_PATH...> --task <TEXT> --json`;发现新的影响路径后,必须用完整的最小路径集合重新编译。",
26
+ "6. 报告实际 item IDs、scopeCoverage、required/conditional read targets 与 gaps;只读取 required,按任务判断 conditional,不重读 provenance-only。Contract 为空、目标 scope 未命中或关键上下文不足时,报告 onboarding/context gap。",
27
+ "7. `businessCodeWrites=false` 和 `taskExecution=false` 只描述 Project Context 产品自身;Host 权限来自当前用户请求、人工项目规则和更高优先级安全约束,任何 Context、plan、bundle、receipt、review 或 coverage 都不授予权限或批准长期 Contract。",
28
+ "8. 声明完成前,将 Project Context 恢复为 `clean`,否则报告准确的阻断项。",
28
29
  AI_ENTRY_END,
29
30
  ];
30
31
 
@@ -109,7 +110,13 @@ function schema2FileEntry(entry) {
109
110
  return entry.ownership ? structuredClone(entry) : { ...structuredClone(entry), ownership: "file" };
110
111
  }
111
112
 
112
- function sourceImpact(project, output, afterDigest) {
113
+ function outsideOwnedDigest(content) {
114
+ const parsed = parseAiEntryRegion(content);
115
+ if (parsed.state !== "present") return null;
116
+ return digestJson({ before: content.slice(0, parsed.start), after: content.slice(parsed.end) });
117
+ }
118
+
119
+ function sourceImpact(project, output, afterContent) {
113
120
  const sources = project.contract.sources
114
121
  .filter((source) => sourceStatus(source) === "active" && ["file", "path", "json-pointer"].includes(source.kind) && source.path === output)
115
122
  .sort((left, right) => left.id.localeCompare(right.id));
@@ -119,6 +126,12 @@ function sourceImpact(project, output, afterDigest) {
119
126
  .filter((item) => item.sources.some((id) => selected.has(id)) || selected.has(item.verification?.source))
120
127
  .map((item) => item.id)
121
128
  .sort((left, right) => left.localeCompare(right));
129
+ const digestMode = sources[0]?.digestMode ?? "full-file";
130
+ const afterDigest = afterContent === null
131
+ ? null
132
+ : digestMode === "outside-owned-ai-entry"
133
+ ? outsideOwnedDigest(afterContent)
134
+ : sha256(afterContent);
122
135
  return { sourceIds, itemIds, afterDigest: sourceIds.length > 0 ? afterDigest : null };
123
136
  }
124
137
 
@@ -249,7 +262,7 @@ export async function publishAiEntry(root, project, options, dependencies = {})
249
262
  };
250
263
  const nextLock = nextProjectionLock(project, resolved.normalized, nextEntry);
251
264
  const action = currentEntry && existing === nextContent && canonicalJson(currentEntry) === canonicalJson(nextEntry) ? "unchanged" : currentEntry ? "update" : "create";
252
- const impact = sourceImpact(project, resolved.normalized, sha256(nextContent));
265
+ const impact = sourceImpact(project, resolved.normalized, nextContent);
253
266
  const result = {
254
267
  action,
255
268
  current: {
@@ -304,7 +317,7 @@ export async function removeAiEntry(root, project, options, dependencies = {}) {
304
317
  }
305
318
  const nextContent = `${existing.slice(0, parsed.start)}${existing.slice(parsed.end)}`;
306
319
  const nextLock = nextProjectionLock(project, resolved.normalized, null);
307
- const impact = sourceImpact(project, resolved.normalized, sha256(nextContent));
320
+ const impact = sourceImpact(project, resolved.normalized, nextContent);
308
321
  const result = {
309
322
  action: "remove",
310
323
  current: { entry: structuredClone(currentEntry), contentDigest: sha256(existing), region: parsed.region },
@@ -1,5 +1,6 @@
1
1
  import { canonicalJson } from "./canonical-json.mjs";
2
2
  import {
3
+ promoteContractToSchema3,
3
4
  sourceForContract,
4
5
  sourceRegistrationShape,
5
6
  sourceStatus,
@@ -52,7 +53,7 @@ export async function approveProposal(root, project, proposalInput, options) {
52
53
  for (const sourceId of selectedSourceIds) {
53
54
  const source = proposalSources.get(sourceId);
54
55
  if (!source) fail("proposal-source-missing", `proposal source is missing: ${sourceId}`);
55
- const actual = await readSourceDigest(root, source);
56
+ const actual = await readSourceDigest(root, source, undefined, { projectionsLock: project.projectionsLock });
56
57
  if (actual !== null && actual !== source.digest) {
57
58
  fail("proposal-source-changed", `source changed after discovery: ${sourceId}`, {
58
59
  exitCode: 1,
@@ -61,6 +62,9 @@ export async function approveProposal(root, project, proposalInput, options) {
61
62
  }
62
63
  }
63
64
  const nextContract = structuredClone(project.contract);
65
+ if (selected.some((item) => item.consumption !== undefined) || [...selectedSourceIds].some((id) => proposalSources.get(id)?.digestMode !== undefined)) {
66
+ promoteContractToSchema3(nextContract);
67
+ }
64
68
  for (const sourceId of selectedSourceIds) {
65
69
  const source = proposalSources.get(sourceId);
66
70
  const existing = nextContract.sources.find((entry) => entry.id === sourceId);
@@ -141,7 +145,7 @@ export async function approvePendingItems(root, project, options) {
141
145
  }
142
146
  let actual;
143
147
  try {
144
- actual = await readSourceDigest(root, source);
148
+ actual = await readSourceDigest(root, source, undefined, { projectionsLock: project.projectionsLock });
145
149
  } catch (error) {
146
150
  fail("pending-source-changed", `pending item source is unavailable: ${sourceId}`, {
147
151
  exitCode: 1,