@neocompose/cli 0.25.0 → 0.25.2
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,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.25.2] - 2026-08-09
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `neo doctor` now audits the pulled document itself, not just this working
|
|
8
|
+
copy's authoring contracts. A project can be perfectly clean to push and
|
|
9
|
+
still hold malformed records, and the first audit catches value rows whose
|
|
10
|
+
storage partition disagrees with their placement parent. `--json` carries a
|
|
11
|
+
`repair` entry per finding, which is what lets the matching repair touch only
|
|
12
|
+
the affected records instead of scanning a project to rediscover them.
|
|
13
|
+
|
|
14
|
+
## [0.25.1] - 2026-08-09
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Stop `First`, `FirstOrDefault`, and collection `Contains` scans as soon as their result is known. Terminal scans now resolve and charge only visited entries, preserve list/dictionary order and value-reference identity, and avoid intermediate entry arrays (P55).
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Seed the owned rows for a field a newly declared member adds to a
|
|
23
|
+
row-backed class default, instead of refusing the push with "no existing
|
|
24
|
+
owned value row". Adding an entry to a row-backed _list_ default has always
|
|
25
|
+
minted its rows; a class field now takes the same path, so replacing a class
|
|
26
|
+
member (`Name = "Blue"` → `Kind = .Blue` across every default that sets it)
|
|
27
|
+
pushes without editing each default in the web editor first. An `@id` on the
|
|
28
|
+
new field is honoured when it names an existing row and, when it does not,
|
|
29
|
+
held to the same UUID v4 identity rule list entries follow.
|
|
30
|
+
- Carry only the new rows and the ancestors that reach them in a member value
|
|
31
|
+
seed. Naming every row the pass touched made the seed depend on which
|
|
32
|
+
constructions the materialization cache preserved that run — so a push and
|
|
33
|
+
its own trusted re-lowering disagreed — and handed back rows stripped of the
|
|
34
|
+
`constructorArgs` a seed row cannot carry, which then failed to project back
|
|
35
|
+
to source. Pushing a new row anywhere under `root.Assets` previously failed
|
|
36
|
+
with "Authored value row … is missing memberId".
|
|
37
|
+
- Drop a stored class body key whose member the push deletes, instead of
|
|
38
|
+
carrying it into a schema that no longer has it while the same push
|
|
39
|
+
tombstones the row it names.
|
|
40
|
+
- Treat a member value seed as pushable work on its own. Adding a row to an
|
|
41
|
+
unordered list moves no stored record — membership is the new row's
|
|
42
|
+
`containerId` — so `neo status` and `neo push` reported a clean working copy
|
|
43
|
+
and silently dropped it. `neo status` now reports pending seeds too.
|
|
44
|
+
- Accept a stored row that spells "no class" as an explicit `null`, and adopt
|
|
45
|
+
the declared storage partition for an unstamped row reached through its
|
|
46
|
+
owning slot, in constructed static value graph validation. Rows materialized
|
|
47
|
+
before their partition was stamped (object-tree `Enabled`, P41 §4.3) sit in
|
|
48
|
+
main under a partitioned parent and failed a push over data it never wrote. A
|
|
49
|
+
row already stamped for a different partition is still rejected.
|
|
50
|
+
|
|
3
51
|
## [0.25.0] - 2026-08-07
|
|
4
52
|
|
|
5
53
|
### Added
|
package/dist/neo.mjs
CHANGED
|
@@ -61613,11 +61613,16 @@ function evalFunction(fn, scope, ctx) {
|
|
|
61613
61613
|
}
|
|
61614
61614
|
return c.includes(target);
|
|
61615
61615
|
}
|
|
61616
|
-
|
|
61617
|
-
|
|
61618
|
-
|
|
61619
|
-
|
|
61620
|
-
|
|
61616
|
+
const targetReferenceId = typeof target === "string" ? target : findKnownRowIdByValueReference(target, ctx);
|
|
61617
|
+
let containsResolvedEntry = false;
|
|
61618
|
+
iterateCollection(c, ctx, (entry, _key, valueId) => {
|
|
61619
|
+
if (valueId !== null && valueId === targetReferenceId || jsEqual(entry, target)) {
|
|
61620
|
+
containsResolvedEntry = true;
|
|
61621
|
+
return 1 /* Break */;
|
|
61622
|
+
}
|
|
61623
|
+
return 0 /* Continue */;
|
|
61624
|
+
});
|
|
61625
|
+
return containsResolvedEntry;
|
|
61621
61626
|
}
|
|
61622
61627
|
case "decimalOp" /* decimalOp */: {
|
|
61623
61628
|
const info = fn.info;
|
|
@@ -61772,6 +61777,7 @@ function evalFunction(fn, scope, ctx) {
|
|
|
61772
61777
|
out[String(key)] = valueId ?? entry;
|
|
61773
61778
|
}
|
|
61774
61779
|
}
|
|
61780
|
+
return 0 /* Continue */;
|
|
61775
61781
|
});
|
|
61776
61782
|
return out;
|
|
61777
61783
|
}
|
|
@@ -61783,10 +61789,9 @@ function evalFunction(fn, scope, ctx) {
|
|
|
61783
61789
|
const sentinel = /* @__PURE__ */ Symbol("not-found");
|
|
61784
61790
|
let found = sentinel;
|
|
61785
61791
|
iterateCollection(c, ctx, (entry, key) => {
|
|
61786
|
-
if (found !== sentinel) return;
|
|
61787
61792
|
if (!innerFn) {
|
|
61788
61793
|
found = entry;
|
|
61789
|
-
return
|
|
61794
|
+
return 1 /* Break */;
|
|
61790
61795
|
}
|
|
61791
61796
|
consumeBudget(ctx, "workUnits", 1, "work unit");
|
|
61792
61797
|
const innerScope = pushParams(
|
|
@@ -61803,7 +61808,9 @@ function evalFunction(fn, scope, ctx) {
|
|
|
61803
61808
|
);
|
|
61804
61809
|
if (result.kind === "return" && result.value === true) {
|
|
61805
61810
|
found = entry;
|
|
61811
|
+
return 1 /* Break */;
|
|
61806
61812
|
}
|
|
61813
|
+
return 0 /* Continue */;
|
|
61807
61814
|
});
|
|
61808
61815
|
if (found !== sentinel) return found;
|
|
61809
61816
|
if (fn.type === "first" /* first */) {
|
|
@@ -61841,6 +61848,7 @@ function evalFunction(fn, scope, ctx) {
|
|
|
61841
61848
|
);
|
|
61842
61849
|
out.push(result.value);
|
|
61843
61850
|
}
|
|
61851
|
+
return 0 /* Continue */;
|
|
61844
61852
|
});
|
|
61845
61853
|
return out;
|
|
61846
61854
|
}
|
|
@@ -64304,6 +64312,7 @@ function snapshotCollectionMembership(collection, ctx) {
|
|
|
64304
64312
|
const valid = forEachRawCollectionEntry(collection, ({ raw }) => {
|
|
64305
64313
|
consumeBudget(ctx, "collectionVisits", 1, "collection visit");
|
|
64306
64314
|
membership.push(raw);
|
|
64315
|
+
return 0 /* Continue */;
|
|
64307
64316
|
});
|
|
64308
64317
|
if (valid) return membership;
|
|
64309
64318
|
throw new NSGetterRuntimeError(
|
|
@@ -64314,21 +64323,23 @@ function forEachRawCollectionEntry(c, callback) {
|
|
|
64314
64323
|
if (Array.isArray(c)) {
|
|
64315
64324
|
for (let key = 0; key < c.length; key += 1) {
|
|
64316
64325
|
const raw = c[key];
|
|
64317
|
-
callback({
|
|
64326
|
+
const control = callback({
|
|
64318
64327
|
raw,
|
|
64319
64328
|
key,
|
|
64320
64329
|
valueId: typeof raw === "string" ? raw : null
|
|
64321
64330
|
});
|
|
64331
|
+
if (control === 1 /* Break */) break;
|
|
64322
64332
|
}
|
|
64323
64333
|
return true;
|
|
64324
64334
|
}
|
|
64325
64335
|
if (typeof c === "object" && c !== null) {
|
|
64326
64336
|
for (const [key, raw] of Object.entries(c)) {
|
|
64327
|
-
callback({
|
|
64337
|
+
const control = callback({
|
|
64328
64338
|
raw,
|
|
64329
64339
|
key,
|
|
64330
64340
|
valueId: typeof raw === "string" ? raw : null
|
|
64331
64341
|
});
|
|
64342
|
+
if (control === 1 /* Break */) break;
|
|
64332
64343
|
}
|
|
64333
64344
|
return true;
|
|
64334
64345
|
}
|
|
@@ -64339,6 +64350,7 @@ function collectionEntries(c, ctx) {
|
|
|
64339
64350
|
forEachRawCollectionEntry(c, ({ raw }) => {
|
|
64340
64351
|
consumeBudget(ctx, "collectionVisits", 1, "collection visit");
|
|
64341
64352
|
entries.push(resolveValueIfId(raw, ctx));
|
|
64353
|
+
return 0 /* Continue */;
|
|
64342
64354
|
});
|
|
64343
64355
|
return entries;
|
|
64344
64356
|
}
|
|
@@ -64346,7 +64358,7 @@ function iterateCollection(c, ctx, callback) {
|
|
|
64346
64358
|
forEachRawCollectionEntry(c, ({ raw, key, valueId }) => {
|
|
64347
64359
|
consumeBudget(ctx, "collectionVisits", 1, "collection visit");
|
|
64348
64360
|
const entry = resolveValueIfId(raw, ctx);
|
|
64349
|
-
callback(entry, key, valueId);
|
|
64361
|
+
return callback(entry, key, valueId);
|
|
64350
64362
|
});
|
|
64351
64363
|
}
|
|
64352
64364
|
function pushParams(parent, parameters, positional, isList) {
|
|
@@ -78101,13 +78113,16 @@ var init_project_version_static_value_writes = __esm({
|
|
|
78101
78113
|
);
|
|
78102
78114
|
}
|
|
78103
78115
|
const normalizedExpected = normalizeMapKey(args.expectedMapKey);
|
|
78116
|
+
const normalizedStored = normalizeMapKey(value.mapKey);
|
|
78104
78117
|
if (this.createdValueIds.has(value.id)) {
|
|
78105
78118
|
this.visitedCreatedValueIds.add(value.id);
|
|
78106
78119
|
if (normalizedExpected === null) delete value.mapKey;
|
|
78107
78120
|
else value.mapKey = normalizedExpected;
|
|
78108
|
-
} else if (
|
|
78121
|
+
} else if (normalizedStored === null && normalizedExpected !== null) {
|
|
78122
|
+
value.mapKey = normalizedExpected;
|
|
78123
|
+
} else if (normalizedStored !== normalizedExpected) {
|
|
78109
78124
|
throw new Error(
|
|
78110
|
-
`${args.path} cannot bind value "${value.id}" from storage partition ${JSON.stringify(
|
|
78125
|
+
`${args.path} cannot bind value "${value.id}" from storage partition ${JSON.stringify(normalizedStored ?? "main")}; expected ${JSON.stringify(normalizedExpected ?? "main")}.`
|
|
78111
78126
|
);
|
|
78112
78127
|
}
|
|
78113
78128
|
if (args.graphValueIds.has(value.id)) {
|
|
@@ -78144,7 +78159,7 @@ var init_project_version_static_value_writes = __esm({
|
|
|
78144
78159
|
}
|
|
78145
78160
|
return;
|
|
78146
78161
|
}
|
|
78147
|
-
if (member.kind !== 7 /* Class */ && value.classId !== void 0) {
|
|
78162
|
+
if (member.kind !== 7 /* Class */ && value.classId !== void 0 && value.classId !== null) {
|
|
78148
78163
|
throw new Error(
|
|
78149
78164
|
`${path} value "${value.id}" has classId but the member is not Class.`
|
|
78150
78165
|
);
|
|
@@ -86434,6 +86449,7 @@ function buildValueLowerContext(state, manifest, options = {}) {
|
|
|
86434
86449
|
pendingValues: registry.pendingValues,
|
|
86435
86450
|
pendingLocalizedTexts: registry.pendingLocalizedTexts,
|
|
86436
86451
|
loweredMemberIdByValueId: /* @__PURE__ */ new Map(),
|
|
86452
|
+
storedClassSchemaMemberIds: /* @__PURE__ */ new Map(),
|
|
86437
86453
|
pendingBindingMembersByClassId: registry.pendingBindingMembersByClassId,
|
|
86438
86454
|
declaredConstructors: declaredConstructorIndex,
|
|
86439
86455
|
initAuthoredRowIds: seedInitAuthoredRowIds(
|
|
@@ -86657,14 +86673,12 @@ function lowerMemberDefaultSourcesV4(state, analysis, manifest, options = {}) {
|
|
|
86657
86673
|
(row) => !existingPendingValueIds.has(row.id)
|
|
86658
86674
|
);
|
|
86659
86675
|
if (pendingRows.length > 0) {
|
|
86660
|
-
const existingRows =
|
|
86661
|
-
|
|
86662
|
-
|
|
86663
|
-
|
|
86664
|
-
|
|
86665
|
-
|
|
86666
|
-
)
|
|
86667
|
-
);
|
|
86676
|
+
const existingRows = pendingSeedGraphRows({
|
|
86677
|
+
context,
|
|
86678
|
+
reconstructedBefore: existingReconstructedKeys,
|
|
86679
|
+
pendingRows,
|
|
86680
|
+
rootBody: lowered.value
|
|
86681
|
+
});
|
|
86668
86682
|
const bindingMembers = [
|
|
86669
86683
|
...context.pendingBindingMembersByClassId.values()
|
|
86670
86684
|
].filter(
|
|
@@ -87247,21 +87261,49 @@ function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
|
|
|
87247
87261
|
`Default value ${binding.label}.${assignment.name} is constructor-projected and must be set through its named constructor argument.`
|
|
87248
87262
|
);
|
|
87249
87263
|
}
|
|
87264
|
+
const childValueMember = recursivePartialMember(member, childMember);
|
|
87265
|
+
const childSlice = assignmentSlices.get(assignment.name);
|
|
87250
87266
|
const existingId = isObjectRecord2(baseBody) ? baseBody[assignment.name] : void 0;
|
|
87251
|
-
const
|
|
87252
|
-
|
|
87267
|
+
const annotatedId = annotatedValue(assignment.value).id;
|
|
87268
|
+
const storedId = typeof existingId === "string" ? existingId : annotatedId;
|
|
87269
|
+
if (storedId !== null && storedId !== void 0) {
|
|
87270
|
+
if (typeof existingId === "string" || context.state[`value:${storedId}`] !== void 0) {
|
|
87271
|
+
body[assignment.name] = lowerValueRow(
|
|
87272
|
+
context,
|
|
87273
|
+
childValueMember,
|
|
87274
|
+
assignment.value,
|
|
87275
|
+
storedId,
|
|
87276
|
+
binding,
|
|
87277
|
+
environment,
|
|
87278
|
+
childSlice
|
|
87279
|
+
);
|
|
87280
|
+
continue;
|
|
87281
|
+
}
|
|
87282
|
+
if (!isPendingId(storedId) && !isUuidV4Id(storedId)) {
|
|
87283
|
+
context.loweringFailures.push({
|
|
87284
|
+
message: `Default value ${binding.label}.${assignment.name} is annotated @id("${storedId}"), which names no existing row, and a new row's durable identity must be a UUID v4. Fix the id if it meant an existing row, or drop the @id to mint a fresh identity.`,
|
|
87285
|
+
site: referenceSite(binding, assignment.value)
|
|
87286
|
+
});
|
|
87287
|
+
}
|
|
87288
|
+
}
|
|
87289
|
+
const placedId = sourceValueSymbol(context, assignment.value);
|
|
87290
|
+
if (placedId !== null) {
|
|
87253
87291
|
throw new Error(
|
|
87254
|
-
`Default value ${binding.label}.${assignment.name}
|
|
87292
|
+
`Default value ${binding.label}.${assignment.name} cannot place existing value ${placedId} into a new structural slot. Create the nested value inline so the atomic construction transaction can own it.`
|
|
87255
87293
|
);
|
|
87256
87294
|
}
|
|
87257
|
-
body[assignment.name] =
|
|
87295
|
+
body[assignment.name] = lowerSeedChild(
|
|
87258
87296
|
context,
|
|
87259
|
-
|
|
87297
|
+
childValueMember,
|
|
87260
87298
|
assignment.value,
|
|
87261
|
-
childId,
|
|
87262
87299
|
binding,
|
|
87300
|
+
`${binding.label}.${assignment.name}`,
|
|
87301
|
+
/* @__PURE__ */ new Map(),
|
|
87302
|
+
/* @__PURE__ */ new Map(),
|
|
87303
|
+
void 0,
|
|
87263
87304
|
environment,
|
|
87264
|
-
|
|
87305
|
+
void 0,
|
|
87306
|
+
childSlice
|
|
87265
87307
|
);
|
|
87266
87308
|
}
|
|
87267
87309
|
return {
|
|
@@ -87402,14 +87444,13 @@ function lowerStoredBinding(context, binding, memberValueIds, seeds) {
|
|
|
87402
87444
|
);
|
|
87403
87445
|
if (pendingRows.length > 0) {
|
|
87404
87446
|
const root = reconstructedOrStateValue(context, valueId);
|
|
87405
|
-
const existingRows =
|
|
87406
|
-
|
|
87407
|
-
|
|
87408
|
-
|
|
87409
|
-
|
|
87410
|
-
|
|
87411
|
-
|
|
87412
|
-
);
|
|
87447
|
+
const existingRows = pendingSeedGraphRows({
|
|
87448
|
+
context,
|
|
87449
|
+
reconstructedBefore: existingReconstructedKeys,
|
|
87450
|
+
pendingRows,
|
|
87451
|
+
rootBody: root.value,
|
|
87452
|
+
rootValueId: valueId
|
|
87453
|
+
});
|
|
87413
87454
|
const bindingMembers = [
|
|
87414
87455
|
...context.pendingBindingMembersByClassId.values()
|
|
87415
87456
|
].filter((pending) => !existingPendingBindingMemberIds.has(pending.id));
|
|
@@ -87444,6 +87485,105 @@ function memberValueId(memberId) {
|
|
|
87444
87485
|
function reconstructedOrStateValue(context, valueId) {
|
|
87445
87486
|
return context.reconstructed.get(`value:${valueId}`)?.fileFields ?? valueBase(context, valueId);
|
|
87446
87487
|
}
|
|
87488
|
+
function pendingSeedGraphRows(args) {
|
|
87489
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
87490
|
+
for (const [key, record3] of args.context.reconstructed) {
|
|
87491
|
+
if (args.reconstructedBefore.has(key)) continue;
|
|
87492
|
+
if (record3.recordKind !== "value") continue;
|
|
87493
|
+
if (record3.recordId === args.rootValueId) continue;
|
|
87494
|
+
candidates.set(record3.recordId, record3.fileFields);
|
|
87495
|
+
}
|
|
87496
|
+
const pendingIds = new Set(args.pendingRows.map((row) => row.id));
|
|
87497
|
+
const bodies = /* @__PURE__ */ new Map();
|
|
87498
|
+
for (const [id2, fields] of candidates) bodies.set(id2, fields.value);
|
|
87499
|
+
for (const row of args.pendingRows) bodies.set(row.id, row.value);
|
|
87500
|
+
const containedIds = /* @__PURE__ */ new Map();
|
|
87501
|
+
const addContained = (containerId, id2) => {
|
|
87502
|
+
if (typeof containerId !== "string") return;
|
|
87503
|
+
const siblings = containedIds.get(containerId);
|
|
87504
|
+
if (siblings === void 0) containedIds.set(containerId, [id2]);
|
|
87505
|
+
else siblings.push(id2);
|
|
87506
|
+
};
|
|
87507
|
+
for (const [id2, fields] of candidates) addContained(fields.containerId, id2);
|
|
87508
|
+
for (const row of args.pendingRows) addContained(row.containerId, row.id);
|
|
87509
|
+
const collectReferenced = (body, into) => {
|
|
87510
|
+
if (typeof body === "string") {
|
|
87511
|
+
if (bodies.has(body)) into.add(body);
|
|
87512
|
+
return;
|
|
87513
|
+
}
|
|
87514
|
+
if (Array.isArray(body)) {
|
|
87515
|
+
for (const entry of body) collectReferenced(entry, into);
|
|
87516
|
+
return;
|
|
87517
|
+
}
|
|
87518
|
+
if (!isObjectRecord2(body)) return;
|
|
87519
|
+
for (const entry of Object.values(body)) collectReferenced(entry, into);
|
|
87520
|
+
};
|
|
87521
|
+
const childIds = (id2) => {
|
|
87522
|
+
const children = new Set(containedIds.get(id2) ?? []);
|
|
87523
|
+
collectReferenced(bodies.get(id2), children);
|
|
87524
|
+
children.delete(id2);
|
|
87525
|
+
return children;
|
|
87526
|
+
};
|
|
87527
|
+
const reachesPending = /* @__PURE__ */ new Map();
|
|
87528
|
+
const visit = (id2) => {
|
|
87529
|
+
const settled = reachesPending.get(id2);
|
|
87530
|
+
if (settled !== void 0) return settled;
|
|
87531
|
+
reachesPending.set(id2, false);
|
|
87532
|
+
let reaches = pendingIds.has(id2);
|
|
87533
|
+
for (const childId of childIds(id2)) {
|
|
87534
|
+
if (visit(childId)) reaches = true;
|
|
87535
|
+
}
|
|
87536
|
+
reachesPending.set(id2, reaches);
|
|
87537
|
+
return reaches;
|
|
87538
|
+
};
|
|
87539
|
+
const roots = new Set(
|
|
87540
|
+
args.rootValueId === void 0 ? [] : containedIds.get(args.rootValueId) ?? []
|
|
87541
|
+
);
|
|
87542
|
+
collectReferenced(args.rootBody, roots);
|
|
87543
|
+
for (const id2 of roots) visit(id2);
|
|
87544
|
+
const rows = [];
|
|
87545
|
+
for (const [id2, fields] of candidates) {
|
|
87546
|
+
if (reachesPending.get(id2) !== true) continue;
|
|
87547
|
+
rows.push(
|
|
87548
|
+
staticValueSeedRow(fields, args.context.loweredMemberIdByValueId.get(id2))
|
|
87549
|
+
);
|
|
87550
|
+
}
|
|
87551
|
+
return rows;
|
|
87552
|
+
}
|
|
87553
|
+
function retainedStoredClassBody(context, storedClassId, baseBody) {
|
|
87554
|
+
const storedMemberIds = storedClassSchemaMemberIds(context, storedClassId);
|
|
87555
|
+
const body = {};
|
|
87556
|
+
for (const [key, childId] of Object.entries(baseBody)) {
|
|
87557
|
+
const storedMemberId = storedMemberIds.get(key);
|
|
87558
|
+
if (storedMemberId !== void 0 && !context.members.has(storedMemberId)) {
|
|
87559
|
+
continue;
|
|
87560
|
+
}
|
|
87561
|
+
body[key] = childId;
|
|
87562
|
+
}
|
|
87563
|
+
return body;
|
|
87564
|
+
}
|
|
87565
|
+
function storedClassSchemaMemberIds(context, classId) {
|
|
87566
|
+
const cached = context.storedClassSchemaMemberIds.get(classId);
|
|
87567
|
+
if (cached !== void 0) return cached;
|
|
87568
|
+
const memberIds = /* @__PURE__ */ new Map();
|
|
87569
|
+
let current = classId;
|
|
87570
|
+
const visited = /* @__PURE__ */ new Set();
|
|
87571
|
+
while (current !== null && !visited.has(current)) {
|
|
87572
|
+
visited.add(current);
|
|
87573
|
+
const data = stateData(context, "class", current);
|
|
87574
|
+
if (data === null) break;
|
|
87575
|
+
if (isObjectRecord2(data.schema)) {
|
|
87576
|
+
for (const [key, memberId] of Object.entries(data.schema)) {
|
|
87577
|
+
if (typeof memberId === "string" && !memberIds.has(key)) {
|
|
87578
|
+
memberIds.set(key, memberId);
|
|
87579
|
+
}
|
|
87580
|
+
}
|
|
87581
|
+
}
|
|
87582
|
+
current = stringOrNull(data.extendsClassId);
|
|
87583
|
+
}
|
|
87584
|
+
context.storedClassSchemaMemberIds.set(classId, memberIds);
|
|
87585
|
+
return memberIds;
|
|
87586
|
+
}
|
|
87447
87587
|
function staticValueSeedRow(value, loweredMemberId) {
|
|
87448
87588
|
const id2 = stringOrNull(value.id);
|
|
87449
87589
|
if (id2 === null) throw new Error("Authored value row is missing id.");
|
|
@@ -88195,7 +88335,11 @@ function lowerClassValue(context, member, expression, base, source, outerEnviron
|
|
|
88195
88335
|
String(base.id ?? member.name)
|
|
88196
88336
|
);
|
|
88197
88337
|
const baseBody = isObjectRecord2(base.value) ? base.value : {};
|
|
88198
|
-
const body =
|
|
88338
|
+
const body = retainedStoredClassBody(
|
|
88339
|
+
context,
|
|
88340
|
+
currentClassId,
|
|
88341
|
+
baseBody
|
|
88342
|
+
);
|
|
88199
88343
|
const assignmentSlices = objectInitializerSlices(authoredSlice);
|
|
88200
88344
|
if (materializedConstruction !== "preserve") {
|
|
88201
88345
|
lowerConstructorProjections(
|
|
@@ -96237,6 +96381,181 @@ var init_scaffold = __esm({
|
|
|
96237
96381
|
}
|
|
96238
96382
|
});
|
|
96239
96383
|
|
|
96384
|
+
// src/project-source/project-integrity.ts
|
|
96385
|
+
function auditProjectIntegrity(workspace) {
|
|
96386
|
+
const records2 = workspace.state.records;
|
|
96387
|
+
const values = collectValueRows(records2);
|
|
96388
|
+
const placements = collectPlacements(values);
|
|
96389
|
+
const memberIdBySchemaKey = collectClassSchemas(records2);
|
|
96390
|
+
const storageKeyByMemberId = collectMemberStorageKeys(records2);
|
|
96391
|
+
const findings = [];
|
|
96392
|
+
for (const value of values.values()) {
|
|
96393
|
+
const placement = placements.get(value.id);
|
|
96394
|
+
if (placement === void 0) continue;
|
|
96395
|
+
const declaration = childStorageKeyDeclaration({
|
|
96396
|
+
placement,
|
|
96397
|
+
memberIdBySchemaKey,
|
|
96398
|
+
storageKeyByMemberId
|
|
96399
|
+
});
|
|
96400
|
+
if (declaration === null) continue;
|
|
96401
|
+
const expected = resolveMapKeyForCreatedValue({
|
|
96402
|
+
declaration,
|
|
96403
|
+
parentMapKey: placement.parent.mapKey,
|
|
96404
|
+
parentClassId: placement.parent.classId ?? void 0
|
|
96405
|
+
});
|
|
96406
|
+
const normalizedExpected = normalizePartition(expected);
|
|
96407
|
+
const normalizedActual = normalizePartition(value.mapKey);
|
|
96408
|
+
if (normalizedExpected === normalizedActual) continue;
|
|
96409
|
+
findings.push({
|
|
96410
|
+
kind: "value-storage-partition",
|
|
96411
|
+
recordKind: "value",
|
|
96412
|
+
recordId: value.id,
|
|
96413
|
+
message: `Value "${value.id}" is stored in partition ${describePartition(normalizedActual)} but its placement parent "${placement.parent.id}" puts it in ${describePartition(normalizedExpected)}.`,
|
|
96414
|
+
repair: {
|
|
96415
|
+
valueId: value.id,
|
|
96416
|
+
parentValueId: placement.parent.id,
|
|
96417
|
+
schemaKey: placement.schemaKey,
|
|
96418
|
+
actualMapKey: normalizedActual,
|
|
96419
|
+
expectedMapKey: normalizedExpected
|
|
96420
|
+
}
|
|
96421
|
+
});
|
|
96422
|
+
}
|
|
96423
|
+
return findings.sort(
|
|
96424
|
+
(left, right) => left.recordId.localeCompare(right.recordId)
|
|
96425
|
+
);
|
|
96426
|
+
}
|
|
96427
|
+
function childStorageKeyDeclaration(args) {
|
|
96428
|
+
const { placement } = args;
|
|
96429
|
+
if (placement.schemaKey === UNORDERED_MEMBERSHIP_KEY) {
|
|
96430
|
+
return normalizeStorageKeyDeclaration(void 0);
|
|
96431
|
+
}
|
|
96432
|
+
const parentClassId = placement.parent.classId;
|
|
96433
|
+
if (parentClassId === null) return null;
|
|
96434
|
+
const memberId = args.memberIdBySchemaKey.get(parentClassId)?.get(placement.schemaKey);
|
|
96435
|
+
if (memberId === void 0) return null;
|
|
96436
|
+
const declaration = args.storageKeyByMemberId.get(memberId);
|
|
96437
|
+
if (declaration === void 0) return null;
|
|
96438
|
+
return declaration;
|
|
96439
|
+
}
|
|
96440
|
+
function collectValueRows(records2) {
|
|
96441
|
+
const values = /* @__PURE__ */ new Map();
|
|
96442
|
+
for (const record3 of Object.values(records2)) {
|
|
96443
|
+
if (record3.recordKind !== "value") continue;
|
|
96444
|
+
const data = record3.data;
|
|
96445
|
+
if (!isObjectRecord2(data)) continue;
|
|
96446
|
+
values.set(record3.recordId, {
|
|
96447
|
+
id: record3.recordId,
|
|
96448
|
+
classId: typeof data.classId === "string" ? data.classId : null,
|
|
96449
|
+
mapKey: typeof data.mapKey === "string" ? data.mapKey : null,
|
|
96450
|
+
containerId: typeof data.containerId === "string" ? data.containerId : null,
|
|
96451
|
+
value: data.value
|
|
96452
|
+
});
|
|
96453
|
+
}
|
|
96454
|
+
return values;
|
|
96455
|
+
}
|
|
96456
|
+
function collectPlacements(values) {
|
|
96457
|
+
const placements = /* @__PURE__ */ new Map();
|
|
96458
|
+
const claim = (childId, placement) => {
|
|
96459
|
+
if (values.has(childId) && !placements.has(childId)) {
|
|
96460
|
+
placements.set(childId, placement);
|
|
96461
|
+
}
|
|
96462
|
+
};
|
|
96463
|
+
for (const parent of values.values()) {
|
|
96464
|
+
const body = parent.value;
|
|
96465
|
+
if (isObjectRecord2(body)) {
|
|
96466
|
+
for (const [schemaKey, childId] of Object.entries(body)) {
|
|
96467
|
+
if (typeof childId === "string") claim(childId, { parent, schemaKey });
|
|
96468
|
+
}
|
|
96469
|
+
continue;
|
|
96470
|
+
}
|
|
96471
|
+
if (!Array.isArray(body)) continue;
|
|
96472
|
+
for (const childId of body) {
|
|
96473
|
+
if (typeof childId === "string") {
|
|
96474
|
+
claim(childId, { parent, schemaKey: UNORDERED_MEMBERSHIP_KEY });
|
|
96475
|
+
}
|
|
96476
|
+
}
|
|
96477
|
+
}
|
|
96478
|
+
for (const value of values.values()) {
|
|
96479
|
+
if (value.containerId === null) continue;
|
|
96480
|
+
const container = values.get(value.containerId);
|
|
96481
|
+
if (container === void 0) continue;
|
|
96482
|
+
claim(value.id, { parent: container, schemaKey: UNORDERED_MEMBERSHIP_KEY });
|
|
96483
|
+
}
|
|
96484
|
+
return placements;
|
|
96485
|
+
}
|
|
96486
|
+
function collectClassSchemas(records2) {
|
|
96487
|
+
const schemas = /* @__PURE__ */ new Map();
|
|
96488
|
+
for (const record3 of Object.values(records2)) {
|
|
96489
|
+
if (record3.recordKind !== "class") continue;
|
|
96490
|
+
const data = record3.data;
|
|
96491
|
+
if (!isObjectRecord2(data) || !isObjectRecord2(data.schema)) continue;
|
|
96492
|
+
const schema = /* @__PURE__ */ new Map();
|
|
96493
|
+
for (const [schemaKey, memberId] of Object.entries(data.schema)) {
|
|
96494
|
+
if (typeof memberId === "string") schema.set(schemaKey, memberId);
|
|
96495
|
+
}
|
|
96496
|
+
schemas.set(record3.recordId, schema);
|
|
96497
|
+
}
|
|
96498
|
+
for (const record3 of Object.values(records2)) {
|
|
96499
|
+
if (record3.recordKind !== "class") continue;
|
|
96500
|
+
const own = schemas.get(record3.recordId);
|
|
96501
|
+
if (own === void 0) continue;
|
|
96502
|
+
const seen = /* @__PURE__ */ new Set([record3.recordId]);
|
|
96503
|
+
let data = record3.data;
|
|
96504
|
+
while (isObjectRecord2(data) && typeof data.extendsClassId === "string") {
|
|
96505
|
+
const parentId = data.extendsClassId;
|
|
96506
|
+
if (seen.has(parentId)) break;
|
|
96507
|
+
seen.add(parentId);
|
|
96508
|
+
for (const [schemaKey, memberId] of schemas.get(parentId) ?? []) {
|
|
96509
|
+
if (!own.has(schemaKey)) own.set(schemaKey, memberId);
|
|
96510
|
+
}
|
|
96511
|
+
const parentRecord = records2[`class:${parentId}`];
|
|
96512
|
+
data = parentRecord?.data;
|
|
96513
|
+
}
|
|
96514
|
+
}
|
|
96515
|
+
return schemas;
|
|
96516
|
+
}
|
|
96517
|
+
function collectMemberStorageKeys(records2) {
|
|
96518
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
96519
|
+
for (const record3 of Object.values(records2)) {
|
|
96520
|
+
if (record3.recordKind !== "member") continue;
|
|
96521
|
+
let data = record3.data;
|
|
96522
|
+
const seen = /* @__PURE__ */ new Set([record3.recordId]);
|
|
96523
|
+
let declaration;
|
|
96524
|
+
while (isObjectRecord2(data)) {
|
|
96525
|
+
if (typeof data.storageKey === "string" && data.storageKey.length > 0) {
|
|
96526
|
+
declaration = data.storageKey;
|
|
96527
|
+
break;
|
|
96528
|
+
}
|
|
96529
|
+
if (typeof data.extendsMemberId !== "string") break;
|
|
96530
|
+
const parentId = data.extendsMemberId;
|
|
96531
|
+
if (seen.has(parentId)) break;
|
|
96532
|
+
seen.add(parentId);
|
|
96533
|
+
data = records2[`member:${parentId}`]?.data;
|
|
96534
|
+
}
|
|
96535
|
+
declarations.set(
|
|
96536
|
+
record3.recordId,
|
|
96537
|
+
normalizeStorageKeyDeclaration(declaration)
|
|
96538
|
+
);
|
|
96539
|
+
}
|
|
96540
|
+
return declarations;
|
|
96541
|
+
}
|
|
96542
|
+
function normalizePartition(mapKey) {
|
|
96543
|
+
if (mapKey === null || mapKey.length === 0) return null;
|
|
96544
|
+
return mapKey === MAIN_STORAGE_PARTITION ? null : mapKey;
|
|
96545
|
+
}
|
|
96546
|
+
function describePartition(mapKey) {
|
|
96547
|
+
return mapKey === null ? `"${MAIN_STORAGE_PARTITION}"` : `"${mapKey}"`;
|
|
96548
|
+
}
|
|
96549
|
+
var UNORDERED_MEMBERSHIP_KEY;
|
|
96550
|
+
var init_project_integrity = __esm({
|
|
96551
|
+
"src/project-source/project-integrity.ts"() {
|
|
96552
|
+
"use strict";
|
|
96553
|
+
init_member_storage_key();
|
|
96554
|
+
init_projection();
|
|
96555
|
+
UNORDERED_MEMBERSHIP_KEY = "(container)";
|
|
96556
|
+
}
|
|
96557
|
+
});
|
|
96558
|
+
|
|
96240
96559
|
// src/neoscript-language-adapter.ts
|
|
96241
96560
|
function createCliNeoScriptContext(options) {
|
|
96242
96561
|
const members = [...options.documents.members];
|
|
@@ -100954,7 +101273,9 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
|
|
|
100954
101273
|
changes: status.changes,
|
|
100955
101274
|
binaryChanges: status.binaryChanges ?? []
|
|
100956
101275
|
});
|
|
100957
|
-
if (status.changes.length === 0
|
|
101276
|
+
if (status.changes.length === 0 && status.authoredValueSeeds.size === 0) {
|
|
101277
|
+
return status;
|
|
101278
|
+
}
|
|
100958
101279
|
const initConversions = status.initConversions ?? [];
|
|
100959
101280
|
if (initConversions.length > 0 && options.json !== true) {
|
|
100960
101281
|
for (const line of describeInitConversions(initConversions)) warn(line);
|
|
@@ -101059,7 +101380,7 @@ async function runPush(workspace, options, preparationOverride) {
|
|
|
101059
101380
|
const status = candidate.status;
|
|
101060
101381
|
const local = candidate.preparedLocal;
|
|
101061
101382
|
preparation?.stop();
|
|
101062
|
-
if (status.changes.length === 0) {
|
|
101383
|
+
if (status.changes.length === 0 && status.authoredValueSeeds.size === 0) {
|
|
101063
101384
|
if (options.json === true) {
|
|
101064
101385
|
console.log(
|
|
101065
101386
|
JSON.stringify({
|
|
@@ -101575,7 +101896,7 @@ async function prepareLocalCandidateV4(workspace, options = {}) {
|
|
|
101575
101896
|
let sourceHash;
|
|
101576
101897
|
let preparedLocal = null;
|
|
101577
101898
|
let documentStatus = status;
|
|
101578
|
-
if (status.changes.length > 0) {
|
|
101899
|
+
if (status.changes.length > 0 || status.authoredValueSeeds.size > 0) {
|
|
101579
101900
|
try {
|
|
101580
101901
|
documentStatus = pushOptions.dryRun ? cloneStatusForDryRun(status) : status;
|
|
101581
101902
|
preparedLocal = await prepareLocalPushArtifactsV4(
|
|
@@ -103116,7 +103437,7 @@ var init_registry2 = __esm({
|
|
|
103116
103437
|
PROJECT_SCHEMA_CONTRACT = Object.freeze({
|
|
103117
103438
|
formatVersion: 3,
|
|
103118
103439
|
contractVersion: "3.9",
|
|
103119
|
-
cliVersion: "0.25.
|
|
103440
|
+
cliVersion: "0.25.2",
|
|
103120
103441
|
projectFileUploadBatchSize: 32,
|
|
103121
103442
|
documentRecords: {
|
|
103122
103443
|
member: {
|
|
@@ -105898,8 +106219,10 @@ function inspectNeoDoctor(workspace) {
|
|
|
105898
106219
|
errors: [],
|
|
105899
106220
|
compatible: true
|
|
105900
106221
|
};
|
|
106222
|
+
const findings = auditProjectIntegrity(workspace);
|
|
105901
106223
|
return {
|
|
105902
106224
|
ok: formatCompatible && compiler.compatible && extension.compatible && source.compatible && files.compatible,
|
|
106225
|
+
document: { findings, consistent: findings.length === 0 },
|
|
105903
106226
|
format: {
|
|
105904
106227
|
current: workspace.config.formatVersion,
|
|
105905
106228
|
expected: CURRENT_FORMAT_VERSION,
|
|
@@ -106158,6 +106481,12 @@ async function runDoctor(workspace, json) {
|
|
|
106158
106481
|
console.log(
|
|
106159
106482
|
` Files: contract v${String(report.files.contractVersion)}, SHA-256, ${String(report.files.trackedBinaryCount)} tracked binar${report.files.trackedBinaryCount === 1 ? "y" : "ies"}, workspace ${report.files.workspaceReadable ? "readable" : "not readable"}/${report.files.workspaceWritable ? "writable" : "not writable"}`
|
|
106160
106483
|
);
|
|
106484
|
+
console.log(
|
|
106485
|
+
` Document: ${report.document.consistent ? "no malformed records" : `${String(report.document.findings.length)} malformed record${report.document.findings.length === 1 ? "" : "s"} (from the last pull)`}`
|
|
106486
|
+
);
|
|
106487
|
+
for (const finding of report.document.findings) {
|
|
106488
|
+
console.log(` Document defect (${finding.kind}): ${finding.message}`);
|
|
106489
|
+
}
|
|
106161
106490
|
if (report.compiler.error)
|
|
106162
106491
|
console.log(` Compiler error: ${report.compiler.error}`);
|
|
106163
106492
|
if (report.extension.error)
|
|
@@ -106169,6 +106498,11 @@ async function runDoctor(workspace, json) {
|
|
|
106169
106498
|
console.log(
|
|
106170
106499
|
report.ok ? "Project authoring contracts are compatible." : "Project authoring contracts need attention."
|
|
106171
106500
|
);
|
|
106501
|
+
if (!report.document.consistent) {
|
|
106502
|
+
console.log(
|
|
106503
|
+
`${String(report.document.findings.length)} malformed record${report.document.findings.length === 1 ? "" : "s"} in the pulled document \u2014 see \`--json\` for the repair entries.`
|
|
106504
|
+
);
|
|
106505
|
+
}
|
|
106172
106506
|
if (!report.ok) process.exitCode = 1;
|
|
106173
106507
|
}
|
|
106174
106508
|
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;
|
|
@@ -106180,6 +106514,7 @@ var init_doctor = __esm({
|
|
|
106180
106514
|
init_workspace_status();
|
|
106181
106515
|
init_source_diagnostics();
|
|
106182
106516
|
init_project_documents();
|
|
106517
|
+
init_project_integrity();
|
|
106183
106518
|
NEO_COMPILER_CONTRACT = "ProjectSourceAnalysisV4";
|
|
106184
106519
|
NEO_VSCODE_EXTENSION_ID = "neocompose.neo-compose-neoscript";
|
|
106185
106520
|
NEO_VSCODE_EXTENSION_CONTRACT_VERSION = "0.3.0";
|
|
@@ -109156,7 +109491,7 @@ ${h("Working copy")}
|
|
|
109156
109491
|
test ${d("[file-or-dir ...] [-t <pattern>] [--reporter=json] [--outputFile <path>]")}
|
|
109157
109492
|
status ${d("[--json]")} diff ${d("[--json]")}
|
|
109158
109493
|
dev ${d("[--push]")} resolve ${d("[--mine|--theirs]")}
|
|
109159
|
-
doctor ${d("[--json]")} ${d("validate
|
|
109494
|
+
doctor ${d("[--json]")} ${d("validate contracts and audit the pulled document")}
|
|
109160
109495
|
|
|
109161
109496
|
${h("Pending source scaffolds")}
|
|
109162
109497
|
class ${d("new <Name> [--abstract]")}
|
|
@@ -109811,7 +110146,12 @@ async function main() {
|
|
|
109811
110146
|
`${paintChangeKind(binary.action === "create" ? "create" : "update")} project-file bytes ${binary.symbol} (${binary.path}) \u2014 ${binary.action}`
|
|
109812
110147
|
);
|
|
109813
110148
|
}
|
|
109814
|
-
if (status.
|
|
110149
|
+
if (status.authoredValueSeeds.size > 0) {
|
|
110150
|
+
console.log(
|
|
110151
|
+
` ${paintChangeKind("create")} ${status.authoredValueSeeds.size} member value seed(s)`
|
|
110152
|
+
);
|
|
110153
|
+
}
|
|
110154
|
+
if (status.conflictedFiles.length === 0 && status.parseErrors.length === 0 && status.changes.length === 0 && status.authoredValueSeeds.size === 0 && (status.binaryChanges?.length ?? 0) === 0) {
|
|
109815
110155
|
console.log(`${sym.ok} Working copy is clean.`);
|
|
109816
110156
|
}
|
|
109817
110157
|
return;
|
package/package.json
CHANGED
|
@@ -41,11 +41,12 @@ Read the relevant specs in full:
|
|
|
41
41
|
- [P50 loops](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p50-neoscript-for-and-foreach-loops.md)
|
|
42
42
|
- [P51 switch](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p51-neoscript-switch-statements.md)
|
|
43
43
|
- [P52 try/catch](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p52-neoscript-try-catch-blocks.md)
|
|
44
|
+
- [P55 breakable collection iteration](https://github.com/ryanbliss/neo-compose-specs/blob/main/neoscript/p55-breakable-collection-iteration.md)
|
|
44
45
|
- [P60 delegate parameters and selector targeting](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p60-closure-parameters-and-selector-targeting.md)
|
|
45
46
|
- [P61 initializer materialization](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/complete/p61-initializer-materialization.md)
|
|
46
47
|
- [P64 NeoScript unit testing and push hooks](https://github.com/ryanbliss/neo-compose-specs/blob/main/new-features/p64-neoscript-unit-testing-and-push-hooks.md)
|
|
47
48
|
|
|
48
|
-
P38–P44, P47–P52, P60–P61, and the initial P64 test/push-hook vertical slice
|
|
49
|
+
P38–P44, P47–P52, P55, P60–P61, and the initial P64 test/push-hook vertical slice
|
|
49
50
|
are implemented. P45 remains deliberately deferred; do not
|
|
50
51
|
build or teach runtime-child provenance. P46 is a hardening proposal, not an
|
|
51
52
|
authoring capability. P52's document header still says proposed, but try/catch
|
|
@@ -82,7 +83,7 @@ wrappers.
|
|
|
82
83
|
The marker near the top of `SKILL.md` must exactly match the package version:
|
|
83
84
|
|
|
84
85
|
```html
|
|
85
|
-
<!-- reviewed-through-cli: 0.25.
|
|
86
|
+
<!-- reviewed-through-cli: 0.25.2 -->
|
|
86
87
|
```
|
|
87
88
|
|
|
88
89
|
The quoted version above is checked too, so this instruction cannot go stale
|
|
@@ -146,6 +146,13 @@ neo login [--api <url>] [--profile editor|release] [--save-project <id>]
|
|
|
146
146
|
neo doctor
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
+
`neo doctor` reports two different things. The authoring contracts (format,
|
|
150
|
+
compiler, extension cache, source, files) decide its exit code; the document
|
|
151
|
+
audit below them reports malformed records in the last pull and deliberately
|
|
152
|
+
does not, so a project with records still to repair does not fail the command.
|
|
153
|
+
`neo doctor --json` carries a `repair` entry per finding, which is what a
|
|
154
|
+
targeted repair is handed instead of scanning a project to rediscover them.
|
|
155
|
+
|
|
149
156
|
The Node CLI talks directly to authenticated Convex APIs through the
|
|
150
157
|
session-gated CAS boundary. Tokens use the OS credential store when available
|
|
151
158
|
and a protected file only as fallback. Use `NEO_COMPOSE_TOKEN` or
|