@design-ai/cli 4.56.0 → 4.57.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/.claude-plugin/plugin.json +109 -25
- package/CHANGELOG.md +36 -0
- package/README.ko.md +9 -26
- package/README.md +10 -23
- package/cli/bin/design-ai.mjs +1 -0
- package/cli/commands/help.mjs +7 -4
- package/cli/commands/index.mjs +292 -0
- package/cli/commands/learn-help.mjs +149 -0
- package/cli/commands/learn-print-profile.mjs +373 -0
- package/cli/commands/learn-print-restore.mjs +349 -0
- package/cli/commands/learn-print-signals.mjs +453 -0
- package/cli/commands/learn.mjs +60 -1281
- package/cli/commands/pack.mjs +7 -3
- package/cli/commands/prompt.mjs +7 -3
- package/cli/commands/search.mjs +94 -1
- package/cli/lib/dispatch.mjs +3 -0
- package/cli/lib/embedding-index.mjs +199 -0
- package/cli/lib/embedding-provider.mjs +121 -0
- package/cli/lib/embedding-rerank.mjs +52 -0
- package/cli/lib/learn-args.mjs +490 -0
- package/cli/lib/learn-backup.mjs +748 -0
- package/cli/lib/learn-curation.mjs +612 -0
- package/cli/lib/learn-eval.mjs +459 -0
- package/cli/lib/learn-profile.mjs +763 -0
- package/cli/lib/learn-select.mjs +223 -0
- package/cli/lib/learn-shared.mjs +158 -0
- package/cli/lib/learn-test-support.mjs +218 -0
- package/cli/lib/learn-usage.mjs +360 -0
- package/cli/lib/learn.mjs +83 -3694
- package/cli/lib/lexical.mjs +137 -0
- package/cli/lib/local-config.mjs +110 -0
- package/cli/lib/mcp-server.mjs +13 -3
- package/cli/lib/pack.mjs +31 -4
- package/cli/lib/prompt.mjs +42 -4
- package/cli/lib/recall.mjs +193 -0
- package/cli/lib/retrieval-index.mjs +0 -0
- package/cli/lib/search-ranked.mjs +170 -0
- package/cli/lib/search.mjs +11 -1
- package/cli/lib/signals-backlog-commands.mjs +673 -0
- package/cli/lib/signals-backlog.mjs +361 -0
- package/cli/lib/signals-eval.mjs +176 -0
- package/cli/lib/signals-readiness.mjs +247 -0
- package/cli/lib/signals-registry.mjs +377 -0
- package/cli/lib/signals-render.mjs +478 -0
- package/cli/lib/signals-shared.mjs +75 -0
- package/cli/lib/signals.mjs +16 -2337
- package/cli/lib/site-bundle-handoff-expected.mjs +173 -0
- package/cli/lib/site-bundle-handoff-runbook-action-summary.mjs +332 -0
- package/cli/lib/site-bundle-handoff-runbook-human-lines.mjs +167 -0
- package/cli/lib/site-bundle-handoff-runbook-maps.mjs +238 -0
- package/cli/lib/site-bundle-handoff-runbook-next-step.mjs +278 -0
- package/cli/lib/site-bundle-handoff-runbook.mjs +44 -931
- package/cli/lib/site-test-support.mjs +68 -0
- package/cli/lib/skill-proposals-apply-commands.mjs +135 -0
- package/cli/lib/skill-proposals-apply-contract.mjs +750 -0
- package/cli/lib/skill-proposals-apply-plan.mjs +118 -0
- package/cli/lib/skill-proposals-generate.mjs +298 -0
- package/cli/lib/skill-proposals-render.mjs +532 -0
- package/cli/lib/skill-proposals-review.mjs +262 -0
- package/cli/lib/skill-proposals.mjs +15 -2046
- package/cli/lib/workspace-args.mjs +83 -0
- package/cli/lib/workspace-git.mjs +169 -0
- package/cli/lib/workspace-learning.mjs +483 -0
- package/cli/lib/workspace-repo.mjs +139 -0
- package/cli/lib/workspace-report.mjs +283 -0
- package/cli/lib/workspace-test-support.mjs +99 -0
- package/cli/lib/workspace.mjs +30 -1056
- package/docs/AI-LEARNING-PHASE2.md +215 -0
- package/docs/DISTRIBUTION.ko.md +2 -2
- package/docs/DISTRIBUTION.md +2 -2
- package/docs/NEXT-SURFACE-DECISION.md +125 -0
- package/docs/PRODUCT-READINESS.md +3 -3
- package/docs/RELEASE-GATES.ko.md +35 -0
- package/docs/RELEASE-GATES.md +234 -0
- package/docs/ROADMAP.md +53 -8
- package/docs/external-status.md +6 -8
- package/docs/inspection-20260630.md +169 -0
- package/docs/integrations/design-ai-mcp-server.md +4 -4
- package/docs/reference/ant-design.md +413 -0
- package/docs/reference/awesome-design-md.md +439 -0
- package/docs/reference/mui.md +783 -0
- package/docs/reference/shadcn-ui.md +298 -0
- package/examples/component-accordion-actions.md +5 -5
- package/examples/component-accordion-details.md +5 -5
- package/examples/component-accordion-summary.md +5 -5
- package/examples/component-accordion.md +3 -3
- package/examples/component-affix.md +1 -1
- package/examples/component-alert-dialog.md +1 -1
- package/examples/component-alert-title.md +1 -1
- package/examples/component-alert.md +3 -3
- package/examples/component-anchor.md +1 -1
- package/examples/component-app-bar.md +1 -1
- package/examples/component-aspect-ratio.md +1 -1
- package/examples/component-auto-complete.md +2 -2
- package/examples/component-avatar-group.md +5 -5
- package/examples/component-avatar.md +3 -3
- package/examples/component-back-top.md +1 -1
- package/examples/component-backdrop.md +1 -1
- package/examples/component-badge.md +3 -3
- package/examples/component-border-beam.md +3 -3
- package/examples/component-bottom-navigation.md +1 -1
- package/examples/component-box.md +1 -1
- package/examples/component-breadcrumb.md +3 -3
- package/examples/component-button-base.md +3 -3
- package/examples/component-button-group.md +2 -2
- package/examples/component-button.md +3 -3
- package/examples/component-calendar.md +2 -2
- package/examples/component-card-actions.md +1 -1
- package/examples/component-card-content.md +1 -1
- package/examples/component-card-header.md +1 -1
- package/examples/component-card-media.md +1 -1
- package/examples/component-card.md +3 -3
- package/examples/component-carousel.md +2 -2
- package/examples/component-cascader.md +1 -1
- package/examples/component-chart.md +1 -1
- package/examples/component-checkbox.md +3 -3
- package/examples/component-click-away-listener.md +1 -1
- package/examples/component-code.md +1 -1
- package/examples/component-collapsible.md +1 -1
- package/examples/component-color-picker.md +1 -1
- package/examples/component-combobox.md +1 -1
- package/examples/component-command.md +1 -1
- package/examples/component-config-provider.md +3 -3
- package/examples/component-context-menu.md +1 -1
- package/examples/component-css-baseline.md +3 -3
- package/examples/component-date-picker.md +2 -2
- package/examples/component-descriptions.md +1 -1
- package/examples/component-dialog-actions.md +1 -1
- package/examples/component-dialog-content-text.md +1 -1
- package/examples/component-dialog-content.md +1 -1
- package/examples/component-dialog-title.md +1 -1
- package/examples/component-dialog.md +2 -2
- package/examples/component-divider.md +3 -3
- package/examples/component-drawer.md +3 -3
- package/examples/component-dropdown.md +3 -3
- package/examples/component-empty.md +2 -2
- package/examples/component-fade.md +1 -1
- package/examples/component-field.md +1 -1
- package/examples/component-filled-input.md +1 -1
- package/examples/component-flex.md +1 -1
- package/examples/component-float-button.md +2 -2
- package/examples/component-form-control-label.md +1 -1
- package/examples/component-form-control.md +1 -1
- package/examples/component-form-controls.md +1 -1
- package/examples/component-form-group.md +1 -1
- package/examples/component-form-helper-text.md +1 -1
- package/examples/component-form-label.md +1 -1
- package/examples/component-form.md +2 -2
- package/examples/component-grid.md +2 -2
- package/examples/component-grow.md +1 -1
- package/examples/component-hover-card.md +1 -1
- package/examples/component-icon-button.md +1 -1
- package/examples/component-icon.md +1 -1
- package/examples/component-image-list.md +1 -1
- package/examples/component-image.md +1 -1
- package/examples/component-input-adornment.md +1 -1
- package/examples/component-input-base.md +1 -1
- package/examples/component-input-number.md +1 -1
- package/examples/component-input-otp.md +2 -2
- package/examples/component-input.md +3 -3
- package/examples/component-item.md +1 -1
- package/examples/component-label.md +1 -1
- package/examples/component-layout.md +1 -1
- package/examples/component-link.md +1 -1
- package/examples/component-list-item-avatar.md +1 -1
- package/examples/component-list-item-button.md +1 -1
- package/examples/component-list-item-icon.md +1 -1
- package/examples/component-list-item-text.md +1 -1
- package/examples/component-list-item.md +1 -1
- package/examples/component-list-subheader.md +1 -1
- package/examples/component-list.md +2 -2
- package/examples/component-masonry.md +1 -1
- package/examples/component-mentions.md +1 -1
- package/examples/component-menu-item.md +1 -1
- package/examples/component-menu-list.md +1 -1
- package/examples/component-menu.md +1 -1
- package/examples/component-menubar.md +1 -1
- package/examples/component-message.md +1 -1
- package/examples/component-mobile-stepper.md +1 -1
- package/examples/component-modal.md +5 -5
- package/examples/component-navigation-menu.md +1 -1
- package/examples/component-notification.md +1 -1
- package/examples/component-outlined-input.md +1 -1
- package/examples/component-pagination.md +3 -3
- package/examples/component-paper.md +1 -1
- package/examples/component-popconfirm.md +1 -1
- package/examples/component-popover.md +3 -3
- package/examples/component-popper.md +1 -1
- package/examples/component-progress.md +2 -2
- package/examples/component-qr-code.md +1 -1
- package/examples/component-radio.md +3 -3
- package/examples/component-rate.md +2 -2
- package/examples/component-resizable.md +1 -1
- package/examples/component-result.md +1 -1
- package/examples/component-scroll-area.md +1 -1
- package/examples/component-segmented.md +3 -3
- package/examples/component-select.md +3 -3
- package/examples/component-separator.md +1 -1
- package/examples/component-sheet.md +1 -1
- package/examples/component-sidebar.md +1 -1
- package/examples/component-skeleton.md +3 -3
- package/examples/component-slide.md +1 -1
- package/examples/component-slider.md +3 -3
- package/examples/component-snackbar-content.md +1 -1
- package/examples/component-snackbar.md +1 -1
- package/examples/component-sonner.md +1 -1
- package/examples/component-space.md +1 -1
- package/examples/component-speed-dial-action.md +1 -1
- package/examples/component-speed-dial.md +1 -1
- package/examples/component-spin.md +2 -2
- package/examples/component-spinner.md +1 -1
- package/examples/component-splitter.md +2 -2
- package/examples/component-stack.md +1 -1
- package/examples/component-statistic.md +1 -1
- package/examples/component-step-button.md +4 -4
- package/examples/component-step-connector.md +4 -4
- package/examples/component-step-content.md +1 -1
- package/examples/component-step-icon.md +1 -1
- package/examples/component-step-label.md +1 -1
- package/examples/component-step.md +2 -2
- package/examples/component-steps.md +2 -2
- package/examples/component-swipeable-drawer.md +1 -1
- package/examples/component-switch.md +3 -3
- package/examples/component-tab-scroll-button.md +5 -5
- package/examples/component-tab.md +1 -1
- package/examples/component-table-body.md +1 -1
- package/examples/component-table-cell.md +1 -1
- package/examples/component-table-container.md +1 -1
- package/examples/component-table-footer.md +1 -1
- package/examples/component-table-head.md +1 -1
- package/examples/component-table-pagination.md +1 -1
- package/examples/component-table-row.md +1 -1
- package/examples/component-table-sort-label.md +1 -1
- package/examples/component-table.md +2 -2
- package/examples/component-tabs.md +3 -3
- package/examples/component-tag-badge.md +2 -2
- package/examples/component-tag.md +1 -1
- package/examples/component-textarea-autosize.md +1 -1
- package/examples/component-textarea.md +1 -1
- package/examples/component-time-picker.md +1 -1
- package/examples/component-timeline.md +1 -1
- package/examples/component-toast.md +4 -4
- package/examples/component-toggle-button.md +1 -1
- package/examples/component-toggle.md +1 -1
- package/examples/component-toolbar.md +1 -1
- package/examples/component-tooltip.md +3 -3
- package/examples/component-tour.md +1 -1
- package/examples/component-transfer.md +1 -1
- package/examples/component-tree-select.md +1 -1
- package/examples/component-tree.md +1 -1
- package/examples/component-typography.md +2 -2
- package/examples/component-upload.md +1 -1
- package/examples/component-watermark.md +1 -1
- package/examples/component-zoom.md +1 -1
- package/knowledge/COVERAGE.md +4 -3
- package/knowledge/components/INDEX.md +202 -202
- package/knowledge/patterns/brand-references.md +72 -72
- package/package.json +1 -1
- package/tools/audit/local-ci.py +16 -1
- package/tools/audit/package-smoke.py +611 -9
- package/tools/audit/registry-smoke.py +534 -0
- package/tools/audit/release-metadata.py +47 -1
- package/tools/audit/smoke_assertions.py +915 -13
- package/tools/migrations/refs-links-to-reference-pages.py +179 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Accepted-proposal apply plan assembly for `design-ai learn --propose-skills --apply-plan`.
|
|
2
|
+
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { applyPlanFollowUpCommands } from "./skill-proposals-apply-commands.mjs";
|
|
6
|
+
import { buildApplyPlanCommandContract } from "./skill-proposals-apply-contract.mjs";
|
|
7
|
+
|
|
8
|
+
function acceptedProposalTask(proposal, index) {
|
|
9
|
+
const routes = Array.isArray(proposal.routeIds) && proposal.routeIds.length > 0
|
|
10
|
+
? proposal.routeIds
|
|
11
|
+
: ["artifact"];
|
|
12
|
+
const evidenceSources = Array.isArray(proposal.evidenceSources) ? proposal.evidenceSources : [];
|
|
13
|
+
return {
|
|
14
|
+
id: `apply-${index + 1}-${proposal.id}`,
|
|
15
|
+
proposalId: proposal.id,
|
|
16
|
+
title: proposal.title,
|
|
17
|
+
candidateSkill: proposal.candidateSkill || path.basename(path.dirname(proposal.candidateSkillPath || "")) || "unknown",
|
|
18
|
+
candidateSkillPath: proposal.candidateSkillPath || "skills/unknown/SKILL.md",
|
|
19
|
+
category: proposal.category || "workflow",
|
|
20
|
+
riskLevel: proposal.riskLevel || "medium",
|
|
21
|
+
routeIds: routes,
|
|
22
|
+
sourceIssueCount: proposal.sourceIssueCount || evidenceSources.length,
|
|
23
|
+
proposedInstructionDelta: proposal.proposedInstructionDelta || "",
|
|
24
|
+
rationale: proposal.rationale || "",
|
|
25
|
+
verificationCommand: proposal.verificationCommand || "node cli/bin/design-ai.mjs check --examples --strict --json",
|
|
26
|
+
evidenceSources,
|
|
27
|
+
reviewDecision: proposal.reviewDecision || {
|
|
28
|
+
proposalId: proposal.id,
|
|
29
|
+
status: proposal.reviewStatus || "accepted",
|
|
30
|
+
reviewedAt: "",
|
|
31
|
+
reviewer: "",
|
|
32
|
+
note: "",
|
|
33
|
+
},
|
|
34
|
+
manualSteps: [
|
|
35
|
+
`Open ${proposal.candidateSkillPath || "the candidate skill file"} and inspect the relevant checklist or playbook section.`,
|
|
36
|
+
"Merge the proposed instruction delta manually instead of pasting duplicate generated text.",
|
|
37
|
+
"Run the verification command and inspect any route-specific failures before marking the work complete.",
|
|
38
|
+
"After the skill edit and verification pass, update the review decision from `accepted` to `applied`.",
|
|
39
|
+
],
|
|
40
|
+
safetyChecklist: [
|
|
41
|
+
"Do not edit learning.json as part of this apply plan.",
|
|
42
|
+
"Do not call external AI APIs, embeddings, or fine-tuning jobs.",
|
|
43
|
+
"Keep the skill delta scoped to the repeated check-capture evidence.",
|
|
44
|
+
"Run the proposal review-check after updating the review file.",
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildSkillProposalApplyPlan(payload, {
|
|
50
|
+
generatedAt = new Date(),
|
|
51
|
+
} = {}) {
|
|
52
|
+
const generatedAtText = generatedAt instanceof Date ? generatedAt.toISOString() : String(generatedAt || "");
|
|
53
|
+
const review = payload.review || {};
|
|
54
|
+
const reviewFile = payload.reviewFile || review.file || "";
|
|
55
|
+
const acceptedProposals = Array.isArray(payload.proposals)
|
|
56
|
+
? payload.proposals.filter((proposal) => proposal.reviewStatus === "accepted")
|
|
57
|
+
: [];
|
|
58
|
+
const tasks = acceptedProposals.map(acceptedProposalTask);
|
|
59
|
+
const reviewStatus = review.status || "unknown";
|
|
60
|
+
const status = reviewStatus === "fail" || !reviewFile || review.exists === false
|
|
61
|
+
? "fail"
|
|
62
|
+
: tasks.length > 0
|
|
63
|
+
? "warn"
|
|
64
|
+
: "pass";
|
|
65
|
+
const followUpCommands = applyPlanFollowUpCommands(payload, reviewFile);
|
|
66
|
+
const commandContract = buildApplyPlanCommandContract(followUpCommands, reviewFile);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
version: 1,
|
|
70
|
+
kind: "skill-proposal-apply-plan",
|
|
71
|
+
generatedAt: generatedAtText,
|
|
72
|
+
file: payload.file,
|
|
73
|
+
usageFile: payload.usageFile,
|
|
74
|
+
signalSource: payload.signalSource,
|
|
75
|
+
reviewFile,
|
|
76
|
+
status,
|
|
77
|
+
proposalStatus: payload.status,
|
|
78
|
+
signalStatus: payload.signalStatus,
|
|
79
|
+
candidateCount: payload.candidateCount || 0,
|
|
80
|
+
proposalCount: payload.proposalCount || 0,
|
|
81
|
+
acceptedCount: tasks.length,
|
|
82
|
+
count: tasks.length,
|
|
83
|
+
pendingReviewCount: payload.pendingReviewCount || 0,
|
|
84
|
+
reviewedCount: payload.reviewedCount || 0,
|
|
85
|
+
review,
|
|
86
|
+
tasks,
|
|
87
|
+
commands: {
|
|
88
|
+
reviewCheckJson: followUpCommands.reviewCheckJson.command,
|
|
89
|
+
reviewCheckReport: followUpCommands.reviewCheckReport.command,
|
|
90
|
+
proposalPatchPreview: followUpCommands.proposalPatchPreview.command,
|
|
91
|
+
strictGate: followUpCommands.strictGate.command,
|
|
92
|
+
},
|
|
93
|
+
commandArgs: {
|
|
94
|
+
reviewCheckJson: followUpCommands.reviewCheckJson.commandArgs,
|
|
95
|
+
reviewCheckReport: followUpCommands.reviewCheckReport.commandArgs,
|
|
96
|
+
proposalPatchPreview: followUpCommands.proposalPatchPreview.commandArgs,
|
|
97
|
+
strictGate: followUpCommands.strictGate.commandArgs,
|
|
98
|
+
},
|
|
99
|
+
commandContract,
|
|
100
|
+
recommendations: tasks.length > 0
|
|
101
|
+
? [{
|
|
102
|
+
level: "warning",
|
|
103
|
+
text: "Apply accepted proposal deltas manually, then mark each reviewed decision as applied only after verification passes.",
|
|
104
|
+
}]
|
|
105
|
+
: [{
|
|
106
|
+
level: "info",
|
|
107
|
+
text: "No accepted skill proposals are ready to apply. Mark reviewed proposals as accepted before generating an apply plan.",
|
|
108
|
+
}],
|
|
109
|
+
privacy: {
|
|
110
|
+
mutatesProfile: false,
|
|
111
|
+
mutatesReviewFile: false,
|
|
112
|
+
mutatesSkillFiles: false,
|
|
113
|
+
callsExternalAiApis: false,
|
|
114
|
+
storesRawBriefText: false,
|
|
115
|
+
exposesEntryTextPreview: true,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// Skill evolution proposal generation from check-capture signals.
|
|
2
|
+
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
defaultLearningFile,
|
|
8
|
+
defaultLearningUsageFile,
|
|
9
|
+
loadLearningProfile,
|
|
10
|
+
} from "./learn.mjs";
|
|
11
|
+
import { PACKAGE_ROOT } from "./paths.mjs";
|
|
12
|
+
import { routeById } from "./route.mjs";
|
|
13
|
+
import { learningSignalRegistry } from "./signals.mjs";
|
|
14
|
+
import {
|
|
15
|
+
applySkillProposalReviewState,
|
|
16
|
+
loadSkillProposalReviewState,
|
|
17
|
+
proposalStatus,
|
|
18
|
+
skillProposalStatus,
|
|
19
|
+
} from "./skill-proposals-review.mjs";
|
|
20
|
+
|
|
21
|
+
const DEFAULT_MIN_EVIDENCE_COUNT = 2;
|
|
22
|
+
|
|
23
|
+
const CATEGORY_FALLBACK_SKILLS = {
|
|
24
|
+
accessibility: "skills/ux-audit/SKILL.md",
|
|
25
|
+
korean: "skills/design-system-builder/SKILL.md",
|
|
26
|
+
workflow: "skills/handoff-spec/SKILL.md",
|
|
27
|
+
brand: "skills/design-critique/SKILL.md",
|
|
28
|
+
constraint: "skills/handoff-spec/SKILL.md",
|
|
29
|
+
preference: "skills/design-critique/SKILL.md",
|
|
30
|
+
other: "skills/design-critique/SKILL.md",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function stableHash(value, length = 10) {
|
|
34
|
+
return createHash("sha256").update(String(value)).digest("hex").slice(0, length);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function slug(value) {
|
|
38
|
+
return String(value || "skill")
|
|
39
|
+
.toLowerCase()
|
|
40
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
41
|
+
.replace(/^-+|-+$/g, "")
|
|
42
|
+
.slice(0, 44) || "skill";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function previewText(text, maxLength = 180) {
|
|
46
|
+
const normalized = String(text || "").replace(/\s+/g, " ").trim();
|
|
47
|
+
if (normalized.length <= maxLength) return normalized;
|
|
48
|
+
return `${normalized.slice(0, maxLength - 1)}...`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function routeIdFromSource(source) {
|
|
52
|
+
const text = String(source || "").trim();
|
|
53
|
+
if (!text.startsWith("check:")) return "";
|
|
54
|
+
const routeId = text.slice("check:".length).trim();
|
|
55
|
+
if (!routeId || routeId === "artifact") return "";
|
|
56
|
+
return routeId;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function issueTitleFromText(text) {
|
|
60
|
+
const normalized = String(text || "").replace(/\s+/g, " ").trim();
|
|
61
|
+
const match = normalized.match(/^Improve future outputs by addressing\s+([^:]+):/i);
|
|
62
|
+
return match ? match[1].trim() : "";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function routeSkillPath(routeId, sourceRoot) {
|
|
66
|
+
if (!routeId) return "";
|
|
67
|
+
try {
|
|
68
|
+
const route = routeById({ routeId, sourceRoot });
|
|
69
|
+
const firstExistingSkill = (route.skills || []).find((skill) => skill.exists);
|
|
70
|
+
return firstExistingSkill?.path || route.skills?.[0]?.path || "";
|
|
71
|
+
} catch {
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function candidateSkillForEntry(entry, sourceRoot) {
|
|
77
|
+
const routeId = routeIdFromSource(entry.source);
|
|
78
|
+
const routeSkill = routeSkillPath(routeId, sourceRoot);
|
|
79
|
+
return {
|
|
80
|
+
routeId,
|
|
81
|
+
skillPath: routeSkill || CATEGORY_FALLBACK_SKILLS[entry.category] || CATEGORY_FALLBACK_SKILLS.other,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function riskForGroup(group) {
|
|
86
|
+
if (group.routeIds.size > 1) return "medium";
|
|
87
|
+
if (group.entries.length >= 5) return "medium";
|
|
88
|
+
if (group.skillPath === "skills/design-system-builder/SKILL.md") return "medium";
|
|
89
|
+
return "low";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function instructionDeltaForGroup(group) {
|
|
93
|
+
const titleText = [...group.titles].join(" ").toLowerCase();
|
|
94
|
+
if (group.category === "accessibility") {
|
|
95
|
+
return "Add a pre-handoff accessibility checkpoint: when the artifact includes UI behavior, explicitly cover keyboard reachability, visible focus state, screen-reader semantics, and WCAG 2.1 AA contrast.";
|
|
96
|
+
}
|
|
97
|
+
if (group.category === "korean") {
|
|
98
|
+
return "Add a Korean-context checkpoint: when Korean users or copy are in scope, explicitly verify Hangul typography, line-height, honorific level, local density conventions, and Korean copy risks.";
|
|
99
|
+
}
|
|
100
|
+
if (titleText.includes("responsive") || titleText.includes("mobile") || titleText.includes("viewport")) {
|
|
101
|
+
return "Add a responsive QA checkpoint: describe desktop, tablet, and mobile behavior, likely breakpoint risks, and the verification command before handoff.";
|
|
102
|
+
}
|
|
103
|
+
if (group.category === "workflow") {
|
|
104
|
+
return "Add a workflow QA checkpoint: convert recurring artifact warnings into evidence, next action, verification command, and residual risk before final handoff.";
|
|
105
|
+
}
|
|
106
|
+
if (group.category === "brand") {
|
|
107
|
+
return "Add a brand-fit checkpoint: state brand tone, visual density, copy posture, and any mismatch risk when the task includes product or marketing artifacts.";
|
|
108
|
+
}
|
|
109
|
+
if (group.category === "constraint") {
|
|
110
|
+
return "Add a guardrail checkpoint: restate operator constraints, dependency limits, and external-system boundaries before proposing implementation changes.";
|
|
111
|
+
}
|
|
112
|
+
return "Add a learned-preference checkpoint: fold repeated local feedback into the skill's final review checklist with evidence and a verification step.";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function verificationCommandForGroup(group) {
|
|
116
|
+
const routeId = [...group.routeIds].sort()[0] || "";
|
|
117
|
+
if (routeId) {
|
|
118
|
+
return `node cli/bin/design-ai.mjs check --examples --route ${routeId} --limit 1 --strict --json`;
|
|
119
|
+
}
|
|
120
|
+
return "node cli/bin/design-ai.mjs check --examples --route design-from-brief --limit 1 --strict --json";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function evidenceForEntry(entry) {
|
|
124
|
+
return {
|
|
125
|
+
kind: "check-capture",
|
|
126
|
+
entryId: entry.id,
|
|
127
|
+
category: entry.category,
|
|
128
|
+
source: entry.source || "check:artifact",
|
|
129
|
+
routeId: routeIdFromSource(entry.source) || "",
|
|
130
|
+
title: issueTitleFromText(entry.text),
|
|
131
|
+
createdAt: entry.createdAt || "",
|
|
132
|
+
textPreview: previewText(entry.text),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function groupCheckCaptureEntries(entries, sourceRoot) {
|
|
137
|
+
const groups = new Map();
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
const candidate = candidateSkillForEntry(entry, sourceRoot);
|
|
140
|
+
const key = `${candidate.skillPath}\n${entry.category || "other"}`;
|
|
141
|
+
if (!groups.has(key)) {
|
|
142
|
+
groups.set(key, {
|
|
143
|
+
key,
|
|
144
|
+
skillPath: candidate.skillPath,
|
|
145
|
+
category: entry.category || "other",
|
|
146
|
+
entries: [],
|
|
147
|
+
routeIds: new Set(),
|
|
148
|
+
sources: new Set(),
|
|
149
|
+
titles: new Set(),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const group = groups.get(key);
|
|
153
|
+
group.entries.push(entry);
|
|
154
|
+
if (candidate.routeId) group.routeIds.add(candidate.routeId);
|
|
155
|
+
if (entry.source) group.sources.add(entry.source);
|
|
156
|
+
const title = issueTitleFromText(entry.text);
|
|
157
|
+
if (title) group.titles.add(title);
|
|
158
|
+
}
|
|
159
|
+
return [...groups.values()];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function proposalFromGroup(group) {
|
|
163
|
+
const evidence = group.entries
|
|
164
|
+
.slice()
|
|
165
|
+
.sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || "")))
|
|
166
|
+
.slice(0, 5)
|
|
167
|
+
.map(evidenceForEntry);
|
|
168
|
+
const routeLabel = [...group.routeIds].sort().join(", ") || "artifact";
|
|
169
|
+
const title = `Update ${group.skillPath} for repeated ${group.category} check captures`;
|
|
170
|
+
const hashInput = `${group.skillPath}|${group.category}|${[...group.sources].sort().join(",")}|${[...group.titles].sort().join(",")}`;
|
|
171
|
+
return {
|
|
172
|
+
id: `skill-proposal-${slug(path.basename(path.dirname(group.skillPath) || group.skillPath))}-${stableHash(hashInput)}`,
|
|
173
|
+
candidateSkill: path.basename(path.dirname(group.skillPath) || group.skillPath),
|
|
174
|
+
candidateSkillPath: group.skillPath,
|
|
175
|
+
title,
|
|
176
|
+
riskLevel: riskForGroup(group),
|
|
177
|
+
category: group.category,
|
|
178
|
+
routeIds: [...group.routeIds].sort(),
|
|
179
|
+
evidenceSources: evidence,
|
|
180
|
+
sourceIssueCount: group.entries.length,
|
|
181
|
+
proposedInstructionDelta: instructionDeltaForGroup(group),
|
|
182
|
+
verificationCommand: verificationCommandForGroup(group),
|
|
183
|
+
rationale: `Repeated ${group.category} check captures were recorded for ${routeLabel}; preview this delta before editing the skill file.`,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function skippedFromGroup(group, minEvidenceCount) {
|
|
188
|
+
return {
|
|
189
|
+
candidateSkillPath: group.skillPath,
|
|
190
|
+
category: group.category,
|
|
191
|
+
sourceIssueCount: group.entries.length,
|
|
192
|
+
reason: `Needs at least ${minEvidenceCount} related check-capture entries before proposing a skill edit.`,
|
|
193
|
+
evidenceSources: group.entries
|
|
194
|
+
.slice()
|
|
195
|
+
.sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || "")))
|
|
196
|
+
.slice(0, 3)
|
|
197
|
+
.map(evidenceForEntry),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function buildSkillEvolutionProposals({
|
|
202
|
+
filePath = defaultLearningFile(),
|
|
203
|
+
usageFile = "",
|
|
204
|
+
signalSource = "",
|
|
205
|
+
root = process.cwd(),
|
|
206
|
+
sourceRoot = PACKAGE_ROOT,
|
|
207
|
+
now = new Date(),
|
|
208
|
+
minEvidenceCount = DEFAULT_MIN_EVIDENCE_COUNT,
|
|
209
|
+
reviewFile = "",
|
|
210
|
+
signalRegistryProvider = learningSignalRegistry,
|
|
211
|
+
} = {}) {
|
|
212
|
+
const resolvedFile = path.resolve(filePath);
|
|
213
|
+
const resolvedUsageFile = path.resolve(usageFile || defaultLearningUsageFile(resolvedFile));
|
|
214
|
+
const generatedAt = (now instanceof Date ? now : new Date(now)).toISOString();
|
|
215
|
+
const profile = loadLearningProfile(resolvedFile);
|
|
216
|
+
const checkEntries = (profile.entries || [])
|
|
217
|
+
.filter((entry) => String(entry.source || "").startsWith("check:"));
|
|
218
|
+
const groups = groupCheckCaptureEntries(checkEntries, sourceRoot);
|
|
219
|
+
const rawProposals = groups
|
|
220
|
+
.filter((group) => group.entries.length >= minEvidenceCount)
|
|
221
|
+
.map(proposalFromGroup)
|
|
222
|
+
.sort((a, b) => (
|
|
223
|
+
b.sourceIssueCount - a.sourceIssueCount
|
|
224
|
+
|| a.candidateSkillPath.localeCompare(b.candidateSkillPath)
|
|
225
|
+
|| a.category.localeCompare(b.category)
|
|
226
|
+
));
|
|
227
|
+
const skipped = groups
|
|
228
|
+
.filter((group) => group.entries.length < minEvidenceCount)
|
|
229
|
+
.map((group) => skippedFromGroup(group, minEvidenceCount))
|
|
230
|
+
.sort((a, b) => a.candidateSkillPath.localeCompare(b.candidateSkillPath) || a.category.localeCompare(b.category));
|
|
231
|
+
const registry = signalRegistryProvider({
|
|
232
|
+
filePath: resolvedFile,
|
|
233
|
+
usageFile: resolvedUsageFile,
|
|
234
|
+
signalSource,
|
|
235
|
+
root,
|
|
236
|
+
now,
|
|
237
|
+
});
|
|
238
|
+
const signalStatus = registry.status || "unknown";
|
|
239
|
+
const reviewState = loadSkillProposalReviewState(reviewFile);
|
|
240
|
+
const reviewed = applySkillProposalReviewState(rawProposals, reviewState);
|
|
241
|
+
const proposals = reviewed.proposals;
|
|
242
|
+
const status = reviewFile
|
|
243
|
+
? skillProposalStatus({
|
|
244
|
+
signalStatus,
|
|
245
|
+
reviewStatus: reviewed.review.status,
|
|
246
|
+
pendingReviewCount: reviewed.review.pendingCount,
|
|
247
|
+
reviewWarnings: reviewed.review.warnings.length,
|
|
248
|
+
})
|
|
249
|
+
: proposalStatus({
|
|
250
|
+
signalStatus,
|
|
251
|
+
proposalCount: proposals.length,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
version: 1,
|
|
256
|
+
generatedAt,
|
|
257
|
+
file: resolvedFile,
|
|
258
|
+
usageFile: resolvedUsageFile,
|
|
259
|
+
signalSource: registry.signalSource || (signalSource ? path.resolve(signalSource) : path.resolve(root)),
|
|
260
|
+
dryRun: true,
|
|
261
|
+
applied: false,
|
|
262
|
+
minEvidenceCount,
|
|
263
|
+
checkCaptureCount: checkEntries.length,
|
|
264
|
+
candidateCount: groups.length,
|
|
265
|
+
count: proposals.length,
|
|
266
|
+
proposalCount: proposals.length,
|
|
267
|
+
skippedCount: skipped.length,
|
|
268
|
+
pendingReviewCount: reviewed.review.pendingCount,
|
|
269
|
+
reviewedCount: reviewed.review.matchedCount,
|
|
270
|
+
reviewFile: reviewed.review.file,
|
|
271
|
+
review: reviewed.review,
|
|
272
|
+
status,
|
|
273
|
+
signalStatus,
|
|
274
|
+
proposals,
|
|
275
|
+
skipped,
|
|
276
|
+
recommendations: proposals.length === 0
|
|
277
|
+
? [{
|
|
278
|
+
level: "info",
|
|
279
|
+
text: "No repeated check-capture groups crossed the proposal threshold yet. Keep capturing explicit check --learn --yes warnings/failures before editing skills.",
|
|
280
|
+
}]
|
|
281
|
+
: reviewed.review.pendingCount === 0 && reviewFile
|
|
282
|
+
? [{
|
|
283
|
+
level: "info",
|
|
284
|
+
text: "All current skill proposals are marked applied or rejected in the review file. Re-run strict checks after any manual skill edits.",
|
|
285
|
+
}]
|
|
286
|
+
: [{
|
|
287
|
+
level: "info",
|
|
288
|
+
text: "Review proposed instruction deltas manually. Mark applied or rejected decisions in the review file to clear the strict proposal gate.",
|
|
289
|
+
}],
|
|
290
|
+
privacy: {
|
|
291
|
+
mutatesProfile: false,
|
|
292
|
+
mutatesSkillFiles: false,
|
|
293
|
+
callsExternalAiApis: false,
|
|
294
|
+
storesRawBriefText: false,
|
|
295
|
+
exposesEntryTextPreview: true,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|