@neocompose/cli 0.43.1 → 0.44.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
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.44.0] - 2026-09-03
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `neo doctor --fix` deletes the rows the document audit's
|
|
8
|
+
`unprojected-value-row` findings name: base value rows no emitted file
|
|
9
|
+
projects and no other record names, which `neo status` reads as CLI-managed
|
|
10
|
+
loose state and never reports. It lists every orphan root with the number of
|
|
11
|
+
rows it owns, confirms, then deletes them under two staleness guards: the CAS
|
|
12
|
+
base the last pull recorded for each row, and the version head each
|
|
13
|
+
transaction expects from the one before it, starting from the head that pull
|
|
14
|
+
left. A project that moved on since the pull therefore fails the commit
|
|
15
|
+
instead of deleting a row that has since gained an owner, and a workspace
|
|
16
|
+
with no recorded head is refused rather than written blind. Sweeps larger
|
|
17
|
+
than one transaction go up in batches sized from the shared cost model.
|
|
18
|
+
`--yes` answers the confirmation for scripted runs; `--fix` has no `--json`
|
|
19
|
+
form.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `neo pull` marked a conflicted record in only the one file the emission
|
|
24
|
+
attributes it to, so a conflict on a member declared by more than one class
|
|
25
|
+
(system world-layer members such as `Name`) left the sibling declarations
|
|
26
|
+
unmarked. Resolving the marked file alone then left the declarations
|
|
27
|
+
disagreeing and the next command refused the workspace. Markers now go in
|
|
28
|
+
every file that declares the record, and `neo resolve` treats those files as
|
|
29
|
+
one conflict: it settles them together and refuses a record whose
|
|
30
|
+
declarations are only partly resolved.
|
|
31
|
+
|
|
3
32
|
## [0.43.1] - 2026-09-03
|
|
4
33
|
|
|
5
34
|
### Changed
|
package/dist/neo.mjs
CHANGED
|
@@ -1043,7 +1043,9 @@ var init_args = __esm({
|
|
|
1043
1043
|
"generate-ids",
|
|
1044
1044
|
"help",
|
|
1045
1045
|
"passWithNoTests",
|
|
1046
|
-
"no-verify"
|
|
1046
|
+
"no-verify",
|
|
1047
|
+
"fix",
|
|
1048
|
+
"yes"
|
|
1047
1049
|
]);
|
|
1048
1050
|
KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
1049
1051
|
"accept-bump",
|
|
@@ -1055,6 +1057,7 @@ var init_args = __esm({
|
|
|
1055
1057
|
"dir",
|
|
1056
1058
|
"dry-run",
|
|
1057
1059
|
"file",
|
|
1060
|
+
"fix",
|
|
1058
1061
|
"force",
|
|
1059
1062
|
"force-recompile",
|
|
1060
1063
|
"function",
|
|
@@ -1087,6 +1090,7 @@ var init_args = __esm({
|
|
|
1087
1090
|
"this-value",
|
|
1088
1091
|
"token-stdin",
|
|
1089
1092
|
"version",
|
|
1093
|
+
"yes",
|
|
1090
1094
|
"reporter",
|
|
1091
1095
|
"outputFile",
|
|
1092
1096
|
"output-file",
|
|
@@ -87879,6 +87883,12 @@ var init_variant_value_graph = __esm({
|
|
|
87879
87883
|
});
|
|
87880
87884
|
|
|
87881
87885
|
// ../src/database/project-version-intents.ts
|
|
87886
|
+
var project_version_intents_exports = {};
|
|
87887
|
+
__export(project_version_intents_exports, {
|
|
87888
|
+
ProjectVersionIntentType: () => ProjectVersionIntentType,
|
|
87889
|
+
createProjectVersionIntent: () => createProjectVersionIntent,
|
|
87890
|
+
isKnownProjectVersionIntentType: () => isKnownProjectVersionIntentType
|
|
87891
|
+
});
|
|
87882
87892
|
function createProjectVersionIntent(type, details) {
|
|
87883
87893
|
if (details === void 0) {
|
|
87884
87894
|
return {
|
|
@@ -96153,6 +96163,151 @@ function variantRootValueId2(data) {
|
|
|
96153
96163
|
if (typeof valueId !== "string" || valueId.length === 0) return null;
|
|
96154
96164
|
return valueId;
|
|
96155
96165
|
}
|
|
96166
|
+
function planProjectVersionTransaction(changes, atomicGroups) {
|
|
96167
|
+
if (changes.length === 0) {
|
|
96168
|
+
throw new Error("A project-version transaction plan requires changes.");
|
|
96169
|
+
}
|
|
96170
|
+
const changeCosts = changes.map(estimateProjectVersionChangeCost);
|
|
96171
|
+
const groups = normalizeAtomicGroups(changes.length, atomicGroups);
|
|
96172
|
+
let totalEstimatedCost = changeCosts.reduce(
|
|
96173
|
+
addProjectVersionTransactionBudgets,
|
|
96174
|
+
IMMEDIATE_FIXED_COST
|
|
96175
|
+
);
|
|
96176
|
+
if (changes.some(projectVersionChangeCanTriggerWholeGraphValidation)) {
|
|
96177
|
+
totalEstimatedCost = addProjectVersionTransactionBudgets(
|
|
96178
|
+
totalEstimatedCost,
|
|
96179
|
+
WHOLE_GRAPH_VALIDATION_SURCHARGE
|
|
96180
|
+
);
|
|
96181
|
+
}
|
|
96182
|
+
const limitingResource = firstExceededResource(
|
|
96183
|
+
totalEstimatedCost,
|
|
96184
|
+
PROJECT_TRANSACTION_IMMEDIATE_BUDGET
|
|
96185
|
+
);
|
|
96186
|
+
if (limitingResource === null) {
|
|
96187
|
+
return {
|
|
96188
|
+
path: "immediate",
|
|
96189
|
+
limitingResource: null,
|
|
96190
|
+
totalEstimatedCost,
|
|
96191
|
+
chunks: [
|
|
96192
|
+
{
|
|
96193
|
+
firstChangeIndex: 0,
|
|
96194
|
+
changeCount: changes.length,
|
|
96195
|
+
estimatedCost: totalEstimatedCost
|
|
96196
|
+
}
|
|
96197
|
+
]
|
|
96198
|
+
};
|
|
96199
|
+
}
|
|
96200
|
+
const chunks = [];
|
|
96201
|
+
let firstChangeIndex = 0;
|
|
96202
|
+
let chunkCost = CHUNK_FIXED_COST;
|
|
96203
|
+
let changeCount = 0;
|
|
96204
|
+
for (const group of groups) {
|
|
96205
|
+
const groupCost = changeCosts.slice(group.firstChangeIndex, group.firstChangeIndex + group.changeCount).reduce(addProjectVersionTransactionBudgets, EMPTY_BUDGET);
|
|
96206
|
+
const nativeExceeded = firstExceededResource(
|
|
96207
|
+
addProjectVersionTransactionBudgets(CHUNK_FIXED_COST, groupCost),
|
|
96208
|
+
PROJECT_TRANSACTION_SINGLE_CHANGE_NATIVE_BUDGET
|
|
96209
|
+
);
|
|
96210
|
+
if (nativeExceeded !== null) {
|
|
96211
|
+
throw new Error(
|
|
96212
|
+
`Project-version atomic group starting at change ${group.firstChangeIndex} (${group.changeCount} changes) exceeds the native worker budget for ${nativeExceeded}.`
|
|
96213
|
+
);
|
|
96214
|
+
}
|
|
96215
|
+
const nextCost = addProjectVersionTransactionBudgets(chunkCost, groupCost);
|
|
96216
|
+
const exceeded = firstExceededResource(
|
|
96217
|
+
nextCost,
|
|
96218
|
+
PROJECT_TRANSACTION_WORKER_BUDGET
|
|
96219
|
+
);
|
|
96220
|
+
if (exceeded !== null && changeCount > 0) {
|
|
96221
|
+
chunks.push({ firstChangeIndex, changeCount, estimatedCost: chunkCost });
|
|
96222
|
+
firstChangeIndex = group.firstChangeIndex;
|
|
96223
|
+
changeCount = 0;
|
|
96224
|
+
chunkCost = CHUNK_FIXED_COST;
|
|
96225
|
+
}
|
|
96226
|
+
chunkCost = addProjectVersionTransactionBudgets(chunkCost, groupCost);
|
|
96227
|
+
changeCount += group.changeCount;
|
|
96228
|
+
}
|
|
96229
|
+
if (changeCount > 0) {
|
|
96230
|
+
chunks.push({ firstChangeIndex, changeCount, estimatedCost: chunkCost });
|
|
96231
|
+
}
|
|
96232
|
+
return {
|
|
96233
|
+
path: "asynchronous",
|
|
96234
|
+
limitingResource,
|
|
96235
|
+
totalEstimatedCost,
|
|
96236
|
+
chunks
|
|
96237
|
+
};
|
|
96238
|
+
}
|
|
96239
|
+
function projectVersionChangeCanTriggerWholeGraphValidation(change) {
|
|
96240
|
+
if (change.recordKind === "enum" || change.recordKind === "member" || change.recordKind === "project" || change.recordKind === "internal-record-relation") {
|
|
96241
|
+
return true;
|
|
96242
|
+
}
|
|
96243
|
+
if (change.recordKind === "class") return change.operation !== "create";
|
|
96244
|
+
if (change.recordKind !== "value") return false;
|
|
96245
|
+
if (change.operation === "delete") return true;
|
|
96246
|
+
return valueChangeCarriesWorldLayerOverride(change);
|
|
96247
|
+
}
|
|
96248
|
+
function valueChangeCarriesWorldLayerOverride(change) {
|
|
96249
|
+
const nextData = change.nextData;
|
|
96250
|
+
if (nextData === null || typeof nextData !== "object") return false;
|
|
96251
|
+
if (Array.isArray(nextData)) return false;
|
|
96252
|
+
const value = nextData.value;
|
|
96253
|
+
if (value === null || typeof value !== "object") return false;
|
|
96254
|
+
if (Array.isArray(value)) return false;
|
|
96255
|
+
return typeof value.layerOverrideValueId === "string";
|
|
96256
|
+
}
|
|
96257
|
+
function normalizeAtomicGroups(changeCount, groups) {
|
|
96258
|
+
if (groups === void 0) {
|
|
96259
|
+
return Array.from({ length: changeCount }, (_, firstChangeIndex) => ({
|
|
96260
|
+
firstChangeIndex,
|
|
96261
|
+
changeCount: 1
|
|
96262
|
+
}));
|
|
96263
|
+
}
|
|
96264
|
+
let nextChangeIndex = 0;
|
|
96265
|
+
for (const group of groups) {
|
|
96266
|
+
if (!Number.isSafeInteger(group.firstChangeIndex) || !Number.isSafeInteger(group.changeCount) || group.firstChangeIndex !== nextChangeIndex || group.changeCount < 1) {
|
|
96267
|
+
throw new Error(
|
|
96268
|
+
"Project-version atomic groups must be positive, ordered, and contiguous."
|
|
96269
|
+
);
|
|
96270
|
+
}
|
|
96271
|
+
nextChangeIndex += group.changeCount;
|
|
96272
|
+
}
|
|
96273
|
+
if (nextChangeIndex !== changeCount) {
|
|
96274
|
+
throw new Error(
|
|
96275
|
+
`Project-version atomic groups cover ${nextChangeIndex}/${changeCount} changes.`
|
|
96276
|
+
);
|
|
96277
|
+
}
|
|
96278
|
+
return [...groups];
|
|
96279
|
+
}
|
|
96280
|
+
function estimateProjectVersionChangeCost(change) {
|
|
96281
|
+
const encoded = canonicalJsonStringify(change);
|
|
96282
|
+
const payloadBytes = new TextEncoder().encode(encoded).byteLength;
|
|
96283
|
+
const hasBase = change.operation !== "create";
|
|
96284
|
+
const counts = changeCountCost(change);
|
|
96285
|
+
return {
|
|
96286
|
+
argumentBytes: payloadBytes + 128,
|
|
96287
|
+
bytesRead: payloadBytes * (hasBase ? 3 : 1) + CHANGE_BYTES_READ_OVERHEAD,
|
|
96288
|
+
bytesWritten: payloadBytes * 2 + CHANGE_BYTES_WRITTEN_OVERHEAD,
|
|
96289
|
+
documentsRead: counts.documentsRead,
|
|
96290
|
+
documentsWritten: counts.documentsWritten,
|
|
96291
|
+
databaseQueries: counts.databaseQueries,
|
|
96292
|
+
// Not metered by Convex: `TransactionMetrics` reports seven dimensions and
|
|
96293
|
+
// this is not one of them, so it cannot be measured the way the others
|
|
96294
|
+
// were. Every index range a change opens is also a database query, so the
|
|
96295
|
+
// measured query count is a strict upper bound on it. Charging exactly
|
|
96296
|
+
// that keeps the dimension a true bound instead of an invented number,
|
|
96297
|
+
// and keeps it dominated by `databaseQueries` rather than silently
|
|
96298
|
+
// becoming the binding constraint on a well-measured plan.
|
|
96299
|
+
indexRanges: counts.databaseQueries,
|
|
96300
|
+
functionsScheduled: 0,
|
|
96301
|
+
scheduledFunctionArgsBytes: 0,
|
|
96302
|
+
returnBytes: payloadBytes + 512
|
|
96303
|
+
};
|
|
96304
|
+
}
|
|
96305
|
+
function changeCountCost(change) {
|
|
96306
|
+
if (change.recordKind === "value") {
|
|
96307
|
+
return VALUE_CHANGE_COUNT_COSTS[change.operation];
|
|
96308
|
+
}
|
|
96309
|
+
return SCHEMA_CHANGE_COUNT_COSTS[change.operation];
|
|
96310
|
+
}
|
|
96156
96311
|
function addProjectVersionTransactionBudgets(left, right) {
|
|
96157
96312
|
return {
|
|
96158
96313
|
argumentBytes: left.argumentBytes + right.argumentBytes,
|
|
@@ -96167,7 +96322,15 @@ function addProjectVersionTransactionBudgets(left, right) {
|
|
|
96167
96322
|
returnBytes: left.returnBytes + right.returnBytes
|
|
96168
96323
|
};
|
|
96169
96324
|
}
|
|
96170
|
-
|
|
96325
|
+
function firstExceededResource(cost, target) {
|
|
96326
|
+
for (const resource of Object.keys(
|
|
96327
|
+
EMPTY_BUDGET
|
|
96328
|
+
)) {
|
|
96329
|
+
if (cost[resource] > target[resource]) return resource;
|
|
96330
|
+
}
|
|
96331
|
+
return null;
|
|
96332
|
+
}
|
|
96333
|
+
var KIB, MIB, PROJECT_TRANSACTION_CHUNK_PAYLOAD_TARGET_BYTES, PROJECT_TRANSACTION_IMMEDIATE_BUDGET, PROJECT_TRANSACTION_WORKER_BUDGET, PROJECT_TRANSACTION_SINGLE_CHANGE_NATIVE_BUDGET, EMPTY_BUDGET, CHUNK_GRAPH_VALIDATION_RESERVE, CHUNK_FIXED_COST, IMMEDIATE_FIXED_COST, WHOLE_GRAPH_VALIDATION_SURCHARGE, VALUE_CHANGE_COUNT_COSTS, SCHEMA_CHANGE_COUNT_COSTS, CHANGE_BYTES_READ_OVERHEAD, CHANGE_BYTES_WRITTEN_OVERHEAD;
|
|
96171
96334
|
var init_project_version_transaction_planning = __esm({
|
|
96172
96335
|
"../src/database/project-version-transaction-planning.ts"() {
|
|
96173
96336
|
"use strict";
|
|
@@ -96256,6 +96419,16 @@ var init_project_version_transaction_planning = __esm({
|
|
|
96256
96419
|
databaseQueries: 410,
|
|
96257
96420
|
indexRanges: 410
|
|
96258
96421
|
};
|
|
96422
|
+
VALUE_CHANGE_COUNT_COSTS = {
|
|
96423
|
+
create: { documentsRead: 3, documentsWritten: 3, databaseQueries: 8 },
|
|
96424
|
+
update: { documentsRead: 6, documentsWritten: 3, databaseQueries: 9 },
|
|
96425
|
+
delete: { documentsRead: 6, documentsWritten: 3, databaseQueries: 14 }
|
|
96426
|
+
};
|
|
96427
|
+
SCHEMA_CHANGE_COUNT_COSTS = {
|
|
96428
|
+
create: { documentsRead: 3, documentsWritten: 5, databaseQueries: 12 },
|
|
96429
|
+
update: { documentsRead: 6, documentsWritten: 5, databaseQueries: 22 },
|
|
96430
|
+
delete: { documentsRead: 6, documentsWritten: 5, databaseQueries: 22 }
|
|
96431
|
+
};
|
|
96259
96432
|
CHANGE_BYTES_READ_OVERHEAD = 4 * KIB;
|
|
96260
96433
|
CHANGE_BYTES_WRITTEN_OVERHEAD = 4 * KIB;
|
|
96261
96434
|
}
|
|
@@ -116774,6 +116947,14 @@ var init_api = __esm({
|
|
|
116774
116947
|
});
|
|
116775
116948
|
|
|
116776
116949
|
// src/convex.ts
|
|
116950
|
+
var convex_exports = {};
|
|
116951
|
+
__export(convex_exports, {
|
|
116952
|
+
api: () => api2,
|
|
116953
|
+
createConvexClient: () => createConvexClient,
|
|
116954
|
+
createConvexClientForApi: () => createConvexClientForApi,
|
|
116955
|
+
mintConvexJwt: () => mintConvexJwt,
|
|
116956
|
+
resolveConvexUrl: () => resolveConvexUrl
|
|
116957
|
+
});
|
|
116777
116958
|
import { ConvexHttpClient } from "convex/browser";
|
|
116778
116959
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
116779
116960
|
async function mintConvexJwt(apiBaseUrl) {
|
|
@@ -117560,6 +117741,7 @@ function acceptSourceEquivalentConflictBases(workspace, mainLocale = workspaceMa
|
|
|
117560
117741
|
};
|
|
117561
117742
|
delete next.conflictServerHash;
|
|
117562
117743
|
delete next.conflictServerData;
|
|
117744
|
+
delete next.conflictFiles;
|
|
117563
117745
|
workspace.state.records[key] = next;
|
|
117564
117746
|
accepted += 1;
|
|
117565
117747
|
}
|
|
@@ -118577,34 +118759,39 @@ async function finishFormat4Pull(args) {
|
|
|
118577
118759
|
const serverByPath = new Map(
|
|
118578
118760
|
(serverResult?.files ?? []).map((file) => [file.path, file])
|
|
118579
118761
|
);
|
|
118762
|
+
const localRecordFiles = declaringSourcePathsV4(localResult);
|
|
118763
|
+
const serverRecordFiles = serverResult === null ? /* @__PURE__ */ new Map() : declaringSourcePathsV4(serverResult);
|
|
118580
118764
|
const conflictPaths = /* @__PURE__ */ new Set();
|
|
118765
|
+
const conflictPathsByKey = /* @__PURE__ */ new Map();
|
|
118581
118766
|
const authoredLocalConflictKeysByPath = /* @__PURE__ */ new Map();
|
|
118582
118767
|
for (const key of conflictKeys) {
|
|
118583
|
-
const
|
|
118584
|
-
const
|
|
118585
|
-
|
|
118586
|
-
|
|
118587
|
-
if (
|
|
118588
|
-
|
|
118768
|
+
const paths = [...localRecordFiles.get(key) ?? []];
|
|
118769
|
+
for (const path of serverRecordFiles.get(key) ?? []) {
|
|
118770
|
+
if (!paths.includes(path)) paths.push(path);
|
|
118771
|
+
}
|
|
118772
|
+
if (paths.length === 0) {
|
|
118773
|
+
const previousPath = workspace.state.records[key]?.file;
|
|
118774
|
+
if (previousPath === void 0) continue;
|
|
118775
|
+
paths.push(previousPath);
|
|
118776
|
+
const keys = authoredLocalConflictKeysByPath.get(previousPath) ?? [];
|
|
118777
|
+
keys.push(key);
|
|
118778
|
+
authoredLocalConflictKeysByPath.set(previousPath, keys);
|
|
118589
118779
|
}
|
|
118590
|
-
const
|
|
118591
|
-
|
|
118592
|
-
authoredLocalConflictKeysByPath.set(path, keys);
|
|
118780
|
+
for (const path of paths) conflictPaths.add(path);
|
|
118781
|
+
conflictPathsByKey.set(key, paths);
|
|
118593
118782
|
}
|
|
118594
118783
|
const emittedPaths = /* @__PURE__ */ new Set([
|
|
118595
118784
|
...localByPath.keys(),
|
|
118596
118785
|
...[...conflictPaths].filter((path) => serverByPath.has(path)),
|
|
118597
118786
|
...authoredLocalConflictKeysByPath.keys()
|
|
118598
118787
|
]);
|
|
118599
|
-
const localRecordFiles = declaringSourcePathsV4(localResult);
|
|
118600
118788
|
const rewritePaths = projectSourcePathsRequiringRewriteV4({
|
|
118601
118789
|
destructive,
|
|
118602
118790
|
localStatus,
|
|
118603
118791
|
plans,
|
|
118604
118792
|
localRecordFiles,
|
|
118605
|
-
serverRecordFiles
|
|
118793
|
+
serverRecordFiles,
|
|
118606
118794
|
previousRecords: workspace.state.records,
|
|
118607
|
-
conflictPaths,
|
|
118608
118795
|
emittedPaths,
|
|
118609
118796
|
mainLocale: workspaceMainLocale(workspace.state.records)
|
|
118610
118797
|
});
|
|
@@ -118612,6 +118799,7 @@ async function finishFormat4Pull(args) {
|
|
|
118612
118799
|
for (const path of localRecordFiles.get(key) ?? []) rewritePaths.add(path);
|
|
118613
118800
|
}
|
|
118614
118801
|
const resolveSourcePath = createWorkspaceSourcePathResolver(workspace.root);
|
|
118802
|
+
const markedPaths = /* @__PURE__ */ new Set();
|
|
118615
118803
|
let written = 0;
|
|
118616
118804
|
for (const path of emittedPaths) {
|
|
118617
118805
|
const absolute = resolveSourcePath(path);
|
|
@@ -118632,8 +118820,10 @@ async function finishFormat4Pull(args) {
|
|
|
118632
118820
|
versionId: workspace.config.versionId
|
|
118633
118821
|
}) : local;
|
|
118634
118822
|
if (content === void 0) continue;
|
|
118823
|
+
const marked = conflictPaths.has(path) && detectConflictMarkers(content) !== null;
|
|
118824
|
+
if (marked) markedPaths.add(path);
|
|
118635
118825
|
mkdirSync8(dirname8(absolute), { recursive: true });
|
|
118636
|
-
if (existing !== null && !rewritePaths.has(path)) continue;
|
|
118826
|
+
if (existing !== null && !marked && !rewritePaths.has(path)) continue;
|
|
118637
118827
|
if (existing !== content) {
|
|
118638
118828
|
writeFileSync8(absolute, content, "utf8");
|
|
118639
118829
|
written += 1;
|
|
@@ -118666,12 +118856,16 @@ async function finishFormat4Pull(args) {
|
|
|
118666
118856
|
continue;
|
|
118667
118857
|
}
|
|
118668
118858
|
const file = localResult.recordFiles.get(key) ?? serverResult?.recordFiles.get(key) ?? baseState?.file;
|
|
118859
|
+
const conflictFiles = (conflictPathsByKey.get(key) ?? []).filter(
|
|
118860
|
+
(conflictPath) => markedPaths.has(conflictPath)
|
|
118861
|
+
);
|
|
118669
118862
|
records2[key] = plan.conflicted ? {
|
|
118670
118863
|
recordKind,
|
|
118671
118864
|
recordId,
|
|
118672
118865
|
contentHash: baseState?.contentHash ?? "",
|
|
118673
118866
|
data: baseState?.data,
|
|
118674
118867
|
...file ? { file } : {},
|
|
118868
|
+
...conflictFiles.length === 0 ? {} : { conflictFiles },
|
|
118675
118869
|
conflictServerHash: serverRecord?.contentHash ?? null,
|
|
118676
118870
|
conflictServerData: plan.serverData
|
|
118677
118871
|
} : {
|
|
@@ -118758,9 +118952,9 @@ async function finishFormat4Pull(args) {
|
|
|
118758
118952
|
);
|
|
118759
118953
|
for (const key of conflictKeys) console.log(` ${sym.fail} ${key}`);
|
|
118760
118954
|
for (const tear of staleConflictTears) {
|
|
118761
|
-
const
|
|
118955
|
+
const rowLabel2 = tear.unanchoredValueCount === 1 ? "row" : "rows";
|
|
118762
118956
|
warn(
|
|
118763
|
-
`Conflict ${tear.recordKind} "${tear.recordId}" retained a local base that does not anchor ${tear.unanchoredValueCount} newly pulled descendant value ${
|
|
118957
|
+
`Conflict ${tear.recordKind} "${tear.recordId}" retained a local base that does not anchor ${tear.unanchoredValueCount} newly pulled descendant value ${rowLabel2}. Resolve it with "neo resolve --mine" or "neo resolve --theirs" before pushing.`
|
|
118764
118958
|
);
|
|
118765
118959
|
}
|
|
118766
118960
|
}
|
|
@@ -118934,7 +119128,7 @@ function projectSourcePathsRequiringRewriteV4(args) {
|
|
|
118934
119128
|
if (args.destructive || args.localStatus === null) {
|
|
118935
119129
|
return new Set(args.emittedPaths);
|
|
118936
119130
|
}
|
|
118937
|
-
const result = new Set(
|
|
119131
|
+
const result = /* @__PURE__ */ new Set();
|
|
118938
119132
|
for (const [key, plan] of args.plans) {
|
|
118939
119133
|
const paths = args.localRecordFiles.get(key) ?? args.serverRecordFiles.get(key);
|
|
118940
119134
|
if (paths === void 0) continue;
|
|
@@ -126300,7 +126494,7 @@ var init_registry2 = __esm({
|
|
|
126300
126494
|
PROJECT_SCHEMA_CONTRACT = Object.freeze({
|
|
126301
126495
|
formatVersion: 4,
|
|
126302
126496
|
contractVersion: "4.1",
|
|
126303
|
-
cliVersion: "0.
|
|
126497
|
+
cliVersion: "0.44.0",
|
|
126304
126498
|
projectFileUploadBatchSize: 32,
|
|
126305
126499
|
documentRecords: {
|
|
126306
126500
|
member: {
|
|
@@ -129434,6 +129628,7 @@ var init_test = __esm({
|
|
|
129434
129628
|
var doctor_exports = {};
|
|
129435
129629
|
__export(doctor_exports, {
|
|
129436
129630
|
inspectNeoDoctor: () => inspectNeoDoctor,
|
|
129631
|
+
planUnprojectedValueRowRepair: () => planUnprojectedValueRowRepair,
|
|
129437
129632
|
runDoctor: () => runDoctor
|
|
129438
129633
|
});
|
|
129439
129634
|
import {
|
|
@@ -129666,7 +129861,7 @@ function workspacePath(root, path) {
|
|
|
129666
129861
|
function errorMessage3(error) {
|
|
129667
129862
|
return error instanceof Error ? error.message : String(error);
|
|
129668
129863
|
}
|
|
129669
|
-
async function runDoctor(workspace,
|
|
129864
|
+
async function runDoctor(workspace, options) {
|
|
129670
129865
|
const inspected = inspectNeoDoctor(workspace);
|
|
129671
129866
|
let errors = [];
|
|
129672
129867
|
try {
|
|
@@ -129685,7 +129880,7 @@ async function runDoctor(workspace, json) {
|
|
|
129685
129880
|
compatible: errors.length === 0
|
|
129686
129881
|
}
|
|
129687
129882
|
};
|
|
129688
|
-
if (json) {
|
|
129883
|
+
if (options.json) {
|
|
129689
129884
|
console.log(JSON.stringify(report, null, 2));
|
|
129690
129885
|
if (!report.ok) process.exitCode = 1;
|
|
129691
129886
|
return;
|
|
@@ -129732,23 +129927,213 @@ async function runDoctor(workspace, json) {
|
|
|
129732
129927
|
console.log(
|
|
129733
129928
|
report.ok ? "Project authoring contracts are compatible." : "Project authoring contracts need attention."
|
|
129734
129929
|
);
|
|
129735
|
-
if (!report.document.consistent) {
|
|
129930
|
+
if (!report.document.consistent && !options.fix) {
|
|
129736
129931
|
console.log(
|
|
129737
129932
|
`${String(report.document.findings.length)} malformed record${report.document.findings.length === 1 ? "" : "s"} in the pulled document \u2014 see \`--json\` for the repair entries.`
|
|
129738
129933
|
);
|
|
129739
129934
|
}
|
|
129740
129935
|
if (!report.ok) process.exitCode = 1;
|
|
129936
|
+
if (options.fix) {
|
|
129937
|
+
await repairUnprojectedValueRows(
|
|
129938
|
+
workspace,
|
|
129939
|
+
report.document.findings,
|
|
129940
|
+
options.yes
|
|
129941
|
+
);
|
|
129942
|
+
}
|
|
129943
|
+
}
|
|
129944
|
+
function planUnprojectedValueRowRepair(workspace, findings) {
|
|
129945
|
+
const roots = [];
|
|
129946
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
129947
|
+
const unrepairableKinds = /* @__PURE__ */ new Set();
|
|
129948
|
+
for (const finding of findings) {
|
|
129949
|
+
if (finding.kind !== "unprojected-value-row") {
|
|
129950
|
+
unrepairableKinds.add(finding.kind);
|
|
129951
|
+
continue;
|
|
129952
|
+
}
|
|
129953
|
+
const owned = [];
|
|
129954
|
+
for (const valueId of finding.repair.orphanedValueIds) {
|
|
129955
|
+
if (claimed.has(valueId)) continue;
|
|
129956
|
+
claimed.add(valueId);
|
|
129957
|
+
const record3 = workspace.state.records[recordStateKey("value", valueId)];
|
|
129958
|
+
if (record3 === void 0) {
|
|
129959
|
+
throw new Error(
|
|
129960
|
+
`Doctor repair planned value row "${valueId}", which the pulled state does not hold. Run \`neo pull\` and rerun \`neo doctor --fix\`.`
|
|
129961
|
+
);
|
|
129962
|
+
}
|
|
129963
|
+
owned.push(record3);
|
|
129964
|
+
}
|
|
129965
|
+
if (owned.length === 0) continue;
|
|
129966
|
+
roots.push({ valueId: finding.repair.valueId, rows: owned });
|
|
129967
|
+
}
|
|
129968
|
+
return { roots, unrepairableKinds: [...unrepairableKinds].sort() };
|
|
129969
|
+
}
|
|
129970
|
+
function rowLabel(count) {
|
|
129971
|
+
return `${String(count)} value row${count === 1 ? "" : "s"}`;
|
|
129741
129972
|
}
|
|
129742
|
-
|
|
129973
|
+
async function repairUnprojectedValueRows(workspace, findings, yes) {
|
|
129974
|
+
const plan = planUnprojectedValueRowRepair(workspace, findings);
|
|
129975
|
+
if (plan.unrepairableKinds.length > 0) {
|
|
129976
|
+
warn(
|
|
129977
|
+
`No automatic repair exists for ${plan.unrepairableKinds.join(", ")}; repairing unprojected value rows only.`
|
|
129978
|
+
);
|
|
129979
|
+
}
|
|
129980
|
+
if (plan.roots.length === 0) {
|
|
129981
|
+
console.log("Nothing to repair.");
|
|
129982
|
+
return;
|
|
129983
|
+
}
|
|
129984
|
+
const rows = plan.roots.flatMap((root) => root.rows);
|
|
129985
|
+
const pulledHeadTransactionHash = workspace.state.headTransactionHash;
|
|
129986
|
+
if (typeof pulledHeadTransactionHash !== "string") {
|
|
129987
|
+
throw new Error(
|
|
129988
|
+
'This workspace has no recorded project head, so `neo doctor --fix` cannot prove its deletions land on the state the audit read. Run "neo pull", then rerun.'
|
|
129989
|
+
);
|
|
129990
|
+
}
|
|
129991
|
+
const rootLabel = `${String(plan.roots.length)} orphan root${plan.roots.length === 1 ? "" : "s"}`;
|
|
129992
|
+
console.log(
|
|
129993
|
+
`${sym.info} ${rowLabel(rows.length)} across ${rootLabel} will be deleted:`
|
|
129994
|
+
);
|
|
129995
|
+
for (const root of plan.roots.slice(0, REPAIR_ROOT_PREVIEW_LIMIT)) {
|
|
129996
|
+
console.log(
|
|
129997
|
+
` ${sym.arrow} ${root.valueId} \u2014 ${rowLabel(root.rows.length)}`
|
|
129998
|
+
);
|
|
129999
|
+
}
|
|
130000
|
+
const hidden = plan.roots.length - REPAIR_ROOT_PREVIEW_LIMIT;
|
|
130001
|
+
if (hidden > 0) {
|
|
130002
|
+
note(
|
|
130003
|
+
` \u2026and ${String(hidden)} more orphan root${hidden === 1 ? "" : "s"}.`
|
|
130004
|
+
);
|
|
130005
|
+
}
|
|
130006
|
+
if (!yes && !isInteractive()) {
|
|
130007
|
+
throw new Error(
|
|
130008
|
+
"neo doctor --fix needs a terminal to confirm the deletion. Re-run with --yes to delete without confirming."
|
|
130009
|
+
);
|
|
130010
|
+
}
|
|
130011
|
+
const confirmed = yes || await promptConfirm({
|
|
130012
|
+
message: `Delete ${rowLabel(rows.length)}?`,
|
|
130013
|
+
fallback: false,
|
|
130014
|
+
defaultValue: false
|
|
130015
|
+
});
|
|
130016
|
+
if (!confirmed) {
|
|
130017
|
+
console.log("Cancelled. No records were deleted.");
|
|
130018
|
+
return;
|
|
130019
|
+
}
|
|
130020
|
+
const { api: api3, createConvexClient: createConvexClient2 } = await Promise.resolve().then(() => (init_convex(), convex_exports));
|
|
130021
|
+
const { createProjectVersionIntent: createProjectVersionIntent2 } = await Promise.resolve().then(() => (init_project_version_intents(), project_version_intents_exports));
|
|
130022
|
+
const changes = rows.map((row) => ({
|
|
130023
|
+
recordKind: "value",
|
|
130024
|
+
recordId: row.recordId,
|
|
130025
|
+
operation: "delete",
|
|
130026
|
+
deleted: true,
|
|
130027
|
+
expectedBaseContentHash: row.contentHash,
|
|
130028
|
+
intent: createProjectVersionIntent2("value.delete", {
|
|
130029
|
+
source: "neo-cli-doctor"
|
|
130030
|
+
})
|
|
130031
|
+
}));
|
|
130032
|
+
const batchSize = immediateCommitBatchSize(changes);
|
|
130033
|
+
const convex = await createConvexClient2(workspace);
|
|
130034
|
+
const progress = spinner(`Deleting ${rowLabel(rows.length)}\u2026`);
|
|
130035
|
+
let deleted = 0;
|
|
130036
|
+
let expectedHeadTransactionHash = pulledHeadTransactionHash;
|
|
130037
|
+
for (let index = 0; index < changes.length; index += batchSize) {
|
|
130038
|
+
const batch = changes.slice(index, index + batchSize);
|
|
130039
|
+
let transactionHash;
|
|
130040
|
+
try {
|
|
130041
|
+
const result = await convex.mutation(
|
|
130042
|
+
api3.serverProjectRecordOperations.commitFromSession,
|
|
130043
|
+
{
|
|
130044
|
+
projectId: workspace.config.projectId,
|
|
130045
|
+
versionId: workspace.config.versionId,
|
|
130046
|
+
operation: "delete",
|
|
130047
|
+
changes: batch,
|
|
130048
|
+
summary: `neo doctor --fix (${rowLabel(batch.length)})`,
|
|
130049
|
+
expectedHeadTransactionHash
|
|
130050
|
+
}
|
|
130051
|
+
);
|
|
130052
|
+
transactionHash = committedTransactionHash(result);
|
|
130053
|
+
} catch (error) {
|
|
130054
|
+
progress.fail(
|
|
130055
|
+
deleted === 0 ? `Failed to delete ${rowLabel(batch.length)}.` : `Failed after deleting ${rowLabel(deleted)}.`
|
|
130056
|
+
);
|
|
130057
|
+
throw repairFailure(error, deleted);
|
|
130058
|
+
}
|
|
130059
|
+
if (transactionHash === null) continue;
|
|
130060
|
+
expectedHeadTransactionHash = transactionHash;
|
|
130061
|
+
deleted += batch.length;
|
|
130062
|
+
progress.update(
|
|
130063
|
+
`Deleting ${rowLabel(rows.length)}\u2026 ${String(deleted)} done`
|
|
130064
|
+
);
|
|
130065
|
+
}
|
|
130066
|
+
progress.succeed(`Deleted ${rowLabel(deleted)} across ${rootLabel}.`);
|
|
130067
|
+
console.log('Run "neo pull" to refresh the working copy.');
|
|
130068
|
+
}
|
|
130069
|
+
function immediateCommitBatchSize(changes) {
|
|
130070
|
+
let fits = 0;
|
|
130071
|
+
let low = 1;
|
|
130072
|
+
let high = changes.length;
|
|
130073
|
+
while (low <= high) {
|
|
130074
|
+
const size = Math.floor((low + high) / 2);
|
|
130075
|
+
if (planProjectVersionTransaction(changes.slice(0, size)).path === "immediate") {
|
|
130076
|
+
fits = size;
|
|
130077
|
+
low = size + 1;
|
|
130078
|
+
} else {
|
|
130079
|
+
high = size - 1;
|
|
130080
|
+
}
|
|
130081
|
+
}
|
|
130082
|
+
if (fits === 0) {
|
|
130083
|
+
throw new Error(
|
|
130084
|
+
"Deleting a single value row exceeds the immediate transaction budget, so `neo doctor --fix` cannot commit anything. The transaction cost model needs to change before this repair can run."
|
|
130085
|
+
);
|
|
130086
|
+
}
|
|
130087
|
+
return fits;
|
|
130088
|
+
}
|
|
130089
|
+
function committedTransactionHash(result) {
|
|
130090
|
+
if (!isObjectRecord2(result)) {
|
|
130091
|
+
throw new Error(
|
|
130092
|
+
"Doctor repair committed a deletion but the server returned no transaction envelope."
|
|
130093
|
+
);
|
|
130094
|
+
}
|
|
130095
|
+
if (result.transaction === null) return null;
|
|
130096
|
+
const transactionHash = isObjectRecord2(result.transaction) ? result.transaction.transactionHash : void 0;
|
|
130097
|
+
if (typeof transactionHash !== "string") {
|
|
130098
|
+
throw new Error(
|
|
130099
|
+
"Doctor repair committed a transaction that carries no hash, so the remaining deletions have no fast-forward base."
|
|
130100
|
+
);
|
|
130101
|
+
}
|
|
130102
|
+
return transactionHash;
|
|
130103
|
+
}
|
|
130104
|
+
function repairFailure(error, deleted) {
|
|
130105
|
+
const applied = deleted === 0 ? "" : ` ${rowLabel(deleted)} were already deleted.`;
|
|
130106
|
+
const code = convexErrorCode(error);
|
|
130107
|
+
if (code === "non-fast-forward" || code === "base-hash-conflict") {
|
|
130108
|
+
return new Error(
|
|
130109
|
+
`neo doctor --fix was refused: the project moved on since this workspace pulled, so the audit is stale.${applied} Run \`neo pull\` to re-derive it, then rerun.`,
|
|
130110
|
+
{ cause: error }
|
|
130111
|
+
);
|
|
130112
|
+
}
|
|
130113
|
+
return new Error(
|
|
130114
|
+
`neo doctor --fix could not delete the audited rows: ${errorMessage3(error)}${applied}`,
|
|
130115
|
+
{ cause: error }
|
|
130116
|
+
);
|
|
130117
|
+
}
|
|
130118
|
+
function convexErrorCode(error) {
|
|
130119
|
+
if (!isObjectRecord2(error)) return null;
|
|
130120
|
+
const data = error.data;
|
|
130121
|
+
if (!isObjectRecord2(data)) return null;
|
|
130122
|
+
return typeof data.error === "string" ? data.error : null;
|
|
130123
|
+
}
|
|
130124
|
+
var NEO_COMPILER_CONTRACT, NEO_VSCODE_EXTENSION_ID, NEO_VSCODE_EXTENSION_CONTRACT_VERSION, NEO_SOURCE_CONTRACT_VERSION, NEO_FILE_CONTRACT_VERSION, SOURCE_EXTENSIONS, FILE_CAPABILITIES, REPAIR_ROOT_PREVIEW_LIMIT;
|
|
129743
130125
|
var init_doctor = __esm({
|
|
129744
130126
|
"src/commands/doctor.ts"() {
|
|
129745
130127
|
"use strict";
|
|
129746
130128
|
init_src();
|
|
129747
130129
|
init_workspace();
|
|
130130
|
+
init_ui();
|
|
129748
130131
|
init_workspace_status();
|
|
129749
130132
|
init_source_diagnostics();
|
|
129750
130133
|
init_project_documents();
|
|
129751
130134
|
init_project_integrity();
|
|
130135
|
+
init_project_version_transaction_planning();
|
|
130136
|
+
init_projection();
|
|
129752
130137
|
NEO_COMPILER_CONTRACT = "ProjectSourceAnalysisV4";
|
|
129753
130138
|
NEO_VSCODE_EXTENSION_ID = "neocompose.neo-compose-neoscript";
|
|
129754
130139
|
NEO_VSCODE_EXTENSION_CONTRACT_VERSION = "0.3.0";
|
|
@@ -129764,6 +130149,7 @@ var init_doctor = __esm({
|
|
|
129764
130149
|
{ extension: ".mp3", kind: "audio", mimeType: "audio/mpeg" },
|
|
129765
130150
|
{ extension: ".ogg", kind: "audio", mimeType: "audio/ogg" }
|
|
129766
130151
|
];
|
|
130152
|
+
REPAIR_ROOT_PREVIEW_LIMIT = 20;
|
|
129767
130153
|
}
|
|
129768
130154
|
});
|
|
129769
130155
|
|
|
@@ -132781,15 +133167,17 @@ __export(resolve_exports, {
|
|
|
132781
133167
|
runResolve: () => runResolve
|
|
132782
133168
|
});
|
|
132783
133169
|
import { readFileSync as readFileSync19, rmSync as rmSync8, writeFileSync as writeFileSync14 } from "node:fs";
|
|
132784
|
-
import { join as join20 } from "node:path";
|
|
133170
|
+
import { join as join20, relative as relative8 } from "node:path";
|
|
132785
133171
|
function runResolve(workspace, side) {
|
|
133172
|
+
const marked = readMarkedProjectSourcesV4(workspace.root);
|
|
133173
|
+
assertRecordConflictsResolveTogetherV4(
|
|
133174
|
+
workspace,
|
|
133175
|
+
new Set(marked.map((file) => file.path))
|
|
133176
|
+
);
|
|
132786
133177
|
const resolvedRecords = adoptServerConflictBases(workspace);
|
|
132787
133178
|
let resolvedFiles = 0;
|
|
132788
|
-
for (const
|
|
132789
|
-
|
|
132790
|
-
if (detectConflictMarkers(source) === null) continue;
|
|
132791
|
-
const resolved = resolveMarkers(source, side);
|
|
132792
|
-
writeFileSync14(filePath, resolved, "utf8");
|
|
133179
|
+
for (const file of marked) {
|
|
133180
|
+
writeFileSync14(file.absolutePath, resolveMarkers(file.source, side), "utf8");
|
|
132793
133181
|
resolvedFiles += 1;
|
|
132794
133182
|
}
|
|
132795
133183
|
let resolvedBinaries = 0;
|
|
@@ -132828,6 +133216,37 @@ function runResolve(workspace, side) {
|
|
|
132828
133216
|
`Resolved ${resolvedFiles} source file(s), ${resolvedRecords} record conflict(s), and ${resolvedBinaries} binary file(s) keeping the "${side}" side. Review with "neo diff", then "neo push".`
|
|
132829
133217
|
);
|
|
132830
133218
|
}
|
|
133219
|
+
function readMarkedProjectSourcesV4(root) {
|
|
133220
|
+
const files = [];
|
|
133221
|
+
for (const absolutePath of listProjectSourceFilesV4(root)) {
|
|
133222
|
+
const source = readFileSync19(absolutePath, "utf8");
|
|
133223
|
+
if (detectConflictMarkers(source) === null) continue;
|
|
133224
|
+
files.push({
|
|
133225
|
+
path: normalizeWorkspaceSourcePath(relative8(root, absolutePath)),
|
|
133226
|
+
absolutePath,
|
|
133227
|
+
source
|
|
133228
|
+
});
|
|
133229
|
+
}
|
|
133230
|
+
return files;
|
|
133231
|
+
}
|
|
133232
|
+
function assertRecordConflictsResolveTogetherV4(workspace, markedPaths) {
|
|
133233
|
+
for (const [key, record3] of Object.entries(workspace.state.records)) {
|
|
133234
|
+
const declaringPaths = record3.conflictFiles;
|
|
133235
|
+
if (declaringPaths === void 0) continue;
|
|
133236
|
+
const stillMarked = [];
|
|
133237
|
+
const alreadyResolved = [];
|
|
133238
|
+
for (const declaringPath of declaringPaths) {
|
|
133239
|
+
const path = normalizeWorkspaceSourcePath(declaringPath);
|
|
133240
|
+
if (markedPaths.has(path)) stillMarked.push(path);
|
|
133241
|
+
else alreadyResolved.push(path);
|
|
133242
|
+
}
|
|
133243
|
+
if (stillMarked.length === 0) continue;
|
|
133244
|
+
if (alreadyResolved.length === 0) continue;
|
|
133245
|
+
throw new Error(
|
|
133246
|
+
`The files declaring conflicted record ${key} disagree: ${stillMarked.join(", ")} still holds conflict markers while ${alreadyResolved.join(", ")} does not. Every file that declares one record resolves together \u2014 finish resolving the marked file(s) to the side you already kept, then run "neo resolve".`
|
|
133247
|
+
);
|
|
133248
|
+
}
|
|
133249
|
+
}
|
|
132831
133250
|
function adoptServerConflictBases(workspace) {
|
|
132832
133251
|
let adopted = 0;
|
|
132833
133252
|
for (const [key, record3] of Object.entries(workspace.state.records)) {
|
|
@@ -132839,6 +133258,7 @@ function adoptServerConflictBases(workspace) {
|
|
|
132839
133258
|
};
|
|
132840
133259
|
delete next.conflictServerHash;
|
|
132841
133260
|
delete next.conflictServerData;
|
|
133261
|
+
delete next.conflictFiles;
|
|
132842
133262
|
workspace.state.records[key] = next;
|
|
132843
133263
|
adopted += 1;
|
|
132844
133264
|
}
|
|
@@ -132886,6 +133306,7 @@ var init_resolve = __esm({
|
|
|
132886
133306
|
init_workspace();
|
|
132887
133307
|
init_workspace_status();
|
|
132888
133308
|
init_source_diagnostics();
|
|
133309
|
+
init_workspace_source_path();
|
|
132889
133310
|
init_project_files();
|
|
132890
133311
|
}
|
|
132891
133312
|
});
|
|
@@ -132936,7 +133357,7 @@ ${h("Working copy")}
|
|
|
132936
133357
|
diff ${d("[--json]")}
|
|
132937
133358
|
dev ${d("[--push]")}
|
|
132938
133359
|
resolve ${d("[--mine|--theirs]")}
|
|
132939
|
-
doctor ${d("[--json] validate contracts and audit the pulled document")}
|
|
133360
|
+
doctor ${d("[--json] [--fix [--yes]] validate contracts and audit the pulled document")}
|
|
132940
133361
|
|
|
132941
133362
|
${h("Branches & releases")}
|
|
132942
133363
|
branch ${d("list | create <name> | switch [ref] | delete <ref> | refresh")}
|
|
@@ -133022,11 +133443,14 @@ ${h("NSFunction preview")}
|
|
|
133022
133443
|
return `${h("neo doctor")} \u2014 validate project authoring contracts
|
|
133023
133444
|
|
|
133024
133445
|
${h("Usage")}
|
|
133025
|
-
neo doctor ${d("[--json]")}
|
|
133446
|
+
neo doctor ${d("[--json] [--fix [--yes]]")}
|
|
133026
133447
|
|
|
133027
133448
|
Neo validates the format-4 browser compiler, VS Code extension cache envelope,
|
|
133028
133449
|
tracked source, local .spec.neo compilation, and managed project-file
|
|
133029
133450
|
capabilities. It does not discover or require .NET.
|
|
133451
|
+
|
|
133452
|
+
${h("Repair")}
|
|
133453
|
+
${d("--fix deletes the value rows the pulled document's unprojected-value-row findings name, after listing them and confirming. --yes skips the prompt. --fix cannot be combined with --json.")}
|
|
133030
133454
|
`;
|
|
133031
133455
|
}
|
|
133032
133456
|
if (command === "dialogue") {
|
|
@@ -133083,7 +133507,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
|
|
|
133083
133507
|
async function main() {
|
|
133084
133508
|
const args = parseArgs(process.argv.slice(2));
|
|
133085
133509
|
if (args.command === "--version") {
|
|
133086
|
-
console.log("0.
|
|
133510
|
+
console.log("0.44.0");
|
|
133087
133511
|
return;
|
|
133088
133512
|
}
|
|
133089
133513
|
if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
|
|
@@ -133168,9 +133592,23 @@ async function main() {
|
|
|
133168
133592
|
return;
|
|
133169
133593
|
}
|
|
133170
133594
|
case "doctor": {
|
|
133595
|
+
assertAllowedFlags(args, /* @__PURE__ */ new Set(["api", "json", "fix", "yes"]));
|
|
133596
|
+
const json = boolFlag(args, "json");
|
|
133597
|
+
const fix = boolFlag(args, "fix");
|
|
133598
|
+
if (fix && json) {
|
|
133599
|
+
throw new NeoCliUsageError(
|
|
133600
|
+
"neo doctor --fix reports and confirms interactively; it has no --json form. Drop --json, or run `neo doctor --json` to read the findings."
|
|
133601
|
+
);
|
|
133602
|
+
}
|
|
133603
|
+
const yes = boolFlag(args, "yes");
|
|
133604
|
+
if (yes && !fix) {
|
|
133605
|
+
throw new NeoCliUsageError(
|
|
133606
|
+
"neo doctor --yes only answers the --fix confirmation. Add --fix, or drop --yes."
|
|
133607
|
+
);
|
|
133608
|
+
}
|
|
133171
133609
|
const workspace = loadWorkspaceForCommand(args);
|
|
133172
133610
|
const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
133173
|
-
await runDoctor2(workspace,
|
|
133611
|
+
await runDoctor2(workspace, { json, fix, yes });
|
|
133174
133612
|
return;
|
|
133175
133613
|
}
|
|
133176
133614
|
case "branch": {
|
package/package.json
CHANGED
|
@@ -83,7 +83,7 @@ wrappers.
|
|
|
83
83
|
The marker near the top of `SKILL.md` must exactly match the package version:
|
|
84
84
|
|
|
85
85
|
```html
|
|
86
|
-
<!-- reviewed-through-cli: 0.
|
|
86
|
+
<!-- reviewed-through-cli: 0.44.0 -->
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
The quoted version above is checked too, so this instruction cannot go stale
|
|
@@ -86,6 +86,12 @@ Resolve source conflicts by editing the final intended source. Use
|
|
|
86
86
|
`neo resolve --mine` or `neo resolve --theirs` only for a deliberate whole-side
|
|
87
87
|
choice. Pull, inspect, dry-run, and retry; there is no force-CAS path.
|
|
88
88
|
|
|
89
|
+
One record is one conflict, however many files declare it. A member declared by
|
|
90
|
+
more than one class is marked in every declaring file, and `neo resolve`
|
|
91
|
+
settles those files together; it refuses a record whose declarations are only
|
|
92
|
+
partly resolved rather than settling the rest to the other side and leaving the
|
|
93
|
+
declarations disagreeing.
|
|
94
|
+
|
|
89
95
|
Compiled initializer IR is server-owned and does not participate in authored
|
|
90
96
|
three-way merge decisions. Either `neo resolve` side adopts the server side as
|
|
91
97
|
the new base for every conflict it settles — `--theirs` then reads clean, and
|
|
@@ -185,6 +191,18 @@ row is invisible to `neo status` by construction, which is why `neo doctor` is
|
|
|
185
191
|
the only place it surfaces.
|
|
186
192
|
`neo doctor --json` carries a `repair` entry per finding, which is what a
|
|
187
193
|
targeted repair is handed instead of scanning a project to rediscover them.
|
|
194
|
+
`neo doctor --fix` consumes those entries for the `unprojected-value-row`
|
|
195
|
+
findings: it lists each orphan root with the number of rows it owns, confirms,
|
|
196
|
+
then deletes them. Two guards make a stale audit fail the commit rather than
|
|
197
|
+
delete a row that has since gained an owner: every row carries the CAS base the
|
|
198
|
+
last pull recorded, and every transaction expects the version head the previous
|
|
199
|
+
one produced, starting from the head that pull left. Adopting an orphan writes
|
|
200
|
+
its new owner rather than the row itself, so only the head guard sees that, and
|
|
201
|
+
a workspace with no recorded head is refused rather than written blind. More
|
|
202
|
+
rows than one transaction budgets for go up in batches sized from the shared
|
|
203
|
+
cost model against the immediate commit path. `--yes` answers the confirmation
|
|
204
|
+
for scripted runs, and `--fix` has no `--json` form. No other finding kind has
|
|
205
|
+
an automatic repair.
|
|
188
206
|
|
|
189
207
|
The Node CLI talks directly to authenticated Convex APIs through the
|
|
190
208
|
session-gated CAS boundary. Tokens use the OS credential store when available
|