openyida 2026.9.2-1 → 2026.9.2-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/lib/app/nav-group.js +221 -53
- package/lib/app/publish.js +13 -1
- package/lib/core/agent-capabilities.js +38 -1
- package/lib/core/cli-error.js +6 -1
- package/lib/core/command-manifest.js +33 -5
- package/lib/core/locales/en.js +3 -0
- package/lib/core/locales/zh.js +4 -1
- package/lib/core/query-data.js +126 -8
- package/package.json +1 -1
- package/scripts/postinstall.js +3 -3
- package/yida-skills/SKILL.md +8 -8
- package/yida-skills/skills/yida-app/SKILL.md +8 -7
- package/yida-skills/skills/yida-app/references/common-issues.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +31 -14
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +2 -1
- package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +2 -2
- package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +26 -11
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +21 -10
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +5 -5
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +1 -1
- package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +1 -1
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +4 -4
- package/yida-skills/skills/yida-create-app/SKILL.md +4 -4
- package/yida-skills/skills/yida-create-form-page/SKILL.md +2 -2
- package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +1 -1
- package/yida-skills/skills/yida-custom-page/SKILL.md +4 -4
- package/yida-skills/skills/yida-custom-page/references/coding-guide.md +1 -1
- package/yida-skills/skills/yida-custom-page/references/design-system.md +1 -1
- package/yida-skills/skills/yida-data-management/SKILL.md +6 -4
- package/yida-skills/skills/yida-data-management/references/data-format-guide.md +13 -5
- package/yida-skills/skills/yida-design/SKILL.md +34 -37
- package/yida-skills/skills/yida-design/references/page-quality-gates.md +2 -2
- package/yida-skills/skills/yida-design/references/style-design-selection.md +2 -2
- package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +1 -1
- package/yida-skills/skills/yida-design/references/visual-scaffold-recipes.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +4 -5
- package/yida-skills/skills/yida-design/workflow/output-design.md +22 -2
- package/yida-skills/skills/yida-design/workflow/output-prd.md +3 -1
- package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +14 -0
- package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +8 -8
- package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +3 -3
- package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +4 -4
- package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +22 -55
- package/yida-skills/skills/yida-nav-group/SKILL.md +3 -1
- package/yida-skills/skills/yida-page-config/SKILL.md +3 -1
- package/yida-skills/skills/yida-prd/SKILL.md +48 -0
- package/yida-skills/skills/yida-prd/references/app/blueprint.md +73 -0
- package/yida-skills/skills/yida-prd/references/app/navigation-patterns.md +43 -0
- package/yida-skills/skills/yida-prd/references/app/role-journey.md +30 -0
- package/yida-skills/skills/yida-prd/workflow/output-prd.md +190 -0
- package/yida-skills/skills/yida-prd/workflow/step-1-read-brief.md +15 -0
- package/yida-skills/skills/{yida-design/workflow/step-3-information-architecture.md → yida-prd/workflow/step-2-information-architecture.md} +2 -2
- package/yida-skills/skills/yida-publish-page/SKILL.md +1 -1
- package/yida-skills/skills/yida-requirement-analysis/SKILL.md +49 -0
- package/yida-skills/skills-index.json +32 -9
- package/yida-skills/skills/yida-design/workflow/step-1-positioning.md +0 -58
package/lib/app/nav-group.js
CHANGED
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
requestWithAutoLogin,
|
|
8
8
|
} = require('../core/utils');
|
|
9
9
|
const { createAuthRef, isAuthRefReady } = require('../core/yida-client');
|
|
10
|
+
const { CliError } = require('../core/cli-error');
|
|
10
11
|
const { parseYidaI18n } = require('../core/yida-i18n');
|
|
11
12
|
|
|
12
13
|
const ROOT_NAV_UUID = 'NAV-SYSTEM-PARENT-UUID';
|
|
@@ -422,6 +423,92 @@ function autoOrderRootNodes(list) {
|
|
|
422
423
|
};
|
|
423
424
|
}
|
|
424
425
|
|
|
426
|
+
function snapshotNavigationRoots(roots) {
|
|
427
|
+
const snapshot = [];
|
|
428
|
+
|
|
429
|
+
const visit = (nodes, parentNavUuid) => {
|
|
430
|
+
(nodes || []).forEach((node, siblingIndex) => {
|
|
431
|
+
snapshot.push({
|
|
432
|
+
navUuid: node.navUuid,
|
|
433
|
+
parentNavUuid,
|
|
434
|
+
siblingIndex,
|
|
435
|
+
navType: node.navType || '',
|
|
436
|
+
});
|
|
437
|
+
visit(node.children || [], node.navUuid);
|
|
438
|
+
});
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
visit(roots || [], ROOT_NAV_UUID);
|
|
442
|
+
return snapshot;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function buildNavigationSnapshot(list) {
|
|
446
|
+
return snapshotNavigationRoots(buildMutableForest(list).roots);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function navigationSnapshotsEqual(left, right) {
|
|
450
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return left.every((item, index) => {
|
|
455
|
+
const other = right[index];
|
|
456
|
+
return other &&
|
|
457
|
+
item.navUuid === other.navUuid &&
|
|
458
|
+
item.parentNavUuid === other.parentNavUuid &&
|
|
459
|
+
item.siblingIndex === other.siblingIndex &&
|
|
460
|
+
item.navType === other.navType;
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function findFirstNavigationDifference(actual, expected) {
|
|
465
|
+
const actualItems = Array.isArray(actual) ? actual : [];
|
|
466
|
+
const expectedItems = Array.isArray(expected) ? expected : [];
|
|
467
|
+
const length = Math.max(actualItems.length, expectedItems.length);
|
|
468
|
+
|
|
469
|
+
for (let index = 0; index < length; index++) {
|
|
470
|
+
const actualItem = actualItems[index] || null;
|
|
471
|
+
const expectedItem = expectedItems[index] || null;
|
|
472
|
+
if (!actualItem || !expectedItem ||
|
|
473
|
+
actualItem.navUuid !== expectedItem.navUuid ||
|
|
474
|
+
actualItem.parentNavUuid !== expectedItem.parentNavUuid ||
|
|
475
|
+
actualItem.siblingIndex !== expectedItem.siblingIndex ||
|
|
476
|
+
actualItem.navType !== expectedItem.navType) {
|
|
477
|
+
return {
|
|
478
|
+
index,
|
|
479
|
+
actual: actualItem,
|
|
480
|
+
expected: expectedItem,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function summarizeNavigationOrderDiff(code, beforeOrder, expectedOrder, observedOrder) {
|
|
489
|
+
const hasObservedOrder = Array.isArray(observedOrder);
|
|
490
|
+
return {
|
|
491
|
+
beforeCount: Array.isArray(beforeOrder) ? beforeOrder.length : 0,
|
|
492
|
+
expectedCount: Array.isArray(expectedOrder) ? expectedOrder.length : 0,
|
|
493
|
+
observedCount: hasObservedOrder ? observedOrder.length : null,
|
|
494
|
+
firstPlannedChange: findFirstNavigationDifference(beforeOrder, expectedOrder),
|
|
495
|
+
...(code === 'NAV_ORDER_READBACK_MISMATCH' && hasObservedOrder
|
|
496
|
+
? { firstReadbackMismatch: findFirstNavigationDifference(observedOrder, expectedOrder) }
|
|
497
|
+
: {}),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function buildNavigationOrderPlan(list, ordered) {
|
|
502
|
+
const beforeOrder = buildNavigationSnapshot(list);
|
|
503
|
+
const expectedOrder = snapshotNavigationRoots(ordered.roots);
|
|
504
|
+
return {
|
|
505
|
+
beforeOrder,
|
|
506
|
+
expectedOrder,
|
|
507
|
+
targetNavUuids: ordered.orderedNavUuids.slice(),
|
|
508
|
+
changed: !navigationSnapshotsEqual(beforeOrder, expectedOrder),
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
425
512
|
function countChildren(list, navUuid) {
|
|
426
513
|
return (list || []).filter((node) => node.parentNavUuid === navUuid).length;
|
|
427
514
|
}
|
|
@@ -455,7 +542,7 @@ async function fetchNavigationList(appType, authRef) {
|
|
|
455
542
|
return Array.isArray(result.content) ? result.content : [];
|
|
456
543
|
}
|
|
457
544
|
|
|
458
|
-
async function postNavAction(appType, action, payload, authRef) {
|
|
545
|
+
async function postNavAction(appType, action, payload, authRef, options = {}) {
|
|
459
546
|
const endpoints = {
|
|
460
547
|
save: 'saveFormNavigation.json',
|
|
461
548
|
update: 'updateFormNavigation.json',
|
|
@@ -475,21 +562,143 @@ async function postNavAction(appType, action, payload, authRef) {
|
|
|
475
562
|
throw new Error(`Unknown nav action: ${action}`);
|
|
476
563
|
}
|
|
477
564
|
|
|
478
|
-
const
|
|
565
|
+
const requestFn = (auth) => {
|
|
479
566
|
const path = `/dingtalk/web/${appType}/query/formnav/${endpoint}` +
|
|
480
567
|
`?_api=${encodeURIComponent(apiNames[action])}` +
|
|
481
568
|
'&_mock=false' +
|
|
482
569
|
`&_stamp=${Date.now()}`;
|
|
483
570
|
return httpPost(auth.baseUrl, path, querystring.stringify(payload));
|
|
484
|
-
}
|
|
571
|
+
};
|
|
572
|
+
const result = options.oneShot === true
|
|
573
|
+
? await requestFn(authRef)
|
|
574
|
+
: await requestWithAutoLogin(requestFn, authRef);
|
|
485
575
|
|
|
486
|
-
if (!result || result.success === false) {
|
|
576
|
+
if (!result || result.success === false || result.__needLogin || result.__csrfExpired) {
|
|
487
577
|
throw new Error(result ? result.errorMsg || `Nav action failed: ${action}` : `Nav action failed: ${action}`);
|
|
488
578
|
}
|
|
489
579
|
|
|
490
580
|
return result;
|
|
491
581
|
}
|
|
492
582
|
|
|
583
|
+
function buildNavigationOrderSuccess(appType, action, ordered, observedList, plan, recoveredByReadback) {
|
|
584
|
+
const orderedNodes = ordered.orderedNavUuids.map((navUuid) => normalizeNavNode(
|
|
585
|
+
resolveNode(observedList, navUuid, { excludeSystem: true })
|
|
586
|
+
));
|
|
587
|
+
return {
|
|
588
|
+
success: true,
|
|
589
|
+
appType,
|
|
590
|
+
action,
|
|
591
|
+
targetParentNavUuid: ROOT_NAV_UUID,
|
|
592
|
+
changed: plan.changed,
|
|
593
|
+
alreadyApplied: !plan.changed,
|
|
594
|
+
mutationPerformed: plan.changed,
|
|
595
|
+
readbackVerified: true,
|
|
596
|
+
recoveredByReadback: recoveredByReadback === true,
|
|
597
|
+
sideEffectState: plan.changed ? 'committed' : 'none',
|
|
598
|
+
orderedNavUuids: ordered.orderedNavUuids,
|
|
599
|
+
orderedNodes,
|
|
600
|
+
skipped: !plan.changed,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function buildNavigationOrderError(code, appType, action, plan, observedOrder, cause) {
|
|
605
|
+
const sideEffectState = code === 'NAV_ORDER_NOT_APPLIED' ? 'none' : 'unknown';
|
|
606
|
+
const retrySafe = code === 'NAV_ORDER_NOT_APPLIED';
|
|
607
|
+
const readbackVerified = code === 'NAV_ORDER_NOT_APPLIED';
|
|
608
|
+
const status = code === 'NAV_ORDER_NOT_APPLIED'
|
|
609
|
+
? 'NOT_APPLIED'
|
|
610
|
+
: code === 'NAV_ORDER_READBACK_MISMATCH'
|
|
611
|
+
? 'SEMANTIC_FAILURE'
|
|
612
|
+
: 'RESULT_UNKNOWN';
|
|
613
|
+
return new CliError(code, {
|
|
614
|
+
code,
|
|
615
|
+
details: {
|
|
616
|
+
target: {
|
|
617
|
+
type: 'navigationOrder',
|
|
618
|
+
appType,
|
|
619
|
+
action,
|
|
620
|
+
navUuids: plan.targetNavUuids,
|
|
621
|
+
},
|
|
622
|
+
changed: plan.changed,
|
|
623
|
+
...(code === 'NAV_ORDER_NOT_APPLIED' ? { mutationPerformed: false } : {}),
|
|
624
|
+
readbackVerified,
|
|
625
|
+
retryable: retrySafe,
|
|
626
|
+
retrySafe,
|
|
627
|
+
sideEffectState,
|
|
628
|
+
status,
|
|
629
|
+
readbackAllowed: true,
|
|
630
|
+
orderDiff: summarizeNavigationOrderDiff(
|
|
631
|
+
code,
|
|
632
|
+
plan.beforeOrder,
|
|
633
|
+
plan.expectedOrder,
|
|
634
|
+
observedOrder
|
|
635
|
+
),
|
|
636
|
+
cause: cause && cause.message ? cause.message : undefined,
|
|
637
|
+
nextStep: `openyida nav-group list ${appType} --flat`,
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async function applyNavigationOrder(appType, action, list, ordered, authRef, dependencies = {}) {
|
|
643
|
+
const fetchList = dependencies.fetchNavigationList || fetchNavigationList;
|
|
644
|
+
const postAction = dependencies.postNavAction || postNavAction;
|
|
645
|
+
const plan = buildNavigationOrderPlan(list, ordered);
|
|
646
|
+
|
|
647
|
+
if (!plan.changed) {
|
|
648
|
+
return buildNavigationOrderSuccess(appType, action, ordered, list, plan, false);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const firstNode = resolveNode(list, ordered.orderedNavUuids[0], { excludeSystem: true });
|
|
652
|
+
let mutationError = null;
|
|
653
|
+
try {
|
|
654
|
+
await postAction(appType, 'updateOrderNew', {
|
|
655
|
+
currentId: firstNode.id,
|
|
656
|
+
parentNavUuid: ROOT_NAV_UUID,
|
|
657
|
+
navType: firstNode.navType,
|
|
658
|
+
ids: ordered.ids.join(','),
|
|
659
|
+
}, authRef, { oneShot: true });
|
|
660
|
+
} catch (error) {
|
|
661
|
+
mutationError = error;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
let updatedList;
|
|
665
|
+
try {
|
|
666
|
+
updatedList = await fetchList(appType, authRef);
|
|
667
|
+
} catch (readbackError) {
|
|
668
|
+
throw buildNavigationOrderError(
|
|
669
|
+
'NAV_ORDER_RESULT_UNKNOWN',
|
|
670
|
+
appType,
|
|
671
|
+
action,
|
|
672
|
+
plan,
|
|
673
|
+
null,
|
|
674
|
+
readbackError
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const observedOrder = buildNavigationSnapshot(updatedList);
|
|
679
|
+
if (navigationSnapshotsEqual(observedOrder, plan.expectedOrder)) {
|
|
680
|
+
return buildNavigationOrderSuccess(appType, action, ordered, updatedList, plan, !!mutationError);
|
|
681
|
+
}
|
|
682
|
+
if (navigationSnapshotsEqual(observedOrder, plan.beforeOrder)) {
|
|
683
|
+
throw buildNavigationOrderError(
|
|
684
|
+
'NAV_ORDER_NOT_APPLIED',
|
|
685
|
+
appType,
|
|
686
|
+
action,
|
|
687
|
+
plan,
|
|
688
|
+
observedOrder,
|
|
689
|
+
mutationError
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
throw buildNavigationOrderError(
|
|
693
|
+
'NAV_ORDER_READBACK_MISMATCH',
|
|
694
|
+
appType,
|
|
695
|
+
action,
|
|
696
|
+
plan,
|
|
697
|
+
observedOrder,
|
|
698
|
+
mutationError
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
|
|
493
702
|
function normalizeListOutput(list, options = {}) {
|
|
494
703
|
const includeSystem = options.includeSystem === true;
|
|
495
704
|
return (list || [])
|
|
@@ -647,62 +856,17 @@ async function orderRootNodes(appType, values, authRef) {
|
|
|
647
856
|
|
|
648
857
|
const list = await fetchNavigationList(appType, authRef);
|
|
649
858
|
const ordered = reorderRootNodes(list, values);
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
await postNavAction(appType, 'updateOrderNew', {
|
|
653
|
-
currentId: firstNode.id,
|
|
654
|
-
parentNavUuid: ROOT_NAV_UUID,
|
|
655
|
-
navType: firstNode.navType,
|
|
656
|
-
ids: ordered.ids.join(','),
|
|
657
|
-
}, authRef);
|
|
658
|
-
|
|
659
|
-
const updatedList = await fetchNavigationList(appType, authRef);
|
|
660
|
-
const orderedNodes = ordered.orderedNavUuids.map((navUuid) => normalizeNavNode(
|
|
661
|
-
resolveNode(updatedList, navUuid, { excludeSystem: true })
|
|
662
|
-
));
|
|
663
|
-
return {
|
|
664
|
-
success: true,
|
|
665
|
-
appType,
|
|
666
|
-
action: 'order',
|
|
667
|
-
targetParentNavUuid: ROOT_NAV_UUID,
|
|
668
|
-
orderedNavUuids: ordered.orderedNavUuids,
|
|
669
|
-
orderedNodes,
|
|
670
|
-
};
|
|
859
|
+
return applyNavigationOrder(appType, 'order', list, ordered, authRef);
|
|
671
860
|
}
|
|
672
861
|
|
|
673
862
|
async function autoOrderNavigation(appType, authRef) {
|
|
674
863
|
const list = await fetchNavigationList(appType, authRef);
|
|
675
864
|
const ordered = autoOrderRootNodes(list);
|
|
676
865
|
if (!ordered.orderedNavUuids.length) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
appType,
|
|
680
|
-
action: 'auto-order',
|
|
681
|
-
targetParentNavUuid: ROOT_NAV_UUID,
|
|
682
|
-
orderedNavUuids: [],
|
|
683
|
-
orderedNodes: [],
|
|
684
|
-
skipped: true,
|
|
685
|
-
};
|
|
866
|
+
const plan = buildNavigationOrderPlan(list, ordered);
|
|
867
|
+
return buildNavigationOrderSuccess(appType, 'auto-order', ordered, list, plan, false);
|
|
686
868
|
}
|
|
687
|
-
|
|
688
|
-
const firstNode = resolveNode(list, ordered.orderedNavUuids[0], { excludeSystem: true });
|
|
689
|
-
await postNavAction(appType, 'updateOrderNew', {
|
|
690
|
-
currentId: firstNode.id,
|
|
691
|
-
parentNavUuid: ROOT_NAV_UUID,
|
|
692
|
-
navType: firstNode.navType,
|
|
693
|
-
ids: ordered.ids.join(','),
|
|
694
|
-
}, authRef);
|
|
695
|
-
|
|
696
|
-
const updatedList = await fetchNavigationList(appType, authRef);
|
|
697
|
-
const updatedOrdered = autoOrderRootNodes(updatedList);
|
|
698
|
-
return {
|
|
699
|
-
success: true,
|
|
700
|
-
appType,
|
|
701
|
-
action: 'auto-order',
|
|
702
|
-
targetParentNavUuid: ROOT_NAV_UUID,
|
|
703
|
-
orderedNavUuids: updatedOrdered.orderedNavUuids,
|
|
704
|
-
orderedNodes: updatedOrdered.orderedNodes,
|
|
705
|
-
};
|
|
869
|
+
return applyNavigationOrder(appType, 'auto-order', list, ordered, authRef);
|
|
706
870
|
}
|
|
707
871
|
|
|
708
872
|
async function setVisibility(appType, value, hidden, authRef) {
|
|
@@ -790,8 +954,11 @@ async function run(args) {
|
|
|
790
954
|
|
|
791
955
|
module.exports = {
|
|
792
956
|
ROOT_NAV_UUID,
|
|
957
|
+
applyNavigationOrder,
|
|
793
958
|
autoOrderNavigation,
|
|
794
959
|
autoOrderRootNodes,
|
|
960
|
+
buildNavigationOrderPlan,
|
|
961
|
+
buildNavigationSnapshot,
|
|
795
962
|
buildI18nTitle,
|
|
796
963
|
buildNavigationTree,
|
|
797
964
|
compareNavigationNodes,
|
|
@@ -800,6 +967,7 @@ module.exports = {
|
|
|
800
967
|
getNavigationPriority,
|
|
801
968
|
moveNodeInTree,
|
|
802
969
|
normalizeNavNode,
|
|
970
|
+
navigationSnapshotsEqual,
|
|
803
971
|
orderRootNodes,
|
|
804
972
|
parseArgs,
|
|
805
973
|
reorderRootNodes,
|
package/lib/app/publish.js
CHANGED
|
@@ -28,7 +28,7 @@ const { createAuthRef, isTokenAuthRef } = require('../core/yida-client');
|
|
|
28
28
|
const { requireSchemaServerRevision } = require('../core/server-revision');
|
|
29
29
|
const { t } = require('../core/i18n');
|
|
30
30
|
const { banner, step, label, success, fail, warn, info, error, result, usage, hint } = require('../core/chalk');
|
|
31
|
-
const { CliError } = require('../core/cli-error');
|
|
31
|
+
const { CliError, isCliError, toErrorPayload } = require('../core/cli-error');
|
|
32
32
|
const { compileSource } = require('./page-compiler');
|
|
33
33
|
const { compileCanvas } = require('./canvas-compile');
|
|
34
34
|
const { runLintCheck } = require('./page-linter');
|
|
@@ -723,6 +723,18 @@ async function runMain(argv) {
|
|
|
723
723
|
success(t('publish.nav_order_ok', orderedNames || '-'));
|
|
724
724
|
} catch (navOrderError) {
|
|
725
725
|
navOrderWarning = navOrderError && navOrderError.message ? navOrderError.message : t('common.unknown_error');
|
|
726
|
+
navOrderResult = isCliError(navOrderError)
|
|
727
|
+
? toErrorPayload(navOrderError)
|
|
728
|
+
: {
|
|
729
|
+
success: false,
|
|
730
|
+
errorCode: 'NAV_ORDER_RESULT_UNKNOWN',
|
|
731
|
+
errorMsg: navOrderWarning,
|
|
732
|
+
retryable: false,
|
|
733
|
+
retrySafe: false,
|
|
734
|
+
sideEffectState: 'unknown',
|
|
735
|
+
readbackVerified: false,
|
|
736
|
+
nextStep: `openyida nav-group list ${appType} --flat`,
|
|
737
|
+
};
|
|
726
738
|
warn(t('publish.nav_order_failed', navOrderWarning));
|
|
727
739
|
}
|
|
728
740
|
}
|
|
@@ -446,6 +446,37 @@ function compactBuilderPath(builderPath) {
|
|
|
446
446
|
};
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
function buildFullAppArtifactRoute(manifest) {
|
|
450
|
+
const workflow = manifest.summary.core_workflows.full_app_build;
|
|
451
|
+
return {
|
|
452
|
+
orchestrator_skill_id: workflow.orchestrator_skill_id,
|
|
453
|
+
requirement_analysis_skill_id: workflow.requirement_analysis_skill_id,
|
|
454
|
+
requirement_brief_path: workflow.requirement_brief_path,
|
|
455
|
+
artifact_generation: workflow.artifact_generation,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function buildApplicationEntryPolicy(builderPath, env = process.env) {
|
|
460
|
+
const runtime = builderPath.runtime || {};
|
|
461
|
+
const managedRuntime = String(env.OPENYIDA_MANAGED_RUNTIME || '').trim().toLowerCase();
|
|
462
|
+
const managedCloudAgent = managedRuntime === 'cloud' ||
|
|
463
|
+
runtime.runtime === 'web_sandbox' ||
|
|
464
|
+
runtime.tool === 'mulerun';
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
schema_version: 1,
|
|
468
|
+
environment: managedCloudAgent ? 'managed_cloud_agent' : 'non_cloud_agent',
|
|
469
|
+
delivery_unit: 'single_application_entry_group',
|
|
470
|
+
resource_delivery: 'summary_only',
|
|
471
|
+
internal_artifact_delivery: 'never',
|
|
472
|
+
entries: {
|
|
473
|
+
workbench: 'always',
|
|
474
|
+
custom: 'when_entry_mode_standalone_and_is_render_nav_false_readback',
|
|
475
|
+
admin: managedCloudAgent ? 'omit' : 'include',
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
449
480
|
function canonicalize(value) {
|
|
450
481
|
if (Array.isArray(value)) {
|
|
451
482
|
return value.map(canonicalize);
|
|
@@ -514,6 +545,7 @@ function buildAgentCapabilitiesSummary() {
|
|
|
514
545
|
const builderPath = compactBuilderPath(
|
|
515
546
|
buildBuilderPath(loginStatus, projectRoot, manifest, process.env, runtime)
|
|
516
547
|
);
|
|
548
|
+
const applicationEntryPolicy = buildApplicationEntryPolicy(builderPath, process.env);
|
|
517
549
|
|
|
518
550
|
return {
|
|
519
551
|
schema_version: 1,
|
|
@@ -532,6 +564,8 @@ function buildAgentCapabilitiesSummary() {
|
|
|
532
564
|
command_manifest_digest_algorithm: 'sha256',
|
|
533
565
|
command_count: manifest.summary.command_count,
|
|
534
566
|
full_capabilities_command: 'openyida agent-capabilities --json',
|
|
567
|
+
full_app_artifact_route: buildFullAppArtifactRoute(manifest),
|
|
568
|
+
application_entry_policy: applicationEntryPolicy,
|
|
535
569
|
runtime,
|
|
536
570
|
builder_path: builderPath,
|
|
537
571
|
};
|
|
@@ -551,6 +585,7 @@ function buildAgentCapabilities() {
|
|
|
551
585
|
const loginStatus = redactLogin(getAuthStatus({ projectRoot, includeSecrets: false }));
|
|
552
586
|
const runtime = buildRuntimeSnapshot(projectRoot);
|
|
553
587
|
const builderPath = buildBuilderPath(loginStatus, projectRoot, manifest, process.env, runtime);
|
|
588
|
+
const applicationEntryPolicy = buildApplicationEntryPolicy(builderPath, process.env);
|
|
554
589
|
|
|
555
590
|
return {
|
|
556
591
|
schema_version: 1,
|
|
@@ -564,6 +599,7 @@ function buildAgentCapabilities() {
|
|
|
564
599
|
active: envSnapshot.active,
|
|
565
600
|
runtime,
|
|
566
601
|
login: loginStatus,
|
|
602
|
+
application_entry_policy: applicationEntryPolicy,
|
|
567
603
|
recommended: {
|
|
568
604
|
preflight_command: 'openyida agent-capabilities --summary-json',
|
|
569
605
|
full_capabilities_command: 'openyida agent-capabilities --json',
|
|
@@ -607,7 +643,7 @@ function buildAgentCapabilities() {
|
|
|
607
643
|
],
|
|
608
644
|
retry_policy: 'Do not repeat the same failed command without changing login state, organization, parameters, files, or field IDs.',
|
|
609
645
|
completion_contracts: {
|
|
610
|
-
full_app: 'Default full-app build is complete after creating or reusing the app, core forms, primary page, publishing it, and returning
|
|
646
|
+
full_app: 'Default full-app build is complete after creating or reusing the app, core forms, primary page, publishing it, and returning one named application entry group. Summarize business resources in prose; do not deliver one artifact or link card per resource.',
|
|
611
647
|
},
|
|
612
648
|
full_app_data_contract: 'Default full-app page code must not call this.dataSourceMap.* unless the same run created and bound a designer data source; use this.utils.yida.* or an entry-only page by default.',
|
|
613
649
|
},
|
|
@@ -638,6 +674,7 @@ async function run(args = []) {
|
|
|
638
674
|
module.exports = {
|
|
639
675
|
buildAgentCapabilities,
|
|
640
676
|
buildAgentCapabilitiesSummary,
|
|
677
|
+
buildApplicationEntryPolicy,
|
|
641
678
|
compactBuilderPath,
|
|
642
679
|
buildCommandManifestDigest,
|
|
643
680
|
run,
|
package/lib/core/cli-error.js
CHANGED
|
@@ -50,6 +50,10 @@ function toErrorPayload(error) {
|
|
|
50
50
|
'deleted',
|
|
51
51
|
'alreadyAbsent',
|
|
52
52
|
'mutationAccepted',
|
|
53
|
+
'mutationPerformed',
|
|
54
|
+
'changed',
|
|
55
|
+
'alreadyApplied',
|
|
56
|
+
'recoveredByReadback',
|
|
53
57
|
'readbackVerified',
|
|
54
58
|
'status',
|
|
55
59
|
].forEach((key) => {
|
|
@@ -86,7 +90,8 @@ function shouldUseStructuredErrorOutput(error, args = []) {
|
|
|
86
90
|
&& details.retrySafe === false
|
|
87
91
|
&& details.sideEffectState === 'unknown'
|
|
88
92
|
);
|
|
89
|
-
|
|
93
|
+
const navigationOrderFailure = /^NAV_ORDER_/.test(error.code || '');
|
|
94
|
+
return ownedPartial || mutationOutcomeUnknown || navigationOrderFailure;
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
module.exports = {
|
|
@@ -1456,17 +1456,45 @@ function summarizeLocalizedCommands(commands) {
|
|
|
1456
1456
|
core_workflows: {
|
|
1457
1457
|
full_app_build: {
|
|
1458
1458
|
mode: 'unified_build',
|
|
1459
|
+
orchestrator_skill_id: 'yida-app',
|
|
1459
1460
|
trigger_phrases: ['默认方案', '不要追问', '直接创建', '尽快搭建'],
|
|
1460
1461
|
default_form_visual_guidance_skill_id: 'yida-form-detail',
|
|
1461
1462
|
default_page_skill_id: 'yida-canvas-custom-page',
|
|
1462
1463
|
default_ui_guidance_skill_id: 'yida-design',
|
|
1464
|
+
requirement_analysis_skill_id: 'yida-requirement-analysis',
|
|
1465
|
+
requirement_brief_path: '.cache/openyida/<project>/requirement-brief.json',
|
|
1466
|
+
artifact_generation: {
|
|
1467
|
+
mode: 'parallel',
|
|
1468
|
+
audience: 'internal',
|
|
1469
|
+
user_visible_delivery: false,
|
|
1470
|
+
tasks: [
|
|
1471
|
+
{ skill_id: 'yida-prd', output_path: 'prd/<project>/prd.md' },
|
|
1472
|
+
{ skill_id: 'yida-design', output_path: 'prd/<project>/design.md' },
|
|
1473
|
+
],
|
|
1474
|
+
join_owner_skill_id: 'yida-app',
|
|
1475
|
+
},
|
|
1463
1476
|
ordinary_jsx_skill_id: 'yida-custom-page',
|
|
1464
1477
|
form_visual_guidance_policy: 'Default form page development loads yida-form-detail as form visual guidance and merges Divider semantic grouping into field JSON. The runtime loads the same application custom theme CSS file in custom pages, forms, submissions, formDetail pages, and FormOpenContainer iframes so all contexts use aligned theme variables.',
|
|
1465
1478
|
page_skill_policy: 'Use YidaCodeCanvas by default for new primary custom pages. Choose yida-custom-page only when the current target has been identified as existing .oyd.jsx/.oyb.jsx/renderJsx/platform Jsx component maintenance; do not switch to yida-custom-page merely because a page needs data, forms, uploads, this.utils.yida.*, or dataSourceMap-like capabilities.',
|
|
1466
|
-
product_design_policy: 'Full app creation first resolves resource context
|
|
1467
|
-
ui_guidance_policy: '
|
|
1468
|
-
default_nav_order_policy: 'After the primary page is successfully published,
|
|
1469
|
-
final_link_policy: 'Return exactly one
|
|
1479
|
+
product_design_policy: 'Full app creation first resolves resource context. yida-requirement-analysis writes one shared requirement brief. yida-prd and yida-design start in parallel from that brief and own prd/<project>/prd.md and prd/<project>/design.md independently; neither waits for or overwrites the other. yida-app joins both artifacts, validates their references, and only then creates or fills missing app/form/process/page resources.',
|
|
1480
|
+
ui_guidance_policy: 'Page implementation consumes yida-prd prd.md for positioning, information architecture, page prototype, native form entry policy, material strategy, business-specific checks, resource creation order, page implementation delivery order, navigation order, and acceptance criteria; it consumes yida-design design.md for app custom theme CSS delivery, themeColor/navTheme, visual states, visualScaffold, surface material, rounded rules, density rules, components, and state styling. prd.md and design.md are the only design sources of truth. Page implementation may extract page-spec.json as a derived implementation handoff from prd.md + design.md; conflicts are resolved by sending business conflicts to yida-prd and visual conflicts to yida-design before regenerating the spec. Demo data, screenshots, public sharing, data-source deep binding, and fine navigation grouping are optional after explicit user request or PRD acceptance criteria.',
|
|
1481
|
+
default_nav_order_policy: 'After the primary page is successfully published, perform exactly one navigation order operation. If the PRD names a navigation order, publish without --auto-nav-order and then call openyida nav-group order <appType> <items...>. If the PRD only gives broad groups or is missing navigation order, use openyida publish ... --auto-nav-order and do not call nav-group order or auto-order afterward. Explicit and automatic ordering are mutually exclusive; never generate per-item move loops. The fallback priority is portal/home/workbench entry > business handling > data management > business analytics > system configuration.',
|
|
1482
|
+
final_link_policy: 'Return exactly one user-visible application entry group, never one artifact or link card per form, process, report, schema, manifest, PRD, design file, or intermediate artifact. The group always contains the app workbench URL. Add the clean custom page URL only when PRD entryMode=standalone and get-form-config readback confirms isRenderNav=false. Add the admin URL only when agent-capabilities application_entry_policy.entries.admin=include. Summarize other business resources in prose unless the user explicitly asks for technical IDs.',
|
|
1483
|
+
application_entry_policy: {
|
|
1484
|
+
delivery_unit: 'single_application_entry_group',
|
|
1485
|
+
workbench: { include: 'always', url: '{base_url}/{appType}/workbench' },
|
|
1486
|
+
custom: {
|
|
1487
|
+
include: 'when_entry_mode_standalone_and_is_render_nav_false_readback',
|
|
1488
|
+
url: '{base_url}/{appType}/custom/{formUuid}',
|
|
1489
|
+
},
|
|
1490
|
+
admin: {
|
|
1491
|
+
include: 'follow_agent_capabilities_application_entry_policy',
|
|
1492
|
+
url: '{base_url}/{appType}/admin',
|
|
1493
|
+
},
|
|
1494
|
+
internal_artifacts: 'never_user_visible',
|
|
1495
|
+
business_resources: 'summary_only',
|
|
1496
|
+
},
|
|
1497
|
+
conditional_entry_command_ids: ['update-form-config', 'get-form-config'],
|
|
1470
1498
|
required_command_ids: [
|
|
1471
1499
|
'agent-capabilities',
|
|
1472
1500
|
'create-app',
|
|
@@ -1496,7 +1524,7 @@ function summarizeLocalizedCommands(commands) {
|
|
|
1496
1524
|
'yida-data-management',
|
|
1497
1525
|
'yida-dashboard',
|
|
1498
1526
|
],
|
|
1499
|
-
completion_contract: 'Resolve context, write the yida-design
|
|
1527
|
+
completion_contract: 'Resolve context, write the shared requirement brief, complete and join the internal parallel yida-prd and yida-design artifacts, create or reuse app, core forms/processes, and primary page in PRD resource order, publish it, apply PRD navigation order or lightweight fallback navigation order, and return one named application entry group. The group contains workbench, conditionally a verified standalone custom entry, and admin only for non-cloud agents. Business resources are summarized instead of emitted as individual delivery artifacts.',
|
|
1500
1528
|
},
|
|
1501
1529
|
},
|
|
1502
1530
|
};
|
package/lib/core/locales/en.js
CHANGED
|
@@ -1849,6 +1849,8 @@ Options:
|
|
|
1849
1849
|
};
|
|
1850
1850
|
|
|
1851
1851
|
Object.assign(module.exports.query_data || (module.exports.query_data = {}), {
|
|
1852
|
+
association_derived_field_read_only: 'Derived association-form query field {0} is read-only and cannot be saved or updated. Write the source association field without the _id suffix.',
|
|
1853
|
+
association_value_invalid: 'The write value for association-form field {0} is invalid. Use an array of objects where every item includes appType, formUuid, formType, instanceId, and title.',
|
|
1852
1854
|
command_unsupported: 'Unsupported data command: {0} {1}. Run openyida commands --json to inspect the actual capability.',
|
|
1853
1855
|
delete_confirmation_required: 'Before deleting a form instance, query it, show the target summary to the user, and add --confirm only after explicit approval. No delete was performed.',
|
|
1854
1856
|
delete_preflight_failed: 'Could not read form instance {0} for delete preflight. No delete was performed.',
|
|
@@ -1857,6 +1859,7 @@ Object.assign(module.exports.query_data || (module.exports.query_data = {}), {
|
|
|
1857
1859
|
delete_result_unknown: 'The delete outcome for form instance {0} is unknown. Perform read-only inspection and do not retry the delete automatically.',
|
|
1858
1860
|
delete_target_mismatch: 'Form instance {0} does not belong to target form {1}. The operation stopped before delete.',
|
|
1859
1861
|
delete_target_unverified: 'Could not verify the identity and ownership of form instance {0}. The operation stopped before delete.',
|
|
1862
|
+
field_reference_unknown: 'Field reference {0} is not a real fieldId or component alias in the target form. The operation stopped before mutation.',
|
|
1860
1863
|
form_mode_unverified: 'Could not verify the type of form {0}. Creation stopped before any data write.',
|
|
1861
1864
|
instance_target_mismatch: 'Instance {0} does not belong to expected business resource {1}. The operation stopped before mutation.',
|
|
1862
1865
|
instance_target_unverified: 'Could not verify the business resource ownership of instance {0}. The operation stopped before mutation.',
|
package/lib/core/locales/zh.js
CHANGED
|
@@ -137,7 +137,7 @@ openyida - 宜搭命令行工具
|
|
|
137
137
|
copy [--force] 复制 project 工作目录到当前 AI 工具环境
|
|
138
138
|
login OAuth token 登录态管理
|
|
139
139
|
logout 退出登录 / 切换账号
|
|
140
|
-
create-app --name "<名称>" [--theme-file <css> 及导航布局选项]
|
|
140
|
+
create-app --name "<名称>" [--theme-file <css> 及导航布局选项] 创建应用;推荐导入应用主题文件,输出 appType
|
|
141
141
|
create-page <appType> "<页面名>" [--mode dashboard] [--hide-nav] 创建自定义页面,输出 pageId
|
|
142
142
|
update-app <appType> [--name "..."] [--hide-app-nav|--show-app-nav] 更新应用信息
|
|
143
143
|
create-form create <appType> "<表单名>" <字段JSON> [--layout <布局>] [--theme <主题>] [--label-align <对齐>] 创建表单页面
|
|
@@ -1803,6 +1803,8 @@ openyida - 宜搭命令行工具
|
|
|
1803
1803
|
};
|
|
1804
1804
|
|
|
1805
1805
|
Object.assign(module.exports.query_data || (module.exports.query_data = {}), {
|
|
1806
|
+
association_derived_field_read_only: '关联表单查询派生字段 {0} 只读,不能用于保存或更新。请写入不带 _id 后缀的原始关联表单字段。',
|
|
1807
|
+
association_value_invalid: '关联表单字段 {0} 的写入值无效。必须使用对象数组,每项完整包含 appType、formUuid、formType、instanceId 和 title。',
|
|
1806
1808
|
command_unsupported: '不支持的数据命令:{0} {1}。请使用 openyida commands --json 查询真实能力。',
|
|
1807
1809
|
delete_confirmation_required: '删除表单实例前必须先查询并向用户展示目标摘要,获得明确确认后追加 --confirm。未执行删除。',
|
|
1808
1810
|
delete_preflight_failed: '无法读取表单实例 {0} 完成删除前校验,未执行删除。',
|
|
@@ -1811,6 +1813,7 @@ Object.assign(module.exports.query_data || (module.exports.query_data = {}), {
|
|
|
1811
1813
|
delete_result_unknown: '表单实例 {0} 的删除结果未知。请只读回查目标,不要自动重试删除。',
|
|
1812
1814
|
delete_target_mismatch: '表单实例 {0} 不属于目标表单 {1},已停止且未执行删除。',
|
|
1813
1815
|
delete_target_unverified: '无法验证表单实例 {0} 的身份和归属,已停止且未执行删除。',
|
|
1816
|
+
field_reference_unknown: '字段引用 {0} 不是当前表单的真实 fieldId 或组件别名,已在写入前停止。',
|
|
1814
1817
|
form_mode_unverified: '无法验证表单 {0} 的类型,已停止创建;未执行任何数据写入。',
|
|
1815
1818
|
instance_target_mismatch: '实例 {0} 不属于期望的业务资源 {1},已在写入前停止。',
|
|
1816
1819
|
instance_target_unverified: '无法验证实例 {0} 的业务资源归属,已在写入前停止。',
|