@thebackstoryis/engineering-with-ai 0.2.9 → 0.3.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/Docs/README.md +2 -0
- package/Docs/autonomous-intent-delivery.md +37 -0
- package/Docs/guide-catalogue.md +2 -0
- package/Docs/quality/persona-driven-test-scenarios.md +6 -0
- package/README.md +17 -0
- package/config/autonomy-worker.schema.json +70 -0
- package/config/autonomy.schema.json +25 -0
- package/package.json +3 -3
- package/public/app.js +7 -0
- package/public/autonomy.js +470 -0
- package/public/index.html +26 -0
- package/public/styles.css +64 -0
- package/skills-src/ewai-autonomy/SKILL.md +18 -0
- package/skills-src/ewai-autonomy/agents/openai.yaml +4 -0
- package/skills-src/ewai-deliver/SKILL.md +2 -0
- package/skills-src/ewai-test-scenarios/references/scenario-contract.md +6 -0
- package/src/autonomy-phase-contracts.mjs +88 -0
- package/src/autonomy-worker.mjs +15 -0
- package/src/autonomy.mjs +199 -0
- package/src/checkin.mjs +30 -6
- package/src/cli.mjs +67 -0
- package/src/delivery-gates.mjs +8 -1
- package/src/delivery.mjs +74 -3
- package/src/design-system-application.mjs +28 -0
- package/src/runtime/afk-conductor.mjs +191 -71
- package/src/runtime/autonomy-operations.mjs +250 -0
- package/src/runtime/autonomy-supervisor.mjs +426 -0
- package/src/runtime/autonomy-workers.mjs +333 -0
- package/src/runtime/autonomy-workspace.mjs +310 -0
- package/src/runtime/dashboard-actions.mjs +69 -0
- package/src/runtime/dashboard-server.mjs +25 -0
- package/src/runtime/database.mjs +25 -6
- package/src/runtime/intent-ownership.mjs +272 -0
- package/src/runtime/mcp-server.mjs +38 -0
- package/src/runtime/provider-adapters.mjs +365 -7
- package/src/runtime/work.mjs +21 -5
- package/src/test-scenarios.mjs +173 -20
package/src/delivery.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import YAML from 'yaml';
|
|
|
13
13
|
import { updateIntentDeliveryState, validateIntentStateCopies } from './intents.mjs';
|
|
14
14
|
import { loadProjectConfig } from './project.mjs';
|
|
15
15
|
import { readRuntimeIntent } from './runtime/intents.mjs';
|
|
16
|
+
import { withIntentMutation, assertIntentMutation, intentOwnershipError } from './runtime/intent-ownership.mjs';
|
|
16
17
|
import {
|
|
17
18
|
addActivityEvent,
|
|
18
19
|
finishActiveSession,
|
|
@@ -204,6 +205,7 @@ function lifecycleScope(state, phase = '') {
|
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
function publishDeliveryLifecycle(paths, state, eventName, input = {}) {
|
|
208
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
207
209
|
let personas = state.intent.personas;
|
|
208
210
|
if (!Array.isArray(personas)) {
|
|
209
211
|
try {
|
|
@@ -287,11 +289,15 @@ function restoreTransition(paths, snapshot) {
|
|
|
287
289
|
}
|
|
288
290
|
|
|
289
291
|
function persistTransition(paths, state) {
|
|
292
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
290
293
|
const snapshot = transitionSnapshot(paths, state);
|
|
291
294
|
try {
|
|
295
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
292
296
|
state.updatedAt = now();
|
|
293
297
|
const copies = syncIntentCopies(paths, state);
|
|
298
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
294
299
|
writeDeliveryDocuments(paths, state);
|
|
300
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
295
301
|
const item = syncOperationalProjection(paths, state);
|
|
296
302
|
validateIntentStateCopies(paths.projectRoot, copies.markdownPath, {
|
|
297
303
|
status: state.intent.status,
|
|
@@ -305,6 +311,7 @@ function persistTransition(paths, state) {
|
|
|
305
311
|
return item;
|
|
306
312
|
} catch (error) {
|
|
307
313
|
try {
|
|
314
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
308
315
|
restoreTransition(paths, snapshot);
|
|
309
316
|
} catch (rollbackError) {
|
|
310
317
|
error.rollbackError = rollbackError.message;
|
|
@@ -347,6 +354,7 @@ function finishCurrentRun(paths, state, status, summary) {
|
|
|
347
354
|
run.status = status;
|
|
348
355
|
run.completedAt = now();
|
|
349
356
|
run.summary = summary;
|
|
357
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
350
358
|
atomicJson(runPath, run);
|
|
351
359
|
finishCommandRun(paths.projectRoot, runId, {
|
|
352
360
|
status,
|
|
@@ -483,12 +491,19 @@ export function readDeliveryState(projectRoot, slug) {
|
|
|
483
491
|
}
|
|
484
492
|
|
|
485
493
|
export function beginDelivery(projectRoot, slug, options = {}) {
|
|
494
|
+
const resolvedOptions = { ...options, tool: options.tool ?? process.env.EWAI_ORCHESTRATOR ?? 'manual' };
|
|
495
|
+
return withIntentMutation(projectRoot, slug, { action: 'begin-harness', provider: resolvedOptions.tool, input: resolvedOptions, ownership: options.ownership }, () => beginOwnedDelivery(projectRoot, slug, resolvedOptions));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function beginOwnedDelivery(projectRoot, slug, options) {
|
|
486
499
|
const paths = deliveryPaths(projectRoot, slug);
|
|
487
|
-
if (existsSync(paths.statePath))
|
|
500
|
+
if (existsSync(paths.statePath)) intentOwnershipError('intent-transition-conflict');
|
|
488
501
|
const { config } = loadProjectConfig(paths.projectRoot);
|
|
489
502
|
const intent = readRuntimeIntent(paths.projectRoot, slug);
|
|
490
503
|
if (!intent) throw new Error(`Cannot begin delivery without an intent: ${slug}`);
|
|
491
|
-
const
|
|
504
|
+
const currentOwner = readWorkItemView(paths.projectRoot, intent.id)?.activity.session;
|
|
505
|
+
if (currentOwner && !currentOwner.endedAt) intentOwnershipError('intent-ownership-conflict');
|
|
506
|
+
const orchestrator = options.tool;
|
|
492
507
|
const validation = effectiveValidationConfig(config, orchestrator);
|
|
493
508
|
const providers = [...new Set(
|
|
494
509
|
Object.values(validation.checkpoints).flatMap((checkpoint) => checkpoint.validators),
|
|
@@ -561,6 +576,7 @@ export function beginDelivery(projectRoot, slug, options = {}) {
|
|
|
561
576
|
blockedReason: ''
|
|
562
577
|
};
|
|
563
578
|
|
|
579
|
+
assertIntentMutation(paths.projectRoot, intent.id);
|
|
564
580
|
mkdirSync(paths.runsRoot, { recursive: true });
|
|
565
581
|
mkdirSync(paths.gatesRoot, { recursive: true });
|
|
566
582
|
atomicJson(resolve(paths.runsRoot, `${run.id}.json`), run);
|
|
@@ -600,6 +616,10 @@ export function beginDelivery(projectRoot, slug, options = {}) {
|
|
|
600
616
|
}
|
|
601
617
|
|
|
602
618
|
export function recordPhaseGate(projectRoot, slug, phaseId, input = {}) {
|
|
619
|
+
return withIntentMutation(projectRoot, slug, { action: 'record-gate', phase: phaseId, input, ownership: input.ownership }, () => recordOwnedPhaseGate(projectRoot, slug, phaseId, input));
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function recordOwnedPhaseGate(projectRoot, slug, phaseId, input) {
|
|
603
623
|
const paths = deliveryPaths(projectRoot, slug);
|
|
604
624
|
const state = readDeliveryState(projectRoot, slug);
|
|
605
625
|
const phase = findTrackedPhase(state, phaseId);
|
|
@@ -609,6 +629,10 @@ export function recordPhaseGate(projectRoot, slug, phaseId, input = {}) {
|
|
|
609
629
|
}
|
|
610
630
|
|
|
611
631
|
export function recordExternalValidationCycle(projectRoot, slug, phaseId, input = {}) {
|
|
632
|
+
return withIntentMutation(projectRoot, slug, { action: 'record-validation', phase: phaseId, input, ownership: input.ownership }, () => recordOwnedExternalValidationCycle(projectRoot, slug, phaseId, input));
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function recordOwnedExternalValidationCycle(projectRoot, slug, phaseId, input) {
|
|
612
636
|
const paths = deliveryPaths(projectRoot, slug);
|
|
613
637
|
const state = readDeliveryState(projectRoot, slug);
|
|
614
638
|
const phase = findTrackedPhase(state, phaseId);
|
|
@@ -672,6 +696,7 @@ export function recordExternalValidationCycle(projectRoot, slug, phaseId, input
|
|
|
672
696
|
);
|
|
673
697
|
|
|
674
698
|
const ledgerPath = resolve(paths.deliveryRoot, phaseId, 'validation-cycles.json');
|
|
699
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
675
700
|
atomicJson(ledgerPath, {
|
|
676
701
|
schema: 'ewai.external-validation-cycles/v1',
|
|
677
702
|
slug,
|
|
@@ -743,7 +768,11 @@ export function continueDelivery(projectRoot, slug) {
|
|
|
743
768
|
};
|
|
744
769
|
}
|
|
745
770
|
|
|
746
|
-
export function startDeliveryPhase(projectRoot, slug, phaseId) {
|
|
771
|
+
export function startDeliveryPhase(projectRoot, slug, phaseId, input = {}) {
|
|
772
|
+
return withIntentMutation(projectRoot, slug, { action: 'start-phase', phase: phaseId, input, ownership: input.ownership }, () => startOwnedDeliveryPhase(projectRoot, slug, phaseId));
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function startOwnedDeliveryPhase(projectRoot, slug, phaseId) {
|
|
747
776
|
const paths = deliveryPaths(projectRoot, slug);
|
|
748
777
|
const state = readDeliveryState(projectRoot, slug);
|
|
749
778
|
assertIntentCopiesConsistent(paths, state);
|
|
@@ -788,6 +817,10 @@ export function startDeliveryPhase(projectRoot, slug, phaseId) {
|
|
|
788
817
|
}
|
|
789
818
|
|
|
790
819
|
export function completeDeliveryPhase(projectRoot, slug, phaseId, options = {}) {
|
|
820
|
+
return withIntentMutation(projectRoot, slug, { action: 'complete-phase', phase: phaseId, input: options, ownership: options.ownership }, () => completeOwnedDeliveryPhase(projectRoot, slug, phaseId, options));
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function completeOwnedDeliveryPhase(projectRoot, slug, phaseId, options) {
|
|
791
824
|
const paths = deliveryPaths(projectRoot, slug);
|
|
792
825
|
const state = readDeliveryState(projectRoot, slug);
|
|
793
826
|
const phase = findTrackedPhase(state, phaseId);
|
|
@@ -868,6 +901,10 @@ export function completeDeliveryPhase(projectRoot, slug, phaseId, options = {})
|
|
|
868
901
|
}
|
|
869
902
|
|
|
870
903
|
export function resumeShelvedDelivery(projectRoot, slug, input = {}) {
|
|
904
|
+
return withIntentMutation(projectRoot, slug, { action: 'resume-shelf', input, ownership: input.ownership }, () => resumeOwnedShelvedDelivery(projectRoot, slug, input));
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function resumeOwnedShelvedDelivery(projectRoot, slug, input) {
|
|
871
908
|
const paths = deliveryPaths(projectRoot, slug);
|
|
872
909
|
const state = readDeliveryState(projectRoot, slug);
|
|
873
910
|
assertIntentCopiesConsistent(paths, state);
|
|
@@ -894,6 +931,7 @@ export function resumeShelvedDelivery(projectRoot, slug, input = {}) {
|
|
|
894
931
|
completedAt: null,
|
|
895
932
|
summary: 'Shelf-ready delivery resumed through mandatory FitCheck.'
|
|
896
933
|
};
|
|
934
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
897
935
|
atomicJson(resolve(paths.runsRoot, `${run.id}.json`), run);
|
|
898
936
|
startCommandRun(paths.projectRoot, state.intent.id, {
|
|
899
937
|
runUuid: run.id,
|
|
@@ -916,6 +954,10 @@ export function resumeShelvedDelivery(projectRoot, slug, input = {}) {
|
|
|
916
954
|
}
|
|
917
955
|
|
|
918
956
|
export function ratifyDeliveryAmendments(projectRoot, slug, input = {}) {
|
|
957
|
+
return withIntentMutation(projectRoot, slug, { action: 'ratify-plan', input, ownership: input.ownership }, () => ratifyOwnedDeliveryAmendments(projectRoot, slug, input));
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function ratifyOwnedDeliveryAmendments(projectRoot, slug, input) {
|
|
919
961
|
const paths = deliveryPaths(projectRoot, slug);
|
|
920
962
|
const state = readDeliveryState(projectRoot, slug);
|
|
921
963
|
assertIntentCopiesConsistent(paths, state);
|
|
@@ -980,10 +1022,12 @@ export function ratifyDeliveryAmendments(projectRoot, slug, input = {}) {
|
|
|
980
1022
|
},
|
|
981
1023
|
];
|
|
982
1024
|
|
|
1025
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
983
1026
|
atomicJson(ratificationPath, ratification);
|
|
984
1027
|
try {
|
|
985
1028
|
persistTransition(paths, state);
|
|
986
1029
|
} catch (error) {
|
|
1030
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
987
1031
|
if (ratificationBefore.existed) atomicText(ratificationPath, ratificationBefore.content);
|
|
988
1032
|
else rmSync(ratificationPath, { force: true });
|
|
989
1033
|
throw error;
|
|
@@ -1197,6 +1241,17 @@ function replayCompletedEvidenceAmendment(paths, state, receiptPath, input) {
|
|
|
1197
1241
|
}
|
|
1198
1242
|
|
|
1199
1243
|
export function ratifyCompletedEvidenceAmendment(projectRoot, slug, input = {}) {
|
|
1244
|
+
try {
|
|
1245
|
+
return withIntentMutation(projectRoot, slug, { action: 'ratify-evidence', input, ownership: input.ownership }, () => ratifyOwnedCompletedEvidenceAmendment(projectRoot, slug, input));
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
if (['intent-mutation-conflict', 'intent-ownership-conflict'].includes(error.code)) {
|
|
1248
|
+
error.message = 'Another completed evidence amendment ratification is in progress. Preview again after it finishes.';
|
|
1249
|
+
}
|
|
1250
|
+
throw error;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
function ratifyOwnedCompletedEvidenceAmendment(projectRoot, slug, input) {
|
|
1200
1255
|
if (input.confirmed !== true) {
|
|
1201
1256
|
throw new Error('Completed evidence amendment requires explicit confirmation.');
|
|
1202
1257
|
}
|
|
@@ -1245,6 +1300,7 @@ function ratifyCompletedEvidenceAmendmentUnderLease(projectRoot, slug, paths, in
|
|
|
1245
1300
|
|
|
1246
1301
|
const amendmentRoot = resolve(paths.deliveryRoot, 'evidence-amendments');
|
|
1247
1302
|
const rootExisted = existsSync(amendmentRoot);
|
|
1303
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
1248
1304
|
completedEvidenceAmendmentRoot(paths, { create: true });
|
|
1249
1305
|
const ledgerSnapshots = assessment.internalPhases.map(({ ledgerPath }) => fileSnapshot(ledgerPath));
|
|
1250
1306
|
const receiptSnapshot = fileSnapshot(receiptPath);
|
|
@@ -1278,16 +1334,19 @@ function ratifyCompletedEvidenceAmendmentUnderLease(projectRoot, slug, paths, in
|
|
|
1278
1334
|
|
|
1279
1335
|
try {
|
|
1280
1336
|
for (const amendment of assessment.internalPhases) {
|
|
1337
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
1281
1338
|
atomicText(amendment.ledgerPath, amendment.candidateRaw.toString('utf8'));
|
|
1282
1339
|
validatePhaseGateAtPaths(paths, amendment.phase, JSON.parse(readFileSync(amendment.ledgerPath, 'utf8')), {
|
|
1283
1340
|
providers: [...state.phases, ...state.adjuncts]
|
|
1284
1341
|
.find((phase) => phase.id === amendment.phase)?.validation?.providers ?? state.providers ?? [],
|
|
1285
1342
|
});
|
|
1286
1343
|
}
|
|
1344
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
1287
1345
|
atomicJson(receiptPath, receipt);
|
|
1288
1346
|
input.afterAmendmentFilesWritten?.();
|
|
1289
1347
|
persistTransition(paths, state);
|
|
1290
1348
|
} catch (error) {
|
|
1349
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
1291
1350
|
for (const snapshot of ledgerSnapshots) {
|
|
1292
1351
|
if (snapshot.existed) atomicText(snapshot.path, snapshot.content);
|
|
1293
1352
|
else rmSync(snapshot.path, { force: true });
|
|
@@ -1307,6 +1366,10 @@ function ratifyCompletedEvidenceAmendmentUnderLease(projectRoot, slug, paths, in
|
|
|
1307
1366
|
}
|
|
1308
1367
|
|
|
1309
1368
|
export function recordBuildApproval(projectRoot, slug, input = {}) {
|
|
1369
|
+
return withIntentMutation(projectRoot, slug, { action: 'approve-build', input, ownership: input.ownership }, () => recordOwnedBuildApproval(projectRoot, slug, input));
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function recordOwnedBuildApproval(projectRoot, slug, input) {
|
|
1310
1373
|
const paths = deliveryPaths(projectRoot, slug);
|
|
1311
1374
|
const state = readDeliveryState(projectRoot, slug);
|
|
1312
1375
|
assertIntentCopiesConsistent(paths, state);
|
|
@@ -1330,6 +1393,7 @@ export function recordBuildApproval(projectRoot, slug, input = {}) {
|
|
|
1330
1393
|
};
|
|
1331
1394
|
const approvalPath = resolve(paths.gatesRoot, 'build/build-approval.json');
|
|
1332
1395
|
const approvalBefore = fileSnapshot(approvalPath);
|
|
1396
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
1333
1397
|
atomicJson(approvalPath, approval);
|
|
1334
1398
|
state.approvals.build = {
|
|
1335
1399
|
path: relative(paths.projectRoot, approvalPath).replaceAll('\\', '/'),
|
|
@@ -1341,6 +1405,7 @@ export function recordBuildApproval(projectRoot, slug, input = {}) {
|
|
|
1341
1405
|
try {
|
|
1342
1406
|
persistTransition(paths, state);
|
|
1343
1407
|
} catch (error) {
|
|
1408
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
1344
1409
|
if (approvalBefore.existed) atomicText(approvalPath, approvalBefore.content);
|
|
1345
1410
|
else rmSync(approvalPath, { force: true });
|
|
1346
1411
|
throw error;
|
|
@@ -1363,6 +1428,10 @@ export function recordBuildApproval(projectRoot, slug, input = {}) {
|
|
|
1363
1428
|
}
|
|
1364
1429
|
|
|
1365
1430
|
export function recordManualQaApproval(projectRoot, slug, input = {}) {
|
|
1431
|
+
return withIntentMutation(projectRoot, slug, { action: 'approve-manual-qa', input, ownership: input.ownership }, () => recordOwnedManualQaApproval(projectRoot, slug, input));
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
function recordOwnedManualQaApproval(projectRoot, slug, input) {
|
|
1366
1435
|
const paths = deliveryPaths(projectRoot, slug);
|
|
1367
1436
|
const state = readDeliveryState(projectRoot, slug);
|
|
1368
1437
|
assertIntentCopiesConsistent(paths, state);
|
|
@@ -1386,6 +1455,7 @@ export function recordManualQaApproval(projectRoot, slug, input = {}) {
|
|
|
1386
1455
|
};
|
|
1387
1456
|
const approvalPath = resolve(paths.gatesRoot, 'manual-qa/approval.json');
|
|
1388
1457
|
const approvalBefore = fileSnapshot(approvalPath);
|
|
1458
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
1389
1459
|
atomicJson(approvalPath, approval);
|
|
1390
1460
|
const gate = state.humanGates.find((candidate) => candidate.id === 'manual-qa');
|
|
1391
1461
|
gate.status = 'approved';
|
|
@@ -1398,6 +1468,7 @@ export function recordManualQaApproval(projectRoot, slug, input = {}) {
|
|
|
1398
1468
|
try {
|
|
1399
1469
|
persistTransition(paths, state);
|
|
1400
1470
|
} catch (error) {
|
|
1471
|
+
assertIntentMutation(paths.projectRoot, state.intent.id);
|
|
1401
1472
|
if (approvalBefore.existed) atomicText(approvalPath, approvalBefore.content);
|
|
1402
1473
|
else rmSync(approvalPath, { force: true });
|
|
1403
1474
|
throw error;
|
|
@@ -13,6 +13,9 @@ import { prepareContextPack } from './runtime/context-assembly.mjs';
|
|
|
13
13
|
|
|
14
14
|
const slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
15
15
|
const sha256Pattern = /^sha256:[a-f0-9]{64}$/;
|
|
16
|
+
// Disposable acceleration only: every use still performs fresh pack validation.
|
|
17
|
+
// One small entry bounds retained source bodies across projects and revisions.
|
|
18
|
+
let recentResolution = null;
|
|
16
19
|
|
|
17
20
|
function clean(value) {
|
|
18
21
|
return String(value ?? '').trim();
|
|
@@ -66,12 +69,37 @@ function deliveryWorkspace(projectRoot, slug) {
|
|
|
66
69
|
return { project, root, state };
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
function resolutionIdentity(projectRoot, selection) {
|
|
73
|
+
const pin = ({ id, version, digest, sourceClass }) => ({ id, version, digest, sourceClass });
|
|
74
|
+
return canonical({ project: realpathSync(projectRoot), root: pin(selection.root),
|
|
75
|
+
packs: selection.packs.map(pin), effectiveDigest: selection.effectiveDigest });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function smallResolution(resolved) {
|
|
79
|
+
if (resolved.packs.length > 8) return false;
|
|
80
|
+
let bytes = 0, count = 0;
|
|
81
|
+
for (const pack of resolved.packs) {
|
|
82
|
+
for (const item of pack.design_system.contributions) {
|
|
83
|
+
bytes += Buffer.byteLength(item.content);
|
|
84
|
+
if (++count > 256 || bytes > 256 * 1024) return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
69
90
|
function resolvedSelection(projectRoot, options = {}) {
|
|
70
91
|
const status = designSystemStatus(projectRoot, options);
|
|
71
92
|
if (status.status !== 'ready') throw new Error(`Project design system is ${status.status}; re-resolve and select it before application`);
|
|
93
|
+
const identity = resolutionIdentity(projectRoot, status);
|
|
94
|
+
if (recentResolution?.identity === identity) return { status, resolved: recentResolution.resolved };
|
|
95
|
+
recentResolution = null;
|
|
72
96
|
const { config } = loadProjectConfig(projectRoot);
|
|
73
97
|
const catalogue = listDesignSystems({ ...options, projectRoot });
|
|
74
98
|
const resolved = resolveDesignSystem(config.design_system?.root?.id ?? DEFAULT_DESIGN_SYSTEM_ID, catalogue);
|
|
99
|
+
// A cold resolution must describe the exact source that passed validation,
|
|
100
|
+
// including provenance; otherwise a changed pack could inherit old approval.
|
|
101
|
+
if (resolutionIdentity(projectRoot, resolved) !== identity) throw new Error('Design-system source changed during preparation; re-resolve before application');
|
|
102
|
+
if (smallResolution(resolved)) recentResolution = { identity, resolved };
|
|
75
103
|
return { status, resolved };
|
|
76
104
|
}
|
|
77
105
|
|