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.
- package/CHANGELOG.md +31 -0
- package/README.md +101 -48
- package/UPGRADING.md +30 -1
- package/docs/05-ACCEPTANCE-CONTRACT.md +20 -1
- package/docs/08-INSTALLATION-AND-DISTRIBUTION.md +62 -22
- 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/27-TEAM-SHARED-CONTEXT-DIRECTION-DISCUSSION.md +30 -0
- package/docs/28-REAL-PROJECT-ONBOARDING-CLOSURE-DESIGN.md +534 -0
- package/docs/AI-PROJECT-INITIALIZATION.md +89 -0
- package/docs/README.md +34 -6
- package/docs/USER-AND-AI-OPERATION-MANUAL.md +87 -40
- package/examples/README.md +6 -6
- package/examples/package.json +1 -1
- package/migration-manifest.json +42 -8
- package/package.json +2 -2
- package/schemas/adaptive-context-bundle.schema.json +70 -0
- package/schemas/capabilities.schema.json +37 -8
- 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/initialization-instruction.schema.json +60 -0
- package/schemas/migration-manifest.schema.json +3 -3
- package/schemas/migration-plan.schema.json +2 -2
- package/schemas/project-status.schema.json +5 -4
- 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 +11 -9
- package/src/project-context/assist.mjs +4 -2
- package/src/project-context/capabilities.mjs +32 -0
- package/src/project-context/checker.mjs +4 -3
- package/src/project-context/cli.mjs +55 -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/initialization-instruction.mjs +42 -0
- package/src/project-context/maintenance.mjs +2 -2
- package/src/project-context/migration-manifest.mjs +7 -5
- package/src/project-context/project-status.mjs +14 -3
- package/src/project-context/project-store.mjs +27 -2
- 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,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", "
|
|
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,
|
|
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
|
|
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:
|
|
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.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 =
|
|
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.
|
|
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 =
|
|
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" ? ["
|
|
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")
|
|
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 });
|