@neocompose/cli 0.25.1 → 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,16 @@
|
|
|
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
|
+
|
|
3
14
|
## [0.25.1] - 2026-08-09
|
|
4
15
|
|
|
5
16
|
### Changed
|
package/dist/neo.mjs
CHANGED
|
@@ -96381,6 +96381,181 @@ var init_scaffold = __esm({
|
|
|
96381
96381
|
}
|
|
96382
96382
|
});
|
|
96383
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
|
+
|
|
96384
96559
|
// src/neoscript-language-adapter.ts
|
|
96385
96560
|
function createCliNeoScriptContext(options) {
|
|
96386
96561
|
const members = [...options.documents.members];
|
|
@@ -103262,7 +103437,7 @@ var init_registry2 = __esm({
|
|
|
103262
103437
|
PROJECT_SCHEMA_CONTRACT = Object.freeze({
|
|
103263
103438
|
formatVersion: 3,
|
|
103264
103439
|
contractVersion: "3.9",
|
|
103265
|
-
cliVersion: "0.25.
|
|
103440
|
+
cliVersion: "0.25.2",
|
|
103266
103441
|
projectFileUploadBatchSize: 32,
|
|
103267
103442
|
documentRecords: {
|
|
103268
103443
|
member: {
|
|
@@ -106044,8 +106219,10 @@ function inspectNeoDoctor(workspace) {
|
|
|
106044
106219
|
errors: [],
|
|
106045
106220
|
compatible: true
|
|
106046
106221
|
};
|
|
106222
|
+
const findings = auditProjectIntegrity(workspace);
|
|
106047
106223
|
return {
|
|
106048
106224
|
ok: formatCompatible && compiler.compatible && extension.compatible && source.compatible && files.compatible,
|
|
106225
|
+
document: { findings, consistent: findings.length === 0 },
|
|
106049
106226
|
format: {
|
|
106050
106227
|
current: workspace.config.formatVersion,
|
|
106051
106228
|
expected: CURRENT_FORMAT_VERSION,
|
|
@@ -106304,6 +106481,12 @@ async function runDoctor(workspace, json) {
|
|
|
106304
106481
|
console.log(
|
|
106305
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"}`
|
|
106306
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
|
+
}
|
|
106307
106490
|
if (report.compiler.error)
|
|
106308
106491
|
console.log(` Compiler error: ${report.compiler.error}`);
|
|
106309
106492
|
if (report.extension.error)
|
|
@@ -106315,6 +106498,11 @@ async function runDoctor(workspace, json) {
|
|
|
106315
106498
|
console.log(
|
|
106316
106499
|
report.ok ? "Project authoring contracts are compatible." : "Project authoring contracts need attention."
|
|
106317
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
|
+
}
|
|
106318
106506
|
if (!report.ok) process.exitCode = 1;
|
|
106319
106507
|
}
|
|
106320
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;
|
|
@@ -106326,6 +106514,7 @@ var init_doctor = __esm({
|
|
|
106326
106514
|
init_workspace_status();
|
|
106327
106515
|
init_source_diagnostics();
|
|
106328
106516
|
init_project_documents();
|
|
106517
|
+
init_project_integrity();
|
|
106329
106518
|
NEO_COMPILER_CONTRACT = "ProjectSourceAnalysisV4";
|
|
106330
106519
|
NEO_VSCODE_EXTENSION_ID = "neocompose.neo-compose-neoscript";
|
|
106331
106520
|
NEO_VSCODE_EXTENSION_CONTRACT_VERSION = "0.3.0";
|
|
@@ -109302,7 +109491,7 @@ ${h("Working copy")}
|
|
|
109302
109491
|
test ${d("[file-or-dir ...] [-t <pattern>] [--reporter=json] [--outputFile <path>]")}
|
|
109303
109492
|
status ${d("[--json]")} diff ${d("[--json]")}
|
|
109304
109493
|
dev ${d("[--push]")} resolve ${d("[--mine|--theirs]")}
|
|
109305
|
-
doctor ${d("[--json]")} ${d("validate
|
|
109494
|
+
doctor ${d("[--json]")} ${d("validate contracts and audit the pulled document")}
|
|
109306
109495
|
|
|
109307
109496
|
${h("Pending source scaffolds")}
|
|
109308
109497
|
class ${d("new <Name> [--abstract]")}
|
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.25.
|
|
86
|
+
<!-- reviewed-through-cli: 0.25.2 -->
|
|
87
87
|
```
|
|
88
88
|
|
|
89
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
|