agentera 3.0.0-dev.20 → 3.0.0-dev.21
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/README.md +19 -0
- package/bundle/CHANGELOG.md +63 -70
- package/bundle/README.md +17 -0
- package/bundle/UPGRADE.md +38 -0
- package/bundle/references/adapters/package-surface-characterization.md +7 -0
- package/bundle/references/artifacts/artifact-registry-interface-model.yaml +3 -3
- package/bundle/references/artifacts/state-storage-authority.yaml +1446 -0
- package/bundle/references/cli/agent-ready-state-contract.yaml +11 -39
- package/bundle/references/cli/audience-namespace-cli-migration.yaml +30 -15
- package/bundle/references/meta/documentation-inventory.md +1 -0
- package/bundle/skills/agentera/capabilities/audit/schemas/artifacts.yaml +18 -10
- package/bundle/skills/agentera/capabilities/audit/schemas/validation.yaml +10 -10
- package/bundle/skills/agentera/capabilities/build/schemas/artifacts.yaml +11 -6
- package/bundle/skills/agentera/capabilities/discuss/schemas/artifacts.yaml +3 -2
- package/bundle/skills/agentera/capabilities/document/schemas/artifacts.yaml +15 -8
- package/bundle/skills/agentera/capabilities/document/schemas/validation.yaml +13 -13
- package/bundle/skills/agentera/capabilities/orchestrate/schemas/artifacts.yaml +15 -9
- package/bundle/skills/agentera/references/contract.md +65 -23
- package/bundle/skills/agentera/schemas/artifacts/decisions.yaml +15 -10
- package/bundle/skills/agentera/schemas/artifacts/experiments.yaml +134 -3
- package/bundle/skills/agentera/schemas/artifacts/health.yaml +16 -10
- package/bundle/skills/agentera/schemas/artifacts/objective.yaml +18 -4
- package/bundle/skills/agentera/schemas/artifacts/plan.yaml +23 -1
- package/bundle/skills/agentera/schemas/artifacts/progress.yaml +16 -10
- package/bundle/skills/agentera/schemas/artifacts/todo.yaml +34 -0
- package/dist/capabilities/audit/instructions.js +1 -1
- package/dist/capabilities/audit/instructions.js.map +1 -1
- package/dist/capabilities/build/instructions.js +1 -1
- package/dist/capabilities/build/instructions.js.map +1 -1
- package/dist/capabilities/discuss/instructions.js +1 -1
- package/dist/capabilities/discuss/instructions.js.map +1 -1
- package/dist/capabilities/document/instructions.js +1 -1
- package/dist/capabilities/document/instructions.js.map +1 -1
- package/dist/capabilities/optimize/instructions.js +1 -1
- package/dist/capabilities/optimize/instructions.js.map +1 -1
- package/dist/capabilities/orchestrate/instructions.js +1 -1
- package/dist/capabilities/orchestrate/instructions.js.map +1 -1
- package/dist/capabilities/plan/instructions.js +1 -1
- package/dist/capabilities/plan/instructions.js.map +1 -1
- package/dist/capabilities/profile/instructions.js +1 -1
- package/dist/capabilities/profile/instructions.js.map +1 -1
- package/dist/capabilities/vision/instructions.js +1 -1
- package/dist/capabilities/vision/instructions.js.map +1 -1
- package/dist/cli/appContext.js +14 -6
- package/dist/cli/appContext.js.map +1 -1
- package/dist/cli/capabilityContext/bespoke.js +3 -1
- package/dist/cli/capabilityContext/bespoke.js.map +1 -1
- package/dist/cli/capabilityContext/build.js +5 -4
- package/dist/cli/capabilityContext/build.js.map +1 -1
- package/dist/cli/capabilityContext/closeout.js +2 -1
- package/dist/cli/capabilityContext/closeout.js.map +1 -1
- package/dist/cli/capabilityContext/contract.js +29 -0
- package/dist/cli/capabilityContext/contract.js.map +1 -1
- package/dist/cli/capabilityContext/evidence.js +21 -20
- package/dist/cli/capabilityContext/evidence.js.map +1 -1
- package/dist/cli/capabilityContext/orchestration.js +3 -2
- package/dist/cli/capabilityContext/orchestration.js.map +1 -1
- package/dist/cli/capabilityContext/progress.js +9 -4
- package/dist/cli/capabilityContext/progress.js.map +1 -1
- package/dist/cli/capabilityContext/slim.js +37 -6
- package/dist/cli/capabilityContext/slim.js.map +1 -1
- package/dist/cli/capabilityContext/startup.js +30 -6
- package/dist/cli/capabilityContext/startup.js.map +1 -1
- package/dist/cli/capabilityContext/types.js +14 -4
- package/dist/cli/capabilityContext/types.js.map +1 -1
- package/dist/cli/commands/backfill.js +194 -0
- package/dist/cli/commands/backfill.js.map +1 -0
- package/dist/cli/commands/compact.js +40 -11
- package/dist/cli/commands/compact.js.map +1 -1
- package/dist/cli/commands/durability.js +85 -0
- package/dist/cli/commands/durability.js.map +1 -0
- package/dist/cli/commands/migrate.js +677 -0
- package/dist/cli/commands/migrate.js.map +1 -0
- package/dist/cli/commands/prime/collectOrientationState.js +23 -0
- package/dist/cli/commands/prime/collectOrientationState.js.map +1 -1
- package/dist/cli/commands/prime/orientationOutput.js +4 -2
- package/dist/cli/commands/prime/orientationOutput.js.map +1 -1
- package/dist/cli/commands/query.js +20 -2
- package/dist/cli/commands/query.js.map +1 -1
- package/dist/cli/commands/schema.js +247 -69
- package/dist/cli/commands/schema.js.map +1 -1
- package/dist/cli/commands/state/decisions.js +28 -10
- package/dist/cli/commands/state/decisions.js.map +1 -1
- package/dist/cli/commands/state/experimentRecords.js +156 -0
- package/dist/cli/commands/state/experimentRecords.js.map +1 -0
- package/dist/cli/commands/state/get.js +141 -0
- package/dist/cli/commands/state/get.js.map +1 -0
- package/dist/cli/commands/state/index.js +2 -2
- package/dist/cli/commands/state/index.js.map +1 -1
- package/dist/cli/commands/state/list.js +159 -0
- package/dist/cli/commands/state/list.js.map +1 -0
- package/dist/cli/commands/state/plan.js +146 -41
- package/dist/cli/commands/state/plan.js.map +1 -1
- package/dist/cli/commands/state/planTasks.js +164 -0
- package/dist/cli/commands/state/planTasks.js.map +1 -0
- package/dist/cli/commands/state/plans.js +154 -0
- package/dist/cli/commands/state/plans.js.map +1 -0
- package/dist/cli/commands/state/write.js +8 -3
- package/dist/cli/commands/state/write.js.map +1 -1
- package/dist/cli/dispatch/check.js +71 -4
- package/dist/cli/dispatch/check.js.map +1 -1
- package/dist/cli/dispatch/index.js +48 -30
- package/dist/cli/dispatch/index.js.map +1 -1
- package/dist/cli/dispatch/lifecycle.js +4 -4
- package/dist/cli/dispatch/lifecycle.js.map +1 -1
- package/dist/cli/dispatch/state.js +23 -3
- package/dist/cli/dispatch/state.js.map +1 -1
- package/dist/cli/errors.js +13 -14
- package/dist/cli/errors.js.map +1 -1
- package/dist/cli/help.js +162 -3
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/orientation.js +146 -34
- package/dist/cli/orientation.js.map +1 -1
- package/dist/cli/planArtifacts.js +31 -2
- package/dist/cli/planArtifacts.js.map +1 -1
- package/dist/cli/prime-blob.js +1 -0
- package/dist/cli/prime-blob.js.map +1 -1
- package/dist/cli/startupCompletenessContract.js +3 -2
- package/dist/cli/startupCompletenessContract.js.map +1 -1
- package/dist/cli/stateQuery.js +5 -3
- package/dist/cli/stateQuery.js.map +1 -1
- package/dist/core/text.js +11 -0
- package/dist/core/text.js.map +1 -0
- package/dist/hooks/compaction/apply.js +224 -46
- package/dist/hooks/compaction/apply.js.map +1 -1
- package/dist/hooks/compaction/dryRun.js +2 -0
- package/dist/hooks/compaction/dryRun.js.map +1 -1
- package/dist/hooks/compaction/index.js +4 -4
- package/dist/hooks/compaction/index.js.map +1 -1
- package/dist/hooks/compaction/parse.js +73 -5
- package/dist/hooks/compaction/parse.js.map +1 -1
- package/dist/hooks/compaction/retention.js +22 -21
- package/dist/hooks/compaction/retention.js.map +1 -1
- package/dist/hooks/compaction/status.js +116 -23
- package/dist/hooks/compaction/status.js.map +1 -1
- package/dist/hooks/sessionStart.js +39 -7
- package/dist/hooks/sessionStart.js.map +1 -1
- package/dist/hooks/validateArtifact/index.js +2 -2
- package/dist/hooks/validateArtifact/index.js.map +1 -1
- package/dist/hooks/validateArtifact/markdown.js +9 -9
- package/dist/hooks/validateArtifact/markdown.js.map +1 -1
- package/dist/hooks/validateArtifact/traversal.js +11 -9
- package/dist/hooks/validateArtifact/traversal.js.map +1 -1
- package/dist/registries/artifactRegistry.js +147 -44
- package/dist/registries/artifactRegistry.js.map +1 -1
- package/dist/release/releaseMetadata.js +95 -1
- package/dist/release/releaseMetadata.js.map +1 -1
- package/dist/state/archiveDiscovery.js +589 -0
- package/dist/state/archiveDiscovery.js.map +1 -0
- package/dist/state/archivePublication.js +225 -0
- package/dist/state/archivePublication.js.map +1 -0
- package/dist/state/archiveRecovery.js +238 -0
- package/dist/state/archiveRecovery.js.map +1 -0
- package/dist/state/archiveReplay.js +54 -0
- package/dist/state/archiveReplay.js.map +1 -0
- package/dist/state/decisionOverlay.js +255 -0
- package/dist/state/decisionOverlay.js.map +1 -0
- package/dist/state/directRetrieval.js +322 -0
- package/dist/state/directRetrieval.js.map +1 -0
- package/dist/state/durability.js +451 -0
- package/dist/state/durability.js.map +1 -0
- package/dist/state/experimentArchive.js +64 -0
- package/dist/state/experimentArchive.js.map +1 -0
- package/dist/state/experimentIdentity.js +191 -0
- package/dist/state/experimentIdentity.js.map +1 -0
- package/dist/state/experimentRetrieval.js +447 -0
- package/dist/state/experimentRetrieval.js.map +1 -0
- package/dist/state/gitBackfill.js +561 -0
- package/dist/state/gitBackfill.js.map +1 -0
- package/dist/state/gitBackfillAuthority.js +141 -0
- package/dist/state/gitBackfillAuthority.js.map +1 -0
- package/dist/state/gitBackfillHistory.js +278 -0
- package/dist/state/gitBackfillHistory.js.map +1 -0
- package/dist/state/gitBackfillOutput.js +209 -0
- package/dist/state/gitBackfillOutput.js.map +1 -0
- package/dist/state/healthRepair.js +70 -0
- package/dist/state/healthRepair.js.map +1 -0
- package/dist/state/legacyIdentity.js +48 -0
- package/dist/state/legacyIdentity.js.map +1 -0
- package/dist/state/legacyMigration.js +516 -0
- package/dist/state/legacyMigration.js.map +1 -0
- package/dist/state/legacyMigrationParser.js +489 -0
- package/dist/state/legacyMigrationParser.js.map +1 -0
- package/dist/state/listAccounting.js +55 -0
- package/dist/state/listAccounting.js.map +1 -0
- package/dist/state/listClassification.js +18 -0
- package/dist/state/listClassification.js.map +1 -0
- package/dist/state/listRetrieval.js +782 -0
- package/dist/state/listRetrieval.js.map +1 -0
- package/dist/state/migrationAuthority.js +413 -0
- package/dist/state/migrationAuthority.js.map +1 -0
- package/dist/state/migrationEnrichment.js +35 -0
- package/dist/state/migrationEnrichment.js.map +1 -0
- package/dist/state/planIdentity.js +45 -0
- package/dist/state/planIdentity.js.map +1 -0
- package/dist/state/planRetrieval.js +314 -0
- package/dist/state/planRetrieval.js.map +1 -0
- package/dist/state/planTaskRetrieval.js +285 -0
- package/dist/state/planTaskRetrieval.js.map +1 -0
- package/dist/state/projectionPolicy.js +184 -0
- package/dist/state/projectionPolicy.js.map +1 -0
- package/dist/state/retrievalAuthority.js +326 -0
- package/dist/state/retrievalAuthority.js.map +1 -0
- package/dist/state/startupProjection.js +498 -0
- package/dist/state/startupProjection.js.map +1 -0
- package/dist/state/write/experimentPublication.js +193 -0
- package/dist/state/write/experimentPublication.js.map +1 -0
- package/dist/state/write/explain.js +28 -14
- package/dist/state/write/explain.js.map +1 -1
- package/dist/state/write/helpers.js +8 -0
- package/dist/state/write/helpers.js.map +1 -0
- package/dist/state/write/index.js +1 -0
- package/dist/state/write/index.js.map +1 -1
- package/dist/state/write/lock.js +24 -1
- package/dist/state/write/lock.js.map +1 -1
- package/dist/state/write/mutation.js +190 -0
- package/dist/state/write/mutation.js.map +1 -0
- package/dist/state/write/operations.js +36 -2
- package/dist/state/write/operations.js.map +1 -1
- package/dist/state/write/planPublication.js +172 -0
- package/dist/state/write/planPublication.js.map +1 -0
- package/dist/state/write/transaction.js +238 -225
- package/dist/state/write/transaction.js.map +1 -1
- package/dist/upgrade/migrateArtifactsV2ToV3.js +2 -2
- package/dist/upgrade/migrateArtifactsV2ToV3.js.map +1 -1
- package/dist/validate/crossCapability.js +9 -6
- package/dist/validate/crossCapability.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import { emitInvalidInput } from "../errors.js";
|
|
5
|
+
import { emitStructured } from "../structured.js";
|
|
6
|
+
import { resolveSourceRoot } from "../../core/sourceRoot.js";
|
|
7
|
+
import { migrationModeFlags, migrationSelectorFlag, migrationFailure, stateMigrationContract, } from "../../state/migrationAuthority.js";
|
|
8
|
+
import { applyLegacyMigration, inspectLegacyMigration, } from "../../state/legacyMigration.js";
|
|
9
|
+
import { migrationEnrichmentContract } from "../../state/migrationEnrichment.js";
|
|
10
|
+
function requestedFormat(argv, formats, formatFlag) {
|
|
11
|
+
let requested;
|
|
12
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
13
|
+
if (argv[index] === formatFlag) {
|
|
14
|
+
requested = argv[index + 1];
|
|
15
|
+
}
|
|
16
|
+
if (argv[index].startsWith(`${formatFlag}=`)) {
|
|
17
|
+
requested = argv[index].slice(formatFlag.length + 1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (!requested || (formats && !formats.includes(requested)))
|
|
21
|
+
return "text";
|
|
22
|
+
return requested;
|
|
23
|
+
}
|
|
24
|
+
function parseInteger(flag, value, pattern) {
|
|
25
|
+
if (pattern && !new RegExp(pattern).test(value)) {
|
|
26
|
+
return `argument ${flag}: invalid int value: '${value}'`;
|
|
27
|
+
}
|
|
28
|
+
const parsed = Number(value);
|
|
29
|
+
return Number.isSafeInteger(parsed) ? parsed : `argument ${flag}: invalid int value: '${value}'`;
|
|
30
|
+
}
|
|
31
|
+
function candidatePathError(candidate, contract, project = process.cwd()) {
|
|
32
|
+
if (candidate.includes("\0") ||
|
|
33
|
+
candidate.includes("\\") ||
|
|
34
|
+
path.isAbsolute(candidate) ||
|
|
35
|
+
path.win32.isAbsolute(candidate)) {
|
|
36
|
+
return "candidate path violates the project boundary: use a project-local relative path";
|
|
37
|
+
}
|
|
38
|
+
let decoded = candidate;
|
|
39
|
+
try {
|
|
40
|
+
decoded = decodeURIComponent(candidate);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return "candidate path contains invalid percent encoding";
|
|
44
|
+
}
|
|
45
|
+
const normalized = decoded.replaceAll("\\", "/");
|
|
46
|
+
const parts = normalized.split("/").filter((part) => part !== "" && part !== ".");
|
|
47
|
+
if (parts.some((part) => part === "..") ||
|
|
48
|
+
decoded.includes("://") ||
|
|
49
|
+
/^[A-Za-z][A-Za-z0-9+.-]*:/.test(decoded)) {
|
|
50
|
+
return "candidate path violates the project boundary: traversal and URI paths are forbidden";
|
|
51
|
+
}
|
|
52
|
+
const normalizedPath = parts.join("/");
|
|
53
|
+
if (contract.inventory.excludedRelativePaths.some((excluded) => normalizedPath === excluded || normalizedPath.startsWith(`${excluded}/`))) {
|
|
54
|
+
return "candidate path is excluded by the authority inventory policy";
|
|
55
|
+
}
|
|
56
|
+
const inScanRoot = contract.inventory.roots.some((root) => {
|
|
57
|
+
const rootParts = root.path === "." ? [] : root.path.split("/").filter(Boolean);
|
|
58
|
+
const relativeParts = parts.slice(rootParts.length);
|
|
59
|
+
return (rootParts.every((part, index) => parts[index] === part) &&
|
|
60
|
+
relativeParts.length > 0 &&
|
|
61
|
+
relativeParts.length <= root.maximumDepth);
|
|
62
|
+
});
|
|
63
|
+
if (!inScanRoot)
|
|
64
|
+
return "candidate path must remain within a declared project-local scan root";
|
|
65
|
+
if (!new RegExp(contract.candidatePattern).test(parts[parts.length - 1])) {
|
|
66
|
+
return "candidate path is unsupported by the authority candidate pattern";
|
|
67
|
+
}
|
|
68
|
+
let projectRoot;
|
|
69
|
+
try {
|
|
70
|
+
projectRoot = fs.realpathSync(project);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return "selected project must resolve to an existing project directory";
|
|
74
|
+
}
|
|
75
|
+
let current = projectRoot;
|
|
76
|
+
for (const part of parts) {
|
|
77
|
+
current = path.join(current, part);
|
|
78
|
+
try {
|
|
79
|
+
if (fs.lstatSync(current).isSymbolicLink()) {
|
|
80
|
+
return "candidate path violates the project boundary: symlink components are forbidden";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
class MigrationInventoryError extends Error {
|
|
90
|
+
failureClass;
|
|
91
|
+
constructor(message, failureClass) {
|
|
92
|
+
super(message);
|
|
93
|
+
this.failureClass = failureClass;
|
|
94
|
+
this.name = "MigrationInventoryError";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function normalizedRelativePath(value) {
|
|
98
|
+
return value.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
99
|
+
}
|
|
100
|
+
function pathIsExcluded(relativePath, contract) {
|
|
101
|
+
return contract.inventory.excludedRelativePaths.some((excluded) => relativePath === excluded || relativePath.startsWith(`${excluded}/`));
|
|
102
|
+
}
|
|
103
|
+
function inventoryEntry(observation, project, contract) {
|
|
104
|
+
const artifactId = contract.fixedNames[observation.relativePath] ?? null;
|
|
105
|
+
const entry = {
|
|
106
|
+
candidate_id: observation.relativePath,
|
|
107
|
+
path: observation.relativePath,
|
|
108
|
+
artifact_id: artifactId,
|
|
109
|
+
entry_number: null,
|
|
110
|
+
classification: "unaddressable",
|
|
111
|
+
detail_availability: "candidate_metadata_only",
|
|
112
|
+
compatibility: "legacy",
|
|
113
|
+
source: "project_local",
|
|
114
|
+
addressable: false,
|
|
115
|
+
size_bytes: observation.sizeBytes,
|
|
116
|
+
};
|
|
117
|
+
const pathError = candidatePathError(observation.relativePath, contract, project);
|
|
118
|
+
if (observation.symbolicLink || pathError?.includes("boundary")) {
|
|
119
|
+
entry.classification = "project_boundary";
|
|
120
|
+
entry.compatibility = "blocked";
|
|
121
|
+
entry.rejection = observation.symbolicLink ? "symlink_escape" : "outside_project";
|
|
122
|
+
}
|
|
123
|
+
else if (!observation.regularFile || pathError) {
|
|
124
|
+
entry.classification = "unsupported";
|
|
125
|
+
entry.compatibility = "unsupported";
|
|
126
|
+
entry.rejection = observation.regularFile ? "unsupported_path" : "non_regular";
|
|
127
|
+
}
|
|
128
|
+
return entry;
|
|
129
|
+
}
|
|
130
|
+
function inventoryStatus(entries, omittedCount, contract) {
|
|
131
|
+
const preferred = omittedCount > 0
|
|
132
|
+
? "degraded"
|
|
133
|
+
: entries.some((entry) => entry.rejection !== undefined)
|
|
134
|
+
? "blocked"
|
|
135
|
+
: "complete";
|
|
136
|
+
return contract.resultStatuses.includes(preferred) ? preferred : contract.resultStatuses[0];
|
|
137
|
+
}
|
|
138
|
+
/** Inventory candidate metadata only; this function never reads bytes or mutates state. */
|
|
139
|
+
export function inventoryCandidates(selectedProject, contract) {
|
|
140
|
+
let project;
|
|
141
|
+
try {
|
|
142
|
+
project = fs.realpathSync(selectedProject);
|
|
143
|
+
if (!fs.statSync(project).isDirectory())
|
|
144
|
+
throw new Error("not a directory");
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
throw new MigrationInventoryError("selected project must resolve to an existing project directory", "project_boundary");
|
|
148
|
+
}
|
|
149
|
+
const observations = [];
|
|
150
|
+
const seen = new Set();
|
|
151
|
+
const candidatePattern = new RegExp(contract.candidatePattern);
|
|
152
|
+
const collect = (rootPath, rootRelative, maximumDepth, depth) => {
|
|
153
|
+
let directoryEntries;
|
|
154
|
+
try {
|
|
155
|
+
directoryEntries = fs.readdirSync(rootPath, { withFileTypes: true });
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
for (const directoryEntry of directoryEntries) {
|
|
161
|
+
const relativePath = normalizedRelativePath(path.join(rootRelative, directoryEntry.name));
|
|
162
|
+
if (pathIsExcluded(relativePath, contract))
|
|
163
|
+
continue;
|
|
164
|
+
const absolutePath = path.join(project, relativePath);
|
|
165
|
+
if (directoryEntry.isDirectory()) {
|
|
166
|
+
if (depth + 1 < maximumDepth)
|
|
167
|
+
collect(absolutePath, relativePath, maximumDepth, depth + 1);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (!candidatePattern.test(directoryEntry.name) || seen.has(relativePath))
|
|
171
|
+
continue;
|
|
172
|
+
seen.add(relativePath);
|
|
173
|
+
let sizeBytes = 0;
|
|
174
|
+
try {
|
|
175
|
+
sizeBytes = fs.lstatSync(absolutePath).size;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
sizeBytes = 0;
|
|
179
|
+
}
|
|
180
|
+
observations.push({
|
|
181
|
+
relativePath,
|
|
182
|
+
sizeBytes,
|
|
183
|
+
symbolicLink: directoryEntry.isSymbolicLink(),
|
|
184
|
+
regularFile: directoryEntry.isFile(),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
for (const root of contract.inventory.roots) {
|
|
189
|
+
const rootRelative = normalizedRelativePath(root.path === "." ? "" : root.path);
|
|
190
|
+
const rootPath = path.resolve(project, rootRelative || ".");
|
|
191
|
+
try {
|
|
192
|
+
const rootStat = fs.lstatSync(rootPath);
|
|
193
|
+
if (rootStat.isSymbolicLink()) {
|
|
194
|
+
throw new MigrationInventoryError(`declared scan root '${root.path}' violates the project boundary: symlink roots are forbidden`, "project_boundary");
|
|
195
|
+
}
|
|
196
|
+
if (!rootStat.isDirectory())
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
if (error instanceof MigrationInventoryError)
|
|
201
|
+
throw error;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
collect(rootPath, rootRelative, root.maximumDepth, 0);
|
|
205
|
+
}
|
|
206
|
+
observations.sort((left, right) => left.relativePath < right.relativePath ? -1 : left.relativePath > right.relativePath ? 1 : 0);
|
|
207
|
+
const entries = [];
|
|
208
|
+
let omittedCount = 0;
|
|
209
|
+
let totalBytes = 0;
|
|
210
|
+
for (const [index, observation] of observations.entries()) {
|
|
211
|
+
if (index >= contract.inventory.maximumCandidateFiles) {
|
|
212
|
+
omittedCount += 1;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (observation.sizeBytes > contract.inventory.maximumFileBytes) {
|
|
216
|
+
omittedCount += 1;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (totalBytes + observation.sizeBytes > contract.inventory.maximumTotalBytes) {
|
|
220
|
+
omittedCount += 1;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
totalBytes += observation.sizeBytes;
|
|
224
|
+
entries.push(inventoryEntry(observation, project, contract));
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
project,
|
|
228
|
+
entries,
|
|
229
|
+
omittedCount,
|
|
230
|
+
status: inventoryStatus(entries, omittedCount, contract),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
export function parseMigrateArgs(argv, contract = stateMigrationContract()) {
|
|
234
|
+
const selectorFlags = new Map(Object.keys(contract.selectors).map((name) => [migrationSelectorFlag(contract, name), name]));
|
|
235
|
+
const formatFlag = migrationSelectorFlag(contract, "format");
|
|
236
|
+
const previewFlags = migrationModeFlags(contract, "preview");
|
|
237
|
+
const applyFlags = migrationModeFlags(contract, "apply");
|
|
238
|
+
const dryRunFlag = previewFlags[0];
|
|
239
|
+
const applyFlag = applyFlags.find((flag) => !previewFlags.includes(flag)) ?? applyFlags[0];
|
|
240
|
+
const forceFlag = applyFlags.find((flag) => flag !== applyFlag);
|
|
241
|
+
const args = {
|
|
242
|
+
project: null,
|
|
243
|
+
artifact: null,
|
|
244
|
+
dryRun: false,
|
|
245
|
+
apply: false,
|
|
246
|
+
force: false,
|
|
247
|
+
format: contract.formats[0],
|
|
248
|
+
limit: contract.defaultLimit,
|
|
249
|
+
};
|
|
250
|
+
const seen = new Set();
|
|
251
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
252
|
+
const token = argv[index];
|
|
253
|
+
const flag = [...selectorFlags.keys()].find((candidate) => token === candidate || token.startsWith(`${candidate}=`));
|
|
254
|
+
if (flag) {
|
|
255
|
+
if (seen.has(flag))
|
|
256
|
+
return { error: `${flag} may only be supplied once` };
|
|
257
|
+
seen.add(flag);
|
|
258
|
+
const inline = token.startsWith(`${flag}=`);
|
|
259
|
+
const value = inline ? token.slice(flag.length + 1) : argv[++index];
|
|
260
|
+
if (value === undefined || value === "" || (!inline && value.startsWith("--"))) {
|
|
261
|
+
return { error: `argument ${flag}: expected a value` };
|
|
262
|
+
}
|
|
263
|
+
const selector = selectorFlags.get(flag);
|
|
264
|
+
if (selector === "format") {
|
|
265
|
+
if (!contract.formats.includes(value)) {
|
|
266
|
+
return {
|
|
267
|
+
error: `argument ${formatFlag}: invalid choice: '${value}' (choose from ${contract.formats.map((item) => `'${item}'`).join(", ")})`,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
args.format = value;
|
|
271
|
+
}
|
|
272
|
+
else if (selector === "project")
|
|
273
|
+
args.project = value;
|
|
274
|
+
else if (selector === "artifact")
|
|
275
|
+
args.artifact = value;
|
|
276
|
+
else if (selector === "path")
|
|
277
|
+
args.path = value;
|
|
278
|
+
else {
|
|
279
|
+
const pattern = selector === "number" ? contract.numberPattern : undefined;
|
|
280
|
+
const parsed = parseInteger(flag, value, pattern);
|
|
281
|
+
if (typeof parsed === "string")
|
|
282
|
+
return { error: parsed };
|
|
283
|
+
if (selector === "number")
|
|
284
|
+
args.number = parsed;
|
|
285
|
+
else if (selector === "limit")
|
|
286
|
+
args.limit = parsed;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else if (token === dryRunFlag) {
|
|
290
|
+
if (args.dryRun)
|
|
291
|
+
return { error: `${dryRunFlag} may only be supplied once` };
|
|
292
|
+
args.dryRun = true;
|
|
293
|
+
}
|
|
294
|
+
else if (token === applyFlag) {
|
|
295
|
+
if (args.apply)
|
|
296
|
+
return { error: `${applyFlag} may only be supplied once` };
|
|
297
|
+
args.apply = true;
|
|
298
|
+
}
|
|
299
|
+
else if (token === forceFlag) {
|
|
300
|
+
if (args.force)
|
|
301
|
+
return { error: `${forceFlag} may only be supplied once` };
|
|
302
|
+
args.force = true;
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
return { error: `unrecognized arguments: ${token}` };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return args;
|
|
309
|
+
}
|
|
310
|
+
export function validateMigrateArgs(args, contract) {
|
|
311
|
+
const limitFlag = migrationSelectorFlag(contract, "limit");
|
|
312
|
+
const dryRunFlag = migrationModeFlags(contract, "preview")[0];
|
|
313
|
+
const applyFlags = migrationModeFlags(contract, "apply");
|
|
314
|
+
const applyFlag = applyFlags.find((flag) => ![dryRunFlag].includes(flag)) ?? applyFlags[0];
|
|
315
|
+
const forceFlag = applyFlags.find((flag) => flag !== applyFlag);
|
|
316
|
+
const selectorValue = (selectorName) => {
|
|
317
|
+
if (selectorName === "project")
|
|
318
|
+
return args.project;
|
|
319
|
+
if (selectorName === "artifact")
|
|
320
|
+
return args.artifact;
|
|
321
|
+
if (selectorName === "number")
|
|
322
|
+
return args.number;
|
|
323
|
+
if (selectorName === "path")
|
|
324
|
+
return args.path;
|
|
325
|
+
if (selectorName === "limit")
|
|
326
|
+
return args.limit;
|
|
327
|
+
if (selectorName === "format")
|
|
328
|
+
return args.format;
|
|
329
|
+
return undefined;
|
|
330
|
+
};
|
|
331
|
+
const selectorFlag = (name) => migrationSelectorFlag(contract, name);
|
|
332
|
+
const activeModeFlags = new Set();
|
|
333
|
+
if (args.dryRun)
|
|
334
|
+
activeModeFlags.add(dryRunFlag);
|
|
335
|
+
if (args.apply)
|
|
336
|
+
activeModeFlags.add(applyFlag);
|
|
337
|
+
if (args.force && forceFlag)
|
|
338
|
+
activeModeFlags.add(forceFlag);
|
|
339
|
+
for (const combination of contract.invalidCombinations) {
|
|
340
|
+
if (!combination.flags.every((flag) => activeModeFlags.has(flag)))
|
|
341
|
+
continue;
|
|
342
|
+
if (combination.requires && !activeModeFlags.has(combination.requires))
|
|
343
|
+
return combination.message;
|
|
344
|
+
if (!combination.requires)
|
|
345
|
+
return combination.message;
|
|
346
|
+
}
|
|
347
|
+
const artifactValues = contract.selectorValidValues.artifact;
|
|
348
|
+
if (args.artifact && !artifactValues.includes(args.artifact)) {
|
|
349
|
+
return `unsupported artifact '${args.artifact}'`;
|
|
350
|
+
}
|
|
351
|
+
const numberRequires = contract.selectors.number.required_with;
|
|
352
|
+
if (args.number !== undefined && typeof numberRequires === "string") {
|
|
353
|
+
const requiredSelector = Object.keys(contract.selectors).find((name) => selectorFlag(name) === numberRequires);
|
|
354
|
+
if (!requiredSelector ||
|
|
355
|
+
selectorValue(requiredSelector) === null ||
|
|
356
|
+
selectorValue(requiredSelector) === undefined) {
|
|
357
|
+
return `argument ${selectorFlag("number")} requires ${numberRequires}`;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (args.limit !== undefined &&
|
|
361
|
+
(args.limit < contract.minimumLimit || args.limit > contract.maximumLimit)) {
|
|
362
|
+
return `argument ${limitFlag} must be between ${contract.minimumLimit} and ${contract.maximumLimit}`;
|
|
363
|
+
}
|
|
364
|
+
if (args.path) {
|
|
365
|
+
const pathError = candidatePathError(args.path, contract, args.project ?? process.cwd());
|
|
366
|
+
if (pathError)
|
|
367
|
+
return pathError;
|
|
368
|
+
}
|
|
369
|
+
const requiredApplySelectors = contract.requiredSelectors.apply ?? [];
|
|
370
|
+
const missingApplySelectors = requiredApplySelectors.filter((required) => {
|
|
371
|
+
const selectorName = Object.keys(contract.selectors).find((name) => selectorFlag(name) === required);
|
|
372
|
+
return (!selectorName ||
|
|
373
|
+
selectorValue(selectorName) === null ||
|
|
374
|
+
selectorValue(selectorName) === undefined);
|
|
375
|
+
});
|
|
376
|
+
if (args.apply && missingApplySelectors.length > 0) {
|
|
377
|
+
return `${applyFlag} requires ${missingApplySelectors.join(" and ")} selector${missingApplySelectors.length === 1 ? "" : "s"}`;
|
|
378
|
+
}
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
function authorityFailure(contract, failureClass) {
|
|
382
|
+
const failure = migrationFailure(contract, failureClass);
|
|
383
|
+
return {
|
|
384
|
+
example: typeof failure.example === "string" ? failure.example : undefined,
|
|
385
|
+
recovery: typeof failure.recovery === "string" ? failure.recovery : undefined,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function failureClassForMessage(contract, message) {
|
|
389
|
+
const modeFailure = contract.invalidCombinations.find((combination) => combination.message === message);
|
|
390
|
+
if (modeFailure)
|
|
391
|
+
return modeFailure.failureClass;
|
|
392
|
+
if (message.includes("project boundary") || message.includes("selected project"))
|
|
393
|
+
return "project_boundary";
|
|
394
|
+
if (message.includes("unsupported"))
|
|
395
|
+
return "unsupported_candidate";
|
|
396
|
+
return "invalid_selector";
|
|
397
|
+
}
|
|
398
|
+
function invalid(message, format, contract, validValues, failureClass = "invalid_selector") {
|
|
399
|
+
const failure = contract ? authorityFailure(contract, failureClass) : {};
|
|
400
|
+
return {
|
|
401
|
+
format,
|
|
402
|
+
body: {
|
|
403
|
+
class: message.startsWith("unsupported artifact") ? "invalid_choice" : "invalid_request",
|
|
404
|
+
message,
|
|
405
|
+
...(contract
|
|
406
|
+
? { syntax: contract.command, example: authorityFailure(contract, failureClass).example }
|
|
407
|
+
: {}),
|
|
408
|
+
...(validValues ? { valid_values: validValues } : {}),
|
|
409
|
+
...(failure.recovery ? { recovery: failure.recovery } : {}),
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
export function projectedEntries(entries, args, contract) {
|
|
414
|
+
const allowedFields = new Set(contract.resultEntryFields);
|
|
415
|
+
const normalized = entries
|
|
416
|
+
.map((entry) => {
|
|
417
|
+
const projected = Object.fromEntries(Object.entries(entry).filter(([field]) => allowedFields.has(field)));
|
|
418
|
+
for (const field of ["path", "candidate_id"]) {
|
|
419
|
+
if (typeof projected[field] === "string") {
|
|
420
|
+
projected[field] = normalizedRelativePath(projected[field]);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return projected;
|
|
424
|
+
})
|
|
425
|
+
.sort((left, right) => {
|
|
426
|
+
const leftPath = String(left.path ?? "").replaceAll("\\", "/");
|
|
427
|
+
const rightPath = String(right.path ?? "").replaceAll("\\", "/");
|
|
428
|
+
return leftPath < rightPath ? -1 : leftPath > rightPath ? 1 : 0;
|
|
429
|
+
});
|
|
430
|
+
const limit = args.limit ?? contract.defaultLimit;
|
|
431
|
+
return {
|
|
432
|
+
entries: normalized.slice(0, limit),
|
|
433
|
+
omittedCount: Math.max(0, normalized.length - limit),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
export function resultCounts(entries, omittedCount, contract) {
|
|
437
|
+
const matches = (entry, predicates) => predicates.every((predicate) => {
|
|
438
|
+
if ("equals" in predicate)
|
|
439
|
+
return entry[predicate.field] === predicate.equals;
|
|
440
|
+
return entry[predicate.field] !== predicate.notEquals;
|
|
441
|
+
});
|
|
442
|
+
const counts = {};
|
|
443
|
+
for (const field of contract.resultCountFields) {
|
|
444
|
+
const rule = contract.resultCountRules[field];
|
|
445
|
+
const sourceEntries = rule.source === "visible_entries" ? entries : [];
|
|
446
|
+
if (rule.operation === "value" && rule.source === "omitted_count") {
|
|
447
|
+
counts[field] = omittedCount;
|
|
448
|
+
}
|
|
449
|
+
else if (rule.operation === "count" &&
|
|
450
|
+
rule.source === "all_candidates" &&
|
|
451
|
+
rule.predicates.length === 0) {
|
|
452
|
+
counts[field] = entries.length + omittedCount;
|
|
453
|
+
}
|
|
454
|
+
else if (rule.operation === "count" && rule.source === "visible_entries") {
|
|
455
|
+
counts[field] = sourceEntries.filter((entry) => matches(entry, rule.predicates)).length;
|
|
456
|
+
}
|
|
457
|
+
else if (rule.operation === "distinct" && rule.source === "visible_entries" && rule.field) {
|
|
458
|
+
counts[field] = new Set(sourceEntries
|
|
459
|
+
.filter((entry) => matches(entry, rule.predicates))
|
|
460
|
+
.map((entry) => entry[rule.field])
|
|
461
|
+
.filter((value) => typeof value === "string" || typeof value === "number")).size;
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
counts[field] = null;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return counts;
|
|
468
|
+
}
|
|
469
|
+
function omissionValues(contract, omittedCount, outputBounded = false) {
|
|
470
|
+
const sourceValues = {
|
|
471
|
+
has_omissions: omittedCount > 0,
|
|
472
|
+
omitted_count: omittedCount,
|
|
473
|
+
omission_reason: omittedCount > 0
|
|
474
|
+
? outputBounded
|
|
475
|
+
? contract.omission.outputBoundedReason
|
|
476
|
+
: contract.omission.boundedReason
|
|
477
|
+
: contract.omission.completeReason,
|
|
478
|
+
retrieval: {
|
|
479
|
+
command: contract.omission.retrieval,
|
|
480
|
+
retry: outputBounded ? contract.omission.outputRetry : contract.omission.retry,
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
return Object.fromEntries(contract.omission.fields.map((field) => {
|
|
484
|
+
const source = contract.omission.fieldSources[field];
|
|
485
|
+
if (!(source in sourceValues)) {
|
|
486
|
+
throw new Error(`state migration omission source '${source}' is unavailable`);
|
|
487
|
+
}
|
|
488
|
+
return [field, sourceValues[source]];
|
|
489
|
+
}));
|
|
490
|
+
}
|
|
491
|
+
function textResponse(response) {
|
|
492
|
+
const diagnostics = response.diagnostics;
|
|
493
|
+
const valueText = (value) => value !== null && typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
494
|
+
return ([
|
|
495
|
+
...Object.entries(response)
|
|
496
|
+
.filter(([field]) => field !== "diagnostics")
|
|
497
|
+
.map(([field, value]) => `${field}: ${valueText(value)}`),
|
|
498
|
+
`diagnostic: ${diagnostics[0]?.message ?? "none"}`,
|
|
499
|
+
`authority: ${response.source_contract.authority}`,
|
|
500
|
+
"",
|
|
501
|
+
].join("\n"));
|
|
502
|
+
}
|
|
503
|
+
function serializedResponseBytes(response, format) {
|
|
504
|
+
const serialized = format === "text"
|
|
505
|
+
? textResponse(response)
|
|
506
|
+
: format === "yaml"
|
|
507
|
+
? YAML.stringify(response, { sortMapEntries: false })
|
|
508
|
+
: JSON.stringify(response, null, 2) + "\n";
|
|
509
|
+
return Buffer.byteLength(serialized, "utf8");
|
|
510
|
+
}
|
|
511
|
+
function boundedDiagnostics(diagnostics, entries, contract) {
|
|
512
|
+
if (!Array.isArray(diagnostics))
|
|
513
|
+
return [];
|
|
514
|
+
const retainedIds = new Set(entries.map((entry) => String(entry.candidate_id ?? "")));
|
|
515
|
+
const retained = [];
|
|
516
|
+
const omitted = new Map();
|
|
517
|
+
for (const value of diagnostics) {
|
|
518
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
519
|
+
continue;
|
|
520
|
+
const diagnostic = value;
|
|
521
|
+
const candidateId = diagnostic.candidate_id;
|
|
522
|
+
if (candidateId === undefined || retainedIds.has(String(candidateId))) {
|
|
523
|
+
retained.push(diagnostic);
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
const failureClass = String(diagnostic.class ?? "unsupported_candidate");
|
|
527
|
+
omitted.set(failureClass, (omitted.get(failureClass) ?? 0) + 1);
|
|
528
|
+
}
|
|
529
|
+
for (const [failureClass, count] of [...omitted.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
530
|
+
retained.push({
|
|
531
|
+
class: failureClass,
|
|
532
|
+
message: `${count} candidate diagnostic(s) were omitted by the serialized output budget`,
|
|
533
|
+
provenance: {
|
|
534
|
+
failure_class: failureClass,
|
|
535
|
+
reason: contract.omission.outputBoundedReason,
|
|
536
|
+
omitted_count: count,
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
return retained;
|
|
541
|
+
}
|
|
542
|
+
function boundMigrationResponse(response, format, contract) {
|
|
543
|
+
if (serializedResponseBytes(response, format) <= contract.outputMaxUtf8Bytes)
|
|
544
|
+
return response;
|
|
545
|
+
const entries = Array.isArray(response.entries)
|
|
546
|
+
? response.entries
|
|
547
|
+
: [];
|
|
548
|
+
const initialOmitted = Number(response.omitted_count ?? 0);
|
|
549
|
+
for (let retainedCount = entries.length; retainedCount >= 0; retainedCount -= 1) {
|
|
550
|
+
const retained = entries.slice(0, retainedCount);
|
|
551
|
+
const omittedCount = initialOmitted + entries.length - retainedCount;
|
|
552
|
+
const bounded = {
|
|
553
|
+
...response,
|
|
554
|
+
entries: retained,
|
|
555
|
+
diagnostics: boundedDiagnostics(response.diagnostics, retained, contract),
|
|
556
|
+
counts: resultCounts(retained, omittedCount, contract),
|
|
557
|
+
...omissionValues(contract, omittedCount, true),
|
|
558
|
+
};
|
|
559
|
+
if (serializedResponseBytes(bounded, format) <= contract.outputMaxUtf8Bytes)
|
|
560
|
+
return bounded;
|
|
561
|
+
}
|
|
562
|
+
throw new Error(`migration output requires more than the authority ${contract.outputMaxUtf8Bytes}-byte ${format} budget after required omission metadata`);
|
|
563
|
+
}
|
|
564
|
+
export function deferredResponse(args, contract, entries = [], inventory, options = {}, sourceRoot = resolveSourceRoot()) {
|
|
565
|
+
const mode = args.apply ? "apply" : args.dryRun ? "preview" : "inventory";
|
|
566
|
+
const projected = projectedEntries(entries, args, contract);
|
|
567
|
+
const omittedCount = (inventory?.omittedCount ?? 0) + projected.omittedCount;
|
|
568
|
+
const omission = omissionValues(contract, omittedCount);
|
|
569
|
+
const values = {
|
|
570
|
+
schemaVersion: contract.resultSchemaVersion,
|
|
571
|
+
command: contract.command,
|
|
572
|
+
status: options.status ?? inventory?.status ?? contract.resultStatuses[0],
|
|
573
|
+
mode,
|
|
574
|
+
project: inventory?.project ?? args.project ?? process.cwd(),
|
|
575
|
+
read_only: !args.apply,
|
|
576
|
+
mutation_intent: args.apply,
|
|
577
|
+
mutation_performed: options.mutationPerformed ?? false,
|
|
578
|
+
remote_contact: false,
|
|
579
|
+
inventory_performed: inventory !== undefined,
|
|
580
|
+
entries: projected.entries,
|
|
581
|
+
counts: resultCounts(projected.entries, omittedCount, contract),
|
|
582
|
+
...omission,
|
|
583
|
+
diagnostics: options.diagnostics ?? [
|
|
584
|
+
{
|
|
585
|
+
class: "implementation_deferred",
|
|
586
|
+
message: `${String(contract.migration.implementation_boundary)} No state was changed.`,
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
source_contract: {
|
|
590
|
+
authority: contract.authorityPath,
|
|
591
|
+
schema_version: contract.schemaVersion,
|
|
592
|
+
execution: "authority_and_dispatch_only",
|
|
593
|
+
result: {
|
|
594
|
+
required_fields: contract.resultRequiredFields,
|
|
595
|
+
entry_fields: contract.resultEntryFields,
|
|
596
|
+
count_fields: contract.resultCountFields,
|
|
597
|
+
count_rules: contract.resultCountRules,
|
|
598
|
+
omission: contract.omission,
|
|
599
|
+
max_utf8_bytes: contract.outputMaxUtf8Bytes,
|
|
600
|
+
},
|
|
601
|
+
inventory: contract.inventory,
|
|
602
|
+
git_required: false,
|
|
603
|
+
remote_contact: "forbidden",
|
|
604
|
+
optional_git_enrichment: migrationEnrichmentContract(sourceRoot),
|
|
605
|
+
},
|
|
606
|
+
};
|
|
607
|
+
const response = Object.fromEntries(contract.resultRequiredFields.map((field) => [field, values[field] ?? null]));
|
|
608
|
+
if (options.operations !== undefined)
|
|
609
|
+
response.operations = options.operations;
|
|
610
|
+
return boundMigrationResponse(response, args.format, contract);
|
|
611
|
+
}
|
|
612
|
+
export function renderText(response, out) {
|
|
613
|
+
out(textResponse(response));
|
|
614
|
+
}
|
|
615
|
+
export function runMigrate(argv, io, sourceRootOverride) {
|
|
616
|
+
let format = "text";
|
|
617
|
+
const sourceRoot = sourceRootOverride ?? resolveSourceRoot();
|
|
618
|
+
let contract;
|
|
619
|
+
try {
|
|
620
|
+
contract = stateMigrationContract(sourceRoot);
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
return emitInvalidInput(io, invalid(`Local migration authority could not be loaded: ${error.message}`, format));
|
|
624
|
+
}
|
|
625
|
+
format = requestedFormat(argv, contract.formats, migrationSelectorFlag(contract, "format"));
|
|
626
|
+
const parsed = parseMigrateArgs(argv, contract);
|
|
627
|
+
if ("error" in parsed) {
|
|
628
|
+
const validValues = parsed.error.includes("invalid choice")
|
|
629
|
+
? contract.selectorValidValues.format
|
|
630
|
+
: undefined;
|
|
631
|
+
return emitInvalidInput(io, invalid(parsed.error, format, contract, validValues, "invalid_selector"));
|
|
632
|
+
}
|
|
633
|
+
const validation = validateMigrateArgs(parsed, contract);
|
|
634
|
+
if (validation) {
|
|
635
|
+
const validValues = validation.startsWith("unsupported artifact")
|
|
636
|
+
? contract.selectorValidValues.artifact
|
|
637
|
+
: undefined;
|
|
638
|
+
return emitInvalidInput(io, invalid(validation, format, contract, validValues, failureClassForMessage(contract, validation)));
|
|
639
|
+
}
|
|
640
|
+
let inventory;
|
|
641
|
+
try {
|
|
642
|
+
inventory = inventoryCandidates(parsed.project ?? process.cwd(), contract);
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
const inventoryError = error;
|
|
646
|
+
return emitInvalidInput(io, invalid(inventoryError.message, format, contract, undefined, inventoryError.failureClass ?? "project_boundary"));
|
|
647
|
+
}
|
|
648
|
+
let inspection;
|
|
649
|
+
try {
|
|
650
|
+
inspection = inspectLegacyMigration(inventory, contract, sourceRoot, parsed);
|
|
651
|
+
}
|
|
652
|
+
catch (error) {
|
|
653
|
+
return emitInvalidInput(io, invalid(`local migration inventory failed: ${error.message}`, format, contract, undefined, "corrupt_candidate"));
|
|
654
|
+
}
|
|
655
|
+
let applyResult;
|
|
656
|
+
if (parsed.apply) {
|
|
657
|
+
applyResult = applyLegacyMigration(inspection, {
|
|
658
|
+
artifact: parsed.artifact,
|
|
659
|
+
number: parsed.number,
|
|
660
|
+
path: parsed.path,
|
|
661
|
+
}, { sourceRoot });
|
|
662
|
+
}
|
|
663
|
+
const diagnostics = [...inspection.diagnostics, ...(applyResult?.diagnostics ?? [])].slice(0, parsed.limit ?? contract.defaultLimit);
|
|
664
|
+
const response = deferredResponse(parsed, contract, inspection.entries, { ...inventory, status: applyResult?.status ?? inspection.status }, {
|
|
665
|
+
status: applyResult?.status ?? inspection.status,
|
|
666
|
+
diagnostics,
|
|
667
|
+
operations: applyResult?.operations ?? inspection.operations,
|
|
668
|
+
mutationPerformed: applyResult?.mutationPerformed ?? false,
|
|
669
|
+
}, sourceRoot);
|
|
670
|
+
const out = io.out ?? ((text) => process.stdout.write(text));
|
|
671
|
+
if (format === "text")
|
|
672
|
+
renderText(response, out);
|
|
673
|
+
else
|
|
674
|
+
emitStructured(response, format, out);
|
|
675
|
+
return 1;
|
|
676
|
+
}
|
|
677
|
+
//# sourceMappingURL=migrate.js.map
|