frontend-project-context 1.6.0 → 1.8.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 (60) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +101 -48
  3. package/UPGRADING.md +30 -1
  4. package/docs/05-ACCEPTANCE-CONTRACT.md +20 -1
  5. package/docs/08-INSTALLATION-AND-DISTRIBUTION.md +62 -22
  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/27-TEAM-SHARED-CONTEXT-DIRECTION-DISCUSSION.md +30 -0
  15. package/docs/28-REAL-PROJECT-ONBOARDING-CLOSURE-DESIGN.md +534 -0
  16. package/docs/AI-PROJECT-INITIALIZATION.md +89 -0
  17. package/docs/README.md +34 -6
  18. package/docs/USER-AND-AI-OPERATION-MANUAL.md +87 -40
  19. package/examples/README.md +6 -6
  20. package/examples/package.json +1 -1
  21. package/migration-manifest.json +42 -8
  22. package/package.json +2 -2
  23. package/schemas/adaptive-context-bundle.schema.json +70 -0
  24. package/schemas/capabilities.schema.json +37 -8
  25. package/schemas/context-query.schema.json +69 -0
  26. package/schemas/coverage-audit.schema.json +32 -0
  27. package/schemas/evidence-bundle.schema.json +2 -2
  28. package/schemas/host-promotion-evidence.schema.json +33 -0
  29. package/schemas/initialization-instruction.schema.json +60 -0
  30. package/schemas/migration-manifest.schema.json +3 -3
  31. package/schemas/migration-plan.schema.json +2 -2
  32. package/schemas/project-status.schema.json +5 -4
  33. package/schemas/projection-lock.schema.json +1 -1
  34. package/schemas/routing-index.schema.json +58 -0
  35. package/schemas/truth-reconciliation-input.schema.json +60 -0
  36. package/schemas/truth-reconciliation-review-bundle.schema.json +155 -0
  37. package/schemas/upgrade-assessment.schema.json +2 -2
  38. package/schemas/upgrade-result-bundle.schema.json +1 -1
  39. package/src/project-context/a130-evaluation.mjs +91 -0
  40. package/src/project-context/adaptive-context-schema.mjs +392 -0
  41. package/src/project-context/adaptive-context.mjs +547 -0
  42. package/src/project-context/ai-entry.mjs +11 -9
  43. package/src/project-context/assist.mjs +4 -2
  44. package/src/project-context/capabilities.mjs +32 -0
  45. package/src/project-context/checker.mjs +4 -3
  46. package/src/project-context/cli.mjs +55 -5
  47. package/src/project-context/contract-schema.mjs +1 -1
  48. package/src/project-context/discovery.mjs +7 -7
  49. package/src/project-context/exchange-schema.mjs +6 -5
  50. package/src/project-context/initialization-instruction.mjs +42 -0
  51. package/src/project-context/maintenance.mjs +2 -2
  52. package/src/project-context/migration-manifest.mjs +7 -5
  53. package/src/project-context/project-status.mjs +14 -3
  54. package/src/project-context/project-store.mjs +27 -2
  55. package/src/project-context/renderer.mjs +75 -1
  56. package/src/project-context/source-reader.mjs +63 -30
  57. package/src/project-context/task-context.mjs +14 -2
  58. package/src/project-context/truth-reconciliation-schema.mjs +488 -0
  59. package/src/project-context/truth-reconciliation.mjs +543 -0
  60. package/src/project-context/upgrade-schema.mjs +5 -1
@@ -26,17 +26,38 @@ 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";
40
+ import {
41
+ INITIALIZATION_INSTRUCTION_COMMAND,
42
+ INITIALIZATION_INSTRUCTION_SCHEMA_VERSION,
43
+ readInitializationInstruction,
44
+ } from "./initialization-instruction.mjs";
29
45
 
30
46
  function schemas(projectionLockWritten = 1) {
31
47
  return {
32
48
  actionPlan: ACTION_PLAN_SCHEMA_VERSION,
49
+ adaptiveContextBundle: ADAPTIVE_CONTEXT_BUNDLE_SCHEMA_VERSION,
33
50
  assistBundle: ASSIST_BUNDLE_SCHEMA_VERSION,
34
51
  capabilities: CAPABILITIES_SCHEMA_VERSION,
35
52
  contract: 2,
53
+ contextQuery: CONTEXT_QUERY_SCHEMA_VERSION,
54
+ coverageAudit: COVERAGE_AUDIT_SCHEMA_VERSION,
36
55
  dashboardViewModel: DASHBOARD_SCHEMA_VERSION,
37
56
  evidenceBundle: EVIDENCE_BUNDLE_SCHEMA_VERSION,
38
57
  evidenceInput: EVIDENCE_INPUT_SCHEMA_VERSION,
39
58
  integrationReviewBundle: INTEGRATION_REVIEW_BUNDLE_SCHEMA_VERSION,
59
+ initializationInstruction: INITIALIZATION_INSTRUCTION_SCHEMA_VERSION,
60
+ hostPromotionEvidence: HOST_PROMOTION_EVIDENCE_SCHEMA_VERSION,
40
61
  projectionLock: 2,
41
62
  projectionLockReadable: [1, 2],
42
63
  projectionLockWritten,
@@ -47,10 +68,13 @@ function schemas(projectionLockWritten = 1) {
47
68
  migrationManifest: 2,
48
69
  migrationPlan: MIGRATION_PLAN_SCHEMA_VERSION,
49
70
  reviewBundle: REVIEW_BUNDLE_SCHEMA_VERSION,
71
+ routingIndex: ROUTING_INDEX_SCHEMA_VERSION,
50
72
  sourceLock: 1,
51
73
  stageContextBundle: STAGE_CONTEXT_BUNDLE_SCHEMA_VERSION,
52
74
  stageReceipt: STAGE_RECEIPT_SCHEMA_VERSION,
53
75
  taskContextPlan: TASK_CONTEXT_PLAN_SCHEMA_VERSION,
76
+ truthReconciliationInput: TRUTH_RECONCILIATION_INPUT_SCHEMA_VERSION,
77
+ truthReconciliationReviewBundle: TRUTH_RECONCILIATION_REVIEW_BUNDLE_SCHEMA_VERSION,
54
78
  upgradeAssessment: UPGRADE_ASSESSMENT_SCHEMA_VERSION,
55
79
  upgradeResultBundle: UPGRADE_RESULT_BUNDLE_SCHEMA_VERSION,
56
80
  };
@@ -78,6 +102,7 @@ export const PERMANENT_BOUNDARIES = Object.freeze({
78
102
 
79
103
  export async function buildCapabilities(root) {
80
104
  const initialization = await inspectProjectInitialization(root);
105
+ const instruction = await readInitializationInstruction();
81
106
  let project = null;
82
107
  let projectionLockWritten = 1;
83
108
  if (initialization.status === "initialized") {
@@ -93,6 +118,13 @@ export async function buildCapabilities(root) {
93
118
  commands: [...COMMANDS],
94
119
  actionKinds: [...ACTION_KINDS],
95
120
  contextBudget: { unit: CONTEXT_BUDGET_UNIT, modelTokens: false, callerMustProvideLimit: true },
121
+ initializationInstruction: {
122
+ id: instruction.id,
123
+ version: instruction.version,
124
+ packagePath: instruction.packagePath,
125
+ digest: instruction.digest,
126
+ command: INITIALIZATION_INSTRUCTION_COMMAND,
127
+ },
96
128
  initialization: initialization.status,
97
129
  initialized: initialization.status === "initialized",
98
130
  project,
@@ -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,12 +23,15 @@ 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";
27
+ import { buildInitializationInstruction, renderInitializationPrompt } from "./initialization-instruction.mjs";
25
28
 
26
29
  const HELP = `project-context — model-neutral project contract compiler
27
30
 
28
31
  Usage:
29
32
  project-context init --project PATH --id ID --name NAME [--write] [--json]
30
33
  project-context capabilities --project PATH [--json]
34
+ project-context instructions --project PATH [--json | --prompt]
31
35
  project-context status --project PATH [--json]
32
36
  project-context evidence --project PATH --input FILE [--json]
33
37
  project-context upgrade-check --project PATH --from-version VERSION [--json]
@@ -44,6 +48,9 @@ Usage:
44
48
  project-context discover --project PATH [--output FILE --write] [--json]
45
49
  project-context approve --project PATH (--proposal FILE | --pending) --ids ID... --by NAME [--rationale TEXT] [--write] [--json | --full-json]
46
50
  project-context context --project PATH --path RELATIVE_PATH... [--task TEXT] [--locale zh-CN|en|all] [--json]
51
+ project-context context-query --project PATH --input FILE [--previous FILE] [--json | --prompt]
52
+ project-context coverage-audit --project PATH [--changed-path RELATIVE_PATH...] [--json]
53
+ project-context index-context --project PATH [--write] [--json]
47
54
  project-context publish --project PATH --target agents|ruler --output FILE [--path RELATIVE_PATH...] [--write] [--json]
48
55
  project-context publish-entry --project PATH --output AGENTS.md [--write] [--json]
49
56
  project-context remove-entry --project PATH --output AGENTS.md [--write] [--json]
@@ -53,22 +60,24 @@ Usage:
53
60
  project-context preflight --project PATH --plan FILE [--json]
54
61
  project-context stage-context --project PATH --plan FILE --stage STAGE_ID [--receipt FILE...] [--receipt-bundle FILE...] [--changed-path RELATIVE_PATH...] [--json]
55
62
  project-context integration-review --project PATH --plan FILE [--receipt FILE...] [--receipt-bundle FILE...] [--main-changed-path RELATIVE_PATH...] [--branch-changed-path RELATIVE_PATH...] [--json]
63
+ project-context reconcile-truth --project PATH --input FILE... [--previous-review FILE] [--json]
56
64
 
57
65
  All commands are read-only unless their own --write flag is present.
58
66
  `;
59
67
  const VALUE_FLAGS = new Set([
60
- "project", "id", "name", "input", "output", "proposal", "by", "task", "target", "rationale",
68
+ "project", "id", "name", "output", "proposal", "by", "task", "target", "rationale",
61
69
  "kind", "pointer", "reference", "subject", "value", "value-json", "statement", "scope", "scope-path",
62
70
  "verification", "verification-source", "verification-expected-json",
63
- "expected-digest", "expected-item-digest", "expected-source-digest", "locale", "plan", "stage", "from-version", "assessment",
71
+ "expected-digest", "expected-item-digest", "expected-source-digest", "locale", "plan", "stage", "from-version", "assessment", "previous", "previous-review",
64
72
  ]);
65
73
  const LIST_FLAGS = new Set([
66
- "ids", "path", "changed-path", "sources", "overrides", "affected-items", "receipt", "receipt-bundle", "main-changed-path", "branch-changed-path",
74
+ "ids", "path", "input", "changed-path", "sources", "overrides", "affected-items", "receipt", "receipt-bundle", "main-changed-path", "branch-changed-path",
67
75
  ]);
68
- const BOOLEAN_FLAGS = new Set(["write", "json", "full-json", "help", "pending"]);
76
+ const BOOLEAN_FLAGS = new Set(["write", "json", "full-json", "help", "pending", "prompt"]);
69
77
  const COMMAND_OPTIONS = new Map([
70
78
  ["init", new Set(["project", "id", "name", "write", "json", "help"])],
71
79
  ["capabilities", new Set(["project", "json", "help"])],
80
+ ["instructions", new Set(["project", "json", "prompt", "help"])],
72
81
  ["status", new Set(["project", "json", "help"])],
73
82
  ["evidence", new Set(["project", "input", "json", "help"])],
74
83
  ["upgrade-check", new Set(["project", "from-version", "json", "help"])],
@@ -98,6 +107,9 @@ const COMMAND_OPTIONS = new Map([
98
107
  ["discover", new Set(["project", "output", "write", "json", "help"])],
99
108
  ["approve", new Set(["project", "proposal", "pending", "ids", "by", "rationale", "write", "json", "full-json", "help"])],
100
109
  ["context", new Set(["project", "path", "task", "locale", "json", "help"])],
110
+ ["context-query", new Set(["project", "input", "previous", "json", "prompt", "help"])],
111
+ ["coverage-audit", new Set(["project", "changed-path", "json", "help"])],
112
+ ["index-context", new Set(["project", "write", "json", "help"])],
101
113
  ["publish", new Set(["project", "target", "output", "path", "write", "json", "help"])],
102
114
  ["publish-entry", new Set(["project", "output", "write", "json", "help"])],
103
115
  ["remove-entry", new Set(["project", "output", "write", "json", "help"])],
@@ -107,6 +119,7 @@ const COMMAND_OPTIONS = new Map([
107
119
  ["preflight", new Set(["project", "plan", "json", "help"])],
108
120
  ["stage-context", new Set(["project", "plan", "stage", "receipt", "receipt-bundle", "changed-path", "json", "help"])],
109
121
  ["integration-review", new Set(["project", "plan", "receipt", "receipt-bundle", "main-changed-path", "branch-changed-path", "json", "help"])],
122
+ ["reconcile-truth", new Set(["project", "input", "previous-review", "json", "help"])],
110
123
  ]);
111
124
 
112
125
  export function parseArgs(argv) {
@@ -332,6 +345,18 @@ async function runCommand(command, options) {
332
345
  if (!COMMAND_OPTIONS.has(command)) fail("command-unknown", `unknown command: ${command}`);
333
346
  rejectUnsupportedOptions(command, options, COMMAND_OPTIONS.get(command));
334
347
  const root = await resolveProjectRoot(required(options, "project"));
348
+ if (command === "instructions") {
349
+ if (options.json && options.prompt) fail("argument-conflict", "instructions accepts only one of --json or --prompt");
350
+ const instruction = await buildInitializationInstruction(root, PERMANENT_BOUNDARIES);
351
+ if (options.prompt) return { exitCode: 0, stdout: renderInitializationPrompt(instruction), stderr: "" };
352
+ const summary = [
353
+ `Initialization instruction ${instruction.instruction.id} v${instruction.instruction.version}.`,
354
+ `Package: ${instruction.package.name}@${instruction.package.version}.`,
355
+ `Target root: ${instruction.targetRoot}.`,
356
+ `Instruction: ${instruction.instruction.packagePath}; ${instruction.instruction.digest}.`,
357
+ ].join("\n") + "\n";
358
+ return { exitCode: 0, stdout: jsonOrText(options, instruction, summary), stderr: "" };
359
+ }
335
360
  if (command === "capabilities") {
336
361
  const capabilities = await buildCapabilities(root);
337
362
  const summary = [
@@ -348,7 +373,7 @@ async function runCommand(command, options) {
348
373
  return { exitCode: result.exitCode, stdout: jsonOrText(options, result.status, summary), stderr: "" };
349
374
  }
350
375
  if (command === "evidence") {
351
- const bundle = await buildEvidenceBundleFile(root, required(options, "input"));
376
+ const bundle = await buildEvidenceBundleFile(root, oneListValue(options, "input"));
352
377
  const summary = [
353
378
  `Evidence ${bundle.result}; project health ${bundle.projectContext.health}.`,
354
379
  `Finding codes: ${bundle.projectContext.findingCodes.join(", ") || "none"}.`,
@@ -411,6 +436,26 @@ async function runCommand(command, options) {
411
436
  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
437
  return { exitCode: bundle.status === "ready" ? 0 : 1, stdout: jsonOrText(options, bundle, summary), stderr: "" };
413
438
  }
439
+ if (command === "context-query") {
440
+ if (options.json && options.prompt) fail("argument-conflict", "context-query accepts only one of --json or --prompt");
441
+ if (options.prompt) {
442
+ const result = await buildAdaptiveContextDeliveryFiles(root, oneListValue(options, "input"), { previousPath: options.previous });
443
+ return { exitCode: result.bundle.taskHealth === "ready" ? 0 : 1, stdout: result.content, stderr: "" };
444
+ }
445
+ const bundle = await buildAdaptiveContextBundleFiles(root, oneListValue(options, "input"), { previousPath: options.previous });
446
+ 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`;
447
+ return { exitCode: bundle.taskHealth === "ready" ? 0 : 1, stdout: jsonOrText(options, bundle, summary), stderr: "" };
448
+ }
449
+ if (command === "coverage-audit") {
450
+ const audit = await buildCoverageAuditFiles(root, options["changed-path"] ?? []);
451
+ const summary = `Registration coverage ${audit.registrationCoverage}: ${audit.categories["review-required"].length} review-required candidate(s).\n`;
452
+ return { exitCode: audit.registrationCoverage === "review-required" ? 1 : 0, stdout: jsonOrText(options, audit, summary), stderr: "" };
453
+ }
454
+ if (command === "index-context") {
455
+ const index = await indexContextFiles(root, { write: options.write });
456
+ const summary = `Routing index ${options.write ? "written" : "preview"}: ${index.items.length} item(s), ${index.sources.length} source(s); ${index.indexDigest}.\n`;
457
+ return { exitCode: 0, stdout: jsonOrText(options, index, summary), stderr: "" };
458
+ }
414
459
  if (command === "integration-review") {
415
460
  const bundle = await buildIntegrationReviewBundleFiles(root, required(options, "plan"), {
416
461
  receiptPaths: options.receipt ?? [],
@@ -421,6 +466,11 @@ async function runCommand(command, options) {
421
466
  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
467
  return { exitCode: bundle.status === "reviewable" ? 0 : 1, stdout: jsonOrText(options, bundle, summary), stderr: "" };
423
468
  }
469
+ if (command === "reconcile-truth") {
470
+ const bundle = await buildTruthReconciliationReviewFiles(root, required(options, "input"), { previousReviewPath: options["previous-review"] });
471
+ const summary = `Truth reconciliation ${bundle.summary.taskHealth}: ${bundle.summary.collisionGroups} collision group(s), ${bundle.summary.findings} finding(s); existing delivery ${bundle.summary.existingDelivery}.\n`;
472
+ return { exitCode: bundle.summary.taskHealth === "ready" ? 0 : 1, stdout: jsonOrText(options, bundle, summary), stderr: "" };
473
+ }
424
474
  const project = await loadProject(root);
425
475
  if (command === "register") {
426
476
  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, 4].includes(entry.rendererVersion)) fail("schema-version-unsupported", "AI Entry rendererVersion must be 1, 2, 3, or 4");
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.8.0";
6
+ export const EXCHANGE_PROTOCOL_VERSION = 8;
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 = 8;
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", "instructions", "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);
@@ -0,0 +1,42 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { fileURLToPath } from "node:url";
3
+ import { sha256 } from "./canonical-json.mjs";
4
+ import { PACKAGE_VERSION } from "./exchange-schema.mjs";
5
+
6
+ export const INITIALIZATION_INSTRUCTION_SCHEMA_VERSION = 1;
7
+ export const INITIALIZATION_INSTRUCTION_ID = "ai-project-initialization";
8
+ export const INITIALIZATION_INSTRUCTION_VERSION = 1;
9
+ export const INITIALIZATION_INSTRUCTION_PACKAGE_PATH = "docs/AI-PROJECT-INITIALIZATION.md";
10
+ export const INITIALIZATION_INSTRUCTION_COMMAND = "project-context instructions --project PATH [--json | --prompt]";
11
+
12
+ const INSTRUCTION_FILE = fileURLToPath(new URL(`../../${INITIALIZATION_INSTRUCTION_PACKAGE_PATH}`, import.meta.url));
13
+
14
+ export async function readInitializationInstruction() {
15
+ const content = await readFile(INSTRUCTION_FILE, "utf8");
16
+ return {
17
+ id: INITIALIZATION_INSTRUCTION_ID,
18
+ version: INITIALIZATION_INSTRUCTION_VERSION,
19
+ packagePath: INITIALIZATION_INSTRUCTION_PACKAGE_PATH,
20
+ digest: sha256(content),
21
+ content,
22
+ };
23
+ }
24
+
25
+ export async function buildInitializationInstruction(targetRoot, boundaries) {
26
+ return {
27
+ schemaVersion: INITIALIZATION_INSTRUCTION_SCHEMA_VERSION,
28
+ package: { name: "frontend-project-context", version: PACKAGE_VERSION },
29
+ instruction: await readInitializationInstruction(),
30
+ targetRoot,
31
+ boundaries: { ...boundaries },
32
+ };
33
+ }
34
+
35
+ export function renderInitializationPrompt(result) {
36
+ return [
37
+ `<!-- frontend-project-context initialization instruction ${result.instruction.version}; ${result.instruction.digest} -->`,
38
+ `<!-- resolved-target-root: ${result.targetRoot} -->`,
39
+ result.instruction.content.trimEnd(),
40
+ "",
41
+ ].join("\n");
42
+ }
@@ -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", "initializationInstruction", "projectStatus",
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.8.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", "initializationInstruction", "projectStatus",
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");
@@ -4,8 +4,9 @@ import { checkExitCode, checkProject } from "./checker.mjs";
4
4
  import { ProjectContextError } from "./errors.mjs";
5
5
  import { PACKAGE_VERSION } from "./exchange-schema.mjs";
6
6
  import { inspectProjectInitialization, loadProject } from "./project-store.mjs";
7
+ import { sourceStatus } from "./contract-schema.mjs";
7
8
 
8
- export const PROJECT_STATUS_SCHEMA_VERSION = 1;
9
+ export const PROJECT_STATUS_SCHEMA_VERSION = 2;
9
10
 
10
11
  function uniqueSorted(values) {
11
12
  return [...new Set(values.filter((value) => value !== undefined && value !== null))]
@@ -46,6 +47,7 @@ function base(initialization, health, nextActions) {
46
47
  snapshots: null,
47
48
  health,
48
49
  entry: { state: "absent", path: null, rendererVersion: null },
50
+ contractReadiness: initialization === "uninitialized" ? "not-initialized" : "contract-incomplete",
49
51
  summary: emptySummary(),
50
52
  findingCodes: [],
51
53
  sourceIds: [],
@@ -65,7 +67,7 @@ function statusBoundaries(boundaries) {
65
67
  export async function buildProjectStatus(root, boundaries) {
66
68
  const initialization = await inspectProjectInitialization(root);
67
69
  if (initialization.status !== "initialized") {
68
- const result = base(initialization.status, initialization.status, initialization.status === "uninitialized" ? ["run-setup-preview"] : ["resolve-conflict"]);
70
+ const result = base(initialization.status, initialization.status, initialization.status === "uninitialized" ? ["read-initialization-instructions"] : ["resolve-conflict"]);
69
71
  result.initialization.present = [...initialization.present];
70
72
  result.initialization.missing = [...initialization.missing];
71
73
  if (initialization.status === "partial") result.findingCodes = ["project-state-partial"];
@@ -112,7 +114,11 @@ export async function buildProjectStatus(root, boundaries) {
112
114
  if (sync.summary.affectedProjections > 0) nextActions.push("review-projection");
113
115
  if (entry.state !== "current") nextActions.push("publish-ai-entry");
114
116
  if (findings.length > 0 && nextActions.length === 0) nextActions.push("run-sync");
115
- if (health === "clean") nextActions.push("ready-for-task");
117
+ if (health === "clean") {
118
+ const hasActiveSource = project.contract.sources.some((source) => sourceStatus(source) === "active");
119
+ const hasApprovedItem = project.contract.items.some((item) => item.status === "approved");
120
+ nextActions.push(hasActiveSource && hasApprovedItem && entry.state === "current" ? "compile-task-context" : "complete-onboarding");
121
+ }
116
122
  }
117
123
  const sourceIds = uniqueSorted([
118
124
  ...findings.flatMap((finding) => finding.source ? [finding.source] : []),
@@ -138,6 +144,11 @@ export async function buildProjectStatus(root, boundaries) {
138
144
  },
139
145
  health,
140
146
  entry,
147
+ contractReadiness: health === "clean" && entry.state === "current" &&
148
+ project.contract.sources.some((source) => sourceStatus(source) === "active") &&
149
+ project.contract.items.some((item) => item.status === "approved")
150
+ ? "contract-ready"
151
+ : "contract-incomplete",
141
152
  summary: {
142
153
  findings: findings.length,
143
154
  changedSources: sync.summary.changedSources,
@@ -1,4 +1,4 @@
1
- import { access, mkdir, rename, rm } from "node:fs/promises";
1
+ import { access, lstat, mkdir, readdir, rename, rm, rmdir } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { randomBytes } from "node:crypto";
4
4
  import { canonicalJson, digestJson } from "./canonical-json.mjs";
@@ -43,8 +43,20 @@ async function exists(filePath) {
43
43
  }
44
44
  }
45
45
 
46
+ async function isEmptyDirectory(filePath) {
47
+ try {
48
+ const stats = await lstat(filePath);
49
+ if (!stats.isDirectory()) return false;
50
+ return (await readdir(filePath)).length === 0;
51
+ } catch (error) {
52
+ if (error?.code === "ENOENT") return false;
53
+ throw error;
54
+ }
55
+ }
56
+
46
57
  export async function inspectProjectInitialization(root) {
47
58
  const files = projectFiles(root);
59
+ const emptyContainer = await isEmptyDirectory(files.directory);
48
60
  const entries = [
49
61
  [CONTEXT_DIRECTORY, files.directory],
50
62
  [`${CONTEXT_DIRECTORY}/${CONTRACT_FILE}`, files.contract],
@@ -53,6 +65,7 @@ export async function inspectProjectInitialization(root) {
53
65
  ];
54
66
  const present = [];
55
67
  for (const [relative, absolute] of entries) {
68
+ if (relative === CONTEXT_DIRECTORY && emptyContainer) continue;
56
69
  if (await exists(absolute)) present.push(relative);
57
70
  }
58
71
  const storePaths = entries.slice(1).map(([relative]) => relative);
@@ -71,8 +84,9 @@ export async function initializeProject(root, id, name, write) {
71
84
  validateContract(initial.contract);
72
85
  validateSourceLock(initial.sourcesLock);
73
86
  validateProjectionLock(initial.projectionsLock);
87
+ const emptyContainer = await isEmptyDirectory(files.directory);
74
88
  const existing = [];
75
- if (await exists(files.directory)) existing.push(CONTEXT_DIRECTORY);
89
+ if ((await exists(files.directory)) && !emptyContainer) existing.push(CONTEXT_DIRECTORY);
76
90
  for (const filePath of [files.contract, files.sourcesLock, files.projectionsLock]) {
77
91
  if (await exists(filePath)) existing.push(path.relative(root, filePath));
78
92
  }
@@ -80,6 +94,17 @@ export async function initializeProject(root, id, name, write) {
80
94
  fail("project-already-initialized", "project context files already exist", { details: { paths: existing } });
81
95
  }
82
96
  if (write) {
97
+ if (emptyContainer) {
98
+ try {
99
+ await rmdir(files.directory);
100
+ } catch (error) {
101
+ if (error?.code !== "ENOENT") {
102
+ fail("project-already-initialized", "project context directory changed before initialization", {
103
+ details: { paths: [CONTEXT_DIRECTORY] },
104
+ });
105
+ }
106
+ }
107
+ }
83
108
  const temporaryDirectory = `${files.directory}.tmp-${process.pid}-${randomBytes(6).toString("hex")}`;
84
109
  try {
85
110
  await mkdir(temporaryDirectory, { recursive: false });